मूल्य रिसीवर बनाम सूचक रिसीवर


108

यह मेरे लिए बहुत अस्पष्ट है कि मैं किस मामले में हमेशा एक पॉइंटर रिसीवर का उपयोग करने के बजाय एक मूल्य रिसीवर का उपयोग करना चाहता हूं।
डॉक्स से रिकैप करने के लिए:

type T struct {
    a int
}
func (tv  T) Mv(a int) int         { return 0 }  // value receiver
func (tp *T) Mp(f float32) float32 { return 1 }  // pointer receiver

डॉक्स भी कहते हैं, "इस तरह के बुनियादी प्रकार, स्लाइस, और छोटे structs के रूप में प्रकार के लिए, एक मूल्य के रिसीवर बहुत सस्ता है, इसलिए जब तक विधि के शब्दों एक सूचक की आवश्यकता है, एक मूल्य के रिसीवर कुशल और स्पष्ट है।"

पहला बिंदु यह कहता है कि यह "बहुत सस्ता" है, लेकिन सवाल यह है कि यह सस्ता है तो सूचक रिसीवर। इसलिए मैंने एक छोटा बेंचमार्क (जीआईएस पर कोड) बनाया, जिसमें मुझे दिखाया गया, कि पॉइंटर रिसीवर एक ऐसी संरचना के लिए और भी तेज है, जिसमें केवल एक स्ट्रिंग फ़ील्ड है। ये परिणाम हैं:

// Struct one empty string property
BenchmarkChangePointerReceiver  2000000000               0.36 ns/op
BenchmarkChangeItValueReceiver  500000000                3.62 ns/op


// Struct one zero int property
BenchmarkChangePointerReceiver  2000000000               0.36 ns/op
BenchmarkChangeItValueReceiver  2000000000               0.36 ns/op

(संपादित करें: कृपया ध्यान दें कि नए जाने वाले संस्करणों में दूसरा बिंदु अमान्य हो गया, टिप्पणियां देखें)
दूसरा बिंदु यह कहता है, यह "कुशल और स्पष्ट" है जो स्वाद का मामला है, है ना? व्यक्तिगत रूप से मैं हर जगह उसी तरह का उपयोग करके संगतता पसंद करता हूं। किस अर्थ में दक्षता? प्रदर्शन के अनुसार ऐसा लगता है कि सूचक लगभग हमेशा अधिक कुशल हैं। कुछ अंतर संपत्ति के साथ कुछ परीक्षण-रन ने मूल्य रिसीवर (0.01-0.1 ns / op की सीमा) का न्यूनतम लाभ दिखाया

क्या कोई मुझे एक ऐसा मामला बता सकता है जहां एक मूल्य रिसीवर स्पष्ट रूप से अधिक समझ में आता है तो एक पॉइंटर रिसीवर? या क्या मैं बेंचमार्क में कुछ गलत कर रहा हूं, क्या मैंने अन्य कारकों की अनदेखी की है?


3
मैंने एक ही स्ट्रिंग फ़ील्ड के साथ और दो फ़ील्ड के साथ समान बेंचमार्क चलाया: स्ट्रिंग और इंट फ़ील्ड। मुझे मूल्य रिसीवर से तेजी से परिणाम मिले। BenchmarkChangePointerReceiver-4 10000000000 0.99 n / op BenchmarkChangeItValueReceiver-4 10000000000 0.33 ns / op यह Go 1.8 का उपयोग कर रहा है। मुझे आश्चर्य है कि जब आपने पिछली बार बेंचमार्क चलाया था, तो संकलक अनुकूलन किए गए थे। देखें सार अधिक जानकारी के लिए।
pbitty

2
आप सही हे। Go1.9 का उपयोग करके अपने मूल बेंचमार्क को चलाना, मुझे अलग-अलग परिणाम अब भी मिलते हैं। सूचक रिसीवर 0.60 ns / op, मान रिसीवर 0.38 ns / op
क्रिसपोर्ट

जवाबों:


118

ध्यान दें कि FAQ में स्थिरता का उल्लेख है

अगला है संगति। यदि टाइप के कुछ तरीकों में पॉइंटर रिसीवर्स होने चाहिए, तो बाकी भी होने चाहिए, इसलिए टाइप सेट का उपयोग किए जाने के तरीके के अनुरूप है। देखें विधि सेट पर अनुभाग जानकारी के लिए है।

