Wziąłem Problem nr 12 z Project Euler jako ćwiczenie programistyczne i do porównania moich (na pewno nie optymalnych) implementacji w C, Python, Erlang i Haskell. Aby uzyskać wyższe czasy wykonania, szukam pierwszego numeru trójkąta z więcej niż 1000 dzielników zamiast 500, jak podano w pierwotnym problemie.
Wynik jest następujący:
DO:
lorenzo@enzo:~/erlang$ gcc -lm -o euler12.bin euler12.c
lorenzo@enzo:~/erlang$ time ./euler12.bin
842161320
real 0m11.074s
user 0m11.070s
sys 0m0.000s
Pyton:
lorenzo@enzo:~/erlang$ time ./euler12.py
842161320
real 1m16.632s
user 1m16.370s
sys 0m0.250s
Python z PyPy:
lorenzo@enzo:~/Downloads/pypy-c-jit-43780-b590cf6de419-linux64/bin$ time ./pypy /home/lorenzo/erlang/euler12.py
842161320
real 0m13.082s
user 0m13.050s
sys 0m0.020s
Erlang:
lorenzo@enzo:~/erlang$ erlc euler12.erl
lorenzo@enzo:~/erlang$ time erl -s euler12 solve
Erlang R13B03 (erts-5.7.4) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.7.4 (abort with ^G)
1> 842161320
real 0m48.259s
user 0m48.070s
sys 0m0.020s
Haskell:
lorenzo@enzo:~/erlang$ ghc euler12.hs -o euler12.hsx
[1 of 1] Compiling Main ( euler12.hs, euler12.o )
Linking euler12.hsx ...
lorenzo@enzo:~/erlang$ time ./euler12.hsx
842161320
real 2m37.326s
user 2m37.240s
sys 0m0.080s
Podsumowanie:
- C: 100%
- Python: 692% (118% z PyPy)
- Erlang: 436% (135% dzięki RichardC)
- Haskell: 1421%
Przypuszczam, że C ma dużą zaletę, ponieważ używa długich do obliczeń, a nie liczb całkowitych o dowolnej długości, jak pozostałe trzy. Ponadto nie musi najpierw ładować środowiska wykonawczego (czy inne?).
Pytanie 1:
Czy Erlang, Python i Haskell tracą prędkość z powodu użycia liczb całkowitych o dowolnej długości, czy też nie, dopóki wartości są mniejsze niż MAXINT
?
Pytanie 2: Dlaczego Haskell jest taki wolny? Czy istnieje flaga kompilatora, która wyłącza hamulce, czy jest to moja implementacja? (To ostatnie jest całkiem prawdopodobne, ponieważ Haskell to książka z siedmioma pieczęciami).
Pytanie 3: Czy możesz mi podpowiedzieć, jak zoptymalizować te wdrożenia bez zmiany sposobu określania czynników? Optymalizacja w jakikolwiek sposób: ładniejsza, szybsza, bardziej „natywna” dla języka.
EDYTOWAĆ:
Pytanie 4: Czy moje implementacje funkcjonalne pozwalają na LCO (optymalizacja ostatniego połączenia, czyli eliminację rekurencji ogona), a tym samym unikają dodawania niepotrzebnych ramek do stosu połączeń?
Naprawdę próbowałem zaimplementować ten sam algorytm jak najbardziej podobny w czterech językach, chociaż muszę przyznać, że moja znajomość Haskell i Erlang jest bardzo ograniczona.
Zastosowane kody źródłowe:
#include <stdio.h>
#include <math.h>
int factorCount (long n)
{
double square = sqrt (n);
int isquare = (int) square;
int count = isquare == square ? -1 : 0;
long candidate;
for (candidate = 1; candidate <= isquare; candidate ++)
if (0 == n % candidate) count += 2;
return count;
}
int main ()
{
long triangle = 1;
int index = 1;
while (factorCount (triangle) < 1001)
{
index ++;
triangle += index;
}
printf ("%ld\n", triangle);
}
#! /usr/bin/env python3.2
import math
def factorCount (n):
square = math.sqrt (n)
isquare = int (square)
count = -1 if isquare == square else 0
for candidate in range (1, isquare + 1):
if not n % candidate: count += 2
return count
triangle = 1
index = 1
while factorCount (triangle) < 1001:
index += 1
triangle += index
print (triangle)
-module (euler12).
-compile (export_all).
factorCount (Number) -> factorCount (Number, math:sqrt (Number), 1, 0).
factorCount (_, Sqrt, Candidate, Count) when Candidate > Sqrt -> Count;
factorCount (_, Sqrt, Candidate, Count) when Candidate == Sqrt -> Count + 1;
factorCount (Number, Sqrt, Candidate, Count) ->
case Number rem Candidate of
0 -> factorCount (Number, Sqrt, Candidate + 1, Count + 2);
_ -> factorCount (Number, Sqrt, Candidate + 1, Count)
end.
nextTriangle (Index, Triangle) ->
Count = factorCount (Triangle),
if
Count > 1000 -> Triangle;
true -> nextTriangle (Index + 1, Triangle + Index + 1)
end.
solve () ->
io:format ("~p~n", [nextTriangle (1, 1) ] ),
halt (0).
factorCount number = factorCount' number isquare 1 0 - (fromEnum $ square == fromIntegral isquare)
where square = sqrt $ fromIntegral number
isquare = floor square
factorCount' number sqrt candidate count
| fromIntegral candidate > sqrt = count
| number `mod` candidate == 0 = factorCount' number sqrt (candidate + 1) (count + 2)
| otherwise = factorCount' number sqrt (candidate + 1) count
nextTriangle index triangle
| factorCount triangle > 1000 = triangle
| otherwise = nextTriangle (index + 1) (triangle + index + 1)
main = print $ nextTriangle 1 1
Euler12[x_Integer] := Module[{s = 1}, For[i = 2, DivisorSigma[0, s] < x, i++, s += i]; s]
. Hurra!Odpowiedzi:
Korzystanie
GHC 7.0.3
,gcc 4.4.6
,Linux 2.6.29
na x86_64 Core 2 Duo (2,5 GHz) maszyny, tworzenia stosującghc -O2 -fllvm -fforce-recomp
do Haskellgcc -O3 -lm
C.-O3
)-O2
flagi)factorCount'
kod nie jest wyraźnie wpisany i domyślnie ustawionyInteger
(dzięki Danielowi za naprawienie mojej błędnej diagnozy tutaj!). Nadanie wyraźnego podpisu typu (co i tak jest standardową praktyką) przy użyciu,Int
a czas zmienia się na 11,1 sekundyfactorCount'
tobie niepotrzebnie zadzwoniłeśfromIntegral
. Poprawka skutkuje jednak brakiem zmian (kompilator jest inteligentny, na szczęście dla ciebie).mod
gdzierem
jest szybszy i wystarczający. To zmienia czas na 8,5 sekundy .factorCount'
stale stosuje dwa dodatkowe argumenty, które nigdy się nie zmieniają (number
,sqrt
). Transformacja pracownik / opakowanie zapewnia nam:Zgadza się, 7,95 sekundy . Konsekwentnie pół sekundy szybciej niż roztwór C . Bez
-fllvm
flagi wciąż otrzymuję8.182 seconds
, więc backend NCG ma się dobrze również w tym przypadku.Wniosek: Haskell jest niesamowity.
Wynikowy kod
EDYCJA: Teraz, kiedy to zbadaliśmy, odpowiedzmy na pytania
W Haskell użycie
Integer
jest wolniejsze niż,Int
ale o ile wolniejsze, zależy od wykonanych obliczeń. Na szczęście (dla maszyn 64-bitowych)Int
wystarczy. Ze względu na przenośność powinieneś prawdopodobnie przepisać mój kod do użyciaInt64
lubWord64
(C nie jest jedynym językiem zlong
).Tak odpowiedziałem powyżej. Odpowiedź brzmiała:
-O2
rem
niemod
(często zapomniana optymalizacja) iTak, to nie był problem. Dobra robota i cieszę się, że to rozważałeś.
źródło
rem
jest tak naprawdę podskładnikiemmod
operacji (nie są takie same). Jeśli spojrzysz na bibliotekę GHC Base, zobaczyszmod
testy dla kilku warunków i odpowiednio dostosujesz znak. (patrzmodInt#
wBase.lhs
)mod
sięrem
po przeczytaniu tej odpowiedzi (heh, ups). Zobacz link do moich czasów, ale krótka wersja jest „prawie identyczna z C”.factorCount'
był rekurencyjny, pomyślałbym, że kompilator może wykryć dodatkowe parametry, które nie ulegają zmianie, i zoptymalizować rekurencję tylko dla zmieniających się parametrów (w końcu Haskell jest czystym językiem, powinno to być łatwe). Czy ktoś myśli, że kompilator mógłby to zrobić, czy powinienem wrócić i przeczytać więcej artykułów teoretycznych?Występują pewne problemy z implementacją Erlang. Jako punkt odniesienia dla następującego, mój zmierzony czas wykonania dla niezmodyfikowanego programu Erlang wyniósł 47,6 sekundy, w porównaniu do 12,7 sekundy dla kodu C.
Pierwszą rzeczą, którą powinieneś zrobić, jeśli chcesz uruchomić intensywny obliczeniowo kod Erlanga, jest użycie kodu natywnego. Kompilacja z
erlc +native euler12
zmniejszyła czas do 41,3 sekundy. Jest to jednak znacznie niższe przyspieszenie (tylko 15%) niż oczekiwano po kompilacji natywnej na tego rodzaju kodzie, a problemem jest twoje użycie-compile(export_all)
. Jest to przydatne w eksperymentach, ale fakt, że wszystkie funkcje są potencjalnie dostępne z zewnątrz, powoduje, że natywny kompilator jest bardzo konserwatywny. (Normalny emulator BEAM nie ma tak dużego wpływu). Zastąpienie tej deklaracji-export([solve/0]).
daje znacznie lepsze przyspieszenie: 31,5 sekundy (prawie 35% od wartości początkowej).Ale sam kod ma problem: dla każdej iteracji w pętli factorCount wykonuje się ten test:
Kod C tego nie robi. Generalnie rzetelne porównanie różnych implementacji tego samego kodu może być trudne, szczególnie jeśli algorytm jest numeryczny, ponieważ musisz mieć pewność, że faktycznie robią to samo. Niewielki błąd zaokrąglenia w jednej implementacji spowodowany gdzieś rzutem czcionki może spowodować, że wykona on o wiele więcej iteracji niż w drugiej, mimo że obie ostatecznie osiągną ten sam wynik.
Aby wyeliminować to możliwe źródło błędów (i pozbyć się dodatkowego testu w każdej iteracji), przepisałem funkcję factorCount w następujący sposób, ściśle wzorowanym na kodzie C:
Ta przepisana, nie
export_all
i natywna kompilacja dała mi następujący czas działania:co nie jest takie złe w porównaniu do kodu C:
biorąc pod uwagę, że Erlang wcale nie jest nastawiony na pisanie kodu numerycznego, jest o 50% wolniejszy niż C w takim programie jak ten, co jest całkiem dobre.
Wreszcie odnośnie twoich pytań:
Pytanie 1: Czy erlang, python i haskell tracą prędkość z powodu użycia liczb całkowitych o dowolnej długości, czy nie, dopóki wartości są mniejsze niż MAKSYMALNA?
Tak, trochę. W Erlang nie można powiedzieć „używaj arytmetyki 32/64-bitowej z zawijaniem”, więc jeśli kompilator nie udowodni pewnych ograniczeń na liczbach całkowitych (i zwykle nie może), musi sprawdzić wszystkie obliczenia, aby zobaczyć jeśli mogą zmieścić się w jednym oznakowanym słowie lub jeśli musi zamienić je w bignum alokowane na stercie. Nawet jeśli w środowisku wykonawczym nigdy nie są używane bignum, kontrole te należy wykonać. Z drugiej strony oznacza to , że wiesz, że algorytm nigdy nie zawiedzie z powodu nieoczekiwanego zawinięcia liczb całkowitych, jeśli nagle podasz mu większe dane wejściowe niż wcześniej.
Pytanie 4: Czy moje implementacje funkcjonalne pozwalają na LCO, a tym samym unikają dodawania niepotrzebnych ramek do stosu wywołań?
Tak, Twój kod Erlang jest poprawny w odniesieniu do optymalizacji ostatniego połączenia.
źródło
Jeśli chodzi o optymalizację Pythona, oprócz korzystania z PyPy (w celu uzyskania imponujących przyspieszeń przy zerowej zmianie kodu), możesz użyć łańcucha narzędzi do tłumaczenia PyPy do skompilowania wersji zgodnej z RPython lub Cython do zbudowania modułu rozszerzenia, zarówno które są szybsze niż wersja C w moich testach, z modułem Cython prawie dwa razy szybszym . Dla porównania dołączam również wyniki testu porównawczego C i PyPy:
C (skompilowany z
gcc -O3 -lm
)PyPy 1.5
RPython (przy użyciu najnowszej wersji PyPy
c2f583445aee
)Cython 0,15
Wersja RPython ma kilka kluczowych zmian. Aby przetłumaczyć na samodzielny program, musisz zdefiniować swój
target
, który w tym przypadku jestmain
funkcją. Oczekuje się, że zostanie zaakceptowany,sys.argv
ponieważ jest to tylko argument, i wymagane jest zwrócenie wartości int. Możesz to przetłumaczyć za pomocą translate.py,% translate.py euler12-rpython.py
który tłumaczy na C i kompiluje dla ciebie.Wersja Cython została przepisana jako moduł rozszerzenia
_euler12.pyx
, który importuję i wywołuję z normalnego pliku python. Jest_euler12.pyx
to zasadniczo taki sam jak wersja, z pewnymi dodatkowymi deklaracjami typu statycznego. Plik setup.py ma normalną płytę kotłową do zbudowania rozszerzenia, używającpython setup.py build_ext --inplace
.Szczerze mówiąc, mam bardzo małe doświadczenie z RPython lub Cython i byłem mile zaskoczony wynikami. Jeśli używasz CPython, pisanie fragmentów kodu obciążających procesor w module rozszerzenia Cython wydaje się bardzo łatwym sposobem na optymalizację programu.
źródło
Implementacja C jest nieoptymalna (jak wskazał Thomas M. DuBuisson), wersja używa 64-bitowych liczb całkowitych (tj. Długi typ danych). Później zbadam listę zestawów, ale przy zgadywanym przypuszczeniu, w skompilowanym kodzie dzieje się dostęp do pamięci, co znacznie spowalnia użycie 64-bitowych liczb całkowitych. Jest to ten lub wygenerowany kod (fakt, że możesz zmieścić mniej 64-bitowych liczb całkowitych w rejestrze SSE lub zaokrąglić liczbę podwójną do 64-bitowej liczby całkowitej, jest wolniejszy).
Oto zmodyfikowany kod (wystarczy wymienić długo z int i jawnie inlined factorCount, chociaż nie sądzę, że jest to konieczne z gcc -O3):
Bieganie + synchronizacja daje:
Dla porównania, implementacja haskell przez Thomasa we wcześniejszej odpowiedzi daje:
Wniosek: nie bierze nic od ghc, jest to świetny kompilator, ale gcc zwykle generuje szybszy kod.
źródło
2.5 seconds
podczas gdy podobna modyfikacja do kodu Haskell (przejście do Word32, dodanie pragmy INLINE) powoduje uruchomienie4.8 seconds
. Być może coś da się zrobić (wydaje się, że nie jest trije) - wynik gcc jest z pewnością imponujący.Spójrz na tego bloga . W ciągu ostatniego roku zrobił kilka problemów z Project Euler w Haskell i Python i ogólnie stwierdził, że Haskell jest znacznie szybszy. Myślę, że między tymi językami ma to więcej wspólnego z Twoją płynnością i stylem kodowania.
Jeśli chodzi o szybkość Pythona, używasz niewłaściwej implementacji! Wypróbuj PyPy , a dla takich rzeczy znajdziesz to znacznie, dużo szybciej.
źródło
Implementację Haskell można znacznie przyspieszyć, korzystając z niektórych funkcji z pakietów Haskell. W tym przypadku użyłem liczb pierwszych, które są właśnie instalowane z „liczbami początkowymi instalacji cabal”;)
Czasy:
Twój oryginalny program:
Ulepszone wdrożenie
Jak widać, ten działa w 38 milisekundach na tej samej maszynie, na której działał w 16 sekund :)
Polecenia kompilacji:
źródło
Dla żartu. Poniżej przedstawiono bardziej „natywną” implementację Haskell:
Korzystanie z
ghc -O3
tego działa konsekwentnie w 0,55-0,58 sekund na moim komputerze (1,73 GHz Core i7).Wydajniejsza funkcja factorCount dla wersji C:
Zmiana
gcc -O3 -lm
longs na ints w main, przy użyciu , to konsekwentnie działa w 0,31-0,35 sekund.Oba mogą działać jeszcze szybciej, jeśli skorzystasz z faktu, że n-ty numer trójkąta = n * (n + 1) / 2, oraz n oraz (n + 1) mają całkowicie odmienne czynniki pierwsze, więc liczba czynników każdej połowy można pomnożyć, aby znaleźć liczbę czynników całości. Następujące:
skróci czas działania kodu c do 0,17-0,19 sekund i może obsługiwać znacznie większe wyszukiwania - ponad 10000 czynników zajmuje około 43 sekund na moim komputerze. Zainteresowanemu czytelnikowi pozostawiam podobne przyspieszenie haskell.
źródło
To mało prawdopodobne. Nie mogę wiele powiedzieć o Erlangu i Haskellu (cóż, może trochę o Haskellu poniżej), ale mogę wskazać wiele innych wąskich gardeł w Pythonie. Za każdym razem, gdy program próbuje wykonać operację z pewnymi wartościami w Pythonie, powinien sprawdzić, czy wartości pochodzą z właściwego typu, i kosztuje to trochę czasu. Twoja
factorCount
funkcja po prostu przydziela listę zrange (1, isquare + 1)
różnymi czasami i czasem działania,malloc
alokacja pamięci w uruchomieniowym jest znacznie wolniejsza niż iteracja w zakresie z licznikiem, tak jak w przypadku C. W szczególności,factorCount()
wywoływana jest wiele razy, a więc przydziela wiele list. Nie zapominajmy również, że Python jest interpretowany, a interpreter CPython nie koncentruje się na optymalizacji.EDYCJA : och, no cóż, zauważam, że używasz Python 3, więc
range()
nie zwraca listy, ale generator. W tym przypadku mój punkt widzenia dotyczący przydzielania list jest w połowie błędny: funkcja po prostu przydzielarange
obiekty, które są jednak nieefektywne, ale nie tak nieefektywne jak przydzielanie listy z dużą ilością elementów.Czy używasz uścisków ? Uściski są znacznie powolnym tłumaczem. Jeśli go używasz, być może możesz uzyskać lepszy czas z GHC spędzić - ale jestem tylko apogeum hipotezy, takie rzeczy, które dobry kompilator Haskell robi pod maską, jest dość fascynujące i daleko poza moim zrozumieniem :)
Powiedziałbym, że grasz w zabawną grę. Najlepszą częścią znajomości różnych języków jest używanie ich w jak najbardziej odmienny sposób :) Ale dygresję, po prostu nie mam żadnych rekomendacji w tym zakresie. Przepraszam, mam nadzieję, że ktoś może ci w tym pomóc :)
O ile pamiętam, musisz tylko upewnić się, że wywołanie rekurencyjne jest ostatnim poleceniem przed zwróceniem wartości. Innymi słowy, funkcja taka jak ta poniżej mogłaby użyć takiej optymalizacji:
Jednak nie byłoby takiej optymalizacji, gdyby twoja funkcja była taka jak ta poniżej, ponieważ po wywołaniu rekurencyjnym istnieje operacja (mnożenie):
Rozdzieliłem operacje na niektóre zmienne lokalne, aby było jasne, które operacje są wykonywane. Jednak najczęściej spotyka się te funkcje jak poniżej, ale są one równoważne z tym, o czym mówię:
Zauważ, że od kompilatora / tłumacza należy decyzja, czy wykona rekursję ogona. Na przykład interpreter języka Python nie robi tego, jeśli dobrze pamiętam (użyłem Python w moim przykładzie tylko ze względu na jego płynną składnię). W każdym razie, jeśli okaże się dziwne rzeczy, takie jak czynnikowych funkcji z dwoma parametrami (i jeden z parametrów ma nazwy, takie jak
acc
,accumulator
itd.), Teraz wiesz, dlaczego ludzie to zrobić :)źródło
Dzięki Haskell naprawdę nie musisz myśleć wyraźnie o rekurencjach.
W powyższym kodzie zastąpiłem jawne rekurencje w odpowiedzi @Thomas zwykłymi operacjami na liście. Kod nadal robi dokładnie to samo, nie martwiąc się o rekurencję ogona. Działa (~ 7,49s ) około 6% wolniej niż wersja w odpowiedzi @Thomas (~ 7,04s ) na moim komputerze z GHC 7.6.2, podczas gdy wersja C z @Raedwulf działa ~ 3,15s . Wygląda na to, że GHC poprawił się w ciągu roku.
PS. Wiem, że to stare pytanie, i natknąłem się na nie z wyszukiwań w Google (teraz zapomniałem, czego szukałem ...). Chciałem tylko skomentować pytanie dotyczące LCO i wyrazić swoje odczucia na temat Haskell w ogóle. Chciałem skomentować najwyższą odpowiedź, ale komentarze nie pozwalają na bloki kodu.
źródło
Więcej liczb i objaśnień dla wersji C. Najwyraźniej nikt nie robił tego przez te wszystkie lata. Pamiętaj, aby głosować na tę odpowiedź, aby wszyscy mogli ją zobaczyć i nauczyć się.
Krok pierwszy: Benchmark programów autora
Specyfikacja laptopa:
Polecenia:
.
Nazwy plików to:
integertype_architecture_compiler.exe
Krok drugi: ponownie zbadaj, ulepsz i przetestuj
VS jest o 250% szybszy niż gcc. Dwa kompilatory powinny dać podobną prędkość. Oczywiście coś jest nie tak z kodem lub opcjami kompilatora. Zbadajmy!
Pierwszym punktem zainteresowania są typy całkowite. Konwersje mogą być kosztowne, a spójność jest ważna dla lepszego generowania kodu i optymalizacji. Wszystkie liczby całkowite powinny być tego samego typu.
To mieszany bałagan
int
ilong
teraz. Zamierzamy to poprawić. Jakiego typu użyć? Najszybszy. Muszę je wszystkie porównać!Typami całkowitymi są
int
long
int32_t
uint32_t
int64_t
iuint64_t
od#include <stdint.h>
W C znajduje się DUŻO liczb całkowitych, plus niektóre podpisane / niepodpisane do zabawy, plus wybór kompilacji jako x86 lub x64 (nie mylić z rzeczywistym rozmiarem całkowitym). To jest wiele wersji do skompilowania i uruchomienia ^^
Krok trzeci: Zrozumienie liczb
Ostateczne wnioski:
Trikowe pytanie: „Jakie są rozmiary int i long w C?”
Prawidłowa odpowiedź to: wielkość int i długa w C nie są dobrze zdefiniowane!
Ze specyfikacji C:
Ze strony podręcznika gcc (flagi -m32 i -m64):
Z dokumentacji MSDN (zakresy typów danych) https://msdn.microsoft.com/en-us/library/s3f49ktz%28v=vs.110%29.aspx :
Podsumowując: wyciągnięte wnioski
Liczby całkowite 32-bitowe są szybsze niż liczby całkowite 64-bitowe.
Standardowe typy liczb całkowitych nie są dobrze zdefiniowane w C ani C ++, różnią się w zależności od kompilatorów i architektur. Gdy potrzebujesz spójności i przewidywalności, użyj
uint32_t
rodziny liczb całkowitych od#include <stdint.h>
.Rozwiązane problemy z prędkością. Wszystkie pozostałe języki mają setki procent opóźnień, C & C ++ znów wygrywają! Zawsze tak robią. Kolejnym ulepszeniem będzie wielowątkowość z wykorzystaniem OpenMP: D
źródło
INT_MIN
iINT_MAX
(-32767 i 32767, które praktycznie narzucają wymógint
co najmniej 16 bitów).long
musi być co najmniej tak duży jakint
, a średnia wymagań zakresulong
wynosi co najmniej 32 bity.Patrząc na twoją implementację Erlang. Czas obejmował uruchomienie całej maszyny wirtualnej, uruchomienie programu i zatrzymanie maszyny wirtualnej. Jestem pewien, że konfiguracja i zatrzymanie erlang vm zajmuje trochę czasu.
Gdyby czas został wykonany w samej maszynie wirtualnej erlang, wyniki byłyby inne, ponieważ w takim przypadku mielibyśmy rzeczywisty czas tylko dla danego programu. W przeciwnym razie uważam, że całkowity czas potrzebny na uruchomienie i załadowanie Erlang Vm plus czas jego zatrzymania (jak umieściłeś w swoim programie) są wliczone do całkowitego czasu, którego używasz do pomiaru czasu program się wyświetla. Rozważ użycie samego czasu Erlanga, którego używamy, gdy chcemy mierzyć czas naszych programów w samej maszynie wirtualnej
timer:tc/1 or timer:tc/2 or timer:tc/3
. W ten sposób wyniki z erlang wykluczą czas potrzebny do uruchomienia i zatrzymania / zabicia / zatrzymania maszyny wirtualnej. Takie jest moje rozumowanie, zastanów się nad tym, a następnie spróbuj jeszcze raz.Właściwie sugeruję, abyśmy spróbowali zmierzyć czas programu (dla języków, które mają środowisko wykonawcze), w ramach środowiska uruchomieniowego tych języków, aby uzyskać dokładną wartość. Na przykład C nie ma narzutów związanych z uruchamianiem i zamykaniem systemu uruchomieniowego, podobnie jak Erlang, Python i Haskell (98% pewności tego - ja poprawiam). Tak więc (w oparciu o to rozumowanie) stwierdzam, że ten test porównawczy nie był wystarczająco precyzyjny / sprawiedliwy dla języków działających na systemie uruchomieniowym. Zróbmy to jeszcze raz z tymi zmianami.
EDYCJA: poza tym, nawet jeśli wszystkie języki miałyby systemy wykonawcze, narzut związany z uruchomieniem każdego z nich i jego zatrzymaniem byłby inny. więc sugeruję, abyśmy spędzili czas z poziomu systemów uruchomieniowych (dla języków, których dotyczy). Wirtualna maszyna Erlang znana jest z dużego obciążenia podczas uruchamiania!
źródło
Na pytanie pierwsze można odpowiedzieć przecząco na pytanie Erlanga. Na ostatnie pytanie można odpowiedzieć, używając odpowiednio Erlanga, jak w:
http://bredsaal.dk/learning-erlang-using-projecteuler-net
Ponieważ jest szybszy niż twój początkowy przykład C, sądzę, że istnieje wiele problemów, które inni już szczegółowo opisali.
Ten moduł Erlang wykonuje się na tanim netbooku w około 5 sekund ... Używa modelu wątków sieciowych w erlang i jako taki pokazuje, jak skorzystać z modelu zdarzeń. Może być rozłożony na wiele węzłów. I to jest szybkie. Nie mój kod.
Poniższy test odbył się na: procesorze Intel (R) Atom (TM) N270 @ 1.60GHz
źródło
C ++ 11, <20ms dla mnie - Uruchom tutaj
Rozumiem, że potrzebujesz wskazówek, które pomogą Ci poprawić znajomość języka, ale ponieważ zostało to tutaj dobrze omówione, pomyślałem, że dodam kontekst dla osób, które mogły spojrzeć na komentarz matematyczny do twojego pytania itp., I zastanawiałem się, dlaczego to kod był o wiele wolniejszy.
Ta odpowiedź ma przede wszystkim na celu zapewnienie kontekstu, aby, miejmy nadzieję, pomóc innym w łatwiejszym ocenie kodu w pytaniu / innych odpowiedziach.
Ten kod wykorzystuje tylko kilka (brzydkich) optymalizacji, niezwiązanych z używanym językiem, w oparciu o:
To zajmuje średnio około 19 ms dla mojego komputera stacjonarnego i 80 ms dla mojego laptopa, co jest dalekie od większości innych kodów, które tu widziałem. I bez wątpienia dostępnych jest jeszcze wiele optymalizacji.
źródło
Próbuję GO:
Dostaję:
oryginalna wersja c: 9.1690 100%
go: 8.2520 111%
Ale używając:
Dostaję:
oryginalna wersja c: 9.1690 100%
thaumkid's wersja c: 0.1060 8650%
pierwsza wersja: 8.2520 111%
druga wersja: 0.0230 39865%
Próbowałem także Python3.6 i pypy3.5-5.5-alpha:
oryginalna wersja c: 8.629 100%
thaumkid's wersja c: 0.109 7916%
Python3.6: 54,795 16%
pypy3,3-5,5-alfa: 13,291 65%
a następnie z następującym kodem mam:
oryginalna wersja c: 8.629 100%
thaumkid's wersja c: 0.109 8650%
Python3.6: 1.489 580%
pypy3,3-5.5-alfa: 0,582 1483%
źródło
Zmiana:
case (divisor(T,round(math:sqrt(T))) > 500) of
Do:
case (divisor(T,round(math:sqrt(T))) > 1000) of
To da poprawną odpowiedź dla przykładu wieloprocesowego Erlang.
źródło
Przyjąłem założenie, że liczba czynników jest duża tylko wtedy, gdy zaangażowane liczby mają wiele małych czynników. Użyłem więc doskonałego algorytmu thaumkida, ale najpierw zastosowałem przybliżenie liczby czynników, która nigdy nie jest zbyt mała. To bardzo proste: sprawdź czynniki pierwsze do 29, a następnie sprawdź pozostałą liczbę i oblicz górną granicę dla liczby czynników. Użyj tego, aby obliczyć górną granicę liczby czynników, a jeśli liczba ta jest wystarczająco wysoka, oblicz dokładną liczbę czynników.
Poniższy kod nie potrzebuje tego założenia do poprawności, ale musi być szybki. Wydaje się, że działa; tylko około jedna na 100 000 liczb daje oszacowanie wystarczająco wysokie, aby wymagać pełnego sprawdzenia.
Oto kod:
Znajduje to 14 753 024 trójkąt z 13824 czynnikami w około 0,7 sekundy, 879 207 615 trójkątną liczbę z 41 440 czynnikami w 34 sekund, 12 524 448 975 75 trójkątną liczbę z 132 840 czynnikami w 10 minut 5 sekund, oraz 26 467 776 064 trójkątną z 172 032 czynników w 21 minut i 25 sekund (Core2 Duo 2,4 GHz), więc ten kod zajmuje średnio tylko 116 cykli procesora na liczbę. Ostatni sam trójkątny numer jest większy niż 2 ^ 68, więc
źródło
Zmodyfikowałem wersję „Jannich Brendle” na 1000 zamiast 500. I wypisz wynik euler12.bin, euler12.erl, p12dist.erl. Oba kody erl do kompilacji używają „+ native”.
źródło
gcc -lm -Ofast euler.c
czas ./a.out
2,79s użytkownik 0,00s system 99% procesor 2,794 ogółem
źródło