source

<=>이 V8에서 이 코드 조각을 사용하는 것보다 느린 이유는 무엇입니까?

ittop 2023. 10. 29. 19:59
반응형

<=>이 V8에서 이 코드 조각을 사용하는 것보다 느린 이유는 무엇입니까?

V8로 자바스크립트 속도 제한 깨기 슬라이드를 읽고 있는데 아래 코드와 같은 예시가 있습니다.왜 그런지 알 수가 없습니다.<=보다 느립니다.<이 경우, 누가 설명해 줄 수 있습니까?어떤 의견이든 감사히 받겠습니다.

느림:

this.isPrimeDivisible = function(candidate) {
    for (var i = 1; i <= this.prime_count; ++i) {
        if (candidate % this.primes[i] == 0) return true;
    }
    return false;
} 

(힌트: primes는 길이 prime_count의 배열입니다)

더 빠른 속도:

this.isPrimeDivisible = function(candidate) {
    for (var i = 1; i < this.prime_count; ++i) {
        if (candidate % this.primes[i] == 0) return true;
    }
    return false;
} 

[자세한 내용] 속도 향상 유의, 현지 환경 테스트에서 결과는 다음과 같습니다.

V8 version 7.3.0 (candidate) 

느림:

 time d8 prime.js
 287107
 12.71 user 
 0.05 system 
 0:12.84 elapsed 

더 빠른 속도:

time d8 prime.js
287107
1.82 user 
0.01 system 
0:01.84 elapsed

다른 답변과 코멘트는 두 루프의 차이점은 첫 번째 루프가 두 번째 루프보다 한 번 더 많은 반복을 실행한다는 점이라고 언급합니다.이는 사실이지만, 25,000개의 요소로 확장되는 배열에서는 한 번의 반복이 약간의 차이만 발생합니다.야구장 추측대로, 우리가 성장하면서 평균 길이를 12,500이라고 가정한다면, 우리가 예상할 수 있는 차이는 약 1/12,500, 혹은 단지 0.008%일 것입니다.

여기서 성능 차이는 한 번의 추가 반복으로 설명되는 것보다 훨씬 크며, 이 문제는 프레젠테이션이 거의 끝날 무렵에 설명됩니다.

this.primes는 연속 배열이고(모든 원소는 값을 갖습니다) 원소는 모두 숫자입니다.

자바스크립트 엔진은 이러한 배열을 실수로 숫자를 포함하지만 다른 값을 포함하거나 값을 포함하지 않을 수 있는 객체의 배열 대신 실제 숫자의 단순한 배열로 최적화할 수 있습니다.첫 번째 형식은 접근 속도가 훨씬 빠릅니다. 코드가 덜 들고, 배열도 훨씬 작아서 캐시에 더 잘 들어맞습니다.하지만 이렇게 최적화된 포맷이 사용되지 않을 수 있는 몇 가지 조건이 있습니다.

한 가지 조건은 배열 요소 중 일부가 누락된 경우입니다.예를 들어,

let array = [];
a[0] = 10;
a[2] = 20;

이제 그 가치는 무엇입니까?a[1]? 가치가 없습니다.(가치가 있다고 말하는 것도 옳지 않습니다.undefined- 를 포함하는 배열 요소undefined값이 완전히 누락된 배열 요소와 다릅니다.)

이것을 숫자로만 표현할 수 있는 방법이 없기 때문에 자바스크립트 엔진은 덜 최적화된 포맷을 사용할 수 밖에 없습니다. 만약a[1]다른 두 요소와 마찬가지로 숫자 값을 포함하고 있으므로 배열은 숫자 배열로만 최적화될 수 있습니다.

어레이가 최적화되지 않은 형식으로 강제로 전환되는 또 다른 이유는 프레젠테이션에서 설명한 것처럼 어레이의 경계를 벗어난 요소에 액세스하려고 하는 경우일 수 있습니다.

첫번째 루프는<=배열의 끝을 지나는 요소를 읽으려고 시도합니다.마지막 추가 반복에서는 다음과 같이 알고리즘이 여전히 올바르게 작동합니다.

  • this.primes[i]에 평가합니다.undefined왜냐면i배열 끝을 지나갑니다.
  • candidate % undefined(어떠한 가치라도)candidate)로 평가합니다.NaN.
  • NaN == 0에 평가합니다.false.
  • .return true실행되지 않습니다.

