如何在Linux中使用C程序刷新CPU缓存?

12

我正在编写一个 C 语言程序,需要清空内存。请问是否有任何 UNIX 系统命令可以用于清空 CPU 缓存。

这是我的项目要求,因为它涉及到计算逻辑所需的时间。

我已经阅读了 cacheflush(char *s, int a, int b) 函数的相关内容,但我不确定它是否适用以及参数该如何传递。


5
为什么你需要清空缓存? - Oliver Charlesworth
2
这是我项目的要求,涉及计算我的逻辑所需的时间,因此我需要清除缓存。 - Rose BEck
1
问:你所说的“内存缓存”究竟是什么意思:http://tldp.org/LDP/tlk/mm/memory.html - paulsm4
9
这真的很奇怪。我想在过去的24小时里至少有3个问题非常类似。考虑到这本来就是一个非常奇怪的问题,我想知道是否有人(也许是老师/教授)要求学生完成这个奇怪的作业?这是作业吗? - phonetagger
可能是如何在Linux中刷新地址空间的CPU缓存区域?的重复问题。 - Ciro Santilli OurBigBook.com
显示剩余5条评论
5个回答

11
  1. 我理解你的意思是“CPU缓存”,而不是内存缓存。

  2. 上面的链接很好:建议“通过CPU写入大量数据”不是特定于Windows。

  3. 以下是同一主题的另一种变体:

  4. 这是一篇关于Linux和CPU缓存的文章:

注意:

在这个(非常,非常低的)级别上,“Linux” != “Unix”。


1
感谢您的帮助。这是一个非常有用的社区,有很多乐于助人的人...您能否请解释一下如何在我的C程序中使用echo 3 > /proc/sys/vm/drop_caches,并提供一个小的示例代码。 - Rose BEck
*nix(通常指Unix和Linux)的美妙之处之一在于您可以将“所有东西都视为文件”。具体而言:fopen("/proc/sys/vm/drop_caches", "w"); fprintf(fp, "3"); fclose(fp);。PS:如果这是作业,请在问题中使用“作业”标签。 - paulsm4
我正在使用fopen("proc/sys/vm.drop_caches", "w")...但是我该如何验证我的缓存是否已经清除了呢?@paulsm4 感谢您的善良和早先的帮助。希望您这次也能帮忙。 :) - Rose BEck

6
这是英特尔建议清除缓存的方法:

mem_flush(const void *p, unsigned int allocation_size){
    const size_t cache_line = 64;
    const char *cp = (const char *)p;
    size_t i = 0;

    if (p == NULL || allocation_size <= 0)
            return;

    for (i = 0; i < allocation_size; i += cache_line) {
            asm volatile("clflush (%0)\n\t"
                         : 
                         : "r"(&cp[i])
                         : "memory");
    }

    asm volatile("sfence\n\t"
                 :
                 :
                 : "memory");
}

