Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >从入门到掉坑:Go 内存池/对象池技术介绍

从入门到掉坑:Go 内存池/对象池技术介绍

作者头像
腾讯技术工程官方号
发布于 2020-06-04 03:10:11
发布于 2020-06-04 03:10:11
2.8K00
代码可运行
举报
运行总次数:0
代码可运行

作者:deryzhou,腾讯 PCG 后台开发工程师

Go 中怎么实现内存池,直接用 map 可以吗?常用库里 GroupCache、BigCache 的内存池又是怎么实现的?有没有坑?对象池又是什么?想看重点的同学,可以直接看第 2 节 GroupCache 总结

0. 前言: tcmalloc 与 Go

以前 C++服务上线,遇到性能优化一定会涉及 Google 大名鼎鼎的 tcmalloc。

相比 glibc,tcmalloc 在多线程下有巨大的优势:

vs tcmalloc

其中使用的就是内存池技术。如果想了解 tcmalloc 的细节,盗一张图解 TCMalloc中比较经典的结构图:

图解 TCMalloc

作为 Google 的得意之作,Golang自然也用上了 tcmalloc 的内存池03 技术。因此我们普通使用 Golang 时,无需关注内存分配的性能问题

1. 关于 map 你需要了解的

既然 Go 本身内存已经做了 tcmalloc 的管理,那实现缓存我们能想到的就是 map 了,是吧?(但仔细想想,map 不需要加锁吗?不加锁用 sync.Map 更好吗)

坑 1: 为什么不用 sync.Map

2020-05-09 补充:多位同学也提到了,bigcache 这个测试并不公平。查了下 issues,map+lock 和 sync.Map 的有人做过测试,性能确实低一些(单锁的情况) https://github.com/golang/go/issues/28938#issuecomment-441737879 但如果是 shards map+lock 和 sync.Map,在不同的读写比(比如读多写少,当超时才更新)时,这块就不好判断哪种实现更优了,有兴趣的同学可以尝试深挖下(而且 doyenli 也提到,sync.Map 内部是 append only 的)

用过 map 的同学应该会知道,map 并不是线程安全的。多个协程同步更新 map 时,会有概率导致程序 core 掉。

那我们为什么不用sync.Map?当然不是因为 go 版本太老不支持这种肤浅原因。

https://github.com/allegro/bigcache-bench 里有张对比数据,纯写 map 是比 sync.Map 要快很多,读也有一定优势。考虑到多数场景下读多写少,我们只需对 map 加个读写锁,异步写的问题就搞定了(还不损失太多性能)。

map vs sync.Map

除了读写锁,我们还可以使用 shard map 的分布式锁来继续提高并发(后面 bigcache 部分会介绍),所以你看最终的 cache 库里,大家都没用 sync.Map,而是用map+读写锁来实现存储。

坑 2: 用 map 做内存池就可以了?

并不能。map 存储 keys 也是有限制的,当 map 中 keys 数量超过千万级,有可能造成性能瓶颈。

这个是我在之前业务中实际遇到的情况,当时服务里用了 GroupCache 做缓存,导致部分线上请求会超时(0.08%左右的超时率)。我们先暂时放下这个问题,弄清原因再来介绍这里的差异。

找了下资料,发现 2014 年 Go 有个 issue 提到 Large maps cause significant GC pauses 的问题。简单来说就是当 map 中存在大量 keys 时,GC 扫描 map 产生的停顿将不能忽略

好消息是 2015 年 Go 开发者已经对 map 中无指针的情况进行了优化:

GC ignore maps with no pointers

我们参考其中的代码,写个GC 测试程序验证下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package main

import (
  "fmt"
  "os"
  "runtime"
  "time"
)

