Ich habe versucht, diese Übung von www.spoj.com zu lösen: FCTRL - Factorial
Du musst es nicht wirklich lesen, mach es einfach, wenn du neugierig bist :)
Zuerst habe ich es in C ++ implementiert (hier ist meine Lösung):
#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;
}
Ich habe es als Lösung für g ++ 5.1 hochgeladen
Das Ergebnis war: Zeit 0,18 Mem 3,3M
Aber dann sah ich einige Kommentare, die behaupteten, dass ihre Zeitausführung weniger als 0,1 war. Da ich mir keinen schnelleren Algorithmus vorstellen konnte, habe ich versucht, denselben Code in C zu implementieren :
#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;
}
Ich habe es als Lösung für gcc 5.1 hochgeladen
Diesmal war das Ergebnis: Zeit 0,02 Mem 2,1M
Jetzt ist der Code fast der gleiche , den ich dem hierstd::ios_base::sync_with_stdio(false);
vorgeschlagenen C ++ - Code hinzugefügt habe , um die Synchronisation mit den Standardpuffern der C-Bibliothek zu deaktivieren. Ich teile auch die printf("%d\n", num_of_trailing_zeros);
zu printf("%d", num_of_trailing_zeros); printf("%s","\n");
Doppel Aufruf zu kompensieren operator<<
in cout << num_of_trailing_zeros << "\n";
.
Aber ich sah immer noch eine bessere x9-Leistung und eine geringere Speichernutzung in C vs. C ++ - Code.
Warum ist das so?
BEARBEITEN
Ich unsigned long
habe unsigned int
im C-Code behoben . Es sollte gewesen sein unsigned int
und die oben gezeigten Ergebnisse beziehen sich auf die neue ( unsigned int
) Version.
std::ostringstream
die Ausgabe mit a akkumulieren und am Endestd::cout
auf einmal an alle senden, verringert sich die Zeit auf 0,02. Die Verwendungstd::cout
in einer Schleife ist in ihrer Umgebung einfach langsamer und ich glaube nicht, dass es einen einfachen Weg gibt, sie zu verbessern.Antworten:
Beide Programme machen genau das Gleiche. Sie verwenden denselben exakten Algorithmus und aufgrund seiner geringen Komplexität hängt ihre Leistung hauptsächlich von der Effizienz der Eingabe- und Ausgabebehandlung ab.
Das Scannen der Eingabe
scanf("%d", &fact_num);
auf der einen Seite undcin >> fact_num;
auf der anderen Seite scheint in beiden Fällen nicht sehr kostspielig zu sein. Tatsächlich sollte es in C ++ weniger kostspielig sein, da die Art der Konvertierung zur Kompilierungszeit bekannt ist und der richtige Parser direkt vom C ++ - Compiler aufgerufen werden kann. Gleiches gilt für die Ausgabe. Sie schreiben sogar einen separaten Aufruf fürprintf("%s","\n");
, aber der C-Compiler ist gut genug, um diesen als Aufruf zu kompilierenputchar('\n');
.Angesichts der Komplexität von E / A und Berechnung sollte die C ++ - Version schneller sein als die C-Version.
Das vollständige Deaktivieren der Pufferung
stdout
verlangsamt die C-Implementierung auf etwas, das noch langsamer als die C ++ - Version ist. Ein weiterer Test von AlexLop mit einemfflush(stdout);
nach dem letztenprintf
ergibt eine ähnliche Leistung wie die C ++ - Version. Es ist nicht so langsam wie das vollständige Deaktivieren der Pufferung, da die Ausgabe in kleinen Blöcken anstelle von jeweils einem Byte in das System geschrieben wird.Dies scheint auf ein bestimmtes Verhalten in Ihrer C ++ - Bibliothek hinzuweisen: Ich vermute, dass Ihr System die Ausgabe implementiert
cin
undcout
löscht,cout
wenn eine Eingabe von angefordert wirdcin
. Einige C-Bibliotheken tun dies ebenfalls, jedoch normalerweise nur beim Lesen / Schreiben zum und vom Terminal. Das von der Website www.spoj.com durchgeführte Benchmarking leitet die Eingabe und Ausgabe wahrscheinlich in und aus Dateien um.AlexLop hat einen weiteren Test durchgeführt: Das gleichzeitige Lesen aller Eingaben in einem Vektor und das anschließende Berechnen und Schreiben aller Ausgaben hilft zu verstehen, warum die C ++ - Version so viel langsamer ist. Es erhöht die Leistung auf das der C-Version, dies beweist meinen Standpunkt und beseitigt den Verdacht auf den C ++ - Formatierungscode.
Ein weiterer Test von Blastfurnace, bei dem alle Ausgaben in einem gespeichert
std::ostringstream
und am Ende in einem Druck gelöscht werden, verbessert die C ++ - Leistung gegenüber der C-Basisversion. QED.PS: Ihr Algorithmus ist falsch,
fact_num >= UINT_MAX / 5
da erfives *= 5
überläuft und sich umwickelt, bevor er wird> fact_num
. Sie können dies korrigieren, indem Siefives
einunsigned long
oder ein machen,unsigned long long
wenn einer dieser Typen größer als istunsigned int
. Auch%u
alsscanf
Format verwenden. Sie haben Glück, dass die Jungs von www.spoj.com in ihren Benchmarks nicht zu streng sind.EDIT: Wie später von vitaux erklärt, wird dieses Verhalten tatsächlich vom C ++ - Standard vorgeschrieben.
cin
istcout
standardmäßig gebunden . Eine Eingabeoperation,cin
für die der Eingabepuffer nachgefüllt werden muss, führt dazucout
, dass die ausstehende Ausgabe geleert wird . In der Implementierung des OPcin
scheintcout
systematisch zu spülen , was etwas übertrieben und sichtbar ineffizient ist.Ilya Popov bot hierfür eine einfache Lösung:
cin
Kann gelöst werden,cout
indem zusätzlich zustd::ios_base::sync_with_stdio(false);
:Beachten Sie auch, dass eine solche erzwungene Spülung auch auftritt, wenn
std::endl
anstelle von'\n'
ein Zeilenende verwendet wirdcout
. Das Ändern der Ausgabezeile auf das idiotischere und unschuldigere C ++ - Aussehencout << num_of_trailing_zeros << endl;
würde die Leistung auf die gleiche Weise beeinträchtigen.quelle
std::ostringstream
sammeln und am Ende alle einmal ausgeben, wird die Zeit auf die Parität mit der C-Version reduziert.ostringstream
für die Ausgabe zu verwenden und es gab Zeit 0,02 QED :). In Bezug auf denfact_num >= UINT_MAX / 5
, guten Punkt!vector
und das anschließende Verarbeiten der Berechnungen (ohneostringstream
) ergibt das gleiche Ergebnis! Zeit 0.02. Beides kombinierenvector
undostringstream
nicht weiter verbessern. Gleiche Zeit 0.02sizeof(int) == sizeof(long long)
dies dernum_of_trailing_zeros += fact_num/fives;
fives
if (fives > UINT_MAX / 5) break;
Ein weiterer Trick, um
iostream
s schneller zu machen , wenn Sie beide verwendencin
undcout
anrufenWenn Sie etwas
cin
eingeben, wird es standardmäßig gelöschtcout
. Dies kann die Leistung erheblich beeinträchtigen, wenn Sie Eingaben und Ausgaben verschachteln. Dies erfolgt für die Befehlszeilenschnittstelle, bei der Sie eine Eingabeaufforderung anzeigen und dann auf Daten warten:In diesem Fall möchten Sie sicherstellen, dass die Eingabeaufforderung tatsächlich angezeigt wird, bevor Sie auf die Eingabe warten. Mit der obigen Linie brechen Sie diese Krawatte
cin
undcout
werden unabhängig.Da C ++ 11, eine weitere Möglichkeit , eine bessere Leistung mit iostreams zu erreichen , ist die Verwendung
std::getline
zusammen mitstd::stoi
, wie folgt aus :Auf diese Weise kann die Leistung dem C-Stil nahe kommen oder sogar übertroffen werden
scanf
. Die Verwendunggetchar
und insbesonderegetchar_unlocked
zusammen mit handschriftlichem Parsen bietet immer noch eine bessere Leistung.PS. Ich habe einen Beitrag geschrieben , in dem verschiedene Möglichkeiten zur Eingabe von Zahlen in C ++ verglichen werden. Dies ist nützlich für Online-Richter, aber leider nur auf Russisch. Die Codebeispiele und die endgültige Tabelle sollten jedoch verständlich sein.
quelle
std::readline
undstd::stoi
entspricht nicht funktional dem OP-Code. Beidecin >> x;
undscanf("%f", &x);
Ameisen-Leerzeichen als Trennzeichen akzeptieren, können mehrere Zahlen in derselben Zeile stehen.Das Problem ist, dass unter Angabe von cppreference :
Dies ist leicht zu testen: wenn Sie ersetzen
mit
und das Gleiche für,
cin >> num_of_inputs
aber behaltencout
Sie , dass Sie in Ihrer C ++ - Version (oder besser gesagt in der IOStream-Version) fast die gleiche Leistung erhalten wie in C 1:Das gleiche passiert, wenn Sie behalten,
cin
aber ersetzenmit
Eine einfache Lösung besteht darin, zu lösen
cout
undcin
wie von Ilya Popov erwähnt:Standardbibliotheksimplementierungen dürfen in bestimmten Fällen den Aufruf zum Löschen weglassen, jedoch nicht immer. Hier ist ein Zitat aus C ++ 14 27.7.2.1.3 (danke an chqrlie):
quelle
is.tie()
nicht ein Nullzeiger ist, die Funktionsaufrufeis.tie()->flush()
die Ausgangssequenz mit einem beliebigen zugeordneten externen C Strom zu synchronisieren. Außer dass dieser Aufruf unterdrückt werden kann, wenn der Put-Bereich vonis.tie()
leer ist. Ferner kann eine Implementierung den Aufruf zum Leeren verschieben, bis ein Aufruf vonis.rdbuf()->underflow()
auftritt. Wenn ein solcher Aufruf nicht erfolgt, bevor das Wachobjekt zerstört wurde, kann der Aufruf zum Löschen vollständig beseitigt werden.