web-dev-qa-db-fra.com

Go peut-il vraiment être beaucoup plus rapide que Python?

Je pense que j'ai peut-être mal implémenté cela parce que les résultats n'ont pas de sens. J'ai un programme Go qui compte jusqu'à 1000000000:

package main

import (
    "fmt"
)

func main() {
    for i := 0; i < 1000000000; i++ {}
    fmt.Println("Done") 
}

Il se termine en moins d'une seconde. D'un autre côté, j'ai un script Python:

x = 0
while x < 1000000000:
    x+=1
print 'Done'

Il se termine en quelques minutes.

Pourquoi la version Go est-elle tellement plus rapide? Est-ce qu'ils comptent tous les deux jusqu'à 1000000000 ou manque-t-il quelque chose?

44
bab

Un milliard n'est pas un très grand nombre. Toute machine raisonnablement moderne devrait être capable de le faire en quelques secondes tout au plus, si elle est capable de faire le travail avec des types natifs. J'ai vérifié cela en écrivant un programme C équivalent, en lisant l'assemblage pour m'assurer qu'il faisait bien l'ajout et en le chronométrant (il se termine en environ 1,8 seconde sur ma machine).

Python, cependant, n'a pas de concept de variables typées nativement (ou d'annotations de type significatives), donc il doit faire des centaines de fois autant de travail dans ce cas. En bref, la réponse à votre question principale est "oui". Allez vraiment peut être beaucoup plus rapide que Python, même sans aucune sorte de supercherie de compilateur comme l'optimisation d'une boucle sans effet secondaire.

81
hobbs

pypy fait réellement un travail impressionnant pour accélérer cette boucle

def main():
    x = 0
    while x < 1000000000:
        x+=1

if __== "__main__":
    s=time.time()
    main()
    print time.time() - s

$ python count.py 
44.221405983
$ pypy count.py 
1.03511095047

~ 97% d'accélération!

Clarification pour 3 personnes qui n'ont pas "compris". Le langage Python lui-même n'est pas lent. L'implémentation CPython est un moyen relativement simple d'exécuter le code. Pypy est une autre implémentation du langage qui fait beaucoup de choses délicates (en particulier le JIT) qui peut faire d'énormes différences. Répondre directement à la question dans le titre - Go n'est pas "beaucoup" plus rapide que Python, Go est beaucoup plus rapide que CPython.

Cela dit, les exemples de code ne font pas vraiment la même chose. Python doit instancier 1000000000 de ses objets int. Go incrémente simplement un emplacement de mémoire.

63
John La Rooy

Ce scénario sera très apprécié des langues décentes de type statique compilées nativement . Les langages de type statique compilés en mode natif sont capables d'émettre une boucle très triviale, par exemple de 4 à 6 opcodes CPU qui utilise une condition de vérification simple pour la terminaison. Cette boucle a effectivement zéro la prédiction de branchement manque et peut être considérée comme effectuant un incrément à chaque cycle de CPU (ce n'est pas tout à fait vrai, mais ..)

