후자의 함수가 변수를 반복해서 작성해야하는 이유는 무엇입니까?


14
var toSizeString = (function() {

 var KB = 1024.0,
     MB = 1024 * KB,
     GB = 1024 * MB;

  return function(size) {
    var gbSize = size / GB,
        gbMod  = size % GB,
        mbSize = gbMod / MB,
        mbMod  = gbMod % MB,
        kbSize = mbMod / KB;

    if (Math.floor(gbSize)) {
      return gbSize.toFixed(1) + 'GB';
    } else if (Math.floor(mbSize)) {
      return mbSize.toFixed(1) + 'MB';
    } else if (Math.floor(kbSize)) {
      return kbSize.toFixed(1) + 'KB';
    } else {
      return size + 'B';
    }
  };
})();

그리고 더 빠른 기능 : (반복적으로 동일한 변수 kb / mb / gb를 항상 계산해야함에 유의하십시오). 성능은 어디서 얻습니까?

function toSizeString (size) {

 var KB = 1024.0,
     MB = 1024 * KB,
     GB = 1024 * MB;

 var gbSize = size / GB,
     gbMod  = size % GB,
     mbSize = gbMod / MB,
     mbMod  = gbMod % MB,
     kbSize = mbMod / KB;

 if (Math.floor(gbSize)) {
      return gbSize.toFixed(1) + 'GB';
 } else if (Math.floor(mbSize)) {
      return mbSize.toFixed(1) + 'MB';
 } else if (Math.floor(kbSize)) {
      return kbSize.toFixed(1) + 'KB';
 } else {
      return size + 'B';
 }
};

3
정적으로 유형이 지정된 언어에서 "변수"는 상수로 컴파일됩니다. 최신 JS 엔진은 동일한 최적화를 수행 할 수 있습니다. 변수가 클로저의 일부인 경우에는 작동하지 않는 것 같습니다.
usr

6
이것은 사용중인 JavaScript 엔진의 구현 세부 사항입니다. 이론적 인 시간과 공간은 동일하며,이를 변화시키는 지정된 JavaScript 엔진의 구현 일뿐입니다. 따라서 질문에 올바르게 대답하려면이를 측정 한 특정 JavaScript 엔진을 나열해야합니다. 어쩌면 누군가는 구현의 세부 사항을 알고 어떻게 다른 것보다 어떻게 / 왜 더 최적화했는지 말할 수 있습니다. 또한 측정 코드를 게시해야합니다.
Jimmy Hoffa

상수 값과 관련하여 "compute"라는 단어를 사용합니다. 참조하는 내용에는 계산할 것이 없습니다 . 상수 값의 산술은 컴파일러가 수행하는 가장 단순하고 명백한 최적화 중 하나이므로 상수 값만있는 표현식을 볼 때마다 전체 표현식이 단일 상수 값으로 최적화되었다고 가정 할 수 있습니다.
Jimmy Hoffa

@JimmyHoffa는 사실이지만, 반면에 함수 호출마다 3 개의 상수 변수를 만들어야합니다.
Tomy

@Tomy 상수는 변수가 아닙니다. 그것들은 다양하지 않으므로 컴파일 후에 다시 만들 필요가 없습니다. 상수는 일반적으로 메모리에 저장되며 해당 상수에 대한 모든 미래 도달 범위는 정확히 동일한 위치로 지정됩니다. 값이 절대 변하지 않기 때문에 다시 작성할 필요 가 없으므로 변수가 아닙니다. 컴파일러는 일반적으로 상수 를 생성 하는 코드를 생성 하지 않으며 컴파일러는 생성을 수행하고 모든 코드 참조를 생성 된 내용으로 보냅니다.
Jimmy Hoffa

답변:


23

최신 JavaScript 엔진은 모두 적시에 컴파일됩니다. "반복해서 다시 만들어야하는"항목에 대해서는 추정 할 수 없습니다. 이러한 종류의 계산은 어느 경우 에나 최적화하기가 비교적 쉽습니다.

반면에 상수 변수를 닫는 것은 일반적인 JIT 컴파일 대상이 아닙니다. 일반적으로 다른 호출에서 해당 변수를 변경하려는 경우 클로저를 만듭니다. 또한 멤버 변수에 액세스하는 것과 OOP의 로컬 int의 차이와 같은 변수에 액세스하기위한 추가 포인터 역 참조를 작성하고 있습니다.

