stringtranslate.com

Tonk

En programación de computadoras , un procesador es una subrutina que se utiliza para inyectar un cálculo en otra subrutina. Los procesadores 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 compilador y programación modular .

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

Fondo

Los primeros años de la investigación de los compiladores vieron una amplia experimentación con diferentes estrategias de evaluación . Una pregunta clave fue 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 " llamar por nombre ", la subrutina recibe la expresión del argumento no evaluado y debe evaluarla.

Una implementación simple de "llamar 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 procesador , que calcula el valor del argumento. La dirección y el entorno [a] de esta subrutina auxiliar luego se pasan 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 procesadores en referencia al lenguaje de programación ALGOL 60, que admite la evaluación de llamadas 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 la 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 perezosos en los que alguna variante de llamada por nombre es la estrategia de evaluación estándar. Los compiladores de estos lenguajes, como el Glasgow Haskell Compiler , han dependido en gran medida de los procesadores, con la característica adicional de que los procesadores guardan su resultado inicial para evitar volver a calcularlo; [6] esto se conoce como memorización o llamada por necesidad .

Los lenguajes de programación funcionales también han permitido a los programadores generar procesadores 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 hipot = ( x , y ) => Math . raíz cuadrada ( 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 causa algún efecto secundario const thunk = () = > hipot ( 3 , 4 );      // el procesador puede pasarse sin ser evaluado... doSomethingWithThunk ( thunk );// ...o procesador evaluado (); // === 5 

Programación orientada a objetos

Los procesadores son útiles en plataformas de programación orientada a objetos que permiten que una clase herede múltiples interfaces , lo que lleva a 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ública A , pública B { pública : int Access () const override { return mejor_valor_ ; }                 privado : int mejor_valor_ ; };  int uso ( B * b ) { return b -> Acceso (); }      int main () { // ... B some_b ; uso ( & some_b ); Calgo_c ; _ uso ( & some_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 usar para llamar Accessa un objeto de ese tipo, a través de una referencia que tiene el mismo tipo. La clase C tendrá una tabla de despacho adicional, utilizada para llamar Accessa un objeto de tipo C a través de una referencia de tipo B. La expresión b->Access()usará la tabla de despacho propia de B o la tabla C adicional, dependiendo del tipo de objeto al que se refiere b. Si se refiere a un objeto de tipo C, el compilador debe asegurarse de que Accessla implementació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 enfoque directo a este problema de ajuste del puntero, el compilador puede incluir un desplazamiento de número entero en cada entrada de la tabla de distribución. 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 luego recuperar el desplazamiento y usarlo para ajustar la dirección de la instancia antes de llamar al método.

La solución que acabamos 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 instancia), al mismo tiempo que aumenta el tamaño de la tabla de despacho para contener las compensaciones. Como alternativa, el compilador puede generar un procesador ajustador 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 las personas que llaman 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 múltiples puntos. La llamada por nombre se utilizó para este propósito en idiomas que no admitían cierres o parámetros de procedimiento .

Interoperabilidad

Los procesadores 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 procesador que automatice los pasos adicionales necesarios para llamar a la rutina de destino, ya sea transformando argumentos, copiándolos en otra ubicación o cambiando el modo de CPU. Un procesador exitoso minimiza el trabajo adicional que debe realizar la persona que llama en comparación con una llamada normal.

Gran parte de la literatura sobre 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 de 16 bits. a direccionamiento de memoria de 32 bits . A medida que los clientes migraron de una plataforma a otra, los procesadores han sido esenciales para admitir el software heredado escrito para las plataformas más antiguas.

La transición del código de 32 bits a 64 bits en x86 también utiliza una forma de procesador ( 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 "procesador 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 el procesamiento de las API de Windows por parte de WoW64 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 en cada segmento. Un segmento que llama a otro segmento debe hacerlo indirectamente a través de una tabla de rama . Cuando un segmento está en la memoria, sus entradas de la tabla de ramas saltan al segmento. Cuando se descarga un segmento, sus entradas se reemplazan con "thunks de recarga" que pueden recargarlo a pedido. [dieciséis]

De manera similar, los sistemas que vinculan dinámicamente módulos de un programa 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 vinculador completa cuando carga el módulo. De esta manera los módulos pueden interactuar sin conocimiento previo de dónde están ubicados en la memoria. [17]

Ver también

Tecnologías Thunk

Conceptos relacionados

Notas

  1. ^ Un golpe seco es un tipo de cierre temprano limitado . El entorno pasado para el procesador 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 golpe recordando que el término "fue acuñado después de que se dieron cuenta (en las primeras horas de la madrugada tras horas de discusión) que el tipo de El argumento en Algol-60 se podía descifrar de antemano con un poco de pensamiento en tiempo de compilación [...] En otras palabras, "ya había sido pensado"; por eso fue bautizado como thunk , que es "el tiempo pasado de "pensar" a las dos de la mañana". Ver: Raymond, Eric S. (1996). Raymond, Eric S. (ed.). The New Hacker's Dictionary. MIT Press. P. 445. ISBN 9780262680929. Consultado el 25 de mayo de 2015 .
  2. ^ Véase Ingerman (1961): "El traductor sabe qué tipo de procesador crear considerando 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 procesador".
  3. ^ Hierros ET (1 de enero de 1961). "Comentarios sobre la Implementación de Procedimientos y Bloques Recursivos en ALGOL". Comunicaciones de la ACM . Asociación de Maquinaria de Computación (ACM). 4 (1): 65–69. doi : 10.1145/366062.366084 . ISSN  0001-0782. S2CID  14646332.
  4. ^ Ingerman, PZ (1 de enero de 1961). "Thunks: una forma de compilar declaraciones de procedimientos con algunos comentarios sobre las declaraciones de procedimientos". Comunicaciones de la ACM . Asociación de Maquinaria de Computación (ACM). 4 (1): 55–58. doi : 10.1145/366062.366084 . ISSN  0001-0782. S2CID  14646332.
  5. ^ Scott, Michael (2009). Pragmática del lenguaje de programación . pag. 395.
  6. ^ Marlow, Simón (2013). Programación Paralela y Concurrente en Haskell . pag. 10.
  7. ^ Queennec, cristiano (2003). Ceceo en trozos pequeños . pag. 176.
  8. ^ Stroustrup, Bjarne (otoño de 1989). "Herencia múltiple para C++" (PDF) . Sistemas Computacionales . USENIX . 1 (4) . 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 ACM SIGPLAN de 1996 sobre sistemas, lenguajes y aplicaciones de programación orientada a objetos, OOPSLA 1996, San José, California, EE. UU., 6 al 10 de octubre de 1996 . 11° OOPSLA 1996: San José, California, Estados Unidos. ACM . ISBN 0-89791-788-X. Archivado 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. ^ Rey, Adrián (1994). Dentro de Microsoft Windows 95 (2ª ed.). Redmond, Washington, Estados Unidos: Microsoft Press . ISBN 1-55615-626-X.
  12. ^ Guía del programador de Microsoft Windows 95: temas clave sobre programación para Windows del equipo de desarrollo de Microsoft Windows (1ª ed.). Redmond, Washington, Estados Unidos: Microsoft Press . 1995-07-01. ISBN 1-55615-834-3. Consultado 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 impresión, 2ª ed.). Lawrence, Kansas, EE. UU.: Libros de investigación y desarrollo / Miller Freeman, Inc. ISBN  0-87930-438-3.
  14. ^ Kauler, Barry (agosto de 1997). Programación de sistemas y lenguaje ensamblador de Windows: programación de bajo nivel de 16 y 32 bits para PC y Windows (2ª ed.). Lawrence, Kansas, EE. UU.: Libros de investigación y desarrollo / Miller Freeman, Inc. ISBN  0-87930-474-X.
  15. ^ "¿Por qué no puedes pensar entre Windows de 32 y 64 bits?". Lo viejo y nuevo . 2008-10-20.
  16. ^ Brillante, Walter (1 de julio de 1990). "Memoria virtual para DOS de 640K". Diario del Dr. Dobb . Consultado el 6 de marzo de 2014 .
  17. ^ Levine, John R. (2000) [octubre de 1999]. Enlazadores y cargadores. La serie Morgan Kaufmann sobre programación e ingeniería de software (1 ed.). San Francisco, Estados Unidos: 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] Fe de erratas: [3]