2013-01-08 2 views
-3

나는 my.class.js의 소스 코드를보고 Firefox에서 fast이 무엇인지 확인했습니다.my.class.js가 그렇게 빠른 이유는 무엇입니까?

extend 함수는 단순히 제 (임의로 무시 기존 속성) 상에 제 2 물체의 특성을 복사하는 데 사용되는
my.Class = function() { 
    var len = arguments.length; 
    var body = arguments[len - 1]; 
    var SuperClass = len > 1 ? arguments[0] : null; 
    var hasImplementClasses = len > 2; 
    var Class, SuperClassEmpty; 

    if (body.constructor === Object) { 
     Class = function() {}; 
    } else { 
     Class = body.constructor; 
     delete body.constructor; 
    } 

    if (SuperClass) { 
     SuperClassEmpty = function() {}; 
     SuperClassEmpty.prototype = SuperClass.prototype; 
     Class.prototype = new SuperClassEmpty(); 
     Class.prototype.constructor = Class; 
     Class.Super = SuperClass; 
     extend(Class, SuperClass, false); 
    } 

    if (hasImplementClasses) 
     for (var i = 1; i < len - 1; i++) 
      extend(Class.prototype, arguments[i].prototype, false);  

    extendClass(Class, body); 

    return Class; 
}; 

:

var extend = function (obj, extension, override) { 
    var prop; 
    if (override === false) { 
     for (prop in extension) 
      if (!(prop in obj)) 
       obj[prop] = extension[prop]; 
    } else { 
     for (prop in extension) 
      obj[prop] = extension[prop]; 
     if (extension.toString !== Object.prototype.toString) 
      obj.toString = extension.toString; 
    } 
}; 

여기에 클래스를 생성하는 데 사용되는 코드의 단편이다 extendClass 기능 클래스 위에 복사 모든 정적 속성을뿐만 아니라 클래스의 프로토 타입 위에 모든 공용 속성 :

var extendClass = my.extendClass = function (Class, extension, override) { 
    if (extension.STATIC) { 
     extend(Class, extension.STATIC, override); 
     delete extension.STATIC; 
    } 
    extend(Class.prototype, extension, override); 
}; 

이것은 모두 매우 간단합니다. 클래스를 만들면 제공 한 생성자 함수를 반환하기 만하면됩니다.

그러나이 생성자의 인스턴스를 만드는 방법이 무엇인지 이해하는 것이 execute fasterVapor.js으로 작성된 동일한 생성자의 인스턴스를 만드는 것보다 낫다.

은 이해하기 위해 노력하고있어 무엇 :

  1. 방법 my.class.js 같은 라이브러리 생성자는 파이어 폭스에 너무 빨리 너무 많은 인스턴스를 만들려면 어떻게해야합니까? 라이브러리의 생성자는 모두 매우 유사합니다. 실행 시간도 유사해야하지 않습니까?
  2. 클래스가 생성되는 방식이 인스턴스화의 실행 속도에 영향을주는 이유는 무엇입니까? 정의와 인스턴스화가 별도의 프로세스가 아닌가?
  3. my.class.js가이 속도 향상을 얻는 곳은 어디입니까? 더 빨리 실행해야하는 생성자 코드의 일부가 표시되지 않습니다. 실제로 MyFrenchGuy.Super.prototype.setAddress.call과 같은 긴 프로토 타입 체인을 통과하면 속도가 크게 느려집니다.
  4. 생성자 함수가 JIT 컴파일입니까? 그렇다면 다른 라이브러리의 생성자 함수도 JIT 컴파일되지 않는 이유는 무엇입니까?
+2

my.class.js의 속도를 높이는 한 가지 측면은 확장 기능이 'hasOwnProperty' 체크. --- 그러나 jsperf 테스트에서 빠른 실행 라이브러리의 차이는 초당 수백만 건의 연산을 수행한다는 사실을 고려할 때 최소한입니다. 그 많은 객체 인스턴스를 만들면 메모리 풋 프린트와 가비지 수집과 관련하여 이미 다른 문제가 발생했을 것입니다. – Blaise

답변

11

나는 누군가를 불쾌하게하지는 않지만, 이런 종류의 일은 입니다. 정말로은 주목할 가치가 없습니다. IMHO. 거의 모든 브라우저의 속도 차이는 JS 엔진에 달려 있습니다. V8 엔진은 메모리 관리 기능이 뛰어납니다. 특히 이전의 IE JScript 엔진과 비교할 때 그렇습니다. FF 모두 반면, 내가 확인

