V8에서이 코드 스 니펫을 사용하여 <=가 <보다 느린 이유는 무엇입니까?


166

V8을 사용하여 Javascript 속도 제한을 깨는 슬라이드를 읽고 있으며 아래 코드와 같은 예가 있습니다. 왜이 경우 <=보다 느린 지 알 수 없습니다. <아무도 설명 할 수 있습니까? 모든 의견을 부탁드립니다.

느린:

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

(힌트 : 소수는 길이 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

10
@DacreDenny 모든 현대 프로세서 (및 인터프리터)에서 이론적으로나 실제 구현에서 계산상의 어려움 <=<동일합니다.
TypeIA

1
문서를 읽었습니다. main실행 25000시간에 루프에서 해당 함수를 호출 하는 코드가 있으므로 전체적으로 해당 변경 작업을 반복하는 횟수가 훨씬 적습니다. 또한 배열의 길이가 5 인 경우 배열의 색인이 시작되기 때문에 값 을 얻으려고하면 array[5]한계를 벗어 undefined납니다 0.
Shidersz

1
이 질문이 얼마나 많은 속도 개선이 달성되는지 (예를 들어, 5 배 더 빠름) 설명하여 사람들이 여분의 반복에 의해 버리지 않도록하는 것이 도움이 될 것입니다. 나는 슬라이드에서 얼마나 빠른지 찾으려고 노력했지만 많은 것이 있었고 그것을 찾지 못했습니다. 그렇지 않으면 직접 편집 할 것입니다.
Captain Man

@CaptainMan 맞습니다. 정확한 속도 향상은 여러 가지 문제를 한꺼번에 다루기 때문에 슬라이드에서 쉽게 얻을 수 없습니다. 그러나이 대화 후 스피커와의 대화에서 그는이 테스트 사례에서 한 번의 추가 반복에서 기대할 수있는 퍼센트의 작은 부분이 아니라 큰 차이가 있음을 확인했습니다. 규모 이상. 그리고 그 이유는 배열 경계 외부에서 읽으려고 할 때 V8이 최적화되지 않은 배열 형식으로 다시 떨어지기 때문입니다.
마이클 기어 리

3
를 사용하여 사용 <=하지만 <버전 과 동일하게 작동 하는 버전을 비교하는 것이 유용 할 수 있습니다 i <= this.prime_count - 1. 이렇게하면 "추가 반복"문제와 "어레이의 마지막 끝"문제가 모두 해결됩니다.
TheHansinator

답변:


132

Google 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();

무엇보다도 성능 차이는 <and <=연산자와 직접 관련 이 없습니다 . 따라서 <=스택 오버플로에서 느리다는 것을 읽었으므로 코드에서 피하기 위해 농구 대를 뛰어 넘지 마십시오.


둘째, 사람들은 배열이 "홀리"라고 지적했다. 이것은 OP 게시물의 코드 스 니펫에서 명확하지 않지만 초기화하는 코드를 볼 때 분명합니다.this.primes .

this.primes = new Array(iterations);

와 배열이 결과 종류 소자 어레이가 끝나더라도는 V8에 완전히 연속 / 충전 / 충전. 일반적으로 구멍이 많은 배열의 연산은 팩형 배열의 연산보다 느리지 만이 경우 그 차이는 무시할 수 있습니다.HOLEY 작은 정수 (구멍을 방지하려면)) 확인란 우리가 공격 할 때마다 this.primes[i]내에서 루프 isPrimeDivisible. 별거 아니야!

TL; DR 여기서 배열 HOLEY은 문제가되지 않습니다.


다른 사람들은 코드가 범위를 벗어났다고 지적했습니다. 일반적으로 권장됩니다 배열의 길이를 넘어서 읽지 않는 .이 경우 실제로 성능이 크게 떨어지는 것을 피했을 것입니다. 그런데 왜? V8은 성능에 약간의 영향 만 미치면서 이러한 아웃 바운드 시나리오 중 일부를 처리 할 수 ​​있습니다.그렇다면이 특별한 경우에 특별한 점은 무엇입니까?

범위를 벗어난 읽기 this.primes[i]undefined 이 줄에 :

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

