arrays - len - ruby select
El empuje de la matriz#causa error de "nivel de pila demasiado profundo" con matrices grandes (2)
Hice dos matrices, cada una con 1 millón de elementos:
a1 = 1_000_000.times.to_a
a2 = a1.clone
Traté de empujar a2 en a1:
a1.push *a2
Esto devuelve SystemStackError: stack level too deep
.
Sin embargo, cuando intento con concat
, no aparece el error:
a1.concat a2
a1.length # => 2_000_000
Tampoco me sale el error con el operador splat:
a3 = [*a1, *a2]
a3.length # => 2_000_000
¿Por qué es este el caso? Miré la documentación para Array#push
, y está escrito en C. Sospecho que puede estar haciendo algo de recursión bajo el capó, y es por eso que está causando este error para arreglos grandes. ¿Es esto correcto? ¿No es una buena idea utilizar el push
para matrices grandes?
Casper ya respondió la pregunta en el título y le dio una solución que puede usar para hacer funcionar a1.push *a2
, pero me gustaría hablar sobre la última pregunta que hizo, sobre si es una buena idea.
Más específicamente, si va a trabajar con matrices que tienen millones de elementos en el código de producción, el rendimiento se convierte en algo a tener en cuenta. http://www.continuousthinking.com/2011/09/07/ruby_array_plus_vs_push.html tiene una descripción general de 4 formas diferentes de manejar la concatenación de matrices en ruby: +
, .push
, <<
y .concat
.
Allí mencionan que array.push
manejará efectivamente cada argumento por separado y aumentará el tamaño del array en un 50% cada vez que el array sea demasiado pequeño. Esto significa que en su ejemplo, a
se aumentará de tamaño 2 veces y obtendrá 1 millón de apéndices. Mientras tanto, array.concat
primero calculará el nuevo tamaño de la matriz, extenderá la matriz original y luego copiará la nueva matriz en el lugar correcto.
Para situaciones como la suya, es muy probable que concat
tenga más rendimiento, tanto desde la memoria como desde la perspectiva del uso de la CPU. Sin embargo, sin puntos de referencia no puedo decir con seguridad. Mi recomendación es medir el tiempo y el uso de la memoria para realizar ambas operaciones para el tamaño de los arreglos que desea manejar. es probable que concat
salga a la cima, pero podría estar equivocado en ese frente.
Creo que esto no es un error de recursión, sino un error de pila de argumentos. Se está ejecutando contra el límite de la profundidad de pila de Ruby VM para los argumentos.
El problema es el operador splat, que se pasa como un argumento para push
. El operador splat se expande en una lista de argumentos de un millón de elementos para push
.
Como los argumentos de la función se pasan como elementos de la pila, y el tamaño máximo preconfigurado del tamaño de la pila de Ruby VM es:
RubyVM::DEFAULT_PARAMS[:thread_vm_stack_size]
=> 1048576
.. aquí es donde viene el límite.
Puedes probar lo siguiente:
RUBY_THREAD_VM_STACK_SIZE=10000000 ruby array_script.rb
..y funcionará bien.
Esta es también la razón por la que desea utilizar concat
lugar, ya que toda la matriz se puede pasar como una sola referencia, y concat
procesará la matriz internamente. A diferencia de push
+ splat, que intentará usar la pila como un almacenamiento temporal para todos los elementos de la matriz.