program story

프로토 타입을 통해 메서드를 정의하는 것과 생성자에서 이것을 사용하는 것-정말 성능 차이?

inputbox 2020. 12. 9. 08:08
반응형

프로토 타입을 통해 메서드를 정의하는 것과 생성자에서 이것을 사용하는 것-정말 성능 차이?


JavaScript에서 우리는 "클래스"를 만들고 공개 함수를 제공하는 두 가지 방법이 있습니다.

방법 1 :

function MyClass() {
    var privateInstanceVariable = 'foo';
    this.myFunc = function() { alert(privateInstanceVariable ); }
}

방법 2 :

function MyClass() { }

MyClass.prototype.myFunc = function() { 
    alert("I can't use private instance variables. :("); 
}

나는 모든 인스턴스가 각각 자신의 것을 얻는 것보다 동일한 함수 사본을 공유하기 때문에 방법 2를 사용하는 것이 더 효율적 이라고 사람들을 여러 번 읽었습니다 . 하지만 프로토 타입을 통해 함수를 정의하는 것은 큰 단점이 있습니다. 개인 인스턴스 변수를 가질 수 없게됩니다.

이론적으로 방법 1을 사용하면 객체의 각 인스턴스에 자체 함수 사본이 제공되므로 할당에 필요한 시간은 말할 것도없고 더 많은 메모리를 사용합니다. 실제로 실제로 일어나는 일입니까? 최적화 웹 브라우저가 쉽게 만들 수있는 것은 이러한 매우 일반적인 패턴을 인식하고 실제로 객체의 모든 인스턴스가 이러한 "생성자 함수"를 통해 정의 된 동일한 함수 사본을 참조 하도록하는 것 입니다. 그런 다음 나중에 명시 적으로 변경되는 경우에만 인스턴스에 자체 함수 복사본을 제공 할 수 있습니다.

둘 사이의 성능 차이에 대한 통찰력 또는 더 나은 실제 경험 은 매우 도움이 될 것입니다.


http://jsperf.com/prototype-vs-this 참조

프로토 타입을 통해 메소드를 선언하는 것이 더 빠르지 만 이것이 관련성이 있는지 여부는 논쟁의 여지가 있습니다.

예를 들어, 임의의 애니메이션의 모든 단계에서 10000 개 이상의 개체를 인스턴스화하지 않는 한 앱에 성능 병목 현상이있을 가능성이 없습니다.

성능이 심각한 문제이고 마이크로 최적화하고 싶다면 프로토 타입을 통해 선언하는 것이 좋습니다. 그렇지 않으면 자신에게 가장 적합한 패턴을 사용하십시오.

JavaScript에는 밑줄 (예 :)을 사용하여 비공개로 표시되도록 속성을 접두사로 지정하는 규칙이 있습니다 _process(). 대부분의 개발자는 사회적 계약을 기꺼이 포기하지 않는 한 이러한 속성을 이해하고 피할 것입니다. 그러나이 경우에는이를 충족하지 않는 것이 좋습니다. 내가 말하려는 것은 : 아마도 진정한 개인 변수 가 필요하지 않을 것입니다 .


새 버전의 Chrome에서 this.method는 prototype.method보다 약 20 % 빠르지 만 새 개체를 만드는 속도는 여전히 느립니다.

항상 새 개체를 만드는 대신 개체를 재사용 할 수 있다면 새 개체를 만드는 것보다 50 %-90 % 더 빠를 수 있습니다. 또한 가비지 콜렉션이 없다는 이점이 있습니다.

http://jsperf.com/prototype-vs-this/59


많은 인스턴스를 생성 할 때만 차이가 있습니다. 그렇지 않으면 멤버 함수 호출 성능이 두 경우 모두 동일합니다.

이것을 증명하기 위해 jsperf에서 테스트 케이스를 만들었습니다.

http://jsperf.com/prototype-vs-this/10


