Find limit of recursion: Difference between revisions
Content added Content deleted
m (→64-bit version: 'stack' size unit MB -> kB) |
(→{{header|Go}}: Update with runtime/debug.SetMaxStack added in Go 1.2) |
||
Line 630: | Line 630: | ||
=={{header|Go}}== |
=={{header|Go}}== |
||
Go features stacks that grow as needed |
Go features stacks that grow as needed making the effective recursion limits relatively large. |
||
Pre-Go 1.2 this could be all of memory and the program would grow without bounds until the system swap space was exhausted and the program was killed (either by the a [http://golang.org/ref/spec#Run_time_panics run-time panic] after an allocation failure or by the operating system killing the process). |
|||
Go 1.2 set a limit to the maximum amount of memory that can be used by a ''single goroutine'' stack. |
|||
The initial setting is 1 GB on 64-bit systems, 250 MB on 32-bit systems. |
|||
The default can be changed by [https://golang.org/pkg/runtime/debug#SetMaxStack <code>SetMaxStack</code>] in the <code>runtime/debug</code> package. It is documented as "useful mainly for limiting the damage done by goroutines that enter an infinite recursion." |
|||
<lang go>package main |
<lang go>package main |
||
import |
import ( |
||
"flag" |
|||
"fmt" |
|||
"runtime/debug" |
|||
) |
|||
func main() { |
func main() { |
||
stack := flag.Int("stack", 0, "maximum per goroutine stack size or 0 for the default") |
|||
⚫ | |||
flag.Parse() |
|||
if *stack > 0 { |
|||
debug.SetMaxStack(*stack) |
|||
} |
|||
⚫ | |||
} |
} |
||
func r(l int) { |
func r(l int) { |
||
if l%1000 == 0 { |
|||
fmt.Println(l) |
|||
} |
|||
} |
|||
r(l + 1) |
|||
}</lang> |
}</lang> |
||
I tested on a smallish computer by today's standards, 1 GB RAM, and the standard Ubuntu installation gave it 2.5 GB swap. The program filled available RAM quickly, at a recursion depth of about 10M. It took a several minutes then to exhaust swap before exiting with this trace: (as you see, at a depth of over 25M.) |
|||
Run without arguments on a 64-bit system: |
|||
{{out}} |
|||
<pre> |
|||
[…] |
|||
4471000 |
|||
4472000 |
|||
4473000 |
|||
runtime: goroutine stack exceeds 1000000000-byte limit |
|||
fatal error: stack overflow |
|||
runtime stack: |
|||
runtime.throw(0x5413ae) |
|||
/usr/local/go/src/pkg/runtime/panic.c:520 +0x69 |
|||
runtime.newstack() |
|||
/usr/local/go/src/pkg/runtime/stack.c:770 +0x486 |
|||
runtime.morestack() |
|||
/usr/local/go/src/pkg/runtime/asm_amd64.s:228 +0x61 |
|||
goroutine 16 [stack growth]: |
|||
main.r(0x444442) |
|||
[…]/rosetta/stack_size/stack.go:9 fp=0xc2680380c8 sp=0xc2680380c0 |
|||
main.r(0x444441) |
|||
[…]/rosetta/stack_size/stack.go:13 +0xc5 fp=0xc268038140 sp=0xc2680380c8 |
|||
main.r(0x444440) |
|||
[…] |
|||
...additional frames elided... |
|||
created by _rt0_go |
|||
/usr/local/go/src/pkg/runtime/asm_amd64.s:97 +0x120 |
|||
goroutine 19 [finalizer wait]: |
|||
runtime.park(0x412a20, 0x542ce8, 0x5420a9) |
|||
/usr/local/go/src/pkg/runtime/proc.c:1369 +0x89 |
|||
runtime.parkunlock(0x542ce8, 0x5420a9) |
|||
/usr/local/go/src/pkg/runtime/proc.c:1385 +0x3b |
|||
runfinq() |
|||
/usr/local/go/src/pkg/runtime/mgc0.c:2644 +0xcf |
|||
runtime.goexit() |
|||
/usr/local/go/src/pkg/runtime/proc.c:1445 |
|||
exit status 2 |
|||
</pre> |
|||
Run with "-stack 262144000" (to simulate the documented 250 MB limit on a 32-bit system): |
|||
{{out}} |
|||
<pre> |
|||
[…] |
|||
1117000 |
|||
1118000 |
|||
runtime: goroutine stack exceeds 262144000-byte limit |
|||
fatal error: stack overflow |
|||
[…] |
|||
</pre> |
|||
On a 32-bit system an <code>int</code> is 32 bits so the maximum value to <code>debug.SetMaxStack</code> is 2147483647 (nearly 2 GB). |
|||
On a 64-bit system an <code>int</code> is usually 64 bits so the maximum value will much larger, more than the available memory. Thus setting the maximum value will either exhaust all the system memory and swap (as with pre-Go 1.2) or will result in a allocation failure and run-time panic (e.g. 32-bit systems often have more memory and swap in total than the memory accessible to a single user program due to the limits of a 32 bit address space shared with the kernel). |
|||
Note, unlike with some other systems, increasing or changing this value only changes the allocation ''limit''. |
|||
The stack still starts out very small and only grows as needed, there is no large stack pre-allocation even when using a very high limit. |
|||
Also note that this is ''per-goroutine'', each goroutine can recurse independently and approach the limit. |
|||
The above code built pre-Go 1.2 (without the then non-existent <code>debug.SetMaxStack</code> call) and |
|||
run on a 1 GB RAM machine with 2.5 GB swap filled available RAM quickly, at a recursion depth of about 10M. |
|||
It took a several minutes to exhaust swap before exiting with this trace: (as you see, at a depth of over 25M.) |
|||
<pre> |
<pre> |
||
[…] |
|||
... |
|||
25611000 |
25611000 |
||
25612000 |
25612000 |
||
Line 696: | Line 774: | ||
_rt0_386+0xc1 /home/sonia/go/src/pkg/runtime/386/asm.s:80 |
_rt0_386+0xc1 /home/sonia/go/src/pkg/runtime/386/asm.s:80 |
||
</pre> |
</pre> |
||
Hey, at least it terminated in a controlled way. I tried this task a few months ago and it crashed the whole computer. I read that the Go runtime has since been improved to handle out of memory conditions more gracefully. Seems so—my machine is still up. |
|||
=={{header|Gri}}== |
=={{header|Gri}}== |