[Schnelligkeit] Festplatte einlesen und nach Pattern suchen
-
Hallo,
ich schreibe gerade ein kleineres Recovery-Programm, das durch die angegebene Festplatte läuft und nach einem selbstdefinierten Pattern sucht. Nun geht es mir um Schnelligkeit. Ich löse das bisher (mit Streams) so, dass ich einen Buffer mit der Größe des Patterns einlese, wieder zurück zur Anfangsposition setze, vergleichen, und schlussendlich ein
stream.get()mache, und das wiederholt sich dann solange, bis die ganze Festplatte durchforstet wurde.Aber ich glaube, es wäre schneller wenn ich halt einen größeren Buffer nehme und sagen wir mal ein paar Megabytes sofort einlese und dann vergleiche. Das wollte ich dann auch tun, aber da fällt mir schon auf, dass es da einige Probleme mit sich bringt. Stell dir z.B. vor, ich schreibe einen Megabyte an Daten in einen Buffer rein. Dabei enthält es das halbe Pattern, nur ganz am Ende. Und die nächste Hälfte des Patterns verbirgt sich dann sofort am Anfang des nächsten Megabytes. Wie mach ich das dann? Ich hätte mir gedacht, dass ich halt immer ein - ich sag mal - tausendes Multipel der Größe des Patterns einlese, dann kann es doch nicht mehr schiefgehen, ist das korrekt?
Das Programm sollte möglichst schnell sein.
Wie finde ich die perfekte Größe des Buffers?
Wie löse ich das Problem, dass das Pattern vermutlich nur zur Hälfte im Buffer liegt?Ich könnte vielleicht bei jedem Neuschreiben des Buffers einen Teil des Endes (der Größe des Patterns) an den Anfang setzen. Aber das scheint mir nicht die perfekte Lösung zu sein.
Wie mach ich das am besten, so, dass es am schnellsten läuft?
-
Pipette schrieb:
Ich könnte vielleicht bei jedem Neuschreiben des Buffers einen Teil des Endes (der Größe des Patterns) an den Anfang setzen.
ja
Pipette schrieb:
Aber das scheint mir nicht die perfekte Lösung zu sein.
wieso denn nicht?
-
Naja, dann ist potenziell immer ein Vergleich zu viel da