// Results of this program on my machine:
//
// for t in 1 2 3 4 5; do go run maps.go $t; done
//
// Higher parallelism does help, to some extent:
//
// for t in 1 2 3 4 5; do GOMAXPROCS=8 go run maps.go $t; done
//
// Output(go 1.14):
// With map[int32]*int32, GC took 456.159324ms
// With map[int32]int32, GC took 10.644116ms
// With map shards ([]map[int32]*int32), GC took 383.296446ms
// With map shards ([]map[int32]int32), GC took 1.023655ms
// With a plain slice ([]main.t), GC took 172.776µs

func main() {
  const N = 5e7 // 5000w

  if len(os.Args) != 2 {
    fmt.Printf("usage: %s [1 2 3 4]\n(number selects the test)\n", os.Args[0])
    return
  }

  switch os.Args[1] {
  case "1":
    // Big map with a pointer in the value
    m := make(map[int32]*int32)
    for i := 0; i < N; i++ {
      n := int32(i)
      m[n] = &n
    }
    runtime.GC()
    fmt.Printf("With %T, GC took %s\n", m, timeGC())
    _ = m[0] // Preserve m until here, hopefully
  case "2":
    // Big map, no pointer in the value
    m := make(map[int32]int32)
    for i := 0; i < N; i++ {
      n := int32(i)
      m[n] = n
    }
    runtime.GC()
    fmt.Printf("With %T, GC took %s\n", m, timeGC())
    _ = m[0]
  case "3":
    // Split the map into 100 shards
    shards := make([]map[int32]*int32, 100)
    for i := range shards {
      shards[i] = make(map[int32]*int32)
    }
    for i := 0; i < N; i++ {
      n := int32(i)
      shards[i%100][n] = &n
    }
    runtime.GC()
    fmt.Printf("With map shards (%T), GC took %s\n", shards, timeGC())
    _ = shards[0][0]
  case "4":
    // Split the map into 100 shards
    shards := make([]map[int32]int32, 100)
    for i := range shards {
      shards[i] = make(map[int32]int32)
    }
    for i := 0; i < N; i++ {
      n := int32(i)
      shards[i%100][n] = n
    }
    runtime.GC()
    fmt.Printf("With map shards (%T), GC took %s\n", shards, timeGC())
    _ = shards[0][0]
  case "5":
    // A slice, just for comparison to show that
    // merely holding onto millions of int32s is fine
    // if they're in a slice.
    type t struct {
      p, q int32
    }
    var s []t
    for i := 0; i < N; i++ {
      n := int32(i)
      s = append(s, t{n, n})
    }
    runtime.GC()
    fmt.Printf("With a plain slice (%T), GC took %s\n", s, timeGC())
    _ = s[0]
  }
}

func timeGC() time.Duration {
  start := time.Now()
  runtime.GC()
  return time.Since(start)
}

代码中一共测试了 5 种情况,写入5000w的 keys 后,主动触发 2 次 GC 来测量耗时:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[1] With map[int32]*int32, GC took 456.159324ms
[2] With map[int32]int32, GC took 10.644116ms
[3] With map shards ([]map[int32]*int32), GC took 383.296446ms
[4] With map shards ([]map[int32]int32), GC took 1.023655ms
[5] With a plain slice ([]main.t), GC took 172.776µs

可以看到,当 map 中没有指针时,扫描停顿时间大约在 10ms 左右,而包含指针int32时则会扩大 45 倍

先看 5 的数据,单纯的 slice 速度飞快,基本没有 GC 消耗。而 map shards 就有点耐人寻味了,为什么我们没有对 map 加锁,分 shard 后 GC 时间还是缩短了呢?说好的将锁分布式化,才能提高性能呢?

坑 3: shards map 能提高性能的元凶(原因)

要了解 shards map 性能变化的原因,需要先弄清楚 Golang GC 的机制。我们先加上GODEBUG=gctrace=1观察下 map 里包含指针与没有指针的 gc 差异:

