안녕하세요! 

 

 

최근 Swift concurrency, actor, Sendable을 주제로 글을 작성했었는데요

 

 

오늘은 Actor를 실제로 활용한 사례를 통해 어떤 문제를 해결하였고 성능적 이점으로는 어떤 것이 있는지 알아보겠습니다.

 

 

저는 SimpleImageProvider라는 라이브러리를 개발하였습니다.

 

 

해당 라이브러리는 캐싱기능을 지원하는데요, 캐싱 여부와 캐싱데이터를 불러오는 기능이 존재합니다.

 

 

하나의 객체로 여러 이미지 요청이 동시에 들어오는 상황에서 해당 기능은 동시성 문제를 야기할 수 있었습니다.

 

 

따라서 저는 GCD를 활용하여 해당 문제를 해결하였습니다.

 

 

아래코드와 같이 write, remove작업이 진행되는 경우 barrier플래그를 통해 동시성 큐이지만 직렬성을 보장받도록 설계하였습니다.

베리어 플래그를 통한 직렬화

 

해당 코드는 대부분의 경우 문제없이 동작 하였지만, 이상하게도 가끔 데드락이 걸려 이미지가 로딩되지 못하는 문제가 발생했습니다.

 

 

디버깅을 통해 계속해서 알아내본 결과 동시성 큐에 sync를 통해 작업을 등록하는 과정에서 데드락이 발생하였습니다.

 

 

처음에는 왜 이런 문제가 발생했는지 알 수 없었습니다.

 

 

하지만, 이유는 생각보다 명확했습니다. 

 

 

저는 아래글에서 GCD와 Swift concurrency를 다루었는데요, 해당 글에 GCD사용시 주의할 점에 제가 작성된 글을 확인해 보았습니다.

 

[Swift] GCD와 Swift concurrency에 대해

안녕하세요 비동기 프로그래밍에 대해 공부한 내역을 정리해 보려고 작성한 포스팅입니다. 기본적으로 iOS프로그래밍은 2가지 비동기 처리방식을 제공합니다. 바로 GCD와 비교적 최근에 도입

ios-adventure-with-aphelios.tistory.com

 

 

이유는 바로 동기 작업을 등록하는 쓰레드와 동기작업이 실행되는 쓰레드가 동일하여 발생한 문제였습니다.

 

 

두 쓰레드가 상호 종료를 기다리는 상황이기에 데드락이 발생하는 것이였습니다.

 

 

왜 몰랐을까?

 

글까지 정리하며 문제 상황에 대한 사전 개념이 있던 제가 왜 이문제를 발견해내지 못했을까요?

 

 

왜냐하면 Swift concurrency와 GCD를 섞어서 사용했기 때문입니다.

 

 

따라서 미처 Task의 쓰레드와 GCD의 쓰레드가 공유될 수 있다는 사실을 간과하였습니다.

 

 

어떻게 해결했을까?

결론 적으로 두 시스템의 쓰레드 관리 방법이 다소 상이하여 개발 시 복잡성이 증가했다고 판단하였습니다.

 

 

따라서 애플에서 공식적으로 지원하는 Task를 위한 공유 자원 관리 도구인 Actor를 사용하는 것이 바람직 하다고 판단했습니다.

 

 

따라서 공유 자원을 가지는 모든 객체의 DispatchQueue를 제거하고 해당 객체를 Actor로 변경했습니다.

 

 

Actor는 사실 class라 간단한 예약어와 몇몇 await키워드를 추가함으로 빠른 변경이 가능했습니다.

 

 

이론적으로 이제 동시성 오류는 발생할 수 없습니다, 몇 십번의 실행을 하여도 더이상 데드락이 발생하지 않는다는 것을 확인할 수 있었습니다.

 

 

참고로 저의 실행환경은 100개의 이미지를 한번에 불러오는 것입니다.

 

 

성능

 

GCD의 문제를 해결하기 위해 Swift concurrency가 등장했다는 것을 잘 아실 것입니다.

 

 

크게 컨텍스트 스위칭을 줄이기, CPU스케쥴링 리소스 감소, 메모리 효율성을 위해서 입니다.

 

 

컨텍스 스위칭 & CPU스케쥴링 리소스 감소

instruments를 활용해 100개의 이미지 획득시 발생하는 컨텍스트 스위칭을 관찰했습니다.

 

 

측정결과 Actor를 활용한 경우 15,823번의 변경이

 

 

GCD를 사용한 경우 17,710번의 변경이 발생한 것을 확인할 수 있었습니다.

 

 

두 경우 모두 다수의 이미지를 한번에 불러온다는 점에서 사실상 활용되는 쓰레드가 비슷할 것이라고 예상했지만, 

 

 

전자의 경우가 조금더 최적화 되어 있음을 확인할 수 있었습니다.

 

 

활용한 CPU 사이클의 경우 Actor를 사용한 경우 1.31기가 사이클

 

 

GCD의 경우 1.72기가 사이클로 차이를 보이는 것을 확인할 수 있었습니다.

 

 

메모리 사용량

쓰레드가 줄었기에 메모리 사용량 역시 Swift concurrency가 적을 것이라고 생각했습니다.

 

 

하지만, 실제로 측정결과 Swift concurrency의 경우 약 68MB를

 

 

GCD의 경우 38MB를 기록했습니다.

 

 

GPT와 상담 결과, 해당 테스트 케이스의 경우 Task가 너무 많이 생성되고 해당 테스크들의

 

 

동시성을 관리하기 위한 추가적인 리소스가 필요하게되기 때문에 메모리가 많이 발생할 수 있다고 합니다.

 

 

GCD의 경우 쓰레드를 다수 사용하여 쓰레드에 의한 메모리가 많아질 수 있지만, 쓰레드를 재사용하는 경우 역시 많아

 

 

메모르 효율성이 높을 수 있다고 합니다.

 

 

트레이드 오프내요!

 

 

번외, KingFisher와 성능 비교

저의 라이브러리와 이미지 캐싱라이브러리로 유명한 kingFisher는 얼마나 큰 성능 차이를 보일까요?

 

 

한번 확인해 봤습니다.

 

1. 메모리

메모리의 경우 약 22MB로 측정되며, 저의 라이브러리와 달리 추가적인 최적화 작업이 발생한다고 예상됩니다.

 

2. 컨텍스트 스위칭

10,000번으로 측정되며, 제 라이브러리보다 효율적입니다..!

 

3. CPU 오버헤드

1.17Gc로 제 라이브러리 보다 효율적입니다...!

 

 

다음 시간에는 Kingfisher라이브러리를 뜯어보고 어떠한 차이점이 있는지 분석해보고 포스팅을 작성해보겠습니다.

 

 

아래 링크는 제 라이브러리로, 이제는 사용하기 안정적인 단계라고 생각합니다! 많이 이용해주시면 좋겠습니다.

 

 

Kingfisher보다 쉬운 로직으로 구현되어 있어 아마 살펴보시기에는 더 좋을 듯합니다!

 

 

 

 

GitHub - J0onYEong/SimpleImageProvider: iOS개발시 사용할 수 있는 간편한 이미지 캐싱 라이브러리입니다.

iOS개발시 사용할 수 있는 간편한 이미지 캐싱 라이브러리입니다. Contribute to J0onYEong/SimpleImageProvider development by creating an account on GitHub.

github.com