이런 상황은 사람들이 "조기 최적화"라인을 버리는 이유입니다. 쉬운 최적화는 이미 컴파일러에 의해 수행됩니다.


나는 당신이 언급 한대로 손실을 일으키는 가변 해상도에 대한 스코프 횡단이라고 생각합니다. 합리적으로 보이지만, JavaScript JIT 엔진의 광기가 무엇인지 진정으로 아는 사람 ...
Jimmy Hoffa

1
이 답변의 가능한 확장 : JIT가 오프라인 컴파일러에 쉬운 최적화를 무시하는 이유 는 전체 컴파일러의 성능이 비정상적인 경우보다 중요하기 때문입니다.
Leushenko

12

변수는 싸다. 실행 컨텍스트와 범위 체인 은 비싸다.

본질적으로 "클로저로 인해"클로저로 요약되는 다양한 답변이 있으며, 본질적으로 사실이지만 문제는 구체적 으로 클로저와 관련 이 없으며 , 다른 범위에서 변수를 참조하는 함수가 있다는 사실입니다. windowIIFE 내부의 로컬 변수가 아닌 객체의 전역 변수 인 경우 동일한 문제가 발생합니다 . 사용해보십시오.

따라서 첫 번째 함수에서 엔진 이이 문장을 볼 때 :

var gbSize = size / GB;

다음 단계를 수행해야합니다.

  1. size현재 범위에서 변수 를 검색 하십시오. (그것을 발견.)
  2. GB현재 범위에서 변수 를 검색 하십시오. (찾을 수 없습니다.)
  3. GB상위 범위에서 변수 를 검색 하십시오. (그것을 발견.)
  4. 계산을 수행하고에 할당하십시오 gbSize.

3 단계는 변수를 할당하는 것보다 훨씬 비쌉니다. 또한,이에게 할 다섯 번 모두 두 번을 포함 GB하고 MB. 함수 시작 부분에서 별칭을 지정 var gb = GB하고 별칭을 대신 참조하면 실제로 약간의 속도 향상이 발생하지만 일부 JS 엔진은 이미이 최적화를 수행 할 수도 있습니다. 물론 실행 속도를 높이는 가장 효과적인 방법은 스코프 체인을 전혀 통과하지 않는 것입니다.

JavaScript는 컴파일러가 컴파일시에 이러한 변수 주소를 해석하는 정적 인 유형의 컴파일 된 언어와는 다릅니다. JS 엔진은 name 으로 해결해야 하며 , 이러한 조회는 매번 런타임에 발생합니다. 따라서 가능하면 피하십시오.

변수 할당은 JavaScript에서 매우 저렴합니다. 그 진술을 뒷받침 할 것이 없지만 실제로 가장 저렴한 작업 일 수 있습니다. 그럼에도 불구하고, 그것은 거의 없다는 것을 말하는 것이 안전 결코 하려고하는 것이 좋습니다 피하기 변수를 생성하는 단계; 해당 영역에서 수행하려는 거의 모든 최적화는 실제로 성능 측면에서 상황을 악화시킵니다.


은 "최적화"성능에 부정적인 영향을 미치지 않는 경우에도, 거의 확실하게 되는 부정적인 코드의 가독성에 영향을 미칠 것이다. 당신이 미친 계산 작업을 수행하지 않는 한, 그것은 종종 나쁜 트레이드 오프입니다 (아마도 permalink 앵커는 없습니다; "2009-02-17 11:41"검색). 요약하자면, "속도가 절대적으로 필요하지 않은 경우 속도보다 선명도를 선택하십시오."
CVn

동적 언어에 대한 매우 기본적인 인터프리터를 작성할 때도 런타임 동안 변수 액세스는 O (1) 연산 인 경향이 있으며 초기 컴파일 중에 O (n) 범위 순회도 필요하지 않습니다. 각 범위에서 새로 선언 된 각 변수에 숫자가 할당되므로 로 var a, b, c액세스 할 수 있습니다 . 모든 범위는 번호가 매겨지며,이 범위가 5 개의 범위 깊이로 중첩 된 경우 구문 분석 중에 알려진 범위 로 완전히 해결됩니다 . 네이티브 코드에서 범위는 스택 세그먼트에 해당합니다. 폐쇄는bscope[1]benv[5][1]env
amon

