Project/개발 업무

Actor에서 Class + OSAllocatedUnfairLock

lgvv 2025. 8. 6. 02:26

Actor에서 Class + OSAllocatedUnfairLock

 

상태 보호를 위해서 actor로 작성된 객체를 class + OSAllocatedUnfairLock으로 변경함.

 

변경하게 된 계기는 고빈도 호출 코드에서 고성능이 보장되어야 하는 영역을 프로파일링하는 과정에서 특정 메서드의 호출 빈도가 높아지는 경우 지속적으로 CPU Usage가 높게 잡히는 부분이 있어서 확인

 

Actor 기반으로 작성한 메서드를 실행했을 때

 

completeTaskWithClosure로 나타나는 영역에서 Weight를 많이 차지함.

 

 

해당 메서드의 구현부는 swiftlang / swift에 존재.

 

https://github.com/swiftlang/swift/blob/main/stdlib/public/Concurrency/Task.cpp

 

 

간단한 Task를 컨텍스트에 넣어서 클로저로부터 최종 반환을 처리하는 함수

 

/// The function that we put in the context of a simple task
/// to handle the final return from a closure.
SWIFT_CC(swiftasync)
static void completeTaskWithClosure(SWIFT_ASYNC_CONTEXT AsyncContext *context,
                                    SWIFT_CONTEXT SwiftError *error) {
  // 클로저 컨텍스트를 해제하기 위해, context 바로 앞에 붙어 있는 AsyncContextPrefix로 이동
  // context 주소에서 prefix 크기만큼 뺀 더 낮은 주소로 이동시킴
  auto asyncContextPrefix = reinterpret_cast<AsyncContextPrefix *>(
      reinterpret_cast<char *>(context) - sizeof(AsyncContextPrefix));

  // 클로저 컨텍스트를 메모리 해제
  swift_release((HeapObject *)asyncContextPrefix->closureContext);

  // Task의 정리 작업 후 Task 객체를 해제
  return completeTaskAndRelease(context, error);
}

 

Class + OSAllocatedUnfairLock으로 작성한 메서드를 실행했을 때

 

completeTaskWithClosure 영역이 사라져서 CPU Usage 감소

 

 

completeTaskWithClosure는 클로저 기반 task가 끝날 때 클로저 컨텍스트를 해제하는 정리 함수.

 

해당 심볼이 무겁게 잡혔다는 건 상태 접근마다 task가 생성되고 정리되고 있었다는 의미.

 

actor 격리 경계를 넘는 호출이 그때마다 비동기 task를 동반했기 때문으로 class + 동기 lock은 task 없이 곧바로 임계구역을 처리하므로 해당 비용을 줄일 수 있음.

 

정확하게 표현하자면 actor라 느림보다는 고빈도, 저경합 상태 접근에는 매번 task를 만드는 비동기 코드가 과하다는 의미.

 

반대로 경합이 크거나 접근 빈도가 낮다면 actor의 격리가 더 안전하고 단순하다고 생각할 수 있으나 actor는 외부에 await을 강제하고 class + lock으로 동시성 문제를 해결하는 것이 더 적절함.

 


(참고)

https://github.com/swiftlang/swift/blob/main/stdlib/public/Concurrency/Task.cpp

swift/stdlib/public/Concurrency/Task.cpp at main · swiftlang/swift