반응형

gc쪽 공부하며 접한 부분들 정리 해나가는 곳.

 

DCheck macro

DCheck : DCHECK는 디버그 모드에서 조건을 검증하기 위해 사용되는 매크로로, C++ 코드에서 DCHECK 매크로가 사용된 위치에서 주어진 조건이 참(true)인지 확인. 만약 조건이 거짓(false)이라면, 프로그램 실행을 중단하고 디버거에 진입하거나 오류 메시지를 출력. 이는 코드의 무결성을 확인하고 디버그 빌드에서 오류를 조기에 발견하기 위해 사용

 

DCHECK(condition): 조건이 참인지 확인
DCHECK_EQ(val1, val2): val1이 val2와 같은지 확인
DCHECK_NE(val1, val2): val1이 val2와 다른지 확인
DCHECK_LE(val1, val2): val1이 val2보다 작거나 같은지 확인
DCHECK_LT(val1, val2): val1이 val2보다 작은지 확인
DCHECK_GE(val1, val2): val1이 val2보다 크거나 같은지 확인
DCHECK_GT(val1, val2): val1이 val2보다 큰지 확인

 

 

https://source.chromium.org/chromium/chromium/src/+/main:v8/include/cppgc/;bpv=0;bpt=0

allocation.h : gc를 위한 메모리 할당 기능 정의
- allocationhandle{}  - garbage collected object들에 대한 메모리 할당관리

- AllocationDispatcher<> : 해당 구조체는 다양한 메모리 정렬 및 공간 설정에 따라 메모리를 할당하는 기능을 제공

- MakeGarbageCollectedTraitBase class: gc object를 위한 기본 할당 및 초기화 기능을 제공

   - call() 주어진 인자들을 통해 객체를 생성하고 초기화

CPPGC_DEFAULT_ALIGNED / CPPGC_DOUBLE_WORD_ALIGNED : 메모리 정렬을위한 macro

 

common.h : embedder의 stack state를 나타내는 enum을 정의

gc를 수행할 때, embedder stack의 kMayContainHeapPointers / kNoHeapPointers 를 참고하여  heap object를 가리키는 지 판별 후 gc하는 듯

 

cross-thread-persistent.h : 다중 스레드 간에 객체 참조를 유지하기 위한 기능 제공

PersistentBase : ASAN을 통해 poisoned memory에 접근하는 것을 허용. 

Known caveat : CTP사용시 주의사항 (ex. CTP는 객체를 가지고 있는 heap이 종료되는 것을 막지 못함, 객체를 소유한 스레드에서 다른 스레드로 이동할 수 있기에 그래프(?)를 통한 전이로 접근하는 것을 지원 X)

 

customspace.h : custom space 정의

customspaceindex{} : custom space를 index로 식별. kSpaceIndex로 유일하게 식별되어야 함. constexpr을 통해 초기화

CustomSpaceBase : 모든 custom spcae가 상속해야하는 기본 class.

compaction : 수동으로 조작되는 slot에 한해서 발생 -> 필요에 따라 기능 지원

 

default-platform.h : cppgc (C++ gc)에서 사용할 수 있는 기본 플랫폼을 정의, 다양한 시스템 자원 관리와 작업 스케줄링을 지원

DefaultPlatform() : 스레드 풀 크기, 유휴 작업 지원, 트레이싱 컨트롤러 등을 설정

foreground task runner : 메인 스레드(foreground thread)에서 실행해야 하는 작업들을 스케줄링하고 관리하는 역할

 

ephemeron-pair.h : EphemeronPair 구조체를 정의

ephemeron : key-value pairs in WeakMap and WeakSet -> 다른 모든 객체가 gc에 의해 표시된 후에만 그 활성도를 결정할 수 있는 약한 참조 객체

- WeakMember<K>는 약한 참조를 나타내며, Member<V>는 강한 참조를 나타남. 약한 참조는 gc에서 객체가 회수될 수 있도록 허용하지만, 강한 참조는 X

- value 객체는 key 객체가 살아있는 경우에만 유지=. key 객체가 죽으면 value 객체에 대한 참조도 제거

ex. event listener가 특정 객체에만 적용되어야 하는 경우, EphemeronPair를 사용하여 event listener를 해당 객체가 살아있-는 동안에만 유지

- 캐시에서 키-값 쌍을 관리할 때, 키가 무효화되면 값도 자동으로 제거

 

 

반응형

