• 提升您的 Go 应用性能的 6 种方法


    优化您的 Go 应用程序

    在这里插入图片描述

    1. 如果您的应用程序在 Kubernetes 中运行,请自动设置 GOMAXPROCS 以匹配 Linux 容器的 CPU 配额

    Go 调度器 可以具有与运行设备的核心数量一样多的线程。由于我们的应用程序在 Kubernetes 环境中的节点上运行,当我们的 Go 应用程序开始运行时,它可以拥有与节点中的核心数量一样多的线程。由于许多不同的应用程序在这些节点上运行,因此这些节点可能包含相当多的核心。

    通过使用 https://github.com/uber-go/automaxprocs,Go 调度器使用的线程数量将与您在 k8s yaml 中定义的 CPU 限制一样多。

    示例:

    应用程序 CPU 限制(在 k8s.yaml 中定义):1 核心
    节点核心数量:64

    通常情况下,Go 调度器会尝试使用 64 个线程,但如果我们使用 automaxprocs,它将仅使用一个线程。

    我观察到在我实施这个方法的应用程序中有相当大的性能提升。约 60% 的 CPU 使用率,约 30% 的内存使用率和约 30% 的响应时间。

    2. 对结构体字段进行排序

    结构体中字段的顺序直接影响您的内存使用情况。

    例如:

    type testStruct struct {
     testBool1  bool    // 1 byte
     testFloat1 float64 // 8 bytes
     testBool2  bool    // 1 byte
     testFloat2 float64 // 8 bytes
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    您可能会认为这个结构体将占用 18 字节,但实际上不会。

    func main() {
     a := testStruct{}
     fmt.Println(unsafe.Sizeof(a)) // 32 bytes
    }
    
    • 1
    • 2
    • 3
    • 4

    这是因为在 64 位架构中内部内存对齐的工作方式。有关更多信息,您可以阅读这篇文章

    在这里插入图片描述

    我们如何降低内存使用?我们可以根据内存填充来对字段进行排序。

    type testStruct struct {
     testFloat1 float64 // 8 bytes
     testFloat2 float64 // 8 bytes
     testBool1  bool    // 1 byte
     testBool2  bool    // 1 byte
    }
    
    func main() {
     a := testStruct{}
     fmt.Println(unsafe.Sizeof(a)) // 24 bytes
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    img

    我们并不总是需要手动排序这些字段。您可以使用诸如 fieldalignment 等工具来自动对结构体进行排序。

    fieldalignment -fix ./... 
    
    • 1

    3. 垃圾回收调优

    在 Go 1.19 之前,我们只能使用 GOGC(runtime/debug.SetGCPercent) 来配置垃圾回收周期;然而,在某些情况下,我们可能会超出内存限制。随着 Go 1.19 的到来,我们现在拥有了 GOMEMLIMITGOMEMLIMIT 是一个新的环境变量,允许用户限制 Go 进程可以使用的内存量。这个功能提供了更好的控制 Go 应用程序内存使用的方式,防止它们使用过多的内存导致性能问题或崩溃。通过设置 GOMEMLIMIT 变量,用户可以确保其 Go 程序在系统上平稳高效地运行,而不会对系统造成不必要的压力。

    它并不替代 GOGC,而是与之配合使用。您还可以禁用 GOGC 百分比配置,只使用 GOMEMLIMIT 来触发垃圾回收。

    在这里插入图片描述

    GOGC 设为 100 和内存限制为 100MB

    在这里插入图片描述

    GOGC 设为关闭(off)并且内存限制为 100。

    在减少垃圾回收的运行量方面有明显的效果,但在应用此设置时需要小心。如果您不了解应用程序的极限,请不要将 GOGC=off

    4. 使用 unsafe 包进行字符串 <-> 字节转换而不进行复制

    在字符串与字节之间进行转换时,我们通常会进行变量的复制。但在 Go 内部,这两种类型通常使用 StringHeaderSliceHeader 值。我们可以在这两种类型之间进行转换,而不进行额外的分配。

    // For Go 1.20 and higher
    func StringToBytes(s string) []byte {
     return unsafe.Slice(unsafe.StringData(s), len(s))
    }
    
    func BytesToString(b []byte) string {
     return unsafe.String(unsafe.SliceData(b), len(b))
    }
    
    // For lower versions
    // Check the example here
    // https://github.com/bcmills/unsafeslice/blob/master/unsafeslice.go#L116
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    诸如 fasthttpfiber 等库也在其内部使用这种结构。

    注意: 如果您的字节或字符串值可能会在后续发生更改,请不要使用此特性。

    5. 使用 jsoniter 替代 encoding/json

    我们通常在代码中使用 MarshalUnmarshal 方法来进行序列化或反序列化。

    Jsoniterencoding/json 的 100% 兼容的替代品。

    以下是一些性能基准:

    在这里插入图片描述

    将其替换为 encoding/json 非常简单:

    import "encoding/json"
    
    json.Marshal(&data)
    json.Unmarshal(input, &data)
    import jsoniter "github.com/json-iterator/go"
    
    var json = jsoniter.ConfigCompatibleWithStandardLibrary
    json.Marshal(&data)
    json.Unmarshal(input, &data)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    6. 使用 sync.Pool 来减少堆分配

    对象池背后的主要概念是避免重复创建和销毁对象的开销,这可能会对性能产生负面影响。

    缓存先前分配但未使用的项目有助于减轻垃圾回收器的负担,并允许稍后重新使用它们。

    以下是一个示例:

    type Person struct {
     Name string
    }
    
    var pool = sync.Pool{
     New: func() any {
      fmt.Println("Creating a new instance")
      return &Person{}
     },
    }
    
    func main() {
     person := pool.Get().(*Person)
     fmt.Println("Get object from sync.Pool for the first time:", person)
     person.Name = "Mehmet"
    
     fmt.Println("Put the object back in the pool")
     pool.Put(person)
    
     fmt.Println("Get object from pool again:", pool.Get().(*Person))
    
     fmt.Println("Get object from pool again (new one will be created):", pool.Get().(*Person))
    }
    
    //Creating a new instance
    //Get object from sync.Pool for the first time: &{}
    //Put the object back in the pool
    //Get object from pool again: &{Mehmet}
    //Creating a new instance
    //Get object from pool again (new one will be created): &{}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    通过使用 sync.Pool,我解决了 New Relic Go Agent 中的内存泄漏问题。以前,它为每个请求创建一个新的 gzip writer。我创建了一个池,以便代理程序可以使用该池中的 writer,而不是为每个请求创建新的 gzip writer 实例,从而大大减少了堆使用,并因此减少了系统的垃圾回收次数。这个改进大约将我们应用程序的 CPU 使用率降低了约 40%,内存使用率降低了约 22%。

    希望对您有所帮助,欢迎提供任何反馈。谢谢。

  • 相关阅读:
    学生个人网页设计作品:旅游网页设计与实现——成都旅游网站4个页HTML+CSS web前端网页设计期末课程大作业 学生DW静态网页设计 学生个人网页设计作品
    通关 MySQL获奖名单已公布
    table展示子集踩坑
    JAVA 基础学习笔记 (6)访问权限修饰符
    Go语言进阶,交叉编译,数字与字符的转换,多变参数
    数据挖掘,在商业智能BI领域的运用
    mongodb 集合复制---聚合管道操作符$out来实现
    Java编程实战9:统计只差一个字符的子串数目
    [SQL-SERVER:数据库安全及维护]:MSSM工具进行附加还原备份等操作
    java计算机毕业设计网络教育学习平台源程序+mysql+系统+lw文档+远程调试
  • 原文地址:https://blog.csdn.net/csjds/article/details/133610834