그리고 그것은 우리를 실제 문제로 인도합니다 :% 연산자는 이제 정수가 아닌 피연산자와 함께 사용되고 있습니다!

  • integer % someOtherInteger매우 효율적으로 계산 될 수 있습니다. JavaScript 엔진은이 경우에 최적화 된 머신 코드를 생성 할 수 있습니다.

  • integer % undefined다른 한편으로 Float64Mod, 덜 효율적인 방법 에 이릅니다.undefined 에 이중으로 표시 .

코드 스 니펫은 실제로 다음 줄 에서 <=를 변경하여 향상시킬 수 있습니다 <.

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

... <=어떻게 든 우수한 연산자가 <아니기 때문에이 특별한 경우에 범위를 벗어난 읽기를 피할 수 있기 때문입니다.


1
의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
Samuel Liew

1
100 % 완료되기 위해 isPrimeDivisible의 this.primes [i]에 대한 키로드 IC는 V8에서 예기치 않게 거대화됩니다. 즉 벌레처럼 보인다 bugs.chromium.org/p/v8/issues/detail?id=8561
마티아스 Bynens

226

다른 답변과 의견에 따르면 두 루프의 차이점은 첫 번째 루프가 두 번째 루프보다 하나 더 많은 반복을 실행한다는 것입니다. 이것은 사실이지만 25,000 개의 요소로 커지는 배열에서는 한 번의 반복이 약간의 차이를 만들뿐입니다. 야구장 추측으로, 우리가 자라면서 평균 길이가 12,500이라고 가정하면, 우리가 기대할 수있는 차이는 1 / 12,500 정도 또는 0.008 %에 불과합니다.

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

this.primes 연속 배열이며 (모든 요소에 값이 있음) 요소는 모두 숫자입니다.

JavaScript 엔진은 이러한 배열을 숫자를 포함하지만 다른 값을 포함하거나 포함하지 않을 수있는 객체의 배열 대신 실제 숫자의 간단한 배열로 최적화 할 수 있습니다. 첫 번째 형식은 액세스 속도가 훨씬 빠릅니다. 코드가 적고 배열이 훨씬 작아 캐시에 더 적합합니다. 그러나이 최적화 된 형식을 사용하지 못하게하는 몇 가지 조건이 있습니다.

배열 요소 중 일부가 누락 된 경우가 있습니다. 예를 들면 다음과 같습니다.

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

이제의 가치는 a[1]무엇입니까? 그것은 값이 없습니다 . (값이 있다고 말하는 것은 올바르지 않습니다 undefined.undefined 하는 배열 요소가 완전히 누락 된 배열 요소와 다릅니다.)

숫자로만 표현할 수있는 방법이 없으므로 JavaScript 엔진은 덜 최적화 된 형식을 사용해야합니다. 만약a[1]다른 두 요소와 같은 숫자 값 포함 된 배열은 숫자 배열로만 최적화 될 수 있습니다.

배열을 최적화되지 않은 형식으로 강제 설정해야하는 또 다른 이유는 프레젠테이션에서 설명한대로 배열 경계 외부의 요소에 액세스하려는 경우 일 수 있습니다.

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

  • this.primes[i]배열 끝을 지났기 undefined때문에 i로 평가됩니다 .
  • candidate % undefined(의 값에 해당 candidate)은로 평가됩니다 NaN.
  • NaN == 0로 평가됩니다 false.
  • 따라서이 return true실행되지 않습니다.

따라서 추가 반복이 발생하지 않은 것처럼 나머지 논리에는 영향을 미치지 않습니다. 코드는 추가 반복이없는 것과 동일한 결과를 생성합니다.

그러나 거기에 도달하기 위해 배열의 끝을지나 존재하지 않는 요소를 읽으려고했습니다. 이로 인해 어레이가 최적화되지 않거나 적어도이 대화 시점에 수행되었습니다.

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

문제는 연설의 90-91 페이지에 설명되어 있으며 그 전후 페이지에서 관련 토론이 있습니다.

나는이 Google I / O 프리젠 테이션에 참석 한 후 스피커 (V8 저자 중 한 명)와 이야기를 나 ed습니다. 나는 자신의 코드에서 배열의 끝을 지나서 특정 상황을 최적화하기위한 오도 된 (후시) 시도로 읽는 기술을 사용하고있었습니다. 그는 배열의 끝을 지나서 읽으 려고 시도 하면 간단한 최적화 형식을 사용하지 못하게 될 것이라고 확인했습니다.

