गो

7

में अनावश्यक ढेर आवंटन के कारण वैराडिक फ़ंक्शंस मैं वर्तमान में गो में कुछ प्रदर्शन संवेदनशील कोड पर काम कर रहा हूं। एक बिंदु पर मेरे पास विशेष रूप से तंग आंतरिक लूप होता है जो उत्तराधिकार में तीन चीजें करता है:गो

  1. डेटा पर कई पॉइंटर्स प्राप्त करें। दुर्लभ त्रुटि की स्थिति में, इनमें से एक या अधिक पॉइंटर्स nil हो सकते हैं।

  2. जांचें कि क्या यह त्रुटि आई है, और यदि उसके पास कोई त्रुटि है तो लॉग इन करें।

  3. पॉइंटर्स में संग्रहीत डेटा के साथ काम करें।

नीचे दिखाया गया एक ही संरचना के साथ खिलौना कार्यक्रम है (हालांकि पॉइंटर्स वास्तव में शून्य नहीं हो सकते हैं)।

package main 

import (
    "math/rand" 
    "fmt" 
) 

const BigScaryNumber = 1<<25 

func DoWork() { 
    sum := 0 
    for i := 0; i < BigScaryNumber; i++ { 
     // Generate pointers. 
     n1, n2 := rand.Intn(20), rand.Intn(20) 
     ptr1, ptr2 := &n1, &n2 

     // Check if pointers are nil. 
     if ptr1 == nil || ptr2 == nil { 
      fmt.Printf("Pointers %v %v contain a nil.\n", ptr1, ptr2) 
      break 
     } 

     // Do work with pointer contents. 
     sum += *ptr1 + *ptr2 
    } 
} 

func main() { 
    DoWork() 
} 

जब मैं अपने मशीन पर इस चलाने के लिए, मैं निम्नलिखित मिल:

$ go build alloc.go && time ./alloc 

real 0m5.466s 
user 0m5.458s 
sys  0m0.015s 

हालांकि, अगर मैं प्रिंट बयान निकालने के लिए, मैं निम्नलिखित मिल:

$ go build alloc_no_print.go && time ./alloc_no_print 

real 0m4.070s 
user 0m4.063s 
sys  0m0.008s 

के बाद से प्रिंट स्टेटमेंट को वास्तव में कभी नहीं कहा जाता है, मैंने जांच की कि क्या प्रिंट स्टेटमेंट किसी भी तरह से स्टैक के बजाय ढेर पर पॉइंटर्स आवंटित किया गया था। मूल कार्यक्रम पर -m ध्वज के साथ चल रहा है संकलक देता है:

$ go build -gcflags=-m alloc.go 
# command-line-arguments 
./alloc.go:14: moved to heap: n1 
./alloc.go:15: &n1 escapes to heap 
./alloc.go:14: moved to heap: n2 
./alloc.go:15: &n2 escapes to heap 
./alloc.go:19: DoWork ... argument does not escape 

जबकि एक प्रिंट बयान कम कार्यक्रम पर यह कर

$ go build -gcflags=-m alloc_no_print.go 
# command-line-arguments 
./alloc_no_print.go:14: DoWork &n1 does not escape 
./alloc_no_print.go:14: DoWork &n2 does not escape 

की पुष्टि कर देता है कि जो भी एक अप्रयुक्त fmt.Printf() खड़ी कर रहा है ढेर आवंटन प्रदर्शन पर एक बहुत ही वास्तविक प्रभाव। मैं एक variadic समारोह जो कुछ नहीं करता है और interface{} रों बजाय पैरामीटर के रूप में *int रों लेता है साथ fmt.Printf() जगह करके समान व्यवहार प्राप्त कर सकते हैं:

func VarArgsError(ptrs ...*int) { 
    panic("An error has occurred.") 
} 

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

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

fmt.Printf("Pointers %v %v contain a nil.", Copy(ptr1), Copy(ptr2)) 

हालांकि यह मुझे नहीं के रूप में ही प्रदर्शन देता है जहां Copy() रूप

func Copy(ptr *int) *int { 
    if ptr == nil { 
     return nil 
    } else { 
     n := *ptr 
     return &n 
    } 
} 

परिभाषित किया गया है: मेरा सर्वोत्तम अनुमान उन्हें प्रिंट बयान में गुजर करने से पहले संकेत के चारों ओर एक Copy() समारोह रैप करने के लिए है प्रिंट कथन केस, यह अजीब है और नहीं कि मैं प्रत्येक वैरिएबल प्रकार के लिए फिर से लिखना चाहता हूं और फिर मेरे त्रुटि लॉगिंग कोड के सभी को लपेटें।

+0