जैसा कि इस सूत्र में बताया गया है :

पॉइंटर्स बनाम वैल्यूज़ फॉर रिसीवर्स के बारे में नियम यह है कि वैल्यू मेथड्स पॉइंटर्स और वैल्यूज़ पर लगाए जा सकते हैं, लेकिन पॉइंटर मेथड्स केवल पॉइंटर्स पर ही लगाए जा सकते हैं।

अभी:

क्या कोई मुझे एक ऐसा मामला बता सकता है जहां एक मूल्य रिसीवर स्पष्ट रूप से अधिक समझ में आता है तो एक पॉइंटर रिसीवर?

कोड की समीक्षा टिप्पणी मदद कर सकते हैं:

  • यदि रिसीवर एक नक्शा, फ़ंक या चान है, तो इसके लिए एक पॉइंटर का उपयोग न करें।
  • यदि रिसीवर एक स्लाइस है और विधि स्लाइस को फिर से नहीं बनाती है या उसे पुनः प्राप्त नहीं करती है, तो इसके लिए एक पॉइंटर का उपयोग न करें।
  • यदि विधि को रिसीवर को म्यूट करना है, तो रिसीवर को एक पॉइंटर होना चाहिए।
  • यदि रिसीवर एक ऐसी संरचना है जिसमें एक sync.Mutexसमान या समान सिंक्रनाइज़िंग फ़ील्ड शामिल है, तो रिसीवर को प्रतिलिपि से बचने के लिए एक सूचक होना चाहिए।
  • यदि रिसीवर एक बड़ी संरचना या सरणी है, तो एक सूचक रिसीवर अधिक कुशल है। कितना बड़ा है? मान लें कि यह विधि के तर्क के रूप में अपने सभी तत्वों को पारित करने के बराबर है। यदि यह बहुत बड़ा लगता है, तो यह रिसीवर के लिए बहुत बड़ा है।
  • कार्य कर सकते हैं या तरीकों, या तो समवर्ती या जब इस विधि से कहा जाता है, तो रिसीवर को म्यूट किया जा सकता है? वैल्यू टाइप करने पर रिसीवर की एक कॉपी तैयार हो जाती है, ताकि विधि को इस रिसीवर पर लागू नहीं किया जा सके। यदि मूल रिसीवर में परिवर्तन दिखाई देने चाहिए, तो रिसीवर को एक सूचक होना चाहिए।
  • यदि रिसीवर एक स्ट्रक्चर, एरे या स्लाइस है और इसका कोई भी तत्व किसी ऐसी चीज का पॉइंटर है, जो म्यूटिंग हो सकती है, तो पॉइंटर रिसीवर को प्राथमिकता दें, क्योंकि यह रीडर के इरादे को और स्पष्ट कर देगा।
  • यदि रिसीवर एक छोटी सी सरणी या संरचना है जो स्वाभाविक रूप से एक मूल्य प्रकार (उदाहरण के लिए, कुछ time.Timeप्रकार की तरह) है, जिसमें कोई परिवर्तनशील क्षेत्र और कोई संकेत नहीं है, या सिर्फ एक साधारण बुनियादी प्रकार है जैसे कि इंट या स्ट्रिंग, एक मूल्य रिसीवर बनाता है भाव
    एक मूल्य रिसीवर उत्पन्न होने वाले कचरे की मात्रा को कम कर सकता है; यदि किसी मान को एक विधि विधि में पारित किया जाता है, तो ढेर पर आवंटित करने के बजाय एक ऑन-स्टैक कॉपी का उपयोग किया जा सकता है। (संकलक इस आवंटन से बचने के बारे में स्मार्ट होने की कोशिश करता है, लेकिन यह हमेशा सफल नहीं हो सकता है।) पहले बिना इस कारण के लिए एक मूल्य रिसीवर प्रकार का चयन न करें।
  • अंत में, जब संदेह हो, तो एक पॉइंटर रिसीवर का उपयोग करें।

उदाहरण के लिए बोल्ड में भाग पाया जाता है net/http/server.go#Write():

