2013-05-14 19 views
14

Ich versuche Fast Inverse Square Root auf Java zu implementieren, um die Vektornormierung zu beschleunigen. Wenn ich jedoch die Single-Precision-Version in Java implementiere, erreiche ich zuerst die gleichen Geschwindigkeiten wie 1F/(float)Math.sqrt(), dann fällt die Geschwindigkeit schnell auf die Hälfte ab. Das ist interessant, denn während Math.sqrt eine native Methode verwendet (ich vermute), handelt es sich um eine Gleitkommadivision, von der ich gehört habe, dass sie sehr langsam ist. Mein Code für die Berechnung der Zahlen ist wie folgt:Warum ist die schnelle inverse Quadratwurzel auf Java so merkwürdig und langsam?

public static float fastInverseSquareRoot(float x){ 
    float xHalf = 0.5F * x; 
    int temp = Float.floatToRawIntBits(x); 
    temp = 0x5F3759DF - (temp >> 1); 
    float newX = Float.intBitsToFloat(temp); 
    newX = newX * (1.5F - xHalf * newX * newX); 
    return newX; 
} 

ein kurzes Programm verwenden ich geschrieben habe, die jeweils 16 Millionen mal zu durchlaufen, dann aggregierte Ergebnisse, und wiederholen Sie, ich Ergebnisse wie folgt aus:

1F/Math.sqrt() took 65209490 nanoseconds. 
Fast Inverse Square Root took 65456128 nanoseconds. 
Fast Inverse Square Root was 0.378224 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 64131293 nanoseconds. 
Fast Inverse Square Root took 26214534 nanoseconds. 
Fast Inverse Square Root was 59.123647 percent faster than 1F/Math.sqrt() 

1F/Math.sqrt() took 27312205 nanoseconds. 
Fast Inverse Square Root took 56234714 nanoseconds. 
Fast Inverse Square Root was 105.895914 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 26493281 nanoseconds. 
Fast Inverse Square Root took 56004783 nanoseconds. 
Fast Inverse Square Root was 111.392402 percent slower than 1F/Math.sqrt() 

Ich bekomme konsistent Zahlen, die für beide die gleiche Geschwindigkeit haben, gefolgt von einer Iteration, bei der Fast Inverse Quadratwurzel etwa 60 Prozent der Zeit von 1F/Math.sqrt() benötigt, gefolgt von mehreren Iterationen, die für Fast Inverse Square Root etwa doppelt so lange dauern als Kontrolle laufen. Ich bin verwirrt, warum FISR von Same gehen würde -> 60 Prozent schneller -> 100 Prozent langsamer, und es passiert jedes Mal, wenn ich mein Programm starte.

EDIT: Die obigen Daten sind, wenn ich es in Eclipse ausführen. Wenn ich das Programm mit javac/java laufen bekomme ich ganz andere Daten:

1F/Math.sqrt() took 57870498 nanoseconds. 
Fast Inverse Square Root took 88206794 nanoseconds. 
Fast Inverse Square Root was 52.421004 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 54982400 nanoseconds. 
Fast Inverse Square Root took 83777562 nanoseconds. 
Fast Inverse Square Root was 52.371599 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 21115822 nanoseconds. 
Fast Inverse Square Root took 76705152 nanoseconds. 
Fast Inverse Square Root was 263.259133 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 20159210 nanoseconds. 
Fast Inverse Square Root took 80745616 nanoseconds. 
Fast Inverse Square Root was 300.539585 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 21814675 nanoseconds. 
Fast Inverse Square Root took 85261648 nanoseconds. 
Fast Inverse Square Root was 290.845374 percent slower than 1F/Math.sqrt() 

EDIT2: Nach ein paar Antworten, wie es scheint die Geschwindigkeit nach mehreren Iterationen stabilisiert, aber die Zahl stabilisiert sie zu sehr volatil ist. Jeder hat eine Idee warum?

Hier ist mein Code (nicht gerade kurz, aber hier ist die ganze Sache):

public class FastInverseSquareRootTest { 

    public static FastInverseSquareRootTest conductTest() { 
     float result = 0F; 
     long startTime, endTime, midTime; 
     startTime = System.nanoTime(); 
     for (float x = 1F; x < 4_000_000F; x += 0.25F) { 
      result = 1F/(float) Math.sqrt(x); 
     } 
     midTime = System.nanoTime(); 
     for (float x = 1F; x < 4_000_000F; x += 0.25F) { 
      result = fastInverseSquareRoot(x); 
     } 
     endTime = System.nanoTime(); 
     return new FastInverseSquareRootTest(midTime - startTime, endTime 
       - midTime); 
    } 

    public static float fastInverseSquareRoot(float x) { 
     float xHalf = 0.5F * x; 
     int temp = Float.floatToRawIntBits(x); 
     temp = 0x5F3759DF - (temp >> 1); 
     float newX = Float.intBitsToFloat(temp); 
     newX = newX * (1.5F - xHalf * newX * newX); 
     return newX; 
    } 

