我有一个独立的 NVIDIA GPU(例如 Kepler 或 Maxwell)。我想在调度某些内核之前清除 L2 缓存,以免污染我的测试结果。
我可以做一些事情,比如分配一大块内存并按顺序读取远处的大量内存,这可能会起作用。但我宁愿做一些简单的事情......
笔记:
假设我正在存储一个对象的链接列表,每个对象都是一个大小为 64 字节的结构,这也是我的缓存行的大小。随着时间的推移,我将在链接列表上进行延迟敏感的添加、删除和迭代。据我所知,性能主要取决于对象是否保存在缓存中,因此 RAM 访问的访问量约为 1 纳秒,而不是 > 50 纳秒。
通常建议通过空间局部性来实现这一点,最好将对象存储在连续的内存块中。这很有用,因为每当我访问内存地址时,处理器实际上都会提取缓存行的数据;我们希望这些附加数据成为其他有用的对象,因此我们将所有对象放在一个连续的块中。
我可能会误解,但如果对象大小 >= 缓存行大小,我们似乎不会从这种效果中受益。一次只能将一个对象放入缓存中。
单线程内的代码具有一定的内存保证,例如先读后写(即将一些值写入内存位置,然后读回它应该给出您写入的值)。
如果线程被重新安排在不同的 CPU 核心上执行,这样的内存保证会发生什么?假设一个线程将 10 写入内存位置 X,然后重新调度到不同的核心。该核心的 L1 缓存可能具有不同的 X 值(与之前在该核心上执行的另一个线程不同),因此现在读取 X 不会像线程期望的那样返回 10。当线程被调度到不同的核心上时,是否会发生一些 L1 缓存同步?
multithreading operating-system cpu-architecture memory-barriers cpu-cache
我以为我了解L1D写未命中是如何处理的,但仔细想想却让我感到困惑。
这是一个汇编语言片段:
;rdi contains some valid 64-bytes aligned pointer
;rsi contains some data
mov [rdi], rsi
mov [rdi + 0x40], rsi
mov [rdi + 0x20], rsi
Run Code Online (Sandbox Code Playgroud)
假设[rdi]和[rdi + 0x40]行在 l1d 中不处于 Exclusive 或 Modified 状态。然后我可以想象以下动作序列:
mov [rdi], rsi 退休。mov [rdi], rsi尝试将数据写入 l1d。RFO 启动,数据放入 WC 缓冲区。mov [rdi + 0x40], rsi退休(mov [rdi], rsi已经退休,所以有可能)mov [rdi + 0x40], rsi 为连续的缓存行启动 RFO,数据被放入 WC 缓冲区。mov [rdi + 0x20], rsi退休(mov [rdi …假设我们有一个处理器,它有两个内核(C0 和 C1)和一个从k最初由 C0 拥有的地址开始的缓存线。如果 C1 在第 8 字节槽上发出一条存储指令k,是否会影响在 C1 上执行的后续指令的吞吐量?
intel优化手册有以下一段
当一条指令将数据写入内存位置 [...] 时,处理器会确保包含该内存位置的行位于其 L1d 缓存中 [...]。如果缓存线不存在,它会使用 RFO 请求 [...] RFO 从下一级获取数据,并在指令退出后存储数据。因此,存储延迟通常不会影响存储指令本身
参考以下代码,
// core c0
foo();
line(k)->at(i)->store(kConstant, std::memory_order_release);
bar();
baz();
Run Code Online (Sandbox Code Playgroud)
从英特尔手动使得报价我认为在上面的代码,代码的执行将看起来像是商店基本上是一个空操作,和结束之间会不会影响延迟foo()和开始bar()。相比之下,对于下面的代码,
// core c0
foo();
bar(line(k)->at(i)->load(std::memory_order_acquire));
baz();
Run Code Online (Sandbox Code Playgroud)
结束foo()和开始之间的延迟bar()会受到加载的影响,因为以下代码将加载结果作为依赖项。
这个问题主要与英特尔处理器(在 Broadwell 系列或更新版本中)如何在上述情况下工作有关。此外,特别是关于如何将类似于上述的 C++ 代码编译为这些处理器的汇编。
我假设简单的自旋锁不会进入操作系统等待这个问题的目的。
我发现简单的自旋锁通常使用lock xchgorlock bts代替 来实现lock cmpxchg。
cmpxchg但是如果期望不匹配,是否会避免写入该值?那么失败的尝试不是更便宜吗cmpxchg?
或者即使cmpxchg发生故障也会写入数据并使其他核心的缓存线无效?
这个问题类似于什么具体将 x86 缓存行标记为脏 - 任何写入,还是需要显式更改?,但它是特定的cmpxchg,而不是普遍的。
我们知道,就缓存命中时间而言,直接映射缓存优于集合关联缓存,因为不涉及特定标签的搜索。另一方面,组关联缓存通常比直接映射缓存具有更好的命中率。
我读到,现代处理器试图通过使用一种称为路径预测的技术来结合两者的优点。他们预测给定集合中最有可能发生命中的行,并仅在该行中进行搜索。如果尝试导致未命中,请在该组的所有缓存行中使用正常的组关联搜索。
我想了解这种路径预测是如何工作的。预测硬件/逻辑的延迟如何小于整套的搜索延迟?
caching processor cpu-architecture cpu-cache micro-architecture
8 路设置缓存关联性是否有我看不到的特殊优势?
附加的脚本针对不同大小的矩阵上不同数量的并行进程评估 numpy.conjugate 例程,并记录相应的运行时间。矩阵形状仅在第一维上有所不同(从 1,64,64 到 256,64,64)。共轭调用始终在 1,64,64 个子矩阵上进行,以确保正在处理的部分适合我系统上的 L2 缓存(每个核心 256 KB,在我的情况下 L3 缓存为 25MB)。运行脚本会生成下图(轴标签和颜色略有不同)。
正如您所看到的,从大约 100,64,64 的形状开始,运行时间取决于所使用的并行进程的数量。
这可能是什么原因造成的?
或者为什么 (100,64,64) 以下的矩阵对进程数的依赖性如此之低?
我的主要目标是找到对此脚本的修改,以便运行时尽可能独立于任意大小的矩阵“a”的进程数量。
如果有 20 个进程:
所有“a”矩阵最多占用:20 * 16 * 256 * 64 * 64 字节 = 320MB
所有“b”子矩阵最多占用:20 * 16 * 1 * 64 * 64 字节 = 1.25MB
因此,所有子矩阵同时适合 L3 缓存,并且单独适合 CPU 每个核心的 L2 缓存。我在这些测试中只使用了物理核心,没有使用超线程。
这是脚本:
from multiprocessing import Process, Queue
import time
import numpy as np
import os
from matplotlib import pyplot as plt
os.environ['OPENBLAS_NUM_THREADS'] = '1' …Run Code Online (Sandbox Code Playgroud) python numpy multiprocessing cpu-cache python-multiprocessing
我目前正在尝试编写一个 L1 缺失率尽可能高的程序。
为了测量 L1 缺失率,我在 Intel Core i7 处理器上使用 MEM_LOAD_RETIRED.L1_MISS 和 MEM_LOAD_RETIRED.L1_HIT 性能计数器事件(我对填充缓冲区命中不感兴趣)。我修改了 Linux 内核,以便在每次上下文切换时提供准确的测量结果,以便我可以准确地确定每个程序的命中和未命中次数。
硬件预取器被禁用。
这是我目前拥有的代码:
#define LINE_SIZE 64
#define CACHE_SIZE 4096 * 8
#define MEM_SIZE CACHE_SIZE * 64
void main(int argc, char* argv[])
{
volatile register char* addr asm ("r12") = mmap(0, MEM_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
volatile register unsigned long idx asm ("r13") = 0;
volatile register unsigned long store_val asm ("r14") = 0;
volatile register unsigned long x64 asm ("r15") = 88172645463325252ull;
while(1) …Run Code Online (Sandbox Code Playgroud)