즉, 추가적인 반복이 전혀 일어나지 않은 것과 같습니다. 나머지 논리에는 아무런 영향을 미치지 않습니다.코드는 추가적인 반복을 하지 않는 것과 같은 결과를 가져옵니다.

그러나 거기에 도달하기 위해 배열의 끝을 지나 존재하지 않는 요소를 읽으려고 했습니다.이로 인해 배열이 최적화되지 않았거나 최소한 이 대화가 진행될 당시에는 그렇게 했습니다.

두번째 루프는<배열 내에 존재하는 요소만 읽으므로 최적화된 배열 및 코드를 허용합니다.

그 문제는 강연의 90-91쪽에 설명되어 있고, 그 전과 후의 페이지에 관련된 논의가 있습니다.

우연히 이 Google I/O 프레젠테이션에 참석하여 연사(V8 저자 중 한 명)와 대화를 나누었습니다.저는 배열의 끝을 지나치는 것을 하나의 특정한 상황을 최적화하기 위한 잘못된 (뒤늦은) 시도로 읽는 것을 포함하는 기술을 제 자신의 코드에 사용해 왔습니다.그는 배열의 끝을 지나서도 읽으려고 하면 단순하게 최적화된 형식이 사용되는 것을 막을 수 있다는 것을 확인했습니다.

V8 작성자가 말한 내용이 여전히 사실일 경우 어레이의 끝을 기준으로 판독하면 최적화되지 않고 더 느린 형식으로 되돌아가야 합니다.

이제 V8이 이 경우를 효율적으로 처리할 수 있도록 개선되었거나 다른 자바스크립트 엔진이 이 경우를 다르게 처리할 수 있습니다.저는 그것에 대해 이러쿵저러쿵 알 수는 없지만, 이 최적화 해제가 바로 그 발표에 대해 말하고 있었던 것입니다.

저는 구글에서 V8을 작업하고 있으며, 기존의 답변과 코멘트 외에 추가적인 통찰력을 제공하고 싶었습니다.

참고로 슬라이드의 전체 코드 예시는 다음과 같습니다.

var iterations = 25000;

function Primes() {
  this.prime_count = 0;
  this.primes = new Array(iterations);
  this.getPrimeCount = function() { return this.prime_count; }
  this.getPrime = function(i) { return this.primes[i]; }
  this.addPrime = function(i) {
    this.primes[this.prime_count++] = i;
  }
  this.isPrimeDivisible = function(candidate) {
    for (var i = 1; i <= this.prime_count; ++i) {
      if ((candidate % this.primes[i]) == 0) return true;
    }
    return false;
  }
};

function main() {
  var p = new Primes();
  var c = 1;
  while (p.getPrimeCount() < iterations) {
    if (!p.isPrimeDivisible(c)) {
      p.addPrime(c);
    }
    c++;
  }
  console.log(p.getPrime(p.getPrimeCount() - 1));
}

main();

우선, 성능 차이는 회사와 관계가 없습니다.<그리고.<=직접적인 연산자.그러니 제발 피하려고 후프를 통과하지 말아주세요.<=당신이 스택 오버플로에서 속도가 느리다고 읽었기 때문에 당신의 코드에서 --- 그렇지 않습니다!


둘째, 사람들은 배열이 "신성한 것"이라고 지적했습니다.OP의 게시물의 코드 조각에서는 명확하지 않았지만, 초기화하는 코드를 보면 명확합니다.this.primes:

this.primes = new Array(iterations);

따라서 배열이 완전히 채워지거나 포장되거나 연속적으로 끝나더라도 V8에서 요소 종류가 있는 배열이 생성됩니다.일반적으로 홀리 어레이의 작업은 패킹된 어레이의 작업보다 느리지만, 이 경우 차이는 무시할 수 있습니다. 이는 히트할 때마다 1개의 Smi(작은 정수) 검사(구멍으로부터 보호)에 해당합니다.this.primes[i]안의 고리에isPrimeDivisible 일 아닙니다걱정 말아요!

TL;DR 어레이는 여기서 문제가 아닙니다.


다른 사람들은 코드가 범위를 벗어난다고 지적했습니다.일반적으로 어레이 길이 이상의 판독은 피하는 것이 좋으며, 이 경우 성능 저하를 크게 피할 수 있었을 것입니다.근데 왜요?V8은 이러한 아웃오브바운드 시나리오 중 일부를 약간의 성능 영향만으로 처리할 수 있습니다.그렇다면 이 특별한 사건은 뭐가 특별한 겁니까?

