Noteworthy changes in Go 1.20

Permalink to this article – https://ift.tt/NqUDC61

On February 1, 2023, US time, Robert Griesemer , the only father of the Go language who has not yet retired, officially released the Go 1.20 version on the Go official blog on behalf of the Go core development team. As Russ Cox said at the 2022 GopherCon conference : Go2 will never come, Go 1.xy will continue indefinitely !

Note: It seems that emerging programming languages ​​like to stay on 1.xy for an infinite continuation, such as Rust that has evolved to version 1.67 ^_^.

After “Go, 13th Anniversary” , the new features of Go 1.20 before the development of the main body freeze (2022.11), I wrote an article ” Preview of New Features of Go 1.20 “. I went over it briefly with everyone, but Go 1.20 was not officially released at that time, and the preview is definitely not comprehensive enough, and some specific points may be different from the official version! Now that the Go 1.20 version is officially released, its Release Notes are also complete. In this article, I will systematically talk about the changes that are worthy of attention in the Go 1.20 version. For the features that have been introduced in detail in the article Qianzhan, I will not repeat them here. You can refer to the content in the article Qianzhan. As for some other features, or the features that are not mentioned much in the forward-looking article, I will focus on them here.

According to the usual practice, we still first look at the changes at the Go grammar level, which may be the most concerned change point for most Gophers.

1. Grammatical changes

Go adheres to the concept of “simplicity from the road”, and has always been “not keeping pace with the times” with regard to Go’s grammatical features. Since Go 1.18 drastically added generic features , Go syntax features have returned to the previous rhythm of “three years old, three years old, and three years of repairing”. The same is true for Go 1.20! The Release Notes said that Go 1.20 contains four changes in the language, but after reading the content of the changes, I think there is only one real change, and the others are tinkering.

1. Conversion from slice to array

The only feature that is regarded as a real grammatical change is the support for type conversion from slice type to array type (or pointer to array type). OK:

 // https://github.com/bigwhite/experiments/blob/master/go1.20-examples/lang/slice2arr.go func slice2arrOK() { var sl = []int{1, 2, 3, 4, 5, 6, 7} var arr = [7]int(sl) var parr = (*[7]int)(sl) fmt.Println(sl) // [1 2 3 4 5 6 7] fmt.Println(arr) // [1 2 3 4 5 6 7] sl[0] = 11 fmt.Println(arr) // [1 2 3 4 5 6 7] fmt.Println(parr) // &[11 2 3 4 5 6 7] } func slice2arrPanic() { var sl = []int{1, 2, 3, 4, 5, 6, 7} fmt.Println(sl) var arr = [8]int(sl) // panic: runtime error: cannot convert slice with length 7 to array or pointer to array with leng th 8 fmt.Println(arr) // &[11 2 3 4 5 6 7] } func main() { slice2arrOK() slice2arrPanic() }

There are two points to pay attention to:

  • If the slice is converted to a pointer of array type, then the pointer will point to the underlying array of the slice, just like the parr variable of slice2arrOK in the above example;
  • The length of the converted array type cannot be greater than the length of the original slice (note that it is the length rather than the capacity of the slice), otherwise a panic will be thrown at runtime.

2. Other tinkering

  • comparable “relaxes” restrictions on generic arguments

The following code will fail to compile before Go 1.20, such as Go 1.19:

 // https://github.com/bigwhite/experiments/blob/master/go1.20-examples/lang/comparable.go func doSth[T comparable](t T) { } func main() { n := 2 var i interface{} = n // 编译错误:interface{} does not implement comparable doSth(i) }

Previously, generic parameters under comparable constraints needed to support strictly comparable (strictly comparable) types as generic arguments. Which types are strictly comparable? The grammar specification of Go 1.20 further clarified: If a type is comparable and is not an interface type or a type composed of interface types, then this type is a strictly comparable type , including:

 - 布尔型、数值类型、字符串类型、指针类型和channel是严格可比较的。 - 如果结构体类型的所有字段的类型都是严格可比较的,那么该结构体类型就是严格可比较的。 - 如果数组元素的类型是严格可比较的,那么该数组类型就是严格可比较的。 - 如果类型形参的类型集合中的所有类型都是严格可比较的,那么该类型形参就是严格可比较的。

We see: the exception is the interface type. Interface types are not “strictly comparable”, but interface types not used as type parameters are comparable (comparable), if the dynamic type of the two interface types is the same and the value is equal, then the two interface types If they are equal, or if the values ​​of both interface types are nil, they are also equal, otherwise they are not equal.

Go version 1.19 and before, interface types that are non-strictly comparable types cannot be used as type parameters of comparable constraint type parameters, just like the sample code in comparable.go above, but starting from Go version 1.20, this requirement has been removed For prevention and control, the interface type is allowed to be assigned as a type parameter to a type parameter of a comparable constraint! But before doing this, you have to make it clear that if two interface types have the same underlying type and are incomparable types (such as slices) like the following, then the code will panic at runtime:

 // https://github.com/bigwhite/experiments/blob/master/go1.20-examples/lang/comparable1.go func doSth[T comparable](t1, t2 T) { if t1 != t2 { println("unequal") return } println("equal") } func main() { n1 := []byte{2} n2 := []byte{3} var i interface{} = n1 var j interface{} = n2 doSth(i, j) // panic: runtime error: comparing uncomparable type []uint8 }

The Go 1.20 language specification took this opportunity to further clarify the specifications for the comparison between structures and arrays: For structure types, Go will compare the fields one by one in the order in which the fields of the structure are declared, until the first one that is not equal fields. If there is no unequal field, the fields of the two structures are equal; for array types, Go will compare elements one by one in the order of the array elements until the first unequal element is encountered. Two arrays are equal if there are no unequal elements.

  • The unsafe package continues to add “syntactic sugar”

After the Go 1.17 version added the Slice function in the unsafe package, the Go 1.20 version added three syntactic sugar functions: SliceData, String and StringData:

 // $GOROOT/src/unsafe/unsafe.go func SliceData(slice []ArbitraryType) *ArbitraryType func String(ptr *byte, len IntegerType) string func StringData(str string) *byte

It is worth noting that due to the immutability of string, the content pointed to by the parameter ptr of the String function and the content pointed to by the pointer returned by StringData are not allowed to be modified after calling String and StringData, but what is the actual situation?

 // https://github.com/bigwhite/experiments/blob/master/go1.20-examples/lang/unsafe.go func main() { var arr = [6]byte{'h', 'e', 'l', 'l', 'o', '!'} s := unsafe.String(&arr[0], 6) fmt.Println(s) // hello! arr[0] = 'j' fmt.Println(s) // jello! s1 := "golang" fmt.Println(s1) // golang b := unsafe.StringData(s1) *b = 'h' // fatal error: fault, unexpected fault address 0x10a67e5 fmt.Println(s1) }

We see: after the unsafe.String function is called, if we modify the content pointed to by the pointer passed in, the change will affect the subsequent returned string content! However, once the content pointed to by the pointer returned by StringData is modified, it will cause a segment fault at runtime, and the program will crash!

2. Tool chain

1. Go installation package “slimming”

Over the years, the “physique” of the installation package of the Go release version has become stronger and stronger, and the compressed package is often more than 100 MB. Take the go1.xy.linux-amd64.tar.gz on the go.dev/dl page as an example. Let’s take a look at the “Physical” trend from Go 1.15 to Go 1.19:

 Go 1.15 - 116MB Go 1.16 - 123MB Go 1.17 - 129MB Go 1.18 - 135MB Go 1.19 - 142MB

If this trend is followed, Go 1.20 is bound to reach more than 150MB. But the Go team has found a “slimming” method, that is: starting from Go 1.20, the installation package of the release version no longer provides precompiled . The size is 95MB ! Compared with Go 1.19, the installation package of Go 1.20 is “thinner” by one-third. This performance is more obvious after the installation package is decompressed:

 ➜ /Users/tonybai/.bin/go1.19 git:(master) ✗ $du -sh 495M . ➜ /Users/tonybai/.bin/go1.20 git:(master) ✗ $du -sh 265M .

We see: Go 1.20 uses a little more than half the disk space of Go 1.19. Moreover, in Go 1.20 version, the source code under GOROOT will be cached in the local cache after building like other user packages. Also, go install will not install .a files for packages under GOROOT.

2. Compiler

1) PGO (profile-guided optimization)

One of the biggest changes in the Go 1.20 compiler is the introduction of the preview version of the PGO optimization technology. This is also a brief introduction to the PGO technology in the preview article. To put it bluntly, PGO technology is based on the original compiler optimization technology, and then conducts another round of optimization for the hot key path of the program in the production environment, and for the hot code execution path, the compiler will release some restrictions, such as Go The default value of the complexity upper limit for determining whether to perform inline optimization on a function is 80 , but for the key hot path indicated by PGO, even if the function complexity exceeds 80, it may be optimized inline.

Previously, Polar Signals, a developer of continuous performance analysis tools, published an article “Exploring Go’s Profile-Guided Optimizations” , which specifically discussed the possible optimization effects of PGO technology. The article used the test examples that come with the Go project. Let’s reproduce it based on this example.

The example we use is in the \$GOROOT/src/cmd/compile/internal/test/testdata/pgo/inline path of the Go 1.20 source code/installation package:

 $ls -l total 3156 -rw-r--r-- 1 tonybai tonybai 1698 Jan 31 05:46 inline_hot.go -rw-r--r-- 1 tonybai tonybai 843 Jan 31 05:46 inline_hot_test.go

We first execute the test in the inline directory, and generate the executable file for the test and the corresponding cpu profile file for subsequent PGO optimization:

 $go test -o inline_hot.test -bench=. -cpuprofile inline_hot.pprof goos: linux goarch: amd64 pkg: cmd/compile/internal/test/testdata/pgo/inline cpu: Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz BenchmarkA-8 1348 870005 ns/op PASS ok cmd/compile/internal/test/testdata/pgo/inline 1.413s

Next, let’s compare the difference between Go compiler inline optimization without PGO and PGO optimization:

 $diff <(go test -run=none -tags='' -timeout=9m0s -gcflags="-m -m" 2>&1 | grep "can inline") <(go test -run=none -tags='' -timeout=9m0s -gcflags="-m -m -pgoprofile inline_hot.pprof" 2>&1 | grep "can inline") 4a5,6 > ./inline_hot.go:53:6: can inline (*BS).NS with cost 106 as: method(*BS) func(uint) (uint, bool) { x := int(i >> lWSize); if x >= len(bs) { return 0, false }; w := bs[x]; w = w >> (i & (wSize - 1)); if w != 0 { return i + T(w), true }; x = x + 1; for loop; return 0, false } > ./inline_hot.go:74:6: can inline A with cost 312 as: func() { s := N(100000); for loop; for loop }

In the above diff command, -run=none -tags=”” -gcflags=”-m -m” is passed to the Go test command to only compile the source file without executing any tests.

We can see that compared with the result without PGO optimization, the result after PGO optimization has two more inline functions. These two functions that can be inlined, one has a complexity overhead of 106 and the other is 312, both of which exceed the The default is 80, but can still be inlined.

Let’s take a look at the actual optimization effect of PGO. We divide it into benchmarks performed 100 times without PGO optimization and with PGO optimization, and then use the benchmarkstat tool to compare the results twice:

 $go test -o inline_hot.test -bench=. -cpuprofile inline_hot.pprof -count=100 > without_pgo.txt $go test -o inline_hot.test -bench=. -gcflags="-pgoprofile inline_hot.pprof" -count=100 > with_pgo.txt $benchstat without_pgo.txt with_pgo.txt goos: linux goarch: amd64 pkg: cmd/compile/internal/test/testdata/pgo/inline cpu: Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz │ without_pgo.txt │ with_pgo.txt │ │ sec/op │ sec/op vs base │ A-8 874.7µ ± 0% 872.6µ ± 0% -0.24% (p=0.024 n=100)

Note: The installation method of benchstat: \$go install golang.org/x/perf/cmd/benchstat@latest

We can see that on my machine (ubuntu 20.04 linux kernel 5.4.0-132), the optimization effect of PGO for this test is not obvious (only 0.24% improvement), and the improvement in the original Polar Signals is not large , only 1.05%.

Go official Release Notes mentioned that the benchmark improvement effect is 3%~4%. At the same time, the official also mentioned that this is only the initial technical preview version of PGO, and the investment in PGO optimization will be strengthened in the future until most programs are significantly optimized. Effect. I personally feel that PGO is still in its early stages and is not recommended for use in production.

Go official has also added a ref page for PGO , please focus on the FAQ, you will gain more!

2) Build speed

After the implementation of Go 1.18 generics, the compilation speed of the Go compiler dropped back (by 15%), and the compilation speed of Go 1.19 did not increase. Although the compilation speed can still “spike” competitors, but for Go, which is famous for its fast compilation speed, this problem must be fixed. Go 1.20 has achieved this, allowing the compilation speed of the Go compiler to return to the level of Go 1.17! Compared with Go 1.19, it has increased by about 10%.

I used the library github.com/reviewdog/reviewdog to test it, and used go 1.17.1, go 1.18.6, go 1.19.1 and Go 1.20 to build this module with go build -a (download all dependent packages before local, excluding the impact of the go get link), the results are as follows:

 go 1.20: $time go build -a github.com/reviewdog/reviewdog/cmd/reviewdog go build -a github.com/reviewdog/reviewdog/cmd/reviewdog 48.01s user 7.96s system 536% cpu 10.433 total go 1.19.1: $time go build -a github.com/reviewdog/reviewdog/cmd/reviewdog go build -a github.com/reviewdog/reviewdog/cmd/reviewdog 54.40s user 10.20s system 506% cpu 12.757 total go 1.18.6: $time go build -a github.com/reviewdog/reviewdog/cmd/reviewdog go build -a github.com/reviewdog/reviewdog/cmd/reviewdog 53.78s user 9.85s system 545% cpu 11.654 total go 1.17.1: $time go build -a github.com/reviewdog/reviewdog/cmd/reviewdog go build -a github.com/reviewdog/reviewdog/cmd/reviewdog 50.30s user 9.76s system 580% cpu 10.338 total

Although not very precise, it generally reflects the level of compilation speed of each version and the improvement of Go 1.20 compared to Go 1.18 and Go 1.19. We see that Go 1.20 is on the same level as Go 1.17, and even surpasses Go 1.17 (but maybe only in my case).

3) Allow type declarations in generic functions/methods

Before Go 1.20, the following code could not be compiled by the Go compiler:

 // https://github.com/bigwhite/experiments/blob/master/go1.20-examples/tools/compiler/local_type_decl.go package main func F[T1 any]() { type x struct{} // 编译错误:type declarations inside generic functions are not currently supported type y = x // 编译错误:type declarations inside generic functions are not currently supported } func main() { F[int]() }

Go 1.20 has improved the implementation of the language front end, and implemented support for type declarations (including defining type aliases) in generic functions/methods through unified IR.

At the same time, Go 1.20 also clarified in the spec which type parameter lists declared recursively are illegal :

 type T1[P T1[P]] … // 不合法: 形参列表中作为约束的T1引用了自己type T2[P interface{ T2[int] }] … // 不合法: 形参列表中作为约束的T2引用了自己type T3[P interface{ m(T3[int])}] … // 不合法: 形参列表中作为约束的T3引用了自己type T4[P T5[P]] … // 不合法: 形参列表中,T4引用了T5 并且type T5[P T4[P]] … // T5引用了T4 type T6[P int] struct{ f *T6[P] } // 正确: 虽然引用了T6,但这个引用发生在结构体定义中而不是形参列表中

4) Version selection of the Go compiler for building the bootstrap source code

Go has implemented bootstrapping since Go 1.5, that is, using Go to implement Go, so who will compile the Go project after bootstrapping? The original version of the Go compiler for Go 1.5 is Go 1.4.

Previously when building the Go distribution from source, when GOROOT_BOOTSTRAP was not set, the build script would default to using Go 1.4, but would use a later version of the Go compiler if one existed.

Go 1.18 and Go 1.19 will first look for go 1.17, and if not, use go 1.4.

Go 1.20 looks for Go 1.17.13, the last version of the current Go 1.17, and uses Go 1.4 if not available.

In the future, the Go core team plans to upgrade the version of the Go compiler that builds the bootstrap source code once a year, for example: Go 1.22 version will use the Go 1.20 version of the compiler.

5) cgo

The Go command now disables cgo by default on systems without a C toolchain. More specifically, CGO_ENABLED is set to 0 by default when the CGO_ENABLED environment variable is not set, the CC environment variable is not set, and the default C compiler (usually clang or gcc) is not found in the PATH environment variable.

3. Other tools

1) Supports collection of code coverage executed by applications

In the preview article, I mentioned that Go 1.20 extends the support for code coverage to the overall level of the application, not just unit test. Here is an example to see how to collect the execution coverage of the application code. Let’s take the code statistics tool gitlab.com/esr/loccount as an example, first modify the Makefile, add the -cover option after go build, then compile loccount, and perform code statistics on itself:

 // /home/tonybai/go/src/gitlab.com/loccount $make $mkdir mycovdata $GOCOVERDIR=./mycovdata loccount . all SLOC=4279 (100.00%) LLOC=1213 in 110 files Go SLOC=1724 (40.29%) LLOC=835 in 3 files asciidoc SLOC=752 (17.57%) LLOC=0 in 5 files C SLOC=278 (6.50%) LLOC=8 in 2 files Python SLOC=156 (3.65%) LLOC=0 in 2 files ... ...

Before executing loccount above, we created a mycovdata directory and set the value of GOCOVERDIR to the path of the mycovdata directory. In such a context, after executing loccount, some coverage statistics data files will be generated in the mycovdata directory:

 $ls mycovdata covcounters.4ec45ce64f965e77563ecf011e110d4f.926594.1675678144659536943 covmeta.4ec45ce64f965e77563ecf011e110d4f

How to check the execution coverage of loccount? We use go tool covdata to view:

 $go tool covdata percent -i=mycovdata loccount coverage: 69.6% of statements

Of course, the covdata subcommand also supports some other functions, you can check the manual mining by yourself.

2) vet

In version 1.20 of Go, the vet subcommand of the go tool chain adds two very useful checks:

  • The loopclosure detection strategy has been enhanced

For details, please refer to https://ift.tt/AkfYqmy code

  • Add check for time format of 2006-02-01

Note that when we use time.Format or Parse, the most commonly used format is 2006-01-02, which is the time format of the ISO 8601 standard, but 2006-02-01 always appears in some codes, which is very easy to cause errors. In this version, go vet will check for this situation.

3. Runtime and standard library

1. Runtime

The adjustment of Go 1.20 runtime is not big, only the internal data structure of GC is fine-tuned. This adjustment can reduce memory overhead by up to 2% and improve CPU performance.

2. Standard library

The standard library is definitely the part that changes the most. The article Qianzhan also introduced the following changes in detail, so I won’t go into details here. You can read that article carefully:

  • Support wrap multiple errors
  • The time package adds three layout format constants DateTime, DateOnly and TimeOnly
  • Added arena package

There are many changes in the standard library, and I cannot list them all here. I will add some that I think are important. For other changes, you can go to Go 1.20 Release Notes to see:

1) arena package

The preview article has briefly described the arena package, and the exploration of the use of the arena package and the best applicable occasions is still in progress. The official blog post “Go 1.20 arenas practice: arena vs. traditional memory management” of the famous continuous performance analysis tool pyroscope gives some good suggestions for the use of arena experimental features, such as:

  • Use arenas only in critical code paths, don’t use them everywhere
  • Profile your code before and after using arenas to ensure that you add arenas where they provide the greatest benefit.
  • Keep an eye on the lifecycle of objects created on the arena. Make sure you don’t leak them to other components in your program, as objects there may outlive the arena’s lifetime.
  • Use defer a.Free() to make sure you don’t forget to free the memory.
  • If you want to use the object after the arena is freed, use arena.Clone() to clone it back into the heap.

The developers of pyroscope believe that arena is a powerful tool, and they also support the retention of the arena feature in the standard library, but they also recommend that arena be included in the ranks of “not recommended” packages like reflect, unsafe, cgo, etc. I also agree with this point. I am also thinking about how to improve the performance of our product’s protocol parser based on arena, and I will share the practice process after I have results.

