这个提交:95a11c7展示了一个通过将一个小的非转义切片移动到栈上触发的实际性能提升。我的理解是,Go编译器总是在堆上分配切片,因为长度在编译时未知。是否有必要尝试对许多/所有非转义切片进行类似的代码转换?这样做有什么缺点?关于如何识别哪些切片可以从这种转换中受益以及哪些可能会产生开销,有什么建议吗?
ajsxfq5m1#
我认为如果我们可以的话,从堆栈开始几乎总是一个赢的策略。这个例子很棘手,可能非常常见:
var b []byte for ... { b = append(b, ...) }
我们如何预先为 b 分配一些空间?我们希望这样做:
b
var bStore [32]byte // on stack b := bStore[:0]
但如果 for 循环运行0次,那么这是不正确的。结果必须是 nil (并具有0容量)。
for
nil
cx6n0qe32#
我想知道,如果始终应用这种转换,即使它从未影响性能,是否会使二进制文件明显变大。这是否会为所有分配的小非逃逸切片完成?还是仅针对那些已知在编译时容量较小的切片?
klr1opcd3#
我们已经在编译时为已知容量的小非逃逸切片分配了栈空间。这个问题是关于在编译时未知大小的情况。
nqwrtyyt4#
这是#20533吗?
q9yhzks05#
它确实很相似。#20533 在你执行 a := make([]byte, n) (alloca风格的分配,或者在堆上显式释放)时,会沿着真正分配n字节的道路走得更远。这个函数是关于分配一个固定大小的缓冲区,并且只在 n 足够小的时候使用它。
a := make([]byte, n)
n
6tqwzwtp6#
将 a := make([]byte, n) 转换为:
var a []byte if n < 64 { a = make([]byte, n, 64) // stack allocation } else { a = make([]byte, n) // heap allocation }
肯定会对代码大小产生一定影响。在某些情况下,也许证明能够移除两个分支中的一个,但我不抱太大希望。我想知道从性能Angular 来看,这样做是否仍然值得。我们还应该探索对不同类型切片执行此操作(同时保持总堆分配在一定限制内)。
kmbjn2e37#
Transforming a := make([]byte, n) into ...Recent example where such transformation was done by hand for performance: 17d5cef ( CL 230657 )./cc @martisch
7条答案
按热度按时间ajsxfq5m1#
我认为如果我们可以的话,从堆栈开始几乎总是一个赢的策略。
这个例子很棘手,可能非常常见:
我们如何预先为
b
分配一些空间?我们希望这样做:但如果
for
循环运行0次,那么这是不正确的。结果必须是nil
(并具有0容量)。cx6n0qe32#
我想知道,如果始终应用这种转换,即使它从未影响性能,是否会使二进制文件明显变大。
这是否会为所有分配的小非逃逸切片完成?还是仅针对那些已知在编译时容量较小的切片?
klr1opcd3#
我们已经在编译时为已知容量的小非逃逸切片分配了栈空间。这个问题是关于在编译时未知大小的情况。
nqwrtyyt4#
这是#20533吗?
q9yhzks05#
它确实很相似。#20533 在你执行
a := make([]byte, n)
(alloca风格的分配,或者在堆上显式释放)时,会沿着真正分配n
字节的道路走得更远。这个函数是关于分配一个固定大小的缓冲区,并且只在n
足够小的时候使用它。6tqwzwtp6#
将
a := make([]byte, n)
转换为:肯定会对代码大小产生一定影响。在某些情况下,也许证明能够移除两个分支中的一个,但我不抱太大希望。我想知道从性能Angular 来看,这样做是否仍然值得。
我们还应该探索对不同类型切片执行此操作(同时保持总堆分配在一定限制内)。
kmbjn2e37#
Transforming a := make([]byte, n) into ...
Recent example where such transformation was done by hand for performance: 17d5cef ( CL 230657 ).
/cc @martisch