Próbowałem rozwiązać to ćwiczenie z www.spoj.com: FCTRL - Factorial
Nie musisz tego czytać, po prostu zrób to, jeśli jesteś ciekawy :)
Najpierw zaimplementowałem to w C ++ (oto moje rozwiązanie):
#include <iostream>
using namespace std;
int main() {
unsigned int num_of_inputs;
unsigned int fact_num;
unsigned int num_of_trailing_zeros;
std::ios_base::sync_with_stdio(false); // turn off synchronization with the C library’s stdio buffers (from https://stackoverflow.com/a/22225421/5218277)
cin >> num_of_inputs;
while (num_of_inputs--)
{
cin >> fact_num;
num_of_trailing_zeros = 0;
for (unsigned int fives = 5; fives <= fact_num; fives *= 5)
num_of_trailing_zeros += fact_num/fives;
cout << num_of_trailing_zeros << "\n";
}
return 0;
}
Wgrałem to jako rozwiązanie dla g ++ 5.1
Wynik był następujący: Czas 0,18 Mem 3,3M
Ale potem zobaczyłem kilka komentarzy, które twierdziły, że ich czas wykonania był mniejszy niż 0,1. Ponieważ nie mogłem myśleć o szybszym algorytmie, próbowałem zaimplementować ten sam kod w C :
#include <stdio.h>
int main() {
unsigned int num_of_inputs;
unsigned int fact_num;
unsigned int num_of_trailing_zeros;
scanf("%d", &num_of_inputs);
while (num_of_inputs--)
{
scanf("%d", &fact_num);
num_of_trailing_zeros = 0;
for (unsigned int fives = 5; fives <= fact_num; fives *= 5)
num_of_trailing_zeros += fact_num/fives;
printf("%d", num_of_trailing_zeros);
printf("%s","\n");
}
return 0;
}
Wgrałem to jako rozwiązanie dla gcc 5.1
Tym razem wynik był następujący: Czas 0,02 Mem 2,1M
Teraz kod jest prawie taki sam , dodałem std::ios_base::sync_with_stdio(false);
do kodu C ++ tak, jak zasugerowano tutaj, aby wyłączyć synchronizację z buforami stdio biblioteki C. Ja również podzieliła printf("%d\n", num_of_trailing_zeros);
się printf("%d", num_of_trailing_zeros); printf("%s","\n");
, aby zrekompensować podwójnym wezwaniem operator<<
w cout << num_of_trailing_zeros << "\n";
.
Ale nadal widziałem lepszą wydajność x9 i mniejsze zużycie pamięci w kodzie C w porównaniu z C ++.
Dlaczego?
EDYTOWAĆ
I stała unsigned long
się unsigned int
w kodzie C. Powinien był być, unsigned int
a wyniki, które pokazano powyżej, są związane z nową ( unsigned int
) wersją.
std::ostringstream
do zgromadzenia danych wyjściowych i wysłanie ich dostd::cout
wszystkich naraz na końcu skraca czas do 0,02. Używaniestd::cout
w pętli jest po prostu wolniejsze w ich środowisku i nie sądzę, aby można było w prosty sposób to poprawić.Odpowiedzi:
Oba programy robią dokładnie to samo. Używają tego samego dokładnego algorytmu, a biorąc pod uwagę jego małą złożoność, ich wydajność jest głównie związana z wydajnością obsługi wejścia i wyjścia.
skanowanie danych wejściowych z
scanf("%d", &fact_num);
jednej strony icin >> fact_num;
z drugiej nie wydaje się zbyt kosztowne. W rzeczywistości powinno to być mniej kosztowne w C ++, ponieważ typ konwersji jest znany w czasie kompilacji, a poprawny parser może zostać wywołany bezpośrednio przez kompilator C ++. To samo dotyczy wyników. Możesz nawet napisać osobne wywołanie forprintf("%s","\n");
, ale kompilator C jest wystarczająco dobry, aby skompilować to jako wywołanieputchar('\n');
.Patrząc więc na złożoność operacji we / wy i obliczeń, wersja C ++ powinna być szybsza niż wersja w C.
Całkowite wyłączenie buforowania
stdout
spowalnia implementację C do czegoś nawet wolniejszego niż wersja C ++. Kolejny test AlexLop zfflush(stdout);
po ostatnimprintf
daje podobną wydajność jak wersja C ++. Nie jest tak powolne, jak całkowite wyłączenie buforowania, ponieważ dane wyjściowe są zapisywane w systemie w małych kawałkach zamiast po jednym bajcie na raz.Wydaje się, że wskazuje to na specyficzne zachowanie w twojej bibliotece C ++: Podejrzewam, że twój system implementuje
cin
icout
opróżnia dane wyjściowe,cout
gdy żądane jest wejściecin
. Niektóre biblioteki C również to robią, ale zwykle tylko podczas odczytu / zapisu do iz terminala. Benchmarking wykonany przez witrynę www.spoj.com prawdopodobnie przekierowuje wejście i wyjście do iz plików.AlexLop przeprowadził kolejny test: odczytanie wszystkich danych wejściowych naraz w wektorze, a następnie obliczenie i zapisanie wszystkich danych wyjściowych pomaga zrozumieć, dlaczego wersja C ++ jest o wiele wolniejsza. Zwiększa wydajność w porównaniu z wersją C, co potwierdza mój punkt widzenia i usuwa podejrzenia dotyczące kodu formatującego C ++.
Kolejny test przeprowadzony przez Blastfurnace, przechowujący wszystkie dane wyjściowe w pliku
std::ostringstream
i opróżniający go w jednym wybuchu na końcu, poprawia wydajność C ++ do podstawowej wersji C. CO BYŁO DO OKAZANIA.PS: Twój algorytm jest nieprawidłowy,
fact_num >= UINT_MAX / 5
ponieważfives *= 5
przepełni się i zawinie, zanim się stanie> fact_num
. Możesz to poprawić, wprowadzającfives
znakunsigned long
lub an,unsigned long long
jeśli jeden z tych typów jest większy niżunsigned int
. Użyj również%u
jakoscanf
formatu. Masz szczęście, że faceci na www.spoj.com nie są zbyt surowi w swoich testach porównawczych.EDYCJA: Jak później wyjaśnił Vitaux, to zachowanie jest rzeczywiście narzucone przez standard C ++.
cin
jestcout
domyślnie powiązany . Operacja wejściowa,cin
dla której bufor wejściowy wymaga uzupełnienia, spowodujecout
opróżnienie oczekującego wyjścia.cin
Wydaje się , że w realizacji POcout
systematyczne spłukiwanie się systematyczne, co jest nieco przesadzone i wyraźnie nieefektywne.Ilya Popov zapewniła na to proste rozwiązanie:
cin
można go rozwiązaćcout
, rzucając inne magiczne zaklęcie opróczstd::ios_base::sync_with_stdio(false);
:Zauważ również, że taki wymuszony kolor występuje również, gdy używa się
std::endl
zamiast'\n'
tworzyć koniec linii nacout
. Zmiana linii wyjściowej na bardziej idiomatyczną i niewinną w C ++ obniżyłabycout << num_of_trailing_zeros << endl;
wydajność w ten sam sposób.źródło
std::ostringstream
i wysyłanie ich wszystkiego raz na końcu sprowadza czas do parytetu z wersją C.ostringstream
do wyjścia i dał czas 0,02 QED :). Odnośniefact_num >= UINT_MAX / 5
DOBREGO punktu!vector
a następnie przetworzenie obliczeń (bezostringstream
) daje ten sam wynik! Czas 0,02. Połączenie obuvector
iostringstream
nie poprawia tego bardziej. W tym samym czasie 0,02sizeof(int) == sizeof(long long)
jest taka: dodaj test w treści pętli po,num_of_trailing_zeros += fact_num/fives;
aby sprawdzić, czyfives
osiągnął maksimum:if (fives > UINT_MAX / 5) break;
Kolejną sztuczką, która przyspiesza
iostream
s, gdy używasz obucin
i,cout
jest dzwonieniecin.tie(nullptr);
Domyślnie, kiedy wprowadzasz cokolwiek z
cin
, opróżniacout
. Może to znacznie pogorszyć wydajność, jeśli dane wejściowe i wyjściowe są przeplatane. Odbywa się to dla interfejsu wiersza poleceń, w którym wyświetla się monit, a następnie czeka na dane:std::string name; cout << "Enter your name:"; cin >> name;
W takim przypadku chcesz się upewnić, że monit jest rzeczywiście wyświetlany, zanim zaczniesz czekać na wprowadzenie danych. Linią powyżej zrywasz ten remis
cin
icout
stajesz się niezależny.Od C ++ 11, jeszcze jednym sposobem na osiągnięcie lepszej wydajności z iostreams jest użycie
std::getline
razem zstd::stoi
:std::string line; for (int i = 0; i < n && std::getline(std::cin, line); ++i) { int x = std::stoi(line); }
W ten sposób może zbliżyć się do stylu C pod względem wydajności, a nawet przewyższyć
scanf
. Używanie,getchar
a zwłaszcza wgetchar_unlocked
połączeniu z ręcznym analizowaniem, nadal zapewnia lepszą wydajność.PS. Napisałem post porównujący kilka sposobów wprowadzania liczb w C ++, przydatny dla sędziów online, ale jest to tylko po rosyjsku, przepraszam. Próbki kodu i ostateczna tabela powinny być jednak zrozumiałe.
źródło
std::readline
istd::stoi
nie jest funkcjonalnie równoważna. Zarówno, jakcin >> x;
iscanf("%f", &x);
akceptacja białych znaków jako separatora, może zawierać wiele liczb w tym samym wierszu.Problem w tym, że cppreference :
Łatwo to sprawdzić: jeśli wymienisz
cin >> fact_num;
z
scanf("%d", &fact_num);
i to samo,
cin >> num_of_inputs
ale zachowajcout
, uzyskasz prawie taką samą wydajność w swojej wersji C ++ (lub raczej w wersji IOStream) jak w C:To samo dzieje się, jeśli zachowasz,
cin
ale wymieniszcout << num_of_trailing_zeros << "\n";
z
printf("%d", num_of_trailing_zeros); printf("%s","\n");
Prostym rozwiązaniem jest rozwiązanie problemu
cout
i,cin
jak wspomniał Ilya Popov:cin.tie(nullptr);
Standardowe implementacje bibliotek mogą pomijać wywołanie opróżnienia w niektórych przypadkach, ale nie zawsze. Oto cytat z C ++ 14 27.7.2.1.3 (dzięki chqrlie):
źródło
is.tie()
nie jest pustym wskaźnikiem, funkcja wywołujeis.tie()->flush()
synchronizację sekwencji wyjściowej z dowolnym skojarzonym zewnętrznym strumieniem C. Tyle tylko, że to wywołanie może zostać stłumione, jeśli obszar putis.tie()
jest pusty. Ponadto implementacja może odroczyć wywołanie spłukiwania do momentu wystąpienia wywołania ofis.rdbuf()->underflow()
. Jeśli takie wywołanie nie nastąpi przed zniszczeniem obiektu wartownika, wywołanie flush może zostać całkowicie wyeliminowane.