¿Por qué Raku funciona tan mal con matrices multidimensionales?

10

Tengo curiosidad por qué Raku realiza tan mala manipulación de matrices multidimensionales. Hice una prueba rápida inicializando una matriz de 2 dimensiones en Python, C # y Raku y el tiempo transcurrido es sorprendentemente alto para la posterior.

Para Raku

my @grid[4000;4000] = [[0 xx 4000] xx 4000];
# Elapsed time 42 seconds !!

Para Python

table= [ [ 0 for i in range(4000) ] for j in range(4000) ]
# Elapsed time 0.51 seconds

C#

int [,]matrix = new int[4000,4000];
//Just for mimic same behaviour
for(int i=0;i<4000;i++)
   for(int j=0;j<4000;j++)
       matrix[i,j] = 0;
# Elapsed time 0.096 seconds

Estoy haciendo mal? Parece demasiada diferencia.

Javi
fuente
55
Solo es lento para las matrices multidimensionales conformadas (por ejemplo, una en la que lo define @grid[4000;4000]) el código de Python no está utilizando una matriz conformada y si intenta lo mismo en Raku obtendrá un tiempo mucho mejor: my @grid = [[0 xx 4000] xx 4000]; significa que debe acceder con @grid[0][0]no @grid[0;0]. Creo que esto se debe principalmente a que las matrices conformadas siguen siendo un trabajo en progreso.
Scimon Proctor
1
En mi máquina @grid[1000;1000] = [[0 xx 1000]xx1000]tomó 12 segundos. @grid = [[0 xx 1000]xx1000]tomó 0.6 así que ... sí. Evitaría matrices conformadas.
Scimon Proctor
55
@Scimon aún puede usar el descriptor de acceso [;] para matrices sin forma. my @grid = [[$++ xx 100] xx 100]; say @grid[0;1]; say @grid[1;1]devuelve 1 y 101 respectivamente
user0721090601
¡Increíble! Eso facilita las cosas.
Scimon Proctor
2
Las matrices multidimensionales en forma no han recibido la bondad de optimización que muchas otras áreas de Rakudo han recibido.
Elizabeth Mattijsen

Respuestas:

13

Una comparación directa inicial

Comenzaré con un código que esté mucho más alineado con su código de Python que con su propia traducción. Creo que el código Raku que es más directamente equivalente a tu Python es:

my \table = [ [ 0 for ^4000 ] for ^4000 ];
say table[3999;3999]; # 0

Este código declara un identificador sin sigilo 1 . Eso:

  • Gotas "formando" (el [4000;4000]en my @table[4000;4000]) Lo dejé caer porque su código de Python no lo está haciendo. La configuración confiere ventajas pero tiene implicaciones de rendimiento. 2

  • Utiliza el enlace en lugar de la asignación . Cambié a enlace porque su código de Python está haciendo enlace, no asignación. (Python no distingue entre los dos.) Si bien el enfoque de asignación de Raku ofrece ventajas fundamentales que vale la pena tener para el código general, tiene implicaciones de rendimiento. 3


Este código con el que comencé mi respuesta todavía es lento.

Primero, el código Raku, ejecutado a través de un compilador Rakudo desde diciembre de 2018, es aproximadamente 5 veces más lento que su código Python, utilizando un intérprete de Python desde junio de 2019, en el mismo hardware. 3

En segundo lugar, tanto el código Raku como el código Python son lentos, por ejemplo, en comparación con su código C #. Podemos hacerlo mejor...

Una alternativa idiomática que es mil veces más rápida.

Vale la pena considerar el siguiente código:

my \table = [ [ 0 xx Inf ] xx Inf ];
say table[ 100_000; 100_000 ]; # 0

A pesar de que este código corresponde a una matriz teórica de 10 mil millones de elementos en lugar del mero elemento de 16 millones en su código Python y C #, el tiempo de reloj de pared para ejecutarlo es menos de la mitad que el código Python, y solo 5 veces más lento que el C # código. Eso sugiere que Rakudo está ejecutando el código Raku más de mil veces más rápido que el código Python equivalente, y cien veces más rápido que el código C #.

El código Raku parece ser mucho más rápido porque la tabla se está inicializando perezosamente mediante el uso xx Inf. 4 El único trabajo significativo se realiza al ejecutar la saylínea. Esto provoca la creación de 100,000 matrices de primera dimensión, y luego rellena solo la matriz de la segunda dimensión 100,000 con 100,000 elementos, para que saypueda mostrar el 0contenido en el último elemento de esa matriz.

Hay más de una forma de hacerlo

Un problema subyacente a su pregunta es que siempre hay más de una forma de hacerlo. 5 Si encuentra un bajo rendimiento para el código donde la velocidad es crítica, codificarlo de manera diferente, como lo he hecho, puede hacer una gran diferencia. 6 6