Bei zig Tausenden von Vergleichen ist das vielleicht ein kleines Performance-Leck?
Gibt es denn einen goldenen Wert, wie viele MiB ich in einem Stück einlesen soll?
Oder besser gefragt: Wie findet man diesen goldenen Wert?
-
Das erste Fragezeichen, was mir aufpoppte, ist: wieso kapselst du das Buffering nicht ab? So, dass du quasi immer weiter ein get() machst, aber das intern gebuffert wird (falls das nicht eh schon getan wird). Ich denke da ein bisschen an die Java Streams:
InputStream is = new BufferedInputStream(new FileInputStream(file), /*n MB*/); while ( ... ) is.get();Da werden ja auch nur alle x Aufrufe wirklich Daten von der Platte gelesen.
-
Pipette schrieb:
stream.get()
Pipette schrieb:
Das Programm sollte möglichst schnell sein.
Dann verwendest du die falsche Sprache.
OK, überlegen wir mal, wo die größten Flaschenhälse sind:
- Daten von der Festplatte in den Arbeitsspeicher übertragen (I/O-Cache)
- Kontextwechsel
- Dynamische Speicheranforderungen.
- Daten unnötig hin und herkopieren.
An 1) selbst kannst du nicht viel machen, außer am Kernel hacken.
An 2) kannst du einiges machen, indem du diese auf ein Minimum reduzierst. Das bedeutet: immer so viele Daten wie Möglich einlesen.
Ah, aber jetzt kommt es zu einem kleinen Problem: TLB-Misses. Sagen wir, du lädst 2 Mebibyte ein, dann hast du für die Adressübersetzung (wenn deine Maschine das kann) bei einer Seitengröße von 4 KiB (Betriebssystem und Arbeitsumgebung hast du ja nicht genannt) 512 Einträge. Die passen wahrscheinlich gerade so in das Workset der CPU. Wenn du jetzt immer wieder Daten einlädst bei 2 MiB, dann passen alle Übersetzungen in das Workset, und nix muss von der Seitentabelle eingeladen werden. Wenn du jetzt 16 MiB einlädst, passen die Übersetzungen nicht mehr in das Workset, und du hast TLB-Misses. Sprich, hier musst du abwägen zwischen Kontextwechsel und TLB-Misses - irgendwo hast du dann die goldene Mitte, wo du möglichst wenig Kontextwechsel bei möglichst wenig TLB-Misses hast. Als C-Programmierer würde ich die CPU fragen, wie deren DTLB-Cache aussieht, und dann, naja, sagen wir 25% oder 50% davon für meinen Buffer reservieren. Gute Kernel versuchen, ihre eigene Pollution so klein wie möglich zu halten, da kann man dann mehr Speicher reservieren. Oder gar nicht reservieren (siehe 4).
Einen generellen "goldenen Wert" gibt es da nicht, den Zahn muss ich dir ziehen. Nur einen Mindestwert gibt es in der Regel, nämlich die verwendete Seitengröße (oft so 4 KiB). Wenn du nur mit Sektoren (oft so 512 Bytes) arbeitest, dann mappt der Kernel intern (aber das siehst du in C++ nicht) einfach 4 KiB ein, aber du hast 8 Calls, um dann jeweils 512 Bytes zu prüfen. Das ist blöd, und einen TLB-Eintrag hast du trotzdem. Wenn du mit 4 KiB arbeitest, hast du nur einen Call und einen TLB-Eintrag. Aber 4 KiB ist ja nicht die Welt, das musst du hochrechnen. Wenn deine Festplatte 64 KiB groß ist (als Beispiel), dann hast du 16 Kontextwechsel (64 / 4 = 16). Wenn du direkt mit 64 KiB arbeitest, hast du nur einen Wechsel, aber dafür 16 TLB-Einträge. Bei einer Festplatte mit 16 MiB sind wir bei 64 KiB bei 256 KW, aber immer noch 16 TLB-Einträgen. Aber bei 16 MiB nur wieder bei einem KW, aber 4096 TLB-Einträgen.An 3) kannst du viel machen:
news weglassen, generell die Datenstrukturen der STL nicht verwenden. Aber wofür man die bei deinem Programm braucht? Keinen Plan.- ist ein Problem. Ein großes Problem. Du kannst jetzt anfangen, Memory-Mapped-I/O zu implementieren, aber das beißt dir in die Eier, weil du ja immer einen kleinen Teil der Daten aus dem vorherigen Block mittragen musst. Du kannst jetzt auch eine Seite direkt vor der Adresse, wo dein File Mapping stehen soll, reserveren, und da dann deine überzähligen Daten an das Ende reinkopieren, dann den nächsten Datenblock einmappen und wieder nach dem Muster suchen. Dem Kernel kannst du in der Regel sagen, wo er das Mapping reintun soll. Aber das ist kompliziert und betriebssystemabhängig, und wenn du das Mapping nicht bekommst, weil von irgendwo her async was reinkommt und dir genau die Adressen wegschnappt, die du haben willst, dann bekommst du Probleme. Aber selten passiert das. Und natürlich schauen dich C++-Programmierer immer böse an, wenn du so nicht-abstrakt an Probleme rangehst.
Hat aber den Vorteil, dass du nur noch deine eigenen Kopien hast. Und an denen kannst du kaum rütteln.
Hey, du hast gefragt, wie das möglichst schnell werden kann, ich gebe hier nur die Informationen weiter. Mir persönlich wäre die Geschwindigkeit ein bisschen egal, weil der Flaschenhals ja eh bei 1) liegt. Ich habe mal so ein Programm geschrieben, sogar hier mal irgendwo gepostet, und da habe ich dann einfach per
readgearbeitet und die Kopien Kopien sein gelassen. Kommt halt darauf an, was dir Geschwindigkeit wert ist.
-
dachschaden schrieb:
- Daten von der Festplatte in den Arbeitsspeicher übertragen (I/O-Cache)
- Kontextwechsel
- Dynamische Speicheranforderungen.
- Daten unnötig hin und herkopieren.
So ein Schmarrn. Punkt 1 macht die Punkte 2-4 doch vollkommen irrelevant.
-
SeppJ schrieb:
So ein Schmarrn. Punkt 1 macht die Punkte 2-4 doch vollkommen irrelevant.
Wenn du mal bis zum Ende gelesen hättest ...
dachschaden schrieb:
Mir persönlich wäre die Geschwindigkeit ein bisschen egal, weil der Flaschenhals ja eh bei 1) liegt.
Habe den Post so geschrieben, dass er auf Leute abzielt, die auch noch den letzten Taktzyklus rauskitzeln wollen. Wie sinnvoll das ist, habe ich deutlich gemacht.
-
dachschaden schrieb:
dachschaden schrieb:
Mir persönlich wäre die Geschwindigkeit ein bisschen egal, weil der Flaschenhals ja eh bei 1) liegt.
Habe den Post so geschrieben, dass er auf Leute abzielt, die auch noch den letzten Taktzyklus rauskitzeln wollen. Wie sinnvoll das ist, habe ich deutlich gemacht.
Dann zielst du mit deinen Maßnahmen auch voll am Stand der Technik vorbei.
Dass ein Programm nichts unnötiges machen sollte (also 3+4), sollte selbstverständlich sein. Deine vorgeschlagenen Gegenmaßnahmen sind jedoch Voodoo. Bei 2 verwechselst du wohl Kontextwechsel mit Cache-Misses. Daran erkennt man den echten Experten.
-
SeppJ schrieb:
Dann zielst du mit deinen Maßnahmen auch voll am Stand der Technik vorbei.
Auf der Basis kann man auch argumentieren, dass C und C++ "voll am Stand der Technik" vorbeizielen, weil wir ja jetzt Java haben. Oder C#. Oder Swift oder Rust oder was auch immer.
SeppJ schrieb:
Bei 2 verwechselst du wohl Kontextwechsel mit Cache-Misses. Daran erkennt man den echten Experten.
Und du verwechselst TLB-Misses mit Cache-Misses. TLBs kennen kein Prefetch. Und selbst im 2nd Level sind die begrenzt. Mal die Daten meiner CPU:
[TLB] Instruction TLB: 2M/4M pages, fully associative, 8 entries [TLB] Data TLB: 4 KByte pages, 4-way set associative, 64 entries [TLB] Data TLB: 2 MByte or 4 MByte pages, 4-way set associative, 32 entries and a separate array with 1 GByte pages, 4-way set associative, 4 entries [Prefetch] 64-Byte prefetching [TLB] Instruction TLB: 4KByte pages, 8-way set associative, 64 entries [STLB] Shared 2nd-Level TLB: 4 KByte/2MByte pages, 8-way associative, 1024 entriesCache-Größe kann ich noch nicht einlesen, daher verlasse ich mich mal auf die Information, dass dieser 6 MiB groß ist. Sagen wir mal, 4 MIB stehen dem Userspace zur Verfügung. Der Flaschenhals ist hier deutlich der TLB-Cache, der nur 1024 Einträge (2nd Level, also 4 MiB) erfassen kann (wenn du 4 KiB-Seiten verwendest - und wenn du Hugepages mit dem Dateisystem verwenden willst, ist das nervend auf Linux und nicht möglich auf Windows).
"Wieso? 4 MiB TLB, 4 MiB Cache, reicht doch."
Weil von den Übersetzungen ein Teil noch an den Kernel gehen. Wir haben also effektiv weniger Platz für Übersetzungen, als vom Cache getragen werden können. Außerdem ist das Holen einer Cache-Line nicht so tragisch wie das Holen einer Übersetzungsadresse, in der mehrmals auf den Speicher zugegriffen werden muss (weil es eben eine Tabelle ist).
Falls das in irgendeiner Art und Weise falsch sein sollte, bei aller Liebe, korrigiere mich. Aber sag nicht einfach, das sei Voodoo. Unnötig? Ja. Den Aufwand nicht gerechtfertigt? Ja, wahrscheinlich, es sei denn, du verwendest den Code später noch etliche Male. Aber Voodoo? Glaube ich nicht.
-
Bitte richtig lesen, die Tipps zu 3+4 sind Voodoo. 2 ist einfach nur den Aufwand nicht wert. Optimier die Pipelines (sowohl Daten als auch Instructions), damit gewinnst du mit einfacheren Mitteln viel mehr.
Warum 3+4 Voodoo sind:
3: Und wie machst du sonst dynamische Speicheranforderungen? Entweder du brauchst sie, oder du verwendest sie besser nicht. Aber wenn du sie brauchst, dann glaubst du doch wohl nicht im Ernst, dass der Name der Handlerstruktur einen Unterschied macht?4: Was hat deine Lösung mit dem beschriebenen Problem zu tun? Weil der Voodoo-Priester mal gesehen hat, wie der weiße Mann eine Memory-mapped file benutzt hat, um etwas schneller zu machen, ist das nun das Zaubermittel für alle Performanceprobleme?
-
@dachschaden
Ich muss SeppJ Recht geben, dein Beitrag ist grösstenteils einfach nur Bullshit.
Alleine wie du Kontextwechsel mit TLB-Misses vergleichst ist drollig^10. Oder dass du auf bem TLB-Cache rumreitest und den normalen Datencache dabei vollkommen ignorierst.Dein Beitrag zeigt wirklich dass du von gewissen Dingen keinen Plan hast. Und über Dinge zu schreiben von denen man keinen Plan hat, das ist ein Problem. Ein grosses Problem.
-
SeppJ schrieb:
Bitte richtig lesen, die Tipps zu 3+4 sind Voodoo. 2 ist einfach nur den Aufwand nicht wert. Optimier die Pipelines (sowohl Daten als auch Instructions), damit gewinnst du mit einfacheren Mitteln viel mehr.
Ich gehe hier von einer einfachen
memmem-Suche aus. Standardmittel. Wenn man hier bereits optimieren will, ist was ganz anderes fundamental kaputt.SeppJ schrieb:
3: Und wie machst du sonst dynamische Speicheranforderungen? Entweder du brauchst sie, oder du verwendest sie besser nicht. Aber wenn du sie brauchst, dann glaubst du doch wohl nicht im Ernst, dass der Name der Handlerstruktur einen Unterschied macht?
Ich sehe so oft Leute, die dynamische Speicheranforderungen verwenden, und das wird gar nicht gebraucht. Beispiel aus dem Kopf: eine Zahl in einen String schreiben. Oder Daten von einem Socket byteweise lesen und in ein Array packen. Es geht nicht darum, keine dynamischen Speicheranforderungen zu verwenden, sondern sie nur dann zu verwenden, wenn es Sinn ergibt.
Wenn man keine MMFs verwenden will, ist man zur Ermittelung der Buffergröße dazu gezwungen. Die Frage ist nicht: "Brauche ich dynamische Speicheranforderungen?", sondern "Warum kann/will ich keine MMFs verwenden?".
SeppJ schrieb:
4: Was hat deine Lösung mit dem beschriebenen Problem zu tun? Weil der Voodoo-Priester mal gesehen hat, wie der weiße Mann eine Memory-mapped file benutzt hat, um etwas schneller zu machen, ist das nun das Zaubermittel für alle Performanceprobleme?
Ein Performanceproblem, welches hier am Deutlichsten zu sehen ist, ist die unnötige Kopie der Daten in den eigenen Buffer (C++-Streams halt). Jenes kann man eliminieren. Was dann übrig bleibt, ist die Sorge des OP, seine eigene Kopie könnte irgendwie die Performance beeinträchtigen, und die ist so gering, dass sie nun wirklich kaum zu rechtfertigen ist.
hustbaer schrieb:
@dachschaden
Ich muss SeppJ Recht geben, dein Beitrag ist grösstenteils einfach nur Bullshit.Genau das hat SeppJ nicht geschrieben (habe ich anfangs aber auch nicht korrekt gelesen). Sondern nur, dass 2 den Aufwand nicht wert ist. Und das habe ich auch von Anfang an gesagt.
hustbaer schrieb:
Alleine wie du Kontextwechsel mit TLB-Misses vergleichst ist drollig^10
Hm, habe mich jetzt nochmal ein bisschen informiert. Ein CW (Linux) kann 100 ns bis mehrere μs benötigen (jetzt nur der Overhead durch den Wechsel, nicht das Einladen der Daten), ein TLB-Miss auf einem Sandy-Bridge ~60 ns. Aber den CW hast du nur immer beim Einladen der Daten, den TLB-Miss hast du ständig (neue Seite wird angefangen, wirft alte Seite raus, Seite wird überprüft, neue Seite wird angefangen, wirft alte Seite raus ...). Bei konservativen 2 MiB (512 Seiten) haben wir also eine Verzögerung von 512 x 60 ns ~ 31 μs.
Pauschal würde ich daher mal davon ausgehen, dass TLB-Misses stärker wehtun. Wenn du dem was hinzuzufügen hast - bin für alles offen. Aber dann komm bitte mit Argumenten. *
hustbaer schrieb:
Oder dass du auf bem TLB-Cache rumreitest und den normalen Datencache dabei vollkommen ignorierst.
200 Cycles (~100 ns) wegen einer Line, während der Datencache größer ist als der TLB, vs. 31 μs ... ich weiß ja nicht, aber ich glaube, ich könnte den Datencache berechtigterweise ignoriert haben. Allein von der Logik her: mehrere Hauptspeicherzugriffe (TLB-Miss) vs. einen (Cache-Miss) ...
EDIT: auch nicht vergessen: Der D-Cache kann prefetching. Die Daten muss man aber immer noch von Speicher laden. Dies kann tatsächlich langsamer sein als die CSs.
EDIT 2: *Das gilt auch für SeppJ. A fool can despise what he cannot get.
-
dachschaden schrieb:
hustbaer schrieb:
@dachschaden
Ich muss SeppJ Recht geben, dein Beitrag ist grösstenteils einfach nur Bullshit.Genau das hat SeppJ nicht geschrieben
Ich war nur höflich. Ich habe das Gesamtpaket schon durchaus bewusst zu Anfang "Schmarrn" genannt.
-
Ich habe jetzt die Wikipedia Seiten für TLB (Translation lookaside buffer) und den Cache geöffnet, verstehe die Beschreibungen jedoch nicht so recht.
Kennt wer gute und einfache Referenzen, wo solches Architektur-Zeug (denk ich mal) genauer erleuchtet wird, damit ich der Diskussion besser folgen kann?
Wäre ich echt dankbar dafür, wenn es da eine einfachere Erklärung dazu gäbe.
Zum Thema mit dem Buffer: Ich lasse den Anwender meiner Klasse selbst entscheiden, wie groß der Buffer sein soll. Ich habs mit 1024 KiB getestet und funktioniert soweit ganz gut - auch das mit dem "Ende des Buffers mit sich schleifen", funktioniert ganz gut. Ich lese einfach X Bytes aus, teste sie und schiebe den Input Stream wieder um Pattern.size() Bytes zurück. Läuft jetzt echt schneller als vorhin, danke dafür.
Ich erstelle übrigens ein einziges Mal einen Vektor mit der angegeben Buffergröße und überschreibe dann immer die Daten. Dabei hab ich eine
bytes_available()-Funktion geschrieben, um zu testen, ob der Rest, den man noch einlesen muss kleiner ist, als der Buffer selbst, wenn das der Fall ist, wird der Vektorresize()et. Aber das ist glaub ich jetzt nicht so ein großes Performance-Leck, das kann nämlich nur ein einziges Mal im Programm vorkommen.Dann habe ich aber noch eine Frage: Wäre es nicht klüger
std::istream::readsome()zu verwenden? Der gibt mir die Anzahl an Bytes zurück, die tatsächlich eingelesen wurden und könnte somit eine Funktion wiebytes_available()einsparen. Das mitbytes_available()funktioniert übrigens mitseekg()undtellg(). Oder tut das alles nicht zur Sache?
-
Pipette schrieb:
Kennt wer gute und einfache Referenzen, wo solches Architektur-Zeug (denk ich mal) genauer erleuchtet wird, damit ich der Diskussion besser folgen kann?
Ich hab' sogar 'nen Link verpflanzt ... aber wenn das Zeug für dich komplettes Neuland ist, dann lass es sein.
Ja, ist lang. Ist auch kompliziert. Da sitzt du lange dran. Aber am Ende bist du schlauer als hustbaer.

