본문 바로가기

엔지니어링(TA, AA, SA)/성능과 튜닝

[시스템성능] 크롬 개발자 도구 활용하기

타임라인 도구 사용법


Chrome 개발자도구의 Timeline 패널을 사용하면 애플리케이션에서 작업이 실행될 때 모든 작업을 기록하고 분석할 수 있습니다. 이는 애플리케이션에서 인지된 성능 문제를 조사하는 데 가장 좋은 출발점입니다.



 - 타임라인 기록을 만들어 페이지 로드 또는 사용자 상호작용 후 발생한 모든 이벤트를 분석합니다.

 - Overview 창에서 FPS(Frame per Second), CPU 및 네트워크 요청을 볼 수 있습니다.

 - Flame Chart 내에서 이벤트를 클릭하면 그에 대한 세부정보를 볼 수 있습니다.

 - 기록의 한 부분을 확대하면 분석이 한결 쉬워집니다.



Timeline 패널 개요


Timeline 패널은 네 개의 창으로 구성되어 있습니다.


1. Controls: 기록을 시작하고 기록을 중단하거나 기록 중에 캡쳐된 정보를 구성합니다.

2. Overview: 페이지 성능에 대한 고차원적인 요약을 제공합니다.

3. Flame Chart: CPU 스택 추적을 시각화한 것


Flame Chart에는 세로 방향의 점선이 한 개 내지 세 개까지 표시될 수 있습니다. 파란색 선은 DOMContentLoaded 이벤트를 나타냅니다. 녹색 선은 첫 페인트까지의 시간을 나타냅니다. 빨간색 선은 load 이벤트를 나타냅니다.


1. Details: 이벤트를 선택하면 이 창에 해당 이벤트에 대한 자세한 정보가 표시됩니다. 선택한 이벤트가 없는 경우, 이 창에는 선택한 타임 프레임에 대한 정보가 표시됩니다.



Overview 창


Overview 창은 세 개의 그래프로 이루어져 있습니다.


1. FPS: 초당 프레임 수입니다. 녹색 막대가 높이 올라갈수록 FPS가 높은 것입니다. FPS 그래프 위에 있는 빨간색 블록은 긴 프레임을 의미하는데, 이것은 버벅거림 발생 후보일 가능성이 높습니다.

2. CPU: CPU 리소스입니다. 이 영역 차트를 보면 CPU 리소스를 사용한 이벤트의 유형을 알 수 있습니다.

3. NET: 각 색상의 막대는 리소스를 나타냅니다. 막대 길이가 길수록 리소스를 회수하는데 시간이 오래 걸렸다는 듯입니다. 각 막대의 색이 옅은 부분은 대기 시간(리소스가 요청된 시간부터 첫 바이트를 다운로드하는 데까지 걸린 시간)을 나타냅니다. 색이 짙은 부분은 전송 시간(첫 바이트부터 마지막 바이트를 다운로드하는 데까지 걸린시간)을 나타냅니다.


막대에 색상이 지정되는 방식은 다음과 같습니다.


 - HTML 파일은 파란색입니다.

 - 스크립트는 노란색입니다.

 - 스타일시트는 보라색입니다.

 - 미디어 파일은 녹색입니다.

 - 기타 리소스는 회색입니다.



기록 만들기


페이지 로드의 기록을 만들려면 Timeline 패널을 열어 기록하고자 하는 페이지를 연 다음 해당 페이지를 새로 고칩니다. Timeline 패널이 페이지 새로고침을 자동으로 기록합니다.


 - 기록은 가급적 짧을수록 좋습니다. 기록이 짧아야 분석하기 쉽기 때문입니다.

 - 불필요한 작업은 피하세요. 기록하고 분석하고자 하는 활동에 관련없는 다른 작업(마우스 클릭, 네트워크 로드 등)은 피하는 것이 좋습니다. 예컨대 Login 버튼을 클릭한 다음 발생하는 이벤트를 기록하고자 하는 경우 페이지를 스크롤하거나 이미지를 로드하는 등 다른 작업도 함께 하지 마세요.

 - 브라우저 캐시를 비활성화합니다. 네트워크 작업을 기록할 때에는 DevTools 설정 패널 또는 Network Conditions 창에서 브라우저 캐시를 비활성화하는 것이 좋습니다.

 - 확장 프로그램을 비활성화합니다. Chrome 확장 프로그램을 사용하면 애플리케이션의 타임라인 기록에 무관한 노이즈가 추가될 수 있습니다. Chrome 창을 incognito 모드로 열거나 새 Chrome 사용자 프로필을 만들어 환경에 확장 프로그램이 없도록 하세요.