경계를 벗어난 읽기 결과는 다음과 같습니다.this.primes[i]존재undefined다음 라인에서:

if ((candidate % this.primes[i]) == 0) return true;

그리고 그것은 우리를 진짜 문제로 이끌었습니다.%연산자를 integer가 아닌 피연산자와 함께 사용하고 있습니다!

  • integer % someOtherInteger매우 효율적으로 계산할 수 있습니다. 자바스크립트 엔진은 이 경우에 매우 최적화된 기계 코드를 생성할 수 있습니다.

  • integer % undefined반면에 효율성이 떨어지는 것에 해당합니다.Float64Mod,부터undefined더블로 표현됩니다.

코드 스니펫은 실제로 다음을 변경함으로써 개선될 수 있습니다.<=안으로<다음 라인에서:

for (var i = 1; i <= this.prime_count; ++i) {

...왜냐하면<=그보다 더 우월한 연산자입니다.<, 하지만 이것이 이 특별한 경우에 bounds 밖의 판독을 피할 수 있다는 이유만으로 말입니다.

TL;DR 느린 루프는 배열 'out-of-bounds'에 액세스하기 때문이며, 이는 엔진이 더 적은 또는 더 적은 최적화로 함수를 다시 컴파일하거나 이러한 최적화 중 어떤 것으로도 함수를 컴파일하지 않도록 강제합니다((JIT-Compiler가 첫 번째 컴파일 'version' 이전에 이 상태를 감지/의심한 경우).그 이유를 아래에서 읽습니다.


Someone just 가지다 to say this (utterly amazed nobody already did):
There used to be a time when the OP's snippet would be a de-facto example in a beginners programming book intended to outline/emphasize that 'arrays' in javascript are indexed starting at 0, not 1, and as such be used as an example of a common 'beginners mistake' (don't you love how I avoided the phrase 'programing error' ;)): Out-of-Bounds Array 액세스.

1:
a Dense Array(인덱스 간 간격이 없음을 의미하며 각 인덱스에서 실제로 요소가 있음) 0 기반 인덱싱을 사용하는 5개 요소(항상 ES262에 있음).

var arr_five_char=['a', 'b', 'c', 'd', 'e']; // arr_five_char.length === 5
//  indexes are:    0 ,  1 ,  2 ,  3 ,  4    // there is NO index number 5



따라서 우리는 실제로 두 제품 간의 성능 차이에 대해 이야기하고 있지 않습니다.<.<=(또는 '하나의 추가 반복'), 하지만 우리는 다음과 같이 말하고 있습니다.
이 )'보다는 무엇입니까올바른 스니펫(b)이 잘못된 스니펫(a)보다 빨리 실행되는 이유'

정답은 2가지입니다(ES262 언어 구현자의 관점에서는 둘 다 최적화의 형태입니다만).

  1. Data-Representation: Array를 메모리에 내부적으로 표현/저장하는 방법 (객체, 해시맵, '실제' 숫자 배열 등)
  2. 기능적 기계 코드: 이러한 '어레이'에 접근/처리(읽기/수정)하는 코드를 컴파일하는 방법

항목 1은 승인된 답변에 의해 충분히(그리고 정확하게 IMHO) 설명되지만, 항목 2: 컴파일에 2개의 단어('코드')만 사용합니다.

더 정확하게: JIT-Compilation 그리고 더 중요하게 JIT-RE-Compilation!

언어 사양은 기본적으로 일련의 알고리즘('정의된 최종 결과를 달성하기 위해 수행하는 단계')에 대한 설명일 뿐입니다.언어를 묘사하는 아주 아름다운 방법인 것으로 드러났습니다.또한 엔진이 지정된 결과를 달성하기 위해 사용하는 실제 방법을 실행자에게 공개함으로써 정의된 결과를 산출할 수 있는 보다 효율적인 방법을 제시할 수 있는 충분한 기회를 제공합니다.사양 적합 엔진은 정의된 입력에 대해 사양 적합 결과를 제공해야 합니다.