V8 작성자가 말한 내용이 여전히 사실이라면 배열의 끝을지나 읽으면 배열이 최적화되지 않고 느린 형식으로 돌아 가야합니다.

이제 V8이이 경우를 효율적으로 처리하기 위해 개선되었거나 다른 JavaScript 엔진이 다르게 처리 할 수 ​​있습니다. 나는 그것에 대해 어떤 식 으로든 다른 방법을 모르지만,이 최적화 해제는 프레젠테이션이 말한 것입니다.


1
배열이 여전히 연속적이라고 확신합니다. 메모리 레이아웃을 변경할 이유가 없습니다. 중요한 것은 속성 액세스에서 범위를 벗어난 인덱스 검사를 최적화 할 수 없으며 때로는 undefined다른 계산으로 이어지는 숫자 대신 코드가 제공되는 경우가 있습니다.
Bergi

1
@ Bergi 저는 JS / V8 전문가는 아니지만 GC 언어의 객체는 거의 항상 실제 객체를 참조합니다. GC 객체 수명이 서로 연결되어 있지 않기 때문에 참조가 인접하더라도 실제 객체는 독립적으로 할당됩니다. 옵티마이 저는 이러한 독립적 할당을 인접하게 패킹 할 수 있지만 (a) 메모리는 급등을 사용하고 (b) 하나 대신 두 개의 연속 블록 (참조 및 참조 된 데이터)이 반복됩니다. 나는 미친 옵티마이
저가

1
@Bergi 최적화되지 않은 경우에는 배열이 연속적 일 수 있지만 배열 요소는 최적화 된 경우와 동일한 유형이 아닙니다. 최적화 된 버전은 추가적인 보풀이없는 단순한 숫자 배열입니다. 최적화되지 않은 버전은 객체 배열 (자바 스크립트가 아닌 내부 객체 형식 Object)입니다. 왜냐하면 배열에서 모든 데이터 유형 조합을 지원해야하기 때문입니다. 위에서 언급했듯이 공급되는 루프의 코드 undefined는 알고리즘의 정확성에 영향을 미치지 않습니다. 계산이 전혀 변경되지 않습니다 (추가 반복이 발생하지 않은 것처럼).
Michael Geary

3
@Bergi이 강연을 한 V8 저자는 배열 범위 밖에서 읽으려고 시도하면 배열이 여러 유형이있는 것처럼 취급 됩니다 . 최적화 된 숫자 전용 형식 대신 배열을 다시 최적화하지 않습니다. 일반적인 형식 최적화 된 경우 C 프로그램에서 사용할 수있는 간단한 숫자 배열입니다. 최적화되지 않은 경우 Value모든 유형의 값에 대한 참조를 보유 할 수있는 객체 의 배열입니다 . (이름 Value을 구성했지만 요점은 배열 요소는 단순한 숫자가 아니라 숫자 또는 다른 유형을 감싸는 객체라는 것입니다.)
Michael Geary

3
나는 V8에서 일한다. 문제의 배열은 다음을 HOLEY사용하여 생성 되었으므로 표시됩니다 new Array(n)( 이 부분은 OP에서 보이지 않았 음). HOLEY배열HOLEY 은 나중에 채워져도 V8 에서 영원히 유지 됩니다 . 즉,이 경우 배열이 홀리 기 때문에 성능 문제가 발생하지 않습니다. 그것은 우리가 각 반복에 대해 여분의 Smi 검사를 수행해야한다는 것을 의미합니다 (구멍을 막기 위해).
Mathias Bynens

19

TL; DR 느린 루프는 Array 'out-of-bounds'에 액세스하기 때문에 엔진이 최적화를하지 않거나 전혀 최적화하지 않고 함수를 다시 컴파일하거나 이러한 최적화로 함수를 컴파일하지 않아야합니다. (JIT-) 컴파일러가 첫 번째 컴파일 'version'전에이 조건을 감지 / 의심 한 경우)를 아래에서 읽으십시오.


