Skip to main content

12. 동시 성능 기법

병렬성이란?#

  • 멀티코어에서는 암달의 법칙이 연산 태스크의 실행 속도를 향상시키는 핵심 요소입니다.
  • T(n) = S + (1/N) * (T - S)

자바 동시성 기초#

  • 가장 기본은 동시성 특유의 반직관적인 모습을 이해하는 것입니다.
  • 동기화를 사용할 때는 아주 신중하게 설계하고 미리 잘 따져봐야합니다. 잘못 사용되면 느려집니다.

JMM의 이해#

  • JMM : 자바 메모리 모델
  • JMM은 다음 질문에 답을 찾는 모델입니다.
    • 두 코어가 같은 데이터를 액세스하면 어떻게 되는지
    • 언제 두 코어가같은 데이터를 바라본다고 장담할 수 있는지
    • 메모리 캐시는 두 질문의 답에 어떤 영향을 미치는지

JMM 같은 메모리 모델은 두 가지 방식으로 접근합니다.

  • 강한 메모리 모델
    • 전체 코어가 항상 같은 값을 바라봅니다.
  • 약한 메모리 모델
    • 코어마다 다른 값을 바라볼 수 있고 그 시점을 제어하는 특별한 캐시 규칙이 있습니다.

자바 명세서는 JVM 구현체가 반드시 지켜야할 규칙들이 있습니다.


동시성 라이브러리 구축#

동시 라이브러리를 구성하는 핵심 요소는 다음의 일반 카테고리로 분류됩니다.

  • 락, 세마포어
  • 아토믹스
  • 블로킹 큐
  • 래치
  • 실행자

Unsafe#

Unsafe로 할 수 있는 일은 다음과 같습니다.

  • 객체는 할당하지만 생성자는 실행하지 않습니다.
  • 원메모리에 액세스하고 포인터 수준의 연산을 수행합니다.
  • 프로세서별 하드웨어 특성(CAS)를 이용합니다.

이를 통해서 고수준의 프레임워크 기능을 구현할 수 있습니다.

  • 신속한 역직렬화
  • 스레드에 안전한 네이티브 메모리 액세스
  • 아토믹 메모리 연산
  • 효율적인 객체/메모리 레이아웃
  • 커스텀 메모리 펜스
  • 네이티브 코드와의 신속한 상호작용
  • JNI에 관한 다중 운영체제 대체물
  • 배열 원소에 volatile 하게 액세스

아토믹스와 CAS#

  • 변수를 업데이트하기 위해 여러 차례 재시도하는 경우, 횟수만큼 성능이 안좋아집니다.

락과 스핀락#

  • 블로킹된 스레드를 CPU에 활성 상태로 놔두고 아무 일도 시키지 않은 채 락을 얻을때까지 재시도하는 것을 스핀락이라고 합니다.

스핀락에 대한 핵심 개념은 다음과 같습니다.

  • 테스트하고 세팅하는 작업은 반드시 아토믹해야합니다.
  • 스핀락에 경합이 발생하면 대기중인 프로세서는 빽빽한 루프를 실행합니다.

동시 라이브러리 정리#

java.util.concurrent#

  • lock() : 기존 방식대로 락을 획득하고 사용할 수 있을때까지 블로킹합니다.
  • newCondition() : 락 주위 조건을 설정해 좀 더 유연하게 락을 활용합니다.
  • tryLock() : 락 획득을 위해 시도합니다.
  • unlock() : 락을 해제합니다

읽기/쓰기 락#

  • 읽기, 쓰기 작업 횟수가 많이 차이나므로

세마 포어#

  • 마포어는 풀 스레드나 DB 접속 객체 등 여러 리소스의 액세스를 허용하는 독특한 기술을 제공

동시 컬렉션#

  • ConcurrentHashMap 등의 동시 컬렉션을 사용하면 좋습니다.

래치와 배리어#

  • 쓰레드 세트의 실행을 제어하는 유용한 기법입니다.

실행자와 태스크 추상화#

  • 일반적으로 저수준의 스레드 문제를 직접 처리하는 것 보다는 java.util.concurrent 패키지에서 적절한 추상화 동시 프로그래밍 지원 기능을 쓰는 것이 좋습니다.

