Próbowałem skryptu bash, ale utworzenie prostego pliku o rozmiarze 1 MB trwało zbyt długo. Myślę, że odpowiedź polega na użyciu /dev/random
lub /dev/urandom
, ale inne posty tutaj pokazują tylko, jak dodawać wszelkiego rodzaju dane do pliku przy użyciu tych rzeczy, ale chcę dodać tylko liczby.
Czy istnieje więc polecenie, którego można użyć do utworzenia losowego pliku o rozmiarze 1 GB zawierającego tylko liczby od 0 do 9?
Edycja: Chcę, aby wynik był mniej więcej taki
0 1 4 7 ..... 9
8 7 5 8 ..... 8
....
....
8 7 5 3 ..... 3
Zakres wynosi od 0 do 9, co oznacza tylko cyfry 0, 1, 2, 3, 4, 5, 6, 7, 8 i 9. Potrzebuję też ich oddzielenia spacjami i 100 na linię, aż do n
liczby linii. To coś, co mnie nie obchodzi, chcę, aby mój ostateczny rozmiar wynosił 1 GB.
Edycja: Używam Ubuntu 16.04 LTS
yes 4 | tr '\n' ' ' | fold -w 200 | head -c1G
Odpowiedzi:
Jest to częściowo trafna odpowiedź ze względu na tytuł pytania.
Kiedy szukasz „najszybszego sposobu na ...” , odpowiedzią jest prawie zawsze jakieś specjalistyczne narzędzie. Te „odpowiedzi” pokazują jedno z takich narzędzi, abyś mógł eksperymentować.
To nie jest poważna odpowiedź, ponieważ nie powinieneś szukać specjalistycznych narzędzi do prac, które wykonujesz tylko raz lub bardzo rzadko. Widzisz, spędzasz więcej czasu na szukaniu narzędzi i poznawaniu ich, niż na robieniu różnych rzeczy. Powłoki i narzędzia takie jak
bash
iawk
nie są najszybsze, ale zwykle można napisać linijkę, aby osiągnąć zadanie, spędzając tylko kilka sekund.perl
Można również użyć lepszych języków skryptowych , chociaż krzywa uczenia sięperl
jest bardzo stroma i waham się polecić ją do takich celów, ponieważ traumatyzują mnie okropne projekty Perla.python
z drugiej strony jest nieco utrudniony ze względu na dość wolne operacje we / wy; jest to jednak problem tylko podczas filtrowania lub generowania gigabajtów danych.W każdym razie następujący przykładowy program C89 (który wykorzystuje POSIX.1 dla zegara o wyższej dokładności tylko, jeśli jest dostępny) powinien osiągnąć szybkość generowania około 100 MB / s (testowany w systemie Linux na laptopie z procesorem Intel i5-4200U, przesyłając dane wyjściowe do
/dev/null
), używając całkiem dobrego generatora liczb pseudolosowych. (Dane wyjściowe powinny przejść wszystkie testy BigCruncha, z wyjątkiem testu MatrixRank, ponieważ kod używa xorshift64 * i metody wykluczania, aby uniknąć popychania cyfr).decimal-digits.c:
Możemy uczynić to znacznie szybszym, jeśli przejdziemy do bufora linii i
fwrite()
raz, zamiast wypisywać każdą cyfrę na raz. Zauważ, że nadal utrzymujemy strumień w pełni buforowany, aby uniknąć zapisów częściowych (innych niż potęga dwóch), jeśli wyjście jest urządzeniem blokowym.Uwaga: oba przykłady zostały zredagowane 18.11.2016, aby zapewnić równomierny rozkład cyfr (zero jest wykluczone; patrz np. Tutaj dla porównania i szczegółów na temat różnych generatorów liczb pseudolosowych).
Kompiluj za pomocą na przykład
i opcjonalnie zainstaluj system dla
/usr/bin
użyciaPobiera liczbę cyfr na linię i liczbę linii. Ponieważ
1000000000 / 100 / 2 = 5000000
(pięć milionów; całkowita liczba bajtów podzielona przez kolumny podzielone przez 2), możesz użyćaby wygenerować gigabajt wielkości
digits.txt
zgodnie z życzeniem OP.Zauważ, że sam program jest napisany bardziej z myślą o czytelności niż o wydajności. Moim zamiarem nie jest pokazanie wydajności kodu - i tak użyłbym POSIX.1 i niskopoziomowych I / O, zamiast ogólnych interfejsów C - ale abyś mógł łatwo zobaczyć, jaki jest balans przy wysiłku w tworzeniu dedykowanych narzędzi w porównaniu do ich wydajności, w porównaniu do skryptów jednowierszowych lub skryptletów z krótką powłoką lub awk.
Używanie biblioteki GNU C, wywoływanie
fputc()
funkcji dla każdego wyniku znaków pociąga za sobą bardzo mały narzut (pośredniego wywołania funkcji lub warunków -FILE
interfejs jest w rzeczywistości dość złożony i wszechstronny, widzisz). W tym konkretnym laptopie Intel Core i5-4200U przekierowanie wyjścia do/dev/null
pierwszej wersji (fputc) zajmuje około 11 sekund, podczas gdy wersja liniowa zajmuje tylko 1,3 sekundy.Często zdarza mi się pisać takie programy i generatory tylko dlatego, że lubię grać z ogromnymi zestawami danych. Jestem dziwny w ten sposób. Na przykład napisałem kiedyś program do drukowania wszystkich skończonych dodatnich wartości zmiennoprzecinkowych IEEE-754 do pliku tekstowego, z wystarczającą precyzją, aby uzyskać dokładnie taką samą wartość podczas analizowania. Plik miał rozmiar kilku gigabajtów (być może około 4G); nie ma tak wielu skończonych pozytywów,
float
jak mogłoby się wydawać. Użyłem tego do porównania implementacji, które czytają i analizują takie dane.W przypadku normalnych przypadków użycia, takich jak OP, skrypty powłoki i skryptlety oraz jednowierszowe są lepszym podejściem. Mniej czasu poświęconego na wykonanie całego zadania. (Z wyjątkiem sytuacji, gdy potrzebują innego pliku każdego dnia lub wiele osób potrzebuje innego pliku, w którym - w rzadkich przypadkach - dedykowane narzędzie, takie jak powyżej, może uzasadnić wysiłek.)
źródło
mmap()
jest to najłatwiejsza droga do najlepszej prędkości we / wy - ale sprawdź przed zgłoszeniem jakichkolwiek roszczeń!write()
, jest zwykle szybsze niżmmap()
.fwrite()
nie jest dużo wolniejszy. Tak, przeprowadziłem testy porównawcze tego (po prostu nie dla tego konkretnego przykładu);write()
w dużych porcjach (262144, 524288 lub 1048576 bajtów) ma tendencję do przewyższania innych metod. Wersjafputc()
zaimplementowana w bibliotece GNU C (którą również obszernie przeprowadziłem testy) jest powolna z wielu powodów; w szczególności implementacja musi wykonywać skoki warunkowe lub pośrednie wezwania dla każdej dodanej postaci; ten niewielki koszt ogólny tak często się sumuje./dev/null
. Scenariusz Stéphane Chazelas zajmuje około 52 sekund; fragment perla (w tymhead
filtrowanie) około 58 sekund; Twójshuf
fragment kodu (z prawidłowym czasem; mierzysz tylko czas shuf, zakładając, że wklejenie nie potrwa dłużej) zajmuje około 69 sekund. Program C ++ 11 Jamesa Hollisa trwa 14 sekund. Powyższy program zajmuje 10 sekund.To:
(zakładając,
head
że implementacja obsługuje-c
) wydaje się być dość szybki w moim systemie.tr
tłumaczy cały zakres bajtów (od 0 do 255, od 0 do 0377 ósemkowo): 25 pierwszych bajtów jako 0, 25 następnych jako 1 ... a następnie 25 9 pozostałych (250 do 255) na „x”, które następnie odrzuć (ztr -d x
), ponieważ chcemy równomiernego rozkładu (zakładając, że/dev/urandom
ma on sam rozkład równomierny), a więc nie podawać stronniczości niektórym cyfrom.To daje jedną cyfrę na 97% bajtów
/dev/urandom
.fold -w 1
sprawia, że jest to jedna cyfra na linię.paste -s
jest wywoływany z listą separatorów, która składa się z 99 znaków spacji i jednego znaku nowej linii, tak aby mieć 100 cyfr oddzielonych spacją w każdym wierszu.head -c1G
otrzyma pierwszy GiB (2 30 ) tego. Pamiętaj, że ostatni wiersz zostanie obcięty i nie będzie ograniczony. Możesz obciąć do 2 30 -1 i ręcznie dodać brakującą nową linię lub obciąć do 10 9 bajtów zamiast tego, co stanowi 50 milionów z tych 200 bajtów linii (head -n 50000000
uczyniłoby to również polecenie standardowe / przenośne).Te czasy (uzyskane
zsh
w systemie czterordzeniowym) wskazują, gdzie spędzany jest czas procesora:Pierwszy
tr
to szyjka butelki, przez większość czasu spędzonego w jądrze (przypuszczam, że do generowania liczb losowych). Czas jest mniej więcej zgodny z szybkością, z której mogę uzyskać bajty/dev/uramdom
(około 19 Mb / s, a tutaj produkujemy 2 bajty na każde 0,97 bajta / dev / urandom z prędkością 32 Mb / s).fold
wydaje się spędzać nieuzasadnioną ilość czasu procesora (15s) po prostu na wstawianie znaku nowej linii po każdym bajcie, ale to nie wpływa na ogólny czas, ponieważ działa na innym procesorze w moim przypadku (dodanie-b
opcji sprawia, że jest to nieco więcej wydajna,dd cbs=1 conv=unblock
wydaje się lepszą alternatywą).Możesz zlikwidować
head -c1G
i ogolić się na kilka sekund, ustawiając limit rozmiaru pliku (limit filesize 1024m
zzsh
lubulimit -f "$((1024*1024))"
z większością innych powłok (w tymzsh
)) zamiast w podpowłoce.Można to poprawić, jeśli wyodrębnimy 2 cyfry dla każdego bajtu, ale potrzebowalibyśmy do tego innego podejścia. Powyższe jest bardzo wydajne, ponieważ
tr
po prostu wyszukuje każdy bajt w 256-bajtowej tablicy. Nie można tego zrobić dla 2 bajtów jednocześnie, a użycie takich rzeczy dohexdump -e '1/1 "%02u"'
obliczenia tekstowej reprezentacji bajtu przy użyciu bardziej złożonych algorytmów byłoby droższe niż samo generowanie liczb losowych. Mimo to, podobnie jak w moim przypadku, masz rdzenie procesora, których czas można zaoszczędzić, może jednak uda się wygolić kilka sekund:Z:
Rozumiem (zauważ jednak, że tutaj jest to 1 000 000 000 bajtów w przeciwieństwie do 1 073 741 824):
Więcej czasu procesora ogółem, ale lepiej rozdzielony między moje 4 rdzenie procesora, więc w rezultacie zajmuje mniej czasu zegara ściennego. Wąskie gardło jest teraz
hexdump
.Jeśli użyjemy
dd
zamiast liniowegofold
, możemy faktycznie zmniejszyć ilość pracyhexdump
do zrobienia i poprawić równowagę pracy między procesorami:(tutaj zakładając GNU
dd
dla swoichiflag=fullblock
istatus=none
) co daje:Powrót do generowania liczb losowych jest wąskim gardłem.
Teraz, jak wskazał @OleTange, jeśli masz
openssl
narzędzie, możesz użyć go, aby uzyskać szybszy (szczególnie na procesorach, które mają instrukcje AES) pseudolosowy generator bajtów.w moim systemie wyrzuca 15 razy więcej bajtów na sekundę niż
/dev/urandom
. (Nie mogę wypowiedzieć się na temat tego, jak to się porównuje pod względem kryptograficznie bezpiecznego źródła losowości, jeśli dotyczy to twojego przypadku użycia).Teraz daje:
z powrotem do
hexdump
wąskiego gardła.Ponieważ nadal mam wolne procesory, mogę uruchomić 3 z nich
hexdump
równolegle.(
<&3
jest to potrzebne dla powłok innych niżzsh
stdin poleceń zamknięcia na / dev / null, gdy są uruchomione w tle).Teraz do 6,2 sekundy, a moje procesory prawie w pełni wykorzystane.
źródło
perl
wariant, który i tak był znacznie wolniejszy. Nie mogę uzyskać 2 cyfr na bajt przy takim podejściu tr | fold | paste.bc
(następnie upuść 0, 1 lub 2 najbardziej znaczące cyfry).Jeśli masz
shuf
dostępne (najnowsze GNU coreutils), możesz to zrobić:Na mojej maszynie wirtualnej jest to teraz nieco wolniej niż odpowiedź Stéphane'a o współczynnik 3: 4.
źródło
shuf
w mojej firmie PC nie ma-r
,fmt
nie ma-g
teżpaste
/printf
trick - dzięki. Twoja odpowiedź jest teraz najwyraźniej szybsza.Jeśli nie potrzebujesz losowości o bardzo wysokiej jakości, a wystarczająca jest prawie równomierna dystrybucja, możesz iść naprawdę szybko, szczególnie na nowoczesnym procesorze z wydajnymi wektorami całkowitymi SIMD, takimi jak x86 z SSE2 lub AVX2.
To jest jak odpowiedź @ NominalAnimal, ponieważ oboje mieliśmy ten sam pomysł, ale ręcznie wektoryzowaliśmy dla x86. (A przy liczbach losowych gorszej jakości, ale prawdopodobnie wystarczających do wielu przypadków użycia). Działa to około 15 lub 30 razy szybciej niż kod @ Nominal, przy ~ 13 GB / s wyjścia ASCII na Intel Haswell 2,5 GHz Procesor z AVX2. To wciąż mniej niż teoretyczna maksymalna przepustowość pamięci głównej (dwukanałowa pamięć DDR3-1600 wynosi około 25,6 GB / s), ale tak naprawdę zapisywałem czas do / dev / null, więc w rzeczywistości po prostu przepisałem bufor, który pozostaje gorący w pamięci podręcznej. Skylake powinien uruchomić ten sam kod znacznie szybciej niż Haswell (patrz na dole tej odpowiedzi).
Zakładając, że faktycznie masz wąskie gardło we / wy na dysku lub gdzieś to potokujesz, szybka implementacja oznacza, że twój procesor nie musi nawet taktować się wyżej niż bezczynnie. Zużywa znacznie mniej energii całkowitej do uzyskania wyniku. (Żywotność baterii / ciepło / globalne ocieplenie.)
Jest to tak szybkie, że prawdopodobnie nie chcesz zapisywać go na dysku. Po prostu ponownie wygeneruj w razie potrzeby (z tego samego materiału siewnego, jeśli chcesz ponownie te same dane). Nawet jeśli chcesz go przesłać do wielowątkowego procesu, który może korzystać ze wszystkich procesorów, uruchomienie tego w celu przesłania danych do niego spowoduje pozostawienie go w pamięci podręcznej L3 (i pamięci podręcznej L2 na rdzeniu, który go napisał), i użyj tak bardzo mały czas procesora. (Należy jednak pamiętać, że
/dev/null
przesyłanie strumieniowe dodaje dużo narzutu w porównaniu do pisania . W Skylake i7-6700k, przesyłanie dowc -c
lub innego programu, który tylko czyta + odrzuca dane wejściowe, jest około 8 razy wolniejsze niż zapisywanie do/dev/null
i wykorzystuje tylko 70% Procesor, ale to wciąż 4,0 GB / s na procesorze 3,9 GHz.Ponowne wygenerowanie jest szybsze niż ponowne odczytanie go nawet z szybkiego dysku SSD podłączonego przez PCIe, ale IDK, jeśli jest bardziej energooszczędny (multiplikator wektor-liczba jest nadal zajęty i prawdopodobnie jest dość energochłonny, podobnie jak inne AVX2 256 ALU wektorów). OTOH, nie wiem, ile czasu procesora czytającego z dysku zabrałoby coś, co maksymalizowało wszystkie rdzenie przetwarzające to wejście. Domyślam się, że przełącznik kontekstowy do ponownego generowania w porcjach 128k może być konkurencyjny w stosunku do uruchamiania kodu systemu plików / pagecache i przydzielania stron do odczytu danych z dysku. Oczywiście, jeśli jest już gorąco w pamięci podręcznej, to po prostu jest zapadający w pamięć. OTOH, piszemy już o tak szybkim jak memcpy! (która musi rozdzielić przepustowość pamięci głównej na odczyt i zapis). (Należy również pamiętać, że zapisywanie w pamięci, że „
rep movsb
(zoptymalizowany memcpy i memset w mikrokodzie, co pozwala uniknąć RFO, ponieważ Andy Glew zaimplementował go w P6 (Pentium Pro )).Jak dotąd jest to tylko dowód koncepcji, a obsługa nowej linii jest tylko w przybliżeniu poprawna. Jest źle na końcach bufora power-of-2. Więcej czasu na rozwój. Jestem pewien, że mógłbym znaleźć bardziej skuteczny sposób wstawiania znaków nowej linii, który jest również dokładnie poprawny, z co najmniej tak niskim narzutem (w porównaniu do wypisywania tylko spacji). Myślę, że jest to około 10 do 20%. Interesuje mnie tylko to, jak szybko możemy uruchomić ten bieg, a nie faktyczna jego dopracowana wersja, więc zostawię tę część jako ćwiczenie dla czytelnika, z komentarzami opisującymi niektóre pomysły.
Na Haswell i5 z maksymalnym turbodoładowaniem 2,5 GHz, z pamięcią RAM DDR3-1600 MHz , czasowo generuje 100GiB, ale został zmniejszony. (Czasowo na cygwin64 na Win10 z gcc5.4
-O3 -march=native
, pominięty,-funroll-loops
ponieważ miałem dość czasu na uzyskanie przyzwoitego czasu na tym pożyczonym laptopie. Powinienem właśnie uruchomić Linuksa na USB).pisanie do / dev / null, chyba że określono inaczej.
wc -c
bufora o rozmiarze 128 kB: 0,32 s z procesorem 2,38 GHz (maks. dwurdzeniowe turbo). (nieskalowane czasy: real = 32.466s użytkownik = 11.468s sys = 41.092s, w tym zarówno to, jak iwc
). Jednak tylko połowa danych została skopiowana, ponieważ mój głupi program zakłada, że zapis zajmuje pełny bufor, nawet jeśli tak nie jest, a cygwin write () robi tylko 64k na wywołanie do potoku.W przypadku SSE2 jest to około 15 razy szybsze niż kod skalarny @Nominal Animal. Dzięki AVX2 jest około 30 razy szybszy. Nie wypróbowałem wersji kodu Nominal, która po prostu używa
write()
zamiast tegofwrite()
, ale przypuszczalnie dla dużych buforów stdio zwykle nie przeszkadza . Jeśli kopiuje dane, spowodowałoby to spowolnienie.Czasy do wyprodukowania 1 GB danych na Core2Duo E6600 (Merom 2.4GHz, 32kB prywatny L1, 4MiB współdzielone pamięci podręczne L2), DDR2-533MHz w 64-bitowym Linuksie 4.2 (Ubuntu 15.10). Nadal używając rozmiaru bufora 128kiB do write (), nie zbadałem tego wymiaru.
pisanie do / dev / null, chyba że określono inaczej.
wc -c
: 0,593 s (nieskalowane: rzeczywiste = 59,266s użytkownik = 20,148s sys = 1m6,548s, w tym czas procesora wc). Taka sama liczba wywołań systemowych write () jak w przypadku cygwin, ale faktycznie przesyłanie wszystkich danych, ponieważ Linux obsługuje wszystkie 128k zapisu () do potoku.fwrite()
wersja (gcc5.2-O3 -march=native
), należy uruchomić z./decdig 100 $((1024*1024*1024/200)) > /dev/null
: 3.19s +/- 0,1%, przy 1,40 instrukcji na cykl. -funrollowe pętle zrobiły może małą różnicę.clang-3.8 -O3 -march=native
: 3,42s +/- 0,1%fwrite
dowc -c
: rzeczywisty = 3,980s użytkownik = 3,176s sys = 2,080sclang++-3.8 -O3 -march=native
): 22,885s +/- 0,07%, z 0,84 instrukcjami na cykl. (g ++ 5.2 był nieco wolniejszy: 22,98s). Pisanie tylko jednej linii na raz prawdopodobnie bolało znacznie.tr < /dev/urandom | ...
: real = 41.430s użytkownik = 26,832s sys = 40.120s.tr
przez większość czasu zajmował się samym rdzeniem procesora, spędzając prawie cały czas w sterowniku jądra, generując losowe bajty i kopiując je do potoku. Drugi rdzeń na tym dwurdzeniowym komputerze działał przez resztę rurociągu.time LC_ALL=C head -c512M </dev/urandom >/dev/null
: tzn. po prostu odczytuje tyle losowości bez orurowania: real = 35.018s użytkownik = 0.036s sys = 34.940s.LANG=en_CA.UTF-8
:: real = 4m32.634s użytkownik = 4m3.288s sys = 0m29.364.LC_ALL=C LANG=C
: real = 4m18.637s użytkownik = 3m50.324s sys = 0m29.356s Wciąż bardzo powoli.dig3 = v%10
krok dotyczy progu rentowności na tym CWU): 0,166 s (1,82 instrukcji na cykl) . Jest to w zasadzie dolna granica tego, do czego możemy się zbliżyć dzięki idealnie wydajnej obsłudze nowej linii.v%10
, 0,222 sekundy +/- 0,4%, 2,12 instrukcji na cykl. (Skompilowane z gcc5.2-march=native -O3 -funroll-loops
. Pętle Unroll pomagają w tym kodzie na tym sprzęcie. Nie używaj go na ślepo, szczególnie w przypadku dużych programów).Jak to jest zrobione
Szybki PRNG jest oczywiście niezbędny. xorshift128 + można wektoryzować, dzięki czemu masz dwa lub cztery 64-bitowe generatory równolegle w elementach wektora SIMD. Każdy krok tworzy pełny wektor losowych bajtów. ( Tutaj implementacja AVX2 256b z wbudowanymi procesorami Intela ). Wybrałem to w porównaniu z wyborem xorshift * Nominal, ponieważ 64-bitowe zwielokrotnienie liczb całkowitych wektorów jest możliwe tylko w SSE2 / AVX2 z technikami o zwiększonej precyzji .
Biorąc pod uwagę wektor losowych bajtów, możemy pokroić każdy 16-bitowy element na wiele cyfr dziesiętnych. Produkujemy wiele wektorów 16-bitowych elementów, z których każdy jest jedną cyfrą ASCII + spacją ASCII . Przechowujemy to bezpośrednio w naszym buforze wyjściowym.
Moja oryginalna wersja właśnie
x / 6554
pobierała jedną losową cyfrę z każdego elementu wektora uint16_t. Zawsze wynosi od 0 do 9 włącznie. Jest tendencyjny9
, ponieważ(2^16 -1 ) / 6554
wynosi tylko 9.99923. (6554 = Ceil ((2 ^ 16-1) / 10), co zapewnia, że iloraz jest zawsze <10.)x/6554
można obliczyć z jednym pomnożeniem przez stałą „magiczną” ( odwrotność stałego punktu ) i prawidłowe przesunięcie wyniku wysokiej połowy. To najlepszy przypadek dzielenia przez stałą; niektóre dzielniki wymagają więcej operacji, a podpisany podział wymaga dodatkowej pracy.x % 10
ma podobny błąd i nie jest tak tani w obliczeniach. (wyjście asm gcc jest równoważnex - 10*(x/10)
, tj. dodatkowe zwielokrotnienie i odjęcie na górze podziału za pomocą modularnego odwrotności multiplikatywnej). Również najniższy bit xorshift128 + nie jest tak wysokiej jakości , więc dzielenie się, aby wziąć entropię z wysokich bitów, jest lepsze ( dla jakości, jak również prędkości) niż modulo, aby pobrać entropię z małych bitów.Możemy jednak użyć więcej entropii w każdym uint16_t, patrząc na małe cyfry dziesiętne, takie jak
digit()
funkcja @ Nominal . Aby uzyskać maksymalną wydajność, postanowiłem wziąć 3 małe cyfry dziesiętne ix/6554
, aby zapisać jeden PMULLW i PSUBW (i prawdopodobnie niektóre MOVDQA) w porównaniu z opcją wyższej jakości, biorąc 4 niskie cyfry dziesiętne. Niskie 3 cyfry dziesiętne mają nieznaczny wpływ na x / 6554, więc istnieje pewna korelacja między cyframi z tego samego elementu (separacja 8 lub 16 cyfr na wyjściu ASCII, w zależności od szerokości wektora).Myślę, że gcc dzieli przez 100 i 1000, zamiast dłuższego łańcucha, który sukcesywnie dzieli się przez 10, więc prawdopodobnie nie skraca znacząco długości łańcucha zależności nie przenoszonej przez pętlę, który daje 4 wyniki z każdego wyjścia PRNG. port0 (mnożenie i przesuwanie wektora) jest wąskim gardłem ze względu na modułowe odwrotne multiplikacje i przesunięcia w xorshift +, więc zdecydowanie warto zapisać wielokrotność wektora.
xorshift + jest tak szybki, że nawet użycie tylko ~ 3,3 bitów losowości na każde 16 (tj. 20% wydajności) nie jest dużo wolniejsze niż dzielenie go na wiele cyfr dziesiętnych. Przybliżamy jedynie rozkład równomierny, ponieważ odpowiedź ta koncentruje się na szybkości, o ile jakość nie jest tak zła.
Wszelkie zachowania warunkowe utrzymujące zmienną liczbę elementów wymagałyby znacznie więcej pracy. (Ale może nadal być to nieco wydajne przy użyciu technik upakowywania po lewej stronie SIMD . Jednak staje się to mniej wydajne dla małych rozmiarów elementów; gigantyczne tabele wyszukiwania z maską losową nie są wykonalne i nie ma tasowania z przecinaniem linii AVX2 z mniejszym niż 32- elementy bitowe. Wersja PSHUFB 128b może nadal być w stanie wygenerować maskę w locie za pomocą BMI2 PEXT / PDEP, podobnie jak w przypadku AVX2 z większymi elementami , ale jest to trudne, ponieważ 64-bitowa liczba całkowita zawiera tylko 8 bajtów. Godbolt link w tej odpowiedzi znajduje się kod, który może działać w przypadku większej liczby elementów).
Jeśli opóźnienie RNG jest wąskim gardłem, moglibyśmy iść jeszcze szybciej, uruchamiając równolegle dwa wektory generatorów, naprzemiennie z których korzystamy. Kompilator nadal może łatwo przechowywać wszystko w rejestrach w rozwiniętej pętli, co pozwala na równoległe działanie dwóch łańcuchów zależności.
W obecnej wersji, dzieląc dane wyjściowe PRNG, faktycznie wąskie gardło w przepustowości portu 0, a nie w opóźnieniu PRNG, więc nie ma takiej potrzeby.
Kod: wersja AVX2
Pełna wersja z większą ilością komentarzy na temat eksploratora kompilatora Godbolt .
Niezbyt schludny, przepraszam, muszę iść spać i chcę to opublikować.
Aby uzyskać wersję SSE2,
s/_mm256/_mm
,s/256/128/
,s/v16u/v8u/
, i zmienićvector_size(32)
do 16. Również zmienić przyrost nowej linii od 16 do 4 * 4 * 8. (Tak jak powiedziałem, kod jest nieporządny i nie jest dobrze skonfigurowany do kompilacji dwóch wersji. Nie planowałem początkowo tworzenia wersji AVX2, ale potem naprawdę chciałem przetestować procesor Haswell, do którego miałem dostęp.)Kompiluj za pomocą gcc, clang lub ICC (lub mam nadzieję, że jakikolwiek inny kompilator, który rozumie dialekt GNU C C99 i wewnętrzne cechy Intela). Rozszerzenia wektorów GNU C są bardzo wygodne, aby kompilator generował magiczne liczby dla dzielenia / modulo przy użyciu modularnych odwrotności multiplikatywnych, a okazjonalne
__attribute__
s są przydatne.Można to zapisać przenośnie, ale zajmie to więcej kodu.
Uwagi dotyczące wydajności:
Nakładający się sklep do wstawiania nowych linii ma znaczny narzut, aby zdecydować, gdzie go umieścić (nieprzewidywalne rozgałęzienia i wąskie gardła nakładki na Core2), ale sam sklep nie ma wpływu na wydajność. Komentowanie tylko tej instrukcji sklepu w asmie kompilatora (pozostawiając wszystkie rozgałęzienia bez zmian) pozostawiło wydajność Core2 całkowicie niezmienioną, a powtarzane przebiegi dały ten sam czas do +/- mniej niż 1%. Doszedłem więc do wniosku, że bufor / pamięć podręczna radzą sobie z tym dobrze.
Mimo to użycie pewnego rodzaju okna obrotowego
ascii_digitspace
z jednym elementem o nowej linii może być jeszcze szybsze, jeśli rozwiniemy się na tyle, że znikną jakiekolwiek liczniki / rozgałęzienia.Zapisywanie do / dev / null jest w zasadzie brakiem operacji, więc bufor prawdopodobnie pozostaje gorący w pamięci podręcznej L2 (256 kB na rdzeń w Haswell). Oczekuje się idealnego przyspieszenia z wektorów 128b do wektorów 256b: nie ma dodatkowych instrukcji, a wszystko (łącznie ze sklepami) dzieje się z podwójną szerokością. Jednak gałąź wstawiania nowej linii jest pobierana dwa razy częściej. Niestety nie miałem czasu na konfigurację cygwina Haswella z tą częścią
#ifdef
.2,5 GHz * 32B / 13,7 GB / s = 5,84 cykli na sklep AVX2 na Haswell. To całkiem nieźle, ale może być szybsze. Może w wywołaniach systemowych cygwin jest trochę narzutów, niż myślałem. Nie próbowałem komentować tych w danych wyjściowych asm kompilatora (co zapewni, że nic nie zostanie zoptymalizowane).
Pamięć podręczna L1 może obsługiwać jeden magazyn 32B na zegar, a L2 nie jest znacznie mniejszą przepustowością (chociaż większe opóźnienie).
Kiedy spojrzałem na IACA kilka wersji temu (bez rozgałęzienia dla nowych linii, ale otrzymując tylko jeden wektor ASCII na wektor RNG), przewidywałem coś w rodzaju jednego sklepu wektorowego 32B na 4 lub 5 zegarów.
Miałem nadzieję przyspieszyć wydobywanie większej ilości danych z każdego wyniku RNG, na podstawie samego spojrzenia na asm, biorąc pod uwagę przewodniki Agner Fog i inne zasoby optymalizacyjne, do których dodałem linki w wiki tagu SO x86 ).
Prawdopodobnie byłoby to znacznie szybsze na Skylake , gdzie mnożenie liczb całkowitych wektora i przesunięcie może działać na dwukrotnie większej liczbie portów (p0 / p1) w porównaniu do Haswella (tylko p0). Xorshift i ekstrakcja cyfr używają wielu przesunięć i mnożeń. ( Aktualizacja: Skylake działa na 3.02 IPC, co daje nam 3,77 cykli na 32-bajtowy sklep AVX2 , z czasem 0,030s na 1 GB iteracji, pisząc do
/dev/null
Linux 4.15 na i7-6700k przy 3,9 GHz.Do poprawnego działania nie wymaga trybu 64-bitowego . Wersja SSE2 jest równie szybka po kompilacji
-m32
, ponieważ nie potrzebuje bardzo wielu rejestrów wektorowych, a cała 64-bitowa matematyka jest wykonywana w wektorach, a nie w rejestrach ogólnego przeznaczenia.W rzeczywistości jest nieco szybszy w trybie 32-bitowym na Core2, ponieważ makro-fuzja porównania / rozgałęzienia działa tylko w trybie 32-bitowym, więc jest mniej ulepszeń rdzenia poza kolejnością (18,3 s (1,85 instrukcji na zegar) vs 16,9 s (2,0 IPC)). Mniejszy rozmiar kodu, ponieważ nie ma przedrostków REX, pomaga również dekoderom Core2.
Ponadto niektóre ruchy wektorowe reg-reg są zastępowane obciążeniami, ponieważ nie wszystkie stałe są już ustalane w regach wektorowych. Ponieważ przepustowość ładowania z pamięci podręcznej L1 nie jest wąskim gardłem, to w rzeczywistości pomaga. (np. pomnożenie przez stały wektor
set1(10)
:movdqa xmm0, xmm10
/pmullw xmm0, xmm1
zamienia się wmovdqa xmm0, [constant]
/pmullw xmm0, xmm1
.) Ponieważ reg-reg MOVDQA wymaga portu ALU, konkuruje on z wykonaną pracą, ale obciążenie MOVDQA konkuruje tylko o szerokość pasma dekodowania interfejsu. (Posiadanie 4-bajtowego adresu w wielu instrukcjach anuluje wiele korzyści z zapisywania prefiksów REX.Nie zdziwiłbym się, gdyby uratowanie ALU MOVDQA było źródłem prawdziwych korzyści, ponieważ frontend powinien nadążać za średnią 2.0 IPC.
Wszystkie te różnice znikają na Haswell, gdzie cała sprawa powinna przebiegać z odkodowanej pamięci podręcznej, jeśli nie z bufora sprzężenia zwrotnego. Makro-synteza gałęzi ALU + działa w obu trybach od Nehalem.
źródło
Oto rozwiązanie, które, mam nadzieję, jest łatwe do zrozumienia:
od
tworzy jednolity strumień cyfr szesnastkowych z/dev/random
.tr
pozbywa się liter, zachowując tylko0-9
cyfryfold
zapewnia, że w wierszu jest 100 cyfrawk
wstawia spacje do liniihead
obcina wejście do 1 gigabajtaźródło
Możesz użyć tego
jot
polecenia:źródło
fmt
nie ma opcji szerokości bramki. W każdym razie będzie to dokładne, ponieważ wszystkie cyfry zajmują dokładnie jedną kolumnę!fmt
wersja tofmt (GNU coreutils) 8.25
(Ubuntu 16.04)536870912
Jest to podobne do metody Stéphane'a Chazelasa, jednak czytam 64 bity jednocześnie, aby poprawić wydajność. Dystrybucja jest nadal jednolita, ale teraz dostajesz 19 cyfr na każde 8 bajtów zamiast tylko 8 w najlepszym przypadku, jak wcześniej
Na platformie 32-bitowej za każdym razem będzie odczytywanych 9 cyfr zamiast 19.
źródło
perl
nie jest skompilowany z obsługą quadów.next if $n >= 1000000000; $s = sprintf("%09u", $n);
aby uzyskać tylko 9 cyfr$n = unpack("Q")
jeśli quad nie jest obsługiwany.BEGIN{$/=\4; $,=" "} $n = unpack("L");
także na<16e18
i dzielisz przez 16, otrzymujesz 18 cyfr 86,7% dla 1,95 dpB. W wersji 32-bitowej<4e9 /4
otrzymuje 9 cyfr 93,1% dla 2,10 dpB. Ale 5 bajtów (jako szesnastkowy (H10))<1e12
daje 12 cyfr 90,9% dla 2,18 dpB, lub podzielenie heksa na pół i wykonanie każdej połowy<1e6
daje 6 cyfr 95,4% dla 2,29 dpB; zbliża się to do granicy log_10 (256) = 2,41.W pewnym sensie zgadzam się z Nominal Animal na używanie skompilowanego języka programowania, jeśli potrzebujesz prędkości. Nie musisz jednak pisać własnego kodu RNG w C. C ++ 11 oferuje doskonałą Mersenne Twister jako część standardowej biblioteki.
Powyższy kod jest dość prosty i zajmuje około minuty, gdy przesyłam dane wyjściowe do pliku. Możemy iść znacznie szybciej, tworząc ciąg wystarczająco duży na 100 cyfr i włamując do niego cyfry. To pozwala nam wywoływać cout na każdej linii, a nie na każdej cyfrze.
Ten kod zajmuje mojej maszynie około sześciu sekund. Pamiętaj, że to standardowe wyjście, więc potokuj go do pliku.
Mam kilka zastrzeżeń. Najpierw piszę to na komputerze z systemem Windows. Myślę, że wszystkie biblioteki są obecne w systemie Linux, ale jeśli się mylę, koniecznie zwróć na to uwagę.
Poza tym generuje dokładnie pół miliarda cyfr oddzielonych spacjami, co technicznie jest gigabajtem, ale może nie dokładnie tym, czego chciałeś. Wyprowadza 5 milionów linii, 100 cyfr na linię. Jeśli różnica jest ważna, możesz zwiększyć liczbę wierszy. W moim systemie Windows plik wydaje się być nieco większy niż 10 ^ 9 bajtów, co myślę, że ma to związek z dodatkowymi znakami nowej linii.
źródło
/dev/null
których byłoby znacznie szybsze niż zapis do prawdziwego plikuwrite()
wywołaniu systemowym polega na zapamiętywaniu w pamięci podręcznej, która blokuje tylko wtedy, gdy jądro zdecyduje się to zrobić, zamiast przydzielić więcej miejsca w buforze. Ten program powinien wąskie gardło we / wy dysku tylko wtedy, gdy pamięć jest napięta lub jeśli użyłeś O_DIRECT do ominięcia pamięci podręcznej. Jeśli jesteśwrite()
w kawałkach mniejszych niż rozmiar pamięci podręcznej, mam nadzieję, że Twoje dane trafią do pamięci głównej tylko raz, a bufor, który został przepisany w miejscu, pozostaje gorący w pamięci podręcznej L2 lub L3.To zależy od twojej definicji „losowej”. Jeśli masz na myśli kryptograficznie losowe, musisz tylko zdobyć dobrą bibliotekę i ugryźć kulę, poczekać, aż się uruchomi.
Jeśli potrzebujesz czegoś, co wygląda dość losowo, oto prosty sposób:
Uruchomienie na wolnej maszynie może zająć godzinę; wystarczająco szybki i losowy do większości celów.
źródło
/dev/urandom
prawdopodobnie będzie lepszygzip
zarówno pod względem szybkości, jak i losowości.Get a file that is several Gb long
potrzebujesz pliku ** co najmniej 8 Gb`, aby uzyskać plik 1 GBźródło
cat file | tr
kiedy możesztr <file
. IIRC, możesz nawet<file tr
. Myślałem, że mówisz tylko o tym skrypcie powłoki, który wygląda niezgrabnie i powoli, jakdu | awk
po każdej linii, aby sprawdzić rozmiar, i ponownie otwierając plik w celu dołączenia każdej linii zamiast przekierowywania poza pętlę.cat /dev/urandom | busy-cmd
jest jednym z tych rzadkich przypadków, w których może to mieć sens, ponieważ może podzielić losowe generowanie i zajęte cmd między procesory. Nie tyle dla tr, ale robi różnicęod
na przykład dla Sama .