// Write writes the headers described in h to w.
//
// This method has a value receiver, despite the somewhat large size
// of h, because it prevents an allocation. The escape analysis isn't
// smart enough to realize this function doesn't mutate h.
func (h extraHeader) Write(w *bufio.Writer) {
...
}

16
The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers सच नहीं, वास्तव में। मूल्य रिसीवर और पॉइंटर रिसीवर दोनों तरीकों को सही ढंग से टाइप किए गए पॉइंटर या नॉन-पॉइंटर पर लगाया जा सकता है। विधि बॉडी पर क्या कहा जाता है, इसके बावजूद, रिसीवर के पहचानकर्ता द्वारा वैल्यू रिसीवर का उपयोग किए जाने पर बाय-कॉपी वैल्यू, और पॉइंटर रिसीवर का उपयोग करने पर एक पॉइंटर को संदर्भित
हार्ट सिंह

3
यहाँ एक बहुत अच्छी व्याख्या है "यदि x पता योग्य है और x का तरीका सेट में m, xm () ((x) .m () के लिए शॉर्टहैंड है।"
tera

@tera हाँ: जिस पर चर्चा की गई है stackoverflow.com/q/43953187/6309
VonC

4
शानदार जवाब लेकिन मैं इस बात से पूरी तरह असहमत हूं: "जैसा कि यह इरादा और अधिक स्पष्ट कर देगा", एनओपीई, एक स्वच्छ एपीआई, एक्स तर्क के रूप में और वाई एक वापसी मूल्य के रूप में एक स्पष्ट इरादा है। पॉइंटर से एक स्ट्रक्चर को पास करना और कोड को ध्यान से पढ़ने में समय बिताना यह जांचने के लिए कि सभी विशेषताओं को संशोधित किया गया है, स्पष्ट, रख-रखाव से बहुत दूर है।
लुकास लुकाक

@HartSimha मुझे लगता है कि ऊपर की पोस्ट इस बात की ओर इशारा करती है कि पॉइंटर रिसीवर के तरीके वैल्यू टाइप के लिए "मेथड सेट" में नहीं हैं। आपके लिंक किए गए खेल के मैदान में, निम्नलिखित पंक्ति को जोड़ने से संकलन त्रुटि हो जाएगी Int(5).increment_by_one_ptr():। इसी तरह, विधि increment_by_one_ptrको परिभाषित करने वाला गुण प्रकार के मूल्य से संतुष्ट नहीं होगा Int
गौरव अग्रवाल

16

@VonC महान, जानकारीपूर्ण उत्तर के अतिरिक्त जोड़ने के लिए।

मुझे आश्चर्य है कि परियोजना के बड़े होने के बाद किसी ने वास्तव में रखरखाव की लागत का उल्लेख नहीं किया, पुराने देव अवकाश और नए आए। जाओ निश्चित रूप से एक युवा भाषा है।

आम तौर पर, मैं जब मैं कर सकता हूं, तो मैं संकेत से बचने की कोशिश करता हूं, लेकिन उनकी जगह और सुंदरता है।

मैं संकेत का उपयोग करता हूं जब:

  • बड़े डेटासेट के साथ काम करना
  • एक बनाए रखने की स्थिति है, उदाहरण के लिए TokenCache,
    • मुझे यकीन है कि सभी क्षेत्र निजी हैं, बातचीत केवल परिभाषित विधि प्राप्तकर्ताओं के माध्यम से संभव है
    • मैं इस समारोह को किसी भी गोरोइन के लिए पास नहीं करता

उदाहरण के लिए:

type TokenCache struct {
    cache map[string]map[string]bool
}

func (c *TokenCache) Add(contract string, token string, authorized bool) {
    tokens := c.cache[contract]
    if tokens == nil {
        tokens = make(map[string]bool)
    }

    tokens[token] = authorized
    c.cache[contract] = tokens
}