Pipette schrieb:
Ich lese einfach X Bytes aus, teste sie und schiebe den Input Stream wieder um Pattern.size() Bytes zurück. Läuft jetzt echt schneller als vorhin, danke dafür.
Du brauchst Pattern.size() - 1. Sonst hättest du das Muster ja gefunden. Aber wenn du es nicht findest, fehlt dir wenigstens ein Byte. Deswegen das -1.
-
dachschaden schrieb:
hustbaer schrieb:
Alleine wie du Kontextwechsel mit TLB-Misses vergleichst ist drollig^10
Hm, habe mich jetzt nochmal ein bisschen informiert. Ein CW (Linux) kann 100 ns bis mehrere μs benötigen (jetzt nur der Overhead durch den Wechsel, nicht das Einladen der Daten),
Was interessiert es irgendwen wie lange der Context-Switch "alleine" dauert, wenn das nicht die Penalty ist die man im Programm dadurch real hat?
Der Context-Switch haut viel stärker rein als bloss die Kosten des Switch alleine. Schliesslich fängt da dann an neuer Code zu laufen. Code der vermutlich irgendwas ganz anderes macht. Code der mit ziemlicher Sicherheit zu L1 Misses im I-Cache, D-Cache *und* TLB-Cache führt. Was erstmal selbst Zeit kostet, und dann weiters nochmal Zeit kostet wenn der ganze Ranz der dabei aus den Caches evicted wurde wieder reingelesen werden muss - was effektiv die Kosten dieser Cache-Misses verdoppelt.
Und wenn der Context-Switch in einen anderen Prozess switched, dann wird's erst richtig böse. Dann werden nänmlich gleich alle TLB-Einträge ungültig die in den Usermode Adressraum zeigen. Und je nach CPU kann es nötig sein dabei den gesamten TLB für ungültig zu erklären.dachschaden schrieb:
ein TLB-Miss auf einem Sandy-Bridge ~60 ns. Aber den CW hast du nur immer beim Einladen der Daten, den TLB-Miss hast du ständig (neue Seite wird angefangen, wirft alte Seite raus, Seite wird überprüft, neue Seite wird angefangen, wirft alte Seite raus ...).
Ich verstehe nicht worauf du hinaus willst. Wieso sollte man den TLB Miss ständig haben?
Du lässt die Daten vom OS 1x in den Puffer reinkopieren, da hast du u.U. den 1. TLB Miss (pro Page). Und dann prüfst/verarbeitest du die Daten im Puffer. Dort hast du - wieder nur u.U. - den 2. Miss (pro Page). 2x (pro Page) ist "ständig" oder wie?dachschaden schrieb:
Pauschal würde ich daher mal davon ausgehen, dass TLB-Misses stärker wehtun. Wenn du dem was hinzuzufügen hast - bin für alles offen. Aber dann komm bitte mit Argumenten. *
Aus deinem Beitrag ist ziemlich klar zu erkennen dass du selbst kaum bis gar keine Versuche zu dem Thema angestellt hast. Und jetzt willst du von mir "Argumente"? Eins hab ich dir oben gegeben: du kannst nicht einfach haufenweise Dinge ignorieren, dann sowas sagen wie "ich glaube X haut mehr rein als Y und wenn jmd. anderer Meinung ist dann will ich aber gute Argumenten hören". Speziell nicht bei einem Thema wo man sich so super-einfach verschätzen kann.
Mach doch einfach ein paar Versuche zu dem Thema. Und dann berichte deine Resultate und die Interpretation dieser Resultate.dachschaden schrieb:
hustbaer schrieb:
Oder dass du auf bem TLB-Cache rumreitest und den normalen Datencache dabei vollkommen ignorierst.
200 Cycles (~100 ns) wegen einer Line, während der Datencache größer ist als der TLB, vs. 31 μs ... ich weiß ja nicht, aber ich glaube, ich könnte den Datencache berechtigterweise ignoriert haben. Allein von der Logik her: mehrere Hauptspeicherzugriffe (TLB-Miss) vs. einen (Cache-Miss) ...
Ach das ist schon anstrengend.
Dir ist bekannt dass die meisten CPUs kein Prefetching über Pagegrenzen hinweg können, oder?
D.h. du hast trotz Prefetching potentiell pro Page einen D-Cache Miss.
Genau so wie du potentiell pro Page einen TLB-Cache Miss hast.
Fällt dir was auf?
Oder überleg' dir mal weiter wie gross der (shared!) L3 Cache von üblichen CPUs ist, und dann guck viele Einträge der (perCoreHW-Thread!) TLB-Cache fassen kann, und für wie viel Speicher das reicht (unter der Annahme dass jede Page zu 100% verwendet wird).
Fällt dir vielleicht jetzt was auf?
-
Naja, bevor dachschaden mal wieder eine neue Speicherverwaltung baut, frage ich mal dumm.
Welchen Pattern-Matching Algorithmus wird hier verwendet? Vielleicht lässt sich ja da noch was drehen.
-
dachschaden schrieb:
SeppJ schrieb:
Dann zielst du mit deinen Maßnahmen auch voll am Stand der Technik vorbei.
Auf der Basis kann man auch argumentieren, dass C und C++ "voll am Stand der Technik" vorbeizielen, weil wir ja jetzt Java haben. Oder C#. Oder Swift oder Rust oder was auch immer.
völliger schwachsinn... wieso ist java neuer als c++ ?
glaube sogar c++ hat den neueren standard. (bin mir gerade nicht ganz sicher...)was soll das also bedeuten?

