Go में Pointers बनाम Values: प्रदर्शन को कैसे न मारें

शुरुआती लोगों के लिए ऑनलाइन Python प्रशिक्षक

Python आसानी से सीखें बिना सिद्धांत के बोझ के। स्वचालित जांच के साथ व्यावहारिक कार्य हल करें और ब्राउज़र में सीधे कोड लिखें।

पाठ्यक्रम शुरू करें

Go में Pointers बनाम Values: प्रदर्शन और सुरक्षा के बीच की रेखा

\n\n

एक स्थिति की कल्पना करें: आप Go में लिख रहे हैं, सब कुछ सही चल रहा है, लेकिन अचानक आप देखते हैं कि बड़े डेटा वॉल्यूम पर एप्लिकेशन "धीमा" होने लगता है। आप प्रोफाइलर खोलते हैं — और देखते हैं कि allocs/op (प्रति ऑपरेशन आवंटन) बहुत अधिक हैं। परिचित लगता है? सबसे अधिक संभावना है, समस्या यह है कि आप फ़ंक्शन में डेटा कैसे पास कर रहे हैं और उसे वापस लौटा रहे हैं। सवाल "पैरामीटर और रिटर्न वैल्यूज़ में Pointers बनाम Values" Stack Overflow पर सबसे गर्म विषयों में से एक है (हज़ारों व्यूज़!), और आज हम इसे हिंदी में, जीवंत और वास्तविक उदाहरणों के साथ समझेंगे।

\n\n

Go एक ऐसी भाषा है जो सादगी पसंद करती है। लेकिन यह सादगी भ्रामक है: वैल्यू (value) और पॉइंटर (pointer) के बीच चुनाव प्रोग्राम की गति और मेमोरी खपत को मौलिक रूप से बदल सकता है। आइए समझते हैं कि कब क्या उपयोग करना चाहिए, ताकि आपका कोड उड़े, रेंगे नहीं।

\n\n

यह विषय इतना महत्वपूर्ण क्यों है?

\n\n

Java या Python के विपरीत, जहाँ सब कुछ ऑब्जेक्ट का संदर्भ है, Go में आप स्पष्ट रूप से चुनते हैं: संरचना की एक प्रतिलिपि पास करें या उसका पता। यह एक छोटी सी बात लगती है। लेकिन वास्तव में, यह एक मौलिक निर्णय है जो प्रभावित करता है:

\n\n
    \n
  • निष्पादन गति — बड़ी संरचनाओं की प्रतिलिपि बनाने में समय लगता है।
  • \n
  • मेमोरी खपत — अतिरिक्त प्रतियाँ = अतिरिक्त आवंटन।
  • \n
  • डेटा सुरक्षा — पॉइंटर्स मूल को बदलने की अनुमति देते हैं, वैल्यूज़ नहीं।
  • \n
  • इंटरफेस के साथ काम — Go में इंटरफेस एक पेचीदा तंत्र है जो पॉइंटर्स और वैल्यूज़ के साथ अलग-अलग काम करता है।
  • \n
\n\n

आइए सीधे मुद्दे पर आते हैं और कोड देखते हैं।

\n\n

उदाहरण #1: छोटी संरचना

\n\n

सरल से शुरू करते हैं। मान लीजिए, हमारे पास दो निर्देशांकों वाली एक संरचना Point है:

\n\n
type Point struct {\n    X, Y float64\n}\n\n// वैल्यू द्वारा पास करना\nfunc MoveByValue(p Point, dx, dy float64) Point {\n    p.X += dx\n    p.Y += dy\n    return p\n}\n\n// पॉइंटर द्वारा पास करना\nfunc MoveByPointer(p *Point, dx, dy float64) {\n    p.X += dx\n    p.Y += dy\n}
\n\n

पहले मामले में, हम संरचना की एक प्रतिलिपि बनाते हैं, उसे संशोधित करते हैं और एक नई लौटाते हैं। दूसरे में, हम मूल को सीधे बदलते हैं। कौन सा विकल्प तेज़ है? इतनी छोटी संरचना (16 बाइट्स) के लिए — लगभग कोई अंतर नहीं है। 16 बाइट्स की प्रतिलिपि बनाना एक परमाणु संक्रिया है, और GC (कचरा संग्रहक) कोई अतिरिक्त भार नहीं बनाता है। इसके अलावा, वैल्यू द्वारा पास करना और भी तेज़ हो सकता है, क्योंकि पॉइंटर को अतिरिक्त डीरेफरेंस (dereference) की आवश्यकता होती है।

\n\n

निष्कर्ष: छोटी संरचनाओं (बुनियादी प्रकारों के 4-8 फ़ील्ड तक) के लिए, बेझिझक वैल्यूज़ का उपयोग करें। यह सुरक्षित है और कोई ओवरहेड नहीं देता है।

\n\n

उदाहरण #2: बड़ी संरचना

\n\n

अब कल्पना करें कि हम उपयोगकर्ता डेटा के साथ काम कर रहे हैं:

\n\n
type User struct {\n    ID        int64\n    Name      string\n    Email     string\n    Avatar    []byte // 1 MB की छवि\n    CreatedAt time.Time\n    UpdatedAt time.Time\n}
\n\n

यहाँ Avatar एक स्लाइस है, जो Go में एक संदर्भ प्रकार है (हेडर: डेटा का पॉइंटर, लंबाई, क्षमता)। लेकिन प्रतिलिपि बनाते समय संरचना केवल स्लाइस हेडर (24 बाइट्स) की प्रतिलिपि बनाएगी, स्वयं बाइट्स की नहीं। हालाँकि, यदि संरचना में कई फ़ील्ड हैं, तो प्रत्येक फ़ंक्शन कॉल पर 100+ बाइट्स की प्रतिलिपि बनाना पहले से ही ध्यान देने योग्य है।

\n\n

एक सरल बेंचमार्क चलाते हैं:

\n\n
func BenchmarkPassByValue(b *testing.B) {\n    u := User{ID: 1, Name: "John", Avatar: make([]byte, 1024*1024)}\n    for i := 0; i < b.N; i++ {\n        _ = processUserByValue(u)\n    }\n}\n\nfunc BenchmarkPassByPointer(b *testing.B) {\n    u := &User{ID: 1, Name: "John", Avatar: make([]byte, 1024*1024)}\n    for i := 0; i < b.N; i++ {\n        _ = processUserByPointer(u)\n    }\n}
\n\n

परिणाम (वास्तविक हार्डवेयर पर):

\n
    \n
  • PassByValue: ~120 ns/op, 0 आवंटन
  • \n
  • PassBy

ब्लॉग

पुस्तक अनुशंसाएँ