clflush指令是否仅从级别1缓存中清除块



我有一个多核系统,有4个核,每个核都有专用的L1和L2缓存以及共享的LLC。缓存具有包容性,这意味着高级缓存是低级缓存的超级集合。我可以直接在LLC上刷新区块吗?还是必须先通过较低级别?

我正在努力理解flush+reload和flush+flush缓存侧通道攻击。

clflush在体系结构上是必需的/保证从所有级别的缓存中逐出该行,这对于将数据提交到非易失性DIMM非常有用。(例如,电池支持的DRAM或3D XPoint)。

手册中的措辞似乎很清楚:

从缓存一致性域中的每个级别的缓存层次结构中无效。。。如果该缓存行在缓存层次结构的任何级别包含修改后的数据,则该数据被写回内存

我认为,如果多个核心有一条处于共享状态的线,那么一个核心上的clflush/clflushopt必须将其从所有核心的专用缓存中逐出。(无论如何,这都会作为从包含的三级缓存中驱逐的一部分发生,但Skylake-X更改为NINE(非包含而非独占)三级缓存。)

我可以直接刷新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),在我的机器(3990X)上,刷新缓存总是低于5ns:

#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以下的单个缓存线。

最新更新