Language/JavaScript

setTimeout 들여다보기

SambaLim 2023. 9. 14. 19:47

들어가기

오픈소스 스터디에서 setTimeout을 들여다보며 알게된 것을 공유합니다.

Event Loop

http://latentflip.com/loupe/

이벤트루프를 설명하기 위한 서비스입니다. Call Stack, Web Apis, Task Queue(Callback Queue) 세 영역으로 나누어 좌측의 코드가 실행되는 동안 어떠한 영역에서 작업이 이루어지고 이동하는지 설명하고 있습니다. setTimeout 과 같이 비동기적으로 실행되는 함수는 콜백이 실행되기 전까지 Web Apis에서 대기하고 실행되기 전에 Task Queue에 담겼다가 Call Stack이 비면 실행됩니다.

V8, Blink

Chromium은 우리가 작성한 Javascript 코드를 실행하기 위해 V8이라는 엔진을 사용합니다. 하지만 브라우저에 렌더링하는 코드는 V8에 포함되어있지 않습니다. 따라서 V8은 렌더링엔진으로 Blink를 사용합니다. 이때 Blink 인터페이스를 V8에 바인딩하여 사용하기위해 Web IDL을 사용합니다.

코드 들여다보기

setTimeout

third_party/blink/renderer/modules/scheduler/dom_timer.cc (링크)

코드의 내용은 정확히 모르겠지만, nesting_level 이라는 것과 timeout.is_zero()를 가지고 TaskType을 정하는 로직이 있습니다.

// Select TaskType based on nesting level.
TaskType task_type;
if (nesting_level_ >= kMaxTimerNestingLevel) {
  task_type = TaskType::kJavascriptTimerDelayedHighNesting;
} else if (timeout.is_zero()) {
  task_type = TaskType::kJavascriptTimerImmediate;
  DCHECK_LT(nesting_level_, max_nesting_level);
} else {
  task_type = TaskType::kJavascriptTimerDelayedLowNesting;
}

여기서 TaskType을 따라가면 무려 84개의 타입을 만나볼 수 있습니다.

third_party/blink/public/platform/task_type.h (링크)

// https://html.spec.whatwg.org/multipage/webappapis.html#timers
// For tasks queued by setTimeout() or setInterval().
//
// Task nesting level is < 5 and timeout is zero.
kJavascriptTimerImmediate = 72,
// Task nesting level is < 5 and timeout is > 0.
kJavascriptTimerDelayedLowNesting = 73,
// Task nesting level is >= 5.
kJavascriptTimerDelayedHighNesting = 10,
// Note: The timeout is increased to be at least 4ms when the task nesting
// level is >= 5. Therefore, the timeout is necessarily > 0 for
// kJavascriptTimerDelayedHighNesting.

TaskPriority

third_party/blink/renderer/platform/scheduler/common/task_priority.h (링크)

정확한 내용은 모르겠지만, Task Queue에서 최적화를 위한 작업들이 있다는 것을 알 수 있습니다.

enum class TaskPriority : base::sequence_manager::TaskQueue::QueuePriority {
  // Priorities are in descending order.
  kControlPriority = 0,
  kHighestPriority = 1,
  kExtremelyHighPriority = 2,
  kVeryHighPriority = 3,
  kHighPriorityContinuation = 4,
  kHighPriority = 5,
  kNormalPriorityContinuation = 6,
  kNormalPriority = 7,
  kDefaultPriority = kNormalPriority,
  kLowPriorityContinuation = 8,
  kLowPriority = 9,
  kBestEffortPriority = 10,

  // Must be the last entry.
  kPriorityCount = 11,
};

nesting level

  1. If timeout is less than 0, then set timeout to 0.
  2. If nesting level is greater than 5, and timeout is less than 4, then set timeout to 4.
// Step 11 of the algorithm at
// https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html requires
// that a timeout less than 4ms is increased to 4ms when the nesting level is
// greater than 5.
constexpr int kMaxTimerNestingLevel = 5;
constexpr base::TimeDelta kMinimumInterval = base::Milliseconds(4);
constexpr base::TimeDelta kMaxHighResolutionInterval = base::Milliseconds(32);

// A timer with a long timeout probably doesn't need to run at a precise time,
// so allow some leeway on it. On the other hand, a timer with a short timeout
// may need to run on time to deliver the best user experience.
bool precise = (timeout < kMaxHighResolutionInterval);

예시

let start = Date.now();
let times = [];

setTimeout(function run() {
  times.push(Date.now() - start); // remember delay from the previous call

  if (start + 100 < Date.now()) alert(times); // show the delays after 100ms
  else setTimeout(run); // else re-schedule
}, 0);

// ex. 0,0,0,0,5,10,15,20,25,30,35,40,45,50,55,60,66,71,75,80,85,90,94,99,105

time

base/time/time_mac.mm (링크)

Chromium은 운영체제가 제공하는 타이머 기능을 활용하여 시간이 경과를 확인합니다. 따라서 시간과 관련한 파일이 OS별로 존재합니다.

참고링크