이것을 고려하지 않았을 수도 있지만 메서드를 객체에 직접 배치하는 것이 실제로 한 가지 방법으로 더 좋습니다.

  1. 메서드 호출은 메서드 를 해결하기 위해 프로토 타입 체인을 참조 할 필요가 없기 때문에 매우 약간 빠릅니다 ( jsperf ).

그러나 속도 차이는 거의 무시할 수 있습니다. 또한 프로토 타입에 메서드를 추가하는 것이 두 가지 더 효과적인 방법으로 더 좋습니다.

  1. 인스턴스 생성 속도 향상 ( jsperf )
  2. 더 적은 메모리 사용

James가 말했듯이 수천 개의 클래스 인스턴스를 인스턴스화하는 경우이 차이가 중요 할 수 있습니다.

즉, 각 개체에 연결하는 함수가 인스턴스간에 변경되지 않으므로 모든 인스턴스 메서드가 공유 함수를 가리키고있는 함수의 복사본 하나만 메모리에 유지한다는 것을 인식하는 JavaScript 엔진을 확실히 상상할 수 있습니다. 사실 파이어 폭스는 이와 같은 특별한 최적화를하고있는 것 같지만 크롬은 그렇지 않습니다.


곁에:

프로토 타입의 메서드 내부에서 개인 인스턴스 변수에 액세스하는 것이 불가능하다는 것이 맞습니다. 그래서 스스로에게 물어봐야 할 질문은 상속과 프로토 타이핑을 활용하는 것보다 인스턴스 변수를 진정으로 비공개로 만들 수 있다는 점을 중요하게 생각합니까? 저는 개인적으로 변수를 진정으로 비공개로 만드는 것은 그다지 중요하지 않으며 변수가 공개되어 있지만 비공개로 간주되어야 함을 나타 내기 위해 밑줄 접두사 (예 : "this._myVar")를 사용한다고 생각합니다. 즉, ES6에는 두 세계를 모두 가질 수있는 방법이 분명히 있습니다!


간단히 말해서, 모든 인스턴스가 공유 할 속성 / 메서드를 생성하려면 방법 2를 사용하십시오. 그것들은 "글로벌"이되며 변경 사항은 모든 인스턴스에 반영됩니다. 인스턴스 별 속성 / 방법을 생성하려면 방법 1을 사용하십시오.

나는 더 나은 참조를했지만, 지금은 한 번 봐 가지고 싶은 . 다른 목적으로 동일한 프로젝트에서 두 방법을 어떻게 사용했는지 볼 수 있습니다.

도움이 되었기를 바랍니다. :)


이 답변은 누락 된 점을 채우는 나머지 답변의 확장으로 간주되어야합니다. 개인적인 경험과 벤치 마크가 모두 통합됩니다.

