这篇博客文章解释了内存库冲突如何破坏转置函数的性能.
现在我不禁要问:在"普通"cpu(在多线程上下文中)是否会发生同样的情况?或者这是特定于CUDA/OpenCL?或者它甚至没有出现在现代CPU中,因为它们的缓存大小相对较大?
Apple提供了一些有关同步变量甚至执行顺序的文档.我没看到的是有关CPU缓存行为的任何文档.Objective-C开发人员有什么保证和控制来确保线程之间的缓存一致性?
考虑以下情况,其中变量在后台线程上设置但在主线程上读取:
self.count = 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
self.count = 5;
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%i", self.count);
});
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,是否应该计数?
更新1
线程间通信中的文档保证共享变量可用于线程间通信.
在两个线程之间传递信息的另一种简单方法是使用全局变量,共享对象或共享内存块.
在这种情况下,这不是必需的吗?这与内存屏障和易失性变量中的文档相矛盾:
但是,如果该变量在另一个线程中可见,则此类优化可能会阻止另一个线程注意到对它的任何更改.将volatile关键字应用于变量会强制编译器在每次使用时从内存加载该变量.
因此,我仍然不知道是否需要volatile,因为编译器可以使用寄存器缓存优化,或者如果不需要,因为编译器以某种方式知道它是"共享"的东西.
关于共享变量是什么或者编译器如何知道它的文档不是很清楚.在上面的例子中,是否计算共享对象?假设count是一个int,那么它不是一个对象.它是共享的内存块还是只适用于__block声明的变量?对于非块,非对象,非全局共享变量,可能需要volatile.
更新2
对于每个人来说,这是一个关于同步的问题,但事实并非如此.这是关于iOS平台上的CPU缓存行为.
我的程序添加了浮点数组,并且在通过 MSVC 和 G++ 进行最大优化编译时展开了 4 倍。我不明白为什么两个编译器都选择展开 4x,所以我做了一些测试,发现只是偶尔在运行时手动展开 1-vs-2 或 1-vs-4 迭代的 t 测试给出 p 值 ~0.03, 2-vs-4 很少 < 0.05,2-vs-8+ 总是 > 0.05。
如果我将编译器设置为使用 128 位向量或 256 位向量,它总是展开 4x,这是 64 字节缓存行的倍数(有意义还是巧合?)。
我考虑缓存行的原因是因为我没想到展开会对顺序读取和写入千兆字节浮点数的内存绑定程序产生任何影响。在这种情况下展开是否有好处?也有可能没有显着差异,而且我的样本量不够大。
我发现这个博客说,对于中等大小的数组,手动展开数组副本速度更快,而对于较长的数组,流式传输速度最快。他们的 AvxAsyncPFCopier 和 AvxAsyncPFUnrollCopier 函数似乎受益于使用整个缓存行以及手动展开。博客中的基准测试及其来源在这里。
#include <iostream>
#include <immintrin.h>
int main() {
// example of manually unrolling float arrays
size_t bytes = sizeof(__m256) * 10;
size_t alignment = sizeof(__m256);
// 10 x 32-byte vectors
__m256* a = (__m256*) _mm_malloc(bytes, alignment);
__m256* b = …Run Code Online (Sandbox Code Playgroud) 我想写一个程序来获取我的缓存大小(L1,L2,L3).我知道它的一般想法.
所以我写了一个小程序.这是我的代码:
#include <cstdio>
#include <time.h>
#include <sys/mman.h>
const int KB = 1024;
const int MB = 1024 * KB;
const int data_size = 32 * MB;
const int repeats = 64 * MB;
const int steps = 8 * MB;
const int times = 8;
long long clock_time() {
struct timespec tp;
clock_gettime(CLOCK_REALTIME, &tp);
return (long long)(tp.tv_nsec + (long long)tp.tv_sec * 1000000000ll);
}
int main() {
// allocate memory and lock
void* map = mmap(NULL, (size_t)data_size, PROT_READ …Run Code Online (Sandbox Code Playgroud) Lisp列表是否始终作为链接列表实现?
就处理器缓存而言,这是一个问题吗?如果是这样,是否有解决方案使用更多连续的结构来帮助缓存?
当从连续的内存位置执行一系列_mm_stream_load_si128()调用(MOVNTDQA)时,硬件预取器是否仍会启动,或者我应该使用显式软件预取(使用NTA提示)以获得预取的好处,同时仍然避免缓存污染?
我问这个的原因是因为他们的目标似乎与我相矛盾.流加载将获取绕过缓存的数据,而预取器尝试主动将数据提取到缓存中.
当顺序迭代一个大型数据结构(处理过的数据不会在很长一段时间内被修饰)时,我有必要避免污染chache层次结构,但我不想因频繁出现频繁的~100次循环处罚-fetcher闲置.
目标架构是Intel SandyBridge
在英特尔的优化指南 2.1.3节中,他们列出了Skylake中高速缓存和内存子系统的一些增强功能(强调我的):
Skylake微体系结构的缓存层次结构具有以下增强功能:
- 与前几代相比,缓存带宽更高.
- 通过扩大的缓冲区同时处理更多的装载和存储.
- 与Haswell微体系结构和前几代产品相比,处理器可以并行执行两次页面遍历.
- 页面拆分负载惩罚从上一代的100个周期下降到5个周期.
- L3写入带宽从上一代的4个周期增加到每行2个.
- 支持CLFLUSHOPT指令,使用SFENCE清除ca che line并管理刷新数据的内存排序.
- 降低了指定NULL指针的软件预取的性能损失.
- L2关联性从8种方式变为4种方式.
最后一个引起了我的注意.以什么方式减少增强方式的数量?就其本身而言,似乎更少的方式比更多方式更严重.当然,我认为可能存在有效的工程原因,为什么减少方式可以作为实现其他增强功能的权衡,但在这里,它本身定位为增强功能.
我错过了什么?
我想测量我的 C++ 代码某些部分的 L1、L2 和 L3 缓存命中/未命中率。我对在我的整个应用程序中使用 Perf 不感兴趣。Perf 可以用作 C++ 中的库吗?
int main() {
...
...
start_profiling()
// The part I'm interested in
...
end_profiling()
...
...
}
Run Code Online (Sandbox Code Playgroud)
我给了英特尔 PCM 一个机会,但我遇到了两个问题。首先,它给了我一些奇怪的数字。其次,它不支持 L1 缓存分析。
如果 Perf 无法实现,那么获取该信息的最简单方法是什么?
我正在尝试为英特尔的clwb指令找到不会使缓存行无效的配置或内存访问模式。我正在使用 NVDIMM 对 Intel Xeon Gold 5218 处理器进行测试。Linux 版本是 5.4.0-3-amd64。我尝试使用 Device?DAX 模式并直接将此字符设备映射到地址空间。我还尝试将此非易失性内存添加为新的 NUMA 节点,并使用numactl --membind命令将内存绑定到它。在这两种情况下,当我使用clwb缓存地址时,它都会被驱逐。我正在观察 PAPI 硬件计数器的驱逐,并禁用预取器。
这是我正在测试的一个简单循环。array 和 tmp 变量,都被声明为 volatile,所以加载是真正执行的。
for(int i=0; i < arr_size; i++){
tmp = array[i];
_mm_clwb(& array[i]);
_mm_mfence();
tmp = array[i];
}
Run Code Online (Sandbox Code Playgroud)
两次读取都会导致缓存未命中。
我想知道是否还有其他人试图检测是否有某种配置或内存访问模式会在缓存中留下缓存行?
我正在尝试准确描述 Linux 上当前 CPU 的数据缓存层次结构:不仅是单个 L1/L2/L3(可能还有 L4)数据缓存的大小,还有它们拆分或共享的方式核心。
例如,在我的 CPU(AMD Ryzen Threadripper 3970X)上,每个内核都有自己的 32 KB 的 L1 数据缓存和 512 KB 的 L2 缓存,但是 L3 缓存在一个核心复合体 (CCX) 内的内核之间共享。换句话说,有 8 个不同的 L3 缓存,每个 16 MB。
Windows 上 CPU-Z 的此屏幕截图的“缓存”部分基本上是我试图找出的内容:
我在 Windows 上使用GetLogicalProcessorInformation().
但是,在 Linux 上,它似乎sysconf()只给我 L1 和 L2 数据缓存的每核缓存大小(_SC_LEVEL1_DCACHE_SIZE和_SC_LEVEL2_DCACHE_SIZE),或总的 L3 缓存大小(_SC_LEVEL3_CACHE_SIZE)。
编辑: lstopo在 VMWare 下的输出。虚拟机有 8 个内核。L1 和 L2 缓存信息很好,但 L3 缓存大小似乎不正确:
cpu-cache ×10
c++ ×4
x86 ×3
c ×2
intel ×2
linux ×2
performance ×2
caching ×1
cpu ×1
ios ×1
linked-list ×1
lisp ×1
objective-c ×1
opencl ×1
perf ×1
prefetch ×1
simd ×1
sse ×1
visual-c++ ×1