Nota: Ho fattop qualche ricerca in piu’ ed ho trovato che la runtime gc genera un solo OS thread e quindi aggiunge thread uno alla volta per evitare conflitti di I/O. D’altra parte la runtime di gccgo mappa le goroutine sui p_thread in rapporto di 1 a 1 (almeno per il momento).
Ho seguito con relativa noncuranza lo sviluppo del linguaggio Go per un po’
di tempo, quando improvisamente mi sono imbattuto in questo post di “Appunti
Digitali” che fa riferimento a questo post su Dalke Scientific.
Mi ha davvero colpito come Stackless Python le dia di santa ragione a Go,
quindi ho deciso di esguire da solo i test sul mio caro vecchio IBM X-41
(Pentium-M 1.5 GHz).
Ho compilato Stackless Python e la toolchain per Go, quindi ho eseguito i
test esattamente come trovati sulla pagina di Dalke. I risultati sono qui
riportati:
$ time ./8.out
100000
real 0m5.197s
user 0m1.508s
sys 0m1.352s
$ time
/usr/local/bin/python2.6 test.py
100000
real 0m3.315s
user 0m1.556s
sys 0m0.148s
Quello che mi ha davvero colpito e’ il fatto che ambo i programmi spendano
aprrossimativamente lo stesso lasso di tempo in userland. Quello che davvero
uccide le prestazioni del programma GO e’ che spende quasi altrettanto tempo in
kernel mode. Mi sono domandato perche’…
Poi mi sono ricordato che, nel Google Tech Talk, Pike dice qualcosa riguardo
al fatto che la runtime di Go gestisce i thread per conto degli utenti, ed io
ho automaticamente interpretato che avrebbe associato i thread utente con gli
OS thread in qualche modo (come e’ la norma). Di fatto questo e’ quello che fa
gccgo (usando gli NTPL).
D’altra partle, leggendo la documentazione di Stackless Python, ho trovato
la pagina riguardante le Tasklets. Si dice chiaramente:
“Le Tasklet sono caratterizzate dall’essere estremamente leggere e portabili, e
sono una eccezionale alternativa a thread di sistema o processi”.
Sono quindi propenso a pensare che le Tasklet siano gestite dalla VM Python
stessa e che non richiedano alcuna transizione user-to-kernel, ne’ altre
interazioni con il kernel… perche’ non sono thread veri (al contrario delle
goroutines in Go).
Questo, con il fatto che Go e’ principalmente inteso per tagliare i tempi
di compilazione, mi fa pensare che la comparativa sia piuttosto ingiusta
e credo che Go garantisca una notevole performance!
Riguardo certe situazioni potenzialmente pericolose ammetto che sono senza
ombra di dubbio un problema. In ogni caso credo che verranno trattate molto in
fretta: Go e’ un linguaggio appena sfornato e qualche spigolatura qua e la non
puo’ essere che attesa, pur con un grande spazio di crescita.
Update:
Ho modificato i programmi di test cosi’ che ogni goroutine/Tasklet chiami una
seconda funzione che cicla 1000 volte e cumula il risultato di “sum = sum+1”.
I risultati sono come previsto: il compilato e’ un ordine di grandezza piu’
veloce dell’interpretato Stackless Pyhton! Ho anche rimpiazzato la chiamata a
range con xrange, come suggerito da Cesare
Di Mauro, ma non ha cambiato molto le cose… cosi’ come non le ha cambiate
usare psyco (un JIT per Python).
$time ./test2 && time /usr/local/bin/python test2.py
100000
real 0m4.037s
user 0m1.040s
sys 0m0.840s
100000
real 0m19.567s
user 0m15.697s
sys 0m0.160s
The source code for Python is:
default=100000)
def f(left, right):
loop()
left.send(right.receive()+1)
def loop():
sum = 0
for i in xrange (1,1000):
sum=sum+1
def main():
options, args = parser.parse_args()
leftmost = stackless.channel()
left, right = None, leftmost
for i in xrange(options.num_tasklets):
left, right = right, stackless.channel()
stackless.tasklet(f)(left, right)
right.send(0)
x = leftmost.receive()
print x
stackless.tasklet(main)()
stackless.run()
And the test code for Go is:
"fmt";
)
var ngoroutine = flag.Int("n", 100000, "how many")
func f(left, right chan int) {
loop();
left < - 1+<-right;
}
func loop() {
var sum int;
sum = 0;
for i := 0; i < 1000; i++ {
sum = sum + 1
}
}
func main() {
flag.Parse();
leftmost := make(chan int);
var left, right chan int = nil, leftmost;
for i := 0; i < *ngoroutine; i++ {
left, right = right, make(chan int);
go f(left, right);
}
right <- 0; // bang!
x := <-leftmost; // wait for completion
fmt.Println(x); // 100000
}
Tasklets and Goroutines are both green treads (e.g. are lightweight ans scale well compared to OS-level threads) , so there shouldn’t be much difference there.
One thing I suspect might be different is that goroutines are supposted to be distributed over a set of threads, where as tasklets only run in one thread (and also only on one core due to the GIL in Python, which means Stackless wouldn’t scale on a multi core system at all).
Goland manages goroutines and can reallocate goroutines to a different os-thread if one goroutine block (on IO for instance).
This adds could add some overhead.
For what I know Golang doesn’t use this feature yet, since it’s not stable yet. So I’m not sure if it has anything to do with the difference in speed.
Your test with an added load is interesting, because the original test results in just a number of function calls, for which Python could do very well. The biggest difference between Golang and Python is in the typesystem, the intepreter is pretty much the least slow thing for that kind of test.
Sorry, but Tasklets and Goroutines are both coroutines, not Green Threads (e.g. a Thread containing only one routine is a coroutine as well).
Tasklets are implemented as Green Threads (they are entirely managed by the Python VM), while Goroutines, as of now, are P-Threads.
I guess the idea for the future is, as you said, to have only a few OS threads and multiplex the Goroutines upon them, but that would require features that are not present in the runtime right now.
Molto interessante! Certo che è raro trovare un linguaggio più lento di python! 🙂
Possibile che vogliano sacrificare così tanto in cambio di una compilazione veloce? A quel punto, perché non usare un interprete e un lint? 😀
(tra l’altro non avevo ancora sentito parlare di stackless python, direi che ha delle caratteristiche decisamente interessanti, da approfondire)
Cominciano ad essere riconosciuti i tuoi meriti:
Google, nel 2010
arriva lo smarthphone
Sarà venduto online
e senza intermediari
Si chiamerà Nexus One. I servizi di telefonia mobile verranno acquistati separatamente
Casomai interessasse: “GCC to merge Go support” 😀
Ciau! ^^
func main(){
//you need set use max cpu, default use one cpu
runtime.GOMAXPROCS(runtime.NumCPU())
}
Except my old laptop had a single CPU with a single core… 🙂
Yes it has green threads (stackless) that allow quickly create many lightweight threads as long as no operations are blocking (something like Ruby’s threads?). What is this great for? What other features it has I want to use over CPython?