'v8' 카테고리의 다른 글

Memory management of the V8 Engine  (0) 2024.07.12
V8 extras  (0) 2024.05.06
Jank Busters Part One  (0) 2024.05.05
Custom startup snapshots  (0) 2024.04.25
Code caching  (0) 2024.04.23

반응형

해당 내용은 몇년전 내용을 기반으로 작성된 것이기에 현재는 https://chromium.googlesource.com/v8/v8/+/main/include/cppgc/README.md

 

Oilpan: C++ Garbage Collection

Incremental GC. Garbage collection work is split up into multiple steps which are interleaved with the mutator, i.e. user code chunked into tasks. Each step is a small chunk of work that is executed either as dedicated tasks between mutator tasks or, as ne

chromium.googlesource.com

요걸 기반으로 봐야한다.(언젠가의 내가 다시 정리하겠지)

 

 

javascript는 단일 스레드 언어이며, service worker를 사용하면 worker당 새 V8 process를 생성한다. 실행중인 program은 V8 process에 할당된 일정량의 memory로 표현되고, 이 메모리 구조를 Resident Set이라 불린다. 아래의 도식도는 v8 engine에서의 memory management에 대한 도식도이다.

v8의 memory space는 stack과 heap으로 나뉘어지며 heap memory에 대해서는 목적에 따라 더 세분화된 space로 나뉘어진다.

다음과 같은 간단한 코드를 실행할 때 메모리가 어떻게 할당되는지 살펴보자.

const newVar = 23;
let myNumber = newVar;
myNumber += 1;
const myString = 'abcd';
const myArray = [];

최종적으로 아래와 같이 할당된다.

숫자와 문자열과 같은 정적인 값은 순서대로 stack에 push되고, object value는 heap에 들어가게 되며, heap영역의 주소는 stack에 push된다. 이 방식이 일반적으로 stack과 heap에서의 일이 분리되는 방식이다.

 

stack memory

v8은 process당 하나의 stack을 가진다. 해당 영역에는 method/function frames, primitive value(기본 값), object를 가리키는 pointer가 저장된다. stack memory의 제한은 --stack_size라는 v8의 flag를 통해 설정할 수 있다.

 

heap memory

v8이 object나 dynamic data를 저장하는 공간이다. 해당 영역은 GC(Garbage Collection)가 위치하며 가장 큰 memory 영역이다. 모든 heap memory영역이 garbage collection이 일어나는 것은 아니고, 오직 young과 old space만이 gc에 의해 관리된다. 더욱 세분화해보자.

 

