Wyzwaniem jest szybkie odfiltrowanie dużego pliku.
- Wejście: Każda linia ma trzy dodatnie liczby całkowite oddzielone spacjami.
Dane wyjściowe: wszystkie wiersze wejściowe
A
B
,T
które spełniają jedno z poniższych kryteriów.- Istnieje inna linia wejściowa
C
,D
,U
gdzieD = A
i0 <= T - U < 100
. - Istnieje inna linia wejściowa
C
,D
,U
gdzieB = C
i0 <= U - T < 100
.
- Istnieje inna linia wejściowa
Aby utworzyć plik testowy, użyj następującego skryptu python, który będzie również używany do testowania. Stworzy plik 1.3G. Możesz oczywiście zredukować Nolines do testowania.
import random
nolines = 50000000 # 50 million
for i in xrange(nolines):
print random.randint(0,nolines-1), random.randint(0,nolines-1), random.randint(0,nolines-1)
Zasady Najszybszy kod po przetestowaniu na pliku wejściowym, który tworzę przy użyciu powyższego skryptu na moim komputerze, wygrywa. Termin wynosi jeden tydzień od pierwszego prawidłowego wpisu.
Moja maszyna Czasy zostaną uruchomione na moim komputerze. Jest to standardowa instalacja Ubuntu z 8 GB pamięci RAM na ośmiordzeniowym procesorze AMD FX-8350. Oznacza to również, że muszę być w stanie uruchomić Twój kod.
Niektóre istotne informacje dotyczące czasu
Czasy zostały zaktualizowane, aby przed każdym testem uruchamiać następujące elementy.
sync && sudo bash -c 'echo 3 > /proc/sys/vm/drop_caches'
time wc test.file
real 0m26.835s
user 0m18.363s
sys 0m0.495s
time sort -n largefile.file > /dev/null
real 1m32.344s
user 2m9.530s
sys 0m6.543s
Status wpisów
Przed każdym testem uruchamiam następującą linię.
sync && sudo bash -c 'echo 3 > /proc/sys/vm/drop_caches'
- Perl (Oczekiwanie na naprawę błędu).
- Scala 1 minut 37 sekund autorstwa @James_pic. (Za pomocą scala -J-Xmx6g Filterer largefile.file output.txt)
- Java . 1 minuta 23 sekundy autorstwa @Geobits. (Korzystanie z java -Xmx6g Filter_26643)
- C . 2 minuty 21 sekund autorstwa @ScottLeadley.
- C . 28 sekund autorstwa @James_pic.
- Python + pandy . Może istnieje proste rozwiązanie „grupowania”?
- C . 28 sekund autorstwa @KeithRandall.
Zwycięzcami są Keith Randall i James_pic.
Nie mogłem rozróżnić ich czasów działania i oba są prawie tak szybkie jak wc!
1 < n < 2147483647
?Odpowiedzi:
C, ~
74,1 sekundyRadix sortuj na T, a następnie przejdź przez tablicę, szukając dopasowań.
Jest szybki, ponieważ jest przyjazny dla pamięci podręcznej. Podstawa sortuje się rozsądnie, a ostatni spacer bardzo. Muszę sprawdzić każdy wiersz pod kątem około 100 innych, ale wszystkie znajdują się obok siebie w pamięci podręcznej.
Dodano: Nie muszę już sprawdzać każdego wiersza na podstawie skanowania 100 innych wierszy. Mała tablica zliczeń bitów b rzędu niskiego rzędu w oknie wystarcza do wyeliminowania większości przypadków tego skanowania.
Teraz około 1/2 parsowania, 1/3 sortowania, 1/6 rzeczywistego dopasowania.
źródło
filter.c
aby zrobić to samo, podszedłem do pytania i znalazłem to. +1Scala 2.10 - 0:41
Problem polega w zasadzie na:
Większość RDBMS zauważy, że łączenie od
x.a
doy.b
ma najwyższą specyficzność, i planuje to jako łączenie skrótowe.Tak właśnie zrobimy. Tworzymy
a
tablicę skrótów danych , łączymy ją z tą samą tabeląb
i filtrujemy różnicę wt
.Połącz z:
I uruchom z:
Na moim komputerze działa to za 2 minuty 27.
Mimo to może być interesujące wypróbować podejście z odpowiedzi @ Lembik, ale w szybszym języku. Odpowiada coś w rodzaju połączenia scalającego
t
. Na papierze powinien być wolniejszy, ale ma lepszą lokalizację pamięci podręcznej, co może popchnąć go do przodu.Aktualizacja
Udało mi się ogolić dużą część czasu z zaskakująco małą zmianą - lepsze mieszanie skrótów. Mapa skrótów jest bardzo wrażliwa na gromadzenie się skrótów, więc ta zmiana sprowadza ją do 1:45 na moim komputerze.
Większość tego czasu spędza na wczytywaniu danych do tablicy.
Jestem ciekawy, dlaczego mój kod odczytu danych jest o wiele wolniejszy niż @Geobits. Wczytanie danych zajmuje mój kod 70 sekund - dłużej niż @Geobits cały program, po usunięciu
Thread.start
błędu. Kusi mnie, by ukraść podejście @Geobits do odczytu danych, ale nie jestem pewien, jak by się z tym czuli bogowie Stack Exchange.Aktualizacja 2
Wprowadziłem dalsze ulepszenia, tym razem do czytnika danych. Używanie operacji dopasowywania wzorców i operacji monadowych w pętli miało negatywny wpływ na wydajność, więc uprościłem ją. Myślę, że
scala.io.Source
to kolejne wąskie gardło do rozwiązania.Teraz na mojej maszynie jest tylko 1:26.
Aktualizacja 3
Pozbyłem się
probe
z OpenHashMultiMap. Kod jest teraz bardziej java-ish i działa w 1:15.Aktualizacja 4
Teraz używam FSM do analizowania danych wejściowych. Czas pracy spada do 0:41
źródło
StringTokenizer
, ale kiedy to robię, analizuję miliony ciągów.String.split
jest obecnie wąskim gardłem, ale w tej chwiliStringTokenizer
nie jest o wiele lepsze - przydzielanie w ciasnej pętli wewnętrznej szkodzi mojej już napiętej GC. Pracuję nad FSM, który wydaje się mieć obietnicę (będąc całkowicie przesadzonym)Java: 1m54s
(Na moim i7)
Ponieważ każdy mecz znajdzie się w odległości 100
t
od swojego partnera, postanowiłem uzupełnić dane wejściowe ot
. Na każde 100 przypada wiadro, więc aby sprawdzić liczbę, należy sprawdzić tylko w stosunku do +/- 1 wiader.Średnio każde wiadro zawiera tylko 100 wpisów, więc skanowanie każdego wiadra nie zajmuje dużo czasu. Ponad połowa czasu spędzana jest na czytaniu i segregowaniu, dopasowanie zajmuje tylko około 40 sekund.
Uwaga: W zależności od konfiguracji JVM może być konieczne zwiększenie wielkości sterty. Zakłada to również nazwę pliku
test.file
. Po prostu zmień to w linii 24, jeśli tak nie jest.źródło
Thread::run
, nieThread.start
, więc wszystko działa namain
wątku. ZThread::start
uruchom czas spada od 1:38 do 0:46 na moim komputerze.sort
czasu. Zwiększyłem stos do 6G, tak samo jak mój (mówiłeś, że masz 8G, więc wydawało się, że to rozsądne przypuszczenie).C - 12 sekund
Postanowiłem przenieść odpowiedź Scali do C, aby zobaczyć, o ile więcej wydajności mogę uzyskać.
Jest to mniej więcej to samo podejście (budowanie otwartej tabeli skrótów
a
), z tym wyjątkiem, że pomijam krok, w którym buduję początkową tablicę, i iteruję bezpośrednio z tabeli skrótów (z jakiegoś powodu nigdy nie mogłem uzyskać takiego podejścia w Scali - Podejrzewam, że obwinienie JVM było winne).Nic nie zawracałem sobie głowy wątkami, ponieważ jest to bolesne.
Kod to:
Połącz z:
I uruchom z:
Lokalizacja pliku testowego jest zakodowana na stałe jako „plik testowy”.
Ponownie odczyt danych zajmuje większość czasu (nieco poniżej 9 sekund). Dopasowanie zajmuje resztę czasu.
Znowu ciekawie będzie zobaczyć, jak to wygląda w porównaniu z odpowiedzią Scotta Leadleya, która używa tego samego języka, ale innej strategii. Scott dołącza do T, co w zasadzie oznacza, że będzie musiał się przyłączyć, ale ponownie, dołączenie do T zapewnia lepszą lokalizację pamięci podręcznej.
źródło
diff <(sort -n James_pic-c.out) <(sort -n James_pic-scala.out)
a
wartość występuje wn
czasach, w którychn >= BUFFER_SIZE + 2
perl, 17m46s na rdzeniu i7 w / 8GB pamięci
Najpierw używamy sort -n -k3, aby uzyskać najważniejsze pole w kolejności, wykorzystując wbudowaną równoległość w nowoczesnych wersjach sort (1). Następnie, ponieważ perl jest znacznie utrudniony przez fakt, że prosty skalar przyjmuje porządek po 80 bajtów każdy (50 milionów * 3 * 80 to za dużo - co najmniej 12 GB), zmniejszamy wydajność do 50 milionów * 12 bajtów tablica (12 bajtów na linię, każda linia zawiera 3 liczby całkowite, które mogą być reprezentowane jako 32-bitowa liczba całkowita). Następnie wystrzeliwujemy 8 wątków obejmujących (z grubsza) 1/8 danych każdy (+ niektóre nakładają się).
Niesortowane dane wyjściowe:
Jestem pewien, że byłoby to o rząd wielkości szybciej w C, ale prawdopodobnie nie poświęcę na to czasu.
źródło
A = D = 8455767
, aleU = 50175
,T = 50130
i takT - U = -45
C # - 30 sekund
Inne podejście niż większość, jeśli dobrze czytam - nie używam żadnych struktur opartych na haszowaniu.
Zwykle nie otrzymuję żadnych wyników, nie jestem pewien, czy jest to anomalia statystyczna, czy błąd w moim rozumowaniu.Naprawiono błędne porównanie sortowania binarnego.źródło
x.A
będzie pochodzićsortedA
ix.B
będzie pochodzićsortedB
, podczas gdy w rzeczywistości oba będą pochodzićsortedB
, a toComparer
da nonsensowne wyniki.A
iB
, istnieje szybszy algorytm niż iteracjaA
i wyszukiwanie binarne, naB
którym jestO(n log(n))
(i faktycznie jest to tablica skrótów biedaka). Zamiast tego możesz połączyć i połączyć dwie listy, czyliO(n)
.B
będą równomiernie rozmieszczone w określonym zakresie, byłoby zamiana wyszukiwania binarnego na wyszukiwanie interpolacyjne, co skraca czas wyszukiwania odO(log(n))
doO(log(log(n))
.do
Brutalna, brutalna siła, brzydka w twojej twarzy C. Po przerwie wybrałbym każdy inny skompilowany język.
Skompiluj z „gcc -m64 -pthreads -O”. Oczekuje wejścia na standardowe wejście. Domyślnie uruchamia wielowątkowy. Użyj opcji „-s”, aby użyć tylko jednego wątku.
źródło
W końcu dostałem szansę zbudowania fizycznego systemu Ubuntu 14.04 podobnego do Lembika i post mortem na moim rozwiązaniu tej układanki. W moim wyborze ważności:
zassane:Zamiast nudzić cię kolejnym parserem FSM, poniższe rozwiązanie wykorzystuje fgets () i lokalną zamianę strtol () [poszukaj s2i ()].
Referencyjna implementacja w Ruby:
To pies, ~ 50x wolniejszy niż rozwiązanie C, ale perl jest równie powolny i mniej zwięzły.
Rozwiązanie C:
Skompiluj z „gcc -O3 -std = c99 -Wall -m64”.
źródło