【问题标题】:Does clflush instruction flush block only from Level 1 Cache?clflush 指令是否仅从一级缓存中刷新块?
【发布时间】:2019-06-23 08:17:18
【问题描述】:

我有一个多核系统,有 4 个内核,每个内核都有私有 L1 和 L2 缓存以及共享 LLC。缓存具有包容性属性,这意味着高级缓存是低级缓存的超集。可以直接刷LLC上的块还是要先过低层?

我正在尝试了解刷新+重新加载和刷新+刷新缓存侧通道攻击。

【问题讨论】:

    标签: caching intel flush cpu-cache instructions


    【解决方案1】:

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

    The wording in the manual 似乎很清楚:

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

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

    可以直接刷LLC上的块还是要先通过低层?

    不清楚你在问什么。您是在问是否可以要求 CPU 仅从 L3 刷新块 而不会干扰 L1/L2?您已经知道 L3 在大多数 Intel CPU 上都具有包容性,因此最终效果与clflush 相同。核心要与 L3 对话,它们必须经过自己的 L1d 和 L2。

    clflush 如果数据仅存在于 L3 中而不存在于执行它的核心的私有 L1d 或 L2 中,则仍然有效。它不是像预取那样的“提示”,也不是本地唯一的东西。

    在未来的 Silvermont 系列 CPU 中,将有一个 cldemote 指令让您将一个块刷新到 LLC,但不能一直刷新到 DRAM。 (而且这只是一个提示,因此如果回写路径忙于驱逐为需求负载腾出空间,它不会强制 CPU 服从它。)

    【讨论】:

    • (另外cldemote 不必打扰其他 CPU 内核;如果它在其他内核的 L1d 中很热,它就会停留在那里。不像 clflushopt 必须占用中间带宽核心,甚至可能是套接字间互连,以确保它不会丢失副本。)
    【解决方案2】:

    CLFLUSH 不可能总是从每个缓存级别逐出。我刚刚写了一个小程序(C++17),在我的机器(3990X)上刷新 cachlines 总是低于 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 的单个缓存线。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-02-16
      • 1970-01-01
      • 2020-11-26
      • 2012-12-10
      • 2011-12-20
      • 2018-02-10
      • 2021-02-05
      相关资源
      最近更新 更多