Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to make Go binaries smaller by disabling comparisons

2025-02-27 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

In this issue, the editor will bring you about how to make Go binaries smaller by banning comparisons. The article is rich in content and analyzed and described from a professional point of view. I hope you can get something after reading this article.

The conventional wisdom is that the more types declared in a Go program, the larger the resulting binaries. This is intuitive, after all, if you write code that does not manipulate defined types, there is no point in defining a bunch of types. However, part of the linker's job is to detect functions that are not referenced by the program (for example, they are part of a library in which only a subset of the functionality is used) and then remove them from the final compilation output. As the saying goes, "the more types, the larger the binaries," which is true for most Go programs.

I'll delve into the meaning of "equality" in the context of Go programs and why changes like this have a significant impact on the size of Go programs.

Define that two values are equal

The syntax of Go defines the concepts of "assignment" and "equality". Assignment is the act of assigning a value to an identifier. Not all declared identifiers can be assigned, such as constants and functions. Equality is the act of comparing two identifiers by checking whether the contents of identifiers are equal.

As a strongly typed language, the concept of "identical" is implanted into the type of identifier from the root. The two identifiers can be the same only if they are of the same type. In addition, the type of the value defines how to compare the two values of the type.

For example, integers are compared by arithmetic. For pointer types, equality means whether they point to the same address. Reference types such as mappings and channels are similar to pointers, and if they point to the same address, they are considered to be the same.

The above are examples of bit-by-bit equality, that is, the bit pattern of memory occupied by values is the same, then these values are equal. This is called memcmp, or memory comparison, and equality is defined by comparing the contents of two memory regions.

Remember this idea. I'll talk about it later.

Structural equality

In addition to scalar types such as integers, floats, and pointers, there are compound types: structures. All structures are arranged in memory in the order in the program. So the following statement:

Type S struct {a, b, c, d int64}

Takes up 32 bytes of memory; a takes 8 bytes, b takes 8 bytes, and so on. Go's rule says that if all the fields of the structure are comparable, then the value of the structure is comparable. So if all the fields of the two structures are equal, they are equal.

A: = S {1,2,3,4} b: = S {1,2,3,4} fmt.Println (a = = b) / / output true

The compiler uses memcmp at the bottom to compare 32 bytes of an and 32 bytes of b.

Fill and align

However, oversimplified bitwise comparison strategies in the following scenarios return the wrong results:

Type S struct {a byte b uint64 c int16 d uint32} func main () a: = S {1,2,3,4} b: = S {1,2,3,4} fmt.Println (a = = b) / / output true}

After compiling the code, the result of this comparison expression is still true, but the compiler at the bottom cannot just rely on comparing an and b bit patterns, because the structure is populated.

Go requires that all fields of the structure be aligned. A 2-byte value must start at an even address, a 4-byte value must start at a multiple of 4, and so on. The compiler adds padding based on the field type and the underlying platform to ensure that the fields are aligned. After populating, what the compiler actually sees is 2:

Type S struct {a byte _ [7] byte / / fill b uint64 c int16 _ [2] int16 / / fill d uint32}

The existence of padding ensures the correct alignment of fields, and padding does take up memory space, but the contents of padding bytes are unknown. You might think that padding bytes in Go is 0, but it's not-the contents of padding bytes are undefined. Because they are not defined as certain values, bitwise comparisons return error results due to differences in the 9 padding bytes distributed in the 24 bytes of s.

Go solves this problem by generating so-called equality functions. In this example, the equality function of s only compares the fields in the function, skipping the padding, so that the two values of type s can be compared correctly.

Type algorithm

Well, this is a big setting, which explains why, for each type defined in a Go program, the compiler generates several supporting functions, which the compiler internally calls typed algorithms. If the type is a mapped key, the compiler generates a hash function in addition to the equality function. In order to maintain stability, hash functions also take into account factors such as filling in the same way as equality functions.

It is actually difficult, sometimes not obvious, for the compiler to intuitively determine when to generate these functions, which exceeds your expectations, and it is difficult for the linker to eliminate unused functions, because reflection often causes the linker to become more conservative when cropping types.

Reduce the size of binaries by prohibiting comparison

Now, let's explain the changes to Brad. Add an incomparable field 3 to the type, and the structure becomes incomparable, thus forcing the compiler to no longer generate equality functions and hash functions, avoiding the elimination of those types by the linker, and reducing the size of the generated binaries in practical applications. As an example of this technology, the following program:

Package main import "fmt" func main () {type t struct {/ _ [0] [] byte / / uncomment to prevent comparison of a byte b uint16 c int32 d uint64} var a t fmt.Println (a)}

Compiled with Go 1.14.2 (darwin/amd64), the size was reduced from 2174088 to 2174056, saving 32 bytes. The 32-byte savings alone may seem negligible, but considering that each type and its transitive closure in your program generates equality and hash functions, as well as their dependencies, the size of these functions varies depending on type size and complexity, and disabling them will greatly reduce the size of the final binaries, which is better than using-ldflags= "- s-w" before.

Finally, if you don't want to define types as comparable, you can enforce tricks like this at the source level, which will make the resulting binaries smaller.

This is how to make Go binaries smaller by prohibiting comparisons. If you happen to have similar doubts, please refer to the above analysis to understand. If you want to know more about it, you are welcome to follow the industry information channel.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report