
在许多系统上,内存是在称为“高速缓存行”的固定块中访问的。在 Intel 系统上,高速缓存行跨越 64 个字节。也就是说,如果您访问字节地址为 64、65……直到 127……的内存,它们都在同一个高速缓存行上。下一个高速缓存行从地址 128 开始,依此类推。
反过来,软件中的数据通常以具有固定大小(以字节为单位)的数据结构进行组织。我们经常将这些数据结构组织成数组。一般来说,一个数据结构可以驻留在不止一个高速缓存行上。例如,如果我在字节地址 127 处放置一个 5 字节的数据结构,那么它将占用一个缓存行的最后一个字节,并在下一个缓存行中占用四个字节。
从内存中加载数据结构时,一个简单的成本模型是访问的缓存行数。如果您的数据结构跨越 32 字节或 64 字节,并且您已对齐数组的第一个元素,那么您每次加载数据结构时只需要访问一个缓存行。
如果我的数据结构有 5 个字节怎么办?假设我将它们打包成一个数组,每个实例仅使用 5 个字节。如果我随机选择一个……我会接触多少缓存行?不出所料,答案平均不超过 1 个缓存行。
让我们概括一下。
假设我的数据结构跨越 z 个字节。设 g 为 z 和 64 之间的最大公约数。假设您从一个大数组中随机加载数据结构的一个实例。一般来说,额外的高速缓存行访问的预期数量是 (z – g)/64。预期的高速缓存行访问总数是 1 + (z – g)/64。您可以检查它是否适用于 z = 32,因为 g 是 32,而 (z – g)/64 是 (32-32)/64 或零。
我为所有不大于缓存行的数据结构创建了下表。最坏的情况是跨越 63 字节的数据结构:然后您几乎总是会触及两个缓存行。
我发现有趣的是,对于大小为 17、20、24 的数据结构,您具有相同的预期缓存行访问次数。这并不意味着跨越 24 字节的数据结构的计算成本与跨越数据结构的成本相同17 个字节。其他一切都相同,较小的数据结构应该会更好,因为它可以更容易地适应 CPU 缓存。
| 数据结构的大小 (z) | 预期的高速缓存行访问 |
|---|---|
| 1 | 1.0 |
| 2 | 1.0 |
| 3 | 1.03125 |
| 4 | 1.0 |
| 5 | 1.0625 |
| 6 | 1.0625 |
| 7 | 1.09375 |
| 8 | 1.0 |
| 9 | 1.125 |
| 10 | 1.125 |
| 11 | 1.15625 |
| 12 | 1.125 |
| 13 | 1.1875 |
| 14 | 1.1875 |
| 15 | 1.21875 |
| 16 | 1.0 |
| 17 | 1.25 |
| 18 | 1.25 |
| 19 | 1.28125 |
| 20 | 1.25 |
| 21 | 1.3125 |
| 22 | 1.3125 |
| 23 | 1.34375 |
| 24 | 1.25 |
| 25 | 1.375 |
| 26 | 1.375 |
| 27 | 1.40625 |
| 28 | 1.375 |
| 29 | 1.4375 |
| 30 | 1.4375 |
| 31 | 1.46875 |
| 32 | 1.0 |
| 33 | 1.5 |
| 34 | 1.5 |
| 35 | 1.53125 |
| 36 | 1.5 |
| 37 | 1.5625 |
| 38 | 1.5625 |
| 39 | 1.59375 |
| 40 | 1.5 |
| 41 | 1.625 |
| 42 | 1.625 |
| 43 | 1.65625 |
| 44 | 1.625 |
| 45 | 1.6875 |
| 46 | 1.6875 |
| 47 | 1.71875 |
| 48 | 1.5 |
| 49 | 1.75 |
| 50 | 1.75 |
| 51 | 1.78125 |
| 52 | 1.75 |
| 53 | 1.8125 |
| 54 | 1.8125 |
| 55 | 1.84375 |
| 56 | 1.75 |
| 57 | 1.875 |
| 58 | 1.875 |
| 59 | 1.90625 |
| 60 | 1.875 |
| 61 | 1.9375 |
| 62 | 1.9375 |
| 63 | 1.96875 |
| 64 | 1.0 |
原文: https://lemire.me/blog/2022/06/06/data-structure-size-and-cache-line-accesses/