하기에 표현되는 "살아있다"는 해당 영역에 위치함을 나타낸다.

  • New Space: New space 혹은 "Young generation"은 새로운 object들이 살아있고, 대부분의 이 object들은 짧게 산다. 이 space는 크기가 작고, JVMS0 & S1과 같이 두개의 smie-space로 이루어져 있다. 이 공간은 추후에 살펴볼 "Scavenger(Minor GC)"에 의해 관리된다. New space의 크기는 --mini_semi_space_size(초기화)와 --max_semi_space_size(최대) v8 flag들에 의해 조절된다.
  • Old Space: Old space 혹은 "Old generation"은 "New space"에서 두번의 minor gc cycle을 거친 object들이 살아남아 이동하게 되는 곳이다. 이 space는 Major GC(Mark-Sweep & Mark-Compact)에 의해 관리된다. --initial_old_space_size(초기화)와 --max_old_space_size(최대) v8 flag들에 의해 조절된다. 이 공간은 두개로 나뉜다.
    • Old pointer space: 다른 object를 가리키는 pointer가 있는 살아남은 object를 포함한다.
    • Old data space: 오직 data(다른 object를 가리키는 pointer를 제외한)만을 가진 object를 포함한다. 문자열, boxed number, unboxed double형 배열은 두 번의 minor GC cycle 동안 "New space"에서 살아남은 후 여기로 이동한다. [ boxed number : 기본형(primitive type)을 객체(object)로 변환하는 것을 의미 (ex. 정수형(int)을 Integer 객체로 변환하는 것.), arrays of unboxed double : 기본형 더블(double) 타입의 배열을 의미 ]
  • Large object space: 다른 space의 크기 제한보다 큰 object가 있는 곳이다. 각 객체는 자체 [mmap](https://en.wikipedia.org/wiki/Mmap)'d 메모리 영역을 갖는다. 큰 object들은 절대로 gc에 의해 이동되지 않는다.
  • Code-space: 이 곳은 Just In Time(JIT) compiler가 compile된 code block들을 저장하는 곳이다. 이 space는 실행가능한 memory가 위치하는 유일한 공간이다.(또한, code는 "Large object space"에도 위치 할 수 있으며, 이 역시 실행가능하다.)
  • Cell space, property cell space, and map space: 이 공간은 각각 Cells, PropertyCells, maps를 포함한다. 각각의 공간들은 모두 같은 크기의 object를 포함하고 있으며 어떤 종류의 object를 가리키는지에 대한 제약이 있어 collection을 간소화한다.

이러한 각 space는 set of pages으로 구성됩니다. page는 운영 체제의 mmap(또는 [MapViewOfFile](https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile)Windows에서)에 의해 할당되며 연속된 memory chunk이다. 각 페이지의 크기는 대형 개체 공간을 제외하고 1MB이다.

 

이제는 아까보단 긴 코드로 어떻게 할당되는지 보자.

class Employee {
  constructor(name, salary, sales) {
    this.name = name;
    this.salary = salary;
    this.sales = sales;
  }
}

const BONUS_PERCENTAGE = 10;

function getBonusPercentage(salary) {
  const percentage = (salary * BONUS_PERCENTAGE) / 100;
  return percentage;
}

function findEmployeeBonus(salary, noOfSales) {
  const bonusPercentage = getBonusPercentage(salary);
  const bonus = bonusPercentage * noOfSales;
  return bonus;
}

let john = new Employee("John", 5000, 5);
john.bonus = findEmployeeBonus(john.salary, john.sales);
console.log(john.bonus);

 

아래의 링크를 통하여 어떻게 program이 실행되고, 어떻게 stack과 heap영역이 활용되는지 볼 수 있다.

https://speakerdeck.com/deepu105/v8-memory-usage-stack-and-heap

 

자세한 설명은 하단의 링크가 매우 잘 정리되어 있다.

https://velog.io/@imnotmoon/JavaScript-V8-Engine%EC%9D%B4-%EB%A9%94%EB%AA%A8%EB%A6%AC%EB%A5%BC-%EA%B4%80%EB%A6%AC%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95

 

V8 Memory management: Garbage collection

program이 자유롭게 사용할 수 있는 크기보다(v8 flag설정에 따라) heap에 더 많은 memory를 할당하려고 할시에 out of memory error가 발생한다. 잘못된 heap관리는 memory leak으로 이어질 수 있다.

 

v8은 gc에 의해 heap memory를 관리한다. 간단히 말해, 더이상 stack에서 직간접적으로(다른 object의 reference) 쓰이지 않는 orphan object(고아객체)가 사용하는 memory를 해제하여 새 object생성을 위한 space를 만든다.

 

Orinoco는 main thread를 해제하기 위해 병렬, 증가 및 concurrent technique를 활용하기 위한 v8 gc project의 
코드명이다.

 

v8의 gc는 v8 process에서 사용되지 않는 memory를 회수하여 재사용할 수 있도록 한다.

v8 gc는 generational하다.( heap의 object는 age에 따라 묶이며 다른 stage에서 지워진다.)

v8에는 gc에 활용되는 두 stage와 세가지 다른 알고리즘이 있다.

 

Minor GC(Scavenger)

이 타입의 gc는 young혹은 new generation을 compact하고 깨끗하게 유지한다. objetc는 상당히 작은( heuristic에 따라 1~8MB 사이 ) 새 space에 할당된다. new space에서의 할당은 매우 저렴(할당 비용이 저렴)하다. 새 object를 위한 공간을 예약할 때마다 증가하는 allocation pointer가 존재한다. allocation pointer가 새 space의 끝에 도달하였을 때, minor gc가 trigger된다. 이 process는 Scavenger라고도 하며 Cheney algorithm으로 구현된다. 자주 발생되며 병렬 helper thread를 사용하며 매우 빠르다.

 [ cheney algorithm : https://zbvs.tistory.com/28 ]

 

- process

새로운 space는  동일한 크기를 가진 ( to-space와 from-space로) 두개의 공간으로 나뉜다. 대부분의 allocation은 from-space에서 이루어진다(항상 old-space에서 할당되는 실행코드와 같은 특정 종류의 object 제외). from-space가 채워지면 minor gc가 trigger된다.

 

과정은 아래의 slide를 참고하자.

https://speakerdeck.com/deepu105/v8-minor-gc

 

1. 새로운 객체가 from-space에 생성된다.

2. v8은 from-space에서 필요한 memory를 가져오려하지만 여유 space가 없는 경우 minor gc를 trigger한다. 

3. minor gc는 gc의 root에서 시작하여 from-space에서 object graph를 재귀적으로 탐색하여 사용되거나 살아있는 object들을 탐색하고, 이러한 object들을 to-space로 이동한다. 이 때, 이러한 object를 참조하는 모든 object도 to-space의 page로 이동하고 해당 pointer를 업데이트한다. 이 작업을 from-space의 모든 object가 scan될 때 까지 반복하며, 해당 작업이 끝날 시에 to-space가 자동으로 압축되어 fragmentation이 감소한다(compaction).

4. 이제 minor gc는 현재 남아있는 모든 object는 garbage이므로 free시킨다.

5. minor gc는 to-space와 from-space를 교환하고, 이제 다시 모든 object는 from-space에 위치하며 to-space는 비어있다.

6. 새로운 object는 from-space에 할당되며 시간이 지나 from-space에 더 많은 object가 존재한다고 가정한다.

7. 또다시 여유 space가 없을 경우, minor gc가 trigger된다. 위의 process가 반복되고 살아남은 object들은 old space로 이동한다. 첫 minor gc에서 살아남은 object는 to-space로 이동하고, 나머지 garbage는 from-space에서 free된다.

8. to-space와 from-space를 교환하고 모든 object는 from-space에 있으며 to-space는 비어있는 상태이다.

9. 이제 다시 새 object는 from-space에 메모리를 할당한다.

 

이 과정은 stop-the-world process이지만, 매우 빠르고 효율적이기에 무시해될 정도의 수준이다. 

 

Magor GC

이 타입의 gc는 old generation을 compact하고 깨끗하게 유진한다. old generation의기준은 scavenger에서 이루어지는 두번의 과정으로부터 살아남은 object들이다. 이는 v8이 old generation의 공간이 충분하지 않다고 판단될 때, trigger된다. 또한, cheney algorithm은 young generation과 같이 작은 공간에 효율적이기에  사용되지 않는다. magor gc는 mark-sweep-compact 3단계 algorithm을 사용하여 수행된다.

1. Marking

scavenger와 같은 느낌으로 garbage collector가 사용중인 object와 사용중이지 않은 object를 식별한다. 이는 reference가 stack에서 heap방향으로 이루어지기에 stack pointer에서부터 시작해 이루어진다. heap momory를 방향성 graph로 간주하여 DFS를 통해 reference여부를 판단하고, 사용중인 object일시에 marking해둔다.

 

2. Sweeping

heap을 탐색한 이후에 살아있는 것으로 marking되지 않은 모든 object의 memory address를 기록한다. 해당 단계에서는 free되는 것이 아닌 단순히 "기록"이 이루어지는 것이다. 기록되는 것은 free-list에 있으며 이후에 참조하여 재사용된다.

 

3. Compacting

fragmentation을 줄이는 단계이다. 필요할 시에 sweeping이후 모든 살아있는 object들이 함께 이동한다. 이 과정을 통해 fragmentation을 감소시키고, 새 object에 memory를 할당하는 성능이 향상된다.

 

major gc역시 stop-the-world process이지만, scavenger와 달리 무거운 과정이므로 아래의 기술들을 사용한다.(아마 몇년된 기술 내용이라 현재에는 바뀐 듯 하다)

 

Incremental GC

Concurrent marking

Concurrent sweeping/compacting

Lazy sweeping

 


https://velog.io/@imnotmoon/JavaScript-V8-Engine%EC%9D%B4-%EB%A9%94%EB%AA%A8%EB%A6%AC%EB%A5%BC-%EA%B4%80%EB%A6%AC%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95

https://dev.to/jennieji/memory-management-in-v8-garbage-collection-and-improvements-18e6

https://deepu.tech/memory-management-in-v8/

https://v8.dev/blog/optimizing-v8-memory

반응형

'v8' 카테고리의 다른 글

v8 macro / library(cppgc)  (0) 2024.07.28
V8 extras  (0) 2024.05.06
Jank Busters Part One  (0) 2024.05.05
Custom startup snapshots  (0) 2024.04.25
Code caching  (0) 2024.04.23

+ Recent posts