이제 자바스크립트 코드/라이브러리/사용량이 증가하고, '실제' 컴파일러가 얼마나 많은 리소스(시간/메모리/등)를 사용하는지 기억하면 웹 페이지를 방문하는 사용자들을 그렇게 오래 기다리게 할 수 없으며, 사용 가능한 리소스를 확보하도록 요구할 수도 없습니다.

다음과 같은 간단한 기능을 상상해 보십시오.

function sum(arr){
  var r=0, i=0;
  for(;i<arr.length;) r+=arr[i++];
  return r;
}

완벽하게 확실하죠?추가적인 해명은 필요 없겠죠?반품유형은Number,그렇죠?
...음.. 아니, 아니, 아니...named function 매개변수에 전달하는 인수에 따라 달라집니다.arr...

sum('abcde');   // String('0abcde')
sum([1,2,3]);   // Number(6)
sum([1,,3]);    // Number(NaN)
sum(['1',,3]);  // String('01undefined3')
sum([1,,'3']);  // String('NaN3')
sum([1,2,{valueOf:function(){return this.val}, val:6}]);  // Number(9)
var val=5; sum([1,2,{valueOf:function(){return val}}]);   // Number(8)

문제가 보이나요?그렇다면 이것이 가능한 거대한 순열을 거의 긁어내지 못한다는 것을 생각해보세요.우리는 우리가 끝날 때까지 함수가 어떤 종류로 돌아오는지조차 모릅니다.

이제 이와 같은 함수 코드가 실제로 다양한 유형의 입력 또는 심지어 변형된 입력에 실제로 사용되고 있다고 상상해 보십시오. 완전히 문자 그대로(소스 코드로) 기술되어 있고 동적으로 프로그램 내에서 생성된 '어레이'도 있습니다.

따라서 만약 함수를 컴파일한다면sum한 번만, 모든 유형의 입력에 대해 항상 사양 정의 결과를 반환하는 유일한 방법은 분명히 모든 사양 규정 주 및 하위 단계를 수행해야만 사양 준수 결과(이름 없는 사전 y2k 브라우저와 같이)를 보장할 수 있습니다.최적화(가정사항이 없기 때문에) 및 완전히 느린 해석 스크립팅 언어가 남아 있지 않습니다.

JIT-Compilation(JIT-Compilation, Just In Time과 같은 JIT)이 현재 인기 있는 솔루션입니다.

따라서 함수가 수행하고 반환하고 수용하는 것에 대한 가정을 사용하여 함수를 컴파일하기 시작합니다.
함수가 예기치 않은 입력을 받기 때문에 비규격 적합 결과를 반환하기 시작할 수 있는지 여부를 탐지하기 위해 가능한 한 간단한 검사를 수행합니다.그런 다음, 이전에 컴파일된 결과를 버리고 좀 더 정교한 것으로 다시 컴파일하고, 이미 가지고 있는 부분적인 결과를 어떻게 할지 결정하고(신뢰할 수 있는지, 아니면 다시 계산해도 되는지), 함수를 다시 프로그램에 연결한 후 다시 시도합니다.궁극적으로 사양에서와 같이 단계적 스크립트 해석으로 되돌아갑니다.

이 모든 것은 시간이 걸립니다!

모든 브라우저는 엔진에서 작동하며, 각 하위 버전에 대해 상황이 개선되고 퇴보하는 것을 볼 수 있습니다.문자열은 역사의 어느 시점에서 정말 불변의 문자열이었습니다(따라서 배열).조인은 문자열 연결보다 빠름), 이제 문제를 완화하는 로프(또는 유사한 것)를 사용합니다.둘 다 사양에 부합하는 결과를 반환하고 그것이 중요합니다!

간단히 말해서, 자바스크립트의 언어 의미론이 종종 우리를 되찾았다고 해서(OP의 예에서 이 무음 버그처럼) 컴파일러가 빠른 기계 코드를 뱉어낼 가능성이 높아지는 것은 아닙니다.우리가 '보통' 올바른 명령어를 썼다고 가정합니다. 컴파일러를 돕고, 우리가 원하는 것을 설명하고, 일반적인 관용구를 선호합니다(브라우저가 최적화하려고 시도할 수 있는 것과 그 이유를 기본적으로 이해하기 위해 asm.js에서 힌트를 얻음).

이 때문에 성능에 대해 이야기하는 것은 중요하면서도 지뢰밭이기도 합니다.(이 때문에) 관련 자료를 지적(인용)하는 것으로 끝맺고 싶습니다.

