Servus,
ich habe ein gößeres Belegarchiv, abfragbar übers Internet.
Alles mit dem Watcom-Compiler in C geschrieben, läuft stabil
und flott.
Ein Kunde möchte sich per SSL einloggen und ich versuche gerade
da OpenSSL mit einzustricken, Da das alles mit GNU erzeugt ist,
verwende ich den da halt auchmal.
Problem: Ich lese, um den Benutzer zu authentifizieren, eine Rechtedatei
mit 72 Benutzergruppemn und knapp 15000 Benutzern (3,5 MB).
Mit Watcom kompiliert: Laufzeit 100 msec
Mit GNU kompiliert: Laufzeit 17 sec (!)
Code ist identisch.
Ich verstehe das irgendwie nicht. Wonach müßte ich da suchen?
Joachim D. schrieb:> Wonach müßte ich da suchen?
Das ist nur so schwer zu sagen. Kannst du denn ein paar Codeschnipsel,
die das fragliche Datei-Handling vornehmen, posten?
Wenn ich sowas simples aufsetze:
1
#include<stdio.h>
2
3
int
4
main(intargc,char**argv)
5
{
6
if(argc<2)
7
return1;
8
9
FILE*f;
10
if((f=fopen(argv[1],"r"))==NULL)
11
return2;
12
13
charline[200];
14
intlineno=0;
15
16
while(fgets(line,sizeofline,f)){
17
lineno++;
18
}
19
printf("Read %d lines\n",lineno);
20
21
return0;
22
}
und auf eins der Dictionaries loslasse, geht das in Millisekunden:
1
% time ./foo /usr/share/dict/swiss
2
Read 356108 lines
3
0.010u 0.000s 0:00.01 100.0% 0+0k 0+0io 0pf+0w
4
% time ./foo /usr/share/dict/ngerman
5
Read 356008 lines
6
0.011u 0.000s 0:00.01 100.0% 0+0k 6672+0io 0pf+0w
Beide Dateien sind 4,5 MiB groß, also in dieser Hinsicht ungefähr
vergleichbar mit deiner.
Hmmmm...
der Code ist ja gleich. Ich habe so das "Gefühl" da passiert
irgndwoanders etwas. Ich habe nur kaum Erfahrungen mit dem
GNU - und eigentlich keinen Plan wo da die Fallstricke sein
könnten. So einenn extremen Laufzeitunterschied habe ich
noch nie erlebt. Das ist fast wie GWBASIC und C :)
Selbst ein zu Jörgs Testprogramm äquivalentes Python-Programm braucht
nur 66 ms. Der Großteil der Zeit entfällt dabei auf das Laden und
Starten des Interpreters. Der eigentliche Einlesevorgang dauert gerade
einmal 3 ms.
Joachim D. schrieb:> Mit Watcom kompiliert: Laufzeit 100 msec> Mit GNU kompiliert: Laufzeit 17 sec (!)
Der Faktor von 170 lässt sich fast nur durch einen Programmfehler
(undefined Behavior, Buffer Overflow o.ä.) erklären, der dazu führt,
dass das Programm – abhängig vom verwendeten Compiler – Dinge tut, die
es gar nicht tun sollte.
Mach den Fehler weg, dann wird das Programm auch mit GCC in 100 ms (oder
noch schneller) laufen ;-)
GNU gprof sollte weiterhelfen, damit wird man recht schnell eingrenzen
können was die 17s an Zeit kostet. Vermutlich irgendein unbeabsichtigter
Systemaufruf pro Zeichen.
Den Fehler (ich denke nicht daß da einer ist) würde ich
ja gerne wegmachen. Das Problem muß vor dem Laden auftreten
(ok, 100 ms sind lange, aber ich lese das dann noch in interne
Strukturen ein).
Profiler wäre mal einen Versuch wert ...
Es ist möglich, dass die C-Lib vom Watcom beim Einlesen etwas anders
puffert als die C-Library vom GNU. Das kann so einen Unterschied schon
ausmachen (Hunderte Systemaufrufe vs. Millionen).
Raten und herumstochern kann man immer noch wenns Profilen nichts ergibt
;-)
Joachim D. schrieb:> derMosphet schrieb:>> Compiler Optimierungen (-O2) sind eingeschaltet?>> gibt 15,8 sec
Also hast du vermutlich nen Bug in deinem Program. Du kannst noch clang
testen. Der Aufwand sollte minimal sein, da die Compileroptionen in der
Regel identisch sind.
Joachim D. schrieb:> Klar ist da ein bug ;)>> time( NULL) gibt auf einmal 0 ??? Gerade gefunden.
Ein weiteres Symptom. Vielleicht solltest du auch mal einen Blick auf
die Sanitizer werfen, die gcc bietet (-fsanitize=...).
Beim watcom macht er mit der Option 'zp1' daqs structure aligment
auf 1 Byte. Ich brauche das programmintern. Das könnte damit etwas
zu tun haben ...
Mal sehen wie der GCC das so macht ;)
Joachim D. schrieb:> Beim watcom macht er mit der Option 'zp1' daqs structure aligment> auf 1 Byte. Ich brauche das programmintern. Das könnte damit etwas> zu tun haben ...>> Mal sehen wie der GCC das so macht ;)
Du änderst mit einer Compileroption das Alignement für alle Strukturen?
Auf welcher Plattform läuft das eigentlich? Windows oder Linux?
Bei C kanns scheidet eine andere Container-Implementierung oder
plötzlich pathologische Bibliotheks-Hash-Funktion aus.
W10 Webserver lokal, W10 Client mit FF (alles aktuell).
Die Rechteddatei (flat text) besteht aus Definitionen der
Benutzergruppen
group XXX {
job (rechte)
...
}
user YYY {
job (rechte)
...
}
etc. 72 Gruppen und knapp 15.000 User.
Der Krempel wird nach dem Laden der Rechte in entsprechende
Strukturen übertragen. Das Übertragen dauert so ewig. Im
Moment habe ich alles SSL-Zeugs raus und das Programm müßte
sich genauso verhalten als hätte ich das mit dem Watcom
kompiliert.
Die Übertragung funktioniert definitiv.
Also: Funktion ok aber halt strunzlahm ...
Unter Linux wär ich der Geschichte mit strace, gprof und valgrind auf
den Pelz gerückt.
Was genau dauert jetzt eigentlich 17s, so Aussagen wie "Das Übertragen
dauert so ewig." klingen irgendwie danach als wär das Problem gar nicht
richtig identifiziert.
1. Öffnen und malloc() für Puffer
2. Laden in den Puffer
3. Entfernen aller CR, LFs
4. dann durch den String handeln und den Kram in die
Strukuren laden
Punkt 4. schwächelt.
Die Größen der Strukuren gibt sizeof() gleich aus.
Joachim D. schrieb:> 4. dann durch den String handeln und den Kram in die> Strukuren laden
Passieren da String -> Numerisch-Umwandlungen? Dunkel erinnere ich mich
an eine Problem mit den Spracheinstellungen bzw. Locale-abhängiger
Zahlenparserei die da jedesmal nachgeschaut hat ob jemand hinterrücks
das Land geändert hat. Ist aber über 10 Jahre aus, dass ich mit dem Mist
beschäftigen musste.
Also die zp1 option ist schon mal ziemlich boese - warum global und
nicht einfach nur auf den paar Structs die du serialisierst? Nicht
boeser eher unschoen
Hast du die relevante funktion schon genau erkannt oder ist "irgendwie"
der ganze code langsam - deine Posting erlauben keine klaren
Rueckschluesse
Als profiler könntest du den VTune von intel verwenden - als teil vom
parallel studio trial, der ist ziemlich einfach zu bedienen
Wie machst du deine Zeitmessung?
Umgebung: Windows 10 Client und Serverm aktuell
httpd: Apache 2.4
MinGW: 6.30
Watcom: 1.9
Ich habe jetzt mal das Programm mit der Option -pg compiliert
und laufen lassen. Dann mit gprof den output lesbar gemacht.
Den etwas merkwürdigen output (der mir nicht weiterhilft, alles
0) hängt an.
Grüße Joe und schon vielen Dank !
Joachim D. schrieb:> Was Zwerge damit zu tun haben .... ;)
Diese nützlichen Zwerge verwalten die Debug-Informationen:
https://en.wikipedia.org/wiki/DWARF
Wenn die BFD-Bibliothek damit nicht klar kommt, dann ist sie wohl
einfach mal zu alt für den Compiler.
Ich vermute allerdings eher, dass es irgendein Problem mit den
(OS-)Timern bei dir gibt, die der Profiler-Code offenbar nicht benutzen
kann, während die DWARF-Warnungen eher harmlos sind.
Joachim D. schrieb:> Das könnte mir gprof auch sagen.
Dass die Timer dam Ende keine Zeit liefern, wird er wohl nicht wissen.
Ich hatte weiter oben mal nach einem minimalistischen Codebeispiel
gefragt, kannst du sowas mal posten?
Ich glaub nicht, dass Jörg W. das mit einem minimalistischen
Codebeispiel gemeint hat. Ich tippe eher auf das was hier beschrieben
wird: https://stackoverflow.com/help/minimal-reproducible-example
Der Vorteil ist, dass du beim Erstellen des Beispiels deinen Fehler
selbst findest oder eine viel konkretere Frage stellen kannst.
Wenn ich da etwas rauswerfe wird es unklar. Ich weiß,
minimalistisch ist das eher nicht. Es muß halt eine
Kleinigkeit sein an der sich der GCC verschluckt.
Der Code ist seit min 10 in dieser Form aktiv und
funktioniert. Halt mit dem Watcom besser als mit dem GCC.
Joachim D. schrieb:> Mit GNU kompiliert: Laufzeit 17 sec (!)
Auch wenn Du den Lauf direkt danach nochmal startest? Wenn es beim
zweiten Mal flotter geht, ist es der Virenscanner, der erstmal die neue
EXE checkt.
Dasselbe Phänomen habe ich hier unter Windows auch, wenn ich mit mingw
ein Windows-Programm erzeuge und danach starte.
Joachim D. schrieb:> Der Code ist seit min 10 in dieser Form aktiv und> funktioniert. Halt mit dem Watcom besser als mit dem GCC.
Kann es sein, dass du Uraltversionen der Tools einsetzt? Immerhin ist
der Watcom ja auch mindestens 9 Jahre alt.
leo
du solltest echt mehr mit Funktionen arbeiten, C++ und STL strings
würden den Code wohl auch gehörig eindampfen
was bleibt:
1. liefert deinen gcc version auch exakt die gleichen Daten wie deine
Watcom version? also keine weiteren Probleme ausser Geschwindigkeit
2. dein Beispiel so zusammen dampfen das man damit auch was anfangen
kann
in dem Code sehe ich jetzt nichts schlimmes/komisches - aber es wäre
wohl am einfachsten wenn du deine lade-routine (von der du denkst das
sie so langsam ist) in ein kleines Testprojekt extrahierst und damit
Zeitmessungen machst
3. VTune von Intel (als Trial)
4. Visual Studio 2019 (Community) downloaden und integrierten Profiler
nutzen:
https://docs.microsoft.com/de-de/visualstudio/profiling/beginners-guide-to-performance-profiling?view=vs-2019
5. mal unter Linux kompilieren: falls du eins da hast (LiveUSB Stick
Ubuntu 19.10?) - da hast du dann gcc und gprof in neu und native
was willst du machen?
Zu 1: Ja, ich habe kontrolliert was er da eingelesen hat
Zu 2: das ist etwas problematisch. Es ist halt ziemlich umfangreich.
Zum Ausprobieren habe ich das alles mal direkt in main()
unmittelbar nach dem Programmstart
aufgerufen ohne das ganze Beiwerk. Es ändert sich nichts.
Zu 3: Noch mehr tools ? Ich denke, es ist kein tuning Problem.
zu 4: MinGW legt das im Unix-Format ab, da kann M$ angeblich
nichts mit anfangen
zu 5: Ich habe irgendwo noch einen älteren Rechner mit debian.
Es muß aber in der Firma unter W10 laufen.
Joachim D. schrieb:> Zu 1: Ja, ich habe kontrolliert was er da eingelesen hat
schon mal gut
> Zu 2: das ist etwas problematisch. Es ist halt ziemlich umfangreich.
aber wäre schon gut - falls es sich (noch) lohnt
> Zum Ausprobieren habe ich das alles mal direkt in main()> unmittelbar nach dem Programmstart> aufgerufen ohne das ganze Beiwerk. Es ändert sich nichts.
ich wuerde das per #define da mal belassen und nur damit testen -
einfach ein kleineres Szenario
> Zu 3: Noch mehr tools ? Ich denke, es ist kein tuning Problem.
das wohl nicht aber es ist ja auch noch nicht klar welche deiner(oder
der Standard-lib) Funktionen plötzlich einen Faktor 170 langsamer sind,
oder?
https://software.intel.com/en-us/vtune/choose-download#standalone
Es geht nicht um tuning sondern um das eingrenzen - du hast gemeint
time(NULL) liefert 0 oder so was - kannst du schon genau sagen welche
Routine so langsam ist?
> zu 4: MinGW legt das im Unix-Format ab, da kann M$ angeblich> nichts mit anfangen
deswegen auch mit VS2019 bauen und profilen - dann ist alles im M$
Format
https://visualstudio.microsoft.com/de/vs/> zu 5: Ich habe irgendwo noch einen älteren Rechner mit debian.> Es muß aber in der Firma unter W10 laufen.
es macht nur Sinn von du was aktuelles hast, ansonsten liegt da ein
uralt gcc drauf und keiner kann helfen und es artet einfach nur aus und
kostet Zeit - am einfachsten ein Ubuntu 19.10 vom USB-Stick booten und
dann gcc/gprof usw. installieren - aber nur wenn du dich aus kennst(und
sonst nix hilft):
https://www.thomas-krenn.com/de/wiki/Ubuntu_von_einem_USB_Stick_installieren
(oder einfach nur als Live-System booten ohne installation) - USB-Stick
muss gross genug sein
GetUsr() wird 14371 mal aufgerufen, also mal mit clock()
die Ticks gezählt. Alles 0, 22mal 16 ??? Eigentlich sind
die user bis auf die Kundennummer, PW und Zugriffsrechte
gleich, Wird immer doller.
>GetUsr() wird 14371 mal aufgerufen,
erwartest du das?
>also mal mit clock() die Ticks gezählt. Alles 0, 22mal 16 ???
was soll das bedeuten - 22mal 16?
>Eigentlich sind die user bis auf die Kundennummer,>PW und Zugriffsrechte gleich,
ich verstehe den Bezug zur Geschwindigkeit nicht
>Wird immer doller.
also verhält sich dein System doch nicht so ganz korrekt?
es könnte problemlos möglich sein das du dich seit 10 Jahren auf
undefiniertes Verhalten verlässt, oder die 10 Jahre (oder älter) stdlib
vom Watcom 1.9 Fehler besser verkraftet oder ignoriert usw.
und der gcc nebst anderer stdlib macht da eben was völlig anderes - das
passiert sehr häufig - deswegen immer schön multi-compiler Builds
machen, damit man genau so was erkennt - deswegen auch VS2019 oder
Linux/gcc zum vergleichen
mal
http://cppcheck.sourceforge.net/ - open source, klein und schnell
und
PVS Studio Trial (der Gründliche) über den Code laufen lassen?
https://www.viva64.com/en/pvs-studio/
@Jörg W.
>if ( atoi( GetSysVar( "timeout"))){> usr[usrmax].timeout = atoi( GetSysVar( "timeout"));
also 14371 x 2 = 28742 Aufrufe von GetSysVar+atoi
es könnte sein das der Watcom das (fälschlicher weise) nicht als
volatile erachtet und dann "nur" die 14371 Aufrufe macht
ich könnte mir gut vor stellen das der Watcom Kompiler da einige
Geschwindigkeits-steigernden aber nicht korrekte Dinge macht
Joachim D. schrieb:> ...
Auf jeden Fall solltest du dringed deinen Code refactorn.
Das ist ja furchtbar. Da blickt ja keiner durch.
diese unsägliche scword( p) / sscanf orgien.
es gibt eine C-Funktion, die Zerlegt dir einen String in Token:
1
char*token;
2
constchars[]=" ";
3
4
/* get the first token */
5
token=strtok(str,s);
6
7
/* walk through other tokens */
8
while(token!=NULL){
9
printf(" '%s'\n",token);
10
11
token=strtok(NULL,s);
12
}
Aber ich würde eher c++, streams und reguläre ausdrücke benutzen oder
gleich ne kleine sqlite-Datenbank verwenden.
Was performance angeht:
Ansonsten ist mir das was Jörg gefunden hat auch aufgestoßen:
Jörg W. schrieb:> GetSysVarcppbert3 schrieb:>>also mal mit clock() die Ticks gezählt. Alles 0, 22mal 16 ???>> was soll das bedeuten - 22mal 16?
Ich vermute, dass die Auflösung von clock 16ms ist und er bei den 14371
Funktionsaufrufen 22 mal einen Taskswitch gemessen hat.
Vlad T. schrieb:> Aber ich würde eher c++, streams und reguläre ausdrücke benutzen oder> gleich ne kleine sqlite-Datenbank verwenden.
Das ist halt das System ohne SQLite. Erheblich schneller
wie das "mit" (selbstgebaute DB).
C++ nur im Notfall, wozu in einer Rechnedatei reguläre Ausdrücke ?
Joachim D. schrieb:> Vlad T. schrieb:>> Aber ich würde eher c++, streams und reguläre ausdrücke benutzen oder>> gleich ne kleine sqlite-Datenbank verwenden.>> Das ist halt das System ohne SQLite. Erheblich schneller> wie das "mit" (selbstgebaute DB).>> C++ nur im Notfall, wozu in einer Rechnedatei reguläre Ausdrücke ?
du willst mir erzählen, dass du schneller dateien lädst und nach usern
und so durchsuchst, als sqlite?
wozu c++?
lesbarer, leichter wartbarer Code, da es eine vernünftige Lib mitbringt
und nicht alles selbst geschrieben werden muss.
Ist mit großer Wahrscheinlichkeit auch schneller, als das ganze scanf,
da die Typen zur Kompile-Zeit bekannt sind und nicht erst ein
Formatstring geparst werden muss.
warum reguläre ausdrücke?
Na zum Parsen der Datensätze.
Aber da weiß ich zu wenig über deinen Aufbau, ob es den Code
übersichtlicher oder komplizierter machen würde.
>C++ nur im Notfall, wozu in einer Rechnedatei reguläre Ausdrücke ?
weil wir das Format nicht kennen und solche "{%[^}]" sscanf Aufrufe
vorkommen - aber ich glaube auch nicht das man hier reguläre Ausdrücke
braucht die Rechte-Datei ist bestimmt sehr einfach zu lesen
>du willst mir erzählen, dass du schneller dateien lädst und nach usern>und so durchsuchst, als sqlite?
ich glaube das wird alles einmal ein gelesen und fertig, die suchen
drauf werden wohl minimal/trivial sein
sqlite ist recht flott.
Es kommt halt eine seine Grenzen wenn da mal 100 Mio Records
zu testen sind. Ich komme da auf 0,1 sec ;)
Man muß auch nicht immer mit Kanonen auf Spatzen ...
Falls das noch nicht durch ist:
Es ist ein CGI-Programm. Der Indianer ruft das auf und möchte
natürlich so schnell wie möglich Ergebnisse sehen ...
sqlite kann ich über die DLL einbinden und verwenden. Das
dauert halt ein wenig.
Joachim D. schrieb:> sqlite ist recht flott.>> Es kommt halt eine seine Grenzen wenn da mal 100 Mio Records> zu testen sind. Ich komme da auf 0,1 sec ;)>> Man muß auch nicht immer mit Kanonen auf Spatzen ...
Das ist das ideale Einsatzgebiet für sqlite.
Er wird es jetzt aber nicht auf sqlite portieren und sooo schlimm sieht
der code nun auch nicht ohne das ganze aus
die Frage bleibt: was genau frisst Zeit
sind es nur die 3 Routinen die du gezeigt hast welche die 17sek
schlucken?
hast du jetzt mit dem VTune oder VS2019 getestet oder wie ist dein
Stand?
Es sind nur die Teile die ich gepostet hatte. In der
Schleife bleiben die 16 sec liegen.
VTune werde ich mir mal anschauen.
Mir qualmt jetzt der Schädel - morgenm wieder ;)
Joachim D. schrieb:> sqlite kann ich über die DLL einbinden und verwenden. Das> dauert halt ein wenig.
man kann aber auch das amalgamation source file einfach mitkompilieren,
dann entfällt der DLL-Ladeprozess und er linkt nur nimmt nur das mit,
was du auch brauchst.
Joachim D. schrieb:> strtok zerstört mir den Quellstring. Gerade beim Einlesen> von Wertepaaren nicht so toll.
wieso?
einmal Input parsen und dann vergessen, was juckt dich, ob da ein paar
'\0' eingestreut werden.
Ist doch sogar gut, dann können die substrings direkt verwendet werden
und müssen nicht erst kopiert werden (es sei denn, du willst sie über
die Lebenszeit des Inputpuffers hinaus behalten)
Joachim D. schrieb:> Es kommt halt eine seine Grenzen wenn da mal 100 Mio Records> zu testen sind. Ich komme da auf 0,1 sec ;)
Ist eventuell eine Frage der gewählten Indexe - dazu ist über deine
Struktur zu wenig bekannt.
Joachim D. schrieb:> Man muß auch nicht immer mit Kanonen auf Spatzen ...
sqlite ist keine Kanone und (d)eine User-Datenbank kein Spatz, zumal du
mindestens 2 Tabellen mit mindestens einer Relation zwischen Benutzer
und Gruppen hast, eventuell auch noch eine Zwischen Gruppen und
Bereichen, auf die diese Gruppen zugriff haben.
Und mit >14000 Userneinträgen sind die ja auch gut gefüllt.
es wäre schön wenn du aus den 3 Funktionen einen kompilierbares Beispiel
machen könntest - also auch mit den Hilfs-Funktionen und das posten -
vielleicht sogar mit einer gleich großen Rechte-Datei mit Fake-Daten und
einer Format-Beschreibung :)
cppbert3 schrieb:> es wäre schön wenn du aus den 3 Funktionen einen kompilierbares Beispiel> machen könntest - also auch mit den Hilfs-Funktionen und das posten -> vielleicht sogar mit einer gleich großen Rechte-Datei mit Fake-Daten und> einer Format-Beschreibung :)
das wär super, dann könnte man eine kleine challange draus machen, wer
den fixesten Parser schreibt.
Wobei ich mich immer noch am meisten wundere, wo die 16s für so ein
Bissel Inputparsing herkommen.
>Wobei ich mich immer noch am meisten wundere,>wo die 16s für so ein>Bissel Inputparsing herkommen.
so weit ich das verstehe ist alles als ein grosser String verfügbar
d.h. es bleiben nur sscanf, memcmp, strcmp, scword, strlen, GetSysVar,
GetAccess, StoreGrpFilter und atoi oder wild rennende Schleifen sonst
ist da ja nichts
Vlad T. schrieb:> Wobei ich mich immer noch am meisten wundere, wo die 16s für so ein> Bissel Inputparsing herkommen.
Sind "wir" uns denn sicher, dass die 16s auch wirklich da anfallen?
Ich noch nicht ganz
@Joachim D.
du musst deutlich sagen was du schon getestet hast und wie
Benchmarking mit der Stopuhr oder sind das diese jDspTime Aufrufe?
sehr ungeschickt finde ich die Verwendung der Globalen Variable
workspace, die einen Pointer auf den gesamten String bekommt.
Die Tests auf ein missing ')' sind quasi wirkungslos, weil abgesehen vom
letzten "Datenbank"eintrag wird es in dem 4MB-String immer noch irgendwo
eine Klammer geben.
Damit hier nennenswerte Zeiten zustande kommen, müsste aber in einem
großem Kontinuierlichem Bereich die Klammern fehlen, aber trotzdem
unschön.
Ich würde entweder meine Datei so definieren, dass pro Zeile ein
Datensatz, und dann die Zeilen unabhängig voneinander prozessieren.
Oder zumindest sauber die Endtokens extrahieren und in den
Datensatz-Parsern sauber abprüfen.
@OP:
Bei welchem C-Compiler ist das eigentlich eine (legale)
Funktionsdefinition (, die keine Warnung wirft):
1
static
2
GetUser(
3
char*name
4
)
5
{
?
Hast du dir mal deine Compilerwarnungen angeschaut?
Ich würde die So definieren:
(das Extrahieren des Namens gehört für mich auch nach drin)
und wenn in drin irgend welche indices bestimmt werden, immer testen, ob
ich über das Ende der aktuellen Definition gelaufen bin.
Eine andere Sache:
sind die Strukturen denn vorher genullt?
> for ( i=0; i<group[grpmax].jmax; i++){
jmax wurde vorher sonst noch nicht gesetzt oder so.
Das Itertieren und strcmp könnte sonst unter umständen recht lange
dauern und müsste, je nach Größe deiner Arrays und jmax Datentyp, auch
nicht unbedingt eine Zugriffsverletzung geben.
Das ist aktuell mein heißester Kandidat.
scanf und isspace sind beide Locale-abhängig. Hast du schon
sichergestellt, dass dir das nicht dazwischenfunkt?
> Irgendwo muß aber ein Unterschied beider Compiler sein.
Kann auch in den Libraries sein ...
Der Rest ist auskommenttiert.
Laufzeit ist 7,9 sec, wenn ich den Kommentar vor dem sscanf
entferne die übliche 15 sec. Deutet auf das sscanf hin.
Wenn ich mit setlocale( LC_ALL, NULL) das locale abrufe
erhalte ich "C". Ich suche noch den Schlauch auf dem
ich stehe. Die Rechtedatei enthält neben ASCII auch deutsche
Umlaute, ich kann mir aber nicht vorstellen, daß das einen
Einfluß haben könnte.
>Deutet auf das sscanf hin.
ich kann mir kaum vorstellen das die alte sscanf implementierung vom
Watcom so viel toller ist als die vom gcc - aber es scheint ja so zu
sein
bau doch mal das sscanf %s Verhalten als eigene Funktion
sollte ja nicht so schwer sein:
sscanf %s: String of characters. This will read subsequent characters
until a whitespace is found (whitespace characters are considered to be
blank, newline and tab).
cppbert3 schrieb:> aber es scheint ja so zu sein
Es ist immer noch wahrscheinlicher, dass Joachim nen Bug in seinem
Programm hat.
Was ist aus dem struct-Alignment Problem geworden? Was ist mit dem
time(NULL) == 0 geworden? Funktioniert gprof mittlerweile? Braucht das
sscanf die Zeit oder etwas anderes? Wird es nur vermutet, weil es zum
Programm hinzufügt wird? Was ist mit den Rückgabewerten von sscanf?
sscanf gibt immer 1 zurück.
Es gibt keine Programme die bugfrei sind. In diesem Fall ist
ein Bug vor diesem Aufruf der sscanf abschießt unwahrscheinlich
(unmittelbar in main() ganz oben rufe ich das auf).
gprof produziert schicht Unsinn.
sizeof zeigt mir bei den verschachtelten Strukturen unter GCC
und Watcom die gleiche Größe, das sollte passen.
Da du ja nicht bereit bist ein minimal funktionierendes Programm mit dem
Fehler zu erstellen, kannst etwas anderes probieren. Schreibe ein
Programm, das deine Datei öffnet und dann einfach jedes Wort "%s" in der
Datei liest. Überprüfe deine Rückgabewerte und achte darauf, dass deine
Buffer groß genug sind. Zeige das Programm hier und teste es an deiner
Datei.
Joachim D. schrieb:> char *> scword(> char *P> )> {> unsigned char *p;>> p = (unsigned char*) P;> if ( isspace( *p)){> for ( ; isspace( *p); p++) ;
Hier hast du vll einen Überlauf, wenn *p == \000.
Ansonsten:
1
char*scword(char*P){
2
char*p=P;
3
4
// skip non-space
5
while(*p&&!isspace(*p)){
6
p++;
7
}
8
9
// skip space
10
while(*p&&isspace(*p)){
11
p++;
12
}
13
14
returnp;
15
}
Macht das gleiche, aber mMn übersichtlicher und ohne Überlauf :-)
Eric B. schrieb:> Hier hast du vll einen Überlauf, wenn *p == \000.
Warum? Wenn *p == '\0', dann gibt iusspace(*p) 0 zurück (schließlich ist
'\0' kein Space), und der if-Zweig wird gar nicht erst angesprungen.
Dass man die Verzweigung ganz weglassen kann, da gebe ich dir recht.
> char *p = P;
Da ist das unsigned verloren gegangen.
Dawai. Alle benutzten Funktionen sind da drinnen.
Laufzeit immer noch 16 sec
load rights.d: ellapsed time 0.3100 sec
after blank: ellapsed time 0.3250 sec
after load grp: ellapsed time 16.7340 sec
> Hier hast du vll einen Überlauf, wenn *p == \000.
Der Kode ist nicht optimal, korrekt ist er aber. isspace(0) ist 0.
Deiner mag kürzer sein, leider ist er aber falsch ;-) (Hint, der
unsigned char pointer ist wichtig!)
Da eh vorher alle Whitespaces nach ' ' gewandelt werden, wäre *p==' '
ein guter Ersatz für das isspace(*p) (und dann würde dein Kode auch
funktionieren).
Joachim D. schrieb:> Dawai. Alle benutzten Funktionen sind da drinnen.>> Laufzeit immer noch 16 sec>> load rights.d: ellapsed time 0.3100 sec> after blank: ellapsed time 0.3250 sec> after load grp: ellapsed time 16.7340 sec
jetzt brauchen wir noch eine Datei zum testen - kannst du was
generieren?
dein #pragma pack(1) ist soooo fiese, aus alter Gewohnheit?
wofür brauchst du das überhaupt - das wirkt sich doch nur auf structs
aus?
mach die Packs wenn noetig IMMER nur um die structs die das wirklich
brauchen - niemals Programmweit - das kann dir die Performanz so in den
Keller drücken, und, und, und
gut nicht unbedingt die Performanz, aber trotzdem ist es nie schön
global alignment zu forcieren - lass den Kompiler machen wie er es fuer
sinnvoll hält - auf einem ARM oder Sparc System wäre das schon lange
nicht mehr portabel - und unnötig
Ohne das jetzt im Detail durchgespielt zu haben, würde ich sagen, dass
da über das Ende von fbuf hinaus gelesen wird, da p gleich an mehreren
Stellen in der Schleife erhöht wird.
cppbert3 schrieb:> jetzt brauchen wir noch eine Datei zum testen - kannst du was> generieren?
Die sollte ich erst anonymisieren. Dauert ein wenig ...
structure alignment brauche ich hier eigentlich nicht. Eher bei
binären Daten wo die Strukturen hintereinander in einer Datei liegen.
Ohne #pragma gleiche Laufzeit.
Yalu X. schrieb:> Ohne das jetzt im Detail durchgespielt zu haben, würde ich sagen, dass> da über das Ende von fbuf hinaus gelesen wird, da p gleich an mehreren> Stellen in der Schleife erhöht wird.
Das paßt.
loaded 3504875
load rights.d: ellapsed time 0.3160 sec
after blank: ellapsed time 0.3460 sec
p 00E17B0C
p-fubf 3504876
after load grp: ellapsed time 16.8970 sec
> Dawai. Alle benutzten Funktionen sind da drinnen.
Das schaut sehr ach einem langsamen sscanf oder isspace aus. Bevor ich
mich damit rumschlagen würde, würd ich die durch was eigenes ersetzen -
sscanf ist eh ne Krankheit ;-)
Allerdings sind in deinem Programm mind drei Fehler drin. Beide
for-Schleifen können das Ende des Strings verpassen und wühlen dann wild
im Speicher: p wird innerhalb des Bodys evtl auf das \0 oder gar drüber
hinaus gesetzt und das p++ geht dann zu weit. Außerdem ist
memcmp(a,b,n) nur gültig, wenn a und b beide noch mind n Bytes haben;
bei dir ist das für a am Ende des Strings nicht gegeben (evtl segfault).
Ich denke nicht, dass das hier das Problem ist, wollte dich nur drauf
hinweisen.
Rolf M. schrieb:> Eric B. schrieb:>> Hier hast du vll einen Überlauf, wenn *p == \000.>> Warum? Wenn *p == '\0', dann gibt iusspace(*p) 0 zurück (schließlich ist> '\0' kein Space), und der if-Zweig wird gar nicht erst angesprungen.
Nicht beim if, sondern in der for-schleife; da wird nicht mehr auf 0
getestet.
Rolf M. schrieb:>> char *p = P;>> Da ist das unsigned verloren gegangen.
"left as excercise for the reader" ;-P
Eric B. schrieb:> Rolf M. schrieb:>> Eric B. schrieb:>>> Hier hast du vll einen Überlauf, wenn *p == \000.>>>> Warum? Wenn *p == '\0', dann gibt iusspace(*p) 0 zurück (schließlich ist>> '\0' kein Space), und der if-Zweig wird gar nicht erst angesprungen.>> Nicht beim if, sondern in der for-schleife; da wird nicht mehr auf 0> getestet.
Da gilt das gleiche wie für das if. Die Schleife bricht ab, sobald das
Zeichen kein Space ist, also auch beim '\0'.
foobar schrieb:> Allerdings sind in deinem Programm mind drei Fehler drin.
Ich hätt noch einen:
Wenn der String, der nach name oder workspace geschrieben wird, mal
länger als 255 Zeichen sein sollte, gibt's einen Buffer Overflow. Ich
würde dem sscanf_Formatstring noch die maximale Länge mitteilen.
Mit so nen Scheiß mußte sich Watcom noch nicht rumschlagen:
1
# echo $LANG
2
en_US.UTF-8
3
# echo ä | grep '[ab]'
4
# echo ä | grep '[a-b]'
5
ä
6
# echo ä | LANG=C grep '[a-b]'
7
#
Wenn du die Libraryersteller den "LANG=C"-Fall nicht ordentlich
optimiert haben, geht die Laufzeit der Stringroutinen schnell gegen
Unbrauchbar. Selbst mit Optimierung ist es gern langsamer als zu
Watcom-Zeiten.
Joachim D. schrieb:> Mit Watcom kompiliert: Laufzeit 100 msec> Mit GNU kompiliert: Laufzeit 17 sec (!)
Wie lange braucht dein Programm auf GNU, wenn in main() nichts
drinsteht? Nicht dass da bloss die Startzeit eines leeren Programms in
einer verhunzten Umgebung gemessen wird.
Rolf M. schrieb:> Ich hätt noch einen:> Wenn der String, der nach name oder workspace geschrieben wird, mal> länger als 255 Zeichen sein sollte, gibt's einen Buffer Overflow. Ich> würde dem sscanf_Formatstring noch die maximale Länge mitteilen.
Das sagt auch CppCheck:
1
[/home/ghost/devel/c_test/main.c:63] (warning) sscanf() without field width
2
limits can crash with huge input data. Add a field width specifier to fix
3
this problem.
4
5
Sample program that can crash:
6
7
#include <stdio.h>
8
int main()
9
{
10
char c[5];
11
scanf("%s", c);
12
return 0;
13
}
14
15
Typing in 5 or more characters may make the program crash. The correct usage
16
here is 'scanf("%4s", c);', as the maximum field width does not include the
Soweit ich sehe, nutzt MinGW die Microsoft-Implementation von sscanf.
Der Watcom-Compiler hat vielleicht eine eigene.
In diesem Thread sind Zahlen zu finden, die darauf hindeuten, dass es
tatsächlich einfach an einem Elends langsamen sscanf unter Windows
liegen könnte:
http://www.cplusplus.com/forum/general/261127/
$ cl /EHsc bar.cpp
$ bar.exe
C fgets, 5242880 records, 2.00438 seconds
C fgets+sscanf, 5242880 records, 13.4581 seconds
rµ schrieb:> Das hat also ~n^2/2 Zeichenvergleiche zur Folge, haut die Caches des> Prozessors zusammen, das spürt man schon bei entsprechendem n.
aaargl, er macht das sogar 2mal pro Durchlauf.
Ersatz vom sscanf im Beispielprogramm ist ja trivial, damit sollte sich
feststellen lassen obs daran liegt.
Falls das reale Programm sscanf für mehr benutzt muss man halt ein wenig
umorganisieren, z.b. so, dass jeglicher whitespace (modulo del {}) durch
\0 ersetzt wird, das Ende des Puffers muss man dann halt anders
erkennen. Damit würde der strlen overhead wegfallen. Falls es das ist.
loaded 3504875
load rights.d: ellapsed time 0.3100 sec
after blank: ellapsed time 0.3200 sec
p 00E02B73
p-fubf 3504979
after load grp: ellapsed time 3.8760 sec
das zweite sscanf durch was anderes ersetzt.
kein sscanf mehr:
loaded 2325000
load rights.d: ellapsed time 0.2290 sec
after blank: ellapsed time 0.2390 sec
p 00E7CA28
p-fubf 2325000
after load grp: ellapsed time 0.2440 sec
rights.d ist die zu parsende Datei (mit Stuß).
Joachim D. schrieb:> Yalu X. schrieb:>> Ohne das jetzt im Detail durchgespielt zu haben, würde ich sagen, dass>> da über das Ende von fbuf hinaus gelesen wird, da p gleich an mehreren>> Stellen in der Schleife erhöht wird.>> Das paßt.
Ich hab's mir gerade mal genauer angeschaut:
Das Problem tritt nur dann auf, wenn die Datei direkt nach dem letzten
'}' endet. Da darauf vermutlich immer noch ein CR+LF folgt, wird das
Ende-'\0' nicht übersehen. Ich würde das aber trotzdem ändern.
Der Grund für die lange Ausführungszeit scheint aber tatsächlich am
sscanf zu liegen. Da wird wohl intern jedesmal noch ein strlen
aufgerufen, was bei deinem schier unendlich langen String natürlich ewig
dauert:
https://stackoverflow.com/questions/23923924/why-is-glibcs-sscanf-vastly-slower-than-fscanf-on-linux
Ich frage mich allerdings, warum du die riesige Datei überhaupt komplett
einliest und nicht mit fscanf auswertest, wo das Problem nicht auftritt.
Edit: Das mit dem strlen hat ja rµ oben schon geschrieben. Sorry für die
Wiederholung.
Yalu X. schrieb:> Der Grund für die lange Ausführungszeit scheint aber tatsächlich am> sscanf zu liegen. Da wird wohl intern jedesmal noch ein strlen> aufgerufen, was bei deinem schier unendlich langen String natürlich ewig> dauert:
Genial. strlen ist doch bei X86 ein einziger Maschinenbefehl ?
Joachim D. schrieb:> Genial. strlen ist doch bei X86 ein einziger Maschinenbefehl ?
Kann es sein. Das führt aber nicht dazu, dass das Lesen und Vergleichen
von mehreren MB Speicher keine Zeit brauchen würde.
Joachim D. schrieb:> Genial. strlen ist doch bei X86 ein einziger Maschinenbefehl ?
Das ändert ja nix daran, dass es eine Schleife ist, die erst bei \0
abbricht, und daher immer alle 3,5mb durchschauen muss. Ob das der
Prozessor als Maschinenbefehl ausführt oder als Programm ist da
unerheblich. Die Daten passen nicht in die schnellsten Caches des
Prozessors, und schmeissen andere Daten eventuell sogar hinaus.
Das ganze passiert 2mal pro Schleifendurchlauf, also pro "Wort", was
insgesamt eine quadratische Komplexität ergibt (Anzahl der
Schleifendurchläufe ist proportional der Dateigröße behaupte ich mal
salopp). In dem Fall also proportional zu 3,5e6^2, also proportional
10^13 Operationen. Ist eigentlich ein Wunder, dass es doch so schnell
ist ;-)
rµ schrieb:> immer alle 3,5mb durchschauen
Natürlich in dem Fall nur von der aktuellen Position bis zum Ende des
Puffers, der Puffer wird immer kleiner, das hilft aber nur einen Faktor
2.
Ich liege jetzt im Liveprogramm bei einer Ladezeit von 0.0789 sec.
Derber Unterschied zu 16 sec.
Ich danke allen die mir hier geholfen haben. Auf sscanf wäre
ich vermutlich nicht so schnell gekommen ;)
cppbert3 schrieb:> Joachim D. schrieb:>> Ladezeit von 0.0789 sec.>> und auch noch schneller als 100ms - zum Glück misst clock() ~atomgenau~> :)
Stimmt. Da muß ich mal das Isotop tauschen ;)
Joachim D. schrieb:> cppbert3 schrieb:>> Joachim D. schrieb:>>> Ladezeit von 0.0789 sec.>>>> und auch noch schneller als 100ms - zum Glück misst clock() ~atomgenau~>> :)>> Stimmt. Da muß ich mal das Isotop tauschen ;)
tausch lieber beide
rµ schrieb:> https://stackoverflow.com/questions/23923924/why-is-glibcs-sscanf-vastly-slower-than-fscanf-on-linux>> Falls das MinGW das auch so macht passiert bei jedem sscanf ein strlen> auf den string, das schaut also jeden Schleifendurchlauf den gesamten> Puffer an.
krass - damit hätte ich auch nicht gerechnet.
das macht es doch bei fscanf/scanf auch nicht (prinzipbedingt) das man
das da so einbaut...
Zumal hier scanf noch nichteinmal für das genutzt wurde, für das es da
ist:
formtatierte Daten einparsen, sondern quasi immer nur zum Tokenizing.
Wofür es deutlich besser geeignete Funktionen gibt, wie ich schon hier
meinte
Vlad T. schrieb:> diese unsägliche scword( p) / sscanf orgien.