गो के संकलित निष्पादन के विशाल आकार का कारण


91

मैंने एक हैलो वर्ल्ड गो कार्यक्रम का अनुपालन किया, जिसने मेरे लिनक्स मशीन पर देशी निष्पादन योग्य उत्पन्न किया। लेकिन मुझे सरल हैलो वर्ल्ड गो कार्यक्रम का आकार देखकर आश्चर्य हुआ, यह 1.9MB था!

ऐसा क्यों है कि गो में इस तरह के एक सरल कार्यक्रम का निष्पादन इतना बड़ा है?


22
विशाल? मुझे लगता है कि तुम बहुत जावा तो नहीं करते!
रिक -777

20
खैर, C / C ++ बैकग्राउंड से im!
कार्तिक राव

मैंने अभी-अभी इस scala-native hello world की कोशिश की: scala-native.org/en/latest/user/sbt.html#minimal-sbt-project यह काफी समय लगा, संकलन करने के लिए, बहुत सारा सामान डाउनलोड करने में, और बाइनरी 3.9 है एमबी।
bli

मैंने 2019 के निष्कर्षों के साथ अपना जवाब नीचे दिया है
VonC

1
C # .NET Core 3.1 में सरल हैलो वर्ल्ड ऐप dotnet publish -r win-x64 -p:publishsinglefile=true -p:publishreadytorun=true -p:publishtrimmed=true~ 26MB के बारे में एक बाइनरी फ़ाइल उत्पन्न करता है!
जलाल

जवाबों:


91

आधिकारिक प्रश्न में यह सटीक प्रश्न दिखाई देता है: मेरा तुच्छ कार्यक्रम इतना बड़ा द्विआधारी क्यों है?

जवाब उद्धृत करते हुए:

जीसी उपकरण श्रृंखला (में linkers 5l, 6lऔर 8l) स्थिर जोड़ने है। सभी गो बायनेरिज़ में गो रन-टाइम शामिल है, साथ ही डायनामिक टाइप चेक, रिफ्लेक्शन और यहां तक ​​कि पैनिक-टाइम स्टैक के निशान का समर्थन करने के लिए आवश्यक रन-टाइम प्रकार की जानकारी भी शामिल है।

लिनक्स पर जीसीसी का उपयोग करके एक सरल सी "हेलो, वर्ल्ड" कार्यक्रम संकलित और जुड़ा हुआ है, जो लगभग 750 केबी का उपयोग करता है, जिसमें एक कार्यान्वयन भी शामिल है printf। एक समतुल्य गो कार्यक्रम का उपयोग fmt.Printfलगभग 1.9 एमबी है, लेकिन इसमें अधिक शक्तिशाली रन-टाइम समर्थन और प्रकार की जानकारी शामिल है।

इसलिए आपकी हैलो वर्ल्ड का मूल निष्पादन 1.9 एमबी है क्योंकि इसमें एक रनटाइम शामिल है जो कचरा संग्रह, प्रतिबिंब और कई अन्य सुविधाएं प्रदान करता है (जो कि आपका प्रोग्राम वास्तव में उपयोग नहीं कर सकता है, लेकिन यह वहां है)। और उस fmtपैकेज का क्रियान्वयन जो आप "Hello World"टेक्स्ट को प्रिंट करने के लिए करते थे (साथ ही उसकी निर्भरताएँ)।

अब निम्नलिखित प्रयास करें: fmt.Println("Hello World! Again")अपने कार्यक्रम में एक और पंक्ति जोड़ें और इसे फिर से संकलित करें। परिणाम 2x 1.9MB नहीं होगा, लेकिन अभी भी सिर्फ 1.9 एमबी! हां, क्योंकि सभी उपयोग किए गए पुस्तकालयों ( fmtऔर इसकी निर्भरताएं) और रनटाइम को पहले से ही निष्पादन योग्य में जोड़ दिया जाता है (और इसलिए केवल 2 पाठ को प्रिंट करने के लिए कुछ और बाइट्स जोड़े जाएंगे जो आपने अभी जोड़े हैं)।


