Crystal est un nouveau venu dans le monde des langages de programmation.
Le but de ce langage est d’avoir une synthaxe similaire à Ruby tout en ayant
les performances du C. Dans cet article, je vous propose de faire une
comparaison entre le langage populaire et rapide, Go, ce jeune langage,
Crystal, et son inspiration, Ruby.
Resultats
Sans plus attendre, voici les résultats en image :
Comme Ruby a des performances bien en dessous de Go et Crystal, voici le même graphique en le retirant :
Plus de détails sur les tests par la suite.
Fibonacci
Comme premier programme, j’ai choisi de faire un benchmark avec un programme
comptant selon la suite de
Fibonacci. N’étant pas un
développeur Go, j’ai trouvé le code
ici et
l’ai modifié pour faire ce que je souhaite, c’est-à-dire m’afficher uniquement
le résultat selon la valeur passée en paramètre.
Ne cherchez pas de différence entre Ruby et Crystal, il n’y en a pas. Dans ce
cas, Ruby et Crystal sont totalement similaires.
Voici les résultats :
12345
go build fib.go &&time ./fib
real 0m0.001s
user 0m0.000s
sys 0m0.001s
12345
time ruby fib.rb
real 0m0.035s
user 0m0.029s
sys 0m0.006s
12345
crystal build --release fib.cr &&time ./fib
real 0m0.003s
user 0m0.001s
sys 0m0.002s
On peut voir que Go gagne suivi de Crystal et de Ruby. Cependant, dans ce test,
les temps d’exécution sont tellement rapides que beaucoup d’autres facteurs
peuvent entrer en jeu dans le temps d’exécution.
Fibonacci BigInt
Pour poursuivre les tests, j’ai voulu augmenter la valeur à calculer. Malheureusement, pour Go et
Crystal, la taille des entiers n’est pas suffisante. Il a donc été nécessaire
de modifier le code pour utiliser des BigInt.
go build fib.go &&time ./fib
real 0m3.831s
user 0m3.826s
sys 0m0.009s
12345
time ruby fib.rb
real 0m11.188s
user 0m9.873s
sys 0m1.290s
12345
crystal build --release fib.cr &&time ./fib
real 0m19.414s
user 0m29.527s
sys 0m4.969s
Le résultat est un peu décevant. L’utilisation du BigInt en Crystal est lente.
La raison de cette lenteur viens du faire que Crystal utilise
GMP qui est manifestement plus lent que l’implémentation
de Go. Vous pouvez voir une discussion sur la lenteur de Crystal
ici.
Autres Benchmarks
Je n’ai pas voulu me laisser abattre au premier test et j’ai décidé d’en faire
d’autres. J’ai trouvé ce projet. Malheureusement, les codes proposés ne
fonctionnent pas toujours avec les dernières versions des langages. Je me suis
contenté de faire une comparaison avec les scripts fonctionnant sans modifications.
Brainfuck
123456
go build brainfuck.go &&time ./brainfuck bench.b
ZYXWVUTSRQPONMLKJIHGFEDCBA
real 0m6.510s
user 0m6.514s
sys 0m0.011s
123456
time ruby brainfuck.rb bench.b
ZYXWVUTSRQPONMLKJIHGFEDCBA
real 3m5.364s
user 3m4.724s
sys 0m0.586s
123456
crystal build --release brainfuck.cr &&time ./brainfuck bench.b
ZYXWVUTSRQPONMLKJIHGFEDCBA
real 0m5.930s
user 0m5.924s
sys 0m0.005s
Havlak
1234567891011121314
go build havlak.go &&time ./havlak
Welcome to LoopTesterApp, Go edition
Constructing Simple CFG...
15000 dummy loops
Constructing CFG...
Performing Loop Recognition
1 Iteration
Another 50 iterations...
..................................................
Found 76001 loops (including artificial root node)(3800050)real 0m32.058s
user 0m53.207s
sys 0m1.433s
1234567891011121314
crystal build --release havlak.cr &&time ./havlak
Welcome to LoopTesterApp, Crystal edition
Constructing Simple CFG...
15000 dummy loops
Constructing CFG...
Performing Loop Recognition
1 Iteration
Another 50 iterations...
..................................................
Found 76002 loops (including artificial root node)(3800100)real 0m14.083s
user 0m20.487s
sys 0m0.186s
On peut voir que Crystal est deux fois plus rapide que Go. J’émets une petite
réserve sur le code fait avec Crystal. Celui-ci a été fait à partir de celui
fait en Python et ne respect pas la “Crystal way”.
Matmul
123456
go build matmul.go &&time ./matmul 1500
-143.500167
real 0m3.189s
user 0m3.156s
sys 0m0.049s
123456
time ruby matmul.rb 1500
-143.5001666666568
real 4m51.220s
user 4m50.842s
sys 0m0.300s
123456
crystal build --release matmul.cr &&time ./matmul 1500
-143.5
real 0m3.868s
user 0m3.879s
sys 0m0.024s
Encore une fois, Ruby est en retrait. Les performances entre Go et Crystal sont
comparables bien que Go est légèrement plus rapide.
Conclusion
Pour conclure, je dirais que Crystal, bien que très jeune, offre des
performances intéressantes. Le futur nous dira s’il s’agit d’un projet éphémère
ou s’il fera ses preuves.
J’espère pouvoir faire d’autres tests sur Crystal très bientôt. Je pense essayer
de faire une API. Suivez-moi sur Twitter pour rester au courant.
Scalingo et Clever Cloud sont deux entreprises françaises proposant des
services d’hébergement dans le cloud. Celles-ci proposent de retirer la gestion
des serveurs aux développeurs et ceux avec un bon nombre de technologies comme
Ruby, Go, Python ou PHP. Après avoir mis à l’épreuve Heroku, Digital Ocean et
Scalingo, j’ai pu déterminer que Scalingo offrait la meilleure performance des
trois. Qu’en est-il de Clever Cloud ?
Avant propos
Cet article se base sur l’article précédent. L’application testée, le code
ainsi que les pages testées sont les mêmes que celles utilisées pour faire la
comparaison entre Digital Ocean et Scalingo.
Je ne suis toujours pas administrateur système. Si vous constatez des
irrégularités dans mes tests, n’hésitez pas à m’en faire part et je les
corrigerais. Cependant, je pense utiliser une méthode simple se basant sur des
faits afin d’avoir une analyse pertinente.
L’application
Les Collectionneurs Associés est une application Rails qui n’a pas été conçue uniquement pour des tests. Cela nous
donne une meilleure idée du comportement des serveurs dans la réalité.
Les serveurs
L’offre la plus petite de Clever Cloud coûte 14,40€ par mois. Pour ce prix,
nous avons 1 CPU et 1Gb de RAM. Pour le même prix, chez Scalingo, nous avons
une machine avec 1CPU et 512Mb de RAM. Cependant, avec Scalingo, il est
possible de descendre plus bas avec 256Mb pour 7,20€. J’ai choisi de prendre
l’offre à 14,40€ dans les deux entreprises. Les deux serveurs se trouvent en
France.
Le serveur à partir duquel je fais mes tests est un serveur fourni par
Digital Ocean situé à Londres. Il est donc représentatif des clients de
l’Europe de l’Ouest, incluant la France, évidemment.
Le code
Le code n’est probablement pas le meilleur que j’ai écrit de ma vie, mais est
largement suffisant pour faire ce que je souhaitais.
Ce code permet d’entrer une adresse et de simuler un certain nombre de
visites concurrentes faisant un certain nombre de requêtes. À la fin de
l’exécution du programme, le temps de réponse de chaque processus est affiché.
Ce code ne prend pas en compte le chargement des images et des assets. Dans mon
cas, ces éléments sont stockés sur un CDN et sont donc indépendants du serveur
où l’application se trouve.
Test 1
Ce premier test affiche une page simple avec 11 requêtes. Voici les temps de
réponse :
Encore une fois, la différence des temps de réponse est négligeable. Pour le
moment, les performances de l’application hébergée sur les serveurs des deux
entreprises sont équivalentes.
Test 3
J’ai voulu poursuivre avec la page d’inscription, plus complexe.
Clever Cloud est légèrement plus rapide et, pour moi, ces résultats permettent
de rattraper les légers retards enregistrés précédemment.
Conclusion
Voici une conclusion très ennuyante pour un article comparatif : les
performances des deux offres sont équivalentes. Ces résultats ne me permettent
pas de trancher entre l’un et l’autre.
Les deux entreprises offrent un
très bon support, leurs interfaces d’administration sont très bien faites et
l’utilisation est deux systèmes est très simple. Pour choisir, il n’y a pas
d’autre solution que de tester vous-même et de faire votre propre opinion, kit
à vous baser sur des notions plus subjectives.
Malgré tout, à la fois Scalingo et Clever Cloud sont plus performants qu’Heroku
et Digital Ocean. Je leur donne une position qui serait décevante aux JO, la
médaille d’or exequo.
Il y a peu, j’ai parlé avec Yann Klis, de Scalingo, sur le forum de Human Coders. Suite à
cette discussion, j’ai souhaité effectuer un benchmark pour comparer les
performances de Digital Ocean et de Scalingo. Dans cet article, je vais exposer
les résultats des tests ainsi que ma méthodologie.
L’application
Voici l’application testée : https://www.associatedartcollectors.com/fr.
Il s’agit d’une application Rails optimisée, avec un système de mise
en cache, des assets sur un CDN, etc. Il s’agit d’une vraie application
(encore en développement). J’ai préféré l’utiliser plutôt que d’en faire une
uniquement pour des tests afin de mieux représenter la réalité.
Les serveurs
Le serveur Digital Ocean est situé à Francfort et coûte 10$ par mois pour
1Go de RAM. Scalingo, quant à lui, est situé en France et coûte 14.40€ pour
512Mb. Scalingo est donc légèrement plus cher pour moins de puissance.
Scalingo étant beaucoup plus facile à gérer, je pense que le temps économisé
au niveau de la gestion du serveur vaut largement le léger surplus de prix. En
effet, dans son utilisation, Scalingo ressemble davantage à Heroku qu’a Digital
Ocean. J’ai choisi de ne pas comparer Scalingo à Heroku, car, selon de
précédents tests, Digital Ocean offre une meilleure performance. J’ai voulu
comparer Scalingo au meilleur.
Note
Je ne suis pas administrateur système. Il est probablement possible de faire
mieux que ce que j’ai fait sur Digital Ocean. Ceci dit, je suis probablement
dans la même situation qu’un bon nombre de développeurs. L’intérêt des systèmes
similaires à Heroku ou Scalingo est également ne pas s’occuper du système. On
créer l’application en 2 minutes, on pousse l’application et ça marche. C’est
tout.
Les tests
La plus grosse part de marché de Scalingo est située en France. J’ai souhaité
commencer mes tests proches de la France. Pour cela, j’ai choisi de créer un
droplet Digital Ocean à Londres.
Mon objectif a été de simuler un certain nombre de requêtes et ceux de manière
concurrente. J’ai donc créé un code qui lance plusieurs processus
simultanément. Chaque processus effectue un certain nombre de requêtes. Le
temps effectué par chaque processus permet de comparer la performance. Plus les
requêtes se sont complétées rapidement, plus le serveur est efficace.
Le code
Le code n’est probablement pas le meilleur que j’ai écrit de ma vie, mais est
largement suffisant pour faire ce que je souhaitais.
Ce code permet d’entrer un nombre de processus simultané et le nombre de requêtes
fait pour chaque processus. De plus, il permet d’indiquer la page appelée par
le code. Pour les tests, j’ai choisi des pages différentes qui sont
représentatives de l’application.
Le code permet également d’afficher le statut de la requête envoyé, afin de
s’assurer qu’elles sont toutes valides, ainsi que le numéro de la requête
effectuée, juste pour voir la progression.
J’ai eu un problème avec le protocole SSL sur Digital Ocean. Pour le test, j’ai
simplement désactivé la vérification.
Test 1
Le premier test consiste uniquement à visiter une page. Pour l’affichage de
cette page, 11 requêtes SQL sont exécutées.
Digital Ocean :
Threads : 10
Requests per thread : 100
Temps de réponse :
163.755285
163.906003
164.070419
164.222361
164.379131
164.520751
164.717367
164.894695
165.059584
165.245484
Scalingo :
Threads : 10
Requests per thread : 100
Temps de réponse :
110.868637
110.982509
111.086731
111.195775
111.377322
111.482194
111.590034
111.697870
111.821420
111.938271
Ce premier test montre clairement que Scalingo offre un temps de réponse plus
rapide. Scalingo est quasiment deux fois plus rapide.
Test 2
Dans ce deuxième test, j’ai voulu tester le comportement avec plus de requêtes
concurrentes.
Digital Ocean :
Threads : 100
Requests per thread : 10
Temps de réponse :
150.062375
150.365437
150.532612
165.780870
165.941798
166.111019
Scalingo :
Threads : 100
Requests per thread : 10
Temps de réponse :
92.787676
92.877818
92.959175
...
102.423316
102.525122
102.622236
Encore cette fois, Scalingo est nettement supérieur. Pendant que les tests
s’effectuaient, je suis allé sur les deux sites avec mon navigateur. Même si cela
ne se reflète par sur les résultats du benchmark, j’ai constaté une grande
dégradation de performance pour les deux fournisseurs.
Test 3
Par la suite, j’ai souhaité tester une autre page un peu plus complexe. Sur
cette page, plus de requêtes SQL sont effectuées et il y a plus de code à
exécuter.
Digital Ocean :
Thread : 10
Request : 100
Temps de réponse :
204.311959
204.509295
204.683958
204.864534
205.055946
205.240614
205.440767
205.621924
205.807302
206.020173
Scalingo :
Thread : 10
Request : 100
Temps de réponse :
143.032002
143.170704
143.322095
143.458987
143.596800
143.742877
143.877174
144.017422
144.161915
144.303397
Sans surprise, le résultat est le même dans ce cas.
Test 4
Enfin, j’ai voulu tester une page encore plus complexe. Il s’agit du
formulaire d’inscription d’un utilisateur. J’utilise des gems comme SimpleForm
pour gérer le formulaire. Les listes des services, des pays et des régions
sont disponibles ce qui correspond à autant de requête SQL et de donnée à
traiter.
Digital Ocean :
Thread : 10
Request : 100
Temps de réponse :
420.542306
420.998163
421.375207
421.724786
422.231918
422.618054
422.983907
423.442330
423.815438
424.163915
Scalingo :
Thread : 10
Request : 100
Temps de réponse :
669.174683
669.800881
670.571744
671.256995
671.949939
672.582819
673.282322
673.983594
674.634513
675.310771
Cette fois, Digital Ocean est plus rapide. Je pense que c’est là que la
différence de puissance du serveur se fait sentir. Étant donné qu’il y a plus
de code a exécuter, le traitement nécessite plus de ressource, et donc, Digital
Ocean est avantagé. Ceci dit, cette page n’est pas l’une des plus utilisées et
je pense que la performance est plus importante pour les pages permettant
uniquement l’affichage.
Conclusion
Je pense que Scalingo gagne le duel. En plus d’offrir une performance plus
qu’honorable, la gestion du serveur est grandement simplifiée. Je conseille
vivement d’essayer cet outil si vous n’avez pas besoin d’avoir un accès complet
à la machine. Comme moi, les développeurs ne sont pas toujours des
administrateurs systèmes. Je trouve beaucoup plus simple de laisser la gestion
du serveur à quelqu’un d’autre. En plus d’être probablement mieux configuré que
ce que j’ai fait, c’est également plus sécuritaire, car, si une faille de
sécurité est découverte, le problème sera géré par des professionnels dans le
domaine.
Pour cet article, mes tests ont été effectués depuis Londres afin de
représenter la clientèle européenne. Pour ma part, je me situe au Québec. Dans
une seconde étape, je vais tester depuis des serveurs en Amérique du Nord et à
partir d’autres endroits dans le monde pour observer le comportement selon
les lieux.
Dans un prochain article, je vais faire la comparaison entre Clever Cloud et
Scalingo. Surveillez mes prochains articles pour connaitre les résultats de mon
prochain benchmark.
Il y a quelque temps, je me suis lancé dans la lecture du code source de
Rails. Mon but étant de mieux comprendre son mécanisme et, peut-être, de
devenir un contributeur. J’ai choisi de commencer par ActiveRecord étant donné
que je trouve que c’est le module le plus “magique” de Rails. J’ai donc voulu
comprendre mieux cette magie apparente. Dans cet article, je vais tenter de
comprendre et d’expliquer ce qu’il se cache derrière ActiveRecord::Base ainsi
que le noyau d’ActiveRecord.
Comme dans les articles précédents, je vais utiliser Pry pour
explorer. Je vous conseille fortement de lire le code en même temps que l’article.
Pour commencer, je vais reprendre
le code servant à créer des issues
dans ActiveRecord en y plaçant un breakpoint afin de pouvoir étudier le
comportement du modèle.
unlessFile.exist?('Gemfile')File.write('Gemfile',<<-GEMFILE) source 'https://rubygems.org' gem 'rails', github: 'rails/rails', ref: '6a7ac40dab' gem 'arel', github: 'rails/arel' gem 'sqlite3' gem 'pry-byebug' GEMFILEsystem'bundle'endrequire'bundler'Bundler.setup(:default)require'active_record'require'minitest/autorun'require'logger'# This connection will do for database-independent bug reports.ActiveRecord::Base.establish_connection(adapter:'sqlite3',database:':memory:')ActiveRecord::Base.logger=Logger.new(STDOUT)ActiveRecord::Schema.definedocreate_table:posts,force:truedo|t|endcreate_table:comments,force:truedo|t|t.integer:post_idendendclassPost<ActiveRecord::Baseendrequire'pry';binding.pryputs'Just a line to make pry stop'
ActiveRecord
Commençons par le commencement. Lorsque l’on charge la gem activerecord, le fichier
activerecord.rb est inclus.
Comme on peut le voir en lisant le code, celui-ci commence par chargé ses
dépendances.
On comprend donc que ActiveRecord dépend d’ActiveSupport, ActiveModel et
Arel. ActiveSupport est un module fournissant des fonctions utiles dans les
autres modules de Rails. ActiveModel contient les informations spécifiques au
modèle du design pattern MVC sans contenir les informations relatives à la
base de données. Arel, quant à lui, permet de créer des requêtes SQL en Ruby.
Les premières lignes du module sont les suivantes :
123
extendActiveSupport::Autoloadautoload:Attribute
Plutôt que de faire un require, ActiveRecord utilise le autoload
d’ActiveSupport. ActiveSupport permet de déterminer le nom du fichier à
charger en incluant le nom du module. Si l’on appelle autoload :Concern depuis
le module ActiveSupport, “active_support/concern” est déterminé. Cette chaîne
de caractère est ensuite envoyée à la fonction
autoload de
Ruby. De cette façon, les modules ne sont chargés qu’au moment où ils sont
appelés. C’est uniquement lorsque l’on utilisera ActiveSupport::Concern dont
le fichier sera chargé. Il s’agit d’un procédé permettant d’économiser la
mémoire puisque l’on ne charge pas ce qui est inutile.
On continuant la lecture du code, on peut s’apercevoir que certain des modules
sont chargé dans un block eager_load. Les chargements automatiques sont mis
dans un tableau. Les modules contenus dans ce tableau seront chargés quand la
fonction ActiveSupport.eager_load! sera appelée.
Certains des éléments sont chargés dans block autoload_under. Il s’agit,
simplement, d’un block ajoutant un dossier au chemin chargé.
Dans le bas du fichier, la fonction eager_load! est définie. Celle-ci permet
de forcer le chargement de sous-modules. Cette fonction à été introduit dans
ce commit.
D’après ce que je comprends, ce code peut-être utile quand on fait du
multithreading ou pour être “CoW friendly”. J’avoue ne pas savoir ce que veut
dire “CoW”. Si quelqu’un peut m’éclairer, ça me plairait!
Ces appels vont activer des événements stockés dans des tableaux puis stock à
nouveau pour une utilisation ultérieure.
ActiveRecord::Base
Tous les développeurs utilisant Ruby on Rails ont probablement fait un modèle
héritant de ActiveRecord::Base. Quand on regarde le fichier
base.rb,
on s’aperçoit qu’il s’agit uniquement d’un fichier en incluant d’autres et
faisant des include et des extend d’autres modules. Deux lignes sont
intéressantes, ActiveSupport.run_load_hooks(:active_record, Base) et include
Core.
L’avant-dernière ligne exécute les événements stockés précédemment.
ActiveRecord est donc indiqué comme étant le moteur par défaut de Arel.
Conclusion
Voici donc le coeur d’ActiveRecord. Je vais tenter de poursuivre ma lecture du
code et j’exposerai mes découvertes dans de prochains articles.
Les associations sont une part importante d’ActiveRecord. Il s’agit d’éléments
que l’on utilise dans les premières phases d’apprentissage de Rails. Bien que
son utilisation soit simple, l’implémentation est assez compliquée. Je vous
propose d’explorer le fonctionnement en observant le code exécuté à l’appel de
l’association belongs_to afin de comprendre le mécanisme complexe.
Dans cet article, je vous propose d’observer le comportement
d’ActiveModel::Dirty et d’en comprendre le fonctionnement en consultant le code
source. ActiveModel::Dirty est l’un des modules méconnus de Rails. Celui-ci
conserve l’historique des changements apportés aux attributs d’un objet et de
revenir en arrière si besoin.
ActiveRecord est probablement un des Gem qui impressionne le plus quand on
commence avec Rails. Comme par magie, ActiveRecord est capable de comprendre
seul quelle table est utilisée pour un modèle. Quand on connait Rails, on
comprend qu’il n’y a aucune magie. Dans cet article, je vais démystifier
ActiveRecord grâce à Pry.
Les IDE traditionnels possèdent généralement des outils avec une interface
graphique pour déboguer. Pry, quant à lui, offre les mêmes possibilités en
utilisant des lignes de commande. Pry est un REPL (Read-Eval-Print Loop) qui
permet de naviguer dans le code afin d’expliquer un bogue ou de comprendre le
fonctionnement interne d’une Gem. Nous allons voir comment faire dans cet article.
ActiveRecord est l’une des premières classes que l’on apprend à utiliser avec
Rails. Dans la vie d’un Rubyste, il est possible que l’on souhaite l’utiliser
sans Rails pour l’exécuter dans un programme en ligne de commande
ou dans une application utilisant Sinatra par exemple. Nous allons voir comment
faire en quelques lignes.
Grâce à Unity 3d, il est possible de créer une scène, d’y intégrer le module
Oculus et d’importer n’importe quel modèle 3d en quelques minutes. Dans cet
article, nous allons voir comment insérer un objet réel dans un tel
environnement.