양봉수 블로그
  • Introduction
  • Cookie SameSite
  • Connection Reset
  • 자주쓰는 쉘스크립트 모음
  • Tshark
  • 네트워크프로그래밍
  • [Spring 개발 이슈모음]
    • issue1 ~ issue21
    • issue22 ~
  • [Java8 in Action]
    • Part1 기초
    • Part2-1 함수형 데이터 처리
    • Part2-2 함수형 데이터 처리
    • Part3-1 효과적인 자바8 프로그래밍
  • [Effective Java]
    • 객체의 생성과 삭제
    • 모든 객체의 공통 메서드
    • 클래스와 인터페이스
    • 제네릭
    • 열거형(enum)과 어노테이션
    • 람다와 스트림
    • 메서드
    • 일반적인 프로그래밍 원칙들
    • 예외
    • 병행성
    • 직렬화
  • 토비의 스프링3.1
    • 정의, IoC DI개념, Bean 라이프사이클
    • 오브젝트와 의존관계
    • 테스트
    • 템플릿
    • 예외
    • 서비스 추상화
    • AOP
    • 스프링 핵심 기술의 응용
    • 스프링 프로젝트 시작하기
    • IoC 컨테이너와 DI
  • [자바 성능 튜닝 이야기]
    • 내가 만든 프로그램의 속도를 알고 싶다
    • 왜 자꾸 String을 쓰지 말라는거야?
    • 어디에 담아야 하는지
    • 지금까지 사용하던 for 루프를 더 빠르게 할 수 있다고?
    • static 제대로 한번 써 보자
    • 클래스 정보 어떻게 알아낼 수 있나
    • 로그는 반드시 필요한 내용만 찍자
  • [대용량 아키텍처와 성능 튜닝]
    • 레퍼런스 아키텍처
    • 마이크로 서비스 아키텍처
    • REST의 이해와 설계
  • 켄트백 구현패턴
  • 클린코드
  • 클린코더스 강의 정리
  • 클린아키텍처
  • 네이버를 만든 기술, 자바편
  • 객체지향의 사실과 오해
  • 객체지향과 디자인패턴
  • 소프트웨어 품질관리(NHN은 이렇게한다)
  • 웹프로그래머를 위한 서블릿 컨테이너의 이해
  • 웹을 지탱하는 기술
  • 마이바티스를 사용한 자바 퍼시스턴스 개발
  • HashMap 효율적으로 사용하기
  • 자바의 정석
  • 슈퍼타입토큰(Super Type Token)
  • Singleton
  • Identity
  • Finalizer attack
  • Git Flow
  • nginx gzip 옵션
  • JUnit+Mockito vs Groovy+Spock
  • Apache and Tomcat
  • Understanding The Tomcat Classpath
  • 실용주의프로그래머 익스트림프로그래밍
  • 애자일적용후기
  • Living Documentation
  • specification by example
  • 확률과 통계
  • Multivariate Distributions
  • 가설검정
  • 단순회귀분석
Powered by GitBook
On this page
  • 반복 구문에서의 속도는?
  • 반복 구문에서의 필요 없는 반복

Was this helpful?

  1. [자바 성능 튜닝 이야기]

지금까지 사용하던 for 루프를 더 빠르게 할 수 있다고?

switch문은 JDK 6까지는 byte, short, char, int 이렇게 네 가지 타입을 사용한 조건 분기만 가능했지만, JDK 7부터는 String도 사용 가능하다. 일반적으로 if문에서 분기를 많이 하면 시간이 많이 소요된다고 생각한다. if문 조건 안에 들어가는 비교 구문에서 속도를 잡아먹지 않는한, if문장 자체에서는 그리 많은 시간이 소요되지 않는다.

반복 구문에서의 속도는?

JDK 5.0 이전에는 for 구문을 다음과 같이 사용하였다. 여기서 list는 값이 들어있는 ArrayList이다.

for (int loop = 0; loop < list.size(); loop++)

이렇게 코딩을 하는 습관은 좋지 않다. 매번 반복하면서 list.size() 메서드를 호출하기 때문이다. 이럴 때는 다음과 같이 수정해야 한다.

int listSize = list.size();
for (int loop = 0; loop < listSize; loop++)

