ฉันปฏิบัติตามโปรแกรม hello world Go ซึ่งสร้างไฟล์ปฏิบัติการบนเครื่อง linux ของฉัน แต่ฉันประหลาดใจเมื่อเห็นขนาดของโปรแกรม Hello world Go แบบธรรมดามันคือ 1.9MB!
เหตุใดการเรียกใช้งานโปรแกรมง่ายๆใน Go จึงมีขนาดใหญ่มาก
ฉันปฏิบัติตามโปรแกรม hello world Go ซึ่งสร้างไฟล์ปฏิบัติการบนเครื่อง linux ของฉัน แต่ฉันประหลาดใจเมื่อเห็นขนาดของโปรแกรม Hello world Go แบบธรรมดามันคือ 1.9MB!
เหตุใดการเรียกใช้งานโปรแกรมง่ายๆใน Go จึงมีขนาดใหญ่มาก
dotnet publish -r win-x64 -p:publishsinglefile=true -p:publishreadytorun=true -p:publishtrimmed=true
สร้างไฟล์ไบนารีประมาณ ~ 26MB!
คำตอบ:
คำถามที่แน่นอนนี้ปรากฏในคำถามที่พบบ่อยอย่างเป็นทางการ: เหตุใดโปรแกรมเล็กน้อยของฉันจึงเป็นไบนารีขนาดใหญ่
อ้างคำตอบ:
Linkers ในห่วงโซ่เครื่องมือประชาคมโลก (
5l
,6l
และ8l
) ทำคงที่เชื่อมโยง ดังนั้นไบนารีของ Go ทั้งหมดจึงรวม Go run-time พร้อมกับข้อมูลประเภทรันไทม์ที่จำเป็นเพื่อรองรับการตรวจสอบประเภทไดนามิกการสะท้อนกลับและแม้แต่สแต็กเทรซเวลาตื่นตระหนกโปรแกรม C "สวัสดีชาวโลก" ที่เรียบง่ายที่คอมไพล์และเชื่อมโยงแบบคงที่โดยใช้ gcc บน Linux นั้นอยู่ที่ประมาณ 750 kB รวมถึงการใช้งาน
printf
. โปรแกรม Go ที่เทียบเท่าโดยใช้fmt.Printf
มีขนาดประมาณ 1.9 MB แต่รวมถึงการสนับสนุนรันไทม์ที่มีประสิทธิภาพมากขึ้นและข้อมูลประเภท
ดังนั้นไฟล์ปฏิบัติการดั้งเดิมของ Hello World ของคุณคือ 1.9 MB เนื่องจากมีรันไทม์ที่ให้การรวบรวมขยะการสะท้อนและคุณสมบัติอื่น ๆ อีกมากมาย (ซึ่งโปรแกรมของคุณอาจไม่ได้ใช้จริงๆ แต่อยู่ที่นั่น) และการใช้งานfmt
แพคเกจที่คุณใช้ในการพิมพ์"Hello World"
ข้อความ (รวมถึงการอ้างอิง)
ลองทำสิ่งต่อไปนี้: เพิ่มอีกfmt.Println("Hello World! Again")
บรรทัดในโปรแกรมของคุณแล้วคอมไพล์อีกครั้ง ผลลัพธ์จะไม่ใช่ 2x 1.9MB แต่ยังคงเป็นเพียง 1.9 MB! ใช่เนื่องจากไลบรารีที่ใช้ทั้งหมด ( fmt
และการอ้างอิง) และรันไทม์ถูกเพิ่มลงในไฟล์ปฏิบัติการแล้ว (และจะเพิ่มไบต์อีกเพียงไม่กี่ไบต์เพื่อพิมพ์ข้อความที่ 2 ที่คุณเพิ่งเพิ่มเข้าไป)
พิจารณาโปรแกรมต่อไปนี้:
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
หากฉันสร้างสิ่งนี้บนเครื่อง Linux AMD64 ของฉัน (Go 1.9) เช่นนี้:
$ go build
$ ls -la helloworld
-rwxr-xr-x 1 janf group 2029206 Sep 11 16:58 helloworld
ฉันได้รับไบนารีที่มีขนาดประมาณ 2 Mb
เหตุผลนี้ (ซึ่งได้อธิบายไว้ในคำตอบอื่น ๆ ) คือเราใช้แพ็คเกจ "fmt" ซึ่งมีขนาดค่อนข้างใหญ่ แต่ไบนารียังไม่ถูกถอดออกและนั่นหมายความว่าตารางสัญลักษณ์ยังคงอยู่ที่นั่น หากเราสั่งให้คอมไพเลอร์ถอดไบนารีแทนมันจะเล็กลงมาก:
$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 1323616 Sep 11 17:01 helloworld
อย่างไรก็ตามหากเราเขียนโปรแกรมใหม่เพื่อใช้ฟังก์ชัน builtin พิมพ์แทน 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 ดังนั้นค่าใช้จ่ายของ Go-runtime จึงอยู่ที่ประมาณ 700 Kb
หมายเหตุว่าปัญหาที่ขนาดไบนารีติดตามโดยปัญหา 6853ในgolang / โครงการไป
ตัวอย่างเช่นกระทำ a26c01a (สำหรับ Go 1.4) ตัด hello world 70kB :
เพราะเราไม่ได้เขียนชื่อเหล่านั้นลงในตารางสัญลักษณ์
เมื่อพิจารณาจากคอมไพเลอร์แอสเซมเบลอร์ตัวเชื่อมโยงและรันไทม์สำหรับ 1.5 ทั้งหมดจะอยู่ใน Go คุณสามารถคาดหวังการเพิ่มประสิทธิภาพเพิ่มเติมได้
อัปเดต 2016 Go 1.7: ได้รับการปรับให้เหมาะสม: โปรดดู " ไบนารี Smaller Go 1.7 "
แต่เหล่านี้วัน (เมษายน 2019) runtime.pclntab
สิ่งที่ใช้สถานที่มากที่สุดคือ
โปรดดูที่ " ทำไมไปไฟล์ปฏิบัติการของฉันเพื่อสร้างภาพขนาดใหญ่? ขนาดของ executables ไปใช้ D3 " จากราฟาเอล 'kena' แคลอรี่
ไม่ได้รับการจัดทำเป็นเอกสารที่ดีเกินไปอย่างไรก็ตามความคิดเห็นนี้จากซอร์สโค้ด Go แสดงให้เห็นถึงวัตถุประสงค์:
// A LineTable is a data structure mapping program counters to line numbers.
จุดประสงค์ของโครงสร้างข้อมูลนี้คือการเปิดใช้งานระบบรันไทม์ของ Go เพื่อสร้างสแต็กเทรซที่อธิบายได้เมื่อเกิดข้อขัดข้องหรือเมื่อมีการร้องขอภายในผ่านทาง
runtime.GetStack
APIจึงดูเหมือนมีประโยชน์ แต่ทำไมถึงมีขนาดใหญ่?
URL https://golang.org/s/go12symtab ที่ซ่อนอยู่ในไฟล์ต้นทางที่ลิงก์ดังกล่าวจะเปลี่ยนเส้นทางไปยังเอกสารที่อธิบายถึงสิ่งที่เกิดขึ้นระหว่าง Go 1.0 และ 1.2 ในการถอดความ:
ก่อนหน้า 1.2 ตัวเชื่อมต่อ Go กำลังส่งตารางบรรทัดที่บีบอัดและโปรแกรมจะคลายการบีบอัดเมื่อเริ่มต้นในขณะทำงาน
ใน Go 1.2 มีการตัดสินใจที่จะขยายตารางบรรทัดล่วงหน้าในไฟล์ปฏิบัติการเป็นรูปแบบสุดท้ายที่เหมาะสมสำหรับการใช้งานโดยตรงในขณะทำงานโดยไม่ต้องมีขั้นตอนการคลายการบีบอัดเพิ่มเติม
กล่าวอีกนัยหนึ่งทีม Go ตัดสินใจที่จะทำให้ไฟล์ปฏิบัติการมีขนาดใหญ่ขึ้นเพื่อประหยัดเวลาในการเริ่มต้น
นอกจากนี้เมื่อดูที่โครงสร้างข้อมูลดูเหมือนว่าขนาดโดยรวมในไบนารีที่คอมไพล์แล้วนั้นมีจำนวนฟังก์ชันที่เป็นเส้นตรงมากในโปรแกรมนอกเหนือจากความใหญ่ของแต่ละฟังก์ชัน