Compartir
Després d’escriure l’anterior entrada sobre el llenguatge de programació go i de llegir un comentari on es diu que Go és força lent, m’he decidit a fer uns benchmarks molt senzills.
la idea és comparar la velocitat d’execució de la devolució de l’logaritme natural d’un nombre donat i la introducció de el mateix en un mapa o hash. Òbviament, la velocitat depèn en gran mesura de la implementació de l’contenidor a cada llenguatge.
Quant a Go, he fet servir el compilador 6g basat en Inferno per que no vull instal·lar una versió inestable de l’compilador GCC en el meu Gentoo Linux i cagar-se de res, suposadament, el gccgo optimitza més el codi que el 6g.
NOTA: Aquest benchmark és bastant cutre i simple, només ho vaig fer per alleujar la meva curiositat i veure més o menys per on caminava el rendiment de Go. En el futur i quan tingui més temps, faré un en seriós afegint Java, Ruby i Scala.
Implementació C
Anem a començar per la implementació en C d’aquest senzill benchmark. He utilitzat les llibreries uthash per crear a l’Hash, pots trobar-les en sourceforge
Codi becnhmark.c
#include <math .h>#include "uthash.h"struct map { float id; float value; UT_hash_handle hh;};struct map *dict = NULL;void add_data(float data_id, float data){ struct map *m; m = malloc(sizeof(struct map)); m->id = data_id; m->value = data; HASH_ADD_INT( dict, id, m );}int main(){ for (int i = 0; i < 5000000; i++) { add_data(i, log(i)); } return 0;}
Compilació
genbeta@dev $ gcc -O3 -Wall -c -fmessage-length=0 -MMD -MP -std=c99 benchmark.cgcc -lm -o cbench benchmark.o
Implementació C ++
Per a C ++ fem servir el contenidor map de la STL
Codi benchmark.cpp
#include <iostream>#include <map>#include <cmath>using namespace std;int main(){ map <float , float> dict; for (int i = 0; i < 5000000; i++) { dict = log(i); } return 0;}
Compilació
genbeta@dev $ g++ -O3 -Wall -c -fmessage-length=0 -MMD -MP benchmark.cpp -o cppbenchmark.og++ -o cppbench cppbenchmark.o
Implementació en C # de Mono
Els experts en .NET C # que em perdonin però no tinc ni idea de C # no sé si aquest snippet està molt bé però el cas és que funciona de meravella, si hi ha algun error prego que em corregeixin.
Codi benchmark.cs
using System;using System.Collections;namespace CSBenchmark{ class MainClass { public static void Main (string args) { Hashtable dict = new Hashtable(); for (int i = 0; i < 5000000; i++) { dict.Add(i, Math.Log(i)); } } }}
Compilació
genbeta@dev $ /usr/bin/gmcs /noconfig "/out:./benchmark.exe" "/r:/usr/lib/mono/2.0/System.dll" /nologo /warn:4 /debug:+ /debug:full /optimize- /codepage:utf8 /platform:x86 "/define:DEBUG" /t:exe "./benchmark.cs"
Implementació en Python
Aquesta sigui segurament la més senzilla de totes:)
Codi benchmark.py
import mathd = {}for i in range(1, 5000000): d = math.log(i)
Implementació en Go
Aquesta també és molt senzilla, està més o menys a l’una amb C ++
Codi benchmark.go
package mainimport "math"func main() { m := make(map float64) var i float64 = 1 for ; i < 5000000.; i++ { m = math.Log(i) }}
Compilació
genbeta@dev $ 6g benchmark.go && 6l benchmark.6 && mv 6.out gobench
Benchmarking
Per a l’execució dels benchmarks utilitzo la comanda time de GNU sobre el sistema següent:
- sistema Operatiu: Gentoo Linux
- Arquitectura: x86_64
- Processador: Intel ® Core ™ i7 CPU @ 2.80GHz
- Velocitat RAM: 1666
Llenguatge | Temps Usuari | Temps Sistema | Temps Real | Ús de CPU | Ús de Ram | Canvis de Context |
---|---|---|---|---|---|---|
C | 1.90s | 0.27s | 2.19s | 99% | 1826704kB | 1 |
C ++ | 4.07s | 0.14s | 4.24s | 99% | 941808kB | 1 |
C # | 1.72s | 0.29 s | 2.01s | 131% | 2032176kB | 1155 |
Python CPY thon 2.7.2 | 3.43s | 0.37s | 3.86s | 99% | 2101312kB | 1 |
Python PyPy-C1.5 | 1.57s | 0.33s | 2.70s | 99% | 2442032kB | 280 |
Go | 2.76s | 0.16s | 2.93s | 99% | 1044304kB | 1 |
és destacable com C # és el més ràpid amb 2.01, no tinc ni idea de si això és així per la implementació de Mono, de el llenguatge en si, o si és que no he fet bé el benchmark en C # però la veritat és que estic gratament impressionat. El segueix molt de prop C amb 2.19s, després la implementació PyPy de Python 2.7 amb 2.70, després d’ell li toca el torn a Go amb 2.93, la implementació de CPython 2.7 el segueix amb 3.86 i molt tristament l’últim lloc l’ocupa C ++ amb 1 4.24
També podem apreciar com Mono i PyPy utilitzen més d’un nucli de la meva processador i7 mentre que la resta només utilitzen un nucli a l’99%.
quant a el consum de memòria , C ++ és el més òptim seguit molt de prop per Go. C # i Python -en les dues implementaciones- fan un ús molt més gran de recursos.
Go Profiling
Però ull, aquest benchmark ha estat executat sense utilitzar profiling a Go. En Go s’usen les eines de profiling per detectar colls d’ampolla i arreglar-los millorant el rendiment considerablement. El paper a què un usuari feia referència en l’anterior post, és un benchmark sense profiling a Go i ja ha tingut la seva resposta per part dels creadors de l’llenguatge en el bloc oficial.Utilitzarem profiling a veure quant podem millorar el rendiment del nostre snippet a Go.
Primer modifiquem el codi de la nostra funció per afegir suport per cpuprofiling, el nostre codi quedaria així: a
package main<br />import ( “math” “os” “flag” “log” “runtime/pprof”<br />)<br />var cpuprofile = flag.String(“cpuprofile”, “”, “write cpu profile to file”)<br />func main() { flag.Parse() if cpuprofile != “” { f, err := os.Create(cpuprofile) if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } m := make(map float64)
var i float64 = 1 for ; i < 5000000.; i++ { m = math.Log(i) }
}
Ara podem executar el snippet amb el paràmetre cpuprofile i depurar amb gopprof:
./gobench -cpuprofile=gobench.profgopprof gobench gobench.profWelcome to pprof! For help, type 'help'.(pprof) top10Total: 290 samples 149 51.4% 51.4% 224 77.2% hash_insert_internal 32 11.0% 62.4% 32 11.0% math.Log 17 5.9% 68.3% 17 5.9% runtime.mcpy 17 5.9% 74.1% 17 5.9% runtime.memmove 16 5.5% 79.7% 16 5.5% memhash 13 4.5% 84.1% 13 4.5% runtime.memclr 13 4.5% 88.6% 13 4.5% scanblock 8 2.8% 91.4% 250 86.2% runtime.mapassign 5 1.7% 93.1% 8 2.8% MHeap_AllocLocked 5 1.7% 94.8% 5 1.7% memwordcopy(pprof)
Podem veure que l’aplicació ocupa la majoria dels cicles de la CPU i el temps d’execució en introduir valors en el map. El depurat d’aplicacions desenvolupades en Go amb gopprof va molt més enllà de la intenció d’aquesta entrada, però comprovant els llistats de el codi i els samples emprats en cada línia podem saber que el coll d’ampolla està en la línia m = math.Log(i)
que acumula 285 dels 290 samples de l’execució.
Queda clar que l’ús d’un map per aquesta benchmark no és gens òptim i podríem utilitzar un slice per emmagatzemar les dades. Si editem l’arxiu i vam comentar la línia /*m := make(map float64)*/
i fem servir un slice en lloc d’un map var p float64; m := make(float64, len(p))
i tornem a generar un arxiu .prof i depurem: a
Welcome to pprof! For help, type 'help'.(pprof) top10 -cumTotal: 25 samples 7 28.0% 28.0% 25 100.0% main.main 0 0.0% 28.0% 25 100.0% runtime.initdone 0 0.0% 28.0% 25 100.0% runtime.mainstart 18 72.0% 100.0% 18 72.0% math.Log(pprof)
Vam comprovar que ara la pràctica totalitat de el temps està dedicada a la funció que retorna el logaritme natural de i
i ja no hi ha cap coll d’ampolla. Anem a eliminar el codi que introdueix la funcionalitat de depuració comentant i passem el benchmark altra vegada.
Llenguatge | Temps Usuari | Temps Sistema | Temps Real | Ús de CPU | Ús de Ram | Canvis de Context |
---|---|---|---|---|---|---|
Go | 0.22s | 0.01s | 0.24s | 99% | 314512kB | 1 |
Ouch! Una millora notable. Però siguem justos, canviarem el hash en la implementació de C per un simple array i convertir el benchmark en simplement calcular el logaritme natural de ie introduir el valor en una matriu.
Llenguatge | Temps Usuari | Temps Sistema | Temps Real | Ús de CPU | Ús de Ram | Canvis de Context |
---|---|---|---|---|---|---|
C | 0.31s | 0.00s | 0.31s | 99% | 1824kB | 1 |
Go | 0.22 s | 0.01s | 0.24s | 99% | 314512kB | 1 |
En aquest cas, Go és fins i tot més ràpid que C retornant el logaritme natural de i
.
Conclusió
Sembla que el tipus map
no està molt fi en Go, tot i així, la velocitat ie ficiència de el llenguatge està entre C i C ++. En aquest cas, no podem fer res cent per cent legítim per eliminar el coll d’ampolla per que és part específica de el test que hem realitzat. No se si cal esperar quatre anys com he llegit per aquí al fet que el llenguatge estigui més madur o no. És clar que encara li falta maduresa i necessita unes quantes implementacions en sistemes per poder millorar. El que si que sé és que jo al menys li seguiré la pista, i més ara que està disponible a Google App Engine i és més eficient que Python i Java.
a Més a Genbeta Dev | Introducció a l’llenguatge de programació Go