package mainimport ( "fmt" "time")func main() { size := 20000 matrix1 := make([][]int, size) for i := 0; i < size; i++ { matrix1[i] = make([]int, size) } start1 := time.Now() // 方法 1 for i := 0; i < size; i++ { for j := 0; j < size; j++ { matrix1[i][j] = i + j } } fmt.Println(time.Since(start1)) matrix2 := make([][]int, size) for i := 0; i < size; i++ { matrix2[i] = make([]int, size) } // 方法 2 start2 := time.Now() for i := 0; i < size; i++ { for j := 0; j < size; j++ { matrix2[j][i] = i + j } } fmt.Println(time.Since(start2)) }
// 1.1769527s
// 8.5584257s
现代计算机处理器是基于一种叫对称多处理 (symmetric multiprocessing, SMP) 的概念。在一个 SMP 系统里,处理器的设计使两个或多个核心连接到一片共享内存 (也叫做主存,RAM)。另外,为了加速内存访问,处理器有着不同级别的缓存,分别是 L1、L2 和 L3。确切的体系结构可能因供应商、处理器模型等等而异。然而,目前最流行的模型是把 L1 和 L2 缓存内嵌在 CPU 核心本地,而把 L3 缓存设计成跨核心共享:
越靠近 CPU 核心的缓存,容量就越小,同时访问延迟就越低 (越快):
Cache | Latency | CPU cycles | Size |
---|---|---|---|
L1 access | ~1.2 ns | ~4 | Between 32 KB and 512 KB |
L2 access | ~3 ns | ~10 | Between 128 KB and 24 MB |
L3 access | ~12 ns | ~40 | Between 2 MB and 32 MB |
同样的,这些具体的数字因不同的处理器模型而异。不过,我们可以做一个粗略的估算:假设 CPU 访问主存需要耗费 60 ns,那么访问 L1 缓存会快上 50 倍。
在处理器的世界里,有一个很重要的概念叫访问局部性 (locality of reference),当处理器访问某个特定的内存地址时,有很大的概率会发生下面的情况:
CPU 在不久的将来会去访问相同的地址:这叫**时间局部性 (temporal locality)**原则。
CPU 会访问特定地址附近的内存地址:这叫**空间局部性 (spatial locality)**原则。
之所以会有 CPU 缓存,时间局部性是其中一个重要的原因。不过,我们到底应该怎么利用处理器的空间局部性呢?比起拷贝一个单独的内存地址到 CPU 缓存里,拷贝一个缓存行 (Cache Line)是更好的实现。一个缓存行是一个连续的内存段。
原文
https://mp.weixin.qq.com/s/viQp36FeMZSqUoFy3VrBNw