stringtranslate.com

Toque

En programación informática , un thunk es una subrutina que se utiliza para inyectar un cálculo en otra subrutina. Los thunks se utilizan principalmente para retrasar un cálculo hasta que se necesite su resultado o para insertar operaciones al principio o al final de la otra subrutina. Tienen muchas otras aplicaciones en la generación de código de compilador y en la programación modular .

El término se originó como una forma irregular caprichosa del verbo think (pensar) . Se refiere al uso original de thunks en los compiladores ALGOL 60 , que requerían un análisis especial (pensamiento) para determinar qué tipo de rutina generar. [1] [2]

Fondo

Los primeros años de investigación de compiladores fueron testigos de una amplia experimentación con diferentes estrategias de evaluación . Una cuestión clave era cómo compilar una llamada a una subrutina si los argumentos pueden ser expresiones matemáticas arbitrarias en lugar de constantes. Un enfoque, conocido como " llamada por valor ", calcula todos los argumentos antes de la llamada y luego pasa los valores resultantes a la subrutina. En el enfoque rival " llamada por nombre ", la subrutina recibe la expresión del argumento no evaluado y debe evaluarla.

Una implementación simple de "llamada por nombre" podría sustituir el código de una expresión de argumento por cada aparición del parámetro correspondiente en la subrutina, pero esto puede producir múltiples versiones de la subrutina y múltiples copias del código de expresión. Como mejora, el compilador puede generar una subrutina auxiliar, llamada thunk , que calcula el valor del argumento. La dirección y el entorno [a] de esta subrutina auxiliar se pasan entonces a la subrutina original en lugar del argumento original, donde se puede llamar tantas veces como sea necesario. Peter Ingerman describió por primera vez los thunks en referencia al lenguaje de programación ALGOL 60, que admite la evaluación de llamada por nombre. [4]

Aplicaciones

Programación funcional

Aunque la industria del software estandarizó en gran medida la evaluación de llamada por valor y llamada por referencia , [5] el estudio activo de llamada por nombre continuó en la comunidad de programación funcional . Esta investigación produjo una serie de lenguajes de programación de evaluación perezosa en los que alguna variante de llamada por nombre es la estrategia de evaluación estándar. Los compiladores para estos lenguajes, como Glasgow Haskell Compiler , se han basado en gran medida en thunks, con la característica adicional de que los thunks guardan su resultado inicial para poder evitar recalcularlo; [6] esto se conoce como memorización o llamada por necesidad .

Los lenguajes de programación funcional también han permitido a los programadores generar thunks explícitamente. Esto se hace en el código fuente envolviendo una expresión de argumento en una función anónima que no tiene parámetros propios. Esto evita que la expresión se evalúe hasta que una función receptora llame a la función anónima, logrando así el mismo efecto que la llamada por nombre. [7] La ​​adopción de funciones anónimas en otros lenguajes de programación ha hecho que esta capacidad esté ampliamente disponible.

La siguiente es una demostración sencilla en JavaScript (ES6):

// 'hypot' es una función binaria const hypot = ( x , y ) => Math . sqrt ( x * x + y * y );            // 'thunk' es una función que no toma argumentos y, cuando se invoca, realiza una operación potencialmente costosa (calcular una raíz cuadrada, en este ejemplo) y/o provoca que se produzca algún efecto secundario const thunk = () => hypot ( 3 , 4 );      // el thunk puede luego pasarse sin ser evaluado... doSomethingWithThunk ( thunk );// ...o thunk evaluado (); // === 5 

Programación orientada a objetos

Los thunks son útiles en plataformas de programación orientada a objetos que permiten que una clase herede múltiples interfaces , lo que genera situaciones en las que se puede llamar al mismo método a través de cualquiera de varias interfaces. El siguiente código ilustra una situación de este tipo en C++ .