12
एसी "हैलो वर्ल्ड" कार्यक्रम, स्टेटिक रूप से ग्लिबेक से जुड़ा हुआ है 750K क्योंकि ग्लिब्क स्पष्ट रूप से स्थिर लिंकिंग के लिए डिज़ाइन नहीं किया गया है और कुछ मामलों में ठीक से स्थिर लिंक के लिए भी असंभव है। एक "हेल्लो वर्ल्ड" प्रोग्राम को स्टेटिक रूप से musl libc के साथ जोड़ा गया है जो कि 14K है।
क्रेग बार्न्स

मैं अभी भी देख रहा हूँ, हालांकि, यह जानना अच्छा होगा कि क्या जुड़ा हुआ है ताकि बस एक हमलावर दुष्ट कोड में लिंक न कर रहा हो।
रिचर्ड

तो एक DLL फ़ाइल में Go रनटाइम लाइब्रेरी क्यों नहीं है, ताकि इसे सभी Go exe फ़ाइलों के बीच साझा किया जा सके? फिर 2 एमबी के बजाय "हेलो वर्ल्ड" प्रोग्राम कुछ केबी हो सकता है। हर प्रोग्राम में पूरे रनटाइम लाइब्रेरी का होना विंडोज पर MSVC के अन्यथा अद्भुत विकल्प के लिए एक घातक दोष है।
डेविड स्पेक्टर

मैं बेहतर ढंग से अपनी टिप्पणी पर आपत्ति जताता हूं: कि गो "स्टेटिकली लिंक्ड" है। ठीक है, फिर कोई DLL नहीं। लेकिन स्थैतिक लिंकिंग का मतलब यह नहीं है कि आपको संपूर्ण लाइब्रेरी में (बाइंड) लिंक करने की आवश्यकता है, केवल वे फ़ंक्शन जो वास्तव में लाइब्रेरी में उपयोग किए जाते हैं!
डेविड स्पेक्टर

44

निम्नलिखित कार्यक्रम पर विचार करें:

package main

import "fmt"

func main() {
    fmt.Println("Hello World!")
}

अगर मैं इसे अपने लिनक्स AMD64 मशीन (1.9 1.9) पर बनाता हूं, तो इस तरह:

$ go build
$ ls -la helloworld
-rwxr-xr-x 1 janf group 2029206 Sep 11 16:58 helloworld

मुझे आ बाइनरी मिलती है जो आकार में लगभग 2 एमबी है।

इसका कारण (जिसे अन्य उत्तरों में समझाया गया है) यह है कि हम "fmt" पैकेज का उपयोग कर रहे हैं जो काफी बड़ा है, लेकिन बाइनरी को भी नहीं छीन लिया गया है और इसका मतलब है कि प्रतीक तालिका अभी भी है। यदि हम बाइनरी को हटाने के लिए कंपाइलर को निर्देश देते हैं, तो यह बहुत छोटा हो जाएगा:

$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 1323616 Sep 11 17:01 helloworld

हालांकि, अगर हम fmt.Println के बजाय, इस तरह से अंतर्निहित फ़ंक्शन प्रिंट का उपयोग करने के लिए प्रोग्राम को फिर से लिखते हैं:

package main

func main() {
    print("Hello World!\n")
}

और फिर इसे संकलित करें:

$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 714176 Sep 11 17:06 helloworld

हम एक और भी छोटे बाइनरी के साथ समाप्त होते हैं। यह उतना ही छोटा है जितना कि हम इसे UPX- पैकिंग जैसे ट्रिक्स का सहारा लिए बिना प्राप्त कर सकते हैं, इसलिए गो-रनटाइम का ओवरहेड लगभग 700 Kb है।


4
UPX बायनेरिज़ को संपीड़ित करता है और जब वे निष्पादित होते हैं, तो उन्हें ऑन-द-फ्लाई को विघटित करता है। मैं इसे समझाए बिना एक चाल को खारिज नहीं करूंगा कि यह क्या करता है, क्योंकि यह कुछ परिदृश्यों में उपयोगी हो सकता है। स्टार्टअप समय और रैम उपयोग की कीमत पर बाइनरी आकार कुछ हद तक कम हो जाता है; इसके अलावा, प्रदर्शन थोड़ा प्रभावित हो सकता है। एक उदाहरण के रूप में, एक निष्पादन योग्य को उसके (छीन लिए गए) आकार के 30% तक सिकुड़ा जा सकता है और चलाने में 35ms अधिक समय लग सकता है।
प्रातः

