2013-03-24 21 views
28

Ich nehme an, dass die Berechnung des Moduls einer Zahl eine etwas teure Operation ist, zumindest im Vergleich zu einfachen arithmetischen Tests (wie sehen, ob eine Zahl die Länge eines Arrays überschreitet). Wenn dies tatsächlich der Fall ist, ist es effizienter zu ersetzen, beispielsweise der folgende Code ein:ist es besser, wenn möglich den mod-Operator zu vermeiden?

res = array[(i + 1) % len]; 

mit folgendem? :

Die erste ist einfacher auf die Augen, aber ich frage mich, ob die zweite effizienter sein könnte. Wenn ja, kann ich erwarten, dass ein optimierender Compiler das erste Snippet durch das zweite ersetzt, wenn eine kompilierte Sprache verwendet wird?

Natürlich funktioniert diese "Optimierung" (wenn es in der Tat eine Optimierung ist) nicht in allen Fällen (in diesem Fall funktioniert es nur, wenn i+1 nie mehr als len ist).

+10

Dies könnte ein Fall sein, den Wald für die Bäume zu vermissen. –

+1

Wenn "len" eine Kompilierzeitkonstante ist, macht ein neuer GCC-Compiler (mit '-02') normalerweise clevere Dinge, wobei er oft den Modul-Maschinenbefehl des Zielprozessors vermeidet. –

+2

Das ist wirklich die Art von Optimierung, die Sie vergessen sollten. Der optimierende Compiler wird es besser machen als du. Viel wichtiger ist die Lesbarkeit Ihres Codes. –

Antwort

20

Mein allgemeiner Rat lautet wie folgt. Verwenden Sie die Version, von der Sie glauben, dass sie für das Auge einfacher ist, und profilieren Sie dann Ihr gesamtes System. Optimieren Sie nur die Teile des Codes, die der Profiler als Engpässe kennzeichnet. Ich wette, dass der Modulo-Operator nicht unter ihnen ist.

Soweit das spezifische Beispiel geht, kann nur Benchmarking sagen, welche schneller auf Ihrer spezifischen Architektur mit Ihrem spezifischen Compiler ist. Sie ersetzen möglicherweise modulo mit branching, und es ist alles andere als offensichtlich, die schneller wäre.

+0

Auf neueren Maschinen ist Ganzzahlarithmetik fast frei; was viel mehr ist Cache-Misses ..... die sind viel teurer. ein L1-Cache-Fehler unterbricht den Prozessor für Hunderte von Zyklen, während denen der Prozessor Dutzende von Divisionen oder Modulus ausführen könnte; Die eventuellen Kosten des Moduls sind also das Rauschen –

+3

@BasileStarynkevitch: Nun, das Cache-Verhalten wird zwischen den beiden Code-Schnipsel identisch sein. Was wichtig ist, ist, ob Version 2 Verzweigungen verwendet und wenn ja, wie gut ein Job der Verzweigungsprädiktor ist. – NPE

+0

@Basile Starynkevitch Ich habe einen Faktor von etwa 300 zwischen Modulo vs Zugriff auf einen großen Tisch auf einem Laptop gesehen. (Das Hinzufügen eines Tests für die Teilbarkeit um 17 Quadrat, um den Array-Zugriff zu vermeiden, war immer noch vorteilhaft.) – starblue

0

Modulo kann mit einer einzigen Prozessoranweisung auf den meisten Architekturen (zB DIV auf x86) durchgeführt werden. Es ist jedoch wahrscheinlich eine vorzeitige Optimierung für das, was Sie brauchen.

+14

Nur weil es eine einzige Anweisung für eine Operation gibt, heißt das nicht, dass sie in einem einzigen Taktzyklus auftritt. –

+2

@ChrisDesjardins Einverstanden, aber '%' wenn der zweite Operator die Potenz von 2 ist, kann als Bitmaske dargestellt werden. – Alex

+5

Entschuldigung musste downvote. Ich habe mit vielen Architekturen gearbeitet (aber nicht mit x86) und muss noch mit einem arbeiten, der mod/div in einer Anweisung ausführt. Und ich habe Apps gesehen, bei denen mod wegen der gesamten zirkulären Pufferung einer der 10 wichtigsten CPU-verbrauchenden Funktionsaufrufe ist - jede "Beispiel" -Kopie gefolgt von einer% Puffergröße. In meinem Fall versuche ich, Mod zu vermeiden, wenn ich kann - normalerweise indem ich behaupte, dass die Eingabepuffergrößen durch 2 teilbar sind, damit der Compiler den Mod optimieren kann. –

16

Einige einfache Messung:

#include <stdio.h> 
#include <stdlib.h> 

int main(int argc, char *argv[]) 
{ 
    int test = atoi(argv[1]); 
    int divisor = atoi(argv[2]); 
    int iterations = atoi(argv[3]); 

    int a = 0; 

    if (test == 0) { 
     for (int i = 0; i < iterations; i++) 
      a = (a + 1) % divisor; 
    } else if (test == 1) { 
     for (int i = 0; i < iterations; i++) 
      a = a + 1 == divisor ? 0 : a + 1; 
    } 

    printf("%d\n", a); 
} 

mit entweder kompilieren gcc oder Klappern mit -O3 und time ./a.out 0 42 1000000000 (Modulo-Version) oder time ./a.out 1 42 1000000000 läuft (Vergleich Version) Ergebnisse

  • 6,25 Sekunden Benutzerlaufzeit für die Modulo-Version,
  • 1,03 Sekunden für die Vergleichsversion.

(mit gcc 5.2.1 oder 3.6.2 Klirren, Intel Core i5-4690K @ 3.50GHz; 64-Bit-Linux)

Das bedeutet, dass es wahrscheinlich eine gute Idee, den Vergleich Version zu verwenden .

+2

Bei realistischeren Daten (zum Beispiel, wenn die Zahl zufällig wäre) wäre die Differenz nicht so groß. – user1209304

+1

Die Vergleichsversion ist nur schneller, weil das Ergebnis der if-Anweisung jedes Mal gleich ist, also bekommt der Verzweigungsvorhersager alles richtig Zeit. Wenn Sie die Eingabe randomisiert haben, ist die Vergleichsversion sogar schlechter als mod – Bigminimus

+1

@Bigminimus Hmm, aber das Ergebnis der if-Klausel ist für beide Tests die gleiche? –

Verwandte Themen