Golang中如何测量缓存行大小?
Golang中如何测量缓存行大小? 如何找出我计算机上的缓存行大小?
我知道有一些方法可以通过运行代码来估算,但我没有找到 Golang 的示例。
另外,在 Linux 中是否有方法可以查看我的缓存行大小,也许是通过某些属性文件?
非常感谢所有的一切。
2020年5月2日星期六,晚上7:09,Sean Killian 通过 Go Forum forum@golangbridge.org 写道:
无论是32、64、128字节还是其他大小,一次性跳过10个值并不能有效利用CPU的缓存行或实际缓存。但是,为什么你必须知道确切的大小呢?
在 Linux 系统上,你可以读取 /proc/cpuinfo:
$ cat /proc/cpuinfo | grep cache
cache size : 30720 KB
cache_alignment : 64
cache size : 30720 KB
cache_alignment : 64
这是你要找的信息吗?
lutzhorn:
cat /proc/cpuinfo | grep cache
你好 @lutzhorn
我正在寻找一个缓存行的大小。这看起来像是整个缓存的大小。也许 cache_alignment 是缓存行的大小,64 位?
对于缓存行,我的理解是发生缓存未命中时加载到内存中的最小数据块的大小。
@JOhn_Stuart,回答你的问题,这应该可行:
https://play.golang.org/p/mCWdVtZaUxL
话虽如此,你是在什么情况下需要这个信息呢?我承认,我一直在做一个私人的个人项目,其中用到了那个值,但对于任何实际的项目,我可能会建议不要这样做,并提供一些替代方案。
skillian:
一次访问并不是对CPU缓存行或实际缓存的有效利用。但为什么你必须知道确切的大小呢?
只是出于好奇,没有实际用途。
不管怎样,你说得有道理,跳过 10 * 8 字节确实效率不高。我正在思考一个实际问题:在一个切片中,只有 25% 的元素会被频繁使用,其余则很少使用。安排这些元素的最佳位置是在数组的开头吗?
有没有关于如何安排数据以利用缓存行大小的例子?
这是一个很好的方法。
看看这段代码,它清晰地展示了缓存行大小的威力:
var arr = make([]int, 64*1024*1024)
func one() {
for i := 0; i < len(arr); i++ {
arr[i] *= 3
}
}
func two() {
for i := 0; i < len(arr); i += 10 {
arr[i] *= 3
}
}
在你阅读下文之前,请预测一下:如果进行基准测试,函数 two 会比函数 one 快多少?毕竟,它只做了十分之一的工作量,而且工作性质相同。
我正在寻找更多类似的代码示例。另外,我确切地知道有一些矩阵 C 代码可以让你精确地判断出缓存行大小,不是通过读取配置文件或属性,而是通过分块进行计算并增加块大小——或者类似的方法。
我正在尝试构思一些 Go 代码,通过测量类似函数的运行时间,让你能够精确地判断出缓存行大小。
你对基准测试中函数 one 和 two 的结果有什么预估吗?
我明白了。我建议下次将这样的帖子发布在 #technical-discussion 板块。当我在 #getting-help 板块看到它时,我的目标是直接解决你遇到的实际问题。
无论如何,根据你给出的频繁使用的切片元素示例,如果可能的话,它们应该是连续的,但不一定非要在数组的开头。这段代码:
package main
import (
"fmt"
)
func main() {
vs := make([]int, 1024)
vs[512] = 123
fmt.Println(vs[512])
}
其内存效率与索引 512 是 0 时一样。切片有一个指向其底层数组的指针,所以当索引是 512 时,会有额外的指令来计算该偏移量相对于切片数据指针的位置,但 CPU 仍然只为存储操作执行一次解引用,为加载操作执行另一次解引用(假设优化器没有优化掉对 vs[512] 的第二次访问)。
我开始整理一份在 Go 中实现缓存高效代码时需要牢记的要点清单,但它开始变得非常长,所以我停下了。长话短说,我建议你看看 Go 的运行时。里面有很多空间优化的例子。我特别记得的一个是 internal/reflectlite.name 类型。
在Golang中测量缓存行大小可以通过多种方式实现。以下是几种实用的方法:
方法1:使用sys包读取系统信息(Linux)
package main
import (
"fmt"
"io/ioutil"
"strconv"
"strings"
)
func getCacheLineSize() (int, error) {
// 读取CPU信息文件
data, err := ioutil.ReadFile("/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size")
if err != nil {
return 0, err
}
// 解析缓存行大小
sizeStr := strings.TrimSpace(string(data))
size, err := strconv.Atoi(sizeStr)
if err != nil {
return 0, err
}
return size, nil
}
func main() {
size, err := getCacheLineSize()
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Cache line size: %d bytes\n", size)
}
方法2:使用cpuid包获取缓存信息
package main
import (
"fmt"
"github.com/klauspost/cpuid/v2"
)
func main() {
// 获取CPU信息
cpu := cpuid.CPU
// 输出缓存行大小
fmt.Printf("Cache line size: %d bytes\n", cpu.CacheLine)
// 详细缓存信息
for _, cache := range cpu.Cache {
fmt.Printf("Cache Level %d: %d KB, Line size: %d bytes\n",
cache.Level, cache.Size, cache.LineSize)
}
}
方法3:通过内存访问模式测量
package main
import (
"fmt"
"time"
)
func measureCacheLineSize() int {
const maxSize = 1024
const iterations = 1000000
// 创建测试数组
data := make([]byte, maxSize*maxSize)
var minTime time.Duration = 1<<63 - 1
var bestSize int
// 测试不同步长
for stride := 1; stride <= maxSize; stride *= 2 {
start := time.Now()
// 按指定步长访问内存
sum := 0
for i := 0; i < iterations; i++ {
for j := 0; j < maxSize; j += stride {
sum += int(data[j*maxSize])
}
}
elapsed := time.Since(start)
_ = sum // 防止编译器优化
if elapsed < minTime {
minTime = elapsed
bestSize = stride
}
}
return bestSize
}
func main() {
size := measureCacheLineSize()
fmt.Printf("Estimated cache line size: %d bytes\n", size)
}
方法4:使用/proc/cpuinfo(Linux)
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func getCacheLineFromProc() (int, error) {
file, err := os.Open("/proc/cpuinfo")
if err != nil {
return 0, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "cache_alignment") {
parts := strings.Split(line, ":")
if len(parts) == 2 {
sizeStr := strings.TrimSpace(parts[1])
size, err := strconv.Atoi(sizeStr)
if err != nil {
return 0, err
}
return size, nil
}
}
}
return 0, fmt.Errorf("cache line size not found")
}
func main() {
size, err := getCacheLineFromProc()
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Cache alignment: %d bytes\n", size)
}
Linux命令行方法
除了Go代码,在Linux系统中可以通过以下命令查看缓存行大小:
# 方法1:查看特定缓存级别的行大小
getconf LEVEL1_DCACHE_LINESIZE
# 方法2:查看所有缓存信息
lscpu | grep cache
# 方法3:查看sysfs信息
cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
# 方法4:使用x86info(需要安装)
x86info -c | grep "cache line size"
完整示例:综合方法
package main
import (
"fmt"
"runtime"
)
func main() {
// 获取系统缓存行大小
fmt.Printf("System cache line size:\n")
// 方法1:使用runtime包(Go 1.19+)
if runtime.CacheLinePadSize > 0 {
fmt.Printf(" Runtime cache line pad size: %d bytes\n", runtime.CacheLinePadSize)
}
// 方法2:尝试不同来源
sizes := []struct {
name string
get func() (int, error)
}{
{"sysfs", getCacheLineSize},
{"procfs", getCacheLineFromProc},
}
for _, source := range sizes {
if size, err := source.get(); err == nil {
fmt.Printf(" %s: %d bytes\n", source.name, size)
}
}
}
这些方法中,使用sysfs(/sys/devices/system/cpu/)通常是最准确和可靠的。cpuid包提供了跨平台的支持,而内存访问模式测量可以在没有系统信息访问权限时使用。