    public static void main(String[] args) throws Exception { 
     for (int i = 0; i < 7; i++) { 
      System.out.println(conductTest().toString()); 
     } 
    } 

    private long controlDiff; 

    private long experimentalDiff; 

    private double percentError; 

    public FastInverseSquareRootTest(long controlDiff, long experimentalDiff) { 
     this.experimentalDiff = experimentalDiff; 
     this.controlDiff = controlDiff; 
     this.percentError = 100D * (experimentalDiff - controlDiff) 
       /controlDiff; 
    } 

    @Override 
    public String toString() { 
     StringBuilder sb = new StringBuilder(); 
     sb.append(String.format("1F/Math.sqrt() took %d nanoseconds.%n", 
       controlDiff)); 
     sb.append(String.format(
       "Fast Inverse Square Root took %d nanoseconds.%n", 
       experimentalDiff)); 
     sb.append(String 
       .format("Fast Inverse Square Root was %f percent %s than 1F/Math.sqrt()%n", 
         Math.abs(percentError), percentError > 0D ? "slower" 
           : "faster")); 
     return sb.toString(); 
    } 
} 
+0

Vielleicht sind die rohen Bit-Konvertierungen in Java nur langsam? (Oder fügen Sie genügend Overhead hinzu, dass alle Gewinne, die die C-Implementierungen erfahren, weggeblasen werden.) Steigt die Verlangsamung * in Läufen innerhalb derselben Sitzung immer *? – user2246674

+0

Auf meiner lokalen die ersten mehrere Lauf fastinverse ist langsamer, aber in späteren Läufen ist es viel schneller. Vielleicht macht JIT etwas. –

+0

Sie sollten dies in der Montage tun, um so schnell von Anfang an zu bekommen. Just in Time Compiler trifft Entscheidungen, um schnell zu werden. –

Antwort

11

Der JIT-Optimierer scheint den Anruf zu Math.sqrt weggeworfen zu haben.

Mit Ihrem unmodifizierten Code, ich habe

1F/Math.sqrt() took 65358495 nanoseconds. 
Fast Inverse Square Root took 77152791 nanoseconds. 
Fast Inverse Square Root was 18,045544 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 52872498 nanoseconds. 
Fast Inverse Square Root took 75242075 nanoseconds. 
Fast Inverse Square Root was 42,308531 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 23386359 nanoseconds. 
Fast Inverse Square Root took 73532080 nanoseconds. 
Fast Inverse Square Root was 214,422951 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 23790209 nanoseconds. 
Fast Inverse Square Root took 76254902 nanoseconds. 
Fast Inverse Square Root was 220,530610 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 23885467 nanoseconds. 
Fast Inverse Square Root took 74869636 nanoseconds. 
Fast Inverse Square Root was 213,452678 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 23473514 nanoseconds. 
Fast Inverse Square Root took 73063699 nanoseconds. 
Fast Inverse Square Root was 211,260168 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 23738564 nanoseconds. 
Fast Inverse Square Root took 71917013 nanoseconds. 
Fast Inverse Square Root was 202,954353 percent slower than 1F/Math.sqrt() 

konsequent langsame Zeiten für fastInverseSquareRoot, und die Zeiten dafür sind alle in dem gleichen Ball-Park, während die Math.sqrt Anrufe erheblich beschleunigt.

den Code ändern, so dass die Anrufe Math.sqrt nicht vermieden werden könnten,

for (float x = 1F; x < 4_000_000F; x += 0.25F) { 
     result += 1F/(float) Math.sqrt(x); 
    } 
    midTime = System.nanoTime(); 
    for (float x = 1F; x < 4_000_000F; x += 0.25F) { 
     result -= fastInverseSquareRoot(x); 
    } 
    endTime = System.nanoTime(); 
    if (result == 0) System.out.println("Wow!"); 

Ich habe

1F/Math.sqrt() took 184884684 nanoseconds. 
Fast Inverse Square Root took 85298761 nanoseconds. 
Fast Inverse Square Root was 53,863804 percent faster than 1F/Math.sqrt() 

1F/Math.sqrt() took 182183542 nanoseconds. 
Fast Inverse Square Root took 83040574 nanoseconds. 
Fast Inverse Square Root was 54,419278 percent faster than 1F/Math.sqrt() 

1F/Math.sqrt() took 165269658 nanoseconds. 
Fast Inverse Square Root took 81922280 nanoseconds. 
Fast Inverse Square Root was 50,431143 percent faster than 1F/Math.sqrt() 

1F/Math.sqrt() took 163272877 nanoseconds. 
Fast Inverse Square Root took 81906141 nanoseconds. 
Fast Inverse Square Root was 49,834815 percent faster than 1F/Math.sqrt() 

1F/Math.sqrt() took 165314846 nanoseconds. 
Fast Inverse Square Root took 81124465 nanoseconds. 
Fast Inverse Square Root was 50,927296 percent faster than 1F/Math.sqrt() 

1F/Math.sqrt() took 164079534 nanoseconds. 
Fast Inverse Square Root took 80453629 nanoseconds. 
Fast Inverse Square Root was 50,966689 percent faster than 1F/Math.sqrt() 

1F/Math.sqrt() took 162350821 nanoseconds. 
Fast Inverse Square Root took 79854355 nanoseconds. 
Fast Inverse Square Root was 50,813704 percent faster than 1F/Math.sqrt() 

viele langsame Zeiten für Math.sqrt und nur mäßig langsame Zeiten für fastInverseSqrt (Jetzt musste es in jeder Iteration eine Subtraktion durchführen).

+0

Mm, sehr nett. Warum ist mein JIT so schlecht über die Math.sqrt()? –

+0

Ich habe absolut keine Ahnung warum. Könnte sein, dass der Java 7 JIT generell bei solchen Dingen besser ist, könnte die spezifische Version sein. –

+0

Wenn Sie den Code direkt kompiliert haben, mussten Sie auf Java 7 sein, da 4_000_000F nicht unter 1.6 kompilieren sollte. –

0

Mein Ausgang für den gesetzten Code ist:

1F/Math.sqrt() took 165769968 nanoseconds. 
Fast Inverse Square Root took 251809517 nanoseconds. 
Fast Inverse Square Root was 51.902977 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 162953919 nanoseconds. 
Fast Inverse Square Root took 251212721 nanoseconds. 
Fast Inverse Square Root was 54.161816 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 161524902 nanoseconds. 
Fast Inverse Square Root took 36242909 nanoseconds. 
Fast Inverse Square Root was 77.562030 percent faster than 1F/Math.sqrt() 

1F/Math.sqrt() took 162289014 nanoseconds. 
Fast Inverse Square Root took 36552036 nanoseconds. 
Fast Inverse Square Root was 77.477196 percent faster than 1F/Math.sqrt() 

1F/Math.sqrt() took 163157620 nanoseconds. 
Fast Inverse Square Root took 36152720 nanoseconds. 
Fast Inverse Square Root was 77.841844 percent faster than 1F/Math.sqrt() 

1F/Math.sqrt() took 162511997 nanoseconds. 
Fast Inverse Square Root took 36426705 nanoseconds. 
Fast Inverse Square Root was 77.585221 percent faster than 1F/Math.sqrt() 

1F/Math.sqrt() took 162302698 nanoseconds. 
Fast Inverse Square Root took 36797410 nanoseconds. 
Fast Inverse Square Root was 77.327912 percent faster than 1F/Math.sqrt() 

Es scheint JIT in gekickt und die performaces fast verzehnfacht. Hoffe, jemand mit einem besseren JIT wird kommen und dies erklären. Meine Umgebung: Java 6, Eclipse.

0

Mein Jit hatte 2 Schritte, um schneller zu werden: erstens ist wahrscheinlich Algorithmus-Optimierungen und zweitens könnte Assembly-Optimierung sein.

1F/Math.sqrt() took 78202645 nanoseconds. 
Fast Inverse Square Root took 79248400 nanoseconds. 
Fast Inverse Square Root was 1,337237 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 76856008 nanoseconds. 
Fast Inverse Square Root took 24788247 nanoseconds. 
Fast Inverse Square Root was 67,747158 percent faster than 1F/Math.sqrt() 

1F/Math.sqrt() took 24162119 nanoseconds. 
Fast Inverse Square Root took 70651968 nanoseconds. 
Fast Inverse Square Root was 192,407996 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 24163301 nanoseconds. 
Fast Inverse Square Root took 70598983 nanoseconds. 
Fast Inverse Square Root was 192,174414 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 24201621 nanoseconds. 
Fast Inverse Square Root took 70667344 nanoseconds. 
Fast Inverse Square Root was 191,994259 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 24219835 nanoseconds. 
Fast Inverse Square Root took 70698568 nanoseconds. 
Fast Inverse Square Root was 191,903591 percent slower than 1F/Math.sqrt() 

1F/Math.sqrt() took 24231663 nanoseconds. 
Fast Inverse Square Root took 70633991 nanoseconds. 
Fast Inverse Square Root was 191,494608 percent slower than 1F/Math.sqrt() 
+0

Ähm ... Meinst du langsamer? –

+0

der erste Durchlauf dauerte 78 ms dann ist der zweite 76,8 ms dann der dritte ist 24 ms immer schneller und schneller, aber Leo CPU ist schneller –

+0

Das ist der sqrt in Java eingebaut, aber OP spricht über schnelle inverse. Aber auch hier ist Ihr schnelles Invers eher unheimlich. –