Ten golf wymaga obliczeń czynnikowych podzielonych na wiele wątków lub procesów.
Niektóre języki ułatwiają koordynację niż inne, więc jest to język agnostyczny. Podano przykładowy kod bez golfa, ale należy opracować własny algorytm.
Celem konkursu jest sprawdzenie, kto może wymyślić najkrótszy (w bajtach, nie sekundach) wielordzeniowy algorytm czynnikowy do obliczania N! mierzona liczbą głosów po zakończeniu konkursu. Powinna istnieć wielordzeniowa przewaga, więc będziemy musieli działać dla N ~ 10 000. Głosujący powinni głosować za odrzuceniem, jeśli autor nie dostarczy prawidłowego wyjaśnienia, w jaki sposób rozkłada pracę na procesory / rdzenie i głosuje w oparciu o zwięzłość golfa.
Dla ciekawości proszę zamieścić kilka numerów wyników. W pewnym momencie może wystąpić kompromis między wynikami a golfem, idź z golfem, o ile spełnia on wymagania. Byłbym ciekawy, kiedy to nastąpi.
Możesz użyć normalnie dostępnych pojedynczych bibliotek dużych liczb całkowitych. Na przykład, Perl jest zwykle instalowany z bigintem. Należy jednak pamiętać, że zwykłe wywołanie funkcji systemowej udostępnianej przez system zwykle nie dzieli pracy na wiele rdzeni.
Musisz zaakceptować ze STDIN lub ARGV wejście N i wyjście do STDOUT wartości N !. Opcjonalnie możesz użyć drugiego parametru wejściowego, aby podać liczbę procesorów / rdzeni do programu, więc nie robi to, co zobaczysz poniżej :-) Lub możesz zaprojektować jawnie dla 2, 4, cokolwiek masz dostępne.
Poniżej opublikuję własny przykład perlowego nieparzystego, poprzednio przesłany na Przepełnienie stosu w ramach algorytmów czynnikowych w różnych językach . To nie jest golf. Przedstawiono wiele innych przykładów, wiele z nich to golf, ale wiele nie. Ze względu na licencje podobne do akcji, możesz użyć kodu we wszystkich przykładach w powyższym linku jako punkcie wyjścia.
Wydajność w moim przykładzie jest słaba z wielu powodów: używa zbyt wielu procesów, zbyt dużej konwersji łańcucha / biginta. Jak powiedziałem, jest to celowo nieparzysty przykład. Obliczy 5000! w niecałe 10 sekund na 4-rdzeniowej maszynie tutaj. Jednak bardziej oczywista dwuwarstwowa pętla dla / następnej pętli może zrobić 5000! na jednym z czterech procesorów w 3.6s.
Na pewno będziesz musiał zrobić to lepiej:
#!/usr/bin/perl -w
use strict;
use bigint;
die "usage: f.perl N (outputs N!)" unless ($ARGV[0] > 1);
print STDOUT &main::rangeProduct(1,$ARGV[0])."\n";
sub main::rangeProduct {
my($l, $h) = @_;
return $l if ($l==$h);
return $l*$h if ($l==($h-1));
# arghhh - multiplying more than 2 numbers at a time is too much work
# find the midpoint and split the work up :-)
my $m = int(($h+$l)/2);
my $pid = open(my $KID, "-|");
if ($pid){ # parent
my $X = &main::rangeProduct($l,$m);
my $Y = <$KID>;
chomp($Y);
close($KID);
die "kid failed" unless defined $Y;
return $X*$Y;
} else {
# kid
print STDOUT &main::rangeProduct($m+1,$h)."\n";
exit(0);
}
}
Moim zainteresowaniem jest po prostu (1) łagodzenie nudy; i (2) uczenie się czegoś nowego. To nie jest dla mnie zadanie domowe ani badawcze.
Powodzenia!
Odpowiedzi:
Matematyka
Funkcja równoległa:
Gdzie g jest
Identity
lubParallelize
zależy od wymaganego rodzaju procesuW przypadku testu synchronizacji nieznacznie zmodyfikujemy funkcję, aby zwracała rzeczywisty czas zegara.
Testujemy oba tryby (od 10 ^ 5 do 9 * 10 ^ 5): (tylko dwa jądra tutaj)
Wynik:
źródło
Haskell:
209200198177 znaków176167 źródło +3310 flaga kompilatoraTo rozwiązanie jest dość głupie. Stosuje produkt równolegle do wartości typu
[[Integer]]
, gdzie wewnętrzne listy mają najwyżej dwa elementy. Gdy lista zewnętrzna spadnie maksymalnie do dwóch list, spłaszczamy ją i zabieramy produkt bezpośrednio. I tak, moduł sprawdzania typu potrzebuje czegoś z dopiskiem Integer, w przeciwnym razie nie zostanie skompilowany.(Zapraszam do przeczytania środkowej części
f
pomiędzyconcat
is
jako „dopóki nie docenię długości”)Wyglądało na to, że będą całkiem niezłe, ponieważ parMap od Control.Parallel.Strategies sprawia, że dość łatwo jest rozdzielić je na wiele wątków. Wygląda jednak na to, że GHC 7 wymaga ogromnych 33 znaków w opcjach wiersza poleceń i zmiennych środowiskowych, aby faktycznie umożliwić wątkowemu środowisku wykonawczemu korzystanie z wielu rdzeni (które zawarłem w sumie).Chyba że coś mi umknie, co jest zdecydowaniemożliwe. ( Aktualizacja : wątkowe środowisko wykonawcze GHC wydaje się używać wątków N-1, gdzie N jest liczbą rdzeni, więc nie ma potrzeby manipulowania opcjami czasu wykonywania).Kompilować:
Środowisko wykonawcze było jednak całkiem dobre, biorąc pod uwagę, że wywołano absurdalną liczbę równoległych ocen i że nie skompilowałem z opcją -O2. Za 50000! na dwurdzeniowym MacBooku otrzymuję:
Całkowity czas dla kilku różnych wartości, pierwsza kolumna jest równoległą do golfa, druga to naiwna sekwencyjna wersja:
Dla odniesienia, naiwna sekwencyjna wersja to (skompilowana z -O2):
źródło
Rubinowy - 111 + 56 = 167 znaków
To jest skrypt dwóch plików, główny plik (
fact.rb
):dodatkowy plik (
f2.rb
):Po prostu bierze liczbę procesów i liczbę do obliczenia jako argumenty i dzieli pracę na zakresy, które każdy proces może obliczyć indywidualnie. Następnie mnoży wyniki na końcu.
To naprawdę pokazuje, o ile wolniej Rubinius jest w stosunku do YARV:
Rubinius:
Ruby 1.9.2:
(Uwaga dodatkowa
0
)źródło
inject(:+)
. Oto przykład z docs:(5..10).reduce(:+)
.8
powinienem tam być,*
gdyby ktoś miał problemy z uruchomieniem tego.Java,
523 519 434 430429 znakówDwie 4 w ostatniej linii to liczba wątków do użycia.
50000! przetestowane w następującym frameworku (wersja niegolfowana oryginalnej wersji i garstka mniej złych praktyk - choć wciąż jest ich dużo) daje (na moim 4-rdzeniowym komputerze z Linuksem) czasy
Zauważ, że powtórzyłem test z jednym wątkiem dla uczciwości, ponieważ jit mógł się rozgrzać.
Java z bigintami nie jest właściwym językiem do gry w golfa (spójrz na to, co muszę zrobić, aby skonstruować nieszczęśliwe rzeczy, ponieważ konstruktor, który zajmuje dużo czasu, jest prywatny), ale hej.
Z nierozwiniętego kodu powinno być całkowicie oczywiste, w jaki sposób dzieli pracę: każdy wątek pomnaża modulo klasy równoważności przez liczbę wątków. Kluczową kwestią jest to, że każdy wątek wykonuje mniej więcej taką samą ilość pracy.
źródło
CSharp -
206215 znakówDzieli obliczenia za pomocą funkcji C # Parallel.For ().
Edytować; Zapomniałem blokady
Czasy wykonania:
źródło
Perl, 140
Pobiera
N
ze standardowego wejścia.Funkcje:
Reper:
źródło
Scala (
345266244232214 znaków)Za pomocą aktorów:
Edytuj - usunięto odniesienia do
System.currentTimeMillis()
, rozebranoa(1).toInt
, zmieniono zList.range
nax to y
Edycja 2 - zmieniłem
while
pętlę na afor
, zmieniłem lewą zakładkę na funkcję listy, która robi to samo, opierając się na niejawnych konwersjach typu, więcBigInt
typ 6 znaków pojawia się tylko raz, zmieniłem println na drukowanieEdycja 3 - dowiedz się, jak zrobić wiele deklaracji w Scali
Edycja 4 - różne optymalizacje, których nauczyłem się, odkąd to zrobiłem
Wersja bez golfa:
źródło
Scala-2.9.0 170
bez golfa:
Silnia 10 jest obliczana na 4 rdzeniach poprzez wygenerowanie 4 list:
które są mnożone równolegle. Byłoby prostsze podejście do podziału liczb:
Ale rozkład nie byłby tak dobry - wszystkie mniejsze liczby kończyłyby się na tej samej liście, najwyższe na innej, co prowadziłoby do dłuższych obliczeń na ostatniej liście (w przypadku wysokich wartości N ostatni wątek nie byłby prawie pusty , ale przynajmniej zawierają elementy rdzeni (N / rdzenie).
Scala w wersji 2.9 zawiera równoległe kolekcje, które same obsługują równoległe wywołanie.
źródło
Erlang - 295 znaków.
Pierwsza rzecz, jaką kiedykolwiek napisałem w Erlangu, więc nie zdziwiłbym się, gdyby ktoś mógł z łatwością zmniejszyć o połowę:
Używa tego samego modelu wątków, co mój poprzedni wpis Ruby: dzieli zakres na podzakres i mnoży zakresy w osobnych wątkach, a następnie mnoży wyniki z powrotem w głównym wątku.
Nie byłem w stanie dowiedzieć się, jak uruchomić skrypt, więc po prostu zapisz jako
f.erl
i otwórz erl i uruchom:z odpowiednimi zamianami.
Mam około 8 sekund za 50000 w 2 procesach i 10 sekund za 1 proces na moim MacBooku Air (dwurdzeniowy).
Uwaga: Właśnie zauważyłem, że zawiesza się, jeśli spróbujesz wykonać więcej procesów niż liczba, aby dokonać podziału na czynniki.
źródło