优化1
紧跟 range 关键字的从字符串到字节切片的转换
package main
import (
"strings"
"testing"
)
var gogogo = strings.Repeat("Go", 1024)
func f() {
for range []byte(gogogo) {
}
}
func g() {
bs := []byte(gogogo)
for range bs {
}
}
func TestT1(t *testing.T) {
t.Log(testing.AllocsPerRun(1, f)) // 0
t.Log(testing.AllocsPerRun(1, g)) // 1
}
优化2
映射元素 读取 索引语法中被用做键值的从字节切片到字符串的转换
package main
var name = bytes.Repeat([]byte{'x'}, 33)
var m, s = make(map[string]string, 10), ""
func f() {
s = m[string(name)]
}
func g() {
key := string(name)
s = m[key]
}
func h() {
m[string(name)] = "Golang"
}
func TestT2(t *testing.T) {
t.Log(testing.AllocsPerRun(1, f)) // 0
t.Log(testing.AllocsPerRun(1, g)) // 1
t.Log(testing.AllocsPerRun(1, h)) // 1
}
优化3
一个字符串比较表达式中被用做比较值的从字节切片到字符串的转换
package main
import "testing"
// optimize 3
var x = []byte{1023: 'x'}
var y = []byte{1023: 'y'}
var b bool
func f() {
b = string(x) != string(y)
}
func g() {
sx, sy := string(x), string(y)
b = sx == sy
}
func TestT3(t *testing.T) {
t.Log(testing.AllocsPerRun(1, f)) // 0
t.Log(testing.AllocsPerRun(1, g)) // 2
}
优化4
含 非空字符串常量 的字符串衔接表达式中的从字节切片到字符串的转换
package main
import "testing"
// optimize 4
var x = []byte{1023: 'x'}
var y = []byte{1023: 'y'}
var s string
func f() {
s = ("-" + string(x) + string(y))[1:]
}
func g() {
s = string(x) + string(y)
}
func TestT4(t *testing.T) {
t.Log(testing.AllocsPerRun(1, f)) // 1
t.Log(testing.AllocsPerRun(1, g)) // 3
}
优化5
[]rune(aString) 转换的时间和空间复杂度都是O(n), 但是len([]rune(aString))中的此转换不许开辟内存
package main
import (
"strings"
"testing"
)
// optimize 5
var GoGoGo = strings.Repeat("Go", 100)
func f() {
_ = len([]rune(GoGoGo))
}
func g() {
_ = len([]byte(GoGoGo))
}
func TestT5(t *testing.T) {
t.Log(testing.AllocsPerRun(1, f)) // 0
t.Log(testing.AllocsPerRun(1, g)) // 1
}
优化6
字符串先切表达式只需开辟一次内存, 无论需要衔接多少个字符串
package main
import "testing"
// optimize 6
var x, y, z, w = "Hello ", "world! ", "Let's ", "Go!"
var s string
func f() {
s = x + y + z + w
}
func g() {
s = x + y
s += z
s += w
}
func TestT6(t *testing.T) {
t.Log(testing.AllocsPerRun(1, f)) // 1
t.Log(testing.AllocsPerRun(1, g)) // 3
}
优化7
for i:=range anArrayOrSlice {anArrayOrSlice[i] = zeroElement} 形式将被优化为一个内部的memclr操作
package main
import "testing"
// optimize 7
const N = 1024 * 100
var a [N]int
func clearArray() {
for i := range a {
a[i] = 0
}
}
func clearSlice() {
s := a[:]
for i := range s {
s[i] = 0
}
}
func clearArrayPtr() {
for i := range &a {
a[i] = 0
}
}
func BenchmarkT71(b *testing.B) {
for i := 0; i < b.N; i++ {
clearArray()
}
}
func BenchmarkT72(b *testing.B) {
for i := 0; i < b.N; i++ {
clearSlice()
}
}
func BenchmarkT73(b *testing.B) {
for i := 0; i < b.N; i++ {
clearArrayPtr()
}
}
// Test Result
// goos: windows
// goarch: amd64
// pkg: github.com/gin-gonic/examples/grpc
// BenchmarkT71
// BenchmarkT71-4 53283 22134 ns/op
// BenchmarkT72
// BenchmarkT72-4 53474 22192 ns/op
// BenchmarkT73
// BenchmarkT73-4 27148 41292 ns/op
// PASS
// coverage: [no statements]
//
// Process finished with exit code 0
优化8
for k = range m {delete(m, k)} 形式将被优化为一个内部的map清空操作
优化9
尺寸不大于4个原生字(即int) 并且字段数不超过4个的结构体值被视为是小尺寸值
package main
import "testing"
// optimize 9
type S1 struct{ a int }
type S2 struct{ a, b int }
type S3 struct{ a, b, c int }
type S4 struct{ a, b, c, d int }
type S5 struct{ a, b, c, d, e int }
type S6 struct{ a, b, c, d, e, f int }
var ss1, ss2, ss3, ss4, ss5, ss6 = make([]S1, 1000), make([]S2, 1000), make([]S3, 1000),
make([]S4, 1000), make([]S5, 1000), make([]S6, 1000)
var x1, x2, x3, x4, x5, x6 int
func Benchmark_Range1(b *testing.B) {
for i := 0; i < b.N; i++ {
for _, v := range ss1 {
x1 = v.a
}
}
}
func Benchmark_Range2(b *testing.B) {
for i := 0; i < b.N; i++ {
for _, v := range ss2 {
x2 = v.a
}
}
}
func Benchmark_Range3(b *testing.B) {
for i := 0; i < b.N; i++ {
for _, v := range ss3 {
x3 = v.a
}
}
}
func Benchmark_Range4(b *testing.B) {
for i := 0; i < b.N; i++ {
for _, v := range ss4 {
x4 = v.a
}
}
}
func Benchmark_Range5(b *testing.B) {
for i := 0; i < b.N; i++ {
for _, v := range ss5 {
x5 = v.a
}
}
}
func Benchmark_Range6(b *testing.B) {
for i := 0; i < b.N; i++ {
for _, v := range ss6 {
x6 = v.a
}
}
}
// goos: windows
// goarch: amd64
// pkg: github.com/gin-gonic/examples/grpc
// Benchmark_Range1
// Benchmark_Range1-4 3486524 332 ns/op
// Benchmark_Range2
// Benchmark_Range2-4 2941167 409 ns/op
// Benchmark_Range3
// Benchmark_Range3-4 1824019 658 ns/op
// Benchmark_Range4
// Benchmark_Range4-4 2939377 411 ns/op
// Benchmark_Range5
// Benchmark_Range5-4 352924 3447 ns/op
// Benchmark_Range6
// Benchmark_Range6-4 307684 3710 ns/op
// PASS
// coverage: [no statements]
//
// Process finished with exit code 0
优化10
接口值包裹指针值比包裹其他类型的值要快 , 因为少开辟一次内存
package main
import "testing"
// optimize 10
var p, p2 = new([100]int), new([100]int)
var ip interface{}
func Benchmark_BoxPointer(b *testing.B) {
for i := 0; i < b.N; i++ {
ip = p
}
}
func Benchmark_PointerAssert(b *testing.B) {
for i := 0; i < b.N; i++ {
p = ip.(*[100]int)
}
}
func Benchmark_PointerAssign(b *testing.B) {
for i := 0; i < b.N; i++ {
p = p2
}
}
// goos: windows
// goarch: amd64
// pkg: github.com/gin-gonic/examples/grpc
// Benchmark_BoxPointer
// Benchmark_BoxPointer-4 1000000000 0.807 ns/op
// Benchmark_PointerAssert
// Benchmark_PointerAssert-4 1000000000 0.891 ns/op
// Benchmark_PointerAssign
// Benchmark_PointerAssign-4 1000000000 0.662 ns/op
// PASS
// coverage: [no statements]
//
// Process finished with exit code 0
优化11
接口值包裹0-255的整数也很快 1.15开始
从Go工具链1.15开始, 在Go运行时内部维护着一个0-255的小数组. 但包裹0-255的整数时, 将直接包裹此数组的相应元素的指针而少开辟一次内存.
但是比直接包裹指针还是慢一点
package main
import "testing"
var x, y = 255, 256
var iy, ix interface{}
func Benchmark_x(b *testing.B) {
for i := 0; i < b.N; i++ {
ix = x
}
}
func Benchmark_y(b *testing.B) {
for i := 0; i < b.N; i++ {
iy = y
}
}
// goos: windows
// goarch: amd64
// pkg: github.com/gin-gonic/examples/grpc
// Benchmark_x
// 1.14
// Benchmark_x-4 59999100 17.4 ns/op
// 1.15
// Benchmark_x-4 59999100 2.98 ns/op
// Benchmark_y
// Benchmark_y-4 70463887 17.2 ns/op
// PASS
// coverage: [no statements]
BCE (Bounds Check Elimination) 优化
Go是一门内存安全的语言. 检查下标越界是保证内存安全的重要举措之一. 但另一方面检查下标越界也耗费一些CPU计算. 事实上绝大部分的下标越界检查都不会发现有问题的. 这就是维护内存安全的代价.
在某些情形下, 编译器在代码编译阶段可以确定某些下标越界检查是不必要的从而可以避免这些检查, 这样将提升程序运行效率
编译器并不总是足够的聪明, 有时需要人为干预引导编译器来消除一些下标越界检查.




本文介绍了Go语言的多项优化内容,包括字符串与字节切片转换、循环操作、结构体值处理、接口值包裹等方面的优化。还阐述了BCE(Bounds Check Elimination)优化,即编译器在某些情形下可避免不必要的下标越界检查,提升程序运行效率,有时需人为引导。
1172

被折叠的 条评论
为什么被折叠?