10

ध्यान दें कि बाइनरी आकार का मुद्दा गोलंग / गो प्रोजेक्ट में 6853 अंक द्वारा ट्रैक किया गया है ।

उदाहरण के लिए, कम से कम a26c01a (गो 1.4 के लिए) हैलो दुनिया को 70kB से काटें :

क्योंकि हम उन नामों को प्रतीक तालिका में नहीं लिखते हैं।

संकलक, कोडांतरक, लिंकर और 1.5 के लिए रनटाइम को ध्यान में रखते हुए पूरी तरह से गो में होगा, आप आगे अनुकूलन की उम्मीद कर सकते हैं।


अपडेट 2016 गो 1.7: यह अनुकूलित किया गया है: " छोटे गो 1.7 बायनेरिज़ " देखें।

लेकिन इन दिनों (अप्रैल 2019), सबसे अधिक जगह क्या है runtime.pclntab
देखें " मेरी गो निष्पादन योग्य फाइलें इतनी बड़ी क्यों हैं? राफेल 'केना' पॉस से डी 3 का उपयोग करके आकार के निष्पादन का आकार ।"

यह बहुत अच्छी तरह से प्रलेखित नहीं है लेकिन गो स्रोत कोड की यह टिप्पणी इसका उद्देश्य बताती है:

// A LineTable is a data structure mapping program counters to line numbers.

इस डेटा संरचना का उद्देश्य गो रनटाइम सिस्टम को runtime.GetStackएपीआई के माध्यम से क्रैश या आंतरिक अनुरोधों पर वर्णनात्मक स्टैक निशान बनाने में सक्षम बनाना है।

तो यह उपयोगी लगता है। लेकिन यह इतना बड़ा क्यों है?

URL https://golang.org/s/go12symtab एक अप्रभावित स्रोत फ़ाइल में छिपा हुआ एक दस्तावेज़ में रीडायरेक्ट करता है जो बताता है कि गो 1.0 और 1.2 के बीच क्या हुआ। विवरण बताने के लिए:

1.2 से पहले, गो लिंकर एक संपीड़ित लाइन तालिका का उत्सर्जन कर रहा था, और कार्यक्रम इसे रन-टाइम पर आरंभीकरण पर विघटित करेगा।

गो 1.2 में, एक अतिरिक्त विघटन कदम के बिना, रन-टाइम में प्रत्यक्ष उपयोग के लिए उपयुक्त अपने अंतिम प्रारूप में निष्पादन योग्य फ़ाइल में लाइन टेबल को पूर्व-विस्तारित करने का निर्णय लिया गया था।

दूसरे शब्दों में, गो टीम ने आरंभिक समय पर बचत करने के लिए निष्पादन योग्य फ़ाइलों को बड़ा करने का निर्णय लिया।

इसके अलावा, डेटा संरचना को देखते हुए, यह प्रतीत होता है कि संकलित बायनेरिज़ में इसका समग्र आकार कार्यक्रम में कार्यों की संख्या में सुपर-रैखिक है, इसके अलावा कि प्रत्येक फ़ंक्शन कितना बड़ा है।

https://science.raphael.poss.name/go-executable-size-visualization-with-d3/size-demo-ss.png


2
मैं यह नहीं देखता कि उसके साथ कार्यान्वयन की भाषा का क्या करना है। उन्हें साझा पुस्तकालयों का उपयोग करने की आवश्यकता है। कुछ हद तक अविश्वसनीय है कि वे पहले से ही इस दिन और उम्र में नहीं हैं।
user207421

3
@ ईजेपी: उन्हें साझा पुस्तकालयों का उपयोग करने की आवश्यकता क्यों है?
इश्कबाज

10
@ ईजेपी, गो की सादगी का हिस्सा साझा पुस्तकालयों का उपयोग नहीं करने में है। वास्तव में, गो में कोई निर्भरता नहीं है, यह सादे सिस्कोल्स का उपयोग करता है। बस एक ही बाइनरी को तैनात करें और यह सिर्फ काम करता है। यह भाषा को काफी चोट पहुंचाएगा और अगर यह अन्यथा होगा तो यह पारिस्थितिकी तंत्र है।
क्रेकर

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