누군가 단지 이 (완전히 깜짝 놀라게 아무도 이미 않았다) 말 :
영업의 조각은 프로그래밍 책 개요 의도 초보자의 사실상의 예가 될 것이다 때 시간이있을 사용 / 자바 스크립트 '배열'인덱스 시작임을 강조 1이 아닌 0에서 시작하여 일반적인 '초보자 실수'의 예로 사용됩니다 ( '프로그래밍 오류'구절을 피하는 방법을 좋아하지 않습니다 ;)) : 범위를 벗어난 배열 액세스 .

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

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



따라서 우리는 실제로 <vs <=(또는 '한 번의 추가 반복') 간의 성능 차이에 대해 이야기하는 것이 아니라
'올바른 스 니펫 (b)이 잘못된 스 니펫 (a)보다 빠르게 실행되는 이유'는 무엇입니까?

대답은 2 배입니다 (ES262 언어 구현 자의 관점에서는 두 가지 모두 최적화의 형태 임).

  1. 데이터 표현 : 배열을 메모리에 내부적으로 나타내거나 저장하는 방법 (객체, 해시 맵, '실제'숫자 배열 등)
  2. Functional Machine-code : 이러한 '어레이'에 액세스 / 처리 (읽기 / 수정)하는 코드를 컴파일하는 방법

항목 1은 받아 들여진 대답에 의해 충분히 (그리고 정확하게 IMHO) 설명되어 있지만, 항목 2 : 컴파일 에 2 단어 ( '코드') 만 사용합니다 .

보다 정확하게 : JIT- 컴파일 및 더욱 중요한 JIT- RE- 컴파일!

언어 사양은 기본적으로 일련의 알고리즘에 대한 설명입니다 ( '정의 된 최종 결과를 달성하기 위해 수행하는 단계'). 그것은 언어를 묘사하는 매우 아름다운 방법입니다. 그리고 엔진이 특정 결과를 구현 자에게 공개하기 위해 사용하는 실제 방법을 남겨두고 정의 된 결과를 생성하는보다 효율적인 방법을 제시 할 수있는 충분한 기회를 제공합니다. 사양 준수 엔진은 정의 된 입력에 대해 사양 준수 결과를 제공해야합니다.

이제 자바 스크립트 코드 / 라이브러리 / 사용률이 증가하고 '실제'컴파일러가 사용하는 리소스 (시간 / 메모리 / 기타)의 양을 기억하면서 사용자가 웹 페이지를 방문하는 시간을 오래 기다릴 수 없다는 점은 분명합니다 (필요합니다). 많은 리소스를 사용할 수 있도록).

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

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

완벽하게 명확합니까? 추가 설명이 필요하지 않습니까? 반환 유형은 Number맞습니까?
음 .. 아니오, 아니오 & 아니오 ... 명명 된 함수 매개 변수에 전달할 인수에 따라 다릅니다 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)

문제를 보시겠습니까? 그렇다면 이것이 가능한 큰 순열을 간신히 버리고 있다고 생각하십시오 ... 우리는 완료 될 때까지 어떤 유형의 RETURN 함수조차 알지 못합니다 ...

이제 동일한 함수 코드 가 문자 그대로 (소스 코드에서) 완전히 설명되고 동적으로 프로그램 내에서 생성 된 '배열'과 같이 다른 유형이나 입력의 변형에 실제로 사용되고 있다고 상상해보십시오 .

따라서 함수 sumJUST ONCE 를 컴파일 하는 경우 모든 유형의 입력에 대해 항상 스펙 정의 결과를 리턴하는 유일한 방법은 스펙 스펙이 지정된 모든 메인 AND 서브 단계 만 수행하면 스펙 준수 결과를 보장 할 수 있습니다. (이름없는 y2k 브라우저와 같은). (가정이 없기 때문에) 최적화가없고 느린 느린 해석 스크립트 언어가 남아 있습니다.

JIT 컴파일 (JIT In Just Time)은 현재 널리 사용되는 솔루션입니다.

따라서 함수의 기능, 반환 및 수락에 대한 가정을 사용하여 함수를 컴파일하기 시작합니다.
함수가 사양에 맞지 않는 결과를 반환하기 시작하는지 (예 : 예기치 않은 입력을 받기 때문에) 감지하기 위해 가능한 한 간단한 검사를 수행합니다. 그런 다음 이전에 컴파일 된 결과를 버리고 더 정교한 것으로 다시 컴파일하고 이미 가지고있는 부분 결과로 무엇을해야하는지 (신뢰할 수 있거나 확실하게 다시 계산할 수 있는지) 결정하고 함수를 프로그램에 다시 연결하고 다시 시도하십시오. 궁극적으로 스펙에서와 같이 단계적으로 스크립트 해석으로 되돌아갑니다.