zu mal selbst wenn c++ "älter" wäre ist das nicht das selbe wie "voll am Stand der Technik vorbei".
denn c++ hat durchaus seine berechtigung gegenüber java und ich z.B. brauche kein java, kann es(keine laufzeitumgebung) und will es auch nicht nutzen.
also was soll deine aussage bedeuten macht für mich gar keine sinn...?
außerdem dynamische speicherverwaltung hat nicht den vorteil das sie schneller oder sonst was ist (wie du ja schon gemerkt hast), sonder dafür das sicherer und fehlerunanfälliger entwickelt werden kann und du keine angst haben musst ein delete zuviel oder zuwenig oder ähnliches zu haben.

dachschaden schrieb:
Und wie machst du sonst dynamische Speicheranforderungen? Entweder du brauchst sie, oder du verwendest sie besser nicht. Aber wenn du sie brauchst, dann glaubst du doch wohl nicht im Ernst, dass der Name der Handlerstruktur einen Unterschied macht?
na ja kommt sicher immer konkret auf das problem an.
aber ich hab gelernt solage du keinen grund hast die stl-container nicht zu benutzen dann mach es und optimiert wird nachdem programmieren wenn das programm nicht schnell genug ist...
ich möchte ja eine übersichtliche, wartbare und erweiterbare lösung haben, das was du beschreibst will eig niemand da es nicht wartbar geschweige denn nachvollziehbar ist
(aber ok es läuft 1 millisekunde bzw mehr oder weniger schneller wow, hast schonmal was von wirtschaftlichkeit gehört? - nicht jedes programm muss das schnellste sein)aber wenn du ein kleines programm schreibst nimm die stl-container...
warum nicht weil dein programm ansonsten 1millisekunde eher fertig ist ?du dafür aber fehleranfälliger programmierst und vorraussichtlich auch mehr zeit brauchst...
lg
-
So, jetzt habe ich mal das Programm implementiert. Dem TE möchte ich die Lösung natürlich nicht direkt verraten, aber hier mal ein paar Kenndaten:
- Gentoo Linux 4.6.0
- In C geschrieben
- kein Support für Windows (könnte ich einfügen, aber dann müsste ich Teile von Backend-Code einbinden, den ich gar nicht veröffentlichen will)
- Daten werden wie beschrieben übermmapeingeladen und ausgeladen.
- für das Speicher-Mapping wirdPROT_READ | PROT_WRITEundMAP_PRIVATE | MAP_NORESERVE | MAP_32BITverwendet (weil wir den reservierten Bereich unten im Speicher haben wollen)
- für das File-Mapping wirdPROT_READundMAP_SHARED | MAP_NORESERVEverwendet, das Mapping wird direkt nach dem Speichermapping gesetzt.
- die Tests fanden mit /dev/sds6 statt (Systempartition - ich wusste gar nicht, dass ich da Shakespeare druff hatte, aber egal).
- um den Test nicht zu verfälschen (siehe unten) wurden nur die ersten 512 MiB der Partition eingelesen
- die Daten befanden sich bereits im I/O-Cache (Arbeitsspeicher), es wurde hier also nicht das Einladen mitgemessen.
- die CPU-Daten habe ich bereits gepostet.Ein paar Punkte am Rande:
mmapmappt nur dann Daten von der Festplatte, wenn der Kernel es muss - sprich, das eigentliche Laden wird nicht in den Calls gemacht, sondern während auf die Speicherbereiche (memmem) zugegriffen wird.
- eine Iteration hat somit zwei CS (Kontextwechsel -mmapundmunmap)
Es wurde gemessen:
mmapmit 256 Mebibyte Bufferlänge. Durchschnittliches Ergebnis: 722 Millionen Cycles, 4 CS
mmapmit 64 Mebibyte Bufferlänge. Durchschnittliches Ergebnis: 730 Millionen Cycles, 16 CS
mmapmit 4 Mebibyte Bufferlänge. Durchschnittliches Ergebnis: 705 Millionen Cycles, 256 CS
mmapmit 2 Mebibyte Bufferlänge. Durchschnittliches Ergebnis: 700 Millionen Cycles, 512 CS
mmapmit 1 Mebibyte Bufferlänge. Durchschnittliches Ergebnis: 700 Millionen Cycles, 1024 CS
mmapmit 64 Kibibyte Bufferlänge. Durchschnittliches Ergebnis: 715 Millionen Cycles, 16384 CSDas Programm habe ich jetzt schnell zusammengeschrieben, und es sind auch vermutlich noch ein paar Bugs drin. Aber einen groben Überblick sollte es schon liefern: Kontextwechsel wiegen offenbar nicht so schwer wie die Verunreinigung des Caches bis zum (von mir prophezeiten) Threshold von 2 Mebibyte.
Gerade habe ich keine Zeit mehr, daran weiterzuarbeiten. Aber die Tage werde ich es mir nochmal genauer anschauen.
WASWEISSICHSCHON schrieb:
völliger schwachsinn... wieso ist java neuer als c++ ?
Mein Eindruck war immer, dass die Hauptkritik der Java-Leute an C++ war, dass dieses viel zu kompliziert, weswegen man dann was eigenes gebaut hat. Eine Ironie, dass die Komplexitätsberge, die Java inzwischen auftürmt, C++ in den Schatten stellt .
WASWEISSICHSCHON schrieb:
denn c++ hat durchaus seine berechtigung gegenüber java und ich z.B. brauche kein java, kann es(keine laufzeitumgebung) und will es auch nicht nutzen.
Das war das Argument von SeppJ, dem ich nicht beipflichte. C hat auch seine Berechtigung, ob einige Leute meinen (ich schaue niemanden an hier), dass es voll am Stand der Technik vorbeigeht.
WASWEISSICHSCHON schrieb:
außerdem dynamische speicherverwaltung hat nicht den vorteil das sie schneller oder sonst was ist (wie du ja schon gemerkt hast), sonder dafür das sicherer und fehlerunanfälliger entwickelt werden kann und du keine angst haben musst ein delete zuviel oder zuwenig oder ähnliches zu haben.