ठीक है, शुरुआत करने वालों के लिए, 'एफएमटी' पैकेज भारी फैंसी स्ट्रक्चर प्रिंटिंग प्राप्त करने के लिए प्रतिबिंब का उपयोग करता है। यदि आप वास्तव में प्रदर्शन के लिए शूटिंग कर रहे हैं तो यह एक बाधा हो सकती है। मुझे एहसास है कि इसे भी बुलाया नहीं जा रहा है - लेकिन यह सोचने के लिए कुछ और है। क्या मैं पूछ सकता हूं, क्या होता है यदि आप अपना खुद का वैरैडिक फ़ंक्शन लिखते हैं जो __NOT__ प्रकार 'इंटरफ़ेस {} 'के तर्कों को स्वीकार करता है? क्या आप एक ही मुद्दे देखते हैं? –

+0

हां, मैंने इसे एक वैरिएडिक फ़ंक्शन पर परीक्षण किया जिसने '* int को तर्क के रूप में लिया, लेकिन यह निर्दिष्ट करना भूल गया कि स्रोत को शामिल करना है या जो मैंने अभी किया है (जिसे मैंने अभी किया है)। परिणाम 'Printf()' के समान हैं। साथ ही, आपने जिन कारणों का उल्लेख किया है, उनके लिए मैं आमतौर पर उन अनुभागों में 'fmt' पैकेज का उपयोग नहीं करता जो प्रदर्शन के लिए महत्वपूर्ण हैं। हालांकि यह निश्चित रूप से ध्यान देने योग्य एक अच्छी बात है। – mansfield

+0

ओह, केवल मामूली रूप से सुंदर है, लेकिन दूसरा विकल्प: 'ptr1, ptr2: = ptr1, ptr2' if block के अंदर। जब तक कि कंपाइलर उस ऑप्टिमाइज़ को ऑप्टिमाइज़ नहीं करता है, अब यह 'if' से बचने वाले दो चर के रूप में घोषित किया गया है, जो 'अगर' भागने के अंदर बनाई गई 'कॉपी' से अस्थायी रूप से लौटाया जा सकता है। – twotwotwo

उत्तर

1

Go FAQ से,

वर्तमान compilers में, यदि एक चर इसका पता ले लिया है, कि चर ढेर पर आवंटन के लिए एक उम्मीदवार है। हालांकि, एक मूल एस्केप विश्लेषण कुछ मामलों को पहचानता है जब ऐसे चर फ़ंक्शन से रिटर्न से पहले नहीं रहेंगे और स्टैक पर रह सकते हैं।

जब पॉइंटर्स फ़ंक्शन पर पास हो जाते हैं, तो मुझे लगता है कि यह भागने के विश्लेषण के दूसरे भाग में विफल रहता है। उदाहरण के लिए, फ़ंक्शन पॉइंटर को अपने पैकेज में ग्लोबल वैरिएबल में असाइन कर सकता है जो वर्तमान स्टैक से अधिक समय तक रहता है। मुझे नहीं लगता कि वर्तमान कंपाइलर ऐसे गहरे भागने का विश्लेषण करता है।

आवंटन की लागत से बचने का एक तरीका लूप के बाहर आवंटन को स्थानांतरित करना होगा और लूप के अंदर आवंटित स्मृति को मान को पुन: असाइन करना होगा।

func DoWork() { 
    sum := 0 
    n1, n2 := new(int), new(int) 

    for i := 0; i < BigScaryNumber; i++ { 
     *n1, *n2 = rand.Intn(20), rand.Intn(20) 
     ptr1, ptr2 := n1, n2 

     // Check if pointers are nil. 
     if ptr1 == nil || ptr2 == nil { 
      fmt.Printf("Pointers %v %v contain a nil.\n", n1, n2) 
      break 
     } 

     // Do work with pointer contents. 
     sum += *ptr1 + *ptr2 
    } 
} 
+0

[उपरोक्त टिप्पणी] [http://stackoverflow.com/questions/27788813/variadic-functions-causing-unnecessary-heap-allocations-in-go#comment43998000_27788813) कारण के कारण अधिक सटीक है। किसी भी तरह आपका समाधान काम करता है अगर अलग-अलग फ़ंक्शन नामों को बदलता नहीं है या पॉइंटर्स को बाद में उपयोग के लिए स्टोर नहीं करता है, तो यह ठीक है अगर ओपी सिर्फ एक प्रिंटफ को त्रुटि होनी चाहिए। – wldsvc

+0

मैंने जो कहा है उसे स्क्रैच करें, आपका कोड लूप के प्रत्येक पुनरावृत्ति पर आने वाले डेटा (एन 1 और एन 2) की प्रतिलिपि बनाता है, जो इस मामले में उप-शीर्ष है। वह संभवतः एक int से अधिक संरचनाओं के लिए पॉइंटर्स से निपट रहा है। उनका एकमात्र समाधान केवल बचने वाले ब्लॉक में डेटा की प्रतिलिपि बनाना है ('अगर ptr1 == nil ... {') – wldsvc