C ++ nieco magiczna
0,84 ms z prostym RNG, 1,67 ms z c ++ 11 std :: knuth
0,16 ms z niewielką modyfikacją algorytmu (patrz edycja poniżej)
Implementacja Pythona działa na moim urządzeniu w 7,97 sekundy. Jest to więc 9488 do 4772 razy szybszy, w zależności od wybranego RNG.
#include <iostream>
#include <bitset>
#include <random>
#include <chrono>
#include <stdint.h>
#include <cassert>
#include <tuple>
#if 0
// C++11 random
std::random_device rd;
std::knuth_b gen(rd());
uint32_t genRandom()
{
return gen();
}
#else
// bad, fast, random.
uint32_t genRandom()
{
static uint32_t seed = std::random_device()();
auto oldSeed = seed;
seed = seed*1664525UL + 1013904223UL; // numerical recipes, 32 bit
return oldSeed;
}
#endif
#ifdef _MSC_VER
uint32_t popcnt( uint32_t x ){ return _mm_popcnt_u32(x); }
#else
uint32_t popcnt( uint32_t x ){ return __builtin_popcount(x); }
#endif
std::pair<unsigned, unsigned> convolve()
{
const uint32_t n = 6;
const uint32_t iters = 1000;
unsigned firstZero = 0;
unsigned bothZero = 0;
uint32_t S = (1 << (n+1));
// generate all possible N+1 bit strings
// 1 = +1
// 0 = -1
while ( S-- )
{
uint32_t s1 = S % ( 1 << n );
uint32_t s2 = (S >> 1) % ( 1 << n );
uint32_t fmask = (1 << n) -1; fmask |= fmask << 16;
static_assert( n < 16, "packing of F fails when n > 16.");
for( unsigned i = 0; i < iters; i++ )
{
// generate random bit mess
uint32_t F;
do {
F = genRandom() & fmask;
} while ( 0 == ((F % (1 << n)) ^ (F >> 16 )) );
// Assume F is an array with interleaved elements such that F[0] || F[16] is one element
// here MSB(F) & ~LSB(F) returns 1 for all elements that are positive
// and ~MSB(F) & LSB(F) returns 1 for all elements that are negative
// this results in the distribution ( -1, 0, 0, 1 )
// to ease calculations we generate r = LSB(F) and l = MSB(F)
uint32_t r = F % ( 1 << n );
// modulo is required because the behaviour of the leftmost bit is implementation defined
uint32_t l = ( F >> 16 ) % ( 1 << n );
uint32_t posBits = l & ~r;
uint32_t negBits = ~l & r;
assert( (posBits & negBits) == 0 );
// calculate which bits in the expression S * F evaluate to +1
unsigned firstPosBits = ((s1 & posBits) | (~s1 & negBits));
// idem for -1
unsigned firstNegBits = ((~s1 & posBits) | (s1 & negBits));
if ( popcnt( firstPosBits ) == popcnt( firstNegBits ) )
{
firstZero++;
unsigned secondPosBits = ((s2 & posBits) | (~s2 & negBits));
unsigned secondNegBits = ((~s2 & posBits) | (s2 & negBits));
if ( popcnt( secondPosBits ) == popcnt( secondNegBits ) )
{
bothZero++;
}
}
}
}
return std::make_pair(firstZero, bothZero);
}
int main()
{
typedef std::chrono::high_resolution_clock clock;
int rounds = 1000;
std::vector< std::pair<unsigned, unsigned> > out(rounds);
// do 100 rounds to get the cpu up to speed..
for( int i = 0; i < 10000; i++ )
{
convolve();
}
auto start = clock::now();
for( int i = 0; i < rounds; i++ )
{
out[i] = convolve();
}
auto end = clock::now();
double seconds = std::chrono::duration_cast< std::chrono::microseconds >( end - start ).count() / 1000000.0;
#if 0
for( auto pair : out )
std::cout << pair.first << ", " << pair.second << std::endl;
#endif
std::cout << seconds/rounds*1000 << " msec/round" << std::endl;
return 0;
}
Skompiluj w wersji 64-bitowej dla dodatkowych rejestrów. Podczas korzystania z prostego generatora losowego pętle w trybie splotowym () działają bez dostępu do pamięci, wszystkie zmienne są przechowywane w rejestrach.
Jak to działa: zamiast przechowywać S
i F
jako tablice w pamięci, jest zapisywany jako bity w uint32_t.
W przypadku S
, gdy n
stosowane są najmniej znaczące bity, gdy ustawiony bit oznacza +1 i wyłączony nieco oznacza -1.
F
wymaga co najmniej 2 bitów, aby utworzyć rozkład [-1, 0, 0, 1]. Odbywa się to poprzez generowanie losowych bitów i badanie 16 najmniej znaczących (nazywanych r
) i 16 najbardziej znaczących bitów (nazywanych l
). Jeśli l & ~r
założymy, że F wynosi +1, jeśli ~l & r
założymy, że F
wynosi -1. W przeciwnym razie F
wynosi 0. To generuje rozkład, którego szukamy.
Teraz mamy S
, posBits
z ustawionym bitem w każdej lokalizacji, w której F == 1 i negBits
z ustawionym bitem w każdym miejscu, w którym F == -1.
Możemy udowodnić, że F * S
(gdzie * oznacza mnożenie) pod warunkiem warunkuje do +1 (S & posBits) | (~S & negBits)
. Możemy również wygenerować podobną logikę dla wszystkich przypadków, w których F * S
wartość wynosi -1. I na koniec wiemy, że sum(F * S)
ocenia się na 0 wtedy i tylko wtedy, gdy w wyniku jest równa liczba -1 i + 1. Jest to bardzo łatwe do obliczenia, po prostu porównując liczbę bitów +1 i -1 bitów.
Ta implementacja używa 32-bitowych liczb całkowitych, a maksymalna n
akceptowana liczba to 16. Możliwe jest skalowanie implementacji do 31 bitów poprzez modyfikację kodu generowania losowego i do 63 bitów przy użyciu uint64_t zamiast uint32_t.
edytować
Następująca funkcja zwojowa:
std::pair<unsigned, unsigned> convolve()
{
const uint32_t n = 6;
const uint32_t iters = 1000;
unsigned firstZero = 0;
unsigned bothZero = 0;
uint32_t fmask = (1 << n) -1; fmask |= fmask << 16;
static_assert( n < 16, "packing of F fails when n > 16.");
for( unsigned i = 0; i < iters; i++ )
{
// generate random bit mess
uint32_t F;
do {
F = genRandom() & fmask;
} while ( 0 == ((F % (1 << n)) ^ (F >> 16 )) );
// Assume F is an array with interleaved elements such that F[0] || F[16] is one element
// here MSB(F) & ~LSB(F) returns 1 for all elements that are positive
// and ~MSB(F) & LSB(F) returns 1 for all elements that are negative
// this results in the distribution ( -1, 0, 0, 1 )
// to ease calculations we generate r = LSB(F) and l = MSB(F)
uint32_t r = F % ( 1 << n );
// modulo is required because the behaviour of the leftmost bit is implementation defined
uint32_t l = ( F >> 16 ) % ( 1 << n );
uint32_t posBits = l & ~r;
uint32_t negBits = ~l & r;
assert( (posBits & negBits) == 0 );
uint32_t mask = posBits | negBits;
uint32_t totalBits = popcnt( mask );
// if the amount of -1 and +1's is uneven, sum(S*F) cannot possibly evaluate to 0
if ( totalBits & 1 )
continue;
uint32_t adjF = posBits & ~negBits;
uint32_t desiredBits = totalBits / 2;
uint32_t S = (1 << (n+1));
// generate all possible N+1 bit strings
// 1 = +1
// 0 = -1
while ( S-- )
{
// calculate which bits in the expression S * F evaluate to +1
auto firstBits = (S & mask) ^ adjF;
auto secondBits = (S & ( mask << 1 ) ) ^ ( adjF << 1 );
bool a = desiredBits == popcnt( firstBits );
bool b = desiredBits == popcnt( secondBits );
firstZero += a;
bothZero += a & b;
}
}
return std::make_pair(firstZero, bothZero);
}
skraca czas działania do 0.160-0.161ms. Ręczne rozwijanie pętli (nie pokazano powyżej) powoduje, że 0.150. Mniej banalne n = 10, iter = 100000 przypadków działa poniżej 250ms. Jestem pewien, że uda mi się go uzyskać poniżej 50 ms, wykorzystując dodatkowe rdzenie, ale to zbyt łatwe.
Odbywa się to poprzez zwolnienie gałęzi wewnętrznej pętli i zamianę pętli F i S.
Jeśli bothZero
nie jest to wymagane, mogę skrócić czas pracy do 0,02 ms, rzadko zapętlając wszystkie możliwe tablice S.
-std=c++0x -mpopcnt -O2
się w trybie 32-bitowym i zajmuje 1,01 ms (nie mam pod ręką 64-bitowej wersji GCC).Python2.7 + Numpy 1.8.1: 10.242 s
Fortran 90+:
0,029 s0,003 s0,022 s0,010 sCholera prosta, przegrałeś zakład! Tu też nie ma kropli paralelizacji, tylko prosty Fortran 90+.
EDYCJA Wziąłem algorytm Guya Sirtona do permutacji tablicy
S
(dobre znalezisko: D). Najwyraźniej miałem również-g -traceback
aktywne flagi kompilatora, które spowalniały ten kod do około 0,017 s. Obecnie kompiluję to jakoDla tych, którzy nie mają
ifort
, możesz użyćEDYCJA 2 : Skrócenie czasu działania wynika z tego, że wcześniej robiłem coś złego i otrzymałem niepoprawną odpowiedź. Robienie tego we właściwy sposób jest najwyraźniej wolniejsze. Nadal nie mogę uwierzyć, że C ++ jest szybszy niż mój, więc prawdopodobnie spędzę trochę czasu w tym tygodniu, próbując ulepszyć to badziewie, aby to przyspieszyć.
EDYCJA 3 : Po prostu zmieniając sekcję RNG za pomocą jednej opartej na RNG BSD (jak sugeruje Sampo Smolander) i eliminując ciągłe dzielenie
m1
, skróciłem czas działania do tego samego, co odpowiedź C ++ autorstwa Guy Sirton . Użycie tablic statycznych (jak sugeruje Sharpie) obniża czas działania poniżej C ++! Yay Fortran! :REEDYCJA 4 Najwyraźniej to się nie kompiluje (z gfortranem) i nie działa poprawnie (nieprawidłowe wartości), ponieważ liczby całkowite przekraczają swoje limity. Wprowadziłem poprawki, aby upewnić się, że to działa, ale wymaga to posiadania albo ifort 11+, albo gfortran 4.7+ (lub innego kompilatora, który pozwala
iso_fortran_env
iint64
rodzaju F2008 ).Oto kod:
Podejrzewam, że teraz pytanie brzmi: czy przestaniesz używać Pythona wolno-jak-melasowego i użyjesz Fortrana szybko-jak-elektronowo-potrafiącego;).
źródło
integer(int64) :: b = 3141592653_int64
dla wszystkich int64. Jest to część standardu fortran i jest oczekiwane przez programistę w zadeklarowanym języku programowania. (zauważ, że domyślne ustawienia mogą oczywiście to zastąpić)Python 2,7 -
0,8820,283(Oryginał OP: 6.404s)
Edycja: Optymalizacja Stevena Rumbalskiego przez wstępne obliczenie wartości F. Dzięki tej optymalizacji cpython bije pypy o 0.365s.
Oryginalny kod OP używa tak małych tablic, że korzystanie z Numpy nie ma żadnej korzyści, co pokazuje ta czysta implementacja Pythona. Ale patrz także tę implementację numpy, która jest trzy razy szybsza niż mój kod.
Optymalizuję również, pomijając resztę splotu, jeśli pierwszy wynik nie jest równy zero.
źródło
F
ponieważ jest ich tylko 4032. ZdefiniujchoicesF = filter(any, itertools.product([-1, 0, 0, 1], repeat=n))
poza pętlami. Następnie w pętli wewnętrznej zdefiniujF = random.choice(choicesF)
. Przy takim podejściu dostaję 3-krotne przyspieszenie.range(iters)
poza pętlą. W sumie otrzymuję przyspieszenie o około 7% w stosunku do twojej bardzo miłej odpowiedzi.Rdza: 0,011s
Oryginalny Python: 8.3
Proste tłumaczenie oryginalnego języka Python.
--opt-level=3
rustc 0.11-pre-nightly (eea4909 2014-04-24 23:41:15 -0700)
a ściślej)źródło
a
si w konwolucjib
; naprawiono (nie zmienia znacząco środowiska wykonawczego).C ++ (VS 2012) -
0.026s0.015sPython 2.7.6 / Numpy 1.8.1 - 12s
Przyspieszenie ~ x800.
Różnica byłaby znacznie mniejsza, gdyby zwinięte tablice były bardzo duże ...
Kilka uwag:
S[0]
że jest „najmniej znaczącą” cyfrą.Dodaj tę główną funkcję do samodzielnego przykładu:
źródło
advance
funkcję, więc mój kod jest teraz szybszy niż twój: P (ale bardzo dobra konkurencja!)do
Zajmuje 0,015 s na moim komputerze, z oryginalnym kodem OP zajmuje ~ 7,7 s. Próbowałem zoptymalizować, generując losową tablicę i zwijając w tej samej pętli, ale wydaje się, że nie robi to dużej różnicy.
Pierwsza tablica jest generowana przez pobranie liczby całkowitej, zapisanie jej w postaci binarnej i zmianę wszystkich 1 na -1 i wszystkich 0 na 1. Reszta powinna być bardzo prosta.
Edycja: zamiast mieć
n
jakoint
, teraz mamyn
jako stałą zdefiniowaną w makrze, więc możemy użyćint arr[n];
zamiastmalloc
.Edycja2: Zamiast wbudowanej
rand()
funkcji, teraz implementuje ona xorshift PRNG. Ponadto podczas generowania tablicy losowej usuwanych jest wiele instrukcji warunkowych.Skompiluj instrukcje:
Kod:
źródło
do{}while(!flag)
lub coś w tym celu. Nie oczekuję, że zmieni to znacznie czas wykonywania (może przyspieszyć).continue;
stwierdzeniem, że przypisane-1
dok
, więck
będzie pętla od 0 ponownie.-=
raczej=-
:-) Pętla while będzie bardziej czytelna.jot
Nie spodziewam się pokonać żadnych skompilowanych języków i coś mi mówi, że zajęłoby to cudowną maszynę, aby uzyskać z tym mniej niż 0,09 s, ale i tak chciałbym przesłać to J, ponieważ jest to dość śliskie.
To trwa około 0,5 s na laptopie z poprzedniej dekady, tylko około 20 razy szybciej niż Python w odpowiedzi. Większość czasu spędza w
conv
ponieważ piszemy to leniwie (obliczamy cały splot) iw pełnej ogólności.Ponieważ wiemy rzeczy o
S
iF
możemy przyspieszyć, dokonując konkretnych optymalizacji dla tego programu. Najlepsze, co udało mi się wymyślić, toconv =: ((num, num+1) { +//.)@:(*/)"1
wybranie dwóch liczb, które odpowiadają sumom diagonalnym i najdłuższym elementom splotu, co w przybliżeniu zmniejsza czas o połowę.źródło
Perl - 9,3 razy szybszy ... 830% poprawy
Na moim starożytnym netbooku kod OP trwa 53 sekundy; Wersja Alistair Buxton zajmuje około 6,5 sekundy, a następna wersja Perla zajmuje około 5,7 sekundy.
źródło
Python 2.7 - numpy 1.8.1 z powiązaniami mkl - 0,086s
(Oryginał OP: 6,404 s) (czysty python Buxtona: 0,270 s)
Jak zauważa Buxton, oryginalny kod OP używa tak małych tablic, że korzystanie z Numpy nie przynosi żadnych korzyści. Ta implementacja wykorzystuje numpy, wykonując wszystkie przypadki F i S jednocześnie w sposób zorientowany na tablicę. To w połączeniu z powiązaniami mkl dla Pythona prowadzi do bardzo szybkiej implementacji.
Zauważ też, że samo ładowanie bibliotek i uruchomienie interpretera zajmuje 0,076s, więc faktyczne obliczenie zajmuje ~ 0,01 sekundy, podobnie jak w rozwiązaniu C ++.
źródło
python -c "import numpy; numpy.show_config()"
pokaże, czy twoja wersja numpy jest skompilowana z blas / atlas / mkl itp. ATLAS to darmowy pakiet przyspieszonej matematyki, z którym można połączyć się z numpy , Intel MKL, za który zazwyczaj musisz zapłacić (chyba że jesteś akademikiem) i może być powiązany z numpy / scipy .MATLAB 0,024s
Komputer 1
Komputer 2
Postanowiłem spróbować och tak powolnego Matlaba. Jeśli wiesz jak, możesz pozbyć się większości pętli (w Matlabie), co czyni go dość szybkim. Jednak wymagania dotyczące pamięci są wyższe niż w przypadku rozwiązań zapętlonych, ale nie będzie to stanowić problemu, jeśli nie masz bardzo dużych tablic ...
Oto co robię:
Zakładam, że nie masz Matlaba, co jest bardzo złe, ponieważ naprawdę chciałbym zobaczyć, jak to wygląda ...
(Funkcja może działać wolniej przy pierwszym uruchomieniu).
źródło
Julia: 0,30 s
Op's Python: 21.36 s (duet Core2)
71-krotne przyspieszenie
Dokonałem pewnych modyfikacji odpowiedzi Juliana Armana: Po pierwsze, zawinąłem ją w funkcję, ponieważ zmienne globalne utrudniają wnioskowanie o typie Julii i JIT: Zmienna globalna może zmienić swój typ w dowolnym momencie i musi być sprawdzana przy każdej operacji . Potem pozbyłem się anonimowych funkcji i interpretacji tablic. Nie są tak naprawdę konieczne i wciąż są dość powolne. Julia jest teraz szybsza dzięki abstrakcjom niższego poziomu.
Jest o wiele więcej sposobów, aby przyspieszyć, ale robi to przyzwoitą robotę.
źródło
Ok, publikuję to tylko dlatego, że uważam, że Java musi być tutaj reprezentowana. Jestem okropny z innymi językami i przyznaję, że nie rozumiem dokładnie problemu, więc potrzebuję pomocy w naprawie tego kodu. Ukradłem większość przykładu C asa kodu, a potem pożyczyłem fragmenty od innych. Mam nadzieję, że to nie jest faux pas ...
Jedną rzeczą, na którą chciałbym zwrócić uwagę, jest to, że języki optymalizujące się w czasie wykonywania muszą być uruchamiane kilka / wiele razy, aby osiągnąć pełną prędkość. Myślę, że uzasadnione jest przyjęcie w pełni zoptymalizowanej prędkości (lub przynajmniej średniej prędkości), ponieważ większość rzeczy, które dotyczą szybkiego biegania, będą uruchamiane kilka razy.
Kod nadal wymaga poprawek, ale i tak go uruchomiłem, aby zobaczyć, o której godzinie.
Oto wyniki dla procesora Intel (R) Xeon (E) E3-1270 V2 @ 3.50GHz na Ubuntu z uruchomionym 1000 razy:
server: / tmp # time java8 -cp. Próbnik
firstzero 40000
Bothzero 20000
czas pierwszego uruchomienia: 41 ms czas ostatniego uruchomienia: 4 ms
rzeczywisty użytkownik 0m5.014s 0m4.664s sys 0m0.268s
Oto mój gówniany kod:
Próbowałem uruchomić kod Pythona po aktualizacji Pythona i zainstalowaniu python-numpy, ale otrzymuję to:
źródło
currentTimeMillis
do testów porównawczych (używaj wersji nano w Systemie), a 1k uruchomień może nie wystarczyć, aby zaangażować JIT (1,5k dla klienta i 10k dla serwera byłoby domyślnymi, chociaż wywołujesz myRand wystarczająco często, że będzie to JITed, który powinien powodować kompilację niektórych funkcji w zbiorze wywołań, które mogą tu działać) .Ostatnim, ale nie mniej ważnym, jest PNRG, który oszukuje, podobnie jak rozwiązanie C ++ i inne, więc nie jest to zbyt niesprawiedliwe.gettimeofday(&time, NULL)
dla milliSeconds, co nie jest monotoniczne i nie daje żadnych gwarancji dokładności (tak więc na niektórych platformach / jądrach jest dokładnie takie samo problemy z obecną implementacją WindowsTimeMillis - więc albo jest to w porządku, albo nie. Z drugiej strony nanoTime korzysta z tego,clock_gettime(CLOCK_MONOTONIC, &tp)
co oczywiście jest również właściwym rozwiązaniem do testowania w Linuksie.Golang wersja 45X Pythona na mojej maszynie na poniższych kodach Golang:
i poniższe kody python skopiowane z góry:
a czas poniżej:
źródło
"github.com/yanatan16/itertools"
? powiedziałbyś także, że to zadziałałoby w wielu goroutinach?C # 0,133
C # w oparciu o zwykły python Alistaira Buxtona : 0,278 s Sparaliżowany
C #: 0,135 s
Python z pytania: 5,907
s Zwykły python Alistaira: 0,853 s
Nie jestem pewien, czy ta implementacja jest poprawna - jej wyniki są inne, jeśli spojrzysz na wyniki na dole.
Z pewnością istnieją bardziej optymalne algorytmy. Właśnie zdecydowałem się użyć bardzo podobnego algorytmu do Pythona.
Jednowątkowy C
Równoległy C #:
Wyjście testowe:
Windows (.NET)
C # jest znacznie szybszy w systemie Windows. Prawdopodobnie dlatego, że .NET jest szybszy niż mono.
Czas użytkownika i sys wydaje się nie działać (używany
git bash
do mierzenia czasu).Linux (mono)
źródło
Haskell: ~ 2000x przyspieszenie na rdzeń
Skompiluj z 'ghc -O3 -funbox-strict-fields -threaded -fllvm' i uruchom z '+ RTS -Nk' gdzie k jest liczbą rdzeni na twoim komputerze.
źródło
Rubin
Ruby (2.1.0) 0.277s
Ruby (2.1.1) 0.281s
Python (Alistair Buxton) 0.330s
Python (alemi) 0.097s
źródło
wątek nie byłby kompletny bez PHP
6.6x szybszy
PHP v.5.5.9 -
1.223 0.646s;vs
Python v2.7.6 - 8.072 sec
convolve
funkcja została nieco uproszczona, aby być szybszym$F
i$FS
sprawdzanie).Wyjścia:
Edytować. Druga wersja skryptu działa tylko dla
0.646 sec
:źródło
Rozwiązanie F #
Czas działania wynosi 0,030 s po skompilowaniu do x86 na CLR Core i7 4 (8) @ 3,4 Ghz
Nie mam pojęcia, czy kod jest poprawny.
źródło
Q, 0,296 seg
Q to język zorientowany na kolekcję (kx.com)
Przepisano kod, aby wyjaśnić idiomatyczne Q, ale nie ma innych sprytnych optymalizacji
Języki skryptowe optymalizują czas programisty, a nie czas wykonywania
Pierwsza próba kodowania = nie zwycięzca, ale rozsądny czas (przyspieszenie około 30x)
UWAGI. -
\S seed
\t sentence
mierzy czas zajęty przez to zdanieźródło
Julia:
12.149 6.929sPomimo ich pretensji do szybkości , początkowy czas kompilacji JIT powstrzymuje nas!
Zauważ, że poniższy kod Julii jest faktycznie bezpośrednim tłumaczeniem oryginalnego kodu Pythona (bez optymalizacji) jako dowód na to, że możesz łatwo przenieść swoje doświadczenie programistyczne na szybszy język;)
Edytować
Bieg z
n = 8
zajmuje 32,935 s. Biorąc pod uwagę, że złożoność tego algorytmu polega naO(2^n)
tym4 * (12.149 - C) = (32.935 - C)
, żeC
jest stałą reprezentującą czas kompilacji JIT. RozwiązujemyC
toC = 5.2203
, co sugeruje, że faktyczny czas wykonanian = 6
wynosi 6,929 s.źródło
Rdza, 6,6 ms, przyspieszenie 1950x
Prawie bezpośrednie tłumaczenie kodu Alistaira Buxtona na Rust. Zastanawiałem się nad użyciem wielu rdzeni ze sztucznego jedwabiu (nieustraszona współbieżność!), Ale to nie poprawiło wydajności, prawdopodobnie dlatego, że jest już bardzo szybkie.
I Cargo.toml, ponieważ używam zależności zewnętrznych:
Porównanie prędkości:
6625608 ns wynosi około 6,6 ms. Oznacza to 1950-krotne przyspieszenie. Możliwych jest tutaj wiele optymalizacji, ale chciałem raczej uzyskać czytelność niż wydajność. Jedną z możliwych optymalizacji byłoby użycie tablic zamiast wektorów do przechowywania wyborów, ponieważ zawsze będą miały
n
elementy. Możliwe jest także użycie RNG innego niż XorShift, ponieważ chociaż Xorshift jest szybszy niż domyślny CSPRNG HC-128, jest najwolniejszy niż naiwny algorytm PRNG.źródło