web-dev-qa-db-fra.com

Pourquoi Erlang est-il plus lent que Java sur tous ces petits repères mathématiques?

En envisageant des alternatives pour Java pour un environnement backend distribué/simultané/avec basculement/évolutif, j'ai découvert Erlang. J'ai passé du temps sur des livres et des articles où presque tous (même Java addicts guys) dit qu'Erlang est un meilleur choix dans de tels environnements, car de nombreuses choses utiles sont prêtes à l'emploi de manière moins sujette aux erreurs.

J'étais sûr qu'Erlang est plus rapide dans la plupart des cas, principalement en raison d'une stratégie de collecte des ordures différente (par processus), de l'absence d'état partagé (threads et processus noir et blanc) et de types de données plus compacts. Mais j'ai été très surpris quand j'ai trouvé comparaisons d'Erlang vs Java échantillons mathématiques où Erlang est plus lent de plusieurs ordres, par exemple de x10 à x100.

Même sur des tâches simultanées, à la fois sur plusieurs cœurs et un seul.

Quelles en sont les raisons? Ces réponses me sont venues à l'esprit:

  • Utilisation de Java primitives (=> no heap/gc) sur la plupart des tâches
  • Même nombre de threads dans Java et processus Erlang donc le modèle d'acteur n'a aucun avantage ici
  • Ou tout simplement que Java est typé statiquement, alors qu'Erlang n'est pas
  • Autre chose?

Si c'est parce que ce sont des algorithmes mathématiques très spécifiques, quelqu'un peut-il montrer plus de tests de performances réels/pratiques?

MISE À JOUR: J'ai jusqu'à présent les réponses résumant qu'Erlang n'est pas le bon outil pour un tel "fast Java Java" spécifique, mais ce qui n'est pas clair pour moi - quelle est la principale raison de une telle inefficacité Erlang ici: typage dynamique, GC ou mauvaise compilation native?

35
yetanothercoder

Erlang n'a pas été construit pour les mathématiques. Il a été construit avec la communication, le traitement parallèle et l'évolutivité à l'esprit, donc le tester pour des tâches mathématiques est un peu comme tester si votre marteau-piqueur vous offre une expérience de massage rafraîchissante.

Cela dit, arrêtons un peu:
Si vous voulez une programmation de type Erlang dans JVM, jetez un œil à Scala Actors ou Akka framework ou Vert.x .

32
npe

Les repères ne sont jamais bons pour dire autre chose que ce qu'ils testent vraiment. Si vous pensez qu'un benchmark teste uniquement des primitives et un modèle de threading classique, c'est ce que vous obtenez. Vous pouvez maintenant avec une certaine confiance dire que Java est plus rapide qu'Erlang sur les mathématiques sur les primitives ainsi que le modèle de threading classique pour ces types de problèmes. Vous ne savez rien sur les performances avec de grandes nombre de threads ou pour des problèmes plus impliqués, car le test n'a pas testé cela.

Si vous faites les types de mathématiques testés par le benchmark, optez pour Java car c'est évidemment le bon outil pour ce travail. Si vous voulez faire quelque chose de très évolutif avec peu ou pas d'état partagé , trouvez une référence pour cela ou du moins réévaluez Erlang.

Si vous avez vraiment besoin de faire des calculs lourds à Erlang, pensez à utiliser HiPE (pensez-y quand même d'ailleurs).

14
Emil Vikström

Comme indiqué dans d'autres réponses - Erlang est conçu pour résoudre efficacement les problèmes de la vie réelle, qui sont un peu opposés aux problèmes de référence.

Mais j'aimerais éclairer un autre aspect - la justesse du code erlang (dans certains cas, signifie la rapidité du développement), qui pourrait être facilement conclue, après avoir comparé les implémentations des benchmarks.

Par exemple, référence de k-nucléotides:
Version Erlang: http://benchmarksgame.alioth.debian.org/u64q/program.php?test=knucleotide&lang=hipe&id=
Version Java: http://benchmarksgame.alioth.debian.org/u64q/program.php?test=knucleotide&lang=Java&id=

Si vous voulez plus de références réelles, je vous suggère Comparaison de C++ et d'Erlang pour le logiciel de télécommunications Motorola

8
stemm

Je me suis intéressé à cela car certains des critères de référence conviennent parfaitement à erlang, comme le séquençage de gènes. Ainsi http://benchmarksgame.alioth.debian.org/ la première chose que j'ai faite a été de regarder les implémentations à complément inverse, pour C et Erlang, ainsi que les détails des tests. J'ai trouvé que le test est biaisé car il ne réduit pas le temps nécessaire à erlang pour démarrer le VM/w les ordonnanceurs, le C nativement compilé est démarré beaucoup plus rapidement. La façon dont ces benchmarks mesurent est fondamentalement : time erl -noshell -s revcomp5 main < revcomp-input.txt

Maintenant, le test de référence indique Java a pris 1,4 seconde et erlang/w HiPE a pris 11. L'exécution du code Erlang (à filetage unique) m'a pris 0,15 seconde, et si vous actualisez le temps qu'il a fallu pour démarrer le vm , la charge de travail réelle n'a pris que 3000 microsecondes (0,003 secondes).

Je n'ai donc aucune idée de la façon dont cela est comparé, si cela est fait 100 fois, cela n'a aucun sens car le coût de démarrage de l'erlang VM sera x100. Si l'entrée est beaucoup plus longue que celle donnée , cela aurait du sens, mais je ne vois aucun détail sur la page Web de cela. Pour rendre les benchmarks plus équitables pour les langages gérés, demandez au code (Erlang/Java) d'envoyer un signal Unix au python = (qui fait l'analyse comparative) qu'il a frappé la fonction de démarrage.

