簡體   English   中英

更快地訪問c ++數組中的隨機元素

[英]faster access to random elements in c++ array

如果事先知道訪問模式,最快的訪問數組中隨機(非順序)元素的方法是什么? 對於每個步驟,訪問都是針對不同需求的隨機操作,因此重新排列元素是昂貴的選擇。 下面的代碼代表了整個應用程序的重要示例。

#include <iostream>
#include "chrono"
#include <cstdlib>

#define NN 1000000

struct Astr{
    double x[3], v[3];
    int i, j, k;
    long rank, p, q, r;
};


int main ()
{
    struct Astr *key;
    key = new Astr[NN];
    int ii, *sequence;
    sequence = new int[NN]; // access pattern is stored here
    float frac ;

    // create array of structs
    // create array for random numbers between 0 to NN to access 'key'
    for(int i=0; i < NN; i++){
        key[i].x[1] = static_cast<double>(i);
        key[i].p = static_cast<long>(i);
        frac = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
        sequence[i] = static_cast<int>(frac  * static_cast<float>(NN));
    }

    // part to check and improve
    // =========================================Random=======================================================
    std::chrono::high_resolution_clock::time_point TstartMain = std::chrono::high_resolution_clock::now();
    double tmp;
    long rnk;

    for(int j=0; j < 1000; j++)
    for(int i=0; i < NN; i++){
        ii = sequence[i];
        tmp = key[ii].x[1];
        rnk = key[ii].p;
        key[ii].x[1] = tmp * 1.01;
        key[ii].p = rnk * 1.01;
    }


    std::chrono::high_resolution_clock::time_point TendMain = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>( TendMain - TstartMain );
    double time_uni = static_cast<double>(duration.count()) / 1000000;

    std::cout << "\n Random array access " << time_uni << "s \n" ;

    // ==========================================Sequential======================================================
    TstartMain = std::chrono::high_resolution_clock::now();

    for(int j=0; j < 1000; j++)
    for(int i=0; i < NN; i++){
        tmp = key[i].x[1];
        rnk = key[i].p;
        key[i].x[1] = tmp * 1.01;
        key[i].p = rnk * 1.01;
    }

    TendMain = std::chrono::high_resolution_clock::now();
    duration = std::chrono::duration_cast<std::chrono::microseconds>( TendMain - TstartMain );
    time_uni = static_cast<double>(duration.count()) / 1000000;

    std::cout << " Sequential array access " << time_uni << "s \n" ;
    // ================================================================================================
    delete [] key;
    delete [] sequence;
}

正如預期的那樣,順序訪問更快。 答案就在我的機器上

Random array access 21.3763s 
Sequential array access 8.7755s 

主要的問題是隨機訪問是否可以更快地進行。 代碼改進可以針對容器本身(例如列表/向量而不是數組)。 是否可以實施軟件預取?

理論上講 ,可以幫助引導預取器加快隨機訪問的速度(嗯,在支持預取器的CPU上-例如,用於Intel / AMD的_mm_prefetch)。 但是實際上,這通常是完全浪費時間,並且通常會減慢代碼速度。

一般的理論是,在使用該值之前,您需要傳遞一個指向_mm_prefetch內在函數的指針,該指針要進行一兩次或兩次循環迭代。 但是,這有問題:

  • 很可能,你會最終調整的代碼你的 CPU。 在其他平台上運行相同的代碼時,您可能會發現不同的CPU緩存布局/大小意味着您的預取優化實際上正在降低性能。
  • 額外的預取指令最終將占用更多的指令緩存,並且很可能還會占用uop緩存。 您可能會發現僅此一項會降低代碼速度。
  • 假設CPU實際上關注_mm_prefetch指令。 這只是一個提示,因此沒有保證,CPU會尊重它。

如果要加快隨機內存訪問的速度,則有比預取imho更好的方法。

  • 減小數據的大小(即使用short / float16s代替int / float,根除結構中任何錯誤的填充等) 通過減小結構的大小,您可以減少讀取的內存,因此速度更快! (簡單的壓縮方案也不是壞主意!)
  • 對數據進行排序,以便您不進行隨機訪問,而是按順序處理數據。

除了這兩個選項之外,最好的選擇是讓預取保持良好狀態,並且編譯器通過您的隨機訪問來完成它(唯一的例外:您正在為〜2001 Pentium 4優化代碼,在該程序中基本上需要預取)

舉一個@robthebloke所說的例子,以下代碼使我的機器提升了約15%的性能:

#include <immintrin.h>

void do_it(struct Astr *key, const int *sequence)  {
    for(int i = 0; i < NN-8; ++i) {
        _mm_prefetch(key + sequence[i+8], _MM_HINT_NTA);
        struct Astr *ki = key+sequence[i];
        ki->x[1] *= 1.01;
        ki->p *= 1.01;
    }
    for(int i = NN-8; i < NN; ++i) {
        struct Astr *ki = key+sequence[i];
        ki->x[1] *= 1.01;
        ki->p *= 1.01;
    }
}

暫無
暫無

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

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