이 모든 시간이 걸립니다!

모든 브라우저는 엔진에서 작동하며, 모든 하위 버전마다 개선 및 회귀를 볼 수 있습니다. 문자열은 역사상 어느 시점에서 실제로 불변의 문자열이었습니다 (따라서 array.join은 문자열 연결보다 빠릅니다). 이제 우리는 로프 (또는 유사한)를 사용하여 문제를 완화시킵니다. 둘 다 사양에 맞는 결과를 반환하며 이것이 중요합니다!

짧은 이야기 : 자바 스크립트의 언어 의미가 종종 우리를 다시 얻었 기 때문에 (OP의 예 에서이 조용한 버그와 같이) '멍청한'실수로 인해 컴파일러가 빠른 기계 코드를 뱉을 확률이 높아지는 것은 아닙니다. 그것은 우리가 '보통'올바른 지시를 썼다고 가정한다 : 우리가 '사용자'(프로그래밍 언어)의 현재 진언은 : 컴파일러를 도와주고, 우리가 원하는 것을 설명하고, 일반적인 관용구를 선호한다 (기본 이해를 위해 asm.js로부터 힌트를 얻는다) 브라우저가 최적화하려고 시도 할 수있는 이유와 이유).

이 때문에 성능에 대해 이야기하는 것이 중요하지만 광산 분야 이기도합니다. 그리고 광산 분야 때문에 관련 자료를 가리키고 인용하는 것으로 정말로 끝내고 싶습니다.

존재하지 않는 객체 속성 및 범위를 벗어난 배열 요소에 액세스 undefined하면 예외를 발생시키는 대신 값이 반환 됩니다. 이러한 동적 기능으로 JavaScript 프로그래밍이 편리하지만 JavaScript를 효율적인 기계 코드로 컴파일하기가 어렵습니다.

...

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

출처 :
"JITProf : JIT- 친숙하지 않은 JavaScript 코드의 정확한 위치 지정"
Berkeley 간행물, 2014 년 Liang Gong, Michael Pradel, Koushik Sen.
http://software-lab.org/publications/jitprof_tr_aug3_2014.pdf

ASM.JS (바운드 배열 액세스를 좋아하지 않음) :

미리 컴파일

asm.js는 JavaScript의 엄격한 하위 집합이므로이 사양은 유효성 검사 논리 만 정의합니다. 실행 의미는 단순히 JavaScript의 의미입니다. 그러나 검증 된 asm.js는 AOT 컴파일에 적합합니다. 또한 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 재 컴파일에 대해 이야기합니다.

OP의 스 니펫에 관한 위의 정보 기반으로 한 이론적 예 :

  • isPrimeDivisible에 전화
  • 일반적인 가정을 사용하여 isPrimeDivisible 컴파일 (범위를 벗어난 액세스 없음)
  • 일해
  • BAM, 갑자기 배열이 범위를 벗어났습니다 (끝에서 오른쪽으로).
  • Crap은 엔진에 따르면 다른 (더 적은) 가정을 사용하여 isPrimeDivisible을 다시 컴파일 하고이 예제 엔진은 현재 부분 결과를 재사용 할 수 있는지 파악하려고 시도하지 않습니다.
  • 느린 기능을 사용하여 모든 작업을 다시 계산하십시오.
  • 반환 결과

따라서 시간은 다음과 같습니다.
첫 실행 (종료 실패) + 각 반복에 대해 느린 기계 코드를 사용하여 모든 작업을 다시 수행 + 재 컴파일 등. 이 이론적 인 예에서는 분명히 2 배 이상 더 걸립니다 !



편집 2 : (면책 조항 : 아래 사실에 근거한 추측)
더 많이 생각할수록이 답변이 실제로 잘못된 스 니펫 a (또는 스 니펫 b의 성능 보너스)에 대한 '벌금'에 대한 더 지배적 인 이유를 더 많이 설명 할 것이라고 생각합니다 , 어떻게 생각하는지에 따라) 정확하게 프로그래밍 오류라고 부릅니다 (스 니펫 a).

