C ++堆内存性能提升

时间:2011-04-16 01:57:33

标签: c++ memory-management stack heap

我正在编写一个需要大量堆内存的函数。是否有可能告诉编译器在特定的for循环中频繁访问这些数据,以便提高性能(通过编译选项或类似方法)?

我无法使用堆栈的原因是我需要存储的元素数量很大,如果我尝试这样做,我会遇到分段错误。

现在代码正在运行,但我认为它可能会更快。

更新: 我正在做这样的事情

vector< set<uint> > vec(node_vec.size());
for(uint i = 0; i < node_vec.size(); i++)
  for(uint j = i+1; j < node_vec.size(); j++)
    // some computation, basic math, store the result in variable x
      if( x > threshold ) {
         vec[i].insert(j);
         vec[j].insert(i);
      }

一些细节:
- 我使用hash_set,几乎没有改进,除了hash_set在我用于模拟目的的所有机器中都不可用的事实 - 我尝试使用数组在堆栈上分配vec,但正如我所说,如果元素数量太大,我可能会出现分段错误

如果node_vec.size()等于k,其中k是几千的量级,我希望vec比node_vec大4或5倍。考虑到我必须多次运行它,这个数量级的代码看起来很慢。当然,我使用多线程来并行化这些调用,但我不能让函数本身运行得比我现在看到的快得多。

例如,是否有可能在高速缓冲存储器中分配vec以进行快速数据检索,或类似的东西?

5 个答案:

答案 0 :(得分:3)

  

我正在编写一个需要大量堆内存的函数...将在特定的for循环中频繁访问

这不是你可以在编译器级别真正优化的东西。我认为你担心的是你有很多内存可能是“陈旧的”(被分页出来)但是在特定的时间点你需要迭代所有内存,可能需要多次,你不需要内存要分页到磁盘的页面。

您需要调查特定于平台的策略以提高性能。使用mlockallVirtualLock可以将页面保留在内存中,但实际上您不需要这样做。但是,请确保您知道将应用程序的内存页锁定到RAM中的含义。你正在从其他进程中占用内存。

您可能还想调查low fragmentation heap(但可能与此问题无关)和this page,它们描述了与for循环相关的缓存行。

后一页是关于CPU如何工作的细节(通常不应该关注的细节)与内存访问有关。

  

示例1:内存访问和性能   与循环1相比,您期望循环2运行多快多少?

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

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

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

第一个循环将数组中的每个值乘以3,第二个循环仅每16个乘以一次。 第二个循环仅执行第一个循环<6>的工作,但在现代机器上,两个for循环大约需要相同的时间:80和78 ms分别在我的机器上。

答案 1 :(得分:1)

<强>更新

vector< set<uint> > vec(node_vec.size());
for(uint i = 0; i < node_vec.size(); i++)
  for(uint j = i+1; j < node_vec.size(); j++)
    // some computation, basic math, store the result in variable x
      if( x > threshold ) {
         vec[i].insert(j);
         vec[j].insert(i);
      }

这仍然没有显示太多,因为我们无法知道条件x > threshold的频率是多少。如果x > threshold非常频繁,那么std::set可能是瓶颈,因为它必须为您插入的每个uint进行动态内存分配。

此外,我们不知道“某些计算”实际意味着什么/做什么/是什么。如果它做得太多,或者以错误的方式做到可能成为瓶颈。

我们不知道您需要如何访问结果。

无论如何,预感:

    vector<pair<int, int> > vec1;
    vector<pair<int, int> > vec2;

    for (uint i = 0; i < node_vec.size(); i++)
    {
        for (uint j = i+1; j < node_vec.size(); j++)
        {
            // some computation, basic math, store the result in variable x
            if (x > threshold)
            {
                vec1.push_back(make_pair(i, j));
                vec2.push_back(make_pair(j, i));
            }
        }
    }

如果您可以在该表单中使用结果,那么您就完成了。否则你可以做一些后期处理。只是不要再将它复制到std::set(显然)。尽量坚持std::vector<POD>。例如。你可以在这样的矢量中建立一个索引:

    // ...
    vector<int> index1 = build_index(node_vec.size(), vec1);
    vector<int> index2 = build_index(node_vec.size(), vec2);
    // ...
}    

vector<int> build_index(size_t count, vector<pair<int, int> > const& vec)
{
    vector<int> index(count, -1);

    size_t i = vec.size();
    do
    {
        i--;
        assert(vec[i].first >= 0);
        assert(vec[i].first < count);
        index[vec[i].first] = i;
    }
    while (i != 0);

    return index;
}

ps。:我几乎可以肯定你的循环是而不是内存限制。虽然不能确定......如果你没有向我们展示的“节点”真的很大,它可能仍然存在。


原始答案:

没有简单的I_will_access_this_frequently_so_make_it_fast(void* ptr, size_t len)种解决方案。

你可以做一些事情。

  1. 确保编译器可以“看到”在关键循环内调用的每个函数的实现。编译器能够“看到”实现所需的内容取决于编译器。有一种方法可以确定:在循环之前在同一个翻译单元中定义所有相关函数,并将它们声明为inline

    这也意味着您应该以任何方式在这些关键循环中调用“外部”函数。而“外部”函数则指的是系统调用,运行时库内容或DLL / SO中实现的内容。也不要调用虚函数,也不要使用函数指针。或者当然不分配或释放内存(在关键循环内)。

  2. 确保使用最佳算法。如果算法的复杂性高于必要性,则线性优化没有实际意义。

  3. 使用尽可能小的类型。例如。如果int能够完成工作,请不要使用signed char。这是我通常不会推荐的,但是当处理大量内存时,它可以提高性能。特别是在非常紧凑的循环中。

  4. 如果您只是复制或填充内存,请使用memcpymemset如果块大于50到100个字节,则禁用这两个函数的内在版本。

  5. 确保以缓存友好的方式访问数据。最佳是“流式传输” - 即以升序或降序地址访问存储器。你可以一次“跳跃”一些字节,但不要跳得太远。最糟糕的是随机访问大块内存。例如。如果你必须处理二维矩阵(如位图图像),其中p [0]到p [1]是“向右”的步骤(x + 1),请确保内部循环增加x和外部递增y。如果你这样做,那么性能的另一方面就会更糟糕

  6. 如果你的指针是别名的,你可以告诉编译器(如何完成取决于编译器)。如果您不知道什么是无别名的意思,我建议您搜索网络和编译器的文档,因为解释超出了范围。

  7. 如果合适,请使用固有的SIMD说明。

  8. 如果您知道在不久的将来需要哪些内存位置,请使用显式预取指令。

答案 2 :(得分:0)

使用编译器选项无法做到这一点。根据您的使用情况(插入,随机访问,删除,排序等),您可能会获得更适合的容器。

答案 3 :(得分:0)

编译器已经可以看到在循环中频繁访问数据。

答案 4 :(得分:0)

假设您在循环之前只分配堆中的数据,请注意,作为@lvella,内存是内存,如果经常访问它,则应该在执行期间有效地缓存它。