簡體   English   中英

如何使用 c++ 代碼驗證 CPU 緩存行大小?

[英]How to verify CPU cache line size with c++ code?

我從Igor 的博客中閱讀了一篇文章。 文章說:

...今天的 CPU 不能逐字節訪問 memory。 相反,它們以(通常)64 字節的塊(稱為高速緩存行)獲取 memory。 當您讀取特定的 memory 位置時,整個緩存行將從主 memory 提取到緩存中。 而且,從同一緩存行訪問其他值很便宜!

文章還提供了c#代碼來驗證上述結論:

int[] arr = new int[64 * 1024 * 1024];

// Loop 1 (step = 1)
for (int i = 0; i < arr.Length; i++) arr[i] *= 3;

// Loop 2 (step = 16)
for (int i = 0; i < arr.Length; i += 16) arr[i] *= 3;

兩個 for 循環的時間大致相同:在 Igor 的機器上分別為 80 和 78 ms,因此驗證了緩存行機制。

然后我參考上面的想法實現了一個c++版本來驗證緩存行大小如下:

#include "stdafx.h"
#include <iostream>
#include <chrono>
#include <math.h>
using namespace std::chrono;

const int total_buff_count = 16;
const int buff_size = 32 * 1024 * 1024;

int testCacheHit(int * pBuffer, int size, int step)
{
    int result = 0;
    for (int i = 0; i < size;) {
        result += pBuffer[i];
        i += step;
    }

    return result;
}

int main()
{
    int * pBuffer = new int[buff_size*total_buff_count];

    for (int i = 0; i < total_buff_count; ++i) {
        int step = (int)pow(2, i);

        auto start = std::chrono::system_clock::now();
        volatile int result = testCacheHit(pBuffer + buff_size*i, buff_size, step);
        auto end = std::chrono::system_clock::now();

        std::chrono::duration<double> elapsed_seconds = end - start;
        std::cout << "step: " << step << ", elapsed time: " << elapsed_seconds.count() * 1000 << "ms\n";
    }

    delete[] pBuffer;
}

但我的測試結果與 Igor 的文章完全不同。 如果步長為1,則時間成本約為114ms; 如果步長為 16,則時間成本約為 78ms。 測試應用是用release配置構建的,我的機器上有32GB的memory,CPU是intel Xeon E5 2420 v2 2.2G; 結果如下。 C++ 測試結果

有趣的發現是,當 step 為 2 和 step 為 2048 時,時間成本顯着降低。我的問題是,如何解釋我的測試中 step 為 2 和 step 為 2048 時的差距? 為什么我的結果與 Igor 的結果完全不同? 謝謝。

我自己對第一個問題的解釋是,代碼的時間成本包含兩部分:一個是“內存讀/寫”,其中包含 memory 讀/寫時間成本,另一個是“其他成本”,其中包含 for 循環和計算成本。 如果 step 是 2,那么“內存讀/寫”成本幾乎沒有變化(因為緩存行),但是計算和循環成本減少了一半,所以我們看到了明顯的差距。 而且我猜我的 CPU 上的緩存線是 4096 字節(1024 * 4 字節)而不是 64 字節,這就是為什么我們在步長為 2048 時出現另一個差距的原因。但這只是我的猜測。 感謝您的幫助,謝謝。

介於 1024 和 2048 之間

請注意,您使用的是未初始化的數組 這基本上意味着

int * pBuffer = new int[buff_size*total_buff_count];

不會導致您的程序實際要求任何物理 memory。 相反,只保留了一些虛擬地址空間。

然后,如果您首先觸摸某個數組元素,則會觸發頁面錯誤,並且操作系統會將頁面映射到物理 memory。 這是一個相對較慢的操作,可能會顯着影響您的實驗。 由於您系統上的頁面大小可能為4 kB ,它可以容納1024 個 4 字節整數 當您 go 執行2048 step時,實際上只有每秒一次的頁面被訪問,並且運行時間按比例下降。

您可以通過提前“觸摸”memory 來避免這種機制的負面影響:

int * pBuffer = new int[buff_size*total_buff_count]{};

當我嘗試這樣做時,我在 64 到 8192 步長之間幾乎線性減少了時間。

介於 1 和 2 之間

您系統上的緩存行大小絕對不是 2048 字節,它很可能是 64 字節(通常,它可能具有不同的值,甚至對於不同的緩存級別可能具有不同的值)。

至於第一部分,對於step為 1,只涉及更多的算術運算(數組元素的添加和i的增量)。

與伊戈爾實驗的區別

我們只能推測為什么伊戈爾的實驗在兩種情況下給出的時間幾乎相同。 我猜算術的運行時間在那里可以忽略不計,因為只涉及一個循環計數器增量並且他寫入數組,這需要將緩存行額外傳輸回 memory (我們可以說字節/操作比率遠高於您的實驗。)

如何使用 c++ 代碼驗證 CPU 緩存行大小?

C++17中有std::hardware_destructive_interference_size破壞性_interference_size,它應該提供最小的緩存行大小。 請注意,它是一個編譯時間值,編譯器依賴於您對目標機器的輸入。 當針對整個架構時,數字可能不准確。

如何使用 c++ 代碼驗證 CPU 緩存行大小?

你確實不能。

你應該編寫可移植的 C++ 代碼。 閱讀n3337

假設您沒有在 C++ 編譯器中啟用編譯器優化 想象一下,你在一些模擬器(比如這些)中運行你的 C++ 編譯器。

特別是在 Linux 上,您可以解析/proc/cpuinfo偽文件並從中獲取 CPU 緩存行大小。

例如:

% head -20 /proc/cpuinfo
processor   : 0
vendor_id   : AuthenticAMD
cpu family  : 23
model       : 8
model name  : AMD Ryzen Threadripper 2970WX 24-Core Processor
stepping    : 2
microcode   : 0x800820b
cpu MHz     : 1776.031
cache size  : 512 KB
physical id : 0
siblings    : 48
core id     : 0
cpu cores   : 24
apicid      : 0
initial apicid  : 0
fpu     : yes
fpu_exception   : yes
cpuid level : 13
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl nonstop_tsc cpuid extd_apicid amd_dcm aperfmperf pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb hw_pstate sme ssbd sev ibpb vmmcall fsgsbase bmi1 avx2 smep bmi2 rdseed adx smap clflushopt sha_ni xsaveopt xsavec xgetbv1 xsaves clzero irperf xsaveerptr arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif overflow_recov succor smca

順便說一句,有許多不同的組織和緩存級別。

You could imagine a C++ application on Linux parsing the output of /proc/cpuinfo then making HTTP requests (using libcurl ) to the Web to get more from it.

另請參閱答案。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM