2013-05-14 6 views
14

벡터 정규화 속도를 높이기 위해 java에 Fast Inverse Square Root을 구현하려고합니다. 그러나 Java에서 단 정밀도 버전을 구현하면 처음에는 1F/(float)Math.sqrt()과 비슷한 속도를 얻은 다음 속도가 절반으로 떨어집니다. 이것은 흥미 롭습니다. Math.sqrt가 원시 메소드를 사용하는 반면에, 이것은 들었던 부동 소수점 나누기를 포함하기 때문에 실제로 느립니다. 다음과 같이 숫자를 계산하는 내 코드는 다음과 같습니다Java에서 왜 빠른 역 제곱근이 너무 이상하고 느릴까요?

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; 
} 

을 나는 각각 1600 만 다음 번 집계 결과를 반복하고, 반복 작성한 짧은 프로그램을 사용하여, 나는 결과는 다음과 같이 얻을 :

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() 

나는 둘 다 똑같은 속도의 숫자를 일관되게 얻고, Fast Inverse Square Root는 1F/Math.sqrt()이 요구하는 시간의 약 60 %를 저장하고, Fast Inverse Square Root는 약 2 배의 반복을 거친다. 컨트롤로 실행합니다. 나는 FISR이 왜 똑같이 -> 60 % 더 빠르 -> 100 % 더 느리게 갈 것인지 혼란스럽고, 나는 프로그램을 실행할 때마다 일어난다.

EDIT : 위의 데이터는 일식으로 실행 한 것입니다.

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 : 나는 javac/java으로 프로그램을 실행하면 나는 완전히 다른 데이터를 얻을 수 몇 반응 후에는 속도가 몇 번 반복 한 후 안정 보이지만, 수, 그것은에 매우 휘발성 안정화시킨다. 누구나 왜 그런 생각이 있니?

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

아마 원시 비트 변환이 자바에서 느린 것일까 요? (또는 C 구현에서 보았던 모든 이익을 날려 버릴만큼 충분한 오버 헤드를 추가하십시오.) 같은 세션 내에서 실행 속도가 항상 * 증가합니까? – user2246674

+0

내 로컬에서 fastinverse는 처음에는 더 느리지 만 나중에 실행하면 훨씬 빠릅니다. 아마도 JIT는 뭔가를하고있을 것입니다. –

+0

처음부터 빨리 시작하려면 어셈블리에서이 작업을 수행해야합니다. 시간이 흐를수록 컴파일러가 빨리 결정할 수 있습니다. –

답변

11

JIT 옵티 마이저가 Math.sqrt에 대한 호출을 던졌습니다. 당신의 수정되지 않은 코드와

, 나는 fastInverseSquareRoot에 대한

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() 

지속적으로 느린 시간을 가지고, 그리고 Math.sqrt 호출이 상당히 빨라 동안의 시간은, 모두 같은 공을 공원에 있습니다.Math.sqrt에 대한 호출은 피할 수없는 수 있도록 코드를 변경

,

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!"); 

나는 fastInverseSqrt에 대한 느린 시간을 Math.sqrt에 대한

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() 

많은 느린 시간을 가지고, 오직 적당히 (이제는 각 반복에서 뺄셈을 수행해야했습니다).

+0

음, 아주 좋아. JIT가 Math.sqrt()에 대해 왜 그렇게 나빴 는가? –

+0

나는 이유를 전혀 모른다. Java 7 JIT가 일반적으로 그러한 것들에 더 좋을 수 있으며, 특정 버전이 될 수 있습니다. –

+0

코드를 직접 컴파일 한 경우 4_000_000F가 1.6에서 컴파일되지 않아야하므로 Java 7에 있어야합니다. –

0

게시 된 코드에 대한 나의 출력은 다음과 같습니다 :

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() 

그것은 JIT가 차게 것, 그리고 performaces 여기

은 (모든 것을 정확히 간결하지,하지만 여기에) 내 코드입니다 거의 10 배 가량 올랐다. JIT의 더 나은 보류와 함께 누군가가 와서 이것을 설명하기를 바랍니다. 내 환경 : Java 6, Eclipse.

0

내 지트는 2 단계로 빨라지고 있습니다. 첫 번째는 알고리즘 최적화이고 두 번째는 어셈블리 최적화입니다.

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

음 ... 당신은 더 느린 것을 의미합니까? –

+0

첫 번째 실행은 78ms가 걸렸고 두 번째는 76.8ms이었고 세 번째는 24ms가 빨랐지만 빠르지 만 당연히 레오의 CPU가 더 빨랐습니다. –

+0

Java에 내장 된 sqrt이지만 OP는 빠른 역행을 말합니다. 그러나 다시, 당신의 빠른 반전은 오히려 기분 좋게 수행됩니다. –