var closure = (function() 
{ 
    var closureVar = 'foo', 
    someVar = 'bar', 
    returnObject = {publicProp: 'foobar'}; 
    returnObject.getClosureVar = function() 
    { 
     return closureVar; 
    }; 
    return returnObject; 
}()); 

마지막 시간, 크롬 실제로 someVar을 GC'ed, 그것은 (closure에 의해 참조)를 인생의 반환 값에 의해 참조되지 않았기 때문에 :

는 다음 사항을 고려 Opera는 전체 기능 범위를 메모리에 유지했습니다.
이 스 니펫에서는 실제로 문제가되지 않지만 수천 줄의 코드로 구성된 모듈 패턴 (AFAIK, 거의 모든 코드가 사용됨)을 사용하여 작성된 라이브러리의 경우 으로 변경 될 수 있습니다 .

어쨌든 현대 JS 엔진은 단지 "멍청한" 구문 분석 및 실행 이상의 것 이상입니다. 당신이 말했듯이 : JIT 컴파일이 진행되고 있지만 가능한 한 코드를 최적화하는 것과 관련된 많은 트릭이 있습니다. 당신이 게시 한 발췌 문장은 FF의 엔진이 을 좋아하는 방식으로 작성되었습니다.
Chrome과 FF간에 가장 빠른 엔진을 가진 일종의 스피드 전투가 있음을 기억하는 것도 중요합니다. 지난 번 모질라의 Rhino 엔진이 구글의 V8을 능가한다고 말했을 때, 그것이 여전히 사실이라면 말은 할 수 없다 ... 그 이후로 구글과 모질라는 모두 엔진에서 작업 해왔다 ...

아래 line : 여러 브라우저 간의 속도 차이가 존재합니다. 아무도 그 점을 부정 할 수는 없지만 한 점의 차이점은 중요하지 않습니다. 단 한 번만 반복해서 쓰는 스크립트는 쓰지 않을 것입니다. 중요한 전반적인 성과입니다.
JS는 벤치 마크에서 까다로운 놈이기도합니다. 콘솔을 열고 재귀 적 기능을 작성한 다음 FF와 Chrome에서 100 번 실행합니다. 각 재귀에 걸리는 시간과 전체 실행 시간을 비교하십시오. 그런 다음 2 시간 정도 기다렸다가 다시 시도하십시오 ... 때때로 FF가 위에 올 수도 있지만 다른 경우에는 Chrome이 더 빠를 수도 있습니다.

var bench = (function() 
{ 
    var mark = {start: [new Date()], 
       end: [undefined]}, 
    i = 0, 
    rec = function(n) 
    { 
     return +(n === 1) || rec(n%2 ? n*3+1 : n/2); 
     //^^ Unmaintainable, but fun code ^^\\ 
    }; 
    while(i++ < 100) 
    {//new date at start, call recursive function, new date at end of recursion 
     mark.start[i] = new Date(); 
     rec(1000); 
     mark.end[i] = new Date(); 
    } 
    mark.end[0] = new Date();//after 100 rec calls, first element of start array vs first of end array 
    return mark; 
}()); 

을하지만 지금, 당신의 처음 질문 (들)에 돌아 가야 : :이 기능을 시도했습니다

첫째 : 당신은 확실히 말하자면, 비교하지 않습니다 제공하는 조각, jQuery의 $.extend 방법 : 실제 복제가 진행 중이며, 복제는 물론입니다. 순환 참조를 전혀 검사하지 않습니다.이 참조는 제가 조사한 대부분의 libs입니다. 순환 참조를 검사하면 전체 프로세스가 느려지지만 수시로 편리하게 사용할 수 있습니다 (아래 예제 1). 성능 차이의 일부는이 코드 이 단순히을 덜 적게 함으로 설명 할 수 있으므로 시간이 덜 필요합니다.

