저는 Java 8을 처음 사용합니다. 여전히 API에 대해 잘 모르지만 새로운 Streams API의 성능과 우수한 이전 컬렉션을 비교하기 위해 작은 비공식 벤치 마크를 만들었습니다.
테스트는의 목록을 필터링하고 Integer
각 짝수에 대해 제곱근을 계산하여 결과 List
로 저장합니다 Double
.
코드는 다음과 같습니다.
public static void main(String[] args) {
//Calculating square root of even numbers from 1 to N
int min = 1;
int max = 1000000;
List<Integer> sourceList = new ArrayList<>();
for (int i = min; i < max; i++) {
sourceList.add(i);
}
List<Double> result = new LinkedList<>();
//Collections approach
long t0 = System.nanoTime();
long elapsed = 0;
for (Integer i : sourceList) {
if(i % 2 == 0){
result.add(Math.sqrt(i));
}
}
elapsed = System.nanoTime() - t0;
System.out.printf("Collections: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
//Stream approach
Stream<Integer> stream = sourceList.stream();
t0 = System.nanoTime();
result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
elapsed = System.nanoTime() - t0;
System.out.printf("Streams: Elapsed time:\t\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
//Parallel stream approach
stream = sourceList.stream().parallel();
t0 = System.nanoTime();
result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
elapsed = System.nanoTime() - t0;
System.out.printf("Parallel streams: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
}.
그리고 듀얼 코어 머신의 결과는 다음과 같습니다.
Collections: Elapsed time: 94338247 ns (0,094338 seconds)
Streams: Elapsed time: 201112924 ns (0,201113 seconds)
Parallel streams: Elapsed time: 357243629 ns (0,357244 seconds)
이 특정 테스트의 경우 스트림이 컬렉션보다 약 두 배 느리며 병렬 처리가 도움이되지 않습니다 (또는 내가 잘못 사용하고 있습니까?).
질문 :
- 이 시험은 공정합니까? 내가 실수 했어?
- 스트림이 컬렉션보다 느립니까? 누구든지 이것에 대한 좋은 공식 벤치 마크를 만들었습니까?
- 어떤 접근 방식을 사용해야합니까?
결과가 업데이트되었습니다.
@pveentjer의 조언에 따라 JVM 예열 (1k 반복) 후 1k 번 테스트를 실행했습니다.
Collections: Average time: 206884437,000000 ns (0,206884 seconds)
Streams: Average time: 98366725,000000 ns (0,098367 seconds)
Parallel streams: Average time: 167703705,000000 ns (0,167704 seconds)
이 경우 스트림이 더 성능이 좋습니다. 필터링 기능이 런타임 동안 한두 번만 호출되는 앱에서 무엇이 관찰 될지 궁금합니다.
답변
-
LinkedList
반복자를 사용하여 목록 중간에서 많이 제거하는 것 외에는 사용 을 중지하십시오 . -
직접 벤치마킹 코드 작성을 중지하고 JMH를 사용하십시오 .
적절한 벤치 마크 :
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(StreamVsVanilla.N)
public class StreamVsVanilla {
public static final int N = 10000;
static List<Integer> sourceList = new ArrayList<>();
static {
for (int i = 0; i < N; i++) {
sourceList.add(i);
}
}
@Benchmark
public List<Double> vanilla() {
List<Double> result = new ArrayList<>(sourceList.size() / 2 + 1);
for (Integer i : sourceList) {
if (i % 2 == 0){
result.add(Math.sqrt(i));
}
}
return result;
}
@Benchmark
public List<Double> stream() {
return sourceList.stream()
.filter(i -> i % 2 == 0)
.map(Math::sqrt)
.collect(Collectors.toCollection(
() -> new ArrayList<>(sourceList.size() / 2 + 1)));
}
}
결과:
Benchmark Mode Samples Mean Mean error Units
StreamVsVanilla.stream avgt 10 17.588 0.230 ns/op
StreamVsVanilla.vanilla avgt 10 10.796 0.063 ns/op
스트림 구현이 상당히 느릴 것으로 예상 한 것처럼. JIT는 모든 람다 항목을 인라인 할 수 있지만 바닐라 버전만큼 완벽하게 간결한 코드를 생성하지는 않습니다.
일반적으로 Java 8 스트림은 마법이 아닙니다. 그들은 이미 잘 구현 된 것들을 가속화 할 수 없었습니다 (아마도 일반 반복 또는 Java 5의 for-each 문으로 대체 Iterable.forEach()
및 Collection.removeIf()
호출). 스트림은 코딩 편의성과 안전성에 관한 것입니다. 편의성-스피드 트레이드 오프가 진행되고 있습니다.
답변
1) 벤치 마크를 사용하여 1 초 미만의 시간이 표시됩니다. 이는 결과에 부작용의 영향이 클 수 있음을 의미합니다. 그래서 당신의 작업을 10 배 늘 렸습니다
int max = 10_000_000;
벤치 마크를 실행했습니다. 내 결과 :
Collections: Elapsed time: 8592999350 ns (8.592999 seconds)
Streams: Elapsed time: 2068208058 ns (2.068208 seconds)
Parallel streams: Elapsed time: 7186967071 ns (7.186967 seconds)
편집하지 않은 ( int max = 1_000_000
) 결과는
Collections: Elapsed time: 113373057 ns (0.113373 seconds)
Streams: Elapsed time: 135570440 ns (0.135570 seconds)
Parallel streams: Elapsed time: 104091980 ns (0.104092 seconds)
결과와 같습니다 : 스트림이 컬렉션보다 느립니다. 결론 : 스트림 초기화 / 값 전송에 많은 시간이 소요되었습니다.
2) 작업 스트림을 늘린 후에는 빨라지지만 (괜찮음) 병렬 스트림은 너무 느리게 유지되었습니다. 뭐가 문제 야? 참고 : 당신은 collect(Collectors.toList())
명령이 있습니다. 단일 컬렉션으로 수집하면 기본적으로 동시 실행의 경우 성능 병목 현상과 오버 헤드가 발생합니다. 교체하여 간접비의 상대적 비용을 추정 할 수 있습니다
collecting to collection -> counting the element count
스트림의 경우 다음을 수행 할 수 있습니다 collect(Collectors.counting())
. 나는 결과를 얻었다 :
Collections: Elapsed time: 41856183 ns (0.041856 seconds)
Streams: Elapsed time: 546590322 ns (0.546590 seconds)
Parallel streams: Elapsed time: 1540051478 ns (1.540051 seconds)
그것은 큰 일입니다! ( int max = 10000000
) 결론 : 컬렉션에 항목을 수집하는 시간의 대부분을했다. 가장 느린 부분이 목록에 추가됩니다. BTW는 단순 ArrayList
에 사용됩니다 Collectors.toList()
.
답변
public static void main(String[] args) {
//Calculating square root of even numbers from 1 to N
int min = 1;
int max = 10000000;
List<Integer> sourceList = new ArrayList<>();
for (int i = min; i < max; i++) {
sourceList.add(i);
}
List<Double> result = new LinkedList<>();
//Collections approach
long t0 = System.nanoTime();
long elapsed = 0;
for (Integer i : sourceList) {
if(i % 2 == 0){
result.add( doSomeCalculate(i));
}
}
elapsed = System.nanoTime() - t0;
System.out.printf("Collections: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
//Stream approach
Stream<Integer> stream = sourceList.stream();
t0 = System.nanoTime();
result = stream.filter(i -> i%2 == 0).map(i -> doSomeCalculate(i))
.collect(Collectors.toList());
elapsed = System.nanoTime() - t0;
System.out.printf("Streams: Elapsed time:\t\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
//Parallel stream approach
stream = sourceList.stream().parallel();
t0 = System.nanoTime();
result = stream.filter(i -> i%2 == 0).map(i -> doSomeCalculate(i))
.collect(Collectors.toList());
elapsed = System.nanoTime() - t0;
System.out.printf("Parallel streams: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
}
static double doSomeCalculate(int input) {
for(int i=0; i<100000; i++){
Math.sqrt(i+input);
}
return Math.sqrt(input);
}
코드를 약간 변경하고 8 개의 코어가있는 맥북 프로에서 실행하면 합리적인 결과를 얻었습니다.
수집 : 경과 시간 : 1522036826ns (1.522037 초)
스트림 : 경과 시간 : 4315833719ns (4.315834 초)
병렬 스트림 : 경과 시간 : 261152901ns (0.261153 초)
답변
당신이하려는 일을 위해, 나는 일반적인 자바 API를 사용하지 않을 것입니다. 많은 boxing / unboxing이 진행되고 있으므로 성능 오버 헤드가 엄청납니다.
개인적으로 저는 많은 API가 많은 객체 쓰레기를 생성하기 때문에 쓰레기라고 생각합니다.
double / int의 기본 배열을 사용하고 단일 스레드를 수행하고 성능이 무엇인지 확인하십시오.
추신 : 벤치 마크를 처리하기 위해 JMH를 살펴볼 수 있습니다. JVM 예열과 같은 일반적인 함정을 처리합니다.
답변