Maintenant, en dehors du benchmark, l'erlang VM exécute essentiellement le code machine à la fin, ainsi que la machine virtuelle Java. Il n'y a donc aucun moyen qu'une opération mathématique soit prendre plus de temps à Erlang qu'à Java.

Ce que Erlang est mauvais, ce sont les données qui doivent souvent muter. Comme un bloc de chiffrement enchaîné. Supposons que vous ayez les caractères "0123456789", maintenant votre cryptage xors les 2 premiers caractères par 7, puis xors les deux caractères suivants par le résultat des deux premiers ajoutés, puis xors les 2 caractères précédents par les résultats des 2 soustraits actuels, puis xors les 4 caractères suivants .. etc

Étant donné que les objets dans Erlang sont immuables, cela signifie que l'ensemble du tableau de caractères doit être copié chaque fois que vous le mutez. C'est pourquoi erlang prend en charge des choses appelées NIFS, qui est un code C que vous pouvez appeler pour résoudre ce problème exact. En fait, tout le cryptage (ssl, aes, blowfish ..) et la compression (zlib, ..) fournis avec Erlang sont implémentés en C, il y a également un coût proche de 0 associé à l'appel de C depuis Erlang.

Donc, en utilisant Erlang, vous obtenez le meilleur des deux mondes, vous obtenez la vitesse de C avec le parallélisme d'Erlang.

Si je devais implémenter le complément inverse de la manière la plus RAPIDE possible, j'écrirais le code de mutation en C mais le code parallèle en utilisant Erlang. En supposant une entrée infinie, j'aurais Erlang divisé sur le> <<Line/binary, ">", Rest/binary>> = read_stream Envoyez le bloc au premier ordonnanceur disponible via un round robin, composé de nœuds cachés privés en réseau EC2 infinis, ajoutés en temps réel au cluster toutes les millisecondes.

Ces nœuds appellent ensuite C en NIFS pour le traitement (C était la mise en œuvre la plus rapide pour le compliment inverse sur le site Web alioth), puis renvoient la sortie au maître de nœud pour l'envoyer à l'ordinateur.

Pour implémenter tout cela dans Erlang, je devrais écrire du code comme si j'écrivais un programme à thread unique, il me faudrait moins d'une journée pour créer ce code.

Pour implémenter cela en Java, je devrais écrire le code à un seul thread, je devrais prendre le hit de performance d'appeler de Managed à Unmanaged (car nous utiliserons évidemment l'implémentation C pour le travail de grognement), puis réécrire pour prendre en charge 64 cœurs. Réécrivez-le ensuite pour prendre en charge plusieurs CPUS. Réécrivez-le à nouveau pour prendre en charge le clustering. Réécrivez-le à nouveau pour résoudre les problèmes de mémoire.

Et c'est Erlang en bref.

7
Vans S

La solution Erlang utilise ETS, Erlang Term Storage, qui est comme une base de données en mémoire exécutée dans un processus distinct. Du fait qu'il se trouve dans un processus distinct, tous les messages à destination et en provenance de ce processus doivent être sérialisés/désérialisés. Cela expliquerait une grande partie de la lenteur, je pense. Par exemple, si vous regardez le benchmark "regex-dna", Erlang n'est que légèrement plus lent que Java là-bas, et il n'utilise pas ETS.

3
pmarreck

Le fait qu'erlang doive allouer de la mémoire pour chaque valeur alors qu'en Java vous réutiliserez généralement les variables si vous voulez que ce soit rapide, cela signifie qu'il sera toujours plus rapide pour les repères de `` boucle serrée '').

Il serait intéressant de comparer une version Java version en utilisant l'indicateur -client et les primitives encadrées et de comparer cela à erlang.

Je pense que l'utilisation de hipe est injuste car ce n'est pas un projet actif. Je serais intéressé de savoir si un logiciel essentiel à la mission fonctionne sur ce point.

2
Andy Till