Gepuffertes Lesen von stdin mit fread in C
Ich versuche effizient zu Lesen von der stdin
mithilfe setvbuf
im `_IOFBF~ Modus. Ich bin neu in der Pufferung. Ich bin auf der Suche nach arbeiten Beispiele.
Die Eingabe beginnt mit zwei Ganzzahlen (n
,k
). Die nächste n
Zeilen der Eingabe enthalten 1 ganze Zahl. Ziel ist es, zu drucken, wie viele ganze zahlen sind teilbar durch k
.
#define BUFSIZE 32
int main(){
int n, k, tmp, ans=0, i, j;
char buf[BUFSIZE+1] = {'0'};
setvbuf(stdin, (char*)NULL, _IONBF, 0);
scanf("%d%d\n", &n, &k);
while(n>0 && fread(buf, (size_t)1, (size_t)BUFSIZE, stdin)){
i=0; j=0;
while(n>0 && sscanf(buf+j, "%d%n", &tmp, &i)){
//printf("tmp %d - scan %d\n",tmp,i); //for debugging
if(tmp%k==0) ++ans;
j += i; //increment the position where sscanf should read from
--n;
}
}
printf("%d", ans);
return 0;
}
Das problem ist, wenn die Zahl ist an der Grenze, die Puffer buf
Lesen 23
aus 2354\n
, wenn es sollte entweder Lesen 2354
(was es nicht) oder gar nichts.
Wie kann ich dieses Problem lösen?
Bearbeiten
Jetzt behoben (mit Analyse).
Bearbeiten
Komplette Problem-Spezifikation
Du musst angemeldet sein, um einen Kommentar abzugeben.
Werde ich empfehlen, versucht die volle Pufferung mit
setvbuf
und Notwasserungfread
. Wenn die Spezifikation ist, dass es eine Zahl pro Zeile, ich werde Sie nehmen, dass für selbstverständlich halten, verwendenfgets
zu Lesen, in eine komplette Linie und übergeben es anstrtoul
analysieren, die Nummer, die sein sollte, in der Zeile.Verwendete ich ein Perl-Skript zum erstellen von 1.000.000 zufällige Ganzzahlen zwischen 0 und 1.000.000 und überprüft, wenn Sie teilbar durch 5 nach dem kompilieren das Programm mit
gcc version 3.4.5 (mingw-vista special r3)
auf meinem Windows XP-laptop. Die ganze Sache dauerte weniger als 0,8 Sekunden.Als ich mich umdrehte Pufferung aus mit
setvbuf(stdin, (char*)NULL, _IONBF, 0);
, die Zeit ging bis etwa 15 Sekunden.fread
und zu bewegen, umsetvbuf
?Eine Sache, die finde ich verwirrend ist, warum Sie beide so die vollständige Pufferung innerhalb des stream-Objekt über den Aufruf
setvbuf
und dadurch Ihre eigenen Pufferung durch das Lesen der Puffer voll ist, inbuf
.Ich verstehe die Notwendigkeit zu tun, Pufferung, aber das ist ein bisschen übertrieben.
Werde ich Ihnen empfehlen, kleben mit
setvbuf
entfernen und Ihre eigene Pufferung. Der Grund ist, dass die Umsetzung Ihrer eigenen Pufferung kann schwierig sein. Das problem ist, was passiert, wenn ein token (in deinem Fall eine Zahl) überspannt die Puffer-Grenze. Zum Beispiel, sagen wir, dein Puffer ist 8 Byte (9 Byte insgesamt für nachgestellte NULL) und Ihren input-stream sieht aus wieDem erstmaligen füllen der buffer erhalten Sie:
während das zweite mal, wenn Sie füllen den Puffer bekommen Sie:
Ordnungsgemäße Pufferung erfordert, dass Sie behandeln diesen Fall so behandeln Sie den Puffer als die zwei zahlen {12345, 12345} und nicht drei zahlen {12345, 12, 234}.
Seit stdio Griffe, die bereits für Sie, einfach. Weiterhin zu nennen
setvbuf
, loszuwerden, diefread
und verwendenscanf
zu Lesen, einzelne zahlen aus dem input-stream.setvbuf
manchmal sehr wirksam. Er hatte zum Beispiel helfen, eine Menge, um es zu 1MB in dem Fall des Lesens 45KB Stücke von Daten von einer SD-Karte. Ohne es zu benutzen, Lesen, nehmen könnte bis zu die Hälfte ein zweites mal, aber jetzt dauert es weniger als 0,05 sec.Version 1 : Mit
getchar_unlocked
wie vorgeschlagen von R Samuel Klatchko (siehe Kommentare)Version 2: Mit
fread
zu Lesen, dass ein block-und Analyse-Nummer von ihm.Ergebnisse: (10 Millionen zahlen getestet für die Teilbarkeit durch 11)
P. S. - Jedem ausführen kompiliert mit GCC -O1-flag
"z\n"
?setvbuf
, 2) Lesen Sie die Daten byte für byte mitgetchar_unlocked
statt mit fread. Erhalten Sie einen ähnlichen speedup.Ist das problem, wenn Sie nicht mit der Umleitung ist, dass Sie nicht die Ursache EOF.
Da dies zu sein scheint Posix (basierend auf der Tatsache, dass Sie gcc verwenden), geben Sie einfach
ctrl-D
(d.h. während Sie die Strg-Taste drücken/loslassen d) wodurch wird EOF erreicht werden.Wenn Sie Windows verwenden, ich glaube, Sie verwenden
ctrl-Z
statt.Wenn Sie nach aus-und-out-Geschwindigkeit, und Sie arbeiten auf einem POSIX-ish-Plattform, sollten Sie über die memory-mapping. Ich nahm Sinan die Antwort mit standard-I/O und zeitlich, und auch das Programm erstellt unter der Verwendung von Speicher-mapping. Beachten Sie, dass memory-mapping nicht funktioniert, wenn die Datenquelle ein terminal oder eine pipe und nicht eine Datei.
Mit einer million Werte zwischen 0 und einer Milliarde (und einen festen Teiler von 17), die durchschnittlichen Zeiten für die beiden Programme wurde:
Grob, memory-mapped I/O ist doppelt so schnell wie standard-I/O.
In jedem Fall war das timing wiederholt 6 mal, nach ignorieren ein warm-up-run. Den Befehl Linien auf:
Können Sie den Wert verwenden, der von
n
mehr Lesen die Eingabe, nachdem Sie gesehen habenn
zahlen.Ändern den Zustand der äußeren
while
Schleife:und ändern Sie den Körper von innen zu:
Des Problems, das Sie weiterhin haben, ist, dass, weil Sie nie passen
buf
im innerenwhile
Schleifesscanf
hält Lesen die gleiche Nummer immer und immer wieder.Wenn Sie mit dem Schalter
strtol()
inteadsscanf()
, dann können Sie dieendptr
output-parameter zu bewegen, durch die Puffer, die als zahlen gelesen werden.sscanf
string, finden Sie in den aktualisierten Antworten.buf
in der inneren Schleife,sscanf
halten nach den gleichen Eingang und sehen die gleiche Anzahl.fread
ist ein square peg und dieses problem ist ein rundes Loch. Man konnte Lesen, eine Linie-at-a-time mitfgets()
statt.Gut, direkt aus der Spitze, scanf("%d%d",&n,&k) wird shove einen Wert in n nur still und leise verlassen k unset - würde Sie sehen, wenn Sie überprüft den Rückgabewert von scanf(), die Ihnen sagt, wie viele Variablen es gefüllt. Ich denke, Sie wollen scanf("%d %d",&n,&k) mit dem Raum.
Zweite ist n die Anzahl der Iterationen, die ausgeführt werden können, aber Sie testen für "n>0" noch nie verringern es. Ergo, n>0 ist immer true und die Schleife wird nicht verlassen.
Als jemand anderes erwähnt, Fütterung stdin über eine pipe bewirkt, dass die Schleife zu beenden, da das Ende von stdin hat ein EOF, was bewirkt, dass fread() NULL zurück, verlassen der Schleife. Wahrscheinlich möchten Sie fügen Sie ein "- n=n-1" oder "n--" irgendwo in dort.
Nächsten, in deiner sscanf, %n ist nicht wirklich eine standard-Sache; ich bin nicht sicher, was es bedeutete, zu tun, aber es kann nichts tun: scanf() in der Regel Stoppt das Parsen bei der ersten unbekanntes format identifier, die nichts anderes tut als hier (denn Sie haben bereits Ihre Daten), sondern ist eine schlechte Praxis.
Schließlich, wenn die Leistung wichtig ist, solltest du besser nicht mit fread() etc auf alle, wie Sie sind nicht wirklich hohe Leistung. Blick auf isdigit(3) und iscntrl(3) und denke darüber nach, wie Sie analysieren die zahlen aus einem raw-Daten-buffer Lesen mit read(2).
Den äußersten
while()
Schleife wird erst verlassen, wenn das Lesen vonstdin
zurückEOF
. Dies kann nur geschehen, wenn das erreichen der tatsächliche end-of-file auf eine input-Datei, oder wenn der Prozess zu schreiben, um eine input-pipe beendet. Daher derprintf()
- Anweisung nie ausgeführt. Ich glaube nicht, das hat nichts zu tun mit dem Aufrufsetvbuf()
.Mabe auch einen Blick auf dieses getline Umsetzung:
http://www.cpax.org.uk/prg/portable/c/libs/sosman/index.php
(Eine ISO-C-routine, um eine Zeile von Daten, Länge unbekannt, aus einem stream.)
Den Grund alle diese permature Optimierung hat eine negligable Auswirkung auf die Laufzeit ist, dass in der *nix-und windows-Typ Betriebssysteme OS behandelt alle I/O und aus dem Datei-system und implementiert 30 Jahre im Wert von Forschung, Tricks und deviousness zu tun dies sehr effizient.
Pufferung, die Sie versuchen zu kontrollieren, ist nur der block von Speicher von Ihrem Programm verwendet. So eine Zunahme der Geschwindigkeit wird minimal sein (die Wirkung von tut 1 großes 'mov' Verse 6 oder 7 kleineren " mov " - Anweisungen).
Wenn Sie wirklich wollen, um diese Fahrt versuchen Sie "mmap", die ermöglicht Ihnen den direkten Zugriff auf die Daten in der Datei-Systeme Puffer.
Hier ist mein byte-by-byte nehmen: