Thursday 23 February 2017

C Code Exponentiell Gleitender Durchschnitt

Ich habe dies mit dem Visual C-Profiler profiliert, und es macht etwa 35 der Laufzeit. Dieser exponentielle gleitende Durchschnitt wird mehr als eine Billion Mal genannt, weil er wiederholt bei der Verarbeitung von mehr als 400 Gigabyte Daten verwendet wird. Die Daten kommen aus einem Raid Level 0 Festplattenlaufwerk-Array, so dass das Lesen der Daten für weniger als 5 der Zeit. Die Größe des Preises ist etwa 100.Ich beschleunigte es ursprünglich um den Faktor 4, indem es so viele Daten wie möglich vorberechnet. Dann konnte ich es nochmal um einen Faktor von ndash steigern PaeneInsula Okt 30 11 um 20:41 Ich konnte die Geschwindigkeit wieder um den Faktor 12 erhöhen, indem ich sie multithreading (die Natur der Daten ist so, dass sie multithreaded in sein kann So dass die Last perfekt ausbalanciert ist.) Und ich habe es läuft auf einem i7 990x (das hat 6 Kerne, hyperthreaded von insgesamt 12), übertaktet. Ndash PaeneInsula Oct 30 11 at 20:51 Sicher, Multithreading kann helfen. Aber Sie können fast sicher die Leistung auf einem einzigen Gewinde-Maschine zu verbessern. Zuerst berechnen Sie es in die falsche Richtung. Nur die modernsten Maschinen können negative Vorwärts-Prefetching. Fast alle Maschinen sind schneller für Einheit Fortschritte. D. h. Ändern Sie die Richtung des Arrays, so dass Sie scannen von niedrig zu hoch anstatt hoch zu niedrig ist fast immer besser. Als nächstes, umschreiben ein bisschen - erlauben Sie mir, die Variablennamen zu verkürzen, um es einfacher zu machen: By the way, Ich werde mit der Verwendung von Stenogrammen p für Preis und s für Glättung, um die Eingabe zu speichern. Ich bin faul. Aber es ist wahrscheinlich schneller zu tun Die Latenz zwischen avgi und avgi-2 ist dann 1 multiplizieren und addieren, anstatt eine Subtraktion und eine Multiplikation zwischen avgi und avgi-1. D. h. Mehr als doppelt so schnell. Im Allgemeinen möchten Sie die Wiederholung so umschreiben, dass avgi in Form von avgj für j so weit zurück berechnet wird, wie Sie gehen können, ohne das Gerät auszufüllen, entweder Ausführungseinheiten oder Register. Sie sind im Grunde mehr multipliziert insgesamt, um weniger Ketten von Vielfachen (und subtrahiert) auf dem kritischen Pfad zu erhalten. Überspringen von avgi-2 bis avgi ist einfach, können Sie wahrscheinlich drei und vier. Genau wie weit, hängt davon ab, was Ihre Maschine ist, und wie viele Register Sie haben. Und die Latenz der Gleitkomma-Addierer und Multiplikator. Oder besser noch, den Geschmack der kombinierten Multiply-Add-Anweisung haben Sie - alle modernen Maschinen haben sie. Z. B. Wenn der MADD oder MSUB 7 Zyklen lang ist, können Sie bis zu 6 andere Berechnungen in seinem Schatten tun, auch wenn Sie nur eine einzige Gleitkommaeinheit haben. Vollständig pipeline. Und so weiter. Weniger, wenn alle anderen Zyklen pipelined, wie für doppelte Genauigkeit auf älteren Chips und GPUs üblich ist. Der Assembler-Code sollte Softwarepipeline sein, so dass unterschiedliche Loop-Iterationen überlappen. Ein guter Compiler sollte das für Sie tun, aber möglicherweise müssen Sie den C-Code umschreiben, um die beste Leistung zu erhalten. Übrigens: Ich meine nicht, dass du ein Array von avg erstellen solltest. Stattdessen würden Sie zwei Durchschnitte benötigen, wenn avgi in Bezug auf avgi-2 berechnet wird, und so weiter. Sie können ein Array von avgi verwenden, wenn Sie wollen, aber ich denke, dass Sie nur 2 oder 4 avgs, genannt, kreativ, avg0 und avg1 (2, 3.) haben müssen, und drehen sie. Diese Art von Trick, Aufteilung eines Akkumulators oder Durchschnitt in zwei oder mehr, kombiniert mehrere Stufen der Wiederholung, ist in Hochleistungs-Code gemeinsam. Ach ja, ss vorberechnen usw. Wenn ich es richtig gemacht habe, wäre dies in unendlicher Genauigkeit identisch. (Bitte überprüfen Sie mich, bitte.) Allerdings in endlichen Präzision FP Ihre Ergebnisse können sich unterscheiden, hoffentlich nur geringfügig, wegen der verschiedenen Rundungen. Wenn das Aufrollen korrekt ist und die Antworten signifikant unterschiedlich sind, haben Sie wahrscheinlich einen numerisch instabilen Algorithmus. Du bist derjenige, der es weiß. Hinweis: Fließkomma-Rundungsfehler ändern die niedrigen Bits Ihrer Antwort. Beide, weil der Umzug des Codes, und mit MADD. Ich denke, dass ist wahrscheinlich okay, aber Sie müssen entscheiden. Hinweis: Die Berechnungen für avgi und avgi-1 sind nun unabhängig. So können Sie einen SIMD-Befehlssatz wie Intel SSE2 verwenden, der den Betrieb auf zwei 64-Bit-Werten in einem 128-Bit breiten Register zu einem Zeitpunkt erlaubt. Das ist gut für fast 2X, auf einer Maschine, die genug ALUs hat. Wenn Sie genug Register haben, um avgi in Bezug auf avgi-4 umzuschreiben (und ich bin sicher, dass Sie auf iA64 tun), dann können Sie 4X breit gehen, wenn Sie Zugang zu einer Maschine wie 256 Bit AVX haben. Auf einem GPU. Können Sie für tiefere Wiederholungen gehen, umzuschreiben avgi in Bezug auf avgi-8, und so weiter. Einige GPUs haben Anweisungen, die AXB oder sogar AXBY als einen Befehl berechnen. Obwohl das für 32-Bit üblicher ist als für 64-Bit-Präzision. An einem gewissen Punkt würde ich wahrscheinlich beginnen zu fragen: wollen Sie dies auf mehrere Preise zu einem Zeitpunkt tun nicht nur dies hilft Ihnen mit Multithreading, wird es auch für den Betrieb auf einem GPU. Und mit breiten SIMD. Minor Late Addition Ich bin ein bisschen peinlich, nicht anzuwenden Horners Rule auf Ausdrücke wie etwas effizienter. Leicht abweichende Ergebnisse mit Rundung. In meiner Verteidigung sollte jeder anständige Compiler dies für Sie tun. Aber Hrners Regel macht die Abhängigkeit Kette tiefer in Form von Multiplikatoren. Möglicherweise müssen Sie die Schleife ein paar Mal entrollen und pipelined. Oder Sie können tun, wo Sie precalculateThe exponentiellen gleitenden Durchschnitt ist eine Art von IIR-Filter, die einfach in C implementiert und verwendet minimale Ressourcen. Anders als ein einfacher gleitender Durchschnitt erfordert es keinen RAM-Puffer, um vorherige Abtastwerte zu speichern. Es muss nur einen Wert (der vorherige Durchschnitt) zu speichern. Ein exponentieller gleitender Durchschnitt wird als die folgende Gleichung ausgedrückt: avgn (in alpha) avgn-1 (1-alpha). Die Implementierung dieser Gleichung mit Floating-Point-Mathematik ist einfach, aber mit festen Punkt-Variablen ist ein wenig heikel. Das Code-Snippet verwendet hier 32-Bit-signierte Ganzzahlen für die Durchschnitts - und Eingabewerte. Zwischenwerte müssen 64-Bit-Mathematik verwenden, um Überlauffehler zu vermeiden. Alpha-Werte nahe bei Null repräsentieren eine starke Mittelung, während ein Alpha-Wert von einem keine Mittelung aufweist. Auf der Zeile, wo temp0 berechnet wird, glaube ich, dass das Ende der Zeile lesen sollte (65535 - alpha) Andernfalls würde ein Alpha von 1 unsachgemäß den vorherigen Durchschnitt sowie den neuen Wert enthalten. Leider hat der dargestellte Code zwei Hauptfehler, da der Durchschnitt als Ganzzahl gespeichert ist. Um dies zu sehen, können Sie wählen alpha zu 1024. Wir beginnen mit adcvalue 0, dann dspemai32 wird 0 wie erwartet zurück. Dann hebt adcvalue auf 1. tmp0 in dspemai32 ist: tmp0 (int64t) 1 (1024) (int64t) 0 (65536 - 1024) 1024 0 64512 1024 so ist der zurückgegebene Wert: (int32t) ((tmp0 32768) 65536) ( 1024 32768) 65536 33792 65536 0 So dspemai32 wird auf 0 zurückkehren, während es (nach lang genug Filterzeit) am Ende Rückkehr 1. Der Code implementiert effektiv einen Filter mit einer toten Zone, nicht ändern, bis die Eingabe von der unterscheidet Durchschnittlich um 32768 alpha oder mehr oder unterscheidet sich durch - (32768 alpha) oder weniger. Nach dem obigen Beispiel wird der Adcwert auf 31 erhöht (was kleiner als 32768 alpha ist). Tmp0 in dspemai32 ist: tmp0 (int64t) 31 (1024) (int64t) 0 (65536 - 1024) 31744 0 64512 31744 Der zurückgegebene Wert lautet: (int32t) ((tmp0 32768) 65536) (31744 32768) 65536 64512 65536 Tmp0 in dspemai32 ist: tmp0 (int64t) 32 (1024) (int64t) 0 (65536 - 1024) 32768 0 64512 32768 so dass der zurückgegebene Wert ist: ( Int32t) ((tmp0 32768) 65536) (32768 32768) 65536 65536 65536 1 So zumindest bewegt sich der Durchschnitt auf den Eingangswert um 1. Das ist gut. Aber dann: tmp0 (int64t) 32 (1024) (int64t) 1 (65536 - 1024) 32768 1 64512 97280 so ist der zurückgegebene Wert: (int32t) ((tmp0 97280) 65536) (97280 32768) 65536 130048 65536 1 So dspemai32 Wird auf die Rückkehr 1, nie den Eingangswert von 32 zu halten. Nicht gut. Der zweite Fehler ist die Integer-Division (tmp0 32768) 65536. In C C wird die ganzzahlige Division in Richtung 0 gehen, so dass in dieser Situation die Totzone sogar größer ist. Viel besser (und viel einfacher) ist der Algorithmus, wie von david. prentice auf avrfreaks. netcomment824765comment-824765 gezeigt: lang gesamt 0 int durchschnittlich 0 int N 0 Arbeitszahl der Proben. Total ADCW add to running total if (N gt MAXSAMPLES) genügend Samples total - durchschnittlich ein anderes entfernen N average total N integer Ich weiß, dass dies mit boost wie pro realisierbar ist: Aber ich möchte wirklich vermeiden, Boost zu verwenden. Ich habe gegoogelt und keine geeigneten oder lesbaren Beispiele gefunden. Grundsätzlich möchte ich den gleitenden Durchschnitt eines laufenden Stroms eines Gleitkommazahlstroms mit den letzten 1000 Zahlen als Datenprobe verfolgen. Was ist der einfachste Weg, um dies zu erreichen, experimentierte ich mit einem kreisförmigen Array, exponentiellen gleitenden Durchschnitt und einem einfacheren gleitenden Durchschnitt und festgestellt, dass die Ergebnisse aus dem kreisförmigen Array meine Bedürfnisse am besten geeignet. Wenn Ihre Bedürfnisse sind einfach, können Sie nur versuchen, mit einem exponentiellen gleitenden Durchschnitt. Setzen Sie einfach, Sie eine Akkumulator-Variable, und wie Ihr Code sieht auf jede Probe, aktualisiert der Code den Akkumulator mit dem neuen Wert. Sie wählen eine konstante Alpha, die zwischen 0 und 1 ist, und berechnen Sie: Sie müssen nur einen Wert von Alpha zu finden, wo die Wirkung einer gegebenen Probe nur für etwa 1000 Proben dauert. Hmm, Im nicht wirklich sicher, dass dies für Sie geeignet ist, jetzt, dass Ive es hier. Das Problem ist, dass 1000 ist ein ziemlich langes Fenster für einen exponentiellen gleitenden Durchschnitt Im nicht sicher, gibt es ein Alpha, die den Durchschnitt über die letzten 1000 Zahlen, ohne Unterlauf in der Gleitkomma Berechnung. Aber, wenn Sie einen kleineren Durchschnitt wünschen, wie 30 Zahlen oder so, dieses ist eine sehr einfache und schnelle Weise, es zu tun. Beantwortet Jun 12 12 at 4:44 1 auf Ihrem Beitrag. Der exponentielle gleitende Durchschnitt kann zulassen, dass das Alpha variabel ist. Somit kann dies dazu verwendet werden, Zeitbasisdurchschnitte (z. B. Bytes pro Sekunde) zu berechnen. Wenn die Zeit seit dem letzten Akkumulator-Update mehr als 1 Sekunde beträgt, lassen Sie Alpha 1.0 sein. Andernfalls können Sie Alpha zulassen (usecs seit letztem update1000000). Ndash jxh Grundsätzlich möchte ich den gleitenden Durchschnitt eines laufenden Stroms eines Gleitkommazahls mit den neuesten 1000 Zahlen als Datenbeispiel zu verfolgen. Beachten Sie, dass im Folgenden die Summe als Elemente als addiert ergänzt wird, wobei kostspielige O (N) - Transversionen vermieden werden, um die Summe zu berechnen, die für den durchschnittlichen Bedarf erforderlich ist. Insgesamt wird ein anderer Parameter von T gebildet, um z. B. Mit einer langen langen, wenn insgesamt 1000 lange s, eine int für char s, oder eine doppelte bis total float s. Dies ist ein wenig fehlerhaft, dass Nennsignale an INTMAX vorbeiziehen könnten - wenn Sie darauf achten, dass Sie ein langes langes nicht signiertes verwenden konnten. Oder verwenden Sie ein zusätzliches Bool-Datenelement, um aufzuzeichnen, wenn der Container zuerst gefüllt wird, während numsamples rund um das Array (am besten dann umbenannt etwas harmlos wie pos). Man nehme an, daß der quadratische Operator (T-Abtastwert) tatsächlich quadratischer Operator (T-Abtastwert) ist. Ndash oPless Jun 8 14 um 11:52 Uhr oPless ahhh. Gut beobachtet. Eigentlich meinte ich, dass es sich um void operator () (T sample) handelt, aber natürlich könntet ihr auch irgendeine Notation verwenden, die ihr mochtet. Wird beheben, danke. Ndash Tony D Jun 14 14 am 14:27


No comments:

Post a Comment