저번 GC 포스팅에서는 stop-the-world라는 개념을 소개했었다. 렌더링 프로세스의 메인스레드에서 GC가 발생하면 렌더링 도중에 화면이 끊길수도 있는 것이다. 이는 1초 이내의 일시정지이더라도 분명 유저 경험에 악영향을 준다. 이를 stop-the-world라고 부른다.
v8 팀은 이러한 stop-the-world에서의 유저 경험 악화를 줄이기 위해 idle time 최적화와 incremental marking을 이루어냈다.
중요한 작업 중에 메인스레드에서 GC가 발생하면 이러한 목표를 달성하기가 더 어려울 수 있다.
그런데 이 시기에 크롬의 렌더링 엔진인 Blink에 task scheduler가 생겼다. 각각의 작업의 priority를 Blink 내부의 task scheduler가 관리할 수 있게 된 것이다. 그리고 GC는 우선순위가 낮은 idle task로 인지한다. 예를 들어 유저가 애니메이션을 재생한다고 가정해보면, 화면에 frame을 띄워주는 작업보다는 GC가 확실히 우선순위가 낮은 작업이다. 이를 idle time 최적화라고 한다.
이렇게 idle time 최적화로 작업 간에 우선순위가 생기면, 지난 시간에 소개한 major GC에서의 마킹 작업도 한 번에 다 진행 하는 것이 아니라 점진적으로 진행할 수 있게된다.
위의 예를 다시 가져와보자. v8팀의 경우, 60 FPS(Frame per second) 목표를 가지고 있다. 초당 60번의 frame을 화면에 보여주는 것이 목표이다. 애니메이션을 보여줄 때 60FPS를 달성하려면 1개 frame을 렌더링하는데 16.6ms가 주어진다. 그리고 16.6ms 이내에 frame 하나를 렌더링하고도 몇 ms 남으면 중간중간이 idle time이 되는 것이다.
그리고 한 번에 메인스레드가 marking을 전부 다 하는 것이 아니라, 조금씩 시간 간격을 두고 끊어서 실행할 수 있게 된다. 이를 incremental marking이라고 한다.
하지만 이러한 최적화마저 여전히 싱글스레드에서 이루어지고 있었다. stop-the-world가 짧아지긴 했지만 여전히 발생하고 있기는 했고, incremental marking도 성능 개선에 긍정적 영향을 미치긴했지만, context switching에 따른 오버헤드가 있었다.
v8팀은 여전히 배가 고팠다(?) 점차 멀티코어 프로세스가 보급되고 GC도 싱글 스레드 환경이 아닌 멀티스레드 환경을 적극적으로 이용하고 새롭게 최적화할 필요가 생겼다. 다음 포스팅부터는 멀티스레드 환경에서 GC의 최적화를 이루기 위해 v8팀의 피눈물나는 프로젝트인 Orinoco의 내용에 대해 알아보자.
참고:
v8 docs
Getting garbage collection for free
https://v8.dev/blog/free-garbage-collection
Trash talk: the Orinoco garbage collector
https://v8.dev/blog/trash-talk