프로그래밍/JavaScript

V8 Engine의 integer, string이 메모리 어디에 할당 되는가?

Hwan2 2023. 1. 23. 18:31
반응형

JavaScript는 debug하기가 어렵습니다. 때문에 node에서 컴파일러로 사용되는 V8 engine의 docs를 보거나,

여러 사람들이 작성한 Reference를 봐야합니다.

 

책 JavaScript Deep Dive에서 간략하게 JavaScript메모리에 대해 설명하고 있는데, 이를 좀 더 파보려고 합니다.

 

1. V8 Engine 메모리 구조

V8 Engine에는 기본적으로 Stack과 Heap메모리가 있으며, Literal을 저장하기위한 Constant pool이 있습니다.

 

2. Integer가 저장되는 방식.

Integer는 기본적으로 V8 engine의 stack pointer에 저장됩니다.

저장되는 영역은 SMI(Small Integer)라는 곳에 저장되는데, 운영되는 OS에 따라 32Bit, 64Bit저장 방식이 있습니다.

 

32Bit방식에서는 최대 31비트까지 저장할 수 있습니다.(0xFFFFFFFE)

나머지 1비트는 flag값으로 31비트 이상되는 값은 SMI가 아닌 Heap영역에 저장되며, Double형태를 가집니다.

64Bit방식에서는 최대 32Bit까지 저장이됩니다. 

여기서 MSB는 부호를 나타내고 (+, -) 마지막 LSB는 Integer의 flag를 나타냅니다.

출처 : http://www.egocube.pe.kr/lecture/content/html-javascript/202004070001#:~:text=BigInt%20%2D%20JavaScript%20%7C%20MDN-,SMI(Small%20Integer)%EC%99%80%20%ED%9E%99%20%EA%B0%9C%EC%B2%B4,-%EA%B8%B0%EB%B3%B8%EC%A0%81%EC%9C%BC%EB%A1%9C%20V8%20%EC%97%94%EC%A7%84

32Bit혹은 64Bit를 넘어서면 Heap에 할당됩니다.

 

이를 확인해보면 다음과 같습니다.

function test1() {
        let hwan1 = 123123123123
}

function test2() {
        let a = 9876;
}
test1()
test2()
$  node --print-bytecode  --print-bytecode-filter='test*' test.js > code.txt

[generated bytecode for function: test1 (0x000107d51b19 <SharedFunctionInfo test1>)]
Parameter count 1
Register count 1
Frame size 8
   34 S> 0x107d524de @    0 : 12 00             LdaConstant [0]
         0x107d524e0 @    2 : 26 fb             Star r0
         0x107d524e2 @    4 : 0d                LdaUndefined
   47 S> 0x107d524e3 @    5 : aa                Return
Constant pool (size = 1)
0x107d52481: [FixedArray] in OldSpace
 - map: 0x0001071c0729 <Map>
 - length: 1
           0: 0x000107d52499 <HeapNumber 123123123123.0>
Handler Table (size = 0)
Source Position Table (size = 6)
0x000107d524e9 <ByteArray[6]>


[generated bytecode for function: test2 (0x000107d51b69 <SharedFunctionInfo test2>)]
Parameter count 1
Register count 1
Frame size 8
   78 S> 0x107d52596 @    0 : 00 0c 94 26       LdaSmi.Wide [9876]
         0x107d5259a @    4 : 26 fb             Star r0
         0x107d5259c @    6 : 0d                LdaUndefined
   84 S> 0x107d5259d @    7 : aa                Return
Constant pool (size = 0)
Handler Table (size = 0)
Source Position Table (size = 7)
0x000107d525a1 <ByteArray[7]>

※실행한 OS는 64Bit입니다.

test1()의 Debug결과를 확인해보면 Number가 32Bit를 넘어서서 Heap에 할당된 것을 확인할 수 있으며,

Constant pool (size = 1) 인걸 알 수 있습니다.

해당 숫자는 Literal로 선언되어 있어서 Constant pool에 등록된 것입니다.

 

반면 test2()의 경우 Debug결과 Constant pool에도 등록이 안되었으며, Heap에 등록된 내용이 없습니다.

이것은 해당 Literal값이 stack에 들어갈 때 pointer자체에 해당 값이 박혔기 때문입니다.

 

3. String이 저장되는 방식

String의 경우에는 문자열 취급이라서 모든 Literal은 Constant pool 에 들어가게 됩니다.

테스트 코드를 보겠습니다.

function test1() {
        let hwan1 = "hwan1"
        let hwan2 = "hwan1"
}

function test2() {
        let hwan1 = "hwan2"
        let hwan2 = new String("hwan2")
}
test1()
test2()
[generated bytecode for function: test1 (0x000107611b31 <SharedFunctionInfo test1>)]
Parameter count 1
Register count 2
Frame size 16
   34 S> 0x1076124e6 @    0 : 12 00             LdaConstant [0]
         0x1076124e8 @    2 : 26 fb             Star r0
   55 S> 0x1076124ea @    4 : 12 00             LdaConstant [0]
         0x1076124ec @    6 : 26 fa             Star r1
         0x1076124ee @    8 : 0d                LdaUndefined
   63 S> 0x1076124ef @    9 : aa                Return
