ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java Multithreading] 성능 최적화의 두 관점 - 처리량과 http 서버
    Java/Multi Threading 기초 2022. 10. 24. 17:48

    Java Multithreading

    멀티스레딩의 개념과 Java에서의 활용법을 공부하고 정리하는 시리즈입니다. (포스팅마다 번호 매기는 건 헷갈려서 그만둘게요...)

    처리량(throughput)

    지난 포스팅에서는 지연시간(latency)를 줄이기 위한 전략 중 하나로 태스크를 서브태스크로 나누는 방식을 소개했습니다. 그리고 거기에는 다음과 같은 오버헤드를 고려해야 했죠.

    • 태스크를 서브태스크로 쪼개는데 드는 비용
    • 스레드를 생성하는 비용
    • start()된 스레드가 스케줄링 되기 까지의 시간
    • 마지막 스레드가 작업을 끝내고 시그널을 보내기까지의 시간
    • 작업을 취합할 스레드가 시작하기까지의 시간
    • 서브태스크의 결과물을 하나로 취합하기 까지의 비용

    하지만 그것은 하나의 작업을 처리하는 시간(지연시간)을 줄이기 위한 해결 방법이었죠. 만약 단위 시간 당 얼마나 많은 처리를 할 수 있는지(처리량, throughput)를 성능의 기준으로 삼는다면 태스크를 서브태스크로 나누는 방식은 적절치 않습니다. 처리량을 늘리기 위한 방법으로는 각 작업마다 별도의 스레드로 병렬 실행하는 방식이 주로 사용됩니다.

    그렇게 되면 태스크를 나누지 않기 때문에 '태스크를 서브태스크로 쪼개는 비용'이나 '합치는 비용', '취합 작업을 진행할 스레드 비용'이 필요치 않게 됩니다. 또, 애초에 하나의 작업은 별개로 시작되고 끝나기 때문에 '다른 스레드가 작업이 끝날 때까지 대기하고 있는 시간'이 생길 이유도 없습니다. 또한 아래에 소개할 스레드풀링이나 넌블로킹큐와 같은 기술을 도입하여 '스레드 생성 비용'이나 'start() 된 스레드가 스케줄링되기를 기다리는 시간'에 대한 고려도 대폭 줄일 수 있습니다.

    앞에서 처리량은 단위 시간 당 완료되는 작업의 수를 말한다고 설명했습니다. 웹서버가 얼마나 많은 요청을 동시에 처리해서 응답을 줄 수 있을지를 항상 고민하는 웹개발자에게는 몹시 친숙한 이슈입니다. 즉, 웹 요청을 처리하는 데에 있어서는 '처리량'이 프로그램의 퍼포먼스를 측정하는 주요한 기준이 될 수 있습니다.

    스레드풀링(thread pooling)

    대개의 웹서버나 WAS에는 스레드풀링이라는 기술이 적용되어있습니다.
    스레드풀링이란 지정한 개수의 스레드를 스레드풀에 생성해두고 런타임 내내 재활용하는 것을 말합니다(모든 스레드풀이 스레드의 개수를 늘 고정적으로 유지하는 것은 아니지만 여기서는 쉬운 이해를 위하여 그렇게 이해해주세요). 유휴 스레드가 생기면 대기 큐에 대기중이던 작업 중 하나를 받아 실행합니다. 작업이 끝나면 응답을 반환하고 다시 유휴 상태로 전환됩니다. 만약 모든 스레드가 작업 중이면, 작업은 대기 큐에 머물면서 기다립니다. 이러한 방식으로 모든 스레드는 runnable 상태를 최대한 유지하게 됩니다.

    그래도 중요한 건

    아무리 스레드풀링이 처리량 최적화에 좋은 기술이라고는 하지만 그럼에도 가장 중요한 건 스레드의 개수를 정하는 것입니다.
    계속해서 설명해오고 있듯이 스레드의 적정 개수는 코어의 개수와 같습니다. 1에서부터 물리적인 코어의 개수까지 스레드의 성능은 N으로 증가하게 되고, 버추얼 코어의 개수까지는 증가폭이 미세하지만 그래도 성능이 개선됩니다. 만약 스레드풀에 가상 코어의 개수보다도 더 많은 스레드를 할당하게 되면 성능은 횡보하거나 오히려 떨어지기도 합니다.

    그리고 http 요청에 블로킹이 걸리는 작업이 있다면, 해당 작업의 지연시간을 고려하여 스레드 수를 재조정해야 합니다. 스레드의 수 == 코어의 수는 모든 스레드가 Runnable 상태를 유지하고 있다는 전제에서만 유효한 것이니까요.

    다음 포스팅에서 뵙겠습니다 🥰

    댓글

Designed by Tistory.