क्यों एक बंद होने की तुलना में धीमी है?


79

पिछले पोस्टर ने Javascript में Function.bind vs क्लोजर पूछा : कैसे चुनें?

और इस उत्तर को भाग में प्राप्त किया, जो लगता है कि बाइंड को बंद करने की तुलना में तेज होना चाहिए:

स्कोप ट्रैवर्सल का मतलब है, जब आप एक मान (चर, वस्तु) को हथियाने के लिए पहुंच रहे हैं जो एक अलग दायरे में मौजूद है, इसलिए अतिरिक्त ओवरहेड जोड़ा जाता है (कोड निष्पादित करने के लिए धीमा हो जाता है)।

बाइंड का उपयोग करते हुए, आप एक मौजूदा स्कोप के साथ एक फ़ंक्शन को कॉल कर रहे हैं, ताकि स्कोवर ट्रैवर्सल न हो।

दो jsperfs सुझाव देते हैं कि बाँध वास्तव में बहुत है, एक बंद की तुलना में बहुत धीमा है ।

यह ऊपर टिप्पणी के रूप में पोस्ट किया गया था

और, मैंने अपना खुद का jsperf लिखने का फैसला किया

तो क्यों इतना धीमा बांध रहा है (क्रोमियम पर 70 +%)?

चूंकि यह तेज नहीं है और क्लोजर एक ही उद्देश्य की सेवा कर सकते हैं, क्या बांधने से बचा जाना चाहिए?


10
"बाँध से बचना चाहिए" --- जब तक आप इसे एक पृष्ठ पर हजारों बार कर रहे हैं - आपको इसकी परवाह नहीं करनी चाहिए।
झारक

1
छोटे टुकड़ों से एक अतुल्यकालिक जटिल कार्य के संयोजन के लिए कुछ ऐसी आवश्यकता हो सकती है जो बिल्कुल उसी तरह दिखती है, जैसे नोडज में, क्योंकि कॉलबैक को किसी तरह संरेखित करने की आवश्यकता होती है।
पॉल

मुझे लगता है कि ऐसा इसलिए है क्योंकि ब्राउज़र ने इसे अनुकूलित करने में उतना प्रयास नहीं किया है। इसे मैन्युअल रूप से लागू करने के लिए मोज़िला कोड ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ) देखें । वहाँ हर मौका ब्राउज़र है कि आंतरिक रूप से कर रहे हैं, जो एक त्वरित बंद करने की तुलना में बहुत अधिक काम है।
डेव

1
अप्रत्यक्ष फ़ंक्शन कॉल ( apply/call/bind) प्रत्यक्ष की तुलना में सामान्य रूप से बहुत धीमी हैं।
जॉर्ज

@zerkms और जो कहना है कि यह हजारों बार नहीं कर रहा है? कार्यक्षमता के कारण यह मुझे लगता है कि आपको आश्चर्य हो सकता है कि यह कितना सामान्य हो सकता है।
एंड्रयू

जवाबों:


142

Chrome 59 अपडेट: जैसा कि मैंने अनुमान लगाया था कि बाइंड के नीचे दिए गए उत्तर में अब नए ऑप्टिमाइज़िंग कंपाइलर के साथ धीमी गति नहीं है। यहाँ विवरण के साथ कोड है: https://codereview.chromium.org/2916063002/

ज्यादातर समय यह कोई फर्क नहीं पड़ता।

जब तक आप एक ऐसा एप्लिकेशन नहीं बना रहे हैं, जहां .bindमुझे अड़चन नहीं है अधिकांश मामलों में पठनीयता की तुलना में पठनीयता अधिक महत्वपूर्ण है। मुझे लगता है कि देशी का उपयोग .bindआमतौर पर अधिक पठनीय और बनाए रखने योग्य कोड के लिए प्रदान करता है - जो एक बड़ा प्लस है।

हालाँकि, जब यह मायने रखता है - .bindधीमा है

हां, .bindक्लोजर की तुलना में काफी धीमा है - कम से कम क्रोम में, कम से कम वर्तमान में जिस तरह से इसे लागू किया गया है v8। मुझे व्यक्तिगत रूप से प्रदर्शन के मुद्दों के लिए Node.JS में स्विच करना पड़ा है (आमतौर पर, प्रदर्शन गहन स्थितियों में धीमे तरह के होते हैं)।