map[]*int: gc 11 @11.688s 2%: 0.004+436+0.004 ms clock, 0.055+0/1306/3899+0.049 ms cpu, 1762->1762->1220 MB, 3195 MB goal, 12 P (forced)map[]int: gc 10 @9.357s 0%: 0.003+14+0.004 ms clock, 0.046+0/14/13+0.054 ms cpu, 1183->1183->746 MB, 2147 MB goal, 12 P (forced)

输出各字段含义可以看GODEBUG 之 gctrace 干货解析,这里我们只关注 cpu 里 0.055+0/1306/3899+0.049 ms cpu 这段的解释:

  • Mark Prepare (STW) - 0.055 表示整个进程在 mark 阶段 STW 停顿时间
  • Marking - 0/1306/3899 三段信息,其中 0 是 mutator assist 占用时间,1306 是 dedicated mark workers+fractional mark worker 占用的时间,3899 是 idle mark workers 占用的时间(虽然被拆分为 3 种不同的 gc worker,过程中被扫描的 P 还是会暂停的,另外注意这里时间是所有 P 消耗时间的总和)
  • Mark Termination (STW) - 0.049 表示整个进程在 markTermination 阶段 STW 停顿时间

只有 Mark 的前后两个阶段会导致 Stop-The-World(STW),中间 Marking 过程是并行的。这里 1306ms 是因为我们启动了 12 个 P,1306ms 和 3899ms 是所有 P 消耗时间的综合。虽然说是 Marking 是并行,但被扫描到的 P 还是会被暂停的。因此这个时间最终反映到业务程序上,就是某个 P 处理的请求,在 GC 时耗时突增(不稳定),不能被简单的忽略

那回到上面的问题了,shards map 的性能又是如何得到提升(近 10 倍)的?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// With map[int32]int32, GC took 11.285541ms
gc 1 @0.001s 7%: 0.010+2.1+0.012 ms clock, 0.12+0.99/2.1/1.2+0.15 ms cpu, 4->6->6 MB, 5 MB goal, 12 P
...
gc 8 @2.374s 0%: 0.003+3.9+0.018 ms clock, 0.042+0.31/6.7/3.1+0.21 ms cpu, 649->649->537 MB, 650 MB goal, 12 P
gc 9 @4.834s 0%: 0.003+7.5+0.021 ms clock, 0.040+0/14/5.1+0.25 ms cpu, 1298->1298->1073 MB, 1299 MB goal, 12 P
gc 10 @9.188s 0%: 0.003+26+0.004 ms clock, 0.045+0/26/0.35+0.053 ms cpu, 1183->1183->746 MB, 2147 MB goal, 12 P (forced)
gc 11 @9.221s 0%: 0.018+9.4+0.003 ms clock, 0.22+0/17/5.0+0.043 ms cpu, 746->746->746 MB, 1492 MB goal, 12 P (forced)

// With map shards ([]map[int32]int32), GC took 1.017494ms
gc 1 @0.001s 7%: 0.010+2.9+0.048 ms clock, 0.12+0.26/3.6/4.1+0.57 ms cpu, 4->7->6 MB, 5 MB goal, 12 P
...
gc 12 @3.924s 0%: 0.003+3.2+0.004 ms clock, 0.040+1.2/7.5/14+0.048 ms cpu, 822->827->658 MB, 840 MB goal, 12 P
gc 13 @8.096s 0%: 0.003+6.1+0.004 ms clock, 0.044+6.0/14/32+0.053 ms cpu, 1290->1290->945 MB, 1317 MB goal, 12 P
gc 14 @11.619s 0%: 0.003+1.2+0.004 ms clock, 0.045+0/2.5/3.7+0.056 ms cpu, 1684->1684->1064 MB, 1891 MB goal, 12 P (forced)
gc 15 @11.628s 0%: 0.003+0.91+0.004 ms clock, 0.038+0/2.3/3.6+0.057 ms cpu, 1064->1064->1064 MB, 2128 MB goal, 12 P (forced)

