首页/文章/Golang

Go1.23 新特性:新标准库 unique,性能更好,开销更小!

2024-07-24
15576 分钟
...

Go1.23 吵来吵去的,现在已经基本尘埃落定了。在我开始写这个新版本特性系列时,Go1.23 rc2 已经发布了有一周多:

今天我们分享的是新的标准库 unique 的介绍和快速入门。

背景

基于 Go unique 官方提案,我简化了一下内容。要做这个主要原因是:Go 缺乏运行时的驻留支持,这与其他语言存在差距。

多年来,Go 社区对弱映射(weak map)和字符串驻留(string interning)的需求已在过去几年的 GitHub 问题中有所体现。

来自 go/issues/62483 给出的动机:

虽然社区中要求这些功能的人很少,但我们看到了 go4.org/intern 包没有内置支持这一功能的后果(简单看了下代码。应该指的是:unsafe 骚操作,会动到 Go 的垃圾回收器逻辑,理论上要跟着 Go 版本调整。挺折腾!)

字符串驻留是什么?

前面的背景存在一些专业名词。尤其是字符串驻留是什么?可能会看的有些懵。这里我们补充一下基础知识。

根据 GPT-4 的概要总结如下:

1、字符串驻留(string interning)是一种在计算机科学中用于优化内存使用和提高性能的技术。

2、主要思想是对于每一个唯一的字符串值,只存储一个副本,这些字符串必须是不可变的。

3、以下是有关字符串驻留的一些关键点:

  • 定义

  • 字符串驻留是一种存储技术,确保每个独特的字符串值在内存中只存在一个副本。

  • 这意味着如果两个字符串具有相同的值,它们将共享同一个内存地址,而不是每个字符串都占用独立的内存空间。

  • 优势

  • 节省内存:通过避免重复的字符串副本,可以显著减少内存消耗。

  • 提高性能:字符串比较操作可以通过比较内存地址而不是逐字符比较来实现,从而加快速度。

标准库 unique

标准库 unique[1],文档非常的短小精悍。这次 Go 官方连个 example 都没有直接给。

在该标准库,unique 会对所有被添加的值进行全局的并发安全缓存,以确保值的唯一性和有效重用。会做到运行时的驻留支持,以此达到开销较佳。

API 如下:

  • Handle 是 T 类型值的全局唯一标识。

  • Make 方法为 T 类型的值返回一个全局唯一的 Handle

  • Handle[T].Value 方法返回产生 Handle 的 T 值的浅拷贝副本。

一眼看到底。比较直接,这个标准库就是围绕着 unique.Handle 来用。

到底怎么用和有什么好处,通过一个例子就能快速了解优点了。

这个新特性代码片段来自 @Anton 大佬的分享。(还是社区的力量大,不像官方文档一个例子都不给)

在以前我们要用 Go 写一个随机词生成器,可以这么写。

生成单词的代码如下:

`func wordGen(nDistinct, wordLen int) func() string {
 vocab := make([]string, nDistinct)
 for i := range nDistinct {
  word := randomString(wordLen)
  vocab[i] = word
 }
 return func() string {
  word := vocab[rand.Intn(nDistinct)]
  return strings.Clone(word)
 }
}

func randomString(n int) string {
 // 脑子进煎鱼了
 const letters = "eddycjyabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
 ret := make([]byte, n)
 for i := 0; i < n; {
  b := make([]byte, 1)
  if _, err := rand.Read(b); err != nil {
   panic(err)
  }
  ret[i] = letters[int(b[0])%len(letters)]
  i++
 }
 return string(ret)
}

`

基于上述方法,我们生成 10000 个单词。看看要使用多少内存。

代码如下:

`var words []string

func main() {
 const nWords = 10000
 const nDistinct = 100
 const wordLen = 40
 generate := wordGen(nDistinct, wordLen)
 memBefore := getAlloc()

words = make([]string, nWords)
 for i := range nWords {
  words[i] = generate()
 }

memAfter := getAlloc()
 memUsed := memAfter - memBefore
 fmt.Printf("Memory used: %dKB\n", memUsed/1024)
}

func getAlloc() uint64 {
 var m runtime.MemStats
 runtime.GC()
 runtime.ReadMemStats(&m)
 return m.Alloc
}

`

本地运行的输出结果是:

// 煎鱼 $ go run main.go Memory used: 622KB

现在改用 Go1.23 的新标准库 unique 来写。代码如下:

`var words []unique.Handle[string]

func main() {
 const nWords = 10000
 const nDistinct = 100
 const wordLen = 40
 generate := wordGen(nDistinct, wordLen)
 memBefore := getAlloc()

words = make([]unique.Handle[string], nWords)
 for i := range nWords {
  words[i] = unique.Make(generate())
 }

memAfter := getAlloc()
 memUsed := memAfter - memBefore
 fmt.Printf("Memory used: %dKB\n", memUsed/1024)
}

`

输出结果:

// 煎鱼在 Go Playground 执行的结果 Memory used: 95KB

内存使用从 622KB 减少到 95KB!优化效果非常明显。而且生成的数量越多,理论上优化效果更大。

为什么那么快就接纳了

如果有经常关注 Go 社区响应的同学,看到背景后可能会想到。这么小众的场景(提出者自己说的),居然这么一帆风顺的就直接过了。还很快来到了正式版本?

其实能对垃圾回收(GC)做这么骚操作,还不被喷的。社区上会这么做的人不多。

因此无论是之前的 go4org/intern 库,还是这次新标准库 unique 提案。相关作者都是 Google 和 Go 团队里的关联者。当然推进的极快了!

unique 提案提出者

go4org/intern 贡献者

总结

本次新标准库 unique 是基于 go4org/intern 库内化而来,虽然都是 Google 自己人开发的。但是该库的加入对于 Go 在运行时的驻留支持增添了一笔新力量。

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
Golang2026-04-10
golang文档
1\. Go 是什么 Go 是一种 强类型、垃圾回收、原生支持并发 的编程语言。代码由 package 组成;用 module 管理依赖;可执行程序通常从 package main 的 main() 函数开始运行。Go 官方一直强调兼容性,Go 1.26 也继续保持 Go 1 的兼容承诺。 最小可运行程序: 这里你已经看到了 Go 最核心的 3 个语法点: package 声明包、import 导入包、func 声明函数。main 包里...
学习
Golang2025-07-11
Go语言strconv包:字符串和数值类型的相互转换
在实际开发中我们往往需要对一些常用的数据类型进行转换,如 string、int、int64、float 等数据类型之间的转换,Go语言中的 strconv 包为我们提供了字符串和基本数据类型之间的转换功能。 strconv 包中常用的函数包括 Atoi()、Itia()、parse 系列函数、format 系列函数、append 系列函数等,下面就来分别介绍一下。 string 与 int 类型之间的转换 字符串和整型之间的转换是我们平...
学习
Golang2025-06-11
深入理解 Go 中的 new() 和 make() 函数
在 Go 语言中, 和 是两个常用的函数,用于创建和初始化不同类型的变量。本文将深入探讨 和 的区别、使用场景以及底层实现原理。 引言 Go 中的 和 函数是用于创建和初始化变量的重要工具。 用于创建指定类型的零值变量,并返回该变量的指针。 用于创建并初始化引用类型的变量,如切片、映射和通道。 函数 函数的基本语法及用法。 创建的变量是指定类型的零值,并返回该变量的指针。 适用于创建引用类型以外的其他类型变量。 函数 函数的基本语法及用...
学习
Golang2025-06-10
Ubuntu 升级 golang 版本完美步骤
一、删除旧的版本(可选) 二、下载最新版本 三、设置环境变量 1、打开profile: 2、添加以下变量: 3、是环境立即生效 4、将环境立即生效载入脚本 先打开文件这个文件: 加入这个命令: 四、查看 go 环境变量 五、GO111MOUDLE和更改GOPROXY 六、查看 go 版本 ⚙️
学习
Golang2025-06-05
Go 自动升级依赖版本
🚀 Go 自动升级依赖版本 在 Go 语言项目中,依赖管理使用 Go Modules ( 和 )。如果想要 自动升级依赖 ,可以使用以下方法。 1\. 方式 1:升级所有依赖 🔹 作用: :升级所有 Go 依赖到最新的 次要版本(minor)或补丁版本(patch) 。 :升级所有项目内的依赖。 2\. 方式 2:升级指定依赖 🔹 作用: 只升级 这个依赖到最新版本。 如果你需要升级到 最新的主要版本(major version)...
学习
Golang2025-06-02
GO项目的打包和部署
在 Go 语言中,打包和部署应用非常简单,因为 Go 编译生成的可执行文件不依赖外部环境。以下是 打包发布和部署 Go 应用 的几种常见方式,以及一些最佳实践。 1\. 基本的打包和构建 构建可执行文件 你可以通过 命令将你的应用打包成一个可执行文件: 说明: 指定输出的可执行文件名(如果省略,输出名默认为当前目录名)。 如果没有错误,当前目录会生成一个可执行文件 (Windows 系统下是 )。 运行可执行文件: 2\. 打包不同平台...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录