원문 :

https://devm.io/java/java-performance-tutorial-how-fast-are-the-java-8-streams-118830

 

Java performance tutorial – How fast are the Java 8 streams?

In this JAX Magazine sneak preview, JAX London speaker Angelika Langer answers the most important question for anyone using Java streams: are they really faster?

devm.io

 

개요

  • stream은 JAVA 8 이후 큰 변화 중 하나다.
  • stream의 주요 장점은 간결성과 병렬처리인데, 성능 측면에서는 어떨까 궁금해서 article을 찾아보게 되었다.

 

서론

  • 아티클의 저자 또한 스트림의 도입으로 병렬 프로그래밍의 접근성을 높였다고 말한다.
  • 그럼 과연 parallel stream operation이 squential operation 보다 항상 빠를까?에 대한 의문이 생겼다고 한다.
  • 보통 병렬 실행이 순차 실행보다 빠를것이라고 예상한다.
  • 실재로도 그럴것인가에 대한 측정을 해보았지만, 이 결과를 맹신해서는 안된다고 주의하고 있다. 

 

 

실험 1 - stream과 for문 비교

  • 실험 환경
    • dual core 
    • 500,000만개의 랜덤 int value
    • 이 중 최대값을 찾는 작업을 진행

 

//case - primitive type

//for 문
int[] a = ints;
int e = ints.length;
int m = Integer.MIN_VALUE;
for(int i=0; i < e; i++)
  if(a[i] > m) m = a[i];
  
  
// stream
int m = Arrays.stream(ints)
         .reduce(Integer.MIN_VALUE, Math::max);
  • 결과
    • for문 : 0.36ms
    • seq stream : 5.35ms

 

//case - wrapped type

//for문
int m = Integer.MIN_VALUE;
for (int i : myList)
     if (i>m) m=i;
     
     
//stream
int m = myList.stream()
          .reduce(Integer.MIN_VALUE, Math::max);
  • 결과
    • for문 : 6.55ms
    • seq stream : 8.33ms

 

 

실험 1 해석

  • 놀랍게도 for문이 단순 sequential stream 보다 나은 성능을 보여주었다.
  • for문이 빠른 이유는 40년 넘는 세월동안 JIT 컴파일러가 for문에 더 최적화 되어있을거란 분석이다.
  • primitive type과, wrapped type일때 성능차이가 달라지는 이유는 다음과 같다고 설명한다.
    • array element에 접근하는 것은 index 기반 메모리 접근이기 때문에 매우 빠르다.
    • 반면 Collection의 경우 iterator기반이기 때문에 상대적으로 오버헤드가 발생한다
    • 또한 boxing, unxboxing의 오버헤드 또한 발생한다.
  • 하지만 우리는 계산적인 측면보다 메모리 접근에 초점을 둔 실험을 진행했다는 것을 염두해야한다.
  • CPU 부담에 따라 결과가 언제든지 달라질 수 있다.
  • 이 실험의 결론으로 가져가야 할 것은 '언제나 스트림이 빠른건 아닐지도 모른다'의 마음가짐 정도이다.

 

 

실험 2 - seq stream과 parallel stream 비교

  • 그렇다면 스트림끼리의 성능 비교는 어떠할까?
  • 똑같이 50만개의 int value로 실험 진행
  • 듀얼코어이기 때문에 최대 두배의 성능을 기대한다.
    • seq/par 비율이 2.0에 근접할 것을 기대한다.
//seq
int m = Arrays.stream(ints)
          .reduce(Integer.MIN_VALUE, Math::max);
          
          
//parallel
int m = Arrays.stream(ints).parallel()
          .reduce(Integer.MIN_VALUE, Math::max);
  • 결과
    • seq : 5.35ms
    • parallel : 3.35 ms
    • seq/par : 1.60

 

실험 2 해석

  • 병렬이기 때문에 속도가 더 빠르게 나온것일까?
  • 병렬 스트림 작업에 기여하는 여러 측면들이 있다.
    • 스트림 소스의 분할이 어떻게 이루어지는가, 배열은 분할이 쉬운편이다.
    • 상태 유지성을 말하는데, 위의 예시에서는 중간 연산이 많지않다.
    • 만약 distinct()와 같은 중간 연산이 포함된다면 이 또한 성능에 영향을 미치게 될것이다.
  • 스트림에 영향을 주는 요소가 많기 때문에, 단순히 위의 결과만 보고 스트림에 대해 오판해서는 안된다고 말한다.

 

 


저자의 첫 아티클이 2015년 7월 27일에 게시되었는데

이 후 무수한 폭격을 맞았는지... 같은해 11월 26일에 추가 아티클이 올라온다.

 

 

https://devm.io/java/follow-up-how-fast-are-the-java-8-streams-122522

 

Follow-up: How fast are the Java 8 Streams?

It's a Superman vs. Batman battle. If we pit sequential streams against regular for-loops, which one comes out faster? After some careful benchmark tests, Angelika Langer shows us which is fastest, and why must be careful to make judgements.

devm.io

 

여기서 저자는 이전 실험에 대한 항변(?)과 추가 실험을 진행하게 되는데

결국 위에서 말했던대로, 단순 순환 반복은 for문이 좀 더 최적화가 잘 되어있지 않을까 하는 의문과 원시타입과 래퍼클래스의 핸들링,CPU 부하가 그렇게 크지 않았다는 점을 포인트로 내용을 전개해가고 있다.

 

개인적으로 느낀점은

1. stream이 주는 간결성은 성능 외적인 장점이 많다는 것

2. 만약 내가 성능을 따진다면 단순 코드 구현적인 요소보다, 하드웨어적 요소 또한 고민을 해야한다는 것 (아키텍처에 대한 이해도 필요하다.)

+ Recent posts