티스토리 뷰
[성능비교 / 궁금증 해결] 2차원 배열 선언에 대한 Array.from(), Array.prototype.map() 시간과 메모리 비교 (Open)
강태풍_719 2023. 7. 24. 13:43🚀 개요
파이썬이나, C언어와 같이 손쉽게 배열을 선언하지 못한다. Javascript에서는 아직 공식적으로 2차원 배열에 대한 선언이 없다.
const array = [[1,2,3],[4,5,6],[7,8,9]]
위와 같은 리터럴적인 표현으로는 2차원 배열이 가능하지만, col과 row 값이 100개만 넘어서도 다음과 같이 선언하기 힘들다. 또는 iteration을 통하여 array.prototype.push() 로 2차원 배열이 선언이 가능하지만, 간단한 2차원 배열 선언에도 시간이 오래 걸린다. 그래서 보통 다음과 같은 방법으로 2차원 배열을 선언한다.
const [col, row] = [100, 100]
Array.from(Array(col),()=>new Array(row).fill(0))
Array(col).fill(0).map(()=>new Array(row).fill(0))
보통 두가지 방식으로 2차원 배열을 선언하는데, 두 가지 모두 같은 방식으로 동작한다. 하지만 시간적인 측면과 메모리 사용적인 측면에서, 차이를 보인다. 앞으로 우린 어떤 메소드를 사용해서 2차원 배열을 선언해야 할까?
🧐Array.from() 과 Array.prototype.map()
Array.from() 은 Array() 클래스에서 직접 메소드를 호출하는 Static Method이다. ArrayLike 객체(인덱스와 값으로 이루어졌고, length 속성을 가짐)를 입력받아, Array를 반환하는 메소드이다.
반면에 Array.prototype.map() 은 직접 객체에 선언하는 메소드이며 iterable 객체에 대하여 index와 value값을 인자로 받는 콜백함수로 리턴을 받아 새로운 iterable 객체를 반환하는 메소드이다.
Array.from()은 선택 매개변수인 mapFn를 가지는데, 배열(혹은 배열 서브클래스)의 각 요소를 맵핑할 때 사용할 수 있습니다. 즉,Array.from(obj, mapFn, thisArg)는 중간에 다른 배열을 생성하지 않는다는 점을 제외하면Array.from(obj).map(mapFn, thisArg)와 같습니다.
-Mozilla 공식문서
공식문서로 보아 아래 코드 블럭이 같은 결과를 리턴한다고 볼 수 있다. 하지만 map 매소드는 중간에 다른 배열을 생성한다는 점에서 메모리사용 측면에서 더 많이 사용할 것이라고 예측할 수 있다.
Array.from(Array(col),()=>new Array(row).fill(0))
Array(col).fill(0).map(()=>new Array(row).fill(0))
col과 row 값이 커짐과 iteration 반복이 커짐에 따라 어떠한 시간적 차이를 보일까?
😛 시간 측정
시간측정에는 console.time()과 console.timeEnd() 을 이용하여 측정하였고, iteration으로 반복 횟수, row와 col로 배열의 사이즈를 조정하였다.
const col = 1000
const row = 1000
const array2thFrom = () => Array.from(Array(col),()=>new Array(row).fill(0))
const array2thMap = () => Array(col).fill(0).map(()=>new Array(row).fill(0))
const iterations = 100
console.time('Array.from()');
for(let i = 0; i < iterations; i++){
array2thFrom();
};
console.timeEnd('Array.from()')
console.time('Array.prototype.map()');
for(let i = 0; i < iterations; i++){
array2thMap();
};
console.timeEnd('Array.prototype.map()')
Array.from() 시간 측정
반복 \ row,col | 10 | 100 | 1000 | 10000 |
1 | 0.107ms | 0.131ms | 8.319ms | 739.297ms |
10 | 0.113ms | 0.851ms | 41.356ms | 5.048s |
100 | 0.318ms | 4.162ms | 254.698ms | 46.240s |
1000 | 3.216ms | 19.13ms | 2.327s | 6m 19s |
Array.prototype.map() 시간 측정
반복 \ row,col | 10 | 100 | 1000 | 10000 |
1 | 0.037ms | 0.061ms | 7.225ms | 950.521ms |
10 | 0.059ms | 0.629ms | 25.748ms | 5.158s |
100 | 0.166ms | 3.049ms | 298.101ms | 45.016s |
1000 | 1.216ms | 16.836ms | 2.303s | 6m 22s |
OS나 컴퓨터의 환경에 따라 시간 측정이 달라질 수 있다는 점을 감안하더라도, 예상 외로 다이나믹한 큰 변화는 보이지 않았고, 사용하기 편한 메소드를 사용하면 될 것 같다.
결과상 볼 수 있었듯이, 대부분의 Array.prototype.map()은 2중 배열의 크기가 10, 100, 1000에서 더 빠른 것을 볼 수 있고, Array.from() 은 2차원 배열의 크기가 10000 이상일 경우 map 메소드 보다 빠른 점을 볼 수 있었으나,
굳이 사용을 따지자면, 2차원 배열의 크기에 따라 1000 이하일 경우 map 메소드를, 10000이상일 경우 from 정적 메소드를 사용하는것을 추천한다.
😛 메모리 측정(Open)
process.memoryUsage()
메모리를 측정하기 위해 처음으로, process.memoryUsage() 를 사용하였다. 이는 JS의 런타임의 메모리 할당을 알기 위하여 사용하였다. node.js 공식문서에 따르면
heapTotal and heapUsed refer to V8's memory usage.
external refers to the memory usage of C++ objects bound to JavaScript objects managed by V8.
rss, Resident Set Size, is the amount of space occupied in the main memory device (that is a subset of the total allocated memory) for the process, including all C++ and JavaScript objects and code.
arrayBuffers refers to memory allocated for ArrayBuffers and SharedArrayBuffers, including all Node.js Buffers.
This is also included in the external value. When Node.js is used as an embedded library, this value may be 0 because allocations for ArrayBuffers may not be tracked in that case.
결과로 rss, heapTotal, heapUsed, external, arrayBuffers 를 키 값으로 가지는 객체를 반환한다. rss 는 main memory device에서 사용되는 총량이고, 우리가 실질적으로 살펴보는 메모리는 heapUsed이다. 이 점을 이용하여 로그를 찍어보았다.
col 과 row 가 100인 2중 배열의 결과 로그를 찍어본 결과, rss와 heapUsed 모두 증가하였으며, garbage collection이 작동을 한다면, reference 를 가지고 있지 않는 배열들의 메모리 할당을 지워버려 memory가 iteration 값과 무관할 줄 알았으나, 어쩐지 iteration 값과 할당된 memory값이 비례하는 것을 확인할 수 있었다. 또한 런타임을 여러번 실행 시킬 때마다, 결과 값이 크게 달라져서, 유의미한 결과는 얻지 못하였다.
이와같은 방법으로 정적으로 메모리를 측정하는 것이 아니라, 브라우저로 런타임 동안의 메모리를 측정하는 것이 각 메소드 간 사용되는 메모리양을 정확하게 측정 할 수 있을 것으로 보인다.
const col = 100
const row = 100
const array2thFrom = () => Array.from(Array(col),()=>new Array(row).fill(0))
const array2thMap = () => Array(col).fill(0).map(()=>new Array(row).fill(0))
const iterations = 1000
let array1, array2
const used1 = process.memoryUsage()
console.log(used1)
console.time('Array.from()');
for(let i = 0; i < iterations; i++){
array1 = array2thFrom()
};
console.timeEnd('Array.from()')
const used2 = process.memoryUsage()
console.log(used2)
console.time('Array.prototype.map()');
for(let i = 0; i < iterations; i++){
array2 = array2thMap()
};
console.timeEnd('Array.prototype.map()')
const used3 = process.memoryUsage()
console.log(used3)
V8 Engine
ECMA Script와 Web Assembly를 위한 엔진이다. 자바스크립트는 Python과 같은 인터프리터 언어이고, 따라서 코드를 해석하고 실행하는 '실행기'가 필요하다. V8 Engine이 그 역할을 한다. C++로 작성되었고, C++ 애플리케이션에 V8 Engine을 내장시킬 수 있다.git : v8 github
- V8 Engine이 등장하기 전에도 분명 자바스크립트를 위한 엔진은 존재했다. V8 Engine은 구글이 개발한 엔진으로, 이전 엔진의 문제점을 개선했다. 개선한 문제들은 아래와 같다.
- JavaScript 코드를 브릿지 없이 바이트 코드로 바로 변환한다.컴파일된 JavaScript 코드를 캐싱한다.
- imnotmoon velog 블로그
- 브라우저를 이용한 런타임 메모리 측정 (Open)
추후 작성 예정
💡새롭게 알게 된 것
2차원 배열의 크기에 따라 1000 이하일 경우 map 메소드를, 10000이상일 경우 from 정적 메소드를 사용하는것이 시간 효율상 좋다.
시간 측정시 console.time()과 console.timeEnd() 을 이용하여 시간을 측정 할 수 있다.
process.memoryUsage() 를 이용하여 JS 런타임 시의 메모리를 정적으로 측정할 수 있다.
참조
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/from
'궁금증 해결' 카테고리의 다른 글
[Javascript] Sort의 알고리즘이 브라우저마다 다른 이유 (0) | 2023.08.30 |
---|
- Total
- Today
- Yesterday
- 깃허브
- 국비지원
- 개발자 회고
- 로딩성능
- JavaScript
- next 14
- 성능 측정
- 깃허브 사용법
- 이미지최적화
- 부트캠프
- Tailwind CSS
- 사이드프로젝트
- 인프콘 2023
- netlify
- ci/cd
- 모노레포
- 프론트엔드개발자
- 프론트엔드 성능
- FE
- 성능 개선
- 국비지원취업
- RARS
- Github Actions
- 패스트캠퍼스
- FE 주니어
- Not Working
- Vercel
- kpt
- 리뷰
- no found
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |