Làm thế nào để giảm kích thước tệp đã biên dịch?


82

Hãy so sánh c và đi: Hello_world.c:

#include<stdio.h>
int main(){
    printf("Hello world!");
}

Hello_world.go:

package main
import "fmt"
func main(){
    fmt.Printf("Hello world!")
}

Biên dịch cả hai:

$gcc Hello_world.c -o Hello_c 
$8g Hello_world.go -o Hello_go.8
$8l Hello_go.8 -o Hello_go

và nó là gì?

$ls -ls
... 5,4K 2010-10-05 11:09 Hello_c
... 991K 2010-10-05 11:17 Hello_go

Giới thiệu về 1Mb Xin chào thế giới. Đùa tôi à? Tôi làm gì sai?

(dải Hello_go -> 893K only)


2
Trên máy Mac x86_64, tệp nhị phân "Hello World" là 1,3 MB giống như trên máy Linux x64 mà tôi giả định. Ngược lại, nhị phân x32 của ARM cũng lớn như nhị phân x86_32. Kích thước phụ thuộc đáng kể vào chiều dài "từ" của kiến ​​trúc tương ứng. Trên máy x32 là 32 bit trên x64 rộng 64 bit. Do đó, nhị phân x32 "Hello World" nhỏ hơn khoảng 30%.
Alex,

17
@Nick: Xem xét việc GO được tiếp thị như một ngôn ngữ hệ thống, tôi nghĩ đó là một câu hỏi công bằng. Tôi làm việc trong các hệ thống và chúng tôi không phải lúc nào cũng có 4GB + RAM và một đĩa lớn.
Ed S.

3
Thực thi 893kb khác xa so với "4GB + RAM và một đĩa lớn", và như những người khác đã chỉ ra, điều này bao gồm thời gian chạy liên kết tĩnh, có thể dễ dàng bị loại trừ.
Nick Johnson

22
Vâng, nó còn rất xa, và tôi biết câu trả lời, nhưng đó là một câu hỏi hợp lệ và thái độ "ai quan tâm đến việc tiêu thụ bộ nhớ" có xu hướng đến từ việc làm việc trên các hệ thống mà điều đó không quan trọng. Bạn dường như nghĩ rằng sự thiếu hiểu biết không sao cả và tốt hơn hết là đừng đặt câu hỏi. Và tôi sẽ nói lại; đôi khi ~ 1MB là rất nhiều, rõ ràng là bạn không làm việc trong thế giới đó. CHỈNH SỬA - Bạn làm việc tại Google! lol. Tôi vẫn không hiểu thái độ 'ai quan tâm'.
Ed S.

12
Rõ ràng là trong bộ phận Java;)
Matt Joiner

Câu trả lời:


29

Có một vấn đề là tệp lớn hơn? Tôi không biết Go nhưng tôi sẽ giả định rằng nó liên kết tĩnh một số lib thời gian chạy, điều này không đúng với chương trình C. Nhưng có lẽ điều đó không có gì đáng lo ngại ngay khi chương trình của bạn lớn hơn.

Như được mô tả ở đây , liên kết tĩnh trong thời gian chạy Go là mặc định. Trang đó cũng cho bạn biết cách thiết lập liên kết động.


2
Có một vấn đề mở , với cột mốc được đặt cho Go1.5
Joe

80

Nếu bạn đang sử dụng hệ thống dựa trên Unix (ví dụ: Linux hoặc Mac OSX), bạn có thể thử xóa thông tin gỡ lỗi có trong tệp thực thi bằng cách xây dựng nó với cờ -w:

go build -ldflags "-w" prog.go

Kích thước tệp được giảm đáng kể.

Để biết thêm chi tiết, hãy truy cập trang của GDB: http://golang.org/doc/gdb


3
Điều tương tự cũng được thực hiện với striplệnh: go build prog.go; strip progsau đó chúng tôi đã được gọi là sọc thực thi: ELF 64-bit LSB thực thi, x86-64, phiên bản 1 (SYSV), liên kết động (sử dụng chia sẻ libs), tước
Alexander I.Grafov

1
Tính năng này hoạt động trên Windows và cung cấp tệp nhị phân nhỏ hơn một chút so với tệp được tạo bằng cách xây dựng thông thường và tách.
Isxek

9
@Axel: Rõ ràng là không hỗ trợ việc loại bỏ các tệp nhị phân đi và có thể gây ra lỗi nghiêm trọng trong một số trường hợp. Xem tại đây .
Flimzy

9
Hiện tại -w, danh sách tài liệu thay vì -s. Không chắc chắn về sự khác biệt là, nhưng bạn có thể muốn cập nhật câu trả lời của bạn :-)
Dynom

1
@Dynom: -w có vẻ giảm kích thước một số nhưng mức giảm không tốt bằng -s. -s dường như ngụ ý -w: golang.org/cmd/link
arkod

42

Câu trả lời năm 2016:

1. Sử dụng Go 1.7

2. Biên dịch với go build -ldflags "-s -w"

➜ ls -lh hello
-rwxr-xr-x 1 oneofone oneofone 976K May 26 20:49 hello*

3. Sau đó sử dụng upx, goupxkhông còn cần thiết kể từ 1.6.

➜ ls -lh hello
-rwxr-xr-x 1 oneofone oneofone 367K May 26 20:49 hello*

3
Tôi muốn nói thêm ở đây rằng những lưu ý thông thường về UPX có thể áp dụng ở đây. Xem phần này để biết thêm thông tin: stackoverflow.com/questions/353634/… (phần lớn áp dụng cho hệ điều hành không phải Windows).
Jonas

23

Các tệp nhị phân Go lớn vì chúng được liên kết tĩnh (ngoại trừ các liên kết thư viện sử dụng cgo). Hãy thử liên kết tĩnh một chương trình C và bạn sẽ thấy nó phát triển với kích thước tương đương.

Nếu đây thực sự là một vấn đề đối với bạn (điều mà tôi rất khó tin), bạn có thể biên dịch bằng gccgo và liên kết động.


19
Ngoài ra, đó là một động thái tốt cho một ngôn ngữ chưa được chấp nhận phổ biến. Không ai quan tâm đến việc làm lộn xộn hệ thống của họ với hệ thống go libs.
Matt Joiner

4
Những người sáng tạo Go rõ ràng thích liên kết động: malware.cat-v.org/software/dynamic-linking . Google liên kết tĩnh tất cả C và C ++ của nó: reddit.com/r/golang/comments/tqudb/…
Graham King

13
Tôi nghĩ rằng bài báo được liên kết nói ngược lại - Người sáng tạo Go rõ ràng thích liên kết tĩnh.
Petar Donchev

17

Bạn sẽ nhận được goupx , nó sẽ "sửa chữa" các tệp thực thi Golang ELF để làm việc upx. Tôi đã có khoảng 78% kích thước tệp thu nhỏ trong một số trường hợp ~16MB >> ~3MB.

Tỷ lệ nén thường có xu hướng 25%, vì vậy nó đáng để thử:

$ go get github.com/pwaller/goupx
$ go build -o filename
$ goupx filename

>>

2014/12/25 10:10:54 File fixed!

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
  16271132 ->   3647116   22.41%  linux/ElfAMD   filename                            

Packed 1 file.

EXTRA: -scờ (dải) có thể thu nhỏ tệp bin hơn nữagoupx -s filename


1
Điều này là tuyệt vời, cảm ơn! Tôi đã thiết lập GOARCH = 386 và chỉ sử dụng upx bình thường trong một thời gian.
codekoala

9
Điều này không còn cần thiết kể từ Go 1.6, bạn có thể sử dụng upx bình thường.
OneOfOne

12

tạo một tệp có tên main.go, hãy thử với chương trình hello world đơn giản.

package main

import "fmt"

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

Tôi sử dụng go phiên bản 1.9.1

$ go version
 go version go1.9.1 linux/amd64

Biên dịch với go buildlệnh chuẩn .

$ go build main.go
$ ls -lh
-rwxr-xr-x-x 1 nil nil 1.8M Oct 27 07:47 main

Hãy biên dịch lại một lần nữa với go buildnhưng với ldflagsnhư đã đề xuất ở trên,

$ go build -ldflags "-s -w" main.go
$ ls -lh
-rwxr-xr-x-x 1 nil nil 1.2M Oct 27 08:15 main

Kích thước tệp giảm 30%.

Bây giờ, hãy sử dụng gccgo,

$ go version
 go version go1.8.1 gccgo (GCC) 7.2.0 linux/amd64

Tòa nhà đi cùng gccgo,

$ go build main.go
$ ls -lh
-rwxr-xr-x 1 nil nil 34K Oct 27 12:18 main

Kích thước nhị phân giảm gần như 100%. Chúng ta hãy một lần nữa cố gắng xây dựng của chúng tôi main.govới gccgonhưng với xây dựng cờ,

$ go build -gccgoflags "-s -w" main.go
-rwxr-xr-x 1 nil nil 23K Oct 27 13:02 main

Cảnh báo: Vì các gccgotệp nhị phân được liên kết động. Nếu bạn có một tệp nhị phân có kích thước rất lớn, tệp nhị phân của bạn khi được biên dịch bằng gccgo sẽ không bị giảm 100%, nhưng nó sẽ bị giảm kích thước đáng kể.

So với gc, gccgo biên dịch mã chậm hơn nhưng hỗ trợ tối ưu hóa mạnh mẽ hơn, do đó, chương trình ràng buộc CPU do gccgo xây dựng thường sẽ chạy nhanh hơn. Tất cả các tối ưu hóa được triển khai trong GCC qua nhiều năm đều có sẵn, bao gồm tối ưu hóa nội tuyến, tối ưu hóa vòng lặp, vectơ hóa, lập lịch hướng dẫn, v.v. Mặc dù không phải lúc nào nó cũng tạo ra mã tốt hơn, nhưng trong một số trường hợp, các chương trình được biên dịch bằng gccgo có thể chạy nhanh hơn 30%.

Các bản phát hành GCC 7 dự kiến ​​sẽ bao gồm việc triển khai hoàn chỉnh các thư viện người dùng Go 1.8. Giống như các bản phát hành trước đó, thời gian chạy Go 1.8 không được hợp nhất hoàn toàn, nhưng điều đó sẽ không hiển thị với các chương trình Go.

Ưu điểm:

  1. Giảm kích thước
  2. Tối ưu hóa.

Nhược điểm

  1. Chậm
  2. Không thể sử dụng phiên bản mới nhất của go.

Bạn có thể xem ở đâyở đây .


Cảm ơn bạn. Tôi có một câu hỏi, khi tôi đọc, gogcckhông hỗ trợ bộ xử lý ARM, có đúng không? Và, bạn có thể đề xuất một công cụ làm giảm kích thước tệp xây dựng Golang không?
M. Rostami

Làm thế nào bạn biên dịch với gccgo?
Vitaly Zdanevich

10

Ví dụ hello-world nhỏ gọn hơn:

package main

func main() {
  print("Hello world!")
}

Chúng tôi bị bỏ qua fmtgói lớn và số nhị phân bị giảm đáng kể:

  $ go build hello.go
  $ ls -lh hello
  ... 259K ... hello2
  $ strip hello
  $ ls -lh hello
  ... 162K ... hello2

Không quá nhỏ gọn như C, nhưng mặc dù được đo bằng K chứ không phải M :) Ok, nó không phải là cách chung chung chỉ cho thấy một số cách để tối ưu hóa kích thước: sử dụng dải và cố gắng sử dụng các gói tối thiểu. Dù sao thì Go không phải là ngôn ngữ để tạo các tệp nhị phân có kích thước nhỏ.


11
Mã này sử dụng hàm print () nội trang và phương pháp này không được khuyến khích. Và câu hỏi không phải là làm thế nào để giảm kích thước mã của chương trình mẫu được cung cấp, mà là một cách tổng quát hơn.
wldsvc

@wldsvc Tôi đồng ý với bạn lưu ý. Mặc dù không hiểu tại sao print () là cách xấu cho đầu ra hiển thị? Đối với các tập lệnh đơn giản hoặc cho đầu ra gỡ lỗi, nó là đủ tốt.
Alexander I.Grafov

3
Xem doc.golang.org/ref/spec#Bootstrapping Các chức năng này được lập thành văn bản để hoàn thiện nhưng không được đảm bảo duy trì bằng ngôn ngữ . Đối với tôi, nó có nghĩa là "không sử dụng chúng" trong bất kỳ tập lệnh nào ngoại trừ mục đích gỡ lỗi một lần.
wldsvc

3

Câu trả lời năm 2018 cho Go 1.11 tiếp theo, như đã tweet bởi Brad Fitzpatrick (giữa tháng 6 năm 2018):

Kể từ vài phút trước, các phần DWARF trong các tệp nhị phân #golang ELF hiện đã được nén, vì vậy các tệp nhị phân ở mẹo hiện nhỏ hơn Go 1.10, ngay cả khi có tất cả các nội dung gỡ lỗi bổ sung ở đầu.

https://pbs.twimg.com/media/DfwkBaqUEAAb8-Q.jpg:large

Cf Golang vấn đề 11799 :

Nén thông tin gỡ lỗi của chúng tôi có thể mang lại chiến thắng kích thước tệp rẻ, đáng kể.

Xem thêm trong cam kết 594eae5

cmd / link: nén các phần DWARF trong tệp nhị phân ELF

Phần khó nhất của điều này là mã bố cục nhị phân (blk, elfshbits và nhiều thứ khác) giả định sự bù trừ không đổi giữa các vị trí tệp của biểu tượng và phần và địa chỉ ảo của chúng.

Tất nhiên, việc nén sẽ phá vỡ sự bù đắp không đổi này.
Nhưng chúng ta cần gán địa chỉ ảo cho mọi thứ trước khi nén để giải quyết các vị trí trước khi nén.

Do đó, quá trình nén cần phải tính toán lại "địa chỉ" của các phần và ký hiệu DWARF dựa trên kích thước nén của chúng.
May mắn thay, chúng nằm ở cuối tệp, vì vậy điều này không làm ảnh hưởng đến bất kỳ phần hoặc biểu tượng nào khác. (Và tất nhiên, có một lượng mã đáng ngạc nhiên giả định phân đoạn DWARF đến cuối cùng, vậy còn một chỗ nữa?)

name        old exe-bytes   new exe-bytes   delta
HelloSize      1.60MB ± 0%     1.05MB ± 0%  -34.39%  (p=0.000 n=30+30)
CmdGoSize      16.5MB ± 0%     11.3MB ± 0%  -31.76%  (p=0.000 n=30+30)
[Geo mean]     5.14MB          3.44MB       -33.08%

Rob Pike đề cập :

Điều đó chỉ hữu ích trên các máy sử dụng ELF.
Binaries vẫn còn quá lớn và đang phát triển.

Brad trả lời:

Ít nhất nó là một cái gì đó. Đã sắp tồi tệ hơn nhiều.
Cầm máu cho một lần giải phóng.

Lý do : Gỡ lỗi thông tin, nhưng cũng đăng ký bản đồ cho GC để cho phép bất kỳ hướng dẫn nào là điểm lưu.


1

Theo mặc định, tệp nhị phân chứa bộ thu gom rác, hệ thống lập lịch trình quản lý các quy trình hoạt động và tất cả các thư viện bạn nhập.

Kết quả là kích thước tối thiểu khoảng 1 Mb.


1

Từ Go 1.8, bạn cũng có thể sử dụng hệ thống plugin mới để chia tệp nhị phân của mình thành một thứ giống với thư viện được chia sẻ. Đối với bản phát hành này, nó chỉ hoạt động trên Linux, nhưng các nền tảng khác có thể sẽ được hỗ trợ trong tương lai.

https://tip.golang.org/pkg/plugin/



1

Theo mặc định, gcc liên kết động và đi - tĩnh.

Nhưng nếu bạn liên kết mã C tĩnh, bạn có thể nhận được một mã nhị phân với kích thước lớn hơn.

Trong trường hợp của tôi:

  • đi x64 (1.10.3) - nhị phân được tạo với kích thước 1214208 byte
  • gcc x64 (6.2.0) - nhị phân được tạo với kích thước 1421312 byte

cả hai tệp nhị phân đều được liên kết tĩnh và không có debug_info.

go build -ldflags="-s -w" -o test-go test.go
gcc -static -s -o test-c test.c
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.