.net - Tamaño de pila bajo Mono
recursion f# (3)
Opción 1: cambiar el tamaño de pila mono
Una explicación obvia es que Mono no obedecerá el segundo argumento de
Thread
si es demasiado grande. ¿Alguien sabe cómo convencer a Mono de asignar una gran pila?
Tiene razón en que Mono limitará el tamaño de la pila, incluso si transfiere un valor grande. Por ejemplo, en mi máquina de prueba Cent OS de 64 bits, el tamaño máximo de pila que Mono asignará es de 2 megabytes. El archivo fuente de Mono C # Thread.cs nos muestra lo que ocurre cuando creas un hilo Mono :
public Thread (ThreadStart start, int maxStackSize) { if (start == null) throw new ArgumentNullException ("start"); threadstart = start; Internal.stack_size = CheckStackSize (maxStackSize); } static int CheckStackSize (int maxStackSize) { if (maxStackSize < 0) throw new ArgumentOutOfRangeException ("less than zero", "maxStackSize"); if (maxStackSize < 131072) // make sure stack is at least 128k big return 131072; int page_size = Environment.GetPageSize (); if ((maxStackSize % page_size) != 0) // round up to a divisible of page size maxStackSize = (maxStackSize / (page_size - 1)) * page_size; int default_stack_size = (IntPtr.Size / 4) * 1024 * 1024; // from wthreads.c if (maxStackSize > default_stack_size) return default_stack_size; return maxStackSize; }
El código anterior pone un límite estricto al tamaño de la pila.
En teoría, podría cambiar el código en una o ambas de las funciones anteriores ( líneas en negrita ) para que se asigne un mayor tamaño de pila. Una vez que hiciste esto, tendrías que construir el tiempo de ejecución Mono y luego ejecutar tu función para ver si el cambio hace la diferencia.
Debo enfatizar que no sé lo suficiente acerca de Mono para entender si la asignación de una pila más grande ayudará en su caso específico. Solo haría esto como último recurso (si ninguna de mis otras respuestas funciona).
He escrito un pequeño fragmento recursivo de código F # para ver cuántos niveles de recursión puedo incluir en la pila en .NET / Mono. Simplemente imprime la profundidad de recursión siempre que sea una potencia exacta de 2, así que descubro la profundidad máxima dentro de un factor 2.
Comienzo el código en un hilo con una cantidad definida de espacio de pila usando System.Threading.Thread (ThreadStart, int)
. Bajo .Net parece tomar aproximadamente 100 bytes por nivel de recursión, y puedo obtener alrededor de 16 millones de niveles en una pila de 2G. El uso de memoria es muy similar en Mono, sin embargo, solo puedo obtener unos 30 mil niveles. Aumentar el valor de tamaño de pila pasado a Thread
pasado por alrededor de 600000
no aumenta la profundidad de recursión.
ulimit
informa que el límite de tamaño de pila es 1G.
Una explicación obvia es que Mono no obedecerá el segundo argumento de Thread
si es demasiado grande. ¿Alguien sabe cómo convencer a Mono de asignar una gran pila?
El código es trivial, pero está debajo solo en caso de que alguien se preocupe:
let rec f i =
if popcount i = 1 then // population count is one on exact powers of 2
printf "Got up to %d/n" i
stdout.Flush ()
if i = 1000000000 then 0 else 1 + f (i+1)
Opción 3: hacerlo iterativo
Una opción es volver a escribir su método para que no sea recursivo, sino iterativo. Es decir, cambia el método para que sea un ciclo que calcule los resultados:
let f i =
let mutable acc = 0
for counter in i .. 1000000000 do
// display progress every 10k iterations
let j = counter % 10000
if j = 0 then
printf "Got up to %d/n" counter
stdout.Flush ()
if counter < 1000000000 then
acc <- acc + 1
acc
Para el valor de entrada 1:
let result = f 1
printf "result %d/n" result
La salida:
Got up to 10000
Got up to 20000
Got up to 30000
...
Got up to 999980000
Got up to 999990000
Got up to 1000000000
result 999999999
Para el valor de entrada 999,999,998:
let result = f 999999998
printf "result %d/n" result
La salida:
Got up to 1000000000
result 2
El código anterior usa dos variables para realizar un seguimiento del progreso:
acc
- the accumulater, que almacena el resultado del cálculo
counter
- es simplemente el número de llamadas iterativas (número de bucles)
Dado que estamos utilizando un bucle para calcular el resultado, no hay ninguna pila adicional asignada.
Opción 2: llamadas F # Tail
Una opción es volver a escribir su método para que use llamadas de cola recursivas. Desde el enlace anterior (Wikipedia):
Una función recursiva de cola es un caso especial de recursión en el que la última instrucción ejecutada en el método es la llamada recursiva. F # y muchos otros lenguajes funcionales pueden optimizar las funciones recursivas de la cola; dado que no se realiza ningún trabajo adicional después de la llamada recursiva, no es necesario que la función recuerde de dónde vino, y por lo tanto no hay razón para asignar memoria adicional en la pila.
F # optimiza las funciones recursivas de cola al decirle al CLR que deje caer el marco de pila actual antes de ejecutar la función objetivo. Como resultado, las funciones recursivas de cola pueden repetirse indefinidamente sin consumir espacio de pila.
Hay una advertencia: Mono no es totalmente compatible con las llamadas recursivas de cola ; lo que debes hacer es probar primero en el tiempo de ejecución de .Net y luego ver si el código se ejecutará en Mono.
Aquí está la función de muestra reescrita usando una llamada recursiva de cola: funciona tanto en .NET (usando Visual Studio 2010) como en Mono (versión de Mono 3.2.0, F # 3.0):
let f i =
let rec loop acc counter =
// display progress every 10k iterations
let j = counter % 10000
if j = 0 then
printf "Got up to %d/n" counter
stdout.Flush ()
if counter < 1000000000 then
// tail recursive
loop (acc + 1) (counter + 1)
else
acc
loop 0 i
Para el valor de entrada 1:
let result = f 1
printf "result %d/n" result
La salida:
Got up to 10000
Got up to 20000
Got up to 30000
...
Got up to 999980000
Got up to 999990000
Got up to 1000000000
result 999999999
Para el valor de entrada 999,999,998:
let result = f 999999998
printf "result %d/n" result
La salida:
Got up to 1000000000
result 2
El código anterior usa dos variables para realizar un seguimiento del progreso:
acc
- el acumulador, que almacena el resultado del cálculo
counter
- es simplemente el número de llamadas recursivas
¿Por qué su código de muestra no es recursivo?
Parafraseando la sección Cómo escribir las funciones recursivas de la cola del artículo de Wikipedia, podemos reescribir esta línea de su código :
if i = 1000000000 then 0 else 1 + f (i+1)
como
if i = 1000000000 then 0
else
let intermediate = f (i+1) // recursion
let result = 1 + intermediate // additional operations
result
La misma definición de recursividad de cola dice que no puede haber operaciones adicionales después de la llamada recursiva. Como podemos ver en la versión reescrita de su código, se requieren operaciones adicionales para producir el resultado.
Recursos
- Tail llama en F # en MSDN
- F Sharp Programación / Recursión en Wikipedia
- Llamadas de cola en Mono : un ejemplo de llamada de cola que no funciona en Mono
- Soporte de llamada de cola en F # en el rastreador de errores Mono
- ¿Cómo puedo implementar una lista recursiva de cola?