[英]Does clflush instruction flush block only from Level 1 Cache?
我有一个多核系统,其中有 4 个内核,每个内核都有私有的 L1 和 L2 缓存以及共享的 LLC。 缓存具有包容性,这意味着高级缓存是低级缓存的超集。 我可以直接刷新 LLC 上的块还是必须先通过较低级别?
我试图了解刷新+重新加载和刷新+刷新缓存侧通道攻击。
clflush
体系结构上要求/保证clflush
从所有级别的缓存中逐出该行,使其对于将数据提交到非易失性DIMM非常有用。 (例如,电池供电的DRAM或3D XPoint)。
手册中的措词似乎很清楚:
在高速缓存一致性域中的高速缓存层次结构的每个级别都无效...如果该高速缓存行在高速缓存层次结构的任何级别上包含已修改的数据,则该数据将写回到内存中
我认为,如果多个内核在共享状态下有一行, clflush
一个内核上的clflush
/ clflushopt
必须从所有内核的专用缓存中驱逐它。 (无论如何,这都是退出包含的L3缓存的一部分,但Skylake-X更改为NINE(非包含的非专有)L3缓存。)
我可以直接冲洗LLC上的块,还是必须先通过较低级别的块?
不清楚你在问什么。 您是否在问是否可以要求CPU 仅从 L3刷新块,而不打扰L1 / L2? 您已经知道L3在大多数Intel CPU上都包含在内,因此最终效果将与clflush
相同。 为了使内核与L3进行通信,它们必须经过自己的L1d和L2。
如果数据仅存在于L3中,而不存在于执行该数据的内核的私有L1d或L2中,则clflush
仍然有效。 它不是预取之类的“提示”,也不是仅限于本地的东西。
在将来的Silvermont系列CPU中,将有一条cldemote
指令,使您可以将块刷新到 LLC,但不能一直刷新到DRAM。 (而且,这只是一个提示,因此,如果写回路径忙于驱逐以腾出空间来容纳需求负载,它不会强制CPU遵循它。)
CLFLUSH 总是从每个缓存级别中逐出,这不可能是真的。 我刚刚写了一个小程序(C++17),在我的机器上刷新缓存线总是低于 5ns(3990X):
#include <iostream>
#include <chrono>
#include <cstring>
#include <vector>
#include <charconv>
#include <sstream>
#include <cmath>
#if defined(_MSC_VER)
#include <intrin.h>
#elif defined(__GNUC__)
#include <x86intrin.h>
#endif
using namespace std;
using namespace chrono;
size_t parseSize( char const *str );
string blockSizeStr( size_t blockSize );
int main( int argc, char **argv )
{
static size_t const DEFAULT_MAX_BLOCK_SIZE = (size_t)512 * 1024;
size_t blockSize = argc < 2 ? DEFAULT_MAX_BLOCK_SIZE : parseSize( argv[1] );
if( blockSize == -1 )
return EXIT_FAILURE;
blockSize = blockSize >= 4096 ? blockSize : 4096;
vector<char> block( blockSize );
size_t size = 4096;
static size_t const ITERATIONS_64K = 100;
do
{
uint64_t avg = 0;
size = size <= blockSize ? size : blockSize;
size_t iterations = (size_t)((double)0x10000 / size * ITERATIONS_64K + 0.5);
iterations += (size_t)!iterations;
for( size_t it = 0; it != iterations; ++it )
{
// make cachlines to get modified for sure by
// modifying to a differnt value each iteration
for( size_t i = 0; i != size; ++i )
block[i] = (i + it) % 0x100;
auto start = high_resolution_clock::now();
for( char *p = &*block.begin(), *end = p + size; p < end; p += 64 )
_mm_clflush( p );
avg += duration_cast<nanoseconds>( high_resolution_clock::now() - start ).count();
}
double nsPerCl = ((double)(int64_t)avg / iterations) / (double)(ptrdiff_t)(size / 64);
cout << blockSizeStr( size ) << " " << nsPerCl << "ns" << endl;
} while( (size *= 2) <= blockSize );
}
size_t parseSize( char const *str )
{
double dSize;
from_chars_result fcr = from_chars( str, str + strlen( str ), dSize, chars_format::general );
if( fcr.ec != errc() )
return -1;
if( !*(str = fcr.ptr) || str[1] )
return -1;
static const
struct suffix_t
{
char suffix;
size_t mult;
} suffixes[]
{
{ 'k', 1024 },
{ 'm', (size_t)1024 * 1024 },
{ 'g', (size_t)1024 * 1024 * 1024 }
};
char cSuf = tolower( *str );
for( suffix_t const &suf : suffixes )
if( suf.suffix == cSuf )
{
dSize = trunc( dSize * (ptrdiff_t)suf.mult );
if( dSize < 1.0 || dSize >= (double)numeric_limits<ptrdiff_t>::max() )
return -1;
return (ptrdiff_t)dSize;
}
return -1;
}
string blockSizeStr( size_t blockSize )
{
ostringstream oss;
double dSize = (double)(ptrdiff_t)blockSize;
if( dSize < 1024.0 )
oss << blockSize;
else if( dSize < 1024.0 * 1024.0 )
oss << dSize / 1024.0 << "kB";
else if( blockSize < (size_t)1024 * 1024 * 1024 )
oss << dSize / (1024.0 * 1024.0) << "MB";
else
oss << (double)blockSize / (1024.0 * 1024.0 * 1024.0) << "GB";
return oss.str();
}
没有任何 DDR 内存可以处理低于 5ns 的单个缓存行的刷新。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.