2016-01-19 5 views
7

Si consideri il seguente programma Go:Perché lo stack overflow dipende da come accedere alla matrice in Go?

package main 

func main() { 
    var buffer [100000000]float64 
    var i int 
    for i = range buffer { 
     buffer[i] = float64(i) 
    } 
} 

Con "go test1.go correre", funziona. (. A meno che non si dispone di troppo poco RAM)

Ora, espando questo programma banalmente:

package main 

func main() { 
    var buffer [100000000]float64 
    var i int 
    var value float64 
    for i, value = range buffer { 
     value = value 
     buffer[i] = float64(i) 
    } 
} 

"andare test2.go RUN" rendimenti:

runtime: goroutine stack exceeds 1000000000-byte limit 
fatal error: stack overflow 

runtime stack: 
runtime.throw(0x473350, 0xe) 
     /usr/local/go/src/runtime/panic.go:527 +0x90 
runtime.newstack() 
     /usr/local/go/src/runtime/stack1.go:794 +0xb17 
runtime.morestack() 
     /usr/local/go/src/runtime/asm_amd64.s:330 +0x7f 

goroutine 1 [stack growth]: 
main.main() 
     /home/bronger/src/go-test/test3.go:3 fp=0xc860075e50 sp=0xc860075e48 
runtime.main() 
     /usr/local/go/src/runtime/proc.go:111 +0x2b0 fp=0xc860075ea0 sp=0xc860075e50 
runtime.goexit() 
     /usr/local/go/src/runtime/asm_amd64.s:1696 +0x1 fp=0xc860075ea8 sp=0xc860075ea0 
exit status 2 

Mi sembra che, in test1.go, è stato utilizzato l'heap, mentre in test2.go è stata utilizzata la pila. Perché?

risposta

9

Secondo il Go specification:

L'espressione di intervallo viene valutata una volta prima di iniziare il ciclo, con una sola eccezione: se l'espressione intervallo è un array o un puntatore a una matrice e al massimo una variabile di iterazione è presente, viene valutata solo la lunghezza dell'espressione dell'intervallo

Quindi nel primo programma solo la lunghezza del buffer viene valutata e posizionata nello stack.

Nel secondo programma l'intero buffer viene posto in pila prima di iterarlo su di esso. Poiché la dimensione del buffer è nota durante la compilazione, il compilatore Go posiziona le istruzioni all'inizio della funzione per pre-allocare lo spazio dello stack. Ecco perché la traccia di panico punta all'inizio della funzione.

In entrambi i casi, buffer è allocato nello heap. Questo può essere confermata da

$ go build -gcflags=-m 
./main.go:4: moved to heap: buffer 

Nota che il programma si comporterà allo stesso modo se si effettua buffer una variabile globale.

Tuttavia, se si modifica buffer in una porzione (buffer := make([]float64, 100000000)), il programma avrà esito positivo in entrambi i casi. Ciò accade perché una sezione è solo un puntatore all'array effettivo e richiede solo pochi byte sullo stack indipendentemente dalla dimensione dell'array di supporto. Quindi il modo più semplice per risolvere il tuo secondo programma è quello di rendere iterare sopra fetta invece di matrice:

.... 
for i, value = range buffer[:] { 
    .... 
} 

Sorprendentemente se si tenta di creare un array più grande [1000000000]float64 poi il compilatore si lamenta (stack frame troppo grande (> 2 GB)). Penso che dovrebbe lamentarsi durante la compilazione del tuo secondo programma invece di lasciarlo prendere dal panico. È possibile segnalare questo problema a http://github.com/golang/go/issues

Problemi correlati