从倒数第三轮内存最大的时候看,GC worker 的耗时都是接近的;唯一差异较大的,是 markTermination 阶段的 STW 时间,shard 方式下少了 1/10,因此推测和该阶段得到优化有关。

至于这个时间为什么能减少,我也不清楚为什么(这个坑挖得太深,只能以后找到资料再来填...)

2. GroupCache

言归正传(众人:什么?!前面写这么多你还没进入正文。我:咳..咳..),我们总结下用 map 实现内存池的要点

  1. 内存池用 map 不用 sync.Map;map 要加读写锁
  2. map 尽量存非指针(key 和 value 都不包含指针)
  3. map 里存放指针,需要注意 keys 过多会带来的 GC 停顿问题
  4. 使用 shards map

然后我们看看GroupCache 的实现方法,这个定义在 lru/lru.go 里:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Cache is an LRU cache. It is not safe for concurrent access.
type Cache struct {
  cache map[interface{}]*list.Element
}

从 cache 的定义可以看出,这是我们说的 map 里包含指针的情况,而且还是不分 shards 的。所以如果你单机 GroupCache 里 keys 过多,还是要注意下用法的。

注:截止目前 1.14,map 里包含指针时 idle worker 耗时问题还未有结论,有兴趣可以参考 10ms-26ms latency from GC in go1.14rc1, possibly due to 'GC (idle)' work 里面的例子和现象。

3. BigCache

相比分布式场景的 GroupCache,如果你本地依然有千万级的 keys,那推荐你用 bigcache。无数经验证明,超大 map 的内存池导致的 GC 延迟,是可以通过切 bigcache 解决的。那 bigcache 到底怎么做到的?

简单来说:shards map + map[uint]uint + []byte + free link = BigCache

  1. 定义 shards cache,避免锁粒度过大
  2. map 里只存放 uint 避免指针
  3. 实现一个 queue 结构(实际是[]byte,通过 uint 下标追加分配)
  4. 采用 free 链机制,删除保留空洞最后一起回收(这块逻辑还蛮复杂的,先留个不大不小的坑吧...)

其内存池定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type cacheShard struct {
  hashmap     map[uint64]uint32        // key在entries中的位置
  entries     queue.BytesQueue         // 实际是[]byte,新数据来了后copy到尾部
}

这样 GC 就变成了map 无指针+[]byte 结构的扫描问题了,因此性能会高出很多。

坑 4: 两种方式(GroupCache 和 BigCache)对具体业务到底有多大影响?

上面只是 map 实现内存池的模拟分析,以及两种典型 Cache 库的对比。如果你也和我一样,问自己“具体两种 Cache 对业务有多大影响呢”?那只能很高兴的对你说:欢迎来到坑底 -_-

我们线上大概需要单机缓存 1000 万左右的 keys。首先我尝试模拟业务,向两种 Cache 中插入 1000w 数据来测试 GC 停顿。然而因为实验代码或其他未知的坑,最后认为这个方法不太可侧

最后讨论,觉得还是用老办法,用 Prometheus 的 histogram 统计耗时分布。我们先统计底层存储(Redis)的耗时分布,然后再分别统计 BigCache 和 GroupCache 在写入 500w 数据后的实际情况。分析结论可知:

40ms 以上请求

从 redis 数据看,40ms 以上请求占比0.08%;BigCache 的 40ms 以上请求占0.04%(即相反有一半以上超时请求被 Cache 挡住了) GroupCache 则是0.2%,将这种长时间请求放大了1倍多(推测和 map 的锁机制有关)

10ms-40ms 请求

redis 本身这个区间段请求占比24.11%;BigCache 则只有15.51%,相当于挡掉了33%左右的高延迟请求(证明加热点 Cache 还是有作用的) GroupCache 这个区间段请求占比21.55%,也比直接用 redis 来得好

