λ-cálculo: ¿Cuál es la representación de funciones más eficiente en memoria?


9

Me gustaría comparar el rendimiento de las estructuras de datos codificadas por funciones (Church / Scott's) con las codificadas clásicamente (ensamblador / C).

Pero antes de hacerlo, necesito saber qué tan eficiente es / puede ser la representación de funciones en la memoria. La función puede, por supuesto, aplicarse parcialmente (también conocida como cierre).

Estoy interesado tanto en el algoritmo de codificación actual que usan los lenguajes funcionales populares (Haskell, ML) como en el más eficiente que se puede lograr.


Punto de bonificación: ¿Existe tal codificación que asigne los enteros codificados de función a los enteros nativos ( short, intetc. en C). ¿Es posible?


Valoro la eficiencia basada en el rendimiento. En otras palabras, cuanto más eficiente es la codificación, menos influye en el rendimiento de la computación con estructuras de datos funcionales.


Todos mis intentos de Google fallaron, tal vez no conozco las palabras clave correctas.
Ford O.

¿Puedes editar la pregunta para aclarar lo que quieres decir con "eficiente"? ¿Eficiente para qué? Cuando solicita una estructura de datos eficiente, debe especificar qué operaciones desea realizar en la estructura de datos, ya que eso afecta la elección de la estructura de datos. ¿O quiere decir que la codificación es tan eficiente como sea posible?
DW

1
Esto es bastante amplio. Hay muchas máquinas abstractas para el cálculo lambda que tienen como objetivo ejecutarlo de manera eficiente (ver, por ejemplo, SECD, CAM, Krivine's, STG). Además de eso, debe considerar los datos codificados de Church / Scott, lo que plantea más problemas. Por ejemplo, en las listas codificadas de Church, la operación de cola debe ser O (n) en lugar de O (1). Creo que leí en alguna parte que la existencia de una codificación para listas en el Sistema F con operaciones de cabeza y cola O (1) seguía siendo un problema abierto.
chi

@DW Estoy hablando de rendimiento / gastos generales. Por ejemplo, con un mapeo de codificación eficiente sobre la lista de la iglesia y la lista de Haskell debería tomar el mismo tiempo.
Ford O.

¿Rendimiento para qué operaciones? ¿Qué quieres hacer con las funciones? ¿Desea evaluar estas funciones en algún valor? Una vez, o evaluar la misma función en muchos valores? ¿Hacer algo más con ellos? ¿Está preguntando cómo compilar una función (escrita en un lenguaje funcional) para que pueda ejecutarse de la manera más eficiente posible?
DW

Respuestas:


11

La cuestión es que realmente no hay mucho margen de maniobra en términos de codificación de funciones. Estas son las principales opciones:

  • Reescritura de términos: almacena funciones como sus árboles de sintaxis abstracta (o alguna codificación de la misma. Cuando llama a una función, atraviesa manualmente el árbol de sintaxis para reemplazar sus parámetros con el argumento. Esto es fácil, pero terriblemente ineficiente en términos de tiempo y espacio .

  • Cierres: tiene alguna forma de representar una función, tal vez un árbol de sintaxis, más probablemente un código de máquina. Y en estas funciones, se refiere a sus argumentos por referencia de alguna manera. Podría ser un desplazamiento de puntero, podría ser un número entero o un índice De Bruijn, podría ser un nombre. Luego representa una función como un cierre : las "instrucciones" de la función (árbol, código, etc.) emparejadas con una estructura de datos que contiene todas las variables libres de la función. Cuando una función se aplica realmente, de alguna manera sabe cómo buscar las variables libres en su estructura de datos, utilizando entornos, aritmética de punteros, etc.

Estoy seguro de que hay otras opciones, pero estas son las básicas, y sospecho que casi cualquier otra opción será una variante u optimización de la estructura de cierre básica.

Por lo tanto, en términos de rendimiento, los cierres funcionan casi universalmente mejor que la reescritura de términos. De las variaciones, ¿cuál es mejor? Eso depende en gran medida de su lenguaje y arquitectura, pero sospecho que el "código de máquina con una estructura que contiene vars libres" es el más eficiente. Tiene todo lo que la función necesita (instrucciones y valores) y nada más, y la llamada no termina haciendo recorridos a largo plazo.

Estoy interesado tanto en el uso del algoritmo de codificación actual de los lenguajes funcionales populares (Haskell, ML)

No soy un experto, pero soy el 99% de la mayoría de los sabores de ML que usan alguna variación de los cierres que describo, aunque con algunas optimizaciones probables. Vea esto para una perspectiva (posiblemente desactualizada).

Haskell hace algo un poco más complicado debido a la evaluación perezosa: utiliza la reescritura de gráficos sin etiquetas de Spineless .

y también en el más eficiente que se pueda lograr.

¿Qué es lo más eficiente? No hay una implementación que sea más eficiente en todas las entradas, por lo que obtendrá implementaciones que son eficientes en promedio, pero cada una sobresaldrá en diferentes escenarios. Por lo tanto, no hay una clasificación definitiva de los más o menos eficientes.

No hay magia aquí. Para almacenar una función, necesita almacenar sus valores libres de alguna manera, de lo contrario está codificando menos información que la función en sí. Tal vez pueda optimizar algunos de los valores libres con una evaluación parcial pero eso es arriesgado para el rendimiento, y debe tener cuidado para asegurarse de que esto siempre se detenga.

Y, tal vez pueda usar algún tipo de compresión o algoritmo inteligente para ganar eficiencia en el espacio. Pero entonces, o bien está intercambiando tiempo por espacio, o se encuentra en una situación en la que se optimizó para algunos casos y disminuyó la velocidad para otros.

Se puede optimizar para el caso común, pero lo que el caso común es puede cambiar el idioma, área de aplicación, etc. El tipo de código que es rápido para un videojuego (cálculo de números, lazos estrechos con gran entrada) es probablemente diferente de qué es rápido para un compilador (recorridos en árbol, listas de trabajo, etc.).

Punto de bonificación: ¿Existe tal codificación que asigne los enteros codificados de funciones a enteros nativos (short, int, etc. en C). ¿Es posible?

No, esto no es posible. El problema es que el cálculo lambda no te permite introspectar términos. Cuando una función toma un argumento con el mismo tipo que un número de Iglesia, necesita poder llamarlo, sin examinar la definición exacta de ese número. Eso es lo que sucede con las codificaciones de la Iglesia: lo único que puedes hacer con ellas es llamarlas, y puedes simular todo lo útil con esto, pero no sin costo.

Más importante aún, los enteros ocupan cada codificación binaria posible. Entonces, si las lambdas se representaran como sus enteros, ¡no tendrías forma de representar lambdas no eclesiásticas! O bien, introduciría una bandera para indicar si una lambda es un número o no, pero entonces cualquier eficiencia que desee probablemente se haya ido por la ventana.

EDITAR: desde que escribí esto, me he dado cuenta de una tercera opción para implementar funciones de orden superior: la desfuncionalización . Aquí, cada llamada a la función se convierte en una gran switchdeclaración, dependiendo de qué abstracción lambda se haya dado como función. La desventaja aquí es que es una transformación completa del programa: no puede compilar partes por separado y luego vincularlas de esta manera, ya que necesita tener el conjunto completo de abstracciones lambda con anticipación.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.