ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • spin lock은 cpu에 얼마나 부하를 줄까?
    성능테스트 2024. 10. 7. 01:00

    대상 독자

    spin lock에 대해서 알고 싶으신 분들

     

    spin lock이 실제 cpu에 얼마나 부하를 주는지 궁금하신 분들

     

     

    개요

    소프트웨어를 개발하거나, 외부 라이브러리들을 활용하다 보면 종종 spin lock으로 구현되어 있는 경우를 발견할 수 있습니다.

     

    이런 경우 while 등의 반복문을 활용해서 매번 체크하는 로직이 포함되는데 spin lock은 cpu에 부하를 주지 않을까? 라는 궁금즘이 생겼습니다.

     

    만약 spin lock 방식을 소프트웨어에 활용하면 cpu에 얼마나 부하를 줄게 될까요? 

     

     

    Spin Lock이란 무엇인가?

    fun getLock() {
        while(true){
            if(락 획득) return
            else 반복하면서 락 획득 시도
        }
    }

     

    spin lock은 임계영역에 들어가기 위해 Lock을 획득해야 하는데, 락을 얻을 때까지 무한 루프를 돌며 확인하는 방법입니다.

     

    스레드가 락을 얻는 과정에서 빙빙 돌고 있다(spinning)라는 것을 의미합니다.

     

    이 과정에서 반복적으로 체크를 하기 때문에 락을 획득하는 외부 시스템에도 부하가 갈 수도 있고, CPU는 지속적으로 일을 하게 되어 다른 일을 할 수 없게 됩니다.

     

    spin lock으로 while 문으로 체크하게 되면 CPU는 얼마큼의 부하를 받게 될까요?

     

    Spin Lock CPU 부하 테스트를 위한 설정 및 코드

    Spring Actuator, Prometheus 활용하여 while문 동작을 동작시키면서 CPU의 리소스를 얼마나 점유하는지 확인해보려고 합니다.

     

    gradle 의존성 추가

    implementation ("org.springframework.boot:spring-boot-starter-actuator")
    runtimeOnly ("io.micrometer:micrometer-registry-prometheus")

     

    actuator와 prometheus 의존성을 추가합니다.

     

     

    application.yml 설정 추가

    management:
      endpoints:
        web:
          exposure:
            include: "*"

     

    이후 서버를 구동하고 http://localhost:8080/actuator/prometheus 로 접근하면 cpu에 대한 지표를 확인해 볼 수 있습니다.

     

    많은 metric은 불필요하니 http://localhost:8080/actuator/metrics/process.cpu.usage 로 접근하면 현재 JVM에서 활용하는 CPU의 사용량만 확인할 수 있습니다.

     

     

    현재 0.014% 만큼만 사용하고 있네요

     

     

    spin lock 유사 코드 작성

    @RestController
    class TestController {
    
        val threadPool: ThreadPoolExecutor = Executors.newFixedThreadPool(50) as ThreadPoolExecutor
        val spinLock = SpinLock()
    
        @PostMapping("/spin-lock-test")
        fun runSpinLock(){
            threadPool.submit {
                spinLock.doSomething()
            }
            println("current Active ThreadPool = ${threadPool.activeCount}")
        }
    }
    
    class SpinLock {
    
        var sum = 0
    
        fun doSomething() {
            while(true){
                sum++
            }
        }
    }

     

    50개의 스레드풀을 구성해 두었고 /spin-lock-test 엔드포인트로 http 요청을 받게 되면 사용가능한 스레드에서 더하기 연산을 하는 while문을 구성해 보았습니다.

     

    실제 호출하면서 CPU 지표 확인해 보기

    postman을 활용하여 /spin-lock/test 로 호출해 보니 current Active ThreadPool은 1개로 나오며 내부적으로 while문이 돌아가고 있게 됩니다.

     

    이제 호출을 늘려가면서 CPU 지표가 어떻게 달라지는지 확인해 보겠습니다.

     

    사용되는 스레드풀의 개수 / CPU 활용률 평균

    0개 / 1.4% 

    1개 / 8% 

    2개 / 16% 활용

    3개 / 25% 활용

    4개 / 31% 활용

    ...

    10개 / 78% 활용

    12개 / 82% 활용

    20개 / 82% 활용

     

    이로써 spin lock을 활용하면서 스레드 개수가 많아짐에 따라 CPU 부하가 증가함을 확인할 수 있었습니다.

     

    특이한 점으로는 12개 까지는 CPU 활용률이 점점 늘어나지만 20개도 동일한 수치를 보여줍니다.

     

    왜 12개 까지는 증가할까요?

     

     

    system.cpu.count 지표를 통하여 JVM에서 사용가능한 프로세서의 개수를 확인할 수 있습니다.

     

    컴퓨터마다 다를 수 있겠지만 현재기준으로 사용가능한 cpu의 개수를 살펴보면 12개이므로 스레드의 개수가 20개여도 CPU는 12개만큼의 일만 할 수 있습니다.

     

    스레드의 개수가 CPU의 개수보다 높아져 context switching이 발생할 수 있지만 context switching으로 인한 비용이 발생할 뿐 이 비용이 추가적인 CPU 사용률을 높이진 않습니다.

     

    결론

    적은 스레드에서 일시적으로 spin lock을 수행하는 것은 괜찮을 수 있으나, 수많은 스레드에서 spin lock 방식이 활용되면 사용가능한 cpu를 점유하게 되고 성능저하가 발생할 수 있습니다.

     

    이런 점을 고려하여 부분적으로는 사용한다면 spin lock 방식의 로직도 문제를 해결하는 하나의 방법이 될 수 있을 것 같습니다.

     

     

     

    참고자료

    https://ko.wikipedia.org/wiki/%EC%8A%A4%ED%95%80%EB%9D%BD

    https://junuuu.tistory.com/968

     

    댓글

Designed by Tistory.