@amon : 그건 어떻게 좋겠 이상적 수 있습니다 와 같은 그 일에,하지만 실제로 작동하는 방법이 아니다. 내가 이것에 관해 책을 쓴 것보다 훨씬 더 지식과 경험이 많은 사람들; 특히 Nicholas C. Zakas의 고성능 JavaScript 를 소개합니다. 다음은 스 니펫 이며, 벤치마킹을 통해 벤치마킹했습니다. 물론 그는 확실히 가장 잘 알려진 유일한 사람은 아닙니다. JavaScript에는 어휘 범위가 있으므로 클로저는 실제로 그렇게 특별하지는 않습니다. 본질적으로 모든 것이 클로저입니다.
Aaronaught

@Aaronaught 재미있는. 그 책이 5 살이 기 때문에 현재 JS 엔진이 변수 조회를 처리하는 방법에 관심이 있었고 V8 엔진의 x64 백엔드를 보았습니다. 정적 분석 동안 대부분의 변수는 정적으로 해결되며 해당 범위에서 메모리 오프셋이 할당됩니다. 기능 범위는 링크 된 목록으로 표시되며 올바른 범위에 도달하기 위해 어셈블리가 롤되지 않은 루프로 방출됩니다. 여기서 우리 *(scope->outer + variable_offset)는 액세스 를 위해 C 코드 에 해당합니다. 각 추가 기능 범위 레벨에는 하나의 추가 포인터 역 참조가 필요합니다. 우리 둘 다 옳은 것 같습니다 :)
amon

2

하나의 예는 폐쇄와 관련이 있고 다른 하나는 그렇지 않습니다. 폐쇄 변수는 일반 변수처럼 작동하지 않기 때문에 클로저 구현은 다소 까다 롭습니다. 이것은 C와 같은 저수준 언어에서 더 분명하지만 JavaScript를 사용하여 설명하겠습니다.

클로저는 함수뿐만 아니라 클로저 한 모든 변수로 구성됩니다. 해당 함수를 호출하려면 모든 닫힌 변수를 제공해야합니다. 닫힌 변수를 나타내는 첫 번째 인수로 객체를받는 함수로 클로저를 모델링 할 수 있습니다.

function add(vars, y) {
  vars.x += y;
}

function getSum(vars) {
  return vars.x;
}

function makeAdder(x) {
  return { x: x, add: add, getSum: getSum };
}

var adder = makeAdder(40);
adder.add(adder, 2);
console.log(adder.getSum(adder));  //=> 42

어색한 호출 규칙을 참고 closure.apply(closure, ...realArgs)이 필요

JavaScript의 내장 객체 지원을 통해 명시적인 vars인수 를 생략하고 this대신 사용할 수 있습니다 .

function add(y) {
  this.x += y;
}

function getSum() {
  return this.x;
}

function makeAdder(x) {
  return { x: x, add: add, getSum: getSum };
}

var adder = makeAdder(40);
adder.add(2);
console.log(adder.getSum());  //=> 42

이러한 예제는 실제로 클로저를 사용하는이 코드와 동일합니다.

function makeAdder(x) {
  return {
    add: function (y) { x += y },
    getSum: function () { return x },
  };
}

var adder = makeAdder(40);
adder.add(2);
console.log(adder.getSum());  //=> 42

이 마지막 예에서 객체는 반환 된 두 함수를 그룹화하는 데만 사용됩니다. this결합은 무관하다. 숨겨진 데이터를 실제 함수로 전달하고 닫힌 변수에 대한 모든 액세스를 해당 숨겨진 데이터의 조회로 변경하는 클로저를 가능하게하는 모든 세부 사항은 언어에 의해 처리됩니다.

그러나 클로저 호출은 추가 데이터를 전달하는 오버 헤드와 관련이 있으며 클로저를 실행하면 캐시 캐시가 잘못되어 일반 변수와 비교할 때 일반적으로 포인터 역 참조로 인해 추가 데이터의 조회 오버 헤드가 발생하므로 놀랄 일이 아닙니다. 클로저에 의존하지 않는 솔루션이 더 잘 수행됩니다. 특히 클로저가 저장하는 모든 작업은 매우 저렴한 산술 연산이므로 파싱 중에도 계속 접힐 수 있습니다.

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