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());
}
}
throughput mode(1초당 진행횟수)에서는 점수가 클수록 성능이 좋은 거다.
구관이 명관인가. (의외로) old for loop이 제일 성능이 좋았다.. 다른 것보다 거의 두배 정도?(정확히 두배라고 말할 순 없지만..) 그다음이 while문이라니.. 나머지는 비슷비슷한 것 같은데 어쨌건 꼴찌는 Collection.foreach였다..ㅋㅋㅋ 어느 글을 믿어야 하나,, 신기방기 하다.
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
'개발 > 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 |