기록 세부정보 보기


Flame Chart에서 이벤트를 선택하면 Details 창에 해당 이벤트에 대한 자세한 정보가 표시됩니다.



일부 탭은 이벤트 유형을 불문하고 모듀 표시됩니다. 다른 탭은 특정 이벤트 유형에서만 이용할 수 있습니다. 각 기록 유형에 대한 자세한 내용은 타임라인 이벤트 참조를 참조하면 됩니다.



기록 중 스크린샷 캡쳐


Timeline 창은 페이지 로드 중에 스크린샷을 캡쳐할 수 있습니다. 이 기능은 일명 필름스트립이라고 합니다. 먼저 Controls 창의 Screenshots 확인란을 활성화해야 기록의 스크린샷을 캡쳐할 수 있습니다. 스크린샷은 Overview 창 아래에 표시됩니다.



마우스를 Screenshots 또는 Overview 창 위로 가져가면 기록에서 해당 지점의 확대된 스크린샷을 볼 수 있습니다. 마우스를 좌우로 움직이면 기록의 애니메이션을 시뮬레이트할 수 있습니다.



자바스크립트 프로필


기록을 하기 전에 JS Profile 확인라나을 활성화해야 타임라인 기록의 자바스크립트 스택을 캡처할 수 있습니다. JS 프로파일러가 활성화되면 Flame Chart에 호출된 자바스크립트 함수가 모두 표시됩니다.




페인팅 프로필


기록을 하기 전에 Paint 확인란을 활성화해야 Paint 이벤트를 더 자세히 살펴볼 수 있습니다. 페인트 프로필을 활성화한 상태에서 Paint 이벤트를 클릭하면 새로운 Paint Profiler 탭이 Details 창에 표시되어 이벤트에 대해 더 상세한 정보를 보여줍니다.



렌더링 설정


기본 DevTools 메뉴를 열고 More tools > Rendering settings 를 선택하면 페인트 문제를 디버그할 때 유용한 여러 가지 렌더링 설정에 액세스할 수 있습니다.렌더링 설정은 Console 창 옆에 탭으로 열립니다.



기록 검색


이벤트를 살펴볼 때에는 한 가지 유형의 이벤트에만 집중하고자 할 수 있습니다. 예를 들어, 모든 Parse HTML 이벤트의 세부정보를 봐야 하는 경우를 생각해보겠습니다.


Timeline에 포커스가 맞춰진 상태에서 Cmd+F(Mac)을 누르면 검색 툴바가 열립니다. 검사하고자 하는 이벤트 유형의 이름, 예를 들어 Event를 입력합니다.


이 툴바는 현재 선택한 시간대에만 적용됩니다. 선택한 시간대를 벗어난 모든 이벤트는 결과에 포함되지 않습니다.


위쪽 및 아래쪽 화살표를 사용하면 결과 목록을 시간순으로 이동할 수 있습니다. 따라서, 첫 번째 결과가 선택한 시간대 내에서 가장 일찍 일어난 이벤트이며 마지막 결과가 마지막 이벤트를 나타냅니다. 위쪽 또는 아래쪽 화살표를 누를 때마다 새로운 이벤트가 선택되므로 해당 이벤트의 세부정보를 Details 창에서 볼 수 있습니다. 위쪽 및 아래쪽 화살표를 누르는 것은 Flame Chart에서 이벤트를 클릭하는 것과 같습니다.



메모리 문제 해결


Chrome과 DevTools를 사용하여 페이지 성능에 영향을 미치는 메모리 문제를 찾아내는 방법을 알아보겠습니다. 메모리 누수, 메모리 팽창 및 잦은 가비지 수집 등이 대표적입니다.



TL;DR

 - 페이지가 현재 얼마나 많은 양의 메모리를 사용하고 있는지 Chrome 작업 관리자를 사용하여 알아볼 수 있습니다.

 - 시간의 흐름에 따른 메모리 사용량을 타임라인 기록으로 시각화합니다.

 - 분리된 DOM 트리(일반적인 메모리 누수 원인)을 힙 스냅샷으로 식별할 수 있습니다.

 - JS 힙에 새 메모리가 할당될 때 할당 타임라인 기록을 사용해 알아낼 수 있습니다.