详细数据分布:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
redis     [  0.1] 0.00%
redis     [  0.5] 0.38%
redis     [    1] 3.48%
redis     [    5] 71.94%
redis     [   10] 22.90%
redis     [   20] 1.21%
redis     [   40] 0.07%
redis     [ +Inf] 0.01%

bigcache  [  0.1] 0.40%
bigcache  [  0.5] 16.16%
bigcache  [    1] 14.82%
bigcache  [    5] 53.07%
bigcache  [   10] 14.85%
bigcache  [   20] 0.66%
bigcache  [   40] 0.03%
bigcache  [ +Inf] 0.01%

groupcache[  0.1] 0.24%
groupcache[  0.5] 9.59%
groupcache[    1] 9.69%
groupcache[    5] 58.74%
groupcache[   10] 19.10%
groupcache[   20] 2.45%
groupcache[   40] 0.17%
groupcache[ +Inf] 0.03%

然而我们测完只能大致知道:本地使用 GroupCache 在 500w 量级的 keys 下,还是不如 BigCache 稳定的(哪怕 GroupCache 实现了 LRU 淘汰,但实际上因为有 Hot/Main Cache 的存在,内存利用效率上不如 BigCache)

分布式情况下,GroupCache 和 BigCache 相比又有多少差距,这个就只能挖坑等大家一起跳了。

4. 对象池与零拷贝

在实际业务中,往往 map 中并不会存储 5000w 级的 keys。如果我们只有 50w 的 keys,GC 停顿就会骤减到 4ms 左右(其间 gc worker 还会并行工作,避免 STW)。

例如无极(腾讯内部的一个配置服务)这类配置服务(或其他高频数据查询场景),往往需要 Get(key) 获取对应的结构化数据。而从 BigCache,CPU 消耗发现(如图),相比网络 IO 和 Protobuf 解析,Get 占用0.78%、Set 占用0.9%,基本可以忽略:

CPU profile

因此优化的思路也很明确,我们参考 GroupCache 的 lru 实现,将 JSON 提前解析好,在业务侧 Get 时直接返回 struct 的指针即可。具体流程不复杂,直接 ppt 截图:

zero-copy

我们把接口设计成注册的方式(注册需要解析 JSON 数据的结构),然后再 Get 时返回该结构的指针实现零拷贝。下面 benchmark 可以反映性能差异和内存分配情况(Client_Get 是实时 JSON 解析,Filter_Get 是优化的对象池 API),可以切实看到0 allocs/op

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
goos: linux
goarch: amd64
pkg: open-wuji/go-sdk/wujiclient
BenchmarkClient_Get-8              1000000        1154 ns/op           1.00 hits        87 B/op        3 allocs/op
BenchmarkFilter_Get-8              4899364         302 ns/op           1.00 hits         7 B/op        1 allocs/op
BenchmarkClient_GetParallel-8      8383149         162 ns/op           1.00 hits        80 B/op        2 allocs/op
BenchmarkFilter_GetParallel-8     13053680        91.4 ns/op           1.00 hits         0 B/op        0 allocs/op
PASS
ok    open-wuji/go-sdk/wujiclient 93.494s
Success: Benchmarks passed.

目前无极尚未对外开源。对具体实现感兴趣的同学,可以看 gist 中filter API 的实现代码