이렇게 하면 필요 없는 size() 메서드 반복 호출이 없어지므로 더 빠르게 처리된다. JDK 5.0부터는 다음과 같이 for-each 를 사용할 수 있다.

ArrayList<String> list = new ArrayList<String>();
…
for (String str : list)
@State(Scope.Thread)
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public class ForLoop {
    int LOOP_COUNT = 100000;
    List<Integer> list;

    @Setup
    public void setUp() {
        list = new ArrayList<>(LOOP_COUNT);
        for (int loop = 0; loop < LOOP_COUNT; loop++) {
            list.add(loop);
        }
    }

    @Benchmark
    public void traditionalForLoop() {
        int listSize = list.size();
        for (int loop = 0; loop < listSize; loop++) {
            resultProcess(list.get(loop));
        }
    }

    @Benchmark
    public void traditionalSizeForLoop() {
        for (int loop = 0; loop < list.size(); loop++) {
            resultProcess(list.get(loop));
        }
    }

    @Benchmark
    public void timeForEachLoop() {
        for (Integer loop : list) {
            resultProcess(loop);
        }
    }

    @Benchmark
    public void timeForEachLoopJava8() {
        list.forEach(this::resultProcess);
    }


    int current;
    public void resultProcess(int result) {
        current = result;
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(ForLoop.class.getSimpleName())
                .warmupIterations(3)
                .measurementIterations(5)
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}
# Run complete. Total time: 00:00:33

Benchmark                       Mode  Cnt    Score    Error  Units
ForLoop.timeForEachLoop         avgt    5  159.759 ± 97.272  us/op
ForLoop.timeForEachLoopJava8    avgt    5  108.508 ± 11.156  us/op
ForLoop.traditionalForLoop      avgt    5  117.491 ± 15.692  us/op
ForLoop.traditionalSizeForLoop  avgt    5  131.080 ± 46.861  us/op

결과를 보면 자바8의 forEach를 사용한게 가장 빠른것으로 나오고, 일반 for-each가 가장 느리게 나온다. 그리고 for문 돌때마다 list.size() 메서드를 호출이 때문에 그렇지 않은것보다 약간 느리게 나왔다.

cf) 중괄호 안에서 아무런 작업을 하지 않거나, resultProcess() 메서드를 호출하지 않을 경우, 자바의 JIT(Just In Time) 컴파일러는최적화를 통해 해당 코드를 무시해 버릴 수도 있다. 그래서 메서드 호출을 하도록 했다.

반복 구문에서의 필요 없는 반복

가장 많은 실수 중 하나는 반복 구문에서 계속 필요 없는 메서드 호출을 하는 것이다. 다음 소스를 보자.

public void sample(DataVo data, String key) {
    TreeSet treeSet2 = null;
    treeSet2 = (TreeSet)data.get(key);
    if (treeSet2 != null) {
        for (int i=0; i< treeSet2.size(); i++) {
            DataVO2 data2 = (DataVO2)treeSet2.toArray()[i];
            ...
        }
    }
}

TreeSet 형태의 테이터를 갖고 있는 DataVO에서 TreeSet을 하나 추출하여 처리하는 부분이다. 이 소스의 문제는 toArray() 메서드를 반복해서 수행한다는 것이다. 참고로 sample 메서드는 애플리케이션이 한 번 호출되면 40번씩 수행된다. 또한 treeSet2 객체에 256개의 데이터들이 들어가 있으므로, 결과적으로 toArray() 메서드는 10,600번씩 반복 호출된다. 그러므로 이 코드는 toArray() 메서드가 반복되지 않도록 for 문 앞으로 옮기는 것이 좋다. 게다가 이 소스의 for문을 보면 treeSet2.size() 메서드를 지속적으로 호출하도록 되어 있다. 수정한 결과는 다음과 같다.

public void sample(DataVo data, String key) {
    TreeSet treeSet2 = null;
    treeSet2 = (TreeSet)data.get(key);
    if (treeSet2 != null) {
        DataVO2[] dataVO2 = (DataVO2)treeSet2.toArray();
        int treeSetSize = treeSet2.size();
        for (int i=0; i< treeSetSize; i++) {
            DataVO2 data2 = dataVO2[i];
            ...
        }
    }
}
Previous어디에 담아야 하는지Nextstatic 제대로 한번 써 보자

Last updated 5 years ago

Was this helpful?