this.primes'밀집 배열'순수 라고 가정하는 것이 꽤 유혹적 입니다.

  • 소스 코드의 하드 코딩 된 리터럴 ( 컴파일 타임 전에 모든 것이 이미 컴파일러에 알려진 것처럼 '실제'배열이 될 수있는 탁월한 후보 ) 또는
  • 사전 크기 ( new Array(/*size value*/))를 오름차순으로 채우는 숫자 함수 ( '실제'배열이되는 다른 오래 알려진 후보)를 사용하여 생성 된 것 같습니다 .

우리는 또한 알고 primes배열의 길이가되는 캐시prime_count! (의도 및 고정 크기임을 나타냄).

또한 대부분의 엔진은 처음에 Array-mod-in-modify (필요한 경우)로 배열을 전달하므로 변경하지 않으면 처리 속도가 훨씬 빨라집니다.

따라서 Array primes가 내부적으로 최적화 된 배열 이라고 가정하는 것이 합리적입니다 (생성 후 배열을 수정하는 코드가없는 경우 컴파일러에 대해 간단하게 알 수 있음). 최적화 된 방식으로 저장된 엔진), 꽤 많은 것처럼 그것은이었다 Typed Array.

필자의 sum함수 예제로 명확하게하려고 시도 했을 때 전달되는 인수는 실제로 발생 해야하는 사항과 특정 코드가 기계 코드로 컴파일되는 방식에 영향을 미칩니다. 함수에 a String를 전달 sum하면 문자열이 변경되지 않고 함수가 JIT 컴파일 방식을 변경해야합니다! 배열을 전달하면 기계 코드 sum의 다른 버전 (아마도이 ​​유형의 경우 추가 또는 '모양' 이라고도 함)을 컴파일해야합니다.

Typed_Array와 같은 primesArray를 on_the- fly로 something_else 로 변환하는 것은 약간 bonkus처럼 보이지만 컴파일러는이 함수가 수정하지 않을 것이라는 것을 알고 있습니다!

이러한 가정 하에서 두 가지 옵션이 남습니다.

  1. 범위를 벗어나지 않는다고 가정하고 번호 크 런처로 컴파일하고, 끝에서 범위를 벗어난 문제에 부딪 히고, 재 컴파일 및 재실행 작업 (위의 편집 1의 이론적 예에 설명 된대로)
  2. 컴파일러가 이미 사전에 바운드 액세스 범위를 벗어 났음을 감지했거나 의심 했습니까? 전달 된 인수가 희소 오브젝트 인 것처럼 함수가 JIT 컴파일되어 기능적인 기계 코드가 느려짐 기타.). 다시 말해, 함수는 특정 최적화에 적합하지 않았으며 마치 'sparse array'(-like) 인수를받는 것처럼 컴파일되었습니다.

나는 지금이 2 중 어느 것이 궁금합니다!


2
몇 가지 근본적인 문제에 대한 좋은 토론-그러나 마지막 문장에서 답을 거의 설명하지 않습니다. 아마도 상단에 tl; dr을 추가 할 수 있습니까? 예 : "느린 루프는 경계 배열을 초과하기 때문에 엔진이 최적화없이 루프를 다시 평가하게합니다. 이유를 알아 보려면 계속 읽으십시오."
brichins

@brichins : 더 내가 지금 그것을 생각하기 때문에 내가 제 2의 추가 편집에 비추어 조금 재 진술 한 제안, 감사 및 감사, 상단에 그 진술뿐만 아니라 실제로 올바른 것
GitaarLAB

6

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

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

범위 내에서 유지하면서 모듈 형 산술을 수행하는 정수와 루핑으로 채워진 배열의 제어 사례를 테스트합니다. 5 가지 테스트 사례가 있습니다.

  • 1. 범위를 벗어남
  • 2. 홀리 어레이
  • 3. NaN에 대한 모듈 식 산술
  • 4. 완전히 정의되지 않은 값
  • 5. 사용 new Array()

처음 4 가지 경우가 실제로 성능에 좋지 않다는 것을 보여줍니다 . 범위를 벗어나는 것이 다른 3보다 약간 낫지 만 4는 모두 최상의 경우보다 약 98 % 느립니다.
new Array()사례는 거의 원시 어레이만큼이나 좋고 몇 퍼센트 느립니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.