कारण क्यों मैं संकेत से बचने:

  • संकेत समवर्ती रूप से सुरक्षित नहीं हैं (गोलांग का पूरा बिंदु)
  • एक बार पॉइंटर रिसीवर, हमेशा पॉइंटर रिसीवर (स्थिरता के लिए सभी संरचना के तरीकों के लिए)
  • म्यूटेक्स "मूल्य कॉपी लागत" की तुलना करने के लिए निश्चित रूप से अधिक महंगे, धीमे और कठिन हैं
  • "मूल्य कॉपी लागत" की बात, क्या यह वास्तव में एक मुद्दा है? समयपूर्व अनुकूलन सभी बुराई की जड़ है, आप हमेशा बाद में संकेत जोड़ सकते हैं
  • यह सीधे तौर पर, मुझे छोटे स्ट्रक्चर को डिजाइन करने के लिए मजबूर करता है
  • स्पष्ट इरादे और स्पष्ट I / O के साथ शुद्ध कार्यों को डिज़ाइन करके पॉइंटर्स को ज्यादातर बचा जा सकता है
  • मेरा मानना ​​है कि कचरा संग्रह कठिन है
  • इनकैप्सुलेशन, जिम्मेदारियों के बारे में बहस करना आसान है
  • इसे सरल रखें, बेवकूफ (हाँ, संकेत मुश्किल हो सकते हैं क्योंकि आप अगले प्रोजेक्ट के देव को कभी नहीं जानते हैं)
  • यूनिट का परीक्षण गुलाबी बाग (मैन मैड एक्सप्रेशन?) के माध्यम से चलने जैसा है, आसान है
  • कोई NIL यदि शर्तें (NIL को पारित किया जा सकता है जहाँ एक पॉइंटर अपेक्षित था)

अंगूठे का मेरा नियम, संभव के रूप में कई समझाया तरीके लिखें:

package rsa

// EncryptPKCS1v15 encrypts the given message with RSA and the padding scheme from PKCS#1 v1.5.
func EncryptPKCS1v15(rand io.Reader, pub *PublicKey, msg []byte) ([]byte, error) {
    return []byte("secret text"), nil
}

cipherText, err := rsa.EncryptPKCS1v15(rand, pub, keyBlock) 

अपडेट करें:

इस प्रश्न ने मुझे इस विषय पर अधिक शोध करने और इसके बारे में एक ब्लॉग पोस्ट लिखने के लिए प्रेरित किया https://medium.com/gophersland/gopher-vs-object-oriented-golang-4fa62b88c701


आप जो कहते हैं, उसका ९९% मुझे पसंद है और इससे दृढ़ता से सहमत हूं। कहा कि मैं सोच रहा हूं कि क्या आपका उदाहरण आपकी बात को स्पष्ट करने का सबसे अच्छा तरीका है। TokenCache अनिवार्य रूप से एक नक्शा नहीं है (@VonC से - "यदि रिसीवर एक नक्शा, दुर्गंध या चान है, तो इसके लिए एक सूचक का उपयोग न करें")। चूंकि नक्शे संदर्भ प्रकार हैं जो आपको एक पॉइंटर रिसीवर "ऐड ()" बनाने से आपको क्या फायदा होता है? TokenCache की कोई भी कॉपी एक ही नक्शे को संदर्भित करेगी। यह खेल का मैदान देखें - play.golang.com/p/Xda1rsGwvhq
रिच

खुशी है कि हम गठबंधन कर रहे हैं। महान बिंदु। वास्तव में, मुझे लगता है कि मैंने इस उदाहरण में पॉइंटर का उपयोग किया है क्योंकि मैंने इसे एक प्रोजेक्ट से कॉपी किया है जहां टोकनकेन केवल नक्शे से अधिक सामान संभाल रहा है। और यदि मैं एक विधि में एक सूचक का उपयोग करता हूं, तो मैं उन सभी में इसका उपयोग करता हूं। क्या आप इस विशेष SO उदाहरण से सूचक को हटाने का सुझाव देते हैं?
लुकास लुकाक

LOL, कॉपी / पेस्ट फिर से हमला! Ill IMO आप इसे छोड़ सकते हैं क्योंकि यह एक ऐसा जाल दिखाता है जिसमें गिरना आसान होता है, या आप नक्शे को किसी चीज़ (एस) से बदल सकते हैं जो राज्य और / या एक बड़ी डेटा संरचना को प्रदर्शित करता है।
रिच

ठीक है, मुझे यकीन है कि वे टिप्पणियों को पढ़ेंगे ... पीएस: अमीर, आपके तर्क उचित प्रतीत होते हैं, मुझे जोड़ने के लिए खुश लिंक्डइन (मेरी प्रोफ़ाइल में लिंक) पर मुझे जोड़ें।
लुकास लुकाक
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.