Czy ktoś może pobić wydajność mojej liczby całkowitej do kodu std :: string, połączonego poniżej?
Jest już kilka pytań, które wyjaśniają, jak przekonwertować liczbę całkowitą na a std::string
w C ++, na przykład to , ale żadne z przedstawionych rozwiązań nie jest wydajne.
Oto gotowy do kompilacji kod dla niektórych typowych metod, z którymi można konkurować:
- „Sposób C ++” z wykorzystaniem stringstream: http://ideone.com/jh3Sa
- sprintf, które osoby z SO-ery zwykle polecają osobom dbającym o wydajność: http://ideone.com/82kwR
W przeciwieństwie do popularnego przekonania , boost::lexical_cast
ma własną implementację ( biały papier ) i nie używać stringstream
i numeryczne operatorzy wstawiania. Naprawdę chciałbym zobaczyć porównanie jego wydajności, ponieważ to drugie pytanie sugeruje, że jest nieszczęśliwy .
I mój własny wkład, który jest konkurencyjny na komputerach stacjonarnych i pokazuje podejście, które działa z pełną prędkością również w systemach wbudowanych, w przeciwieństwie do algorytmów zależnych od liczby całkowitej modulo:
- Algorytmy Bena: http://ideone.com/SsEUW
Jeśli chcesz użyć tego kodu, udostępnię go na uproszczonej licencji BSD (dozwolone wykorzystanie komercyjne, wymagane podanie źródła). Po prostu zapytaj.
Wreszcie funkcja ltoa
jest niestandardowa, ale szeroko dostępna.
- Wersja ltoa, dla każdego, kto ma kompilator, który ją zapewnia (ideone nie): http://ideone.com/T5Wim
Wkrótce opublikuję moje wyniki pomiarów jako odpowiedź.
Zasady algorytmów
- Podaj kod umożliwiający konwersję co najmniej 32-bitowych liczb całkowitych ze znakiem i bez znaku na liczbę dziesiętną.
- Utwórz dane wyjściowe jako plik
std::string
. - Żadnych sztuczek, które są niekompatybilne z wątkami i sygnałami (na przykład bufory statyczne).
- Możesz założyć zestaw znaków ASCII.
- Upewnij się, że przetestowałeś swój kod na
INT_MIN
maszynie z dopełnieniem do dwóch, gdzie wartość bezwzględna nie jest reprezentowalna. - Idealnie, wyjście powinno być znakowym dla znaku identycznego z kanonicznej wersji C ++ przy użyciu
stringstream
, http://ideone.com/jh3Sa , ale wszystko, co jest jasno zrozumiałe, ponieważ właściwa ilość jest ok, zbyt. - NOWOŚĆ : Chociaż możesz użyć dowolnych opcji kompilatora i optymalizatora (z wyjątkiem całkowicie wyłączonych) do porównania, kod musi również skompilować się i dawać poprawne wyniki przynajmniej w VC ++ 2010 i g ++.
Nadzieja na dyskusję
Oprócz lepszych algorytmów chciałbym również uzyskać testy porównawcze na kilku różnych platformach i kompilatorach (użyjmy przepustowości MB / s jako naszej standardowej jednostki miary). Uważam, że kod mojego algorytmu (wiem, że sprintf
benchmark ma kilka skrótów - teraz naprawiony) jest dobrze zdefiniowanym zachowaniem przez standard, przynajmniej przy założeniu ASCII, ale jeśli widzisz jakieś niezdefiniowane zachowanie lub dane wejściowe, dla których wyjście jest nieprawidłowy, proszę o to zwrócić uwagę.
Wnioski:
Różne algorytmy działają dla g ++ i VC2010, prawdopodobnie z powodu różnych implementacji std::string
każdego z nich. VC2010 najwyraźniej radzi sobie lepiej z NRVO, pozbycie się zwrotu po wartości pomogło tylko w gcc.
Okazało się, że kod przewyższa sprintf
o rząd wielkości. ostringstream
pozostaje w tyle o czynnik 50 i więcej.
Zwycięzcą wyzwania jest user434507, który tworzy kod działający z 350% szybkością mojej własnej na gcc. Dalsze wpisy są zamknięte z powodu kaprysów społeczności SO.
Obecni (finałowi?) Mistrzowie prędkości to:
- Dla gcc: user434507, 8 razy szybciej niż
sprintf
: http://ideone.com/0uhhX - Visual C ++: Timo, 15 razy szybciej niż
sprintf
: http://ideone.com/VpKO3
źródło
Odpowiedzi:
Spowoduje to wysadzenie w systemach, które nie zezwalają na niewyrównany dostęp do pamięci (w takim przypadku pierwsze niewyrównane przypisanie przez
*(short*)
spowodowałoby segfault), ale w przeciwnym razie powinno działać bardzo dobrze.Jedną ważną rzeczą do zrobienia jest zminimalizowanie użycia
std::string
. (To ironiczne, wiem.) Na przykład w programie Visual Studio większość wywołań metod std :: string nie jest wbudowanych, nawet jeśli określisz / Ob2 w opcjach kompilatora. Więc nawet coś tak trywialnego jak wywołaniestd::string::clear()
, którego można by się spodziewać bardzo szybko, może zająć 100 znaczników zegara przy łączeniu CRT jako biblioteki statycznej i aż 300 taktów przy łączeniu jako biblioteka DLL.Z tego samego powodu zwracanie przez odwołanie jest lepsze, ponieważ pozwala uniknąć przypisania, konstruktora i destruktora.
źródło
sprintf
. A z VC ++ 2010, pobiera około 50 MB / s, około dwa razy szybciej niż sprintf.sprintf
implementacji poszedłem na skróty , wspomniałem już o tym w swoim pytaniu, ale uważam, że kod do bitu daje dokładnie taki sam wynik jak stringstream.sprintf
opakowania, jak również, aby uniknąć nieporozumień.Ach, tak przy okazji, niesamowite wyzwanie ... Świetnie się przy tym bawiłem.
Mam dwa algorytmy do przesłania (kod jest na dole, jeśli masz ochotę do niego przeskoczyć). W moich porównaniach wymagam, aby funkcja zwracała ciąg znaków i obsługiwała int i unsigned int. Porównywanie rzeczy, które nie tworzą łańcucha, do tych, które to robią, nie ma sensu.
Pierwsza z nich to zabawna implementacja, która nie używa żadnych wstępnie obliczonych tabel wyszukiwania ani jawnego dzielenia / modulo. Ten jest konkurencyjny w stosunku do innych z gcc i ze wszystkimi oprócz Timo na msvc (nie bez powodu, który wyjaśnię poniżej). Drugi algorytm to moje rzeczywiste zgłoszenie zapewniające najwyższą wydajność. W moich testach bije wszystkie inne zarówno na gcc, jak i msvc.
Myślę, że wiem, dlaczego niektóre wyniki na MSVC są bardzo dobre. std :: string ma dwa odpowiednie konstruktory,
std::string(char* str, size_t n)
a
std::string(ForwardIterator b, ForwardIterator e)
gcc robi to samo dla obu z nich ... to znaczy używa drugiego do implementacji pierwszego. Pierwszy konstruktor można zaimplementować znacznie wydajniej niż ten, a MSVC to robi. Dodatkową korzyścią jest to, że w niektórych przypadkach (jak mój szybki kod i kod Timo) konstruktor napisów może być wbudowany. W rzeczywistości samo przełączanie się między tymi konstruktorami w MSVC jest prawie dwukrotną różnicą dla mojego kodu.
Moje wyniki testów wydajnościowych:
Źródła kodu:
- Voigt
- Timo
- ergosys
- user434507
- user-voigt-timo
- hopman-fun
- hopman-fast
gcc 4.4.5 -O2 na Ubuntu 10.10 64-bit, Core i5
MSVC 2010 64-bit / Ox w systemie Windows 7 64-bit, Core i5
Oto kilka wyników i ramy testowania / pomiaru czasu na stronie ideone
http://ideone.com/XZRqp
Zauważ, że ideone to środowisko 32-bitowe. Oba moje algorytmy cierpią z tego powodu, ale hopman_fast jest przynajmniej nadal konkurencyjny.
Zauważ, że dla tych dwóch lub innych, które nie tworzą ciągu, dodałem następujący szablon funkcji:
A teraz mój kod ... najpierw ten zabawny:
A potem szybki:
źródło
Dane porównawcze dla kodu podanego w pytaniu:
Na ideone (gcc 4.3.4):
Core i7, Windows 7 64-bitowy, 8 GB RAM, Visual C ++ 2010 32-bitowy:
cl /Ox /EHsc
Core i7, Windows 7 64-bitowy, 8 GB RAM, Visual C ++ 2010 64-bitowy:
cl /Ox /EHsc
Core i7, Windows 7 64-bitowy, 8 GB RAM, cygwin gcc 4.3.4:
g++ -O3
edytować : miałem dodać własną odpowiedź, ale pytanie zostało zamknięte, więc dodaję je tutaj. :) Napisałem własny algorytm i udało mi się uzyskać przyzwoitą poprawę w stosunku do kodu Bena, chociaż testowałem go tylko w MSVC 2010. Zrobiłem również test porównawczy wszystkich przedstawionych do tej pory implementacji, używając tej samej konfiguracji testowej, która była w oryginale Bena kod. - Timo
Intel Q9450, Win XP 32-bitowy, MSVC 2010
cl /O2 /EHsc
-
źródło
std::string
czy słaba optymalizacja kodu arytmetycznego. Stworzę kolejną wersję, którastd::string
na końcu się nie konwertuje i zobaczę, czy gcc wypada lepiej.Chociaż otrzymane tutaj informacje dotyczące algorytmów są całkiem niezłe, myślę, że pytanie jest „zepsute” i wyjaśnię, dlaczego tak myślę:
Pytanie dotyczy wykonania konwersji
int
->std::string
i może to być interesujące przy porównywaniu powszechnie dostępnej metody, takiej jak różne implementacje stringstream lub boost :: lexical_cast. Nie ma jednak sensu prosić o nowy kod , wyspecjalizowany algorytm, aby to zrobić. Powodem jest to, że int2string zawsze będzie obejmował alokację sterty z std :: string i jeśli próbujemy wycisnąć ostatnie z naszego algorytmu konwersji, nie sądzę, aby sensowne było mieszanie tych pomiarów z alokacjami sterty wykonanymi przez std: :strunowy. Jeśli chcę wydajnej konwersji, zawsze będę używał bufora o stałym rozmiarze i na pewno nigdy nie będę alokować niczego na stercie!Podsumowując uważam, że czasy powinny zostać podzielone:
Te aspekty nie powinny być mieszane w jednym czasie, IMHO.
źródło
std::string
wymóg „wyjście jako ” został tam umieszczony, aby wszystko było sprawiedliwe i spójne dla wszystkich zgłoszeń. Algorytmy, które szybciej dająstd::string
wyniki, będą również szybciej wypełniać wstępnie przydzielony bufor.Nie mogę testować pod VS, ale wydaje się, że jest to szybsze niż twój kod dla g ++, około 10%. Prawdopodobnie można go dostroić, wybrane wartości decyzyjne są przypuszczeniami. tylko int, przepraszam.
źródło
char
naunsigned
daje podobną poprawę szybkości w moim kodzie, przynajmniej na gcc / ideone ideone.com/uthKK . Jutro przetestuję na VS.Zaktualizowana odpowiedź użytkownika2985907 ... modp_ufast ...
źródło
Mismatch found: Generated: -99 Reference: -9099999 Mismatch found: Generated: -99 Reference: -9099998 Mismatch found: Generated: -99 Reference: -9099997
Oto moja mała próba tej zabawnej układanki.
Zamiast używać tabel przeglądowych, chciałem, aby kompilator to wszystko rozgryzł. W szczególności w tym przypadku - jeśli czytasz Hackers 'Delight, widzisz, jak działają divide i modulo - co sprawia, że bardzo możliwe jest zoptymalizowanie tego za pomocą instrukcji SSE / AVX.
Test porównawczy wydajności
Jeśli chodzi o szybkość, mój test porównawczy mówi mi, że jest 1,5 razy szybszy niż praca Timo (na moim Intel Haswell działa z prędkością około 1 GB / s).
Rzeczy, które można uznać za oszustwo
Jeśli chodzi o oszustwo nie-tworzenia ciągów standardowych, którego używam - oczywiście wziąłem to pod uwagę również w moim benchmarku metody Timo.
Używam wewnętrznego: BSR. Jeśli chcesz, możesz zamiast tego użyć tabel DeBruijn - o czym pisałem sporo w moim „najszybszym 2logu”. Oczywiście ma to wpływ na wydajność (* cóż ... jeśli wykonujesz wiele operacji itoa, możesz faktycznie zrobić szybszy BSR, ale myślę, że to niesprawiedliwe ...).
Tak to działa
Pierwszą rzeczą do zrobienia jest ustalenie, ile pamięci potrzebujemy. Jest to w zasadzie 10log, który można zaimplementować na wiele inteligentnych sposobów. Zobacz często cytowane „ Bit Twiddling Hacks uzyskać szczegółowe informacje, ”.
Następną rzeczą do zrobienia jest wykonanie numerycznego wyjścia. Używam do tego rekursji szablonu, więc kompilator to rozgryzie.
Używam „modulo” i „div” tuż obok siebie. Jeśli przeczytasz Hacker's Delight, zauważysz, że oba są ze sobą ściśle powiązane, więc jeśli masz jedną odpowiedź, prawdopodobnie masz również drugą. Pomyślałem, że kompilator może dowiedzieć się szczegółów ... :-)
Kod
Pobieranie liczby cyfr za pomocą (zmodyfikowanego) log10:
Zdobycie sznurka:
źródło
Siedziałem na tym jakiś czas i w końcu zabrałem się za opublikowanie tego.
Kilka dodatkowych metod w porównaniu do podwójnego słowa na raz hopman_fast . Wyniki dotyczą std :: string zoptymalizowanego pod kątem krótkich ciągów znaków, ponieważ w przeciwnym razie różnice w wydajności zostaną zasłonięte przez narzut kodu zarządzającego ciągiem kopiowania przy zapisie. Przepustowość jest mierzona w taki sam sposób, jak w innym miejscu w tym temacie, liczniki cykli dotyczą surowych części serializacji kodu przed skopiowaniem buforu wyjściowego do ciągu.
Przełączniki czasowe kompilacji:
-DVSTRING - włącza ciągi SSO dla starszych konfiguracji GCC
-DBSR1 - włącza szybki log10
-DRDTSC - włącza liczniki cykli
źródło
Wydaje mi się, że stworzyłem najszybszy algorytm zamiany liczb całkowitych na łańcuch. Jest to odmiana algorytmu Modulo 100, która jest około 33% szybsza, a co najważniejsze, jest szybsza zarówno dla mniejszych, jak i dużych liczb. Nazywa się to algorytmem Script ItoS. Aby przeczytać artykuł, który wyjaśnia, w jaki sposób zaprojektowałem algorytm, @zobacz https://github.com/kabuki-starship/kabuki-toolkit/wiki/Engineering-a-Faster-Integer-to-String-Algorithm . Możesz użyć algorytmu, ale pomyśl o wniesieniu wkładu w maszynę Kabuki VM i sprawdź Script ; zwłaszcza jeśli interesuje Cię AMIL-NLP i / lub protokoły sieciowe definiowane programowo.
Autor
źródło
std::string
, aby porównanie z innymi wymienionymi tutaj metodami było prawidłowe. Na początku nie mogłem znaleźć zastosowania operatora przesunięcia dla drzewa wyszukiwania binarnego, ponieważ porównanie jest już wyjątkowo szybkie, ale teraz zdaję sobie sprawę, że byłoby to przydatne do wstępnego obliczenia tej przesuniętej wartości, gdybyś tego potrzebował. Ale ty go nie używasz. Z drugiej strony, nie otrzymujesz dużych literałów zakodowanych w instrukcjach, więc może to wystarczający powód.Modyfikacja rozwiązania user434507. Zmodyfikowano, aby używać tablicy znaków zamiast ciągu C ++. Działa trochę szybciej. Przeniosłem również czek na 0 niżej w kodzie ... ponieważ nigdy się to nie zdarza w moim konkretnym przypadku. Przenieś go z powrotem, jeśli jest to bardziej powszechne w Twoim przypadku.
źródło
Mismatch found: Generated: -9999999990 Reference: -999999999 Mismatch found: Generated: -9999999980 Reference: -999999998 Mismatch found: Generated: -9999999970 Reference: -999999997
Używamy następującego kodu (dla MSVC):
Szablon tBitScanReverse:
pomocnicy char / wchar_t:
Moce 10 tabel:
Rzeczywisty wydruk:
Ostatnią pętlę można rozwinąć:
Główny pomysł jest taki sam jak @atlaste zasugerowany wcześniej: https://stackoverflow.com/a/29039967/2204001
źródło
Natknąłem się na to z powodu niedawnej aktywności; Naprawdę nie mam czasu na dodawanie testów porównawczych, ale chciałem dodać to, co napisałem w przeszłości, kiedy potrzebuję szybkiej konwersji liczb całkowitych na ciąg ...
https://github.com/CarloWood/ai-utils/blob/master/itoa.h
https://github.com/CarloWood/ai-utils/blob/master/itoa.cxx
Sztuczka zastosowana tutaj polega na tym, że użytkownik musi dostarczyć std :: tablicę, która jest wystarczająco duża (na stosie) i że ten kod zapisuje ciąg do tego od tyłu, zaczynając od jednostek, a następnie zwraca wskaźnik do tablicy z przesunięciem do miejsca, w którym wynik faktycznie się zaczyna.
To dlatego nie alokuje ani nie przenosi pamięci, ale nadal wymaga dzielenia i modulo na cyfrę wyniku (co uważam za wystarczająco szybkie, ponieważ jest to tylko kod uruchamiany wewnętrznie na procesorze; dostęp do pamięci jest zwykle problemem imho).
źródło
Dlaczego nikt nie używa funkcji div z biblioteki standardowej, kiedy potrzebne są zarówno iloraz, jak i reszta?
Używając kodu źródłowego Timo, otrzymałem coś takiego:
Ok, w przypadku int bez znaku nie można użyć funkcji div, ale bez znaku można obsługiwać osobno.
Zdefiniowałem makro COPYPAIR w następujący sposób, aby przetestować warianty kopiowania 2 znaków z par digit_pairs (nie znalazłem żadnej oczywistej przewagi żadnej z tych metod):
źródło