비동기 실행#

  • Executors는 헬퍼 클래스로, 선택한 로직에 따라서 서비스 및 기반 스레드 풀을 생성하는 new* 팩토리 메서드 시리즈를 제공합니다. 보통 이 팩토리 메서드로 실행자 메서드를 생성합니다.

ExecutorService 선택하기#

  • 올바른 ExecutorService를 선택하면 비동기 프로세스를 적절히 잘 제어할 수 있고, 풀 스레드 개수를 정확히 잘 정하면 성능이 뚜렷이 향상될 수 있습니다

포크/조인#

  • 하위 분할 테스크를 효율적으로 처리합니다.
  • 작업 빼앗기 알고리즘을 구현합니다.

최신 자바 동시성#

  • 자바 동시성은 실행 시간이 긴 블로킹 캐스크를 다른 스레드와 함께 실행할 수 있는 환경을 염두하고 설계되었습니다.
  • CPU 리소스를 효율적으로 사용하는 문제가 더욱 중요하게 부각됍니다.
  • 현대 자바는 언어 및 표준 라이브러리에 내장된 추상화를 이용해 성능을 크게 높일 수 있는 환경을 제공합니다.

스트림과 병렬 스트림#

  • 자바 8의 가장 큰 변경 사항

락-프리 기법#

  • 블로킹이 처리율에 악영향을 미치고성능을 저하시킬 수 있다는 전제하에서 시작합니다.
  • CPU 코어를 차지하는 것은 사용률, 전력 소비 측면에서 비용이 듭니다.

액터 기반 기법#

  • 태스크를 스레드 하나보다 더 작게 나타내려는 다양한 접근 방식이 고안되었습니다.
  • 액터는 그 자체로 고유한 상태와 로직을 갖고 있습니다. 동시에 다른 액터와 소통하는 메일박스 체계를 갖춘, 작고 독립적인 처리 단위입니다.
  • 액터는 병렬 시스템 내부에서 하나의 네트워크를 형성하고 그 속에서 각자 나름대로 작업을 수행함으로써 하부 동시 모델을 완전히 추상화한 모습을 바라봅니다

일반적으로 락킹 체제보다 아카를 쓰는 것이 다음의 장점이 있습니다.

  • 도메인 모델 내에서 가변 상태를 캡슐화하는 건 의외로 까다로운 일입니다. 특히, 객체 내부 요소를 가리키는 레퍼런스가 언제라도 제어권 밖으로 벗어날 수 있기 때문에 더욱더 그렇습니다.
  • 상태를 락으로 보호하면 처리율이 크게 떨어질 수 있습니다.
  • 락을 쓰면 데드락을 비롯한 별별 문제가 유발될 수 있습니다.

마치며.#

  • 싱글 스레드 애플리케이션을 동시성 기반의 설계 방식으로 전환할 때는 다음을 고려합니다.
    • 순서대로 죽 처리하는 성능을 정확히 측정할 수 있어야 합니다.
    • 변경을 적용한 다음 진짜 성능이 향상됐는지 테스트해야합 니다.
    • 성능 테스트는 재실행하기 쉬워야 합니다. 특히, 시스템이 처리하는 데이터 크기가 달라질 가능성이 큰 경우 그렇습니다.
  • 다음과 같은 유혹은 피해야합니다.
    • 병렬 스트림을 곳곳에 갖다쓴다.
    • 수동으로 락킹하는 복잡한 자료 구조를 생성한다.
    • java.util.concurrent에 이미 있는 구조를 다시 만든다.
  • 다음과 같이 목표를 정해야합니다.
    • 동시 컬렉션을 이용해 스레드 핫 성능을 높입니다.
    • 하부 자료 구조를 최대한 활용할 수 있는 형태로 액세스를 설계합니다.
    • 애플리케이션 전반에 걸쳐 락킹을 줄입니다.
    • 가급적 스레드를 직접 처리하지 않도록 태스크/비동기를 적절히 추상화합니다.
Last updated on