Angenommen, ich bekomme ein Array von ganzen Zahlen mit fester Breite (dh sie passen in ein Register der Breite ), . Ich möchte die Summe auf einer Maschine mit 2er-Komplementarithmetik berechnen , die Additionen modulo mit umlaufender Semantik ausführt . Das ist einfach - aber die Summe kann die Registergröße überschreiten, und wenn dies der Fall ist, ist das Ergebnis falsch.
Wenn die Summe nicht überläuft, möchte ich sie berechnen und so schnell wie möglich überprüfen, ob kein Überlauf vorliegt. Wenn die Summe überläuft, möchte ich nur wissen, dass dies der Fall ist. Mir ist kein Wert wichtig.
Das naive Hinzufügen von Zahlen in der Reihenfolge funktioniert nicht, da eine Teilsumme überlaufen kann. Beispielsweise ist bei 8-Bit-Registern gültig und hat eine Summe von 125 , obwohl die Teilsumme 120 + 120 den Registerbereich [ - 128 , 127 ] überläuft .
Natürlich könnte ich ein größeres Register als Akkumulator verwenden, aber nehmen wir den interessanten Fall an, in dem ich bereits die größtmögliche Registergröße verwende.
Es gibt eine bekannte Technik, um Zahlen mit dem entgegengesetzten Vorzeichen als aktuelle Teilsumme hinzuzufügen . Diese Technik vermeidet Überläufe bei jedem Schritt, auf Kosten der Uncache-Freundlichkeit und der geringen Nutzung der Verzweigungsvorhersage und der spekulativen Ausführung.
Gibt es eine schnellere Technik, die möglicherweise die Berechtigung zum Überlaufen von Teilsummen nutzt und auf einem typischen Computer mit einem Überlaufflag, einem Cache, einem Verzweigungsprädiktor und spekulativer Ausführung und Laden schneller ist?
(Dies ist eine Folge der sicheren Überlaufsummierung. )
quelle
Antworten:
Sie können Zahlen der Größe w ohne Überlauf hinzufügen, wenn Sie die Arithmetik ⌈ log n ⌉ + w Bits verwenden. Mein Vorschlag ist, genau das zu tun und dann zu überprüfen, ob das Ergebnis im Bereich liegt. Algorithmen für die Multipräzisionsarithmetik sind bekannt (siehe TAOCP-Abschnitt 4.3, wenn Sie eine Referenz benötigen). Oft gibt es Hardware-Unterstützung für das Hinzufügen ( Übertragsflag und Hinzufügen mit Übertragsanweisung ), auch ohne diese Unterstützung können Sie sie ohne datenabhängigen Sprung implementieren ( Dies ist gut für Sprungvorhersagen. Sie benötigen nur einen Durchgang für die Daten und können die Daten in der bequemsten Reihenfolge aufrufen (was für den Cache gut ist).n w ⌈ logn ⌉ + w
Wenn die Daten nicht in den Speicher passen, ist der begrenzende Faktor die E / A und wie gut es Ihnen gelingt, die E / A mit der Berechnung zu überlappen.
Wenn die Daten in den Speicher passen, haben Sie wahrscheinlich (die einzige Ausnahme, die ich mir vorstellen kann, ist ein 8-Bit-Mikroprozessor, der normalerweise 64 KB Speicher hat), was bedeutet, dass Sie Arithmetik mit doppelter Genauigkeit ausführen. Der Overhead über eine Schleife macht w⌈ logn ≤ ≤ w w -Bits-Arithmetik kann nur aus zwei Befehlen bestehen (einer zum Vorzeichen erweitern, der andere mit Carry hinzufügen) und einem leichten Anstieg des Registerdrucks (aber wenn ich recht habe, hat selbst das ausgehungerte x86-Register genügend Register, auf die der einzige Speicher zugreifen kann Die innere Schleife kann die Daten abrufen. Ich denke, es ist wahrscheinlich, dass ein OO-Prozessor in der Lage sein wird, die zusätzlichen Operationen während der Speicherladezeit zu planen, so dass die innere Schleife mit der Speichergeschwindigkeit ausgeführt wird und die Übung darin besteht, die Nutzung der verfügbaren Bandbreite zu maximieren (Prefetch) oder Interleaving-Techniken können je nach Speicherarchitektur hilfreich sein.
In Anbetracht des neuesten Punktes ist es schwierig, sich andere Algorithmen mit besserer Leistung vorzustellen. Datenabhängige (und damit nicht vorhersehbare) Sprünge kommen ebenso nicht in Frage wie mehrere Durchgänge der Daten. Selbst der Versuch, die verschiedenen Kerne des heutigen Prozessors zu verwenden, wäre schwierig, da die Speicherbandbreite wahrscheinlich gesättigt sein wird, aber es könnte eine einfache Möglichkeit sein, verschachtelten Zugriff zu implementieren.
quelle
Auf einer Maschine, auf der sich ganzzahlige Typen wie ein abstrakter algebraischer Ring verhalten (was im Grunde bedeutet, dass sie umbrochen werden), könnte man die Summen von Element [i] und (Element [i] >> 16) für bis zu 32767 Elemente berechnen. Der erste Wert würde die unteren 32 Bits der korrekten Summe ergeben. Der letztere Wert würde die Bits 16-47 von etwas ergeben, das nahe an der korrekten Summe liegt, und unter Verwendung des ersteren Werts kann er leicht angepasst werden, um die Bits 16-47 der exakten korrekten Summe zu ergeben.
Pseudocode wäre so etwas wie:
Nach dem obigen Code sollten Sum2 und Sum1 zusammen die richtige Summe ergeben, unabhängig von dazwischenliegenden Überläufen. Wenn es notwendig ist, mehr als 32768 Zahlen zu summieren, können sie in Gruppen von 32768 unterteilt werden. Nach der Berechnung von Sum2 für jede Gruppe kann man sie zu einer "großen Summe" mit zwei Variablen für alle Gruppen als Ganzes hinzufügen.
In einigen Sprachen könnte der Operator für die Verschiebung nach rechts durch eine Division durch 65536 ersetzt werden. Dies funktioniert im Allgemeinen bei der Berechnung von Sum2, nicht jedoch beim Extrahieren von Sum1MSB. Das Problem ist, dass einige Sprachen Divisionen gegen Null runden, während hier eine Divisionsrundung auf die nächst niedrigere Zahl (gegen negative Unendlichkeit) durchgeführt werden muss. Fehler bei der Berechnung von Sum2 würden später korrigiert, Fehler bei der Berechnung von Sum2LSB würden sich jedoch auf das Endergebnis auswirken.
Beachten Sie, dass nichts in den Endergebnissen darauf hindeutet, ob eine der Berechnungen mit Sum1 "übergelaufen" ist. Wenn jedoch garantiert wird, dass die Werte sauber umbrochen werden, sollte sich der Code nicht darum kümmern müssen, ob ein Überlauf aufgetreten ist.
quelle