(Otra muy buena opción es hacer una pregunta SO ...)

El futuro

Raku es cuidadosamente diseñado para ser altamente optimiz poder , es decir, capaz de un solo días de ejecución mucho más rápida dada trabajo compilador suficiente en los próximos años , que, por ejemplo, Perl o Python 5 3 puede, en teoría, nunca correr, a menos que vayan a través de un subterráneas rediseño y años de trabajo de compilación correspondiente.

Una analogía algo correcta es lo que sucedió con el rendimiento de Java en los últimos 25 años. Rakudo / NQP / MoarVM están a la mitad del proceso de maduración por el que ha pasado la pila Java.

Notas al pie

1 Podría haber escrito my $table := .... Pero las declaraciones de la forma my \foo ...eliminan la consideración de los sigilos y permiten el uso en =lugar de :=lo que se requeriría con un identificador sigiliado. (Como beneficio adicional, "recortar el sigilo" da como resultado un identificador sin sigilo, familiar para los codificadores en los muchos idiomas que no usan sigilos, que por supuesto incluye Python y C #).

2 La configuración algún día puede dar como resultado operaciones de matriz más rápidas para algún código. Mientras tanto, como se menciona en los comentarios sobre su pregunta, claramente actualmente hace lo contrario, ralentizándolo significativamente. Me imagino que eso se debe en gran parte a que cada acceso a la matriz se está verificando dinámicamente de forma ingenua en este momento, lentamente todo, y tampoco ha habido ningún esfuerzo para usar el tamaño fijo para ayudar a acelerar las cosas. Además, cuando traté de encontrar una solución rápida para su código, no pude encontrar uno usando la matriz de tamaño fijo debido a que muchas operaciones en matrices de tamaño fijo no están implementadas actualmente. Una vez más, es de esperar que estos se implementen algún día, pero presumiblemente no han sido un punto de dolor suficiente para que nadie pueda trabajar en implementarlos hasta la fecha.

3 Al momento de escribir esto, TIO está utilizando Python 3.7.4, de junio de 2019, y Rakudo v2018.12, de diciembre de 2018. El rendimiento de Rakudo está mejorando con el tiempo significativamente más rápido que el intérprete oficial de Python 3, por lo que lo haría esperar que la brecha entre el último Rakudo y el último Python, cuando Rakudo es más lento, sea significativamente más estrecha de lo que se indica en esta respuesta. En particular, el trabajo actual está mejorando significativamente el rendimiento de las tareas.

El valor xx predeterminado es 4 para el procesamiento diferido, pero algunas expresiones fuerzan una evaluación ansiosa debido a la semántica del lenguaje o las limitaciones actuales del compilador. En el año de edad v2018.12 Rakudo, para una expresión de la forma [ [ foo xx bar ] xx baz ]de permanecer perezoso y no ser obligado a evaluar con entusiasmo, tanto bar y bazdebe ser Inf. Por el contrario, my \table = [0 xx 100_000 for ^100_000]es vago sin uso de Inf. (El último código en realidad está almacenando 100,000 Seqs en la primera dimensión en lugar de 100,000 Arrays - se say WHAT table[0]muestra en Seqlugar de Array- pero la mayoría del código no podrá detectar la diferencia - say table[99_999;99_999]todavía se mostrará 0).

5 Algunas personas piensan que es una debilidad aceptar que hay más de una forma de pensar y codificar soluciones a problemas dados. En realidad es una fortaleza en al menos tres saludos. Primero, en general, cualquier problema no trivial puede resolverse mediante muchos algoritmos distintos con diferencias dramáticas en el perfil de rendimiento. Esta respuesta incluye un enfoque ya disponible con un Rakudo de un año que será más de mil veces más rápido que Python en la práctica en algunos escenarios. En segundo lugar, un lenguaje deliberadamente flexible y multi-paradigma como Raku permite un codificador (o equipo de codificadores) para expresar una solución que se consideran elegante y fácil de mantener, o que simplemente hace el trabajo, en base a lo quepensar es lo mejor, no lo que impone el lenguaje. En tercer lugar, el rendimiento de Rakudo como compilador optimizador es actualmente notablemente variable. Afortunadamente, tiene un gran generador de perfiles 6 , por lo que uno puede ver dónde hay un cuello de botella y una gran flexibilidad, por lo que puede probar una codificación alternativa y esto puede producir un código mucho más rápido.

6 Cuando el rendimiento es importante, o si está investigando problemas de rendimiento, consulte la página de Raku doc ​​sobre rendimiento ; la página cubre una variedad de opciones, incluido el uso del Rakudo profiler.

raiph
fuente