W Scala wygląda tak:
object Speed {
var N=10000
var M=1000
def calc():Double = {
var sum:Double = 0.0;
for (i <- 0 until N) {
var x:Double=20.0*i/N-10.0;
sum+=Math.pow(Math.E,-x*x)*(20.0/N);
}
return sum;
}
def main(args: Array[String]) = {
var s:Double=0;
var start:Long=System.currentTimeMillis();
for (i <- 0 until M) {
s+=calc();
}
println((System.currentTimeMillis()-start)/(M*1.0));
}
}
W Groovy tak:
N=10000;
M=1000;
def double calc() {
double sum=0;
for (i in 0..N) {
double x = 20.0*i/N-10.0;
sum+=Math.pow(Math.E, -x*x)*20.0/N;
}
return sum;
}
long start = System.currentTimeMillis();
double s = 0;
for (i in 0..M) {
s+=calc();
}
println((System.currentTimeMillis()-start)/(M*1.0));
Trudno powiedzieć która składnia jest bardziej czytelna, na pewno bardziej mi się podoba zapis pętli
for
w Groovy.Jednak okazało się, że Groovy poległo na całej linii jeśli chodzi o wydajność ;-)
Scala [na JVM] potrzebowała na jedno wykonanie funkcji
calc()
średnio 5.15 ms, a Groovy na to samo potrzebowało aż 138.79 ms! Dla porównania Java potrzebowała 4.84 ms.Na razie nie czuję się przekonany do żadnego z tych języków ;-) wydaje się tak na pierwszy rzut oka, że Scala jest taka jakby dojrzalsza, a Groovy choć bardziej podobne do Java'y to tak celuje w język skryptowy.
Podobne postybeta
Raspberry Pi to nie jest demon prędkości ;-)
Całkujący Dart ;-)
C# i Java okazały się szybsza od Pythona :-) [było Java okazała się szybsza od C# i Pythona]
ETA - liczymy szacowany czas zakończenia [przybycia ;-)]
Go wolniejsze od C i JavaScript, i ciut szybsze niż Java ;-) [a jednak od Java'y też wolniejsze]
Bardziej idiomatyczna wersja Scali (będzie ciut wolniejsza przez boxing Double'i):
OdpowiedzUsuńhttp://gist.github.com/274519
Er, zapomniałem zmienić vary na vale
OdpowiedzUsuńTo Twoje rozwiązanie ma jakiś urok :-) ale jak dla mnie taka konstrukcja:
OdpowiedzUsuńdef calc = (0.0 /: (0 until N)) {(s, i) =>
val x = 20.0 * i / N - 10.0
s + Math.pow(Math.E, -x * x) * (20.0 / N)
}
wygląda strasznie nieczytelnie.
rozumiem, ze s ma być zainicjalizowane wartością 0.0, po czym i ma przybierać wartości od 0 do N [bez N jak mniemam], następnie do x ma być przypisany wyni, a s ma zostać zwiększone.......
Do końca taki paradygmat do mnie nie przemawia :-) choć to pewnie sprawa przyzwyczajenia.
A co do prędkości to ta wersja jest tylko nieznacznie [o dziesiątki mikrosekund wolniejsza od wersji którą ja przedstawiłem]
Zamiast:
OdpowiedzUsuń(0.0 /: (0 until N)) {(s, i) => ...
możesz:
(1 until 10).foldLeft(0.0){(akumulator, i) => ...}
/: jest prawie aliasem foldLeft. Metody kończące się dwukropkiem aplikuje się odwrotnie (receiver po prawej stronie) Oczywiście notacją z kropką można wymusić:
1 :: 2 :: Nil
to to samo co:
Nil.::(2).::(1)
Co do przyzwyczajeń to oczywiście zgadzam się. Jednak jak zaczniesz pisać więcej używając paradygmatu funkcyjnego ten zapis stanie się dla Ciebie bardziej naturalny niż imperatywna pętla.
Dla przykładu, mając listę zwiększ wszystkie elementy o jeden, wybierz elementy większe od 3 i oblicz ich sumę:
List(5, 1, 10) map (1+) filter (3<) reduceLeft(_+_)
Groovy jest interpretowany, Scala kompilowana. To jakbyś porównywał Pythona z C++.
OdpowiedzUsuńA ja bym to w Scali napisał tak: http://gist.github.com/276105
PS przemelek, zrobiłbyś jakiś markdown w komentarzach, można by tu wklejać kawałki kodu zamiast linkować w świat.
@Mekk wpadłem na to, że Groovy jest interpretowane, a Scala kompilowana, po prostu myślałem, że oba są kompilowane. No i okazuje się, że to porównanie Pythona z C++ nie do końca jest słuszne bo po pierwsze interpretowany Python był jednak szybszy niż Groovy ;-) po drugie gdy go skompilować JITem to też znacznie przyśpiesza :-)
OdpowiedzUsuńZ tego co mi wiadomo to "se ne da", i tak jest lepiej niż standardowo w BlogSpot'cie bo mam ustawione komentarze pod postem, a standardowo wyskakuje w ogóle oddzielne okienko.
To że groovy jest językiem dynamicznym to nie znaczy że nie jest kompilowany. Groovy traci na rzecz scali oczywiście przez dynamic dispatch. Btw, pokaż kod dla którego groovy jest wolniejszy od pythona.
OdpowiedzUsuńKod w Groovy masz powyżej :-), jedna iteracja calc() wykonuje się średnio jak napisałem wyżej 138.79 ms, kod w Pythonie [samą funkcję calc()] znajdziesz w tym poście http://przemelek.blogspot.com/2009/10/java-okazaa-sie-szybsza-od-c-i-pythona.html [albo 1 link w sekcji podobne posty powyżej] i jedna iteracja calc() wykonuje się 11.7-11.8 ms, czyli 13 razy szybciej w Pythonie niż w Groovy :-)
OdpowiedzUsuńTutaj jak Ci się nie chce zaglądać do tego drugiego posta masz kod w Pythonie, mam nadzieje, że formatowania się zbytnio nie popsują ;-)
import time
import math
N=10000
M=1000
def calc():
sum=0
for i in range(0,N):
x=20.0*i/N-10
sum=sum+math.e**(-x**2)*(20.0/N)
return sum
start = time.time()
for i in range(0,M):
calc()
print ((time.time()-start)*1000/M)
OK, popsuły się ;-)
OdpowiedzUsuńNo to wszystko jasne. Mimo że groovy ssie jak nie wiem to i tak okazał sie nieznacznie szybszy od pythona.
OdpowiedzUsuńDwie rzeczy, a nawet trzy:
double x = 20.0*i/N-10.0;
Tutaj w pętli tworzony jest tymczasowy BigDecimal (dla operacji "/"), który jest niesamowicie ciężki.
Kolejna sprawa, po zsamplowaniu tego przykładu:
Stub + native Method
58.4% 0 + 770 java.lang.Throwable.fillInStackTrace
5.5% 64 + 9 calc.calc
2.4% 28 + 3 java.math.BigDecimal.setScale
Compiled + native Method
6.2% 82 + 0 ExceptionBlob
Interpreted + native Method
3.6% 1 + 46
groovy.lang.MetaClassImpl.checkIfStdMethod
1.0% 0 + 13 java.lang.Throwable.fillInStackTrace
Czyli właściwie 5.5% dzieje się w calc, a 70% czasu java traci na tworzeniu stacktrace'a i rzucaniu jakiegoś wyjątku w bebechach (prawdopodobnie w BigDecimal)
Wreszcie, czy testy prowadzisz na kompilatorze serwerowym hotspota ? i czy grzejesz te przykłady ?
W przeciwnym przypadku wszystkie testy uruchamiane do tej pory na jvm można wyrzucić do śmieci...
N=10000
M=1000
def double calc() {
double sum=0 // dzięki temu zapobiegasz tworzeniu BigDecimala
double DN=N // ...
for (i in 0..N) {
double di = i
double x = 20.0*di/DN-10.0;
sum+=Math.pow(Math.E, -x*x)*20.0/DN;
}
return sum;
}
def bench() {
long start = System.currentTimeMillis();
double s = 0
for (i in 0..M) {
s+=calc();
}
println((System.currentTimeMillis()-start)/(M*1.0));
}
bench()
bench()
bench()
bench()
Powyższy przykład można oczywiście jeszcze przyśpieszyć...
Jeśli odpalić Twój program w Groovy z przełącznikiem serwer to jest trochę szybszy od Pythona :-) bo średnia z 4 wypisanych wyników to 10.09 ms, dla Pythona jest to 10.74 ms.
OdpowiedzUsuńTak się przy okazji pobawiłem przełącznikiem -server i mnie Java zaskoczyła :-) Java 1.5 z przełącznikiem -server potrzebowała średnio 1.34 ms, ale już 1.6 3.67 ms... choć fakt, że chodzi mi po głowie coś o jakimś zamieszaniu z arytmetyką zmiennoprzecinkową.
1.5 jest szybsza od 1.6 po rozgrzaniu ? To też byłaby niespodzianka...
OdpowiedzUsuńTak sobie czytam tego posta oraz komentarze i zastanawiam się jak wielu z was na codzień rozwiązuje numeryczne zadania w pracy a ilu z was używa języków programowania do pobrania inputu od usera, sprawdzenia go i wygenerowania outputu na bazie odczytów z bazy danych...
OdpowiedzUsuńTe 130ms są niczym wobec jednego zapytania do bazy danych, która jest na drugim końcu świata (albo labu :D)...
Co do wydajności to zgadzam się, że Groovy jest wolniejszy od Scali/Javy - ale nie jest to język do tego samego... Groovy lepiej nadaje się do deklarowania rzeczy (konfiguracje, opis funkcjonalności, DSLe - patrz Grails) a sama Java/Scala lepiej nadaje się do zastowań numerycznych (czego i tak jest znacznie mniej w ogólnym rozrachunku...)
Moim zdaniem np. Lift czy tym podobne rozwiązania ze statycznym językiem pod spodem nigdy nie będą tak eleganckie jak platforma oparta o język dynamiczny.