두 번째 : 생성자를 선언하면 (클래스는 JS에는 존재하지 않음) 인스턴스를 만드는 것은 사실 두 가지 다른 작업입니다 (생성자가 객체의 인스턴스를 생성한다고 선언하더라도 (정확히 Function 인스턴스) 당신이 당신의 생성자를 작성하는 방법은 을 큰 숫자 인과 같이 만들 수있다. 다시 말하지만, 이는 일반화 된 것이며 특정 엔진의 특정 유스 케이스에는 적용되지 않을 수있다. 예를 들어, V8은 해당 함수가 생성자의 일부인 경우에도 모든 인스턴스에 대해 단일 함수 객체 만들기 - 또는 그렇게 말합니다.

세 번째 : 생각할 수있는 것처럼 비정상적이지 않은 긴 프로토 타입 체인 이동 , 멀리에서 사실. JS가 함수 호출을 해결하거나 표현식을 해결하는 방식에 내재되어 있기 때문에 예제 2에서 3 개의 프로토 타입 체인을 지속적으로 탐색하고 있습니다 (예제 3 참조).

마지막으로, 아마도 JIT로 컴파일되었지만 다른 라이브러리가 JIT로 컴파일되지 않는다는 것은 스택되지 않는다고 말합니다. 그들은 다시는 그렇지 않을 수도 있습니다. 앞에서 말했듯이, 다른 엔진은 다른 작업에서 성능이 뛰어납니다 ... FF JIT는이 코드를 컴파일하고 다른 엔진은 그렇지 않습니다.
다른 라이브러리가 JIT 컴파일되지 않는 이유는 순환 참조, 딥 복제 기능, 종속성 (여러 가지 이유로 extend 메서드가 사용 된 이유)을 확인하는 주된 이유입니다.

예 1 :이 함수는 객체의 첫 번째 수준 클론

var shallowCloneCircular = function(obj) 
{//clone object, check for circular references 
    function F(){}; 
    var clone, prop; 
    F.prototype = obj; 
    clone = new F(); 
    for (prop in obj) 
    {//only copy properties, inherent to instance, rely on prototype-chain for all others 
     if (obj.hasOwnProperty(prop)) 
     {//the ternary deals with circular references 
      clone[prop] = obj[prop] === obj ? clone : obj[prop];//if property is reference to self, make clone reference clone, not the original object! 
     } 
    } 
    return clone; 
}; 

는 원래의 객체 속성에 의해 참조되어있는 모든 오브젝트는 여전히 공유한다. 간단한 수정은 단순히 반복적으로 위의 함수를 호출하는 것입니다,하지만 당신은 모든 수준에서 순환 참조의 불쾌한 업무를 처리해야합니다 : 물론

var circulars = {foo: bar}; 
circulars.circ1 = circulars;//simple circular reference, we can deal with this 
circulars.mess = {gotcha: circulars};//circulars.mess.gotcha ==> circular reference, too 
circulars.messier = {messiest: circulars.mess};//oh dear, this is hell 

, 이것은 가장 일반적인 아니다 첫 번째 선언, 이론적으로

function CleanConstructor() 
{}; 
CleanConstructor.prototype.method1 = function() 
{ 
    //do stuff... 
}; 
var foo = new CleanConstructor(), 
bar = new CleanConstructor); 
console.log(foo === bar);//false, we have two separate instances 
console.log(foo.method1 === bar.method1);//true: the function-object, referenced by method1 has only been created once. 
//as opposed to: 
function MessyConstructor() 
{ 
    this.method1 = function() 
    {//do stuff 
    }; 
} 
var foo = new MessyConstructor(), 
bar = new MessyConstructor(); 
console.log(foo === bar);//false, as before 
console.log(foo.method1 === bar.method1);//false! for each instance, a new function object is constructed, too: bad performance! 

: 당신이 방어 코드를 작성하려는 경우 상황은, 그러나, 당신은 ... 많은 사람들이 미친 코드 모든 시간을 쓰기는 사실을 인정하는

예 2가 생성자는 입니다. 느림지저분한 방법보다: method1이 참조하는 함수 객체는 단일 인스턴스가 만들어지기 전에 만들어집니다. 두 번째 예제에서는 생성자가 호출되는 경우를 제외하고 method1을 만들지 않습니다. 그러나 단점은 거대한입니다. 첫 번째 예의 경우 new 키워드를 잊어 버리면 반환 값은 undefined입니다. 두 번째 생성자는 new 키워드를 생략 할 때 전역 함수 객체를 만들고 물론 각 호출에 대해 새 함수 객체를 만듭니다. 당신은 ... 예 3

예를 우리에게 가져다 공회전 사실이다 생성자 (및 프로토 타입)가 3 :