Constant pool (size = 1)
0x107612499: [FixedArray] in OldSpace
 - map: 0x000106940729 <Map>
 - length: 1
           0: 0x000107611921 <String[#5]: hwan1>
Handler Table (size = 0)
Source Position Table (size = 8)
0x0001076124f1 <ByteArray[8]>


[generated bytecode for function: test2 (0x000107611b81 <SharedFunctionInfo test2>)]
Parameter count 1
Register count 4
Frame size 32
   98 S> 0x1076125be @    0 : 12 00             LdaConstant [0]
         0x1076125c0 @    2 : 26 fb             Star r0
  119 S> 0x1076125c2 @    4 : 13 01 00          LdaGlobal [1], [0]
         0x1076125c5 @    7 : 26 f9             Star r2
         0x1076125c7 @    9 : 12 00             LdaConstant [0]
         0x1076125c9 @   11 : 26 f8             Star r3
         0x1076125cb @   13 : 25 f9             Ldar r2
  119 E> 0x1076125cd @   15 : 65 f9 f8 01 02    Construct r2, r3-r3, [2]
         0x1076125d2 @   20 : 26 fa             Star r1
         0x1076125d4 @   22 : 0d                LdaUndefined
  139 S> 0x1076125d5 @   23 : aa                Return
Constant pool (size = 2)
0x107612569: [FixedArray] in OldSpace
 - map: 0x000106940729 <Map>
 - length: 2
           0: 0x000107611939 <String[#5]: hwan2>
           1: 0x0001069440d1 <String[#6]: String>
Handler Table (size = 0)
Source Position Table (size = 11)
0x0001076125d9 <ByteArray[11]>

test1()을 보면 Constant pool(size = 1) 인걸 확인할 수 있습니다.

즉, 문자열 "hwan1"이라는 Literal은 Constant pool에 등록되어,

2개의 변수가 동시에 하나의 문자열을 참조하고 있는걸 확인할 수 있습니다.

 

또한 Constant pool에 등록된 값들은 Heap에 들어가게되며, String같은 경우 같은 값을 참조하게 됩니다.

 const arr = [];
setTimeout(() => {
    for(let  i = 0;i< 10000;i++) {
        arr.push('hwan');
    }
}, 3000);

const arr2 = [];
setTimeout(() => {
    for(let  i = 0;i< 10000;i++) {
        arr2.push(new String('hwan'));
    }
}, 5000);

Literal로 넣은 String
new로 참조변수를 만들어서 참조하는 String

위 사진을 보면 같은 String에 대해서 같은 주소값을 참조하고 있는걸 확인할 수 있습니다.

특이한 점은 new로 할당된 변수들의 메모리 사용을보면 Literal보다 더 많이 사용하는걸 알 수 있습니다.

 

구조를 보면 다음과 같습니다.

때문에 변수에 할당된 String을 비교해보면 위와같은 결과가 나오는 것을 확인해볼 수 있습니다.

 

참고

https://medium.com/@stankoja/v8-bug-hunting-part-1-setting-up-the-debug-environment-7ef34dc6f2de

 

V8 Bug Hunting Part 1: Setting up the debug environment

Disclaimer: This is not really a write-up, but it’s just me taking notes. The content may chaotic, wrong or there may be better ways to do…

medium.com

https://blog.outsider.ne.kr/1307

 

Node.js의 v8-inpector 디버깅 :: Outsider's Dev Story

Node.js를 디버깅하는 방법에 대해서 몇 번 글을 썼다. * [node.js 디버깅에 ndb 사용하기](https://blog.outsider.ne.kr/538) * [Node.js의 디버거 devtool과 v6의 네이티브 v8_inspector 지원](https://blog.o...

blog.outsider.ne.kr

https://levelup.gitconnected.com/bytefish-vs-new-string-bytefish-what-is-the-difference-a795f6a7a08b

 

‘bytefish’ vs new String(‘bytefish’): What is the difference?

In the following two lines of code, we use two different ways to create strings:

levelup.gitconnected.com

https://stackoverflow.com/questions/61722899/constant-pool-content-lost-when-generating-v8s-bytecode

 

constant pool content lost when generating v8's bytecode

Here is the output generate by execute 'node --print-bytecode --print-bytecode-filter=incrementX index.js > code.txt'.It lost the constant pool content.Only have the pool size.I tested in Node.js

stackoverflow.com

https://blog.dashlane.com/how-is-data-stored-in-v8-js-engine-memory/

 

How is data stored in V8 JS engine memory?

Introduction After working for a few years on embedded systems and industrial PCs, focusing on low-level software development on Linux kernels, RTOS and

blog.dashlane.com

http://www.egocube.pe.kr/lecture/content/html-javascript/202004070001

 

반응형