Eine optimierte Implementierung der Heaviside-Funktion

8

Ich möchte eine Implementierung der Heaviside-Funktion (super) optimieren.

Ich arbeite an einem numerischen Algorithmus (in Fortran), wo Geschwindigkeit besonders wichtig ist. Dies verwendet die Heaviside-Funktion viele Male, derzeit implementiert durch die intrinsische Signum-Funktion wie folgt:

%Vor%

Ich bin hauptsächlich an dem Fall interessiert, wo x eine reelle Zahl mit doppelter Genauigkeit auf Intel-Prozessoren ist.

Ist es möglich, eine effizientere Implementierung der Heaviside-Funktion zu entwickeln? Vielleicht Assembler verwenden, einen Superoptimierungscode oder Aufruf an eine vorhandene externe Bibliothek?

    
Ed Smith 13.09.2013, 13:21
quelle

1 Antwort

7

Haben Sie heaviside = 0.5*(sign(1,x)+1) gemeint? In jedem Fall zeigt das Testen mit gcc 4.8.1 fortran, dass High Performance Marks Idee von Vorteil sein sollte. Hier sind 3 Möglichkeiten:

heaviside1 - Original heaviside2 - Die Idee von High Performance Mark heaviside3 - eine weitere Variante

%Vor%

Wenn gcc kompiliert wird, erzeugt er diese drei eigenständigen Funktionen:

%Vor%

Bei der Kompilierung mit gcc erzeugt heaviside1 eine Multiplikation, die die Ausführung verlangsamen kann. heaviside2 eliminiert die Multiplikation. heaviside3 hat die gleiche Anzahl an Anweisungen wie heaviside2, verwendet aber 2 Speicherzugriffe weniger.

Für die Standalone-Funktionen:

%Vor%

Der Inline-Code für diese Funktionen vermeidet die Notwendigkeit der Rückgabeanweisung und übergibt idealerweise die Argumente in Registern und lädt andere Register mit benötigten Konstanten vor. Das genaue Ergebnis hängt vom verwendeten Compiler und vom aufrufenden Code ab. Eine Schätzung für inline Code:

%Vor%

Es sieht so aus, als könnte die Funktion von nur zwei vom Compiler generierten Anweisungen verarbeitet werden: vcmplesd + vandpd. Der erste Befehl erstellt eine Maske mit allen Nullen, wenn das Argument negativ ist, oder eine Maske mit allen Einsen. Der zweite Befehl wendet die Maske auf einen Registerkonstantenwert von Eins an, um den Ergebniswert von Null oder Eins zu erzeugen.

Obwohl ich diese Funktionen nicht bewertet habe, sieht es so aus, als ob die heaviside-Funktion nicht viel Ausführungszeit benötigt.

--- 23.09.2013: Hinzufügen von x86_64 Assemblerversionen und C-Sprachen-Benchmark ---

Dateifunktionen.s

%Vor%

Datei ctest.c

%Vor%

mingw64 build command: gcc -Wall -Wextra -O3 -octest.exe ctest.c functions.s

Programmausgabe von Intel Core i7-2600K bei 4,0 GHz:

%Vor%

Diese Timing-Ergebnisse beinhalten die Ausführung der Generierung von Pseudozufallsargumenten und des Ergebnissummencodes, die benötigt werden, um zu verhindern, dass der Optimierer die ansonsten unbenutzte lokale Funktion heaviside_c1 eliminiert.

heisiside_c1 stammt aus dem ursprünglichen Fortran-Vorschlag, portiert nach C. heaviside_a1 ist eine Assembler-Implementierung. heaviside_a2 ist eine Modifikation der Assembler-Sprachversion, die Registerkonstanten verwendet, die vom Aufrufer übergeben werden, um den Aufwand zu vermeiden, sie zu generieren. Für meinen Prozessor zeigt das Benchmarking keinen Vorteil für die Übergabe von Konstanten.

Die Funktionen der Assemblersprache setzen voraus, dass xmm0 das Ergebnis zurückgibt und xmm1 und xmm2 als Scratch-Register verfügbar sind. Dies gilt für die von Windows verwendete Aufrufkonvention x64. Diese Annahme sollte für andere Aufrufkonventionen bestätigt werden.

Um Speicherzugriffe zu vermeiden, erwartet die Version der Assemblersprache, dass das Argument als Wert in einem Register übergeben wird (XMM0). Da dies nicht der Fortran-Standard ist, ist eine spezielle Deklaration erforderlich. Dieser scheint für gfortran 64-bit richtig zu funktionieren:

%Vor%     
ScottD 16.09.2013, 05:18
quelle