티스토리 뷰
es-toolkit의 PR을 구경하다가
bench: remove import overhead 라는 PR을 보게 됐다.
해당 PR에는 아래와 같은 수정이 있는데 이게 import의 overhead를 어떻게 줄일 수 있지라는 궁금증이 생겼다.
그리고 gpt에게 물어봤다.
첫 번째 질문
1번에서 2번으로 코드를 수정했는데 import overhead가 줄었어 어떤 차이야?
1번
import { after as afterToolkit } from 'es-toolkit';
import { after as afterLodash } from 'lodash';
2번
import { after as afterToolkit_ } from 'es-toolkit';
import { after as afterLodash_ } from 'lodash';
const afterToolkit = afterToolkit_;
const afterLodash = afterLodash_;
GPT 첫 번째 답변
1. 첫 번째 코드
import { after as afterToolkit } from 'es-toolkit';
import { after as afterLodash } from 'lodash';
- 이 코드는 es-toolkit와 lodash 모듈에서 각각 after라는 함수를 가져와 직접 afterToolkit과 afterLodash에 할당합니다.
- 결과:
import 구문은 모듈의 모든 코드를 실행한 뒤, 필요한 부분(after)을 가져옵니다.
만약 두 모듈(es-toolkit과 lodash)이 크거나 불필요한 부분을 포함하고 있다면 초기 로드 시간과 메모리 사용량이 증가할 수 있습니다.
2. 두 번째 코드
import { after as afterToolkit_ } from 'es-toolkit';
import { after as afterLodash_ } from 'lodash';
const afterToolkit = afterToolkit_;
const afterLodash = afterLodash_;
- 이 코드는 es-toolkit와 lodash에서 after를 각각 afterToolkit_과 afterLodash_라는 이름으로 가져오고, 이후에 변수 할당으로 afterToolkit과 afterLodash를 정의합니다.
- 결과:
import 구문은 여전히 모듈을 실행하고 필요한 after를 가져오지만, 변수 할당을 통해 모듈 초기화 과정에서의 직접 참조를 피합니다.
차이가 발생하는 이유
1. 모듈 실행 및 바인딩
- 첫 번째 코드: afterToolkit과 afterLodash는 직접적으로 모듈에서 가져온 값에 바인딩됩니다. 이런 방식은 모듈 초기화 시점에 더 많은 메타데이터 관리 비용이 발생할 수 있습니다.
- 두 번째 코드: afterToolkit_과 afterLodash_를 가져온 뒤, 이를 로컬 변수에 다시 할당합니다. 이 과정은 런타임에서 바인딩 메타데이터를 단순화시켜 약간의 오버헤드 감소를 유발할 수 있습니다.
2. V8 엔진 최적화
- V8 같은 자바스크립트 엔진은 직접 import한 값과 로컬 변수에 재할당한 값을 다르게 처리합니다.
- 첫 번째 방식은 모듈 네임스페이스 참조 비용이 더 클 수 있습니다.
- 두 번째 방식은 로컬 변수 접근이 더 빠릅니다.
결론
두 번째 방식은 재할당을 통해 import 바인딩의 최적화를 유도하며, 모듈 참조 비용을 줄이는 효과가 있습니다. 따라서 초기화 속도와 런타임 접근 성능에서 약간의 이점을 얻을 수 있습니다.
두 번째 질문
GPT 두 번째 답변
자바스크립트의 **ES 모듈(ESM)**은 다음 단계를 통해 동작합니다:
1.1 모듈 로드 및 파싱 단계
- import 구문이 실행되면, 자바스크립트 엔진은 해당 모듈 파일(예: es-toolkit 또는 lodash)을 찾습니다.
- 모듈 파일을 읽어 들이고 전체 코드를 파싱합니다.
- 필요한 경우 **의존성(다른 import된 모듈)**도 재귀적으로 로드합니다.
1.2 모듈 실행 단계
- 모듈이 파싱되면 모든 코드가 실행됩니다.
- 예를 들어, es-toolkit이나 lodash 모듈 안에 함수 정의, 변수 초기화, 객체 생성, 로직 실행 등이 있으면 이들이 모두 실행됩니다.
- 실행 결과로 모듈의 export 객체가 만들어집니다.
- 이 객체는 모듈이 제공하는 모든 export된 값(함수, 변수, 클래스 등)을 포함합니다.
1.3 필요한 부분 가져오기
- import { after } from 'lodash';처럼 부분 import를 사용할 경우, 엔진은 모듈의 export 객체에서 after라는 이름의 값을 찾아 가져옵니다.
2. 실제 동작 예시
2.1 lodash 모듈 구조 (가상 예시)
// lodash/index.js
export const after = (n, func) => { /* 정의 */ };
export const chunk = (array, size) => { /* 정의 */ };
export const cloneDeep = (value) => { /* 정의 */ };
// 추가적인 내부 로직 (모듈 실행 시 실행됨)
console.log('lodash 모듈이 로드되었습니다.');
2.2 import 구문의 동작
import { after } from 'lodash';
위 코드를 실행하면 다음이 발생합니다:
- lodash/index.js 파일이 로드되고 전체 코드가 실행됩니다.
- 여기서 console.log('lodash 모듈이 로드되었습니다.');도 실행됩니다.
- after, chunk, cloneDeep 함수가 메모리에 정의됩니다.
- 실행 결과로 export 객체가 생성됩니다:
{
after: [Function: after],
chunk: [Function: chunk],
cloneDeep: [Function: cloneDeep]
}
3. import { after }는 이 export 객체에서 after를 찾아 가져옵니다.
3. 모듈 실행의 부작용
모든 모듈의 코드를 실행하는 이유는 모듈이 **부작용(side effect)**을 포함할 수 있기 때문입니다.
예시: 부작용이 있는 모듈
// example.js
console.log('Example 모듈 실행 중...');
export const exampleFunction = () => console.log('Hello, world!');
// main.js
import { exampleFunction } from './example.js';
- main.js를 실행하면, example.js의 모든 코드가 실행되기 때문에 다음이 출력됩니다:
Example 모듈 실행 중...
- 이후 exampleFunction만 가져옵니다.
4. 요약
- import 구문은 모듈의 전체 코드를 실행하여 필요한 export 객체를 생성합니다.
- 필요한 부분만 가져오더라도 모듈 전체 실행은 피할 수 없습니다.
- 따라서 큰 모듈에서는 불필요한 실행 및 메모리 사용이 성능에 영향을 줄 수 있습니다. 이를 최적화하려면 필요한 부분만 export하거나, 로직을 나눠서 import하는 방법이 있습니다.
세 번째 질문
두 번째 코드도 결국 import에서 모든 모듈을 실행하는데 import overhead가 줄어드는 이유는 뭐야?
GPT 세 번째 답변
좋은 질문입니다! 두 번째 코드에서 import overhead가 줄어드는 이유는, 모듈의 실행 자체가 아니라, 변수 접근과 바인딩 방식의 차이에 있습니다. 이를 자세히 설명하겠습니다.
1. 두 코드의 비교
첫 번째 코드:
import { after as afterToolkit } from 'es-toolkit';
import { after as afterLodash } from 'lodash';
두 번째 코드:
import { after as afterToolkit_ } from 'es-toolkit';
import { after as afterLodash_ } from 'lodash';
const afterToolkit = afterToolkit_;
const afterLodash = afterLodash_;
두 코드 모두 모듈 전체를 실행하는 것은 동일합니다. 하지만 import된 값을 사용하는 방식에서 차이가 발생하여 성능에 영향을 미칩니다.
2. import 구문과 바인딩
첫 번째 코드의 동작
- import { after as afterToolkit }는 es-toolkit 모듈에서 after를 가져와 모듈 네임스페이스와 강하게 바인딩합니다.
- afterToolkit은 항상 모듈 네임스페이스를 통해 값을 참조합니다.
- 이는 모듈 내부에서 값이 변경될 가능성을 고려한 동작으로, 런타임 시점마다 모듈 네임스페이스를 확인하게 되어 약간의 오버헤드가 발생합니다.
두 번째 코드의 동작
- import { after as afterToolkit_ }로 가져온 afterToolkit_은 모듈 네임스페이스와 바인딩되지만, 이후:
const afterToolkit = afterToolkit_;
로컬 변수 afterToolkit에 재할당됩니다.
- 로컬 변수는 모듈 네임스페이스와의 연결이 끊어지며, 단순히 메모리 주소만을 참조합니다.
- 로컬 변수 접근은 더 빠르고 메타데이터 오버헤드가 적습니다.
3. 성능 차이의 원인
3.1 모듈 네임스페이스 접근 비용
- 모듈에서 값을 직접 import할 경우, 엔진은 모듈 네임스페이스 객체를 통해 값을 참조해야 합니다.
- 이 객체에는 export된 모든 값과 관련된 메타데이터가 포함되어 있으며, 이로 인해 접근 비용이 조금 더 높아집니다.
- 특히, 반복적으로 함수나 변수를 호출하거나 참조할 때 이 오버헤드가 누적될 수 있습니다.
3.2 로컬 변수 접근 비용
- 두 번째 코드에서는 import한 값을 로컬 변수로 재할당했기 때문에, 이후에는 단순히 로컬 스코프의 변수로 접근합니다.
- 로컬 변수는 모듈 네임스페이스와 독립적으로 작동하며, 접근 속도가 더 빠릅니다.
- V8 같은 자바스크립트 엔진은 로컬 변수에 대해 더 효율적으로 최적화를 수행할 수 있습니다.
4. 실제 성능 차이
- 첫 번째 코드는 모듈 네임스페이스를 통해 값을 참조하기 때문에, 런타임 접근 비용이 조금 더 높습니다.
- 두 번째 코드는 import된 값을 로컬 변수에 재할당하여, 더 빠른 변수 접근을 가능하게 합니다.
- 이 차이는 모듈 내 큰 함수 호출이나 반복문에서 더욱 두드러지게 나타납니다.
5. 왜 import overhead가 줄어드는가?
결론적으로, 두 번째 코드에서 import overhead가 줄어드는 이유는 다음과 같습니다:
- 모듈 네임스페이스 참조 대신 로컬 변수 사용으로 인해 접근 비용이 줄어듭니다.
- 엔진 최적화가 더 효율적으로 적용됩니다.
- 로컬 변수는 스코프와 메모리 구조가 간단하여 최적화가 빠르게 이루어집니다.
- 모듈 실행 자체는 동일하지만, 런타임 접근 속도가 개선되어 반복적인 참조에서 성능 향상을 얻을 수 있습니다.
6. 적용 시점
이 방식은 특히 다음과 같은 경우 유용합니다:
- 모듈에서 가져온 함수나 변수를 반복적으로 사용하는 경우.
- 성능 최적화가 중요한 대규모 프로젝트에서 미세한 개선이 누적될 때.
'토막지식시리즈 > javascript 토막지식' 카테고리의 다른 글
for of vs for (0) | 2024.09.09 |
---|---|
for in vs for of (자바스크립트) (0) | 2021.11.15 |
Typescript에서 interface와 타입별칭으로 정의한 타입의 프리뷰가 다른이유. (0) | 2021.10.15 |
Typescript는 어떻게 동작할까? (왜 safe할까?) (0) | 2021.10.15 |
Typescript 타입 종류 알아보기! (unknown) (0) | 2021.07.10 |
- 20200510
- 20200502
- chapter7
- 20200330
- 20201204
- likelion
- 20200319
- 20200427
- 20200429
- 20200503
- 20200428
- 생활코딩리눅스
- 20200504
- 20200512
- 20200425
- 20200423
- 20200622
- 20200317
- chapter8
- 20200624
- 20200804
- 20200417
- 20200413
- 20200420
- 백준
- 20200406
- 20200421
- 20200415
- 20200403
- 20200424
- Total
- Today
- Yesterday