2
你的源代码是什么? - horro
请问您能提供源代码吗? - Ammar Faizi
我偶然发现了这个。根据X86优化手册(https://software.intel.com/content/www/us/en/develop/download/intel-64-and-ia-32-architectures-optimization-reference-manual.html)的说明,顺序应该是:首先使用sfence进行缓存一致性,然后对缓冲区中的每个缓存行使用clflush。 - dturvene

5
如果您正在编写一个用户模式(而不是内核模式)程序,并且它是单线程的,那么您根本没有理由去打扰您的缓存。您的用户模式程序可以忘记它的存在;它只是为了加速您的程序执行,操作系统通过处理器的MMU来管理它。
我能想到只有几个原因,您可能确实希望从用户模式应用程序中刷新缓存:
1. 您的应用程序旨在在对称多处理器系统上运行,或者与外部硬件进行数据交易。 2. 您只是测试缓存以进行某种性能测试(在这种情况下,您可能真的应该编写测试以在内核模式下操作,例如作为驱动程序)。
无论如何,假设您正在使用Linux...
#include <asm/cachectl.h>

int cacheflush(char *addr, int nbytes, int cache);

假设您有一块刚写入的内存,您希望确保它从缓存刷新到主内存中。该块开始于addr,nbytes长,并且在两个缓存(或两者皆有)中:

   ICACHE Flush the instruction cache.
   DCACHE Write back to memory and invalidate the affected valid cache lines.
   BCACHE Same as (ICACHE|DCACHE).

通常只需要清除DCACHE,因为当您写入数据到“内存”(即缓存)时,它通常是数据而不是指令。
如果您想出于某种奇怪的测试原因刷新“所有缓存”,则可以malloc()一个大块,该块比您的CPU缓存更大(甚至是8倍),将任意旧垃圾写入其中,然后只需清除整个块即可。
另请参见:如何在C++中执行缓存操作?

谢谢你的帮助。我该如何在Ubuntu/Unix/Linux中知道我的CPU缓存大小,因为我想清除整个缓存。 - Rose BEck
请参见http://superuser.com/questions/48505/how-to-find-virtual-memory-size-and-cache-size-of-a-linux-system。另请参阅我的第二个答案,我认为这可能更符合您的要求......我想说“更符合您的需求”,但我认为您为了性能测试而清除缓存的目标是错误的。我认为更有意义的性能测试方式是在将代码加载到缓存后测试其性能,通过运行一次将其放入缓存中,然后使用计时器测试它大约10,000次,并将时间除以10,000。 - phonetagger
错误: asm/cachectl.h:没有那个文件或目录。在Linux上使用gnu-gcc编译器会抛出此错误。有什么办法可以解决它...使其接受cachectl.h作为头文件。 - Rose BEck
在x86架构上不支持cacheflush()系统调用。 - undefined

2

好的,抱歉我的第一次回答有误。我后来读了你问题下面的跟进评论,所以我现在明白你想要清除指令缓存来启动程序(或其中的部分),这样当你测试它的性能时,你也测试了它最初从主内存加载到指令缓存中的加载时间。你是否还需要将代码将使用的任何数据刷新到主内存中,以便数据和代码都是新的加载?

首先,我想提到的是,主内存本身也是一种缓存,你的硬盘(无论是磁盘上的程序还是磁盘上的交换空间)是程序指令可能来自的最低、最慢的地方。话虽如此,当你第一次运行一个例程时,如果它还没有通过已经执行的其他代码从磁盘加载到主内存中,那么它的CPU指令将首先从磁盘加载。这需要比从主内存加载到缓存中多一个数量级或更长的时间。然后,一旦它加载到主内存中,从主内存加载到缓存中需要大约一个数量级的时间,而从缓存加载到CPU的指令获取器中则需要更少的时间。因此,如果您想测试代码的冷启动性能,您必须决定什么是冷启动......从磁盘中取出它,还是从主内存中取出它。我不知道是否有任何命令可以将指令/数据“清除”到交换空间之外的主内存中,因此将其刷新到主内存中就是您能做的(据我所知),但请记住,即使您刷新了指令缓存,您的测试结果可能仍然与第一次运行不同(当它可能从磁盘上获取)到后续运行。

那么,如何清除指令缓存以确保自己的代码被刷新到主内存中呢?

如果我需要这样做(在我看来非常奇怪),我可能会先找到我的函数在内存中的长度和大致位置。由于我使用的是Linux,我会发出命令“objdump -d {myprogram} > myprogram.dump.txt”,然后我会在编辑器中打开myprogram.dump.txt并搜索我想要清除的函数,并通过使用十六进制计算器从它们的起始地址减去它们的结束地址来确定它们有多长。我会把每个函数的大小写下来。稍后,我会在我的代码中添加cacheflush()调用,将它们作为'addr'传递给它想要清除的每个函数的地址,将我找到的长度作为'nbytes',并将ICACHE作为参数。为了安全起见,我可能会略微增加一些大小,大约10%,以防我对代码进行一些微调并忘记调整nbytes。我会为我想要清除的每个函数调用cacheflush()。然后,如果我需要清除数据,如果它正在使用全局/静态数据,我也可以清除它们(DCACHE),但如果它是堆栈或堆数据,则没有真正的现实方法可以(或应该)将其从缓存中清除。试图这样做将是一个愚蠢的练习,因为它会创造一个在正常执行中几乎不会存在的条件。假设您正在使用Linux...
#include <asm/cachectl.h>

int cacheflush(char *addr, int nbytes, int cache);

...where cache is one of:
   ICACHE Flush the instruction cache.
   DCACHE Write back to memory and invalidate the affected valid cache lines.
   BCACHE Same as (ICACHE|DCACHE).

顺便问一下,这是为课堂作业而准备的吗?

@paulsm4 - Linux的man页面上没有提到这个。嗯,继续往下看man页面...(我假设你正在使用Linux,对吗?如果是的话,你尝试过使用“man cacheflush”命令了吗?)...在底部附近,我在我的版本的man页面中看到两件令人担忧的事情...“BUGS-当前实现忽略addr和nbytes参数。因此,整个缓存总是被刷新。”并且更进一步...“NOTE-此系统调用仅适用于基于MIPS的系统。它不应该在旨在可移植的程序中使用。” - phonetagger
1
@paulsm4 错误:asm/cachectl.h:没有那个文件或目录。在Linux上使用gnu-gcc编译器会抛出此错误。有什么办法可以解决它...使其接受cachectl.h作为头文件。 - Rose BEck

0
通常情况下,用户空间程序没有必要刷新缓存。自修改代码(SMC)是一个特殊情况。 如果您正在寻找一种在修改内存中的代码后同步icache(指令缓存)的方法,有两种“可移植”(不完全准确)的方法:
1. GCC内置函数__builtin___clear_cache()。某些目标(例如ARM)要求使用SMC使icache无效(并与dcache同步)。对于不需要icache无效的其他目标(例如x86),__builtin___clear_cache()函数为空函数。
  void __builtin___clear_cache(void *begin, void *end)

Linux的系统调用cacheflush()。这个系统调用只提供给mips、arc、csky、m68k、nios2、parisc、sh、avr32和frv平台使用。X86和ARM不提供这个系统调用。所以你可能想要使用上面提到的GCC内置函数。
  int cacheflush(void addr, int nbytes, int cache)

顺便说一下,__builtin___clear_cache() 最终会在 aarch64 的 libgcc 中被展开为对 __aarch64_sync_cache_range() 的函数调用。您可以在下面查看 GCC 代码以了解其实现方式。

https://github.com/gcc-mirror/gcc/blob/master/libgcc/config/aarch64/sync-cache.c#L31

对于Clang,它在这里:

https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/builtins/clear_cache.c#L123


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