본문 바로가기
IT/Internals

I/O, CPU Bound

by 물통꿀꿀이 2019. 6. 23.

이번 포스팅은 CPU 및 IO Bound에 대해 알아보려고 한다.


(이와 관련되서는 아래의 글을 참조)

https://hellsoft.se/https-hellsoft-se-understanding-cpu-and-io-bound-for-asynchronous-operations-6511c70a5685


IOwait

CPU 입장에서는 로컬 파일 시스템 또는 네트워크 상의 소켓에서 데이터를 Read/Write 할 때 많은 시간을 대기하게 된다. 이러한 이유를 이해가기 위해서는 하드웨어 레벨로 내려가야하는데, 단순히 하드웨어는 관련 데이터를 전달 받을 때까지 사용할 수 없기 때문이다. (하드웨어는 관련 데이터를 모두 전달 받을 때까지 CPU는 대기하게 된다.)

즉, RAM을 사용하지 않는 Read/Write와 같은 I/O 작업은 일반적으로 *IOwait를 하게 된다. (RAM을 사용하면 이미 관련 데이터가 저장되어 있어서 바로 사용할 수 있다.) 

*IOwait는 커널 레벨의 시스템 콜 명령어로 CPU에게 데이터가 모두 전달되어 사용할 수 있을 때까지 현재 스레드 작업을 잠시 멈추는 역할을 한다.


조금 더 위로 올라가서 OS는 IOwait가 발생할 때, 현재 스레드를 멈추고 준비되어 있는 다른 스레드를 선택한다. (이론상 OS는 무한대의 IOwait 상태의 스레드를 가질 수 있지만 현실에서는 RAM, Context Switching과 같은 리소스를 고려해야 한다.) 그런데 여기서 중요한 점은 I/O 작업을 하는 스레드는 위에서 언급했듯이 하드웨어 간의 작업이 완료될 때까지 대부분의 시간을 Sleep 상태로 보내야 한다. 

게다가 시스템 내부적으로 Read/Write 각각의 작업에 스레드를 할당한다. 그 말은 I/O을 위해 존재하는 스레드 풀이 적으면 OS에서 다른 스레드를 선택할 때까지 (다른 I/O 작업이 끝날 때까지) 대기해야 한다는 것이다.


결국 많은 Read/Write 작업이 필요한 어플리케이션 입장에서는 I/O 작업으로 인한 대기가 많아져 성능이 나아질 수 밖에 없다. 

그렇기 때문에 I/O 작업을 수행하는 스레드 풀의 개수(상한선)을 늘려야 한다는 것을 의미한다.


CPU-only Work

어플리케이션 중에서는 I/O 작업을 하지 않는, 메인 스레드에서 작업하기 보다는 백그라운드 스레드에서 작업해야 하는 것들이 존재한다. (예를 들어 데이터 압축/해제, AI 등과 같은 계산 작업이 방대한 어플리케이션)


(리소스 상관하지 않고) 간단한 예로 사이즈가 큰 텍스트 문자를 정렬해야 한다면, 모든 텍스트 문자를 RAM에 저장하고 준비된 CPU 작업하면 된다. 즉, IOwait가 발생하지 않는다.
그러나 정렬 작업이 I/O와 별개로 메인 스레드에서 발생한다면 실행 시간이 길어 질 수 있다. 그렇기 때문에 백그라운드 스레드에서 작업을 실행하고, 작업 결과를 메인 스레드로 전달해야 한다.


그렇다면 위의 예제를 바탕으로 동시에 정렬된 4개의 배열이 필요하다고 가정해보자. 그렇다면 CPU는 4개의 코어가 필요하다. (즉, 하드웨어 상에서 병렬적으로 4개의 스레드를 실행시킬 수 있도록 지원해야 한다.) 

만약 모든 코어에서 정렬을 수행하고 있다면 할지라도 IOwait는 발생하지 않는다. 다시 말해서 IOwait가 발생가지 않기 때문에 CPU는 I/O 작업에 대해 스레드를 멈출 필요가 없다. 

(물론 IOwait가 발생하진 않더라도 Context Switching이 발생하긴 하겠지만 스레드를 멈추지는 않기 때문에 CPU 시간이 줄어든진 않는다.)


CPU Bound에 대한 중요한 점은 CPU 코어의 개수(병렬적으로 수행 할 수 있는 스레드 개수)와 비슷한 스레드 풀을 가지고 있어야 한다는 점이다. 


What if

위에서 언급한 예를 좀 더 확장해서 네트워크 상에서 이미지를 가져와서 리스트(안드로이드)에서 보여주려고 한다. 또한 이미지를 보여주기 전에 각 이미지에 대한 Blur 작업이 우선되어야 한다.

이런 상황에서는 무거운 CPU 작업 및 I/O 작업이 리스트의 각 아이템에 대해 이루어져야 한다. 이 상황에서 어떤 스레드 풀이 사용되야 할까?

만일 해당 시점에 하나의 이미지만 보여주려고 했다면 I/O 스레드 풀을 통해 작업을 하는 것이 맞을 것이다. 그러나 다수의 이미지에서는 어떨까?


이 문제는 사용자의 상황에 달려있다. 물론 I/O 스레드에서 모든 것을 수행해도 된다. 그렇지만 성능 문제가 발생하였을 경우에 CPU 스레드 풀로 전환하는 것이 낫다. 

(물론 I/O 작업이 많은 어플리케이션에서는 CPU 스레드 풀보다 I/O 스레드 풀이 더 커야 한다.)


Summary

백그라운드에서 작업 하는 대부분의 시간은 I/O 작업이다. 내장 or 서드 파티 라이브러리릍 통해 사용하는 편이 낫다. 그러나 스스로 최적화하는 경우 (경험적으로) 문제가 발생할 수 있다. 때문에 자신만의 스레드풀을 정의하지 않는 것이 좋다.


또한 동시성을 지원하는 몇몇 라이브러리는 자신만의 스레드 풀을 제공하는데 어떤 것을 사용해야 할지 잘 생각해봐야한다.

만일 CPU 또는 I/O를 사용해야 할 경우에는 2개의 파트로 작업을 쪼갤 것인지 아니면 스레드 간에 스위칭을 할 것인지 고려해야 한다.


사실 두 가지를 비교한 글을 번역 및 읽어보면서 결론적인 결론은 일단 해보고 결과를 바탕으로 조정해야 한다는 것이다.

물론 해당 글에서는 "많은 I/O 스레드 작업은 괜찮고 많은 CPU 스레드 작업은 나쁠 수 있다"고는 하지만 모든 것은 상황에 따라 다르다는 것을 가정하고 있다.

때문에 해당 글은 이론적인 측면이고 실제 환경에서는 여러 실험을 통해 성능을 높일 수 있는 방안으로 택해야 하겠다.

댓글