{"id":2881,"date":"2017-03-16T19:30:57","date_gmt":"2017-03-16T18:30:57","guid":{"rendered":"http:\/\/roboblog.fatal-fury.de\/?p=2881"},"modified":"2017-03-21T19:35:00","modified_gmt":"2017-03-21T18:35:00","slug":"c-guns-inputoutput-library","status":"publish","type":"post","link":"http:\/\/roboblog.fatal-fury.de\/?p=2881","title":{"rendered":"C++ Guns - std Input\/output library"},"content":{"rendered":"<p>Erstmal ein Link zum Thema. Die sind selten. <a href=\"http:\/\/wordaligned.org\/articles\/cpp-streambufs\">Copy, load, redirect and tee using C++ streambufs<\/a><br \/>\nUnd noch einen <a href=\"http:\/\/www.mr-edd.co.uk\/blog\/beginners_guide_streambuf\">A beginner's guide to writing a custom stream buffer (std::streambuf)<\/a><\/p>\n<p>Also ich muss ganz ehrlich sagen, diese C++ iostream library ist einfach nur schei\u00dfe.<br \/>\nTotal.... unm\u00f6glich damit was gescheites zu machen. Ich meine jetzt nicht std::cout oder die \">>\" \"<<\" Stream Syntax. Nein, man muss das Geschichtlich sehen. Die I\/O Library wurde designt, also C++ designt wurde. Ist also neben std::vector und std::string eines der \u00e4ltesten Sachen in der Sprache. Anders als std::vector und std::string sind diese std::*stream Klassen einfach zu abstrakt geworden.\nAber man muss das verstehen. Damals waren virtuelle Klassen und Templates neu, und wann es zuviel des Guten ist, muss man ja auch erst noch herausfinden. So finden sich in der <a href=\"http:\/\/en.cppreference.com\/w\/cpp\/io\">Dokumentation<\/a> gleich 11 Klassen um Zeug aus\/in Datein\/String\/Streams zu lesen\/schreiben. Ich ziehe hier absichtlich den Vergleich zu Qt und Fortran. Ja, Spass muss sein.<br \/>\nBei Qt gibt es QIODevice als Basisklasse f\u00fcr unter anderem QFile und QBuffer und dazu QTextstream. Wobei QBuffer\/Textstream hier ein normalen String meint, so wie es std::basic_stringstream ist. Unter Fortran g\u00e4be es write\/read(fhdl\/string)...<\/p>\n<p>Jetzt gibt es zwischen std und Qt ganz klare Unterschiede im Design. Fangen wir mit Qt an. Das ist am pragmatischsten.<br \/>\n* Ich will von einer Datei lesen\/schreiben, nehm ich QFile.<br \/>\n* Ich will von einem String lesen\/schreiben, nehm ich QBuffer\/QTextstream.<br \/>\n* Ich will diese tollen Stream \"<<\" \">>\" Operatoren, nehm ich QTextstream.<br \/>\nUnd jetzt kommt der clou:<br \/>\nEgal ob man mit Dateien oder Strings arbeitet, die Stream Operatoren funktionieren mit QTextstream immer. Eben WEIL QIODevice als Basisklasse designt wurde.<\/p>\n<p>So weit so gut.<\/p>\n<p>In der C++ I\/O Library gibt es \u00c4hnliche Ans\u00e4tze.<br \/>\n* Ich will von einer Datei lesen\/schreiben, nehm ich basic_fstream.<br \/>\n* Ich will von einem String lesen\/schreiben, nehm ich  basic_stringstream.<br \/>\n* Ich will diese tollen Stream \"<<\" \">>\" Operatoren, ja, sind schon dabei.<br \/>\nUnd jetzt kommt der clou:<br \/>\nEgal ob man mit Dateien oder Strings arbeitet, die Stream Operatoren funktionieren immer. Eben WEIL basic_ostream und basic_istream als Basisklassen designt wurden.<\/p>\n<p>Jetzt geht die c++ I\/O Library weiter und macht noch die Unterscheidung, ob man nur lesen, nur schreiben, oder beides gleichzeitig will. Und wie es die Philosophie einer streng typ basierten Sprache so will, dr\u00fcckt man das in dem Typ einer Variable aus. Und nicht (nur) mit einer Laufzeit Variablen.<\/p>\n<p>Aus diesem Grund gibt es neben basic_fstram auch basic_ifstram und basic_ofstram.<br \/>\nUnd neben basic_stringstream auch basic_istringstram und basic_ostringstream. Und nat\u00fcrlich, neben basic_iostram auch basic_istram und basic_ostram. Damit sind 9 von 11 Klassen gefunden.<\/p>\n<p>Schwachsinn? IMO! Damalig geiles Design um zu zeigen was in der Sprache steckt? Absolut!<br \/>\nWir kommen zu den Templates.<br \/>\nAhso, beinah h\u00e4tte ich Fortran vergessen.<br \/>\nBei Fortran gibt es write\/read(fhdl\/string).<\/p>\n<p>Kennt ihr den Unterschied zwischen QString und QBytearray? Beides wird benutzt um \"Text\" darzustellen. Und QString ist sozusagen die Standardklasse bei Qt. Intern wird ein 16bit Character Type benutzt, statt dem sonst \u00fcblichen 8 bit. Das hat einfach den Grund, weil Qt Weltweit agiert. Und entsprechend auch Weltsprachen darstellen muss. Nun gibt es in der Welt viel mehr als 256 darstellbare Zeichen. Also wird ein 16bit Character genutzt. <\/p>\n<p>Der C++ Erfinder hat das 1984 schon gesehen und die I\/O Library entsprechend vorbereitet. Ob das die C++ Programmierer nun auch alle verstanden haben, sei mal dahin gestellt. Der Character Typ, ob 8bit oder 16bit, kann \u00fcber Templates festgelegt werden. Die Anzahl der Klassen steigt also nicht, nur ihr Typ \u00e4ndert sich.<br \/>\nSo entspricht QBytearray std::string und QString entspricht std::wstring. F\u00fcr alle vorgestellten std steram Klassen gibt es eine \"normale \"std::istraem\" und eine \"std::wistrem\" Klasse. std::strinstream und std::wstringstream u.s.w.<\/p>\n<p>Man sieht deutlich, dass die Information bei C++ im Type steckt. Ob pragmatisch oder schlecht sei dahin gestellt.<br \/>\nBei Fortran gibt es write\/read(fhdl\/string) und hast du es mit ausl\u00e4ndische Sprachen zu tun, hast du Pech gehabt.<\/p>\n<p>Es fehlen noch std::basic_ios und std::ios_base. Bei Qt gibt es keine direkten Vergleichs Klassen, da die Implementierungsdetails versteckt sind. Aber bei C++ 1984 war das \u00fcberhaupt noch kein Thema. Also, std::basic_ios ist eine Basisklasse von allen bisher vorgestellten std:: Klassen. Jede Funktion, die std::basic_ios bereit stellt, haben auch alle anderen Funktionen. Das sind haupts\u00e4chlich Funktionen f\u00fcr die Fehlerbehandlung oder den Status des internen Buffers.<br \/>\nWie gesagt, das ist bei Qt alles weg-abstrahiert. Interne Buffer sind da irgendwo und machen ihren Job. Das ist f\u00fcr den Qt Anwender auch total egal. Hauptsache, man kann lesen\/schreiben.<br \/>\nAber C++ hat ja als Ziel, dass man alles tun kann. Also muss man auch die ganze Kontrolle haben. Und dennoch den Kompfor, dass man sich nicht selbst in den Fu\u00df schie\u00dft. Naja.<br \/>\nWenn bei Qt ein String NICHT in ein Integer konvertiert werden kann, dann passiert entweder garnichts, oder eine optionale logische Variable wird auf false gesetzt.<br \/>\nBei C++ gibt es die Funktionen good() eof() fail() bad() oder Exception, um zu \u00fcberpr\u00fcfen ob alles geklappt hat. Bei Qt gibt es okay, oder nicht okay; zwei M\u00f6glichkeiten. Bei C++ gibt 16. Naja, fast. Wie immer ist C++ detaillierter. Wenn etwas nicht good() ist, dann ist es entweder eof() oder fail() oder bad(). Klar, oder?<br \/>\nAber wenn eof(), dann ist das nicht unbedingt fail() oder bad(). Das Ende der Datei erreicht? Absolut kein Problem. Nun, zwischen fail() und bad() liegt der Unterschied, dass bad() schlechter als fail() ist. bad() impliziert fail(). Wenn bad() ist, dann geht es nicht weiter. Dann geht die Welt unter. Dann ist alles vorbei. Aber mit fail() koennen wir weiter leben. Eine Konvertierung von String nach Integer failt zwar, aber sonst ist alles okay und wir k\u00f6nnen weiter machen.<\/p>\n<p>Wann genau welcher Fehler auftrifft, kann in der <a href=\"http:\/\/en.cppreference.com\/w\/cpp\/io\/ios_base\/iostate\">IObase Doku<\/a> nachgelesen werden.<\/p>\n<p>Nun gibt es seit C++ auch Exception. Alle Google Entwickler m\u00f6gen hier jetzt bitte sterben. Exceptions bieten die M\u00f6glichkeit den Programmteil wo \"alles ist gut\", von dem Programmteil \"Fehlerbehandlung\" zu trennen. Liest man dann den Code von Oben nach Unten, liest man nur den Teil, der ausgef\u00fchrt wird, wenn kein Fehler passiert. Nur das interessiert einem meistens.<br \/>\nDie Exceptions ignoriert man bei Qt komplett. Nun, das ist ein Weltweites Framework welches versucht es jedem recht zu machen. Es sei ihnen verziehen.<br \/>\nBei C++ hat man, wie immer, die Wahl.<\/p>\n<p>Kommen wir nun langsam zum Kern der Sache, warum ich mich damit besch\u00e4ftige. Ich m\u00f6chte einen XYZ Reader bauen, der stumpft pro Zeile drei Zahlen liest. Es sollen immer drei pro Zeile sein. Und ich m\u00f6chte das mit pure C++ machen, weil Qt nicht \u00fcberall installiert ist.<br \/>\nDas ist auch absolut kein Problem.<\/p>\n<pre><code>\r\nstream >> var1 >> var2 >> var3;\r\n<\/code><\/pre>\n<p>Das funktioniert bei C++ genauso wie bei Qt genauso wie bei Fortran. Nur da m\u00fcsste man read() schreiben.<br \/>\nAber was ist mit der Fehlerbehandlung? Wenn irgendwas schief l\u00e4uft, dann will ich genau wissen, WAS und WO und WARUM es schief lief. Auf dumm herum Raten hab ich kein Bock. Lebenszeit und so.<br \/>\nJa Schei\u00dfe, das funktioniert weder bei C++ noch bei Qt noch bei Fortran gut. Bei Fortran und Qt gib es zwar Statusvariabeln ob etwas schief lief, aber DAS etwas schief lief, muss man noch selbst pr\u00fcfen. Bei C++ ist das DAS mit exceptions zwar automatisiert, aber es bleibt immer noch das WAS.<br \/>\nUnd das WO, z.b. mit Zeilennummern, muss man alles noch selbst implementieren. Ich h\u00e4tte gerne eine Fehlermeldung wie: \"Datei xyz. Zeile: 123. String 'scheisse' kann nicht nach interger konvertiert werden\". Ja dann ist alles klar. Und nicht \"iostream error\". Den Error kanntes dir sonst wo hin stecken. <\/p>\n<p>Wir k\u00f6nnen dieses Verhalten nat\u00fcrlich selbst implementieren. Eine Zeile einlesen und parsen. Bei Fortran hat man gleich das Problem: Wie gro\u00df muss der Buffer sein? BUMM FAIL.<br \/>\nBei Qt wird eine Kopie f\u00fcr den Buffer angelegt. BUMM Performance FAIL.<br \/>\nBei C++ kapiert kein Aas wie er das implementieren soll. BUMM BRAINFAIL.<\/p>\n<p>C++ soll performant sein. Und damit meinte ich nicht. \"Lade die Webseite mit 4 Bildern und 3 Textzeilen in unter 10 Sekunde\". Das ist f\u00fcr Gehirn entfernte Idioten. Ich meinte so etwas wie \"Verarbeite eine Milliarden Buchstaben in einer Sekunde\".<br \/>\nUnd dazu geh\u00f6rt auch, dass Eingabe Daten nicht erst unn\u00fctz in einem extra Buffer geladen werden, sondern, dass gleich auf den Daten gearbeitet wird.<\/p>\n<p>Und genau hier komme ich zur abstrakten Vorgehensweise von \"Lese drei Zahlen pro Zeile ein\". Ein Zeilenende ist durch ein \\n gekennzeichnet. Es muss also erst ein Abschnitt gefunden werden, von wo bis zum n\u00e4chsten \\n g\u00fcltige Daten vorliegen (unformatiertes lesen). Und dann erst kann auf diesem Abschnitt versucht werden, formatiert zu lesen. Also 3 Zahlen zu extrahieren. Diese Erkenntnis selbst, ist f\u00fcr viele schon Brainfuck genug.<\/p>\n<p>Es sollen also folgende Fehler erkannt werden.<br \/>\nEOF bevor 3 Zahlen fertig gelesen wurden.<br \/>\nNewline char bevor 3 Zahlen fertig gelesen werden.<br \/>\nKonvertierungs Fehler von String nach Zahl.<\/p>\n<p>So, und wie wird das Umgesetzt?<br \/>\nErstmal die Version, welche eine Zeile in ein String einliest und dann parst.<br \/>\nDatei werden die Daten nicht gespeichert, nur eingelesen, geparst und verworfen.<br \/>\nTestdatei mit 772MB, 28895639 Zeilen a 3 Zahlen.<\/p>\n<p>Workrechner:<br \/>\nreal    0m44.049s<br \/>\nuser    0m43.892s<br \/>\nsys     0m0.176s<br \/>\n17.55 MB\/s<\/p>\n<p>real    0m44.965s<br \/>\nuser    0m44.760s<br \/>\nsys     0m0.216s<br \/>\n17.16 MB\/s<\/p>\n<p>Heimrechner:<br \/>\nreal\t2m0.688s<br \/>\nuser\t1m58.776s<br \/>\nsys\t0m1.228s<br \/>\n6.38 MB\/s<\/p>\n<p>real\t1m54.953s<br \/>\nuser\t1m54.172s<br \/>\nsys\t0m0.628s<br \/>\n6.66 MB\/s<\/p>\n<p>Wir haben also ein Durchsatz von 17 MB\/s bzw. 6 MB\/s<\/p>\n<pre><code>\r\nstd::ifstream ss(fileName);\r\nif(!ss.is_open()) throw();\r\ntry {\r\n  ss.exceptions(std::ios_base::failbit);\r\n  for(size_t i=0; i < n and ss.good(); ++i) {\r\n    std::getline(ss, line);\r\n    std::istringstream ss2(line);\r\n    ss2.exceptions(std::ios_base::failbit);\r\n    ss2 >> data[0];\r\n  }\r\n} catch(std::ios_base::failure& ex) {\r\n...\r\n}\r\n<\/code><\/pre>\n<p>Zwei Input streams zu erzeugen kommt mir wrong vor. Aber der eine liest unformatted und der andere formatted.<br \/>\nAber der formated stream kann ja auf dem Buffer vom unformatted stream Arbeiten. Oder so \u00e4hnlich. Eigentlich, ist der newline character ein Zeichen fuer den formated stream. Und nun kann man std::ctype und die std::locale  von std::istream so \u00e4nderen, dass '\\n' nicht als whitespace anerkannt wird. Damit es es m\u00f6glich, Zahl f\u00fcr Zahl einzulesen aber bei '\\n' ist schluss. Dummerweise muss man im dann von Hand noch '\\n' und restliche whitespaces einlesen. Das f\u00fchrt wieder zu versteckte Fehler. Und die virtuellen Fehler sind eh Performance Killer. Das MUSS besser gehen.<\/p>\n<p>Workrechner:<br \/>\nreal    0m32.871s<br \/>\nuser    0m32.600s<br \/>\nsys     0m0.276s<br \/>\n23.49 MB\/s<\/p>\n<p>real    0m32.776s<br \/>\nuser    0m32.576s<br \/>\nsys     0m0.200s<br \/>\n23.55 MB\/s<\/p>\n<p>Heimrechner<br \/>\nreal\t1m37.050s<br \/>\nuser\t1m33.396s<br \/>\nsys\t0m1.512s<br \/>\n7.95 MB\/s<\/p>\n<p>real\t1m39.499s<br \/>\nuser\t1m31.360s<br \/>\nsys\t0m1.524s<br \/>\n7.76 MB\/s<\/p>\n<p>Immerhin von 17 auf 23 bzw. von 6 auf 7 MB\/s hoch. <\/p>\n<p>So, habe mit mmap die Inhalt direkt ueber den Speicher zugreifbar gemacht. Zum convertieren nehme ich std::strtod und bals std::from_chars. Die arbeiten direkt auf dem Speicher. Kein dummen rumkopieren in irgendwelche Buffer mehr. Dann schaun wir mal:<\/p>\n<p>Heimrechner<br \/>\nreal\t0m33.893s<br \/>\nuser\t0m31.112s<br \/>\nsys\t0m0.876s<br \/>\n22.77 MB\/s<\/p>\n<p>real\t0m30.617s<br \/>\nuser\t0m29.564s<br \/>\nsys\t0m0.356s<br \/>\n25.21 MB\/s<\/p>\n<p>Nu schau sich das mal einer an. Von 7 MB\/s auf 25 MB\/s hoch. Faktor 4 schneller. Dann m\u00fcsste mein Workrechner locker mit 60 MB\/s arbeiten. Mit eingeschalteter Compiler Optimierung ist sogar noch etwas mehr drin.<\/p>\n<p>Der Cluster parst bei eingeschalteter Optimierung die 772 MB in 9.6sec. Das macht stolze 80.4 MB\/s!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Erstmal ein Link zum Thema. Die sind selten. Copy, load, redirect and tee using C++ streambufs Und noch einen A beginner's guide to writing a custom stream buffer (std::streambuf) Also ich muss ganz ehrlich sagen, diese C++ iostream library ist einfach nur schei\u00dfe. Total.... unm\u00f6glich damit was gescheites zu machen. Ich meine jetzt nicht std::cout [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[17],"class_list":["post-2881","post","type-post","status-publish","format-standard","hentry","category-allgemein","tag-cpp"],"_links":{"self":[{"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/posts\/2881","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2881"}],"version-history":[{"count":9,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/posts\/2881\/revisions"}],"predecessor-version":[{"id":2910,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=\/wp\/v2\/posts\/2881\/revisions\/2910"}],"wp:attachment":[{"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2881"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2881"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/roboblog.fatal-fury.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2881"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}