RAIL 성능 모델의 원칙에 따르면 성능 개선을 위해 노력할 때에는 항상 사용자에게 주안점을 두어야 합니다. 메모리 문제는 사용자가 인지할 수 있는 경우가 많기 때문에 중요합니다. 사용자가 메모리 문제를 인지할 수 있는 방법은 다음과 같습니다.


 - 페이지 성능이 시간이 지나면서 점점 더 악화됩니다. 이것은 아마도 메모리 누수의 징후일 가능성이 큽니다. 메모리 누수란 페이지 내에서 버그로 인해 해당 페이지가 시간이 지날수록 점점 더 많은 메모리를 사용하게 되는 것을 말합니다.

 - 페이지 성능이 일관적으로 불량합니다. 이것은 아마도 메모리 팽창의 징후일 가능성이 큽니다. 메모리 팽창이란 페이지가 최적의 페이지 속도를 위해 필요한 것보다 많은 양의 메모리를 사용하는 것을 말합니다.

 - 페이지 성능이 지연되거나 자주 일시적으로 중단되는 것처럼 보입니다. 이것은 아마도 잦은 가비지 수집의 징후일 가능성이 큽니다. 가비지 수집이란 브라우저가 메모리를 회수하는 것을 말합니다. 이 작업이 언제 발생할지는 브라우저가 결정합니다. 수집 중에는 모든 스크립트 실행이 일시 중지됩니다. 따라서 브라우저가 가비지 수집을 많이 할수록 스크립트 실행이 일시중지 되는 경우도 많아집니다.



메모리 팽창: 얼마나 많아야 '너무 많은'것일까요?


메모리 누수는 쉽게 정의할 수 있습니다. 사이트가 점점 더 많은 메모리를 사용한다면 누수가 발생한 것입니다. 하지만 메모리 팽창은 그렇게 꼭 집어 말하기 조금 어렵습니다. '메모리를 너무 많이 사용한다'고 말하려면 어떤 조건을 충족해야 하는 것일까요?


이런 경우 숫자가 따로 정해져 있는 것도 아닙니다. 기기와 브라우저에 따라 각기 용량이 다른 경우가 많기 때문입니다. 고급 스마트폰에서는 원활하게 실행되는 페이지가 저사양 스마트폰에서는 다운될 수도 있습니다.


여기에서 중요한 것은 RAIL 모델을 사용하여 사용자에게 주안점을 두는 것입니다. 사용자에게 인기있는 기기가 무엇인지 파악한 다음, 그러한 기기에서 페이지를 테스트해보세요. 페이지 환경이 일관적으로 불량하다면, 해당 페이지는 그러한 기기의 메모리 용량을 초과하는 것일 수 있습니다.



Chrome 작업 관리자로 메모리 사용량을 실시간 모니터링


Chrome 작업 관리자를 메모리 문제 조사의 출발점으로 삼아야합니다. 작업 관리자는 페이지가 얼마나 많은 메모리를 사용하고 있는지 알려주는 실시간 모니터입니다.



 - Memory(메모리 사용량) 열은 네이티브 메모리를 나타냅니다. DOM 노드는 네이티브 메모리에 저장됩니다. 이 값이 늘어나는 경우 DOM 노드가 생성되고 있는 것입니다.


 - JavaScript Memory 열은 JS 힙을 나타냅니다. 이 열에는 두 개의 값이 들어있습니다. 여기서 관심을 가져야 할 값은 바로 활성 숫자(괄호 안에 있는 숫자)입니다. 활성 숫자는 페이지의 연결 가능한 객체들이 사용하고 있는 메모리의 양을 나타냅니다. 이 숫자가 늘어나고 있다면, 새 객체가 생성되고 있는 것이거나 기존 객체가 성장하고 있는 것입니다.



타임라인 기록으로 메모리 누수 시각화


Timeline 패널을 조사의 또 다른 출발점으로 사용할 수도 있습니다. Timeline 패널을 사용하면 시간의 흐름에 따를 페이지의 메모리 사용량을 시각화하는데 도움이 됩니다.


  1. DevTools에서 Timeline 패널을 엽니다.

  2. Memory 확인란을 활성화합니다.

  3. 기록을 만듭니다.