clase A { público : virtual int Access () const { valor de retorno_ ; }            privado : int valor_ ; };  clase B { público : virtual int Access () const { valor de retorno_ ; }            privado : int valor_ ; };  clase C : público A , público B { público : int Access () const override { return better_value_ ; }                 privado : int mejor_valor_ ; };  int use ( B * b ) { return b -> Acceso (); }      int main () { // ... B algún_b ; uso ( & algún_b ); C algún_c ; uso ( & algún_c ); }         

En este ejemplo, el código generado para cada una de las clases A, B y C incluirá una tabla de despacho que se puede utilizar para llamar Accessa un objeto de ese tipo, a través de una referencia que tenga el mismo tipo. La clase C tendrá una tabla de despacho adicional, que se utiliza para llamar Accessa un objeto de tipo C a través de una referencia de tipo B. La expresión b->Access()utilizará la propia tabla de despacho de B o la tabla adicional de C, dependiendo del tipo de objeto al que b se refiere. Si se refiere a un objeto de tipo C, el compilador debe asegurarse de que la Accessimplementación de C reciba una dirección de instancia para todo el objeto C, en lugar de la parte B heredada de ese objeto. [8]

Como método directo para solucionar este problema de ajuste de punteros, el compilador puede incluir un desplazamiento entero en cada entrada de la tabla de despacho. Este desplazamiento es la diferencia entre la dirección de la referencia y la dirección requerida por la implementación del método. El código generado para cada llamada a través de estas tablas de despacho debe recuperar el desplazamiento y utilizarlo para ajustar la dirección de la instancia antes de llamar al método.

La solución que se acaba de describir tiene problemas similares a la implementación ingenua de llamada por nombre descrita anteriormente: el compilador genera varias copias de código para calcular un argumento (la dirección de la instancia), al mismo tiempo que aumenta el tamaño de la tabla de despacho para contener los desplazamientos. Como alternativa, el compilador puede generar un procesador de ajuste junto con la implementación de C Accessque ajusta la dirección de la instancia en la cantidad requerida y luego llama al método. El procesador puede aparecer en la tabla de despacho de C para B, eliminando así la necesidad de que los llamadores ajusten la dirección ellos mismos. [9]

Cálculos numéricos que requieren evaluaciones en múltiples puntos

Las rutinas para cálculos como la integración necesitan calcular una expresión en varios puntos. La llamada por nombre se utilizó para este propósito en lenguajes que no admitían cierres o parámetros de procedimiento .

Interoperabilidad

Los thunks se han utilizado ampliamente para proporcionar interoperabilidad entre módulos de software cuyas rutinas no pueden llamarse entre sí directamente. Esto puede ocurrir porque las rutinas tienen diferentes convenciones de llamada , se ejecutan en diferentes modos de CPU o espacios de direcciones , o al menos una se ejecuta en una máquina virtual . Un compilador (u otra herramienta) puede resolver este problema generando un thunk que automatice los pasos adicionales necesarios para llamar a la rutina de destino, ya sea transformar argumentos, copiarlos a otra ubicación o cambiar el modo de CPU. Un thunk exitoso minimiza el trabajo adicional que debe realizar el llamador en comparación con una llamada normal.

Gran parte de la literatura sobre los procesadores de interoperabilidad se relaciona con varias plataformas Wintel , incluidas MS-DOS , OS/2 , [10] Windows [11] [12] [13] [14] y .NET , y con la transición del direccionamiento de memoria de 16 bits al de 32 bits . A medida que los clientes han migrado de una plataforma a otra, los procesadores han sido esenciales para dar soporte al software heredado escrito para las plataformas más antiguas.

La transición de código de 32 bits a código de 64 bits en x86 también utiliza una forma de thunk ( WoW64 ). Sin embargo, debido a que el espacio de direcciones x86-64 es mayor que el disponible para el código de 32 bits, el antiguo mecanismo de "thunk genérico" no se podía utilizar para llamar a código de 64 bits desde código de 32 bits. [15] El único caso de código de 32 bits que llama a código de 64 bits es en el thunk de WoW64 de las API de Windows a 32 bits.

Superposiciones y enlaces dinámicos

En sistemas que carecen de hardware de memoria virtual automática , los procesadores pueden implementar una forma limitada de memoria virtual conocida como superposiciones . Con las superposiciones, un desarrollador divide el código de un programa en segmentos que se pueden cargar y descargar de forma independiente, e identifica los puntos de entrada a cada segmento. Un segmento que llama a otro segmento debe hacerlo indirectamente a través de una tabla de ramificación . Cuando un segmento está en la memoria, sus entradas de la tabla de ramificación saltan al segmento. Cuando se descarga un segmento, sus entradas se reemplazan con "procesadores de recarga" que pueden volver a cargarlo a pedido. [16]

De manera similar, los sistemas que vinculan dinámicamente módulos de un programa entre sí en tiempo de ejecución pueden usar procesadores para conectar los módulos. Cada módulo puede llamar a los demás a través de una tabla de procesadores que el enlazador completa cuando carga el módulo. De esta manera, los módulos pueden interactuar sin conocimiento previo de dónde se encuentran ubicados en la memoria. [17]

Véase también

Tecnologías Thunk

Conceptos relacionados

Notas

  1. ^ Un thunk es un tipo de cierre limitado temprano . El entorno que se pasa para el thunk es el de la expresión, no el de la rutina llamada. [3]

Referencias

  1. ^ Eric Raymond rechaza "un par de mitos onomatopéyicos que circulan sobre el origen de este término" y cita a los inventores del thunk recordando que el término "fue acuñado después de que se dieron cuenta (en las primeras horas de la mañana tras las horas de discusión) de que el tipo de argumento en Algol-60 se podía deducir de antemano con un poco de pensamiento en tiempo de compilación [...] En otras palabras, ya se había "pensado en ello"; por lo tanto, se lo bautizó como thunk , que es "el tiempo pasado de "pensar" a las dos de la mañana". Véase: Raymond, Eric S. (1996). Raymond, Eric S. (ed.). The New Hacker's Dictionary. MIT Press. p. 445. ISBN 9780262680929. Recuperado el 25 de mayo de 2015 .
  2. ^ Véase Ingerman (1961): "El traductor sabe qué tipo de thunk crear teniendo en cuenta la formación del parámetro real y las declaraciones escaneadas previamente... [C]uando se compila una declaración de procedimiento, el traductor, nuevamente observando la sintaxis, sabe qué tipo de dirección esperar de un thunk".
  3. ^ ET Irons (1961-01-01). "Comentarios sobre la implementación de procedimientos y bloques recursivos en ALGOL". Comunicaciones de la ACM . 4 (1). Association for Computing Machinery (ACM): 65–69. doi : 10.1145/366062.366084 . ISSN  0001-0782. S2CID  14646332.
  4. ^ Ingerman, PZ (1961-01-01). "Thunks: una forma de compilar declaraciones de procedimiento con algunos comentarios sobre declaraciones de procedimiento". Comunicaciones de la ACM . 4 (1). Association for Computing Machinery (ACM): 55–58. doi : 10.1145/366062.366084 . ISSN  0001-0782. S2CID  14646332.
  5. ^ Scott, Michael (2009). Pragmática del lenguaje de programación . pág. 395.
  6. ^ Marlow, Simon (2013). Programación paralela y concurrente en Haskell . pág. 10.
  7. ^ Queinnec, Christian (2003). Lisp in Small Pieces (Lisp en pequeños fragmentos) . pág. 176.
  8. ^ Stroustrup, Bjarne (otoño de 1989). "Herencia múltiple para C++" (PDF) . Sistemas informáticos . 1 (4). USENIX . Consultado el 4 de agosto de 2014 .
  9. ^ Driesen, Karel; Hölzle, Urs (1996). "El costo directo de las llamadas a funciones virtuales en C++" (PDF) . Actas de la Conferencia SIGPLAN de la ACM de 1996 sobre sistemas, lenguajes y aplicaciones de programación orientada a objetos, OOPSLA 1996, San José, California, EE. UU ., del 6 al 10 de octubre de 1996. 11.ª OOPSLA 1996: San José, California, EE. UU. ACM . ISBN 0-89791-788-XArchivado desde el original (PDF) el 29 de diciembre de 2019. Consultado el 24 de febrero de 2011 .[ enlace muerto ]
  10. ^ Calcote, John (mayo de 1995). "Thunking: uso de bibliotecas de 16 bits en OS/2 2.0". Revista para desarrolladores de OS/2 . 7 (3): 48–56.
  11. ^ King, Adrian (1994). Inside Microsoft Windows 95 (2.ª ed.). Redmond, Washington, EE. UU.: Microsoft Press . ISBN 1-55615-626-X.
  12. ^ Guía del programador para Microsoft Windows 95: temas clave sobre programación para Windows del equipo de desarrollo de Microsoft Windows (1.ª ed.). Redmond, Washington, EE. UU.: Microsoft Press . 1 de julio de 1995. ISBN 1-55615-834-3. Recuperado el 26 de mayo de 2016 . {{cite book}}: |work=ignorado ( ayuda )
  13. ^ Hazzah, Karen (1997). Escritura de controladores de dispositivos y VxD de Windows: secretos de programación para controladores de dispositivos virtuales (segunda edición, 2.ª edición). Lawrence, Kansas, EE. UU.: R&D Books / Miller Freeman, Inc. ISBN  0-87930-438-3.
  14. ^ Kauler, Barry (agosto de 1997). Lenguaje ensamblador de Windows y programación de sistemas: programación de bajo nivel de 16 y 32 bits para PC y Windows (2.ª ed.). Lawrence, Kansas, EE. UU.: R&D Books / Miller Freeman, Inc. ISBN  0-87930-474-X.
  15. ^ "¿Por qué no se puede cambiar entre Windows de 32 y 64 bits?". The Old New Thing . 2008-10-20.
  16. ^ Bright, Walter (1 de julio de 1990). "Virtual Memory For 640K DOS". Diario del Dr. Dobb . Consultado el 6 de marzo de 2014 .
  17. ^ Levine, John R. (2000) [octubre de 1999]. Linkers and Loaders. La serie Morgan Kaufmann en ingeniería de software y programación (1.ª edición). San Francisco, EE. UU.: Morgan Kaufmann . ISBN 1-55860-496-0. OCLC  42413382. Archivado desde el original el 5 de diciembre de 2012. Consultado el 12 de enero de 2020 .Código: [1][2] Erratas: [3]