En programación de computadoras , un procesador es una subrutina que se usa para inyectar un cálculo adicional 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 de compilador y programación modular .
El término se originó como un participio pasado humorístico e incorrecto de "pensar". Es decir, un "valor de procesador" pasa a estar disponible después de que su rutina de cálculo sea pensada o ejecutada. [1]
Fondo
Los primeros años de la investigación de compiladores vieron una amplia experimentación con diferentes estrategias de evaluación . Una pregunta clave fue cómo compilar una llamada a 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 de " llamada por nombre ", la subrutina recibe la expresión de argumento no evaluada 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 se pasan luego 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 llamadas por nombre. [3]
Aplicaciones
Programación funcional
Aunque la industria del software se estandarizó en gran medida en la evaluación de llamada por valor y llamada por referencia , [4] 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 perezosa 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 , se han basado en gran medida en los procesadores, con la característica añadida de que los procesadores guardan su resultado inicial para evitar volver a calcularlo; [5] esto se conoce como memorización o llamada por necesidad .
Los lenguajes de programación funcional 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 sea evaluada hasta que una función receptora llame a la función anónima, logrando así el mismo efecto que la llamada por nombre. [6] 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 simple 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 (calculando una raíz cuadrada, en este ejemplo) y / o causa algún efecto secundario const thunk = () = > hipot ( 3 , 4 );// el golpe seco y luego se puede pasar por ahí sin ser evaluado ... doSomethingWithThunk ( golpe seco );// ... o thunk evaluado (); // === 5
Programación orientada a objetos
Thunks son útiles en la programación orientada a objetos plataformas que permiten una clase de múltiples interfaces heredan , lo que lleva a situaciones en las que el mismo método que se podría llamar 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 acceso () const { retorno VALUE_ ; } privado : int valor_ ; };clase B { público : Virtual int acceso () const { retorno VALUE_ ; } privado : int valor_ ; };clase C : público A , público B { público : int Access () const override { return better_value_ ; } privado : int valor_mejor_ ; };int use ( B * b ) { return b -> Access (); }int main () { // ... B some_b ; use ( & some_b ); C some_c ; use ( & 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 Access
a 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 Access
a 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 b se refiere. Si se refiere a un objeto de tipo C, el compilador debe asegurarse de que la Access
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. [7]
Como un enfoque directo a este problema de ajuste de 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 recuperar el desplazamiento y usarlo 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 la llamada por nombre descrita anteriormente: el compilador genera varias copias de código para calcular un argumento (la dirección de la instancia), al tiempo que aumenta los tamaños de la tabla de despacho para contener las compensaciones. Como alternativa, el compilador puede generar un procesador de ajuste junto con la implementación de C Access
que 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 llamantes ajusten la dirección ellos mismos. [8]
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 llamadas , 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 automatiza 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 la CPU. Un procesador exitoso minimiza el trabajo adicional que debe hacer 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 , [9] Windows [10] [11] [12] [13] y .NET , y con la transición de 16 bits al direccionamiento de memoria de 32 bits . A medida que los clientes han migrado 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 al código de 64 bits en x86 también utiliza una forma de procesamiento (WoW64). Sin embargo, debido a que el espacio de direcciones x86-64 es más grande que el disponible para el código de 32 bits, el antiguo mecanismo de "procesador genérico" no se pudo utilizar para llamar al código de 64 bits desde el código de 32 bits. [14] El único caso de código de 32 bits que llama al código de 64 bits está en el procesamiento de WoW64 de las API de Windows a 32 bits.
Superposiciones y enlaces dinámicos
En los sistemas que carecen de hardware de memoria virtual automática , los procesadores pueden implementar una forma limitada de memoria virtual conocida como superposiciones . Con 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 ramas . 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 "recargar thunks" que pueden recargarlo a pedido. [15]
De manera similar, los sistemas que enlazan dinámicamente módulos de un programa juntos 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 forma los módulos pueden interactuar sin conocimiento previo de dónde se encuentran ubicados en la memoria. [dieciséis]
Ver también
Tecnologías Thunk
- Interfaz de modo protegido de DOS (DPMI)
- Servicios de modo protegido de DOS (DPMS)
- J / Directo
- Capa de Microsoft para Unicode
- Servicios de invocación de plataforma
- Win32s
- Windows en Windows
- WoW64
- libffi
Conceptos relacionados
- Función anónima
- Futuros y promesas
- Llamada a procedimiento remoto
- Calce (computación)
- Trampolín (informática)
- Expresión reducible
Notas
- ^ Un thunk 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. [2]
Referencias
- ↑ 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 discusión) que el tipo de un El argumento en Algol-60 se pudo resolver de antemano con un poco de pensamiento en tiempo de compilación [...] En otras palabras, ya se había 'pensado'; por lo tanto, se bautizó como un 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 .
- ^ ET Irons (1 de enero de 1961). "Comentarios sobre la Implementación de Procedimientos Recursivos y Bloques 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 .
- ^ Ingerman, PZ (1 de enero de 1961). "Thunks: una forma de compilar declaraciones de procedimiento con algunos comentarios sobre declaraciones de procedimiento". Comunicaciones de la ACM . Asociación de Maquinaria de Computación (ACM). 4 (1): 55–58. doi : 10.1145 / 366062.366084 . ISSN 0001-0782 .
- ^ Scott, Michael (2009). Pragmática del lenguaje de programación . pag. 395.
- ^ Marlow, Simon (2013). Programación paralela y concurrente en Haskell . pag. 10.
- ^ Queinnec, Christian (2003). Lisp en pedazos pequeños . pag. 176.
- ^ Stroustrup, Bjarne (otoño de 1989). "Herencia múltiple para C ++" (PDF) . Sistemas informáticos . USENIX . 1 (4) . Consultado el 4 de agosto de 2014 .
- ^ Driesen, Karel; Hölzle, Urs (1996). "El costo directo de las llamadas a funciones virtuales en C ++" (PDF) . OOPSLA . Consultado el 24 de febrero de 2011 . Cite journal requiere
|journal=
( ayuda ) - ^ Calcote, John (mayo de 1995). "Thunking: uso de bibliotecas de 16 bits en OS / 2 2.0" . Revista para desarrolladores OS / 2 . 7 (3).
- ^ King, Adrian (1994). Dentro de Microsoft Windows 95 (2ª ed.). Redmond, Washington, Estados Unidos: Microsoft Press . ISBN 1-55615-626-X.
- ^ Guía del programador de Microsoft Windows 95: Temas clave sobre programación para Windows del Equipo de desarrollo de Microsoft Windows . Referencia técnica (1ª ed.). Redmond, Washington, Estados Unidos: Microsoft Press . 1995-07-01. ISBN 1-55615-834-3. Consultado el 26 de mayo de 2016 .
- ^ Hazzah, Karen (1997). Escritura de controladores de dispositivos y VxD de Windows: secretos de programación para controladores de dispositivos virtuales (segunda impresión, segunda edición). Lawrence, Kansas, EE. UU .: I + D Books / Miller Freeman, Inc. ISBN 0-87930-438-3.
- ^ 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 .: I + D Books / Miller Freeman, Inc. ISBN 0-87930-474-X.
- ^ "¿Por qué no puedes conectar entre Windows de 32 y 64 bits?" . Lo viejo y nuevo . 2008-10-20.
- ^ Bright, Walter (1 de julio de 1990). "Memoria virtual para DOS 640K" . Diario del Dr. Dobb . Consultado el 6 de marzo de 2014 .
- ^ Levine, John R. (2000) [octubre de 1999]. Enlazadores y cargadores . La Serie Morgan Kaufmann en Ingeniería de Software y Programación (1 ed.). San Francisco, Estados Unidos: Morgan Kaufmann . ISBN 1-55860-496-0. OCLC 42413382 . ISBN 978-1-55860-496-4 . Archivado desde el original el 5 de diciembre de 2012 . Consultado el 12 de enero de 2020 .Código: [1] [2] Errata: [3]