C++缓存感知编程。

64

在C ++中是否有一种方法可以确定CPU的缓存大小?我有一个处理大量数据的算法,我想将这些数据分成适合缓存的块。这可能吗? 您能给我关于编写具有缓存大小意识(特别是关于多线程/多核数据处理方面)的程序的其他提示吗?

谢谢!


我的阻塞算法实验表明它会干扰GCC的优化。最佳块大小并不总是L1缓存大小。我建议使用不同的块大小进行测试。 - Dr. Jekyll
10个回答

17
根据 Ulrich Drepper 的 "程序员应该了解的内存知识",在Linux上你可以执行以下操作:

Once we have a formula for the memory requirement we can compare it with the cache size. As mentioned before, the cache might be shared with multiple other cores. Currently {There definitely will sometime soon be a better way!} the only way to get correct information without hardcoding knowledge is through the /sys filesystem. In Table 5.2 we have seen the what the kernel publishes about the hardware. A program has to find the directory:

/sys/devices/system/cpu/cpu*/cache
这在第6节:程序员可以做什么中列出。
他还在图6.5下面描述了一个简短的测试,用于确定L1D缓存大小,如果无法从操作系统中获取,则可以使用该测试。
在他的论文中,我还发现了另一件事:在Linux上,sysconf(_SC_LEVEL2_CACHE_SIZE)是一个系统调用,应该返回L2缓存的大小,尽管它似乎没有很好的文档记录。

12

C++本身不涉及CPU缓存,因此语言本身没有支持查询缓存大小的功能。如果你正在开发Windows平台下的应用程序,可以使用GetLogicalProcessorInformation()函数来查询有关CPU缓存的信息。


1
当然,C++关注CPU缓存。不关心这一点意味着在热路径中比你本来可以的速度慢100-1000倍。而且语言本身没有内置对此的支持,因为第一,并非所有系统都支持它;第二,了解缓存大小通常是完全无关紧要的,除非您也完全控制内存分配策略。C++提供了很多功能,但不足以扮演“高速缓存大师”的角色。 - thesaint
如果有什么问题,你的C/C++编译器会关心缓存。你可以在GCC上使用“-march”标志来让它针对某些特定的CPU进行优化。 还有这个:http://en.cppreference.com/w/cpp/atomic/memory_order - snowflake

9

预先分配一个大数组。然后按顺序访问每个元素并记录每次访问的时间。理想情况下,当缓存未命中时,访问时间会有所增加。然后您可以计算出您的L1缓存。这可能不起作用,但值得一试。


4

读取CPU(x86)的CPUID,然后通过查找表确定缓存大小。该表必须填充CPU制造商在其编程手册中公布的缓存大小。


2
嘿,那听起来很有趣!有没有类似的预组合表可以在线获取? - Mat
http://www.x86-guide.com/en/index.html可能会有这样的表格。然而,问题在于如何处理未识别的 CPU,以及是否希望每次推出新的 CPU 时都更新程序? - Robert S. Barnes
1
如果你的程序在发布后使用了新的CPU,那么这个解决方案是否会失效? - Billy ONeal
@Billy ONeal:不一定。CPUID具有“子标识”功能,可用于查询支持查询的每个CPU上的缓存大小/关联性/行大小,请参见http://en.wikipedia.org/wiki/CPUID#EAX.3D80000006h:_Extended_L2_Cache_Features。 - FrankH.
@FrankH:Tobias的答案说要使用查找表,而不是使用CPUID的子识别功能。 - Billy ONeal

4

根据您所需的操作,您也可以将其留给一些库。由于您提到多核处理,您可能需要查看Intel Threading Building Blocks

TBB包括缓存感知内存分配器。更具体地说,请检查cache_aligned_allocator(在参考文档中,我找不到任何直接链接)。


4
您可以查看此线程:http://software.intel.com/en-us/forums/topic/296674 简短的答案在另一个线程中:
在现代IA-32硬件上,缓存行大小为64。128的值是Intel Netburst微架构(例如Intel Pentium D)的遗留问题,其中64字节的行成对地组合成128字节的扇区。当获取扇区中的一行时,硬件会自动获取扇区中的另一行。因此,从虚假共享的角度来看,在Netburst处理器上,有效的行大小为128字节。(http://software.intel.com/en-us/forums/topic/292721

4

有趣的是,我曾经写过一个程序来完成这个任务(用的是C语言,但我相信很容易将其融入C ++代码中)。

http://github.com/wowus/CacheLineDetection/blob/master/Cache%20Line%20Detection/cache.c

get_cache_line函数是有趣的函数,它返回数组访问时间数据中最大峰值之前的位置。在我的机器上它猜对了!如果需要,它可以帮助你编写自己的代码。
它基于这篇文章,最初引起了我的兴趣:http://igoro.com/archive/gallery-of-processor-cache-effects/

2
有两种需要区分的情况。您需要在编译时还是运行时知道缓存大小?
在编译时确定缓存大小
对于一些应用程序,您知道代码将在哪个精确的架构上运行,例如,如果您可以直接在主机上编译代码。在这种情况下,简化查找大小并硬编码是一个选择(可以在构建系统中自动化)。在今天的大多数计算机上,L1缓存行应为64字节。
如果您想避免这种复杂性或者需要支持在未知架构上进行编译,您可以使用C++17功能 std :: hardware_constructive_interference_size 作为一个好的后备。它将提供缓存行的编译时估计值,但 请注意其局限性。请注意,编译器无法完美地猜测创建二进制文件时的缓存行大小,因为通常情况下它是与架构相关的。
在运行时确定缓存大小
在运行时,您知道确切的机器,但需要使用特定于平台的代码从操作系统中读取信息。一个很好的起点是来自this answer的代码片段,它支持主要平台(Windows、Linux、MacOS)。类似地,您也可以在运行时读取L2缓存大小。
我建议不要尝试通过在启动时运行基准测试并测量哪个表现最佳来猜测缓存行。虽然可能有效,但如果CPU被其他进程使用,则也容易出错。
结合两种方法
如果您必须发布一个二进制文件,并且将在后续运行的机器具有各种不同架构和不同缓存大小,则可以为每个缓存大小创建专门的代码部分,然后在应用程序启动时动态选择最合适的代码部分。

1
据我所知,GCC有一个__builtin_prefetch提示。

http://gcc.gnu.org/onlinedocs/gcc-3.3.6/gcc/Other-Builtins.html

这方面,它有出色的部分。基本上,它建议:

__builtin_prefetch (&array[i + LookAhead], rw, locality);

其中rw是0(准备读取)或1(准备写入)的值,而locality使用数字0-3,其中零表示没有局部性,3表示非常强的局部性。

两者都是可选的。 LookAhead将是要向前查看的元素数。 如果内存访问需要100个周期,并且展开的循环相隔两个周期,则LookAhead可以设置为50或51。


-1

缓存通常会做正确的事情。对于普通程序员唯一真正担心的是虚假共享,而你无法在运行时处理它,因为它需要编译器指令。


4
未解答问题。 - JBentley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接