完整分享 PPT 可在后台回复:go 获取。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯技术工程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
如何打造高性能的 Go 缓存库
我在看一些优秀的开源库的时候看到一个有意思的缓存库 fastcache,在它的介绍主要有以下几点特点:
luozhiyun
2021/05/16
1.2K0
深入探讨 BigCache 的性能优化手段
bigcache是一个高性能的内存缓存库,专为需要高并发访问和低延迟响应的应用场景设计。本文将深入探讨 BigCache 的性能优化手段,包括分片机制、高效哈希算法、读写锁的使用等,并引用相关源码进行详细说明。
萝卜要努力
2025/03/07
1540
深入探讨 BigCache 的性能优化手段
我是如何实现Go性能5倍提升的?
代码的稳健、可读和高效是每一位 coder 的共同追求,写出更高效的代码不仅让自己爽、让 reviewer 赏心悦目,更能对业务带来实际的正面影响。本文将从实践及源码层面对 Go 的高性能编程进行解析,带你进入 Go 的高性能世界。
腾讯云开发者
2024/01/04
2.2K1
我是如何实现Go性能5倍提升的?
你不知道的Golang map
在开发过程中,map是必不可少的数据结构,在Golang中,使用map或多或少会遇到与其他语言不一样的体验,比如访问不存在的元素会返回其类型的空值、map的大小究竟是多少,为什么会报"cannot take the address of"错误,遍历map的随机性等等。 本文希望通过研究map的底层实现,以解答这些疑惑。 基于Golang 1.8.3
sunsky
2020/08/20
1.2K0
你不知道的Golang map
Go 高性能系列教程之五:内存和垃圾回收
Go 是一门自动垃圾收回的语言。作为自动垃圾回收的语言,Go 程序的性能通常取决于他们与垃圾收集器的交互。
Go学堂
2023/01/31
5010
每位 Gopher 都应该了解的 Golang 语言的垃圾回收算法
关于垃圾回收,比较常见的算法有引用计数、标记清除和分代收集。Golang 语言使用的垃圾回收算法是标记清除。本文主要介绍一下 Golang 语言的垃圾回收算法。
frank.
2021/02/07
1.5K0
Go GC 20 问
本文作者欧长坤,德国慕尼黑大学在读博士,Go/etcd/Tensorflow contributor,开源书籍《Go 语言原本》作者,《Go 夜读》SIG 成员/讲师,对 Go 有很深的研究。Github:@changkun,https://changkun.de。
梦醒人间
2020/02/13
1.3K0
Go GC 20 问
golang源码分析:bigcache
github.com/allegro/bigcache 是一个0GC的local cache,bigcache的核心设计有两项:分片(shard),其实通过分片降低访问压力是cache常见的操作;使用BytesQueue避免GC开销。
golangLeetcode
2023/09/06
4720
golang源码分析:bigcache
聊聊两个Go即将过时的GC优化策略
这篇文章本来是要讲 Go Memory Ballast 以及 Go GC Tuner 来调整 GC 的策略,实现原理怎么样,效果如何。但是在写的过程中,发现 Go 1.19版本出了,有个新特性让这两个优化终究成为历史。
luozhiyun
2022/09/28
1.2K0
聊聊两个Go即将过时的GC优化策略
【c++实战项目】从零实现一个高并发内存池
当前项目是实现一个高并发的内存池,他的原型是google的一个开源项目tcmalloc,tcmalloc全称Thread-Caching Malloc,即线程缓存的malloc,实现了高效的多线程内存管理,用于替代系统的内存分配相关的函数(malloc、free)
用户10925563
2024/09/26
1540
【c++实战项目】从零实现一个高并发内存池
动手实现一个localcache - 欣赏优秀的开源设计
本地缓存的简单实现可以使用map[string]interface{} + sync.RWMutex的组合,使用sync.RWMutex对读进行了优化,但是当并发量上来以后,还是变成了串行读,等待锁的goroutine就会block住。为了解决这个问题我们可以进行分桶,每个桶使用一把锁,减少竞争。分桶也可以理解为分片,每一个缓存对象都根据他的key做hash(key),然后在进行分片:hash(key)%N,N就是要分片的数量;理想情况下,每个请求都平均落在各自分片上,基本无锁竞争。
Golang梦工厂
2022/07/11
3370
动手实现一个localcache - 欣赏优秀的开源设计
Golang GC 介绍
GC 全称 Garbage Collection,目前主流的垃圾回收算法有两类,分别是追踪式垃圾回收算法(Tracing garbage collection)和引用计数法( Reference counting )。
恋喵大鲤鱼
2024/02/07
7560
Golang GC 介绍
Go内存管理及性能观测工具
Golang内存分配算法主要源自Google的TCMalloc算法,TCMalloc将内存分成三层最外层Thread Cache、中间层Central Cache、最里层Page Heap。Thread Cache和Central Cache里放着不同size的空闲内存块,相同size的空闲内存块会以链表的形式排布。申请内存分为两种,<=256KB的对象都被认为是小对象,>256KB的被认为是大对象,直接通过Page Heap来获取。大对象分配内存都是以Page为单位,即大对象内存以Page对齐。如果你看懂了下面的逻辑图,那么你已经理解了RCMalloc算法。
黄豆酱
2022/08/18
1.4K0
Go内存管理及性能观测工具
超干货!彻底搞懂Golang内存管理和垃圾回收
导语 | 现代高级编程语言管理内存的方式分自动和手动两种。手动管理内存的典型代表是C和C++,编写代码过程中需要主动申请或者释放内存;而Java和Go等语言使用自动的内存管理系统,由内存分配器和垃圾收集器来代为分配和回收内存,开发者只需关注业务代码而无需关注底层内存分配和回收,虽然语言帮我们处理了这部分,但是还是有必要去了解一下底层的架构设计和执行逻辑,这样可以更好的掌握一门语言,本文主要以go内存管理为切入点再到go垃圾回收,系统地讲解了go自动内存管理系统的设计和原理。 一、TCMalloc go内存
腾讯云开发者
2022/07/19
6.1K0
超干货!彻底搞懂Golang内存管理和垃圾回收
Golang GC 从原理到优化
这篇文章与笔者之前所写几篇不同,是一篇综述型的文章,将从 GC 理论、在 Golang 中的应用、以及如何去做优化,这三个角度逐次进行阐述,文章中对于一些技术点会引用到多篇文章,希望读者也都能进行阅读,这有助于更全面的了解 Golang GC。
薯条的编程修养
2023/01/30
2.7K1
一文搞懂Go语言垃圾回收机制
垃圾回收(Garbage Collection, GC)是现代编程语言管理内存的核心机制之一。Go语言凭借其高效的并发模型和简洁的内存管理设计,在服务端开发、云计算等领域广受欢迎。这篇文章我就带大家从原理、实现到调优,详细解析Go语言的垃圾回收机制。
闫同学
2025/04/22
1750
Go语言GC实现原理及源码分析
在垃圾收集器开始工作时,从 GC Roots 开始进行遍历访问,访问步骤可以分为下面几步:
luozhiyun
2021/03/21
1.4K0
PHP转Go速学手册
整理了一份简要的手册,帮助大家高效的上手Go语言,主要是通过对比PHP和Go的不同点来强化理解,内容主要分为以下四部分:
用户1093396
2021/07/28
2.3K0
Go每日一库之180:fastcache(协程安全且支持大量数据存储的高性能缓存库)
这是官方 Github 主页上的项目介绍,和 fasthttp 名字一样以 fast 打头,作者对项目代码的自信程度可见一斑。此外该库的核心代码非常轻量, 笔者本着学习的目的分析下内部的代码实现。
luckpunk
2023/09/30
3670
万字长文带你深入浅出 Golang Runtime
本文作者:yifhao,腾讯PCG NOW直播 后台工程师 介绍 本文基于 2019.02 发布的 go 1.12 linux amd64 版本, 主要介绍了 Runtime 实现的一点原理和细节, 对大家容易错或者网络上很多错误的地方做一些梳理: Golang Runtime 是个什么 Golang Runtime 的发展历程, 每个版本的改进 Go 调度: 协程结构体, 上下文切换, 调度队列, 大致调度流程, 同步执行流又不阻塞线程的网络实现等 Go 内存: 内存结构, mspan 结构, 全
腾讯技术工程官方号
2019/12/03
2.5K0
万字长文带你深入浅出 Golang Runtime
相关推荐
如何打造高性能的 Go 缓存库
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验