Les implémentations Python doivent faire de manière significative plus de travail, principalement en raison du typage dynamique. Python doit faire plusieurs appels différents (internes et externes) juste pour ajouter deux ints ensemble. Dans Python it must call __add__ (Il s'agit en fait de i = i.__add__(1), mais cette syntaxe ne fonctionnera qu'en Python 3.x), qui à son tour doit vérifier le type de la valeur transmise (pour vous assurer qu'il s'agit bien d'un int), puis il ajoute les valeurs entières (en les extrayant des deux objets), puis la nouvelle valeur entière est à nouveau enveloppée dans un nouvel objet. Enfin, il réaffecte le nouvel objet à la variable locale. C'est beaucoup plus de travail qu'un seul opcode à incrémenter, et ne traite même pas la boucle elle-même - par comparaison, la version Go/native n'incrémente probablement un registre que par effet secondaire.

Java sera juste beaucoup meilleur dans un benchmark trivial comme celui-ci et sera probablement assez proche de Go; le JIT et static-typing de la variable de compteur peuvent le garantir (il utilise une instruction JVM d'ajout d'entier spécial). Encore une fois, Python n'a pas un tel avantage. Maintenant, il existe des implémentations comme PyPy/RPython , qui exécutent une phase de typage statique et devraient favoriser beaucoup mieux que CPython ici ..

20
user166390

Vous avez deux choses à l'œuvre ici. Le premier est que Go est compilé en code machine et s'exécute directement sur le CPU tandis que Python est compilé en bytecode exécuté sur une VM (particulièrement lente).

La deuxième chose, et plus importante, qui affecte les performances est que la sémantique des deux programmes est en fait sensiblement différente. La version Go crée une "boîte" appelée "x" qui contient un nombre et incrémente de 1 à chaque passage dans le programme. La version Python doit en fait créer une nouvelle "boîte" (objet int) à chaque cycle (et, éventuellement, les jeter). Nous pouvons le démontrer en modifiant légèrement vos programmes:

package main

import (
    "fmt"
)

func main() {
    for i := 0; i < 10; i++ {
        fmt.Printf("%d %p\n", i, &i)
    }
}

...et:

x = 0;
while x < 10:
    x += 1
    print x, id(x)

C'est parce que Go, en raison de ses racines C, prend un nom de variable pour faire référence à un endroit , où Python prend des noms de variables pour faire référence aux choses . Puisqu'un entier est considéré comme une entité unique et immuable en python, nous devons constamment en créer de nouvelles. Python devrait être plus lent que Go, mais vous avez choisi le pire des cas - dans le jeu Benchmarks , nous voyons que Go est, en moyenne, environ 25 fois plus rapide (100x dans le pire cas).

Vous avez probablement lu que si vos programmes Python sont trop lents, vous pouvez les accélérer en déplaçant les choses en C. Heureusement, dans ce cas, quelqu'un l'a déjà fait pour vous. Si vous réécrivez votre boucle vide pour utiliser xrange () comme ceci:

for x in xrange(1000000000):
    pass
print "Done."

... vous le verrez fonctionner environ deux fois plus vite. Si vous trouvez que les compteurs de boucles sont en fait un goulot d'étranglement majeur dans votre programme, il est peut-être temps d'étudier une nouvelle façon de résoudre le problème.

8
Sean McSomething

@troq

Je suis un peu en retard à la fête mais je dirais que la réponse est oui et non. Comme l'a souligné @gnibbler, CPython est plus lent dans l'implémentation simple, mais pypy est compilé en jit pour un code beaucoup plus rapide lorsque vous en avez besoin.

Si vous effectuez un traitement numérique avec CPython, la plupart le feront avec numpy, ce qui entraînera des opérations rapides sur les tableaux et les matrices. Récemment, j'ai fait beaucoup de choses avec numba qui vous permet d'ajouter un simple wrapper à votre code. Pour celui-ci, je viens d'ajouter @njit à une fonction incALot () qui exécute votre code ci-dessus.

Sur ma machine, CPython prend 61 secondes, mais avec l'encapsuleur numba, il faut 7,2 microsecondes qui seront similaires à C et peut-être plus rapides que Go. C'est une accélération de 8 millions de fois.

Donc, en Python, si les choses avec des nombres semblent un peu lentes, il existe des outils pour y remédier - et vous obtenez toujours la productivité du programmeur de Python et le REPL.

def incALot(y):
    x = 0
    while x < y:
        x += 1

@njit('i8(i8)')
def nbIncALot(y):
    x = 0
    while x < y:
        x += 1
    return x

size = 1000000000
start = time.time()
incALot(size)
t1 = time.time() - start
start = time.time()
x = nbIncALot(size)
t2 = time.time() - start
print('CPython3 takes %.3fs, Numba takes %.9fs' %(t1, t2))
print('Speedup is: %.1f' % (t1/t2))
print('Just Checking:', x)

CPython3 takes 58.958s, Numba takes 0.000007153s
Speedup is: 8242982.2
Just Checking: 1000000000
2
John 9631

Le problème est Python est interprété, GO ne l'est pas, il n'y a donc pas de véritable moyen de tester les vitesses de test. Les langages interprétés sont généralement (pas toujours un composant vm), c'est là que réside le problème, tout test que vous exécutez est exécuté dans des limites interprétées et non dans des limites d'exécution réelles. Go est légèrement plus lent que C en termes de vitesse et cela est principalement dû au fait qu'il utilise la récupération de place au lieu de la gestion manuelle de la mémoire. Cela dit, GO par rapport à Python est rapide parce que c'est un langage compilé, la seule chose qui manque à GO est le test de bogues, je me corrige si je me trompe.

1
Dawg

Il est possible que le compilateur se soit rendu compte que vous n'avez pas utilisé la variable "i" après la boucle, il a donc optimisé le code final en supprimant la boucle.

Même si vous l'avez utilisé par la suite, le compilateur est probablement assez intelligent pour remplacer la boucle par

i = 1000000000;

J'espère que cela aide =)