क्यों? क्योंकि .bindकिसी फ़ंक्शन को दूसरे फ़ंक्शन के साथ लपेटने और उपयोग करने .callया करने की तुलना में एल्गोरिथ्म बहुत अधिक जटिल है .apply। (मजेदार तथ्य, यह [देशी फ़ंक्शन] के लिए सेट स्ट्रिंग के साथ एक फ़ंक्शन भी देता है)।

इसे देखने के दो तरीके हैं, विनिर्देश के दृष्टिकोण से, और कार्यान्वयन के दृष्टिकोण से। चलो दोनों का निरीक्षण करते हैं।

सबसे पहले, आइए विनिर्देश में परिभाषित बाइंड एल्गोरिथ्म देखें :

  1. लक्ष्य इस मान होने दें।
  2. यदि IsCallable (लक्ष्य) गलत है, तो TypeError अपवाद फेंकें।
  3. आज्ञा देना एक नया (संभवतः खाली) इस तर्क के बाद प्रदान किए गए सभी तर्क मूल्यों की आंतरिक सूची (arg1, arg2 आदि), क्रम में।

...

(21. कॉल करें [[DefineOwnProperty]] F की आंतरिक विधि "दलीलें", तर्कों के साथ, प्रॉपर्टीस्क्रिप्ट {[प्राप्त करें]: फेंकने वाला, [[सेट]]: फेंकने वाला, [[सक्षम]]: झूठा, [[विन्यास योग्य] ]: झूठा}, और झूठा।

(22. रिटर्न एफ।

बहुत जटिल लगता है, सिर्फ एक लपेट से बहुत अधिक।

दूसरा, आइए देखें कि यह क्रोम में कैसे कार्यान्वित होता है

आइए FunctionBindv8 (क्रोम जावास्क्रिप्ट इंजन) स्रोत कोड की जाँच करें :

function FunctionBind(this_arg) { // Length is 1.
  if (!IS_SPEC_FUNCTION(this)) {
    throw new $TypeError('Bind must be called on a function');
  }
  var boundFunction = function () {
    // Poison .arguments and .caller, but is otherwise not detectable.
    "use strict";
    // This function must not use any object literals (Object, Array, RegExp),
    // since the literals-array is being used to store the bound data.
    if (%_IsConstructCall()) {
      return %NewObjectFromBound(boundFunction);
    }
    var bindings = %BoundFunctionGetBindings(boundFunction);

    var argc = %_ArgumentsLength();
    if (argc == 0) {
      return %Apply(bindings[0], bindings[1], bindings, 2, bindings.length - 2);
    }
    if (bindings.length === 2) {
      return %Apply(bindings[0], bindings[1], arguments, 0, argc);
    }
    var bound_argc = bindings.length - 2;
    var argv = new InternalArray(bound_argc + argc);
    for (var i = 0; i < bound_argc; i++) {
      argv[i] = bindings[i + 2];
    }
    for (var j = 0; j < argc; j++) {
      argv[i++] = %_Arguments(j);
    }
    return %Apply(bindings[0], bindings[1], argv, 0, bound_argc + argc);
  };

  %FunctionRemovePrototype(boundFunction);
  var new_length = 0;
  if (%_ClassOf(this) == "Function") {
    // Function or FunctionProxy.
    var old_length = this.length;
    // FunctionProxies might provide a non-UInt32 value. If so, ignore it.
    if ((typeof old_length === "number") &&
        ((old_length >>> 0) === old_length)) {
      var argc = %_ArgumentsLength();
      if (argc > 0) argc--;  // Don't count the thisArg as parameter.
      new_length = old_length - argc;
      if (new_length < 0) new_length = 0;
    }
  }
  // This runtime function finds any remaining arguments on the stack,
  // so we don't pass the arguments object.
  var result = %FunctionBindArguments(boundFunction, this,
                                      this_arg, new_length);

  // We already have caller and arguments properties on functions,
  // which are non-configurable. It therefore makes no sence to
  // try to redefine these as defined by the spec. The spec says
  // that bind should make these throw a TypeError if get or set
  // is called and make them non-enumerable and non-configurable.
  // To be consistent with our normal functions we leave this as it is.
  // TODO(lrn): Do set these to be thrower.
  return result;

हम कार्यान्वयन में यहां महंगी चीजों का एक गुच्छा देख सकते हैं। अर्थात् %_IsConstructCall()। यह निश्चित रूप से विनिर्देशन का पालन करने की आवश्यकता है - लेकिन यह कई मामलों में एक साधारण आवरण की तुलना में धीमी बनाता है।


किसी अन्य नोट पर, कॉलिंग .bindभी थोड़ा अलग है, कल्पना नोट्स "फंक्शन ऑब्जेक्ट्स जो फंक्शन.प्रोटोटाइप.बाइंड का उपयोग करके बनाया गया है, उनके पास एक प्रॉपोटाइप प्रॉपर्टी या [[कोड]], [[फॉर्मलपैरमेटर्स]], और [[स्कोप] इंटरनल नहीं है] गुण"


अगर f = g.bind (सामान); f () g (सामान) से धीमा होना चाहिए? मुझे यह बहुत जल्दी पता चल सकता है, मुझे बस उत्सुकता है अगर एक ही बात हर बार होती है, तो हम एक फ़ंक्शन को कहते हैं, चाहे वह उस फ़ंक्शन को कोई फर्क नहीं पड़ता हो, या यदि यह निर्भर करता है कि वह फ़ंक्शन कहाँ से आया है।
पॉल

4
@Paul कुछ संदेह के साथ मेरा जवाब लें। यह सब क्रोम के भविष्य के संस्करण (/ V8) में अनुकूलित किया जा सकता है। मैंने शायद ही कभी खुद .bindको ब्राउज़र में बचने के लिए पाया है, ज्यादातर मामलों में पठनीय और समझने योग्य कोड बहुत महत्वपूर्ण है। बाउंड फ़ंक्शंस की गति के लिए - हाँ, बाउंड फ़ंक्शंस इस समय धीमे रहेंगे , खासकर जब thisआंशिक में मूल्य का उपयोग नहीं किया जाता है। आप इसे बेंचमार्क से, विनिर्देश से और / या स्वतंत्र रूप से कार्यान्वयन से देख सकते हैं (बेंचमार्क)
बेंजामिन ग्रुएनबाम

मुझे आश्चर्य है कि: 1) 2013 के बाद से कुछ भी बदल गया है (यह अब दो साल हो गए हैं 2) क्योंकि तीर के कार्यों में इस प्रकार की बाध्यता है - डिजाइन द्वारा तीर फ़ंक्शन धीमी हैं।
कुबा व्यारोसेक

1
@ क्यूबाविकस्ट्रोक 1) नहीं, 2) नहीं, क्योंकि बाइंड डिजाइन द्वारा धीमी नहीं है, यह सिर्फ तेजी से लागू नहीं है। एरो फ़ंक्शंस V8 में अभी तक नहीं उतरे हैं (वे उतरे और फिर वापस आ गए) जब हम देखेंगे।
बेंजामिन ग्रुएनबाम