var foo = [];//create an array - empty 
console.log(foo[123]);//logs undefined. 

좋아, 그래서 무대 뒤에서 무슨 일 : foo개체을 참조하고, Array의 인스턴스는 개체 프로토 타입 (그냥 시도 Object.getPrototypeOf(Array.prototype))을 상속받습니다.이 때문에, 배열 인스턴스가 어떤 객체와 거의 같은 방식으로 작동하므로 것을 추론하기 위하여 약자 즉

foo[123] ===> JS checks instance for property 123 (which is coerced to string BTW) 
    || --> property not found @instance, check prototype (Array.prototype) 
    ===========> Array.prototype.123 could not be found, check prototype 
     || 
     ==========> Object.prototype.123: not found check prototype? 
      || 
      =======>prototype is null, return undefined 

는, 당신이 설명과 같은 체인이 너무 억지 또는 드문 일이 아니다. 그것은 JS가 작동하는 방식입니다. 그래서 느린 것을 기대하는 것은 생각을하기 때문에 뇌가 튀어 오를 것으로 기대하는 것과 같습니다 : 예, 너무 많이 생각하면 마모가 될 수 있지만 휴식을 취해야 할 때만 알면됩니다. 프로토 타입 체인의 경우와 마찬가지로 : 위대한, 그냥 천천히 느껴진다. 예 ...

+0

동기 부여가되어있는 한 투표를하지 않아도됩니다. –

1

프로그래밍 할 때 코드를 기능을 희생하지 않고 가능한 한 작게 만드는 것이 좋습니다. 나는 그것을 minimalist code라고 부른다.

코드를 난독 화하는 데는 좋은 이유가 될 수 있습니다. 난독 화 (Obfuscation)는 더 작은 메소드와 변수 이름을 사용하여 파일 크기를 줄이므로 리버스 엔지니어링을 어렵게하고 파일 크기를 줄이며 다운로드 속도를 높이고 성능을 향상시킬 수 있습니다. 구글의 자바 스크립트 코드는 매우 혼란스럽고 속도가 빨라진다.

JavaScript에서는 크기가 커질수록 항상 좋은 것은 아닙니다. 코드를 축소 할 수있는 방법을 찾았 으면 코드를 즉시 구현합니다. 최소의 양으로도 성능에 도움이된다는 것을 알기 때문입니다.

예를 들어 변수가 함수 외부에 필요하지 않은 함수에서 var 키워드를 사용하면 가비지 수집에 도움이됩니다. 가비지 수집은 변수를 메모리에 보관하는 것과 비교하여 매우 작은 속도 향상을 제공합니다.

"초당 수백만 건의 연산"(Blaise의 단어)을 생성하는 이와 같은 라이브러리를 사용하면 작은 성능 향상으로 눈에 띄는/측정 가능한 차이가 발생할 수 있습니다.

따라서 my.class.js은 "최소 단위로 코딩되었거나"어떤 방식으로 최적화되었을 수 있습니다. 키워드도 var 일 수 있습니다.

다소 도움이 되었기를 바랍니다. 그것이 도움이되지 않는다면, 나는 당신에게 좋은 대답을 얻으시길 바랍니다.

+0

그게 내가 생각 하기엔 너무 작지만 항상 작아지는 것은 아닙니다. JavaScript (7 줄의 코드 만 사용)에서 내 [augment] (http://javascript.github.com/augment/#section-20 "augment.js") 메소드를 살펴보십시오. 그것은'my.class.js'와 다른 라이브러리들이하는 일을 정확하게 수행하지만 여전히 [느린 속도입니다] (http://jsperf.com/oop-benchmark/108 "JavaScript Object Oriented Libraries Benchmark · jsPerf")보다 그것들은 파이어 폭스에서 (그러나 오페라에서 가장 빠른 것이고, 크롬에서 두번째로 빠른 것이다.) 23. 내가 정말로 알고 싶었던 것은 다른 라이브러리가 Firefox에서 인스턴스를 그렇게 빨리 만드는 이유입니다. –

+0

그건 재미 있어요. 그러나 Firefox는 오페라 (HTML/CSS의 경우 Presto, 자바 스크립트의 경우 Caraken) 및 HTML (HTML/CSS의 경우 Webkit, JavaScript의 경우 V8)과 다른 엔진에서 실행됩니다. 어쩌면 그것은 엔진 일 것입니다. –

관련 문제