2014-02-23 6 views
11

Java 8 스트림을 연습하기 위해 다음 중첩 루프를 Java 8 스트림 API로 변환하려고 시도했습니다. 내가 더 빨리 될 것으로 예상 나의 코어 I5 760스트림 및 성능이있는 Java 8 중첩 루프

public static int digitSum(BigInteger x) 
{ 
    int sum = 0; 
    for(char c: x.toString().toCharArray()) {sum+=Integer.valueOf(c+"");} 
    return sum; 
} 

@Test public void solve() 
    { 
     int max = 0; 
     for(int i=1;i<100;i++) 
      for(int j=1;j<100;j++) 
       max = Math.max(max,digitSum(BigInteger.valueOf(i).pow(j))); 
     System.out.println(max); 
    } 

내 솔루션에 ~ 0.135s (< 100 B, A)는^B의 가장 큰 숫자의 합을 계산하고 소요 paralellism 실제로했다의 때문에 합니다 (parallel()없이 0.19 초) 0.25s :

int max = IntStream.range(1,100).parallel() 
      .map(i -> IntStream.range(1, 100) 
      .map(j->digitSum(BigInteger.valueOf(i).pow(j))) 
      .max().getAsInt()).max().getAsInt(); 

내 질문에

  • 내가 변환 오른쪽을하거나 더 나은 방법 t이 한 o 네스트 된 루프를 스트림 계산으로 변환 하시겠습니까?
  • 왜 스트림 변형이 이전 버전보다 훨씬 느립니까?
  • 왜 parallel() 문이 실제로 시간을 0.19 초에서 0.25 초로 늘렸습니까?

마이크로 벤치 마크는 깨지기 쉽고 병렬 처리는 큰 문제에 대해서만 가치가 있지만 CPU의 경우 0.1 초조차도 영원하다는 것을 알고 있습니다. 맞습니까?

업데이트 I 이클립스 케플러의 JUnit 4에서는 프레임 워크 측정

(이것은 테스트를 수행하는데 걸리는 시간을 나타낸다). < 1,000 대신 100 B A 용

내 결과 :

  • 전통적인 루프 186s
  • 순차 스트림 193s
  • 병렬 스트림 55S

업데이트 2 sum+=Integer.valueOf(c+""); 교체 sum+= c - '0'; (감사합니다 Peter!) 10 초 전체를 면도했습니다. 평행 방법의 onds, 45s에 그것을 가져 오는. 성능에 큰 영향을 줄 것으로 기대하지 않았습니다!

또한 CPU 코어의 수 (이 경우 4 개)에 대한 병렬 처리를 줄이면 시간이 44.8 초로 단축되었으므로 (예 : a와 b = 0이 추가되었지만 성능에 많은 영향을 미치지 않습니다.) :

int max = IntStream.range(0, 3).parallel(). 
      .map(m -> IntStream.range(0,250) 
      .map(i -> IntStream.range(1, 1000) 
      .map(j->.digitSum(BigInteger.valueOf(250*m+i).pow(j))) 
      .max().getAsInt()).max().getAsInt()).max().getAsInt(); 
+2

어떻게 측정합니까? 적절한주의를 기울이지 않으면 마이크로 벤치 마크 결과가 오도 될 수 있습니다. – assylias

+3

'sum + = Integer.valueOf (c + "");'를'sum + = c - '0'; '으로 대체하는 것이 훨씬 빠르다. –

+2

FWIW 당신은'digitSum'의 루프를'CharSequence.chars()'메소드를 사용하여 스트림으로 대체 할 수 있습니다. 그것은 char 배열을 할당하는 것을 피한다. –

답변

22

코드를 기반으로 신속하고 더러운 마이크로 벤치 마크를 만들었습니다. 결과는 :

루프 : 3192
람다 병렬 3140
, λ : 868

그래서 루프 및 람다 동일 병렬 스트림 성능을 크게 향상시킨다. 귀하의 벤치마킹 방법론으로 인해 결과가 신뢰할 수 없다고 생각됩니다.

public static void main(String[] args) { 
    int sum = 0; 

    //warmup 
    for (int i = 0; i < 100; i++) { 
     solve(); 
     solveLambda(); 
     solveLambdaParallel(); 
    } 

    { 
     long start = System.nanoTime(); 
     for (int i = 0; i < 100; i++) { 
      sum += solve(); 
     } 
     long end = System.nanoTime(); 
     System.out.println("loop: " + (end - start)/1_000_000); 
    } 
    { 
     long start = System.nanoTime(); 
     for (int i = 0; i < 100; i++) { 
      sum += solveLambda(); 
     } 
     long end = System.nanoTime(); 
     System.out.println("lambda: " + (end - start)/1_000_000); 
    } 
    { 
     long start = System.nanoTime(); 
     for (int i = 0; i < 100; i++) { 
      sum += solveLambdaParallel(); 
     } 
     long end = System.nanoTime(); 
     System.out.println("lambda parallel : " + (end - start)/1_000_000); 
    } 
    System.out.println(sum); 
} 