1
क्या भविष्य में होने वाले किसी कॉल के लिए पहले से ही "बाइंड" कर दिया गया है, क्या यह कोई धीमा होगा? यानी a: फ़ंक्शन () {}। Bind (यह) ... भविष्य की कॉल हैं (a) किसी भी धीमी की तुलना में अगर मैं पहली जगह में कभी नहीं बाँधूँ?
Wayofthefuture

1

मैं यहाँ थोड़ा परिप्रेक्ष्य देना चाहता हूँ:

ध्यान दें कि जबकि bind()आईएनजी धीमा है, एक बार बाध्य होने पर फ़ंक्शन को कॉल करना नहीं है!

लिनक्स में फ़ायरफ़ॉक्स 76.0 में मेरा परीक्षण कोड:

//Set it up.
q = function(r, s) {

};
r = {};
s = {};
a = [];
for (let n = 0; n < 1000000; ++n) {
  //Tried all 3 of these.
  //a.push(q);
  //a.push(q.bind(r));
  a.push(q.bind(r, s));
}

//Performance-testing.
s = performance.now();
for (let x of a) {
  x();
}
e = performance.now();
document.body.innerHTML = (e - s);

तो जबकि यह सच है कि .bind()आईएनजी बाध्यकारी नहीं होने की तुलना में कुछ ~ 2X धीमा हो सकता है (मैंने यह भी परीक्षण किया), उपरोक्त कोड सभी 3 मामलों (0, 1, या 2 चर) के लिए समान समय लेता है।


व्यक्तिगत रूप से, मुझे परवाह नहीं है कि क्या .bind()मेरे वर्तमान उपयोग के मामले में आईएनजी धीमी है, मैं कोड के प्रदर्शन के बारे में परवाह करता हूं, क्योंकि एक बार वे चर पहले से ही कार्यों के लिए बाध्य होते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.