성능, 확장성
성능
성능을 높인다는 것은 적은 자원을 사용하면서 더 많은 작업을 하도록 한다는 것이다.
단일 스레드가 아니라 여러 스레드를 사용하는 경우 스레드 간의 작업을 조율하거나 컨텍스트 스위칭이 발생하는 등 성능 상의 비용이 발생한다.
하지만 여러 스레드를 효율적으로 잘 활용한다면 성능, 응답성을 높이고 처리량도 늘릴 수 있다.
보통 성능을 고려할 때 확장성과 처리량과 용량이라는 세 가지 측면을 중요시한다.
단일 구조의 애플리케이션에 비해 여러 계층으로 나뉜 애플리케이션이 티어 간 통신으로 인해 자원을 더 많이 사용할 수 있다.
하지만 단일 구조 애플리케이션이 처리할 수 있는 양보다 훨씬 많은 요청이 들어오는 경우, 시스템 자원을 더 많이 사용하더라도 많은 처리량을 감당할 수 있어야 한다.
일단 제대로 동작하게 만들고 난 다음 빠르게 동작하도록 최적화해야 한다. 예상한 것보다 심각하게 성능이 떨어지는 경우에만 최적화를 하는 것으로 충분하다.
좀 더 최적화 되어있는 코드는 보통 복잡하기 때문에 코드의 가독성과 유지보수의 용이성이 떨어진다.
"빠르다"의 정의를 내리고, 어떤 조건일 경우에 이 방식이 빠른지, 얼마나 많이 해당 조건이 발생하는 건지, 개발 비용이나 유지보수 비용이 얼마나 증가하며 그를 감수할 만 한지 고민해봐야 한다.
성능 튜닝 시에는 항상 성능 목표에 대한 명확한 요구사항이 있어야 하며 실제 환경과 유사한 성능 측정 도구가 필요하다.
암달의 법칙
병렬 작업과 순차 작업의 비율에 따라 하드웨어 자원을 추가 투입했을 때 얼마나 속도가 증가하는지 예측할 수 있다.
순차적으로 실행되어야 하는 작업의 비율이 F이고 프로세서(코어) 개수가 N일 경우,
1 / F + ((1 - F) / N)정도까지 속도를 증가시킬 수 있다.단순히 계산하면 N이 무한대로 증가할 수 있을 때 속도 증가량은 최대 1/F까지 증가한다. 예를 들어 F가 0.1인 경우에는 속도가 10배 증가할 수 있다는 의미이다.
프로세서가 계속해서 증가한다 해도 처리량이 계속해서 늘어나는 것이 아니라 오히려 떨어지거나 일정 수치 이상 오르지 않을 수 있다. 따라서 이 법칙을 통해 적절한 프로세서 개수를 정해야 한다.
작업을 멀티 스레드로 병렬 처리하는 경우, 큐에 있는 작업을 꺼낼 때와 여러 작업 결과를 합칠 때 순차적으로 처리하게 된다.
스레드와 비용
컨텍스트 스위칭
CPU 개수보다 많은 양의 스레드를 사용하는 경우, 스레드가 돌아가면서 실행되기 때문에 컨텍스트 스위칭이 발생한다.
컨텍스트 스위칭으로 인해 스레드가 사용하던 데이터가 프로세스의 캐시 메모리에서 사라지면 다음에 다시 로드해야 하기 때문에 처리 속도가 느려질 수 있다.
락을 비롯한 리소스를 획득하기 위해 대기 상태에 들어가는 요청이 많은 경우 컨텍스트 스위칭 횟수와 스케줄링 부하가 늘어나면서 전체적인 처리량이 줄어든다.
메모리 동기화
메모리 배리어는 캐시를 플러시하거나 무효화하고, 하드웨어와 관련된 쓰기 버퍼를 플러시하고, 실행 파이프라인을 늦출 수 있는 명령어이다. 자바에서는 synchronized, volatile 키워드를 통해 해당 기능을 이용할 수 있다.
메모리 배리어를 사용하면 컴파일러가 제공하는 최적화 기법을 제대로 사용하지 못해 성능 저하가 발생할 수 있다.
JVM은 락을 거는 대상 객체가 특정 스레드 내에 한정되어 있다면 다른 스레드와의 경쟁 조건이 발생할 수 없기 때문에 락을 걸지 않는 등 최적화를 하기도 한다.
또한 연달아 붙어 있는 락을 하나로 묶는 락 확장 방식을 사용해 락을 확보하고 해제하는 시간을 줄이기도 한다.
락 경쟁 줄이기
락을 점유하는 동안 수행하는 작업을 최소화해야 한다.
락 분할
락이 두 개 이상의 독립적인 상태 변수를 한 번에 묶어 동기화하고 있다면, 각각의 락으로 동기화하도록 분할하여 확장성을 높일 수 있다.
락 스트라이핑
독립적인 객체를 여러 크기의 단위로 묶고 락을 나누는 방법을 통해 락 경쟁이 발생할 확률을 줄이고 병렬성을 높일 수 있다.
모든 연산을 수행할 때 마다 한 번씩 접근해야 하는 카운터 변수와 같은 필드를 핫 필드라고 칭하는데, 이 필드에 대해 여러 스레드가 접근해야 하는 경우 병목이 될 수 있다.
따라서 이러한 핫 필드를 가급적 줄이고, 줄이는게 어렵다면 단일 연산 변수(AtomicXXX) 타입을 사용하는 것이 좋다. ConcurrentHashMap의 경우, 전체 카운트를 하나의 변수에 두지 않고 락으로 분배된 각 부분마다 카운터 변수를 둔 후, 전체 카운터가 필요할 때 변수들의 합을 구해 사용한다.
CPU 모니터링
vmstat, mpstat 등의 도구를 사용해 CPU 활용도를 모니터링하면서 충분히 활용하고 있는지 확인해야 한다.
CPU를 충분히 활용하고 있지 않다면, 부하가 충분히 가해지지 않았거나 I/O 또는 외부 작업으로 인한 제약이 있거나 락 경쟁이 발생했을 수 있다.
Last updated