내 경험이 진행되는 한, 메서드가 비공개이든 아니든간에 생성자를 사용하여 문자 그대로 내 개체를 종교적으로 구성합니다. 주된 이유는 내가 시작했을 때 그것이 나에게 가장 쉬운 즉각적인 접근 방식 이었기 때문에 특별한 선호도가 아니기 때문입니다. 내가 보이는 캡슐화를 좋아하고 프로토 타입이 약간 분리 된 것을 좋아하는 것처럼 간단했을 수도 있습니다. 내 개인 메서드는 범위의 변수로도 할당됩니다. 이것이 내 습관이고 모든 것을 잘 견디고 있지만 항상 최선의 습관은 아니며 때로는 벽에 부딪힌다. 구성 개체 및 코드 레이아웃에 따라 매우 동적 인 자체 조립을 사용하는 이상한 시나리오를 제외하고는 특히 성능이 우려되는 경우 제 생각에는 약한 접근 방식 인 경향이 있습니다. 내부가 비공개라는 것을 아는 것은 유용하지만 올바른 훈련을 통해 다른 수단을 통해이를 달성 할 수 있습니다. 성능을 심각하게 고려하지 않는 한 현재 작업에 가장 적합한 것을 사용하십시오.

  1. 프로토 타입 상속과 규칙을 사용하여 항목을 비공개로 표시하면 콘솔이나 디버거에서 개체 그래프를 쉽게 탐색 할 수 있으므로 디버깅이 더 쉬워집니다. 반면에 이러한 규칙은 난독 화를 다소 어렵게 만들고 다른 사람들이 자신의 스크립트를 사이트에 쉽게 추가 할 수 있도록합니다. 이것이 개인 범위 접근 방식이 인기를 얻은 이유 중 하나입니다. 진정한 보안은 아니지만 대신 저항을 추가합니다. 불행히도 많은 사람들은 여전히 ​​이것이 보안 JavaScript를 프로그래밍하는 진정한 방법이라고 생각합니다. 디버거가 정말 좋아 졌기 때문에 코드 난독 화가 대신합니다. 클라이언트에 너무 많은 보안 결함이있는 경우에는 찾아봐야 할 디자인 패턴입니다.
  2. 규칙을 사용하면 소란스럽지 않은 보호 속성을 가질 수 있습니다. 그것은 축복이자 저주가 될 수 있습니다. 덜 제한적이므로 일부 상속 문제를 완화합니다. 재산에 접근 할 수있는 다른 곳을 고려할 때 여전히 충돌의 위험이 있거나인지 부하가 ​​증가합니다. 자체 어셈블 링 개체를 사용하면 여러 상속 문제를 해결할 수 있지만 틀에 얽매이지 않을 수있는 이상한 일을 할 수 있습니다. 내 모듈은 기능이 다른 곳에서 필요 (공유)되거나 외부에서 필요하지 않는 한 노출 될 때까지 항목이 꺼내지지 않는 풍부한 내부 구조를 갖는 경향이 있습니다. 생성자 패턴은 단순한 단편적인 객체보다 자체 포함 된 정교한 모듈을 만드는 경향이 있습니다. 당신이 그것을 원한다면 괜찮습니다. 그렇지 않으면 좀 더 전통적인 OOP 구조와 레이아웃을 원한다면 관습에 따라 액세스를 규제하는 것이 좋습니다. 내 사용 시나리오에서 복잡한 OOP는 종종 정당화되지 않으며 모듈이 트릭을 수행합니다.
  3. 여기에있는 모든 테스트는 최소한입니다. 실제 사용에서는 모듈이 더 복잡하여 여기에서 테스트가 표시하는 것보다 히트가 훨씬 더 클 수 있습니다. 여러 메서드를 사용하는 개인 변수를 사용하는 것은 매우 일반적이며 각 메서드는 프로토 타입 상속으로 얻을 수없는 초기화에 더 많은 오버 헤드를 추가합니다. 대부분의 경우 이러한 개체의 인스턴스가 누적되어 누적 될 수 있지만 떠 다니기 때문에 중요하지 않습니다.
  4. There is an assumption that prototype methods are slower to call because of prototype lookup. It's not an unfair assumption, I made the same myself until I tested it. In reality it's complex and some tests suggest that aspect is trivial. Between, prototype.m = f, this.m = f and this.m = function... the latter performs significantly better than the first two which perform around the same. If the prototype lookup alone were a significant issue then the last two functions instead would out perform the first significantly. Instead something else strange is going on at least where Canary is concerned. It's possible functions are optimised according to what they are members of. A multitude of performance considerations come into play. You also have differences for parameter access and variable access.
  5. Memory Capacity. It's not well discussed here. An assumption you can make up front that's likely to be true is that prototype inheritance will usually be far more memory efficient and according to my tests it is in general. When you build up your object in your constructor you can assume that each object will probably have its own instance of each function rather than shared, a larger property map for its own personal properties and likely some overhead to keep the constructor scope open as well. Functions that operate on the private scope are extremely and disproportionately demanding of memory. I find that in a lot of scenarios the proportionate difference in memory will be much more significant than the proportionate difference in CPU cycles.
  6. Memory Graph. You also can jam up the engine making GC more expensive. Profilers do tend to show time spent in GC these days. It's not only a problem when it comes to allocating and freeing more. You'll also create a larger object graph to traverse and things like that so the GC consumes more cycles. If you create a million objects and then hardly touch them, depending on the engine it might turn out to have more of an ambient performance impact than you expected. I have proven that this does at least make the gc run for longer when objects are disposed of. That is there tends to be a correlation with memory used and the time it takes to GC. However there are cases where the time is the same regardless of the memory. This indicates that the graph makeup (layers of indirection, item count, etc) has more impact. That's not something that is always easy to predict.
  7. Not many people use chained prototypes extensively, myself included I have to admit. Prototype chains can be expensive in theory. Someone will but I've not measured the cost. If you instead build your objects entirely in the constructor and then have a chain of inheritance as each constructor calls a parent constructor upon itself, in theory method access should be much faster. On the other hand you can accomplish the equivalent if it matters (such as flatten the prototypes down the ancestor chain) and you don't mind breaking things like hasOwnProperty, perhaps instanceof, etc if you really need it. In either case things start to get complex once you down this road when it comes to performance hacks. You'll probably end up doing things you shouldn't be doing.
  8. Many people don't directly use either approach you've presented. Instead they make their own things using anonymous objects allowing method sharing any which way (mixins for example). There are a number of frameworks as well that implement their own strategies for organising modules and objects. These are heavily convention based custom approaches. For most people and for you your first challenge should be organisation rather than performance. This is often complicated in that Javascript gives many ways of achieving things versus languages or platforms with more explicit OOP/namespace/module support. When it comes to performance I would say instead to avoid major pitfalls first and foremost.
  9. There's a new Symbol type that's supposed to work for private variables and methods. There are a number of ways to use this and it raises a host of questions related to performance and access. In my tests the performance of Symbols wasn't great compared to everything else but I never tested them thoroughly.

