C++ 更快地访问c+中的随机元素+;排列

C++ 更快地访问c+中的随机元素+;排列,c++,performance,memory-access,C++,Performance,Memory Access,如果事先知道访问模式,那么访问数组中随机(非顺序)元素的最快方式是什么?每一步的访问都是随机的,因此重新排列元素是一个昂贵的选择。下面的代码是整个应用程序的重要示例 #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;

如果事先知道访问模式,那么访问数组中随机(非顺序)元素的最快方式是什么?每一步的访问都是随机的,因此重新排列元素是一个昂贵的选择。下面的代码是整个应用程序的重要示例

#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;
}
主要问题是随机访问是否可以更快。 代码改进可以从容器本身(例如列表/向量而不是数组)方面进行。可以实现软件预取吗?

理论上,可以帮助引导预取器加速随机访问(好的,在支持它的CPU上-例如英特尔/AMD的预取)。然而,在实践中,这通常是完全浪费时间,而且往往会减慢代码的速度

一般的理论是,在使用该值之前,在一两次循环迭代中传递指向_mm_预取内在函数的指针。但是,这方面存在一些问题:

  • 很可能您最终会调整CPU的代码。在其他平台上运行相同的代码时,您可能会发现不同的CPU缓存布局/大小意味着您的预取优化现在实际上正在降低性能
  • 额外的预取指令最终将使用更多的指令缓存,最有可能的是uop缓存。您可能会发现,仅此一点就降低了代码的速度
  • 这假设CPU实际上关注_mm_预取指令。这只是一个提示,因此没有保证CPU会尊重它
如果您想加速随机内存访问,有比预取imho更好的方法

  • 减少数据的大小(例如,在int/float中使用shorts/float16s,消除结构中的任何错误填充,等等)。通过减小结构的大小,您可以读取更少的内存,因此它将运行得更快!(简单的压缩方案也不是个坏主意!)
  • 对数据进行排序,这样您就可以按顺序处理数据,而不是进行随机访问

除了这两个选项之外,最好的办法是不使用预取功能,而编译器会使用您的随机访问功能(唯一的例外是:您正在为~2001奔腾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;
    }
}
#包括
void do_it(结构Astr*键,常量int*序列){
对于(int i=0;ix[1]*=1.01;
ki->p*=1.01;
}
对于(int i=NN-8;ix[1]*=1.01;
ki->p*=1.01;
}
}

如果在存储之前就知道访问模式,那么您可以按照随机访问模式存储阵列,以便实际的内存访问是顺序的。访问模式是预先知道的。请澄清这到底意味着什么,以及它如何应用于发布的示例。@rishikshraje这只是一个示例代码。。由于许多原因,实际代码具有随机访问权限。在每一个步骤中以不同的方式排列元素会很昂贵。
#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;
    }
}