2) Add crypto/ecdh package

Filippo Valsorda , the main maintainer of the cryptography package (crypto), left Google and became a full-time open source project maintainer . This seems to give it more energy and motivation to better plan, design and implement the crypto package. The crypto/ecdh package was added to the Go standard library at his suggestion .

Compared with the crypto/elliptic and other packages that existed before the standard library, the API of the crypto/ecdh package is more advanced. Go officially recommends using the high-level API of ecdh, so that you no longer have to struggle with low-level cryptographic functions in the future.

3) HTTP Response Controller

In the past, the timeout of the HTTP handler was globally specified by the http server: including ReadTimeout and WriteTimeout. But sometimes it would be useful to support these timeouts (and possibly other options) within a request scope. Damien Neil created this proposal to add ResponseController , here is an example of using ResponseController in HandlerFunc:

 http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) { ctl := http.NewResponseController(w, r) ctl.SetWriteDeadline(time.Now().Add(1 * time.Minute)) // 仅为这个请求设置deadline fmt.Fprintln(w, "Hello, world.") // 这个写入的timeout为1-minute })

4) The context package adds the WithCancelCause function

The context package adds a new WithCancelCause function, which is different from WithCancel. Through the Context returned by WithCancelCause, we can get the reason for cancel, such as the following example:

 // https://github.com/bigwhite/experiments/blob/master/go1.20-examples/library/context.go func main() { myError := fmt.Errorf("%s", "myError") ctx, cancel := context.WithCancelCause(context.Background()) cancel(myError) fmt.Println(ctx.Err()) // context.Canceled fmt.Println(context.Cause(ctx)) // myError }

We see that through context.Cause, we can get the error reason passed in when the Context is cancelled.

4. Portability

Go’s support for new cpu architectures and OSs has always been at the forefront. Go 1.20 also adds experimental support for freebsd on risc-v, and its environment variables are GOOS=freebsd, GOARCH=riscv64. But Go 1.20 will also be the last Go version to support the following platforms:

  • Windows 7, 8, Server 2008 and Server 2012
  • MacOS 10.13 High Sierra and 10.14 (my mac os with 10.14 installed will not be supported in go 1.21 again^_^)

Recently, the Go team has a new proposal: support for WASI (GOOS=wasi GOARCH=wasm) . What is WASI? The only way out of the browser! Once a WASI-compliant WASM program is generated, the program can run on any WASI-capable or compatible runtime. Not surprisingly, this proposal will land in Go 1.21 or Go 1.22.

The sample code for this article can be downloaded here .


“Gopher Tribe” knowledge planet aims to create a boutique Go learning and advanced community! High-quality first release of Go technical articles, “three days” first reading right, analysis of the development status of Go language twice a year, fresh Gopher daily 1 hour in advance every day, online courses, technical columns, book content preview, must answer within six hours Guaranteed to meet all your needs about the Go language ecology! In 2023, the Gopher tribe will further focus on how to write elegant, authentic, readable, and testable Go code, pay attention to code quality and deeply understand Go core technology, and continue to strengthen interaction with star friends. Everyone is welcome to join!

img{512x368}

img{512x368}

img{512x368}

img{512x368}

The well-known cloud hosting service provider DigitalOcean released the latest hosting plan. The entry-level Droplet configuration is upgraded to: 1 core CPU, 1G memory, 25G high-speed SSD, and the price is 5$/month. Friends who need to use DigitalOcean can open this link address : https://ift.tt/6WBGXa5 to start your DO host road.

Gopher Daily (Gopher Daily News) archive repository – https://ift.tt/hgqzcDa

my contact information:

  • Weibo (temporarily unavailable): https://ift.tt/yX10Hnj
  • Weibo 2: https://ift.tt/oAWl9u4
  • Blog: tonybai.com
  • github: https://ift.tt/G6BNOcd

Business cooperation methods: writing, publishing, training, online courses, partnerships, consulting, advertising cooperation.

© 2023, bigwhite . All rights reserved.

This article is transferred from https://tonybai.com/2023/02/08/some-changes-in-go-1-20/
This site is only for collection, and the copyright belongs to the original author.