존재하지 않는 개체 속성 및 Out of Bounds 배열 요소에 액세스하면undefined예외를 제기하는 대신 값을 지정합니다.이러한 동적 기능은 자바스크립트의 프로그래밍을 편리하게 해주지만, 자바스크립트를 효율적인 기계 코드로 컴파일하는 것을 어렵게 합니다.

...

효과적인 JIT 최적화를 위한 중요한 전제는 프로그래머들이 체계적인 방식으로 자바스크립트의 동적 기능을 사용한다는 것입니다.예를 들어, JIT 컴파일러는 객체 속성이 특정한 순서로 지정된 유형의 객체에 추가되는 경우가 많거나 Out of Bounds 배열 액세스가 거의 발생하지 않는다는 점을 악용합니다.JIT 컴파일러는 이러한 규칙성 가정을 활용하여 런타임에 효율적인 기계 코드를 생성합니다.코드 블록이 가정을 만족하는 경우, 자바스크립트 엔진은 효율적으로 생성된 기계 코드를 실행합니다.그렇지 않으면 엔진이 더 느린 코드로 돌아가거나 프로그램을 해석해야 합니다.

:
JIT에 IT Prof: JIT에 비우호적인 자바스크립트 코드를 정확히 집어내는 것"
버클리 출판, 2014, 량공, 마이클 프라델, 쿠식 센.

ASM.JS(Outbound Array 액세스도 좋아하지 않음):

선행 컴파일

asm.js는 자바스크립트의 엄격한 부분집합이기 때문에 이 규격에서는 검증 로직만 정의하고 실행 시맨틱스는 단순히 자바스크립트의 것입니다.그러나 검증된 asm.js는 AOT(Ahead-of-Time) 컴파일에 적용할 수 있습니다.게다가, AOT 컴파일러에 의해 생성된 코드는 다음과 같은 특징을 가지고 꽤 효율적일 수 있습니다.

  • 정수 및 부동 소수점 숫자의 상자에 저장되지 않은 표현
  • 런타임 유형 검사의 부재;
  • 쓰레기 수거 부재; 및
  • 효율적인 힙 로드 및 저장소(플랫폼에 따라 구현 전략이 다름).

유효성 검사에 실패한 코드는 해석 및/또는 JIT(Just-in-time) 컴파일과 같은 전통적인 방법으로 실행해야 합니다.

http://asmjs.org/spec/latest/

그리고 마지막으로 https://blogs.windows.com/msedgedev/2015/05/07/bringing-asm-js-to-chakra-microsoft-edge/ .
경계 검사를 제거할 때 엔진의 내부 성능 개선에 대한 작은 하위 섹션이 있는 경우(루프 밖에서 경계 검사를 해제하는 것만으로도 이미 40%의 개선 효과가 있음).



:
여러 출처에서 다양한 수준의 JIT-Recompilation에 대해 해석까지 언급하고 있습니다.

OP의 스니펫에 관한 위의 정보를 바탕으로 한 이론적 예:

  • 통화 대상은 프라임 구분 가능
  • 컴파일은 일반적인 가정을 사용하여 PrimeDivisable(PrimeDivisable)입니다(Out of Bounds 액세스 없음).
  • 일을하다
  • BAM, 갑자기 배열이 경계를 벗어납니다(오른쪽 끝).
  • '크랩' 엔진은 'PrimeDivisible'을 다른(덜) 가정을 사용하여 재컴파일해 봅시다'라고 말합니다. 이 예제 엔진은 현재의 부분 결과를 재사용할 수 있는지 파악하지 못합니다.
  • 더 느린 함수를 사용하여 모든 작업을 다시 계산합니다(종료되기를 바라며, 그렇지 않으면 반복하고 이번에는 코드를 해석하십시오).
  • 반품결과

따라서 시간은 다음과 같습니다.
첫 번째 실행(끝에서 실패) + 각 반복에 대해 더 느린 기계 코드를 사용하여 모든 작업을 다시 수행 + 재컴파일 등.이 이론적 예에서 2배 이상의 시간이 소요됩니다!



2:(disclaim: 아래의 사실에 근거한 추측)
생각할수록, 이 답변이 실제로 잘못된 스니펫 a(또는 스니펫 b의 성과 보너스)에 대한 더 지배적인 이유를 설명해 줄 수도 있다고 생각합니다. 정확히 왜 제가 프로그래밍 오류라고 부르는지(스니펫 a).

