병렬 프로그래밍의 성능을 분석, 모니터링, 성능 향상 방법에 대한 내용.
성능 튜닝은 프로그램 내부 구조를 복잡하게 할 수 있음.
일단 정상적으로 동작하도록 만든 후, 튜닝 하는 것이 좋다.
11.1 성능에 대해
- 성능을 높인다는 것은 더 적은 자원을 사용하면서 더 많은 일을 하도록 만드는 것이다.
- 여러 개의 스레드를 사용하려 한다면 항상 단일 스레드를 사용할 때보다 성능상의 비용을 지불해야만 한다.
스레드간의 작업 내용 조율(락, 신호 보내기, 메모리 동기화)
- 컨텍스트 스위칭
스레드를 생성하거나 제거하는 일
- 스레드의 효율적인 스케줄링
11.1.1 성능 대 확장성
애플리케이션 성능을 측정하는 데이터
- 서비스 시간, 대기 시간, 처리량, 효율성, 확장성, 용량
단일 티어 애플리케이션이 다중 티어 애플리케이션 보다 성능이 나을 가능성이 많다. 하지만 단일 티어 애플리케이션이 처리할 수 있는 부하를 넘어서는 작업량이 주어지면 문제가 심각해진다.
따라서 서버 애플리케이션을 만들 때는 얼마나 빠르게 보다는, 얼마나 많이 라는 측면을 훨씬 중요하게 생각하는 경우가 많다.
11.1.2 성능 트레이드 오프 측정
최적화 기법을 너무 이른 시점에 적용하지 말아야 한다. 일단 제대로 동작하게 만들고 난 다음에 빠르게 동작하도록 최적화해야 하며, 예상한 것보다 심각하게 성능이 떨어지는 경우에만 최적화 기법을 적용하는 것으로도 충분하다.
11.2 암달의 법칙
암달의 법칙(Amdahl’s law)을 사용하면 병렬 작업과 순차 작업의 비율에 따라 하드웨어 자원을 추가로 투입했을 때 이론적으로 속도가 얼마나 빨라질지에 대한 예측 값을 얻을 수 있다.
속도 증가량 <= 1 / ( F + (1-F) / N )
F : 순차적으로 실행돼야 하는 작업의 비율(0~1)
N : 하드웨어에 꽂혀 있는 프로세서의 개수
- N이 무한대일 경우, 최대 속도 증가량은 1/F
순차 실행해야 하는 부분이 50%일 경우, F=0.5 가 되고 속도 증가량은 1/0.5=2
최대 2배까지만 빠르게 할 수 있다.
- 하드웨어에 CPU가 10개 꽂혀 있을 때, 10% 의 순차 작업을 갖고 있는 프로그램은 최고 5.3배 만큼의 속도가 증가 할 수 있다.
1 / ( 0.1 + ( 1 – 0.1) / 10) = 5.26….
암달의 법칙을 적용해보려면 애플리케이션 내부에서 순차적으로 처리해야 하는 작업이 얼마나 되는지 확인해야 한다.
11.2.1 예제 : 프레임웍 내부에 감춰져 있는 순차적 실행 구조
동기화 처리한 synchronizedLinkedList보다 ConcurrentLinkedQueue가 성능이 더 좋다.
11.3 스레드와 비용
11.3.1 컨텍스트 스위칭
컨텍스트 스위칭이란 스레드 스케줄링에 의해 다음 스레드가 실행되야 할 때 현재 스레드의 실행 상태를 보관해 두고, 다음 스레드의 실행 상태를 읽어들이는 것을 말한다.
유닉스 시스템의 vmstat 명령이나 윈도우 시스템의 perfmon 유틸리티를 사용하면 컨텍스트 스위칭이 일어난 횟수를 확인할 수 있으며 커널 수준에서 얼마만큼의 시간을 소모했는지 확인할 수 있다.
커널 활용도가 10%가 넘는 높은 값을 갖고 있다면 스케줄링에 부하가 걸린다는 의미이며, 애플리케이션 내부에서 I/O 작업이나 락 관련 동기화 부분에서 대기 상태에 들어가는 부분이 원인일 가능성이 높다.
11.3.2 메모리 동기화
- 메모리 배리어 : 캐시 플러시하거나 캐시 무효화
- 락 생략(lock elision)
다음 코드에 대해서 정교하게 만들어진 JVM이라면 4번이나 락을 잡았다 놓는것 대신 한번에 빠르게 실행된다.
public String getStoogeNames() { List<String> stooges = new Vector<String>(); stooges.add("Moe"); stooges.add("Larry"); stooges.add("Curly"); return stooges.toString(); }
굉장히 잘 만들어진 컴파일러라면 getStoogeNames 메소드가 항상 같은 값을 리턴하는 것을 파악한 후 매 실행시마다 처음 실행결과를 리턴하도록 재 컴파일하기도 한다.
- 락 확장(lock coarsening)
연달아 붙어 잇는 여러 개의 synchronized 블록을 하나의 락으로 묶는 방법
11.3.3 블로킹
경쟁 조건이 발생하는 동기화 작업에는 운영체제가 관여하는데 해당 부분은 일정량의 자원을 소모한다. 락을 확보하지 못한 스레드는 대기상태에 들어가도록 하는데 JVM은 두 가지 방법을 사용한다.
- 스핀 대기(spin waiting) : 락을 확보할 때까지 계속해서 재시도하는 방법
- 운영체제가 제공하는 기능을 사용해 스레드를 실제 대기 상태로 두는 방법
대기 시간이 짧을 때는 스핀 대기가 효과적이고, 길 때는 운영체제의 기능을 호출하는게 효율적이다.
대부분의 경우 운영체제의 기능을 호출하는 방법이 사용됌.
11.4 락 경쟁 줄이기
락으로 사용 제한이 걸려 있는 독점적인 자원을 사용하려는 모든 스레드는 해당 자원을 한 번에 한 스레드만 사용할 수 있기 때문에 순차적으로 처리될 수 밖에 없다.
(병렬 애플리케이션에서 확장성에 가장 큰 위협이 되는 존재는 바로 특정 자원을 독점적으로 사용하도록 제한하는 락이다.)
락 경쟁 조건을 줄일 수 있는 방법
- 락을 확보한 채로 유지되는 시간을 최대한 줄인다.
- 락을 확보하고자 요청하는 횟수를 최대한 줄인다.
- 독점적인 락 대신 병렬성을 크게 높여주는 여러 가지 조율 방법을 사용한다.
11.4.1 락 구역 좁히기
락이 꼭 필요하지 않은 코드를 synchronized 블록 밖으로 뽑아냄.
11.4.2 락 정밀도 높이기
다수의 락을 사용해 각 객체 별로 필요한 만큼만 락을 확보하도록 하면 스레드 간의 경쟁을 크게 줄일 수 있다.
=> 경쟁이 줄어들면 락을 확보하려고 대기하는 경우도 줄어들기 때문에 애플리케이션의 확장성이 늘어날 것이라고 예상할 수 있다.
- 락 분할 : 모든 메소드를 하나의 단일 락으로 동기화하는 대신 락을 분할한다.
11.4.3 락 스트라이핑(lock striping)
독립적인 객체를 여러 가지 크기의 단위로 묶어내고, 묶인 블록 단위로 락을 나누는 방법을 말한다. 예를 들어 ConcurrentHashMap 클래스는 16개의 락을 배열로 담은후 16개의 락 각자가 전체 해시 범위의 1/16에 대한 락을 담당한다. 따라서 락 경쟁이 발생할 확률을 1/16로 낮춰준다.
11.4.4 핫 필드 최소화
- 핫 필드 : 모든 연산을 수행할 때마다 한 번씩 사용해야 하는 카운터 변수와 같은 부분
자주 계산하고 사용하는 값을 캐시에 저장하도록 최적화하면 확장성을 떨어뜨릴 수 밖에 없는 핫 필드(hot fields)가 나타난다.
HashMap 에서 size 확인 시 매번 계산하는 대신 따로 저장해둘 수 있지만,
데이터를 넣고 뺄 때마다 매번 사이즈 값을 변경해야 한다면 멀티스레드 환경에서는 해당 값을 변경하느라 경쟁이 발생하게 된다.
=> 성능 최적화를 하려했으나 확장성이 떨어지게 됌.
ConcurrentHashMap 은 락으로 분할된 부분마다 카운트 변수를 두어 size 메소드 호출시에는 모든 카운트 변수를 더해 돌려준다.
11.4.5 독점적인 락을 최소화하는 다른 방법
병렬성이 높은 클래스를 공유 객체로 사용하는 방법
- ReadWriteLock
- Atomic 클래스(CPU에서 제공하는 저수준 병렬 처리 기능 활용)
11.4.6 CPU 활용도 모니터링
애플리케이션의 확장성을 테스트할 때 그 목적은 대부분 CPU를 최대한 활용하는 데 있다.
11.4.7 객체 풀링은 하지 말자
- 최근의 자바 프로그램은 메모리를 할당하는 작업이 C 언어의 malloc 함수보다 빨라졌다.
- 병렬 애플리케이션에서 객체 풀링을 사용했을 경우에는 해당 풀에서 객체를 얻기 위한 경쟁이 발생하게 된다.
- 스레드가 대기상태에 들어가는 비용때문에 새로운 객체를 생성하는게 성능상 좋다.
11.5 예제 : Map 객체의 성능 분석
11.6 컨텍스트 스위치 부하 줄이기
로그 출력 시 출력 시 마다 println을 호출하는 것보다, 로그 출력 전담 백그라운 스레드를 사용하는 것이 좋다.
'개발 > 병렬 프로그래밍' 카테고리의 다른 글
자바 병렬 프로그래밍 - 15장 단일 연산 변수와 넌블로킹 동기화 (0) | 2016.09.08 |
---|---|
자바 병렬 프로그래밍 - 13장 명시적인 락 (0) | 2016.09.07 |
자바 병렬 프로그래밍 - 10장 활동성 최대로 높이기 (0) | 2016.09.01 |
자바 병렬 프로그래밍 - 8장 스레드 풀 활용 (0) | 2016.08.30 |
자바 병렬 프로그래밍 - 7장 중단 및 종료 (0) | 2016.08.29 |