반응형

java를 사용하여 배열 안의 원소 loop을 도는 많은 방법들에 대해 정리한다.

 

old school for loop

List<Student> studentList = new ArrayList<>();
studentList.add(a);
studentList.add(b);
studentList.add(c);

for (int i = 0; i < studentList.size(); i++) {
  System.out.println("Roll number: " + studentList.get(i).getRollNumber());
}

advanced for loop

for (Student st : studentList) {
   System.out.println("Roll number: " +  st.getRollNumber());
}

while, hasNext

Iterator<Object> it = list.iterator();
while (it.hasNext()) {
  Object o = it.next();
  // do stuff
}
  old for advanced for
since jdk1 jdk5
index increasing custom가능(2씩 증가 등); 역순 가능 무조건 1씩 증가만 가능; 역순 불가
index approach index 접근 가능 index 접근 불가
usage 어떠한 셀 수 있는 container object에 사용 가능 iterable interface를 구현한 구현체만 사용가능

위 세 방법 모두 성능상에 큰 차이는 없고, 굳이 따지자면 old for loop이 index의 객체를 탐사해야 하니(Object.get(i)) 조금 더 느릴 수 있다는 글이 있다. advance for loop으로 짜면 컴파일러가 while hasNext 문으로 변환할 거라 두 방법은 사실 거의 같은 거라고 볼 수 있다.

단순 1씩 증가한 loop이라면 advanced for loop을 사용하는 게 보기에도 더 좋을 듯하다.

라는 의견을 보았으나.. 믿기 힘든 상황이 되었다.(아래 테스트 참고)

 

Collection.foreach

List<Integer> list = Arrays.asList(1,2,3,4);
list.forEach(System.out::println);
// Output
1
2
3
4

Collection.stream.foreach

List<Integer> list = Arrays.asList(1,2,3,4);
list.stream().forEach(System.out::println);
// Output
1
2
3
4
  foreach stream.foreach
iterator collection iterator사용 collection을 stream으로 바꾸고 stream의 iterator 사용
order 순서 보장 순서 보장 않음
exception 구조 변화가 있으면 바로 exception 반환 나중에 exception반환
lock synchronized collection에 대한 작업이 중첩되면 우선 lock을 걸고 작업이 끝날 때 까지 기다림  lock없이 바로 접근

이 두 foreach의 성능을 비교하자면 stream.foreach가 스트림으로 변환 수 iterator를 사용하므로 더 느릴 것이라는 의견이 많았다.


benchmark test: jdk18, gradle7.4, jmh1.29, mac intel i7

그냥 글로 보기에는 와닿지 않아서 간단하게나마 벤치마크 테스트를 돌려본다.

아래 소스 외, 나머지는 기본 세팅으로 진행했다.

private static List<Integer> list = new ArrayList<>();
static {
    for(int i=0; i < 1_000_000; i++) {
        list.add(i);
    }
}

@Benchmark
@Fork(value = 1, warmups = 1)
@BenchmarkMode(Mode.Throughput)
public void usingStream(Blackhole blackhole) {
    list.stream().forEach(i -> blackhole.consume(i));
}

@Benchmark
@Fork(value = 1, warmups = 1)
@BenchmarkMode(Mode.Throughput)
public void usingIterator(Blackhole blackhole) {
    //list.listIterator().forEachRemaining(i -> blackhole.consume(i));
    list.forEach(i -> blackhole.consume(i));
}

@Benchmark
@Fork(value = 1, warmups = 1)
@BenchmarkMode(Mode.Throughput)
public void usingAdvancedForLoop(Blackhole blackhole) {
    for(Integer i : list) {
        blackhole.consume(i);
    }
}

@Benchmark
@Fork(value = 1, warmups = 1)
@BenchmarkMode(Mode.Throughput)
public void usingSimpleForLoop(Blackhole blackhole) {
    for(int i = 0; i < list.size() ; i++) {
        blackhole.consume(i);
    }
}

@Benchmark
@Fork(value = 1, warmups = 1)
@BenchmarkMode(Mode.Throughput)
public void usingWhileHasNext(Blackhole blackhole) {
    Iterator<Integer> itr = list.iterator();
    while(itr.hasNext()){
        blackhole.consume(itr.next());
    }
}

benchmark result

throughput mode(1초당 진행횟수)에서는 점수가 클수록 성능이 좋은 거다.

구관이 명관인가. (의외로) old for loop이 제일 성능이 좋았다.. 다른 것보다 거의 두배 정도?(정확히 두배라고 말할 순 없지만..) 그다음이 while문이라니.. 나머지는 비슷비슷한 것 같은데 어쨌건 꼴찌는 Collection.foreach였다..ㅋㅋㅋ 어느 글을 믿어야 하나,, 신기방기 하다.

 

jmh modes: http://hg.openjdk.java.net/code-tools/jmh/file/6cc1450c6a0f/jmh-core/src/main/java/org/openjdk/jmh/annotations/Mode.java

 

code-tools/jmh: 6cc1450c6a0f jmh-core/src/main/java/org/openjdk/jmh/annotations/Mode.java

view jmh-core/src/main/java/org/openjdk/jmh/annotations/Mode.java @ 929:6cc1450c6a0f profilers: perfasm, warn about low event count. author shade date Thu, 24 Jul 2014 00:21:17 +0400 parents 8d5845e7d89a children 0ca62574e95f line source /* * Copyright (c)

hg.openjdk.java.net

 

728x90
반응형

'개발 > java' 카테고리의 다른 글

[Date] java8 이하에서 날짜 timezone 변환  (0) 2022.11.04
[mac] oracle jdk -> open jdk java교체하기  (0) 2022.08.22
[jmh] benchmark test  (0) 2022.08.04
[powermock] mock static method  (0) 2022.07.21
[java] jvm, java, gc  (0) 2022.02.24

+ Recent posts