강제 가비지 수집으로 기록을 시작하고 끝내는 것이 좋습니다. collect garbage 버튼을 누른 상태로 강제 가비지 수집을 기록합니다.

var x = [];

function grow() {
	for (var i = 0; i < 10000; i++) {
		document.body.appendChild(document.createElement('div'));
	}
	x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

이 코드에서 참조된 버튼을 누를때마다 일만개의 div 노드가 문서 본문에 추가되고 백만개의 x글자로 이루어진 문자열이 x 배열로 푸시됩니다. 이 코드를 실행하면 아래의 스크린샷과 같은 타임라인 기록이 생성됩니다.



우선, 사용자 인터페이스의 설명입니다. Overview 창의 HEAP 그래프는 JS 힙을 나타냅니다. Overview 창 아래에 Counter 창이 있습니다. 여기에서 메모리 사용량을 JS 힙(Overview 창의 HEAP 그래프와 동일), 문서, DOM 노드, 리스너 및 GPU 메모리 등의 항목별로 분석한 내용을 확인할 수 있습니다. 확인란을 비활성화하면 해당 내용이 그래프에서 숨겨집니다.


이제 코드를 스크린샷과 비교하여 분석해보겠습니다. 노드 카운터(녹색 그래프)를 보면 이것이 노드와 깔끔하게 일치한다는 사실을 확인할 수 있습니다. 노드 카운트는 불연속적인 단계에서 증가합니다. 각 노드 카운트 증가를 grow() 호출로 간주할 수 있습니다. JS 힙 그래프(파란색 그래프)는 이처럼 간단하지 않습니다. 모범 사례를 따르면, 첫번째 하강부분은 사실 collect garbage 버튼을 누르면 실행되는 강제 가비지 수집입니다. 기록이 진행될수록 JS 힙 크기가 갑자기 치솟는 것을 확인할 수 있습니다. 이는 당연하고 예상할 수 있는 현상입니다. 자바스크립트가 버튼을 누를때마다 DOM 노드를 생성하고 수 백만개의 문자로 구성된 문자열을 생성할 때 대량의 작업을 수행하기 때문입니다. 여기서 중요한 것은 JS 힙이 처음에 시작했을 때보다 높은 곳에서 끝난다는 사실입니다(여기서 '시작'은 강제 가비지 수집 이후 시점). 실제로는 JS 힙 크기 또는 노드 크기가 이렇게 증가하는 패턴이 나타나면 메모리 누수를 의미할 가능성이 있습니다.



힙 스냅샷으로 분리된 DOM 트리 메모리 누수 발견


DOM 노드를 가비지 수집할 수 있는 경우는 페이지의 DOM 트리 또는 자바스크립트 코드 중 어느쪽에서도 이에 대한 참조가 없을 때 뿐입니다.  노드를 '분리되었다'고 말하는 것은 노드가 DOM 트리에서 제거되었지만 몇몇 자바스크립트에서 여전히 해당 노드를 참조하는 경우를 의미합니다. 분리된 DOM 노드는 메모리 누수를 초래하는 일반적인 원인입니다. 아래는 DevTools의 힙 프로파일러를 사용하여 분리된 노드를 파악하는 방법 입니다.

var detachedNodes;

function create() {
	var ul = document.createElement('ul');
	for (var i = 0; i < 10; i++) {
		var li = document.createElement('li');
		ul.appendChild(li);
	}
	detachedTree = ul;
}

document.getElementById('create').addEventListener('click', create);

코드에서 참조된 버튼을 클릭하면 10개의 li 자식 노드가 있는 ul 노드가 생성됩니다. 이들 노드는 코드에서는 참조되지만 DOM 트리에는 존재하지 않으므로 분리되었다고 할 수 있습니다.


힙 스냅샷은 분리된 노드를 식별하는 한가지 방법입니다. 힙 스냅샷은 이름에서 알 수 있듯이, 스냅샷 시점에 페이지의 JS 객체와 DOM 노드에서 메모리가 어떻게 분포되어 있는지 보여줍니다.


스냅샷을 만들려면 DevTools를 열어 Profiles 패널로 이동한 다음 Take Heap Snapshot 라디올 버튼을 삭제하고, Take Snapshot 버튼을 누릅니다.



스냅샷을 처리하고 로드하려면 약간의 시간이 걸릴 수 있습니다. 작업이 완료되면 왼쪽 패널(일면 HEAP SNAPSHOTS)에서 이를 선택합니다.


Class filter 텍스트 상자에 Detached 라고 입력하여 분리된 DOM 트리를 검색합니다.




노란색으로 강조표시된 노드는 자바스크립트 코드에서 직접 참조하는 노드입니다. 빨간색으로 강조표시된 노드는 직접 참조가 없습니다. 이들이 활성 상태인 유일한 이유는 노란색 노드의 트리에 속하기 때문입니다. 일반적으로 노란색 노드에만 주안점을 두면 됩니다. 코드를 수정하여 노란색 노드가 필요한 시간 이상으로 활성화되지 않도록 한 다음 노란색 노드 트리에 속한 빨간색 노드도 없앱니다.


노란색 노드를 클릭하면 더욱 심층적으로 조사할 수 있습니다. Objects 창에서 이를 참조하는 코드에 대한 자세한 내용을 볼 수 있습니다. 예를 들어 아래의 스크린샷에서는 detachedTree 변수가 해당 노드를 참조하고 있음을 알 수 있습니다. 이 메모리 누수를 해결하려면 detachedTree를 사용하는 코드를 조사하여 더이상 필요없는 노드에 대한 참조를 제거하도록 해야 합니다.




잦은 가비지 수집 관측


페이지가 일시 중지하는 상황이 자주 발생하면 가비지 수집 문제일 수 있습니다.


잦은 가비지 수집을 관측하려면 Chrome 작업 관리자 또는 타임라인 메모리 기록을 사용하면 됩니다. 작업 관리자의 경우, Memory 또는 Javascript Memory 값이 너무 자주 오르내리는 경우 가비 수집이 잦다는 의미입니다. 타임라인 기록에서 JS 힙 또는 노드 카운트 그래프가 자주 오르내리는 경우, 가비지 수집이 잦다는 의미입니다.


문제가 무엇인지 파악했으면 할당 타임라인 기록을 사용하여 메모리가 어디에 할당되는지, 어느 함수가 할당을 초래하는지 알아낼 수 있습니다.




메모리 용어


이 섹션에서는 메모리 분석에 흔히 사용되는 일반적인 용어를 설명합니다. 이러한 용어는 여러 언어의 다양한 메모리 프로파일링 도구에 적용할 수 있습니다.


여기에 설명된 여러 가지 용어와 개념은 크롬 개발자도구 힙 프로파일러에 대한 내용입니다. 자바, 닷넷 또는 기타 메모리 프로파일러를 다뤄본 경험이 있다면 다시 돌아보는 기회로 삼을 수 있습니다.



객체 크기


메모리를 일종의 기본 유형(예: 숫자와 문자열)과 객체(연관 배열)로 이루어진 그래프라고 생각하면 됩니다. 이것을 시각적으로 표현하면 아래와 같이 여러개의 상호 연결된 점으로 이루어진 그래프 형태로 나타날 수 있습니다.



객체는 다음 두 가지 방식으로 메모리를 보유할 수 있습니다.


  - 객체 자체가 직접 보유합니다.

  - 암시적으로 다른 객체에 대한 참조를 보유함으로써 해당 객체가 가비지 수집기에 의해 자동으로 폐기되지 않도록 합니다.


개발자도구에서 힙 프로파일러를 다룰때에는 몇 가지 정보가 담긴 열로 이루어진 형태를 접할 가능성이 높습니다. 그중 가장 눈에 띄는 두가지는 Shallow Size와 Retained Size이지만, 이들이 의미하는 바는 무엇일까요?


Shallow Size

객체 자체가 보유한 메모리 크기입니다.


일반적인 자바스크립트 객체에는 설명 내용을 담고 즉시 값을 저장하기 위한 약간의 메모리가 있습니다. 대개 배열과 문자열만 상당히 큰 Shallow Size를 가질 수 있습니다. 다만 문자열과 외부 배열의 경우 렌더러 메모리에 자체 기본 저장소가 있으며 자바스크립트 힙에는 작은 래퍼 객체만 노출됩니다.


렌더러 메모리는 검사한 페이지가 렌더링되는 프로세스의 모든 메모리이며, 기본 메모리 + 페이지의 JS 힙 메모리 + 페이지가 시작한 전담 작업자 모두 JS 힙 메모리가 이에 해당됩니다. 그럼에도 불구하고 작은 객체도 간접적으로, 즉 다른 객체가 자동 가비지 수집 프로세스에 의해 폐기되지 않도록 차단하는 방식으로 대량의 메모리를 보유할 수 있습니다.



Retained Size

객체 자체가 삭제되고, 그와 함께 GC roots에서 연결할 수 없게 된 종속 객체도 모두 삭제되면 확보되는 메모리 크기입니다.


GC roots는 네이티브 코드로부터 V8 외부의 자바스크립트 객체로 참조를 만들때 생성되는 (지역 또는 전역) 핸들로 구성됩니다. 이러한 핸들은 모두 GC roots > Handle scope 및 GC roots > Global handles 아래의 힙 스냅샷 내에서 찾을 수 있습니다. 이 문서에서 브라우저 구현에 대해 자세하게 다루지 않으면서 핸들에 대해 설명하면 조금 혼란스러울 수 있습니다. GC 루트와 핸들은 둘 다 개발자가 별로 신경을 쓰지 않아도 되는 부분입니다.


많은 내부 GC가 있고 대부분은 사용자가 별 관심을 기울이지 않습니다. 애플리케이션 관점에서 보면 크게 다음과 같은 종류의 루트가 있습니다.


 - Windows 전역 객체 (각 iframe에서). 힙 스냅샷에는 거리 필드가 있는데, 이는 창에서 최단 거리의 보존 경로에 있는 속성 참조의 수를 의미합니다.

 - 문서를 탐색하여 도달할 수 있는 모든 네이티브 DOM 노드로 구성된 문서 DOM 트리. 일부는 JS 래퍼가 없을 수도 있지만 래퍼가 있으면 문서가 활성화된 동안 래퍼가 활성화됩니다.

 - 경우에 따라 디버거 컨텍스트 및 개발자도구 콘솔이 객체를 보존할 수도 있습니다. 콘솔을 비우고 디버거 내 활성 중단점이 없는 상태로 힙 스냅샷을 생성합니다.


메모리 그래프는 루트로 시작하며, 브라우저의 window 객체일 수도 있고 Node.js 모듈의 Global 객체일 수도 있습니다. 이 루트 객체의 가비지 수집 방법은 개발자가 통제할 수 없습니다.


루트에서 연결할 수 없는 것은 무엇이든 가비지 수집(GC)됩니다.


참고: Shallow Size 및 Retained Size 열은 모두 데이터를 바이트 단위로 나타냅니다.



객체 보존 트리


힙은 상호 연결된 객체의 네트워크 수입니다. 수학에서는 이 구조를 그래프 또는 메모리 그래프라고 부릅니다. 그래프는 엣지로 연결된 노드로 구성되며, 모두 레이블이 지정됩니다.


 - 노드(또는 객체)에는 이를 빌드하는 데 사용된 생성자 함수의 이름을 사용하여 레이블이 지정됩니다.

 - 엣지는 속성의 이름을 사용하여 레이블이 지정됩니다.


힙 프로파일러를 사용하여 프로필을 기록하는 방법을 알아보세요. 아래의 힙 프로파일러 기록에서 특히 눈에 띄는 몇가지 중에서 거리가 포함됩니다. 이 거리는 GC 루트로부터의 거리입니다. 동일한 유형의 객체 중 거의 모두가 동일한 거리에 있고 몇개만 좀 더 먼 거리에 있다면 조사할 가치가 있는 문제입니다.


도미네이터


도미네이터 객체는 각 객체가 정확히 한 개의 도미네이터를 갖기 때문에 트리 구조로 구성됩니다. 객체의 도미네이터에는 자신이 지배하는 객체에 대한 참조가 없을 수도 있습니다. 즉, 도미네이터의 트리는 그래프의 스패닝 트리가 아닙니다.



아래 예시에서 노드 #3은 #10을 지배하지만, #7도 GC에서 #10로의 모든 단순 경로에 존재합니다. 따라서 루트로부터 객체A로 이어지는 모든 단순 경로에 객체 B가 존재하면 객체 B는 객체 A의 도미네이터입니다.