그렇게 생각하는 것은 꽤 유혹적입니다.this.primes는 'dense 배열'의 순수한 수치이며, 둘 중 하나입니다.

  • source-code에서 하드 코딩된 리터럴(컴파일-time 에 컴파일러에게 모든 것이 이미 알려져 있으므로 '실제' 배열이 될 우수한 후보로 알려져 있음) 또는
  • 사전 크기를 채우는 수치 함수를 사용하여 생성될 가능성이 높습니다.new Array(/*size value*/)) 오름차순('진짜' 배열이 될 것으로 오랫동안 알려진 다른 후보).

우리는 또한 알고 있습니다.primes배열의 길이는 다음과 같이 캐시됩니다.prime_count)! (의도와 고정된 크기를 indic합니다.)

또한 대부분의 엔진은 처음에는 필요할 때 복사-온-수정(copy-on-modify) 방식으로 어레이를 통과하므로 변경하지 않을 경우 훨씬 더 빠르게 처리할 수 있습니다.

따라서 배열은 다음과 같이 가정하는 것이 타당합니다.primes이미 내부적으로 최적화된 배열일 가능성이 크며, 생성 후에 배열을 수정하는 코드가 없으면 컴파일러가 알 수 있습니다. 따라서 이미(엔진에 적용 가능한 경우) 최적화된 방식으로 저장되어 있습니다. 마치 다음과 같이 말입니다.Typed Array.

내가 내게 분명히 말하려 했던 것처럼.sum함수 예를 들어, 전달되는 인수는 실제로 발생해야 하는 것과 특정 코드가 기계 코드로 컴파일되는 방법에 큰 영향을 미칩니다. 통과.Stringsum함수는 문자열을 변경하는 것이 아니라 함수가 JIT-Compiled 되는 방식을 변경해야 합니다!배열 전달 대상sum기계 코드의 다른 버전을 컴파일해야 합니다. (아마도 이 유형에 대해 추가적인 경우도 있을 것입니다.

Type_Array를 변환하는 것은 약간 우둔한 것처럼 보입니다.primes컴파일러가 이 함수가 수정되지 않을 것이라는 것을 알고 있는 동안 다른 것으로 즉시 배열합니다!

다음과 같은 가정 하에서 두 가지 옵션을 남깁니다.

  1. 경계를 벗어남이 없다고 가정하고 번호 크런치로 컴파일하고, 마지막에 경계를 벗어남 문제에 부딪혀 다시 컴파일하고 다시 작업합니다(위의 편집 1의 이론적 예에서 설명한 바와 같이).
  2. 컴파일러가 이미 탐지(또는 의심?)했습니다.Out of Bound 액세스를 맨 앞에 두고 함수는 JIT-Compiled(JIT-Compiled) 방식으로 전달된 인수가 희박한 개체로 인해 기능 머신 코드가 느려지는 것처럼 처리되었습니다(더 많은 검사/변환/강요 등).즉, 함수는 특정 최적화에 적합하지 않으며 마치 '희소 배열'(유사) 인수를 받은 것처럼 컴파일되었습니다.

이제 이 둘 중에 어떤 것인지 정말 궁금해요!

여기에 과학성을 더하기 위해 jsperf가 있습니다.

https://jsperf.com/ints-values-in-out-of-array-bounds

int와 looping으로 채워진 배열의 제어 케이스를 한계 내에서 유지하면서 모듈러 산술을 수행하여 테스트합니다.테스트 케이스는 5가지입니다.

  • 1. 루프 아웃오브바운즈
  • 2. 홀리 배열
  • 3. NaN에 대한 모듈러 산술
  • 4. 완전히 정의되지 않은 값
  • 로. A를 사용.new Array()

앞의 4가지 경우는 정말 성능이 나쁘다는 것을 보여줍니다.경계를 벗어나는 루프는 다른 3개보다 약간 낫지만, 4개 모두 최적의 경우보다 약 98% 느립니다.
enew Array()케이스는 원시 배열과 거의 비슷하지만 몇 퍼센트 정도 느려집니다.

언급URL : https://stackoverflow.com/questions/53643962/why-is-slower-than-using-this-code-snippet-in-v8

반응형