切片(slice)性能及陷阱 | Go 语言高性能编程 | 极客兔兔
geektutu opened this issue · comments
https://geektutu.com/post/hpg-slice.html
Go 语言/golang 高性能编程,Go 语言进阶教程,Go 语言高性能编程(high performance go)。详细介绍了切片(slice) 常用的几种操作 append、copy 等的性能及原理。并且介绍了切片的陷阱,即什么情况下会产生大量内存被占用,而没法释放的情况。
牛批!
@chocolateszz 笔芯 ღ( ´・ᴗ・` ) 😋
大佬爱你,上班看得停不下来
大佬爱你,上班看得停不下来
@bestgopher 大E了啊,没有闪,武林要和为贵。
我跟兔兔可以成为好朋友!🥰
例如表达式 s[n] 访问数组的第 n 个元素
这段应该是 访问数组中下标为 n 的元素
吧
总结:
- GO 中的数组变量属于值类型,当数组变量被赋值或传递时,实际上会复制整个数组
- 切片本质是数组片段的描述,包括数组的指针,片段的长度和容量,切片操作并不复制切片指向的元素,而是复用原来切片的底层数组
- 长度是切片实际拥有的元素,使用
len
可得到切片长度 - 容量是切片预分配的内存能够容纳的元素个数,使用
cap
可得到切片容量- 当 append 之后的元素小于等于 cap,将会直接利用底层元素剩余的空间
- 当 append 后的元素大于 cap,将会分配一块更大的区域来容纳新的底层数组,在容量较小的时候,通常是以 2 的倍数扩大
- 长度是切片实际拥有的元素,使用
- 可能存在只使用了一小段切片,但是底层数组仍被占用,得不到使用,推荐使用
copy
替代默认的re-slice
刚刚看了Java中对于string的优化,和这个还是有几分相似之处的。
谢谢楼主的文章
有个问题想问一下, 为什么我这样写:
package main
import (
"fmt"
"math/rand"
"runtime"
"time"
)
func printMem() {
var rtm runtime.MemStats
runtime.ReadMemStats(&rtm)
fmt.Printf("%f MB\n", float64(rtm.Alloc)/1024./1024.)
}
func makeArr() []int {
arr := make([]int, 8000000)
return arr
}
func main() {
arr := makeArr()
rand.Seed(time.Now().UnixNano())
for i := 0; i < len(arr); i++ {
arr[i] = rand.Int()
}
arr=arr[:5]
printMem()//61.218254 MB
runtime.GC()
printMem()//0.184059 MB
fmt.Println(len(arr))
}
得出来的结果, 发现在调用runtime.GC之后内存使用明显减少,回收了部分底层数组呢?
想请教一下这个测试为什么会是这样的结果。谢谢!
谢谢楼主的文章 有个问题想问一下, 为什么我这样写:
package main import ( "fmt" "math/rand" "runtime" "time" ) func printMem() { var rtm runtime.MemStats runtime.ReadMemStats(&rtm) fmt.Printf("%f MB\n", float64(rtm.Alloc)/1024./1024.) } func makeArr() []int { arr := make([]int, 8000000) return arr } func main() { arr := makeArr() rand.Seed(time.Now().UnixNano()) for i := 0; i < len(arr); i++ { arr[i] = rand.Int() } arr=arr[:5] printMem()//61.218254 MB runtime.GC() printMem()//0.184059 MB fmt.Println(len(arr)) }得出来的结果, 发现在调用runtime.GC之后内存使用明显减少,回收了部分底层数组呢? 想请教一下这个测试为什么会是这样的结果。谢谢!
因为你最后只用到了 arr
的长度,没有使用 arr
底层数组,所以底层数组直接被回收掉了。
你可以在代码最后加上 arr[0] = 1
看看,这样打印出来的结果就一样了。
因为你最后只用到了
arr
的长度,没有使用arr
底层数组,所以底层数组直接被回收掉了。你可以在代码最后加上
arr[0] = 1
看看,这样打印出来的结果就一样了。
谢谢回复
还是有个疑问,
我理解的, arr切片本质上是个结构体, 这个结构体应该是在栈上的, 没有被垃圾回收, 那它指向的底层数组为什么会被回收呢?
因为你最后只用到了
arr
的长度,没有使用arr
底层数组,所以底层数组直接被回收掉了。
你可以在代码最后加上arr[0] = 1
看看,这样打印出来的结果就一样了。谢谢回复 还是有个疑问, 我理解的, arr切片本质上是个结构体, 这个结构体应该是在栈上的, 没有被垃圾回收, 那它指向的底层数组为什么会被回收呢?
看上去和编译优化有关,你的代码加上 -gcflags='-N'
编译后打印的结果就变成一样的了。
因为你最后只用到了
arr
的长度,没有使用arr
底层数组,所以底层数组直接被回收掉了。
你可以在代码最后加上arr[0] = 1
看看,这样打印出来的结果就一样了。谢谢回复 还是有个疑问, 我理解的, arr切片本质上是个结构体, 这个结构体应该是在栈上的, 没有被垃圾回收, 那它指向的底层数组为什么会被回收呢?
看上去和编译优化有关,你的代码加上
-gcflags='-N'
编译后打印的结果就变成一样的了。
明白了, 谢谢大佬
博主,请问改电子书是如何部署搭建的?
博主,其中有一个例子:
func lastNumsBySlice(origin []int) []int {
return origin[len(origin)-2:]
}
last num 最后一个元素应该是 origin[len(origin)-1:] 吧
有个问题哈,事先知道slice长度的情况下最好先预设长度会比不预设快,但是实际我benchmark测试结果如下(golang 1.18),结果恰恰相反,这是怎么回事?
goos: windows
goarch: amd64
pkg: demo
cpu: Intel(R) Core(TM) i5-9400 CPU @ 2.90GHz
BenchmarkSlice1-6 181 6142071 ns/op
BenchmarkSlice2-6 150 7996002 ns/op
PASS ok
demo 3.832s
代码如下:
func slice1() {
var s []int
for i := 0; i < 1000000; i++ {
s = append(s, i)
}
}
//事先分配
func slice2() {
var s []int = make([]int, 1000000)
for i := 0; i < 1000000; i++ {
s = append(s, i)
}
}
有个问题哈,事先知道slice长度的情况下最好先预设长度会比不预设快,但是实际我benchmark测试结果如下(golang 1.18),结果恰恰相反,这是怎么回事?
goos: windows
goarch: amd64
pkg: demo
cpu: Intel(R) Core(TM) i5-9400 CPU @ 2.90GHz
BenchmarkSlice1-6 181 6142071 ns/op
BenchmarkSlice2-6 150 7996002 ns/op
PASS ok
demo 3.832s
代码如下:
func slice1() { var s []int for i := 0; i < 1000000; i++ { s = append(s, i) } } //事先分配 func slice2() { var s []int = make([]int, 1000000) for i := 0; i < 1000000; i++ { s = append(s, i) } }
@programmer-liu slice2
里应该是 make([]int, 0, 1000000)
。
学到了
请教博主,为什么我将gc移到for循环外,两种方法的内存占用都会变得很小呢?
func testLastChars(t *testing.T, f func([]int) []int) {
t.Helper()
ans := make([][]int, 0)
for k := 0; k < 100; k++ {
origin := generateWithCap(128 * 1024) // 1M
ans = append(ans, f(origin))
}
runtime.GC() // 两个方法都是 0.17M
printMem(t)
_ = ans
}
@thetacoding
请教博主,为什么我将gc移到for循环外,两种方法的内存占用都会变得很小呢?func testLastChars(t *testing.T, f func([]int) []int) { t.Helper() ans := make([][]int, 0) for k := 0; k < 100; k++ { origin := generateWithCap(128 * 1024) // 1M ans = append(ans, f(origin)) } runtime.GC() // 两个方法都是 0.17M printMem(t) _ = ans }
因为你的ans在后面
@thetacoding
请教博主,为什么我将gc移到for循环外,两种方法的内存占用都会变得很小呢?func testLastChars(t *testing.T, f func([]int) []int) { t.Helper() ans := make([][]int, 0) for k := 0; k < 100; k++ { origin := generateWithCap(128 * 1024) // 1M ans = append(ans, f(origin)) } runtime.GC() // 两个方法都是 0.17M printMem(t) _ = ans }
因为你的ans在GC代码之后没有继续使用了,所以会被直接回收,你可以在后面加一个ans[0] = []int{1}
这个结果是不是有点问题?
8 printLenCap(nums) // len: 5, cap: 8 [1 2 3 4 50]