寻求有关阵列数组内存性能的知识

时间:2015-12-06 21:46:47

标签: c++ arrays performance

背景:多声道实时数字音频处理。

访问模式:“Column-major”,如下所示:

for (int sample = 0; sample < size; ++sample)
{
    for (int channel = 0; channel < size; ++channel)
    {
        auto data = arr[channel][sample];
        // do some computations
    }
}

我正在寻求关于如何让CPU和内存生活更轻松的建议。我意识到交错数据会更好,但这是不可能的。

我的理论是,只要您按顺序访问内存一段时间,CPU就会预取它 - 这是否适用于N(通道)缓冲区?那么缓冲区的大小,任何“断点”呢?

将通道置于连续内存(增加位置)中是否非常有用,或者仅适用于非常小的缓冲区(例如,缓存行的大小)?我们可以谈论缓冲区&gt;相距100 kb。

我想计算部分的时间也会使内存优化的时间可以忽略不计 -

这是一个案例,手动预取是否有意义?

我可以测试/配置我自己的系统,但我只有 - 1系统。因此,我所做的任何设计选择都只会对该特定系统产生积极影响任何有关这些事项的知识都受到赞赏,链接,文献等,平台特定的知识。

如果问题太模糊,请告诉我,我主要认为在这个领域有一些wiki-ish经验/信息会很好。

修改

我创建了一个程序,用于测试我提到的三个案例(在所谓的增加的性能顺序中提到的远程,adjecant和连续),它们在小型和大型数据集上测试这些模式。也许人们会运行它并报告异常情况。

#include <iostream>
#include <chrono>
#include <algorithm>

const int b = 196000;
const int s = 64 / sizeof(float);
const int extra_it = 16;
float sbuf1[s];
float bbuf1[b];

int main()
{

    float sbuf2[s];
    float bbuf2[b];
    float * sbuf3 = new float[s];
    float * bbuf3 = new float[b];
    float * sbuf4 = new float[s * 3];
    float * bbuf4 = new float[b * 3];
    float use = 0;

    while (1)
    {
        using namespace std;

        int c;
        bool sorb;

        cout << "small or big test (0/1)? ";
        if (!(cin >> sorb))
            return -1;

        cout << endl << "test distant buffers (0), contiguous access (1) or adjecant access (2)? ";

        if (!(cin >> c))
            return -1;


        auto t = std::chrono::high_resolution_clock::now();

        if (c == 0)
        {
            // "worst case scenario", 3 distant buffers constantly touched
            if (sorb)
            {
                for (int k = 0; k < b * extra_it; ++k)
                    for (int i = 0; i < s; ++i)
                    {
                        sbuf1[i] = k; // static memory
                        sbuf2[i] = k; // stack memory
                        sbuf3[i] = k; // heap memory
                    }
            }
            else
            {
                for (int k = 0; k < s * extra_it; ++k)
                    for (int i = 0; i < b; ++i)
                    {
                        bbuf1[i] = k; // static memory
                        bbuf2[i] = k; // stack memory
                        bbuf3[i] = k; // heap memory
                    }
            }

        }
        else if (c == 1)
        {
            // "best case scenario", only contiguous memory touched, interleaved
            if (sorb)
            {
                for (int k = 0; k < b * extra_it; ++k)
                    for (int i = 0; i < s * 3; i += 3)
                    {
                        sbuf4[i] = k;
                        sbuf4[i + 1] = k;
                        sbuf4[i + 2] = k;
                    }
            }
            else
            {
                for (int k = 0; k < s * extra_it; ++k)
                    for (int i = 0; i < b * 3; i += 3)
                    {
                        bbuf4[i] = k;
                        bbuf4[i + 1] = k;
                        bbuf4[i + 2] = k;
                    }
            }

        }
        else if (c == 2)
        {
            // "compromise", adjecant memory buffers touched
            if (sorb)
            {
                auto b1 = sbuf4;
                auto b2 = sbuf4 + s;
                auto b3 = sbuf4 + s * 2;

                for (int k = 0; k < b * extra_it; ++k)
                    for (int i = 0; i < s; ++i)
                    {
                        b1[i] = k;
                        b2[i] = k;
                        b3[i] = k;
                    }
            }
            else
            {
                auto b1 = bbuf4;
                auto b2 = bbuf4 + b;
                auto b3 = bbuf4 + b * 2;

                for (int k = 0; k < s * extra_it; ++k)
                    for (int i = 0; i < b; ++i)
                    {
                        b1[i] = k;
                        b2[i] = k;
                        b3[i] = k;
                    }
            }

        }
        else
            break;


        cout << chrono::duration_cast<chrono::milliseconds>(chrono::high_resolution_clock::now() - t).count() << " ms" << endl;

        // basically just touching the buffers, avoiding clever optimizations
        use += std::accumulate(sbuf1, sbuf1 + s, 0);
        use += std::accumulate(sbuf2, sbuf2 + s, 0);
        use += std::accumulate(sbuf3, sbuf3 + s, 0);
        use += std::accumulate(sbuf4, sbuf4 + s * 3, 0);

        use -= std::accumulate(bbuf1, bbuf1 + b, 0);
        use -= std::accumulate(bbuf2, bbuf2 + b, 0);
        use -= std::accumulate(bbuf3, bbuf3 + b, 0);
        use -= std::accumulate(bbuf4, bbuf4 + b * 3, 0);


    }

    std::cout << use;

    std::cin.get();
}

令我惊讶的是,在我的英特尔i7-3740qm上,远程缓冲区始终优于更适合本地的测试。然而,它很接近。

0 个答案:

没有答案