Schonmal einen Allokator programmiert? Oder zumindest mal in den Code von einem geblinzelt?
Beides kann ich bejahen. Und mein Allokator hat keines der genannten Probleme, hat keine Userspace-Locks, und kann sogar, wenn Support dafür besteht, für kleinere Allokationen diese einfacher auf dem Stack anlegen (für gewöhnlich auf alles unter 4 KiB getrimmt). Er ist wohl auf Bulk-Allokationen ausgerichtet - 10-mal Resizing wird dich mit dem Ding mehr kosten als mit Standardmitteln (CW wegen), aber wenn du die vermeidest, ist das Ding rasend.
EDIT: @Bitte ein Bit, das noch: verwende
memmem, ich habe aber vor Jahren eine Boyer-Moore-Implementierung gebaut, die könnte ich, wenn ich Zeit habe, auch noch mal einbauen.
-
Hallo,
Ich hab das Programm jetzt auch fertig implementiert, sind sicherlich noch einige Dinge da, die man besser hätte machen können. Aber naja. Ich wills mit euch teilen. Würde mich freuen, wenn sich jemand den Code mal ansehen will und mir noch weitere Ratschläge gibt, wie man das Programm noch besser gestalten kann. Das Programm ist ein Kommandozeilenwerkzeug und ich habe es so gebaut, dass man wirklich alles selber einstellen kann. Die Namen der Optionen sind vielleicht nicht immer perfekt gewählt, aber das liegt an meinen mangelnden Englischkenntnissen. Ist auch wirklich kein großes Ding, sind rund 200 - 300 Zeilen. Und es klappt wunderbar! Ich habe mal testhalber nach dem Muster '
#include <iostream>' gesucht und habe tatsächlich einen Haufen (eigentlich alles) gefunden.