List, Map, Set, Array 들과는 달리 내부적인 저장소가 없다.
주로 사용하는 Collections 들은 내부적으로 저장소가 있다. 심지어 String마저도 내부적으로는 char[] 를 가지고 있다. 대부분의 자료구조들이 자체적으로 값을 가지고 있는것에 반해 Stream은 저장소를 별도로 가지고 있지 않다.
List<String> l = new ArrayList(Arrays.asList("one", "two"));
Stream<String> sl = l.stream();
l.add("three");
String s = sl.collect(joining(" "));
System.out.println(s);
위코드의 출력값은 “one two three” 이다. 계속 참조하고 있다라고 이해하는게 차라리 받아들이기 쉬울 것 같다.
pipeline
- Stream에서 호출하는 함수들은 대부분 pipeline 인터페이스에 정의되어있다.
- 파이프라인에서 사용되는 함수들은 크게 2가지다. 중개함수, 종료함수
- 중개함수 : filter(), map(), sorted() ..
- 종료함수 : sum(), max() ..
- 중개함수는 Stream을 구성하는 엘리먼트들이 서로 연관성이 있느냐에 따라 stateless, stateful로 나뉜다.
- stateless : filter(), map()...
- stateful : sorted()..
이렇게 나누는 또 다른 이유는 Stream을 병렬로 실행했을때 안정성을 확보할 수 있느냐 없느냐의 차이일 수도 있다.
- 종료함수들은 내부적으로 reduce()를 호출하는데, reduce()함수 호출시 Stream이 재사용할 수 없도록 Stream의 상태값을 마킹(?)한다.
- 종료함수가 실행되기 이전까지 중개함수들은 lazy방식으로 실행되어 불필요한 연산을 줄일 수 있다고 한다.
성능
for-loop, stream, parallelstream 3가지를 비교해보는 테스트 코드를 진행해봤다.
테스트내용은 반복 10만, 조건1가지, 수행코드1가지다.
테스트내용은 반복 10만, 조건1가지, 수행코드1가지다.
private double testForloop(int type){
long t0 = nanoTime();
long elapsed = 0;
//int array
if(type==0){
int sum=0;
for(int i=0; i<100000; i++){
if(i%5==0){
sum+=i;
}
}
} else if(type==1){
List<String> result = new ArrayList<>();
for(int i=0; i<100000; i++){
if(i%5==0){
result.add("No." + String.valueOf(i));
}
}
}
elapsed = nanoTime() - t0;
double res = elapsed / Math.pow(10, 9);
return res;
}
/*-------------------------------------------------*/
private double testStream(int type){
long t0 = nanoTime();
long elapsed = 0;
if(type==0){
int sum = 0;
sum = IntStream.range(0,100000)
.filter(i->i%5==0)
.sum();
}else if(type==1){
List<String> result = new ArrayList<>();
result = IntStream.range(0,100000)
.filter(i->i%5==0)
.mapToObj(i -> "No." + String.valueOf(i))
.collect(Collectors.toList());
}
elapsed = nanoTime() - t0;
double res = elapsed / Math.pow(10, 9);
return res;
}
/*-------------------------------------------------*/
private double testParallelStream(int type){
long t0 = nanoTime();
long elapsed = 0;
if(type==0){
int sum = 0;
sum = IntStream.range(0,100000)
.parallel()
.filter(i->i%5==0)
.sum();
}else if(type==1){
List<String> result = new ArrayList<>();
result = IntStream.range(0,100000)
.parallel()
.filter(i->i%5==0)
.mapToObj(i -> "No." + String.valueOf(i))
.collect(Collectors.toList());
}
elapsed = nanoTime() - t0;
double res = elapsed / Math.pow(10, 9);
return res;
}
작업을 실행할때 primitive type인 int형 배열과, Object인 ArrayList를 가지고 거의 동일한 행동을 하도록 작성하였다. 그리고 다음과 같은 실행결과를 얻었다..
Array
for-loop / Stream / ParallelStream
0.006007506000000001/0.18573956100000014/0.04818631299999998
ArrayList
for-loop / Stream / ParallelStream
0.4064511349999998/0.596784643/0.3017301929999999
나름대로 의미가 있었다.
ParallelStream이 단일 Stream 보다는 확실히 성능이 좋았다.
array를 사용할때는 단순 for문이 월등하게 성능이 좋았고
arrayList를 사용할때는 ParallelStream이 좋았다.
array는 내부적으로 index 를 가지고 있기때문에 변환작업이 있는 Stream 보다는 성능이 나은것으로 보여진다. 그리고 Stream은 cpu 부하에 크게 부담을 주지 않는다면 가급적 병렬로 사용하는것이 좋을듯 하다.
ParallelStream이 단일 Stream 보다는 확실히 성능이 좋았다.
array를 사용할때는 단순 for문이 월등하게 성능이 좋았고
arrayList를 사용할때는 ParallelStream이 좋았다.
array는 내부적으로 index 를 가지고 있기때문에 변환작업이 있는 Stream 보다는 성능이 나은것으로 보여진다. 그리고 Stream은 cpu 부하에 크게 부담을 주지 않는다면 가급적 병렬로 사용하는것이 좋을듯 하다.
댓글
댓글 쓰기