Disclaimers:

  1. There are lots of discussions about performance and there isn't always a permanently correct answer for this as usage scenarios and engines change. Always profile but also always measure in more than one way as profiles aren't always accurate or reliable. Avoid significant effort into optimisation unless there's definitely a demonstrable problem.
  2. It's probably better instead to include performance checks for sensitive areas in automated testing and to run when browsers update.
  3. Remember sometimes battery life matters as well as perceptible performance. The slowest solution might turn out faster after running an optimising compiler on it (IE, a compiler might have a better idea of when restricted scope variables are accessed than properties marked as private by convention). Consider backend such as node.js. This can require better latency and throughput than you would often find on the browser. Most people wont need to worry about these things with something like validation for a registration form but the number of diverse scenarios where such things might matter is growing.
  4. You have to be careful with memory allocation tracking tools in to persist the result. In some cases where I didn't return and persist the data it was optimised out entirely or the sample rate was not sufficient between instantiated/unreferenced, leaving me scratching my head as to how an array initialised and filled to a million registered as 3.4KiB in the allocation profile.
  5. In the real world in most cases the only way to really optimise an application is to write it in the first place so you can measure it. There are dozens to hundreds of factors that can come into play if not thousands in any given scenario. Engines also do things that can lead to asymmetric or non-linear performance characteristics. If you define functions in a constructor, they might be arrow functions or traditional, each behaves differently in certain situations and I have no idea about the other function types. Classes also don't behave the same in terms as performance for prototyped constructors that should be equivalent. You need to be really careful with benchmarks as well. Prototyped classes can have deferred initialisation in various ways, especially if your prototyped your properties as well (advice, don't). This means that you can understate initialisation cost and overstate access/property mutation cost. I have also seen indications of progressive optimisation. In these cases I have filled a large array with instances of objects that are identical and as the number of instances increase the objects appear to be incrementally optimised for memory up to a point where the remainder is the same. It is also possible that those optimisations can also impact CPU performance significantly. These things are heavily dependent not merely on the code you write but what happens in runtime such as number of objects, variance between objects, etc.

참고URL : https://stackoverflow.com/questions/12180790/defining-methods-via-prototype-vs-using-this-in-the-constructor-really-a-perfo

반응형