public static int digitSum(BigInteger x) { 
    int sum = 0; 
    for (char c : x.toString().toCharArray()) { 
     sum += Integer.valueOf(c + ""); 
    } 
    return sum; 
} 

public static int solve() { 
    int max = 0; 
    for (int i = 1; i < 100; i++) { 
     for (int j = 1; j < 100; j++) { 
      max = Math.max(max, digitSum(BigInteger.valueOf(i).pow(j))); 
     } 
    } 
    return max; 
} 

public static int solveLambda() { 
    return IntStream.range(1, 100) 
      .map(i -> IntStream.range(1, 100).map(j -> digitSum(BigInteger.valueOf(i).pow(j))).max().getAsInt()) 
      .max().getAsInt(); 
} 

public static int solveLambdaParallel() { 
    return IntStream.range(1, 100) 
      .parallel() 
      .map(i -> IntStream.range(1, 100).map(j -> digitSum(BigInteger.valueOf(i).pow(j))).max().getAsInt()) 
      .max().getAsInt(); 
} 

나는 또한이 수동 테스트보다 신뢰성이 JMH으로 실행합니다. 결과는 위와 일치합니다 (통화 당 마이크로 초) :

Benchmark        Mode Mean  Units 
c.a.p.SO21968918.solve     avgt 32367.592 us/op 
c.a.p.SO21968918.solveLambda    avgt 31423.123 us/op 
c.a.p.SO21968918.solveLambdaParallel  avgt 8125.600 us/op 
+3

역순으로 테스트를 실행하면 얻는 결과를보고 싶습니다. –

+2

@PeterLawrey 같은 결과 (람다 병렬 : 836, 람다 : 3124, 루프 : 3184) – assylias

+0

재미있는 결과! 아마 인텔 터보 부스트 (하나의 코어 만 사용하는 경우 자동 오버 클러킹) 때문일 수도 있습니까? 그러나 Junit이 정말 여러 번 반복해서 항상 비슷한 결과를 얻었 기 때문에 시간 측정에 정말 불안정한 지 확실하지 않습니다. –

3

문제는 최적의 코드가 아닌 것입니다. 많이 최적화 된 코드를 사용하면 JVM이 코드를 최적화 할만큼 똑똑한 지 여부에 크게 의존하게됩니다. 루프는 훨씬 더 오래되어 더 잘 이해됩니다.

루프 코드에서 큰 차이점은 작업 세트가 매우 작습니까? 한 번에 최대 한 자리 합계 만 고려하고 있습니다. 이것은 코드가 캐시 친화적이며 매우 짧은 수명의 객체를 가지고 있음을 의미합니다. stream()의 경우, 더 많은 오버 헤드로 더 많은 캐시를 사용하여 한 번에 작업 세트에 더 많은 콜렉션을 구축 할 수 있습니다. 귀하의 GC 시간이 더 길거나 더 자주 나올 것으로 기대합니다.

왜 스트림 변형이 이전 버전보다 훨씬 느린가요?

루프는 자바가 개발되기 전부터 주변 환경에 맞게 최적화되어 있습니다. 그것들은 매우 효율적으로 하드웨어에 매핑 될 수 있습니다. 스트림은 상당히 새롭고 많이 최적화되지 않았습니다.

왜 parallel() 문이 실제로 0.19s에서 0.25s로 시간을 증가 시켰습니까?

대부분 공유 리소스에 병목 목을 가지고 있습니다. 꽤 많은 양의 쓰레기를 만들지 만 이것은 보통 상당히 비슷합니다. 더 많은 스레드를 사용하면 더 많은 오버 헤드를 보장 할뿐 아니라 추가 CPU 성능을 활용할 수있는 것은 아닙니다.

+0

흠.하지만 내 코드를보고 공유 리소스가 보이지 않습니다. 자바 라이브러리 만 사용하거나 간과 할 수 있습니까? –

+1

@kirdie 공유하고있는 하드웨어 리소스가 있습니다. 예 : L3 캐시, L1/L2 캐시, 주 메모리 및 가비지 수집기가 일부 역할을 할 수 있습니다. –