go原来还可以这么玩?

每天一个go优化小知识,每天进步一点点~

非指针区域GC不扫描

the garbage collector will skip regions of memory that it can prove will contain no pointers. For example, regions of the heap which back slices of type []byte aren't scanned at all. This also holds true for arrays of struct types that don't contain any fields with pointer types.

垃圾回收不会扫描 不含指针的slice 。比如你需要设计一个本地缓存map,为了做到零GC, 底层可以考虑使用[]byte实现

增加padding的方式避免false sharing

性能要求特别高的并发访问同一个对象的场景中,可以通过增加padding的方式避免false sharing,提升CPU cache的命中率,从而提升性能

比如sync.Pool中就使用到了该技巧 必知必会系列-sync.Pool

关注可能会逃逸到堆上的行为

可能会逃逸到堆上的行为

segment.com/blog/alloca...

  • Sending pointers or values containing pointers to channels. At compile time there's no way to know which goroutine will receive the data on a channel. Therefore, the compiler cannot determine when this data will no longer be referenced.
  • Storing pointers or values containing pointers in a slice. An example of this is a type like []*string. This always causes the contents of the slice to escape. Even though the backing array of the slice may still be on the stack, the referenced data escapes to the heap.
  • Backing arrays of slices that get reallocated because an append would exceed their capacity. In cases where the initial size of a slice is known at compile time, it will begin its allocation on the stack. If this slice's underlying storage must be expanded based on data only known at runtime, it will be allocated on the heap.
  • Calling methods on an interface type. Method calls on interface types are a dynamic dispatch --- the actual concrete implementation to use is only determinable at runtime. Consider a variable r with an interface type of io.Reader. A call to r.Read(b) will cause both the value of r and the backing array of the byte slice b to escape and therefore be allocated on the heap.

函数返回值是值类型还是指针类型,哪一个性能好,需要case by case分析。

指针类型 会在堆上分配内存,效率不见得比 在栈上分配并拷贝要快

一般而言小对象使用值类型返回性能好,大对象使用指针类型性能好。

复用已分配的内存

  • 使用sync.Pool存放临时变量,做到协程间共享已分配内存 具体参看必知必会-sync.Pool
  • \[\]byte复用的一个例子
go 复制代码
type mystruct struct{
    data []byte
}

  // 比如该方法通过修改 slice的header头里面的size=0 但是底层的内存是复用的 
func (s *mystruct) Clear() {
        b.data = b.data[:0] 
}

// AddString 避免string转换到[]byte造成的内存分配和拷贝 
func (s *mystruct) AddString(s string) {
        b.data = append(b.data, s...)
}

string 和 byte 0拷贝转换

这是一个非常经典的例子。实现字符串和 bytes 切片之间的转换,要求是 zero-copy

完成这个目标,我们需要了解 slice 和 string 的底层数据结构:

go 复制代码
type StringHeader struct {
        Data uintptr
        Len  int
}

type SliceHeader struct {
        Data uintptr
        Len  int
        Cap  int
}

实现

go 复制代码
func string2bytes(s string) []byte {
        stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))

        bh := reflect.SliceHeader{
                Data: stringHeader.Data,
                Len:  stringHeader.Len,
                Cap:  stringHeader.Len,
        }

        return *(*[]byte)(unsafe.Pointer(&bh))
}

func bytes2string(b []byte) string{
        sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b))

        sh := reflect.StringHeader{
                Data: sliceHeader.Data,
                Len:  sliceHeader.Len,
        }

        return *(*string)(unsafe.Pointer(&sh))
}

由于默认字符串内存是分配在不可修改区的,使用上述的 string2bytes string 转为 slice 后,只能进行读取,不能修改其底层数据值:

go 复制代码
func main() {
    // 官方标准编译器会将 s1 的字节开辟在不可修改内存区
    s1 := "Goland"
    // 转为字节数组
    b1 := string2bytes(s1)
    fmt.Printf("%s\n", b1)
    // 修改字节数组的值 会panic
    // b1[0] = 'g'

    // 下面这种方式不会存放在不可修改区, 转为字节数组后, 可以修改值
    s2 := strings.Join([]string{"Go", "land"}, "")
    b2 := string2bytes(s2)
    fmt.Printf("%s\n", b2) // Goland
    b2[5] = 'g'            // 相当于修改底层数组的值,原字符串的值也会随之改变
    fmt.Println(s2)        // Golang
}

关注内存对齐

Go 在编译的时候会按照一定的规则自动进行内存对齐。之所以这么设计是为了减少 CPU 访问内存的次数,加大 CPU 访问内存的吞吐量。如果不进行内存对齐的话,很可能就会增加CPU访问内存的次数。具体可参看必知必会系列-Unsafe

map读多写少场景,降低并发抢锁概率

使用快照思想,读写分离,结合一定的同步机制。针对读多写少的场景,极致优化性能。具体可参看必知必会系列-sync.Map

相关推荐
小小工匠12 小时前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
大鱼>16 小时前
地平线BPU部署实战:YOLOv8在J5/X3上的算法适配与性能优化
算法·yolo·性能优化
醉颜凉17 小时前
Elasticsearch高性能优化:Bulk API大规模数据导入性能调优全攻略
elasticsearch·性能优化·jenkins
隔窗听雨眠18 小时前
C语言函数递归从入门到精通(下):性能优化与工程实践
c语言·算法·性能优化
昇腾CANN20 小时前
【cann-samples系列】GroupedMatmul MX量化矩阵乘的深度性能优化实践
线性代数·性能优化·矩阵·昇腾·cann
霸道流氓气质20 小时前
Spring Boot 微服务性能优化完全指南
spring boot·微服务·性能优化
步步为营DotNet21 小时前
Blazor 与 Microsoft.Extensions.AI 在客户端性能优化中的协同应用
人工智能·microsoft·性能优化
不能只会打代码1 天前
边缘视频分析平台的架构设计与性能优化——从750ms到190ms的调优之路
java·spring boot·redis·性能优化·边缘计算·物联网竞赛
山东点狮信息科技有限公司1 天前
企业级 MES 制造执行系统架构设计与实践
spring cloud·性能优化·系统架构·策略模式·点狮
龙智DevSecOps解决方案2 天前
3A 游戏优化技术栈:如何打通引擎级分析工具与 DevOps 持续集成管线?
unity·性能优化·游戏开发·技术美术·perforce·unrealengine