【问题标题】:Memory allocation optimized away by compilers编译器优化掉的内存分配
【发布时间】:2015-05-02 22:34:24
【问题描述】:

在他的演讲"Efficiency with algorithms, Performance with data structures" 中,Chandler Carruth 谈到了在 C++ 中需要更好的分配器模型。当前的分配器模型侵入了类型系统,因此几乎不可能在许多项目中工作。另一方面,Bloomberg allocator model 没有侵入类型系统,而是基于虚函数调用,这使得编译器无法“看到”分配并对其进行优化。在他的演讲中,他谈到了编译器对内存分配进行重复数据删除 (1:06:47)。

我花了一些时间找到一些内存分配优化的例子,但我发现了这个在 clang 下编译的代码示例,优化了所有的内存分配,只返回 1000000 而不分配任何东西。

template<typename T>
T* create() { return new T(); }

int main() {
    auto result = 0;
    for (auto i = 0; i < 1000000; ++i) {
        result += (create<int>() != nullptr);
    }

    return result;
}

下面的论文http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3664.html 还说分配可以在编译器中融合,并且似乎表明一些编译器已经做了这种事情。

由于我对有效内存分配的策略非常感兴趣,我真的很想了解为什么 Chandler Carruth 在 Bloomberg 模型中反对虚拟调用。上面的例子清楚地表明,当它可以看到分配时,clang 会优化东西。

  1. 我想要一个“现实生活中的代码”,这种优化很有用,并且可以由任何当前的编译器完成
  2. 您有任何代码示例,其中不同的分配被当前编译器融合了吗?
  3. Chandler Carruth 在 1:06:47 的演讲中说编译器可以“删除”你的分配,你明白他的意思吗?

【问题讨论】:

  • create 函数是具有运行时副作用的函数,编译器不可能在编译时知道这些运行时副作用的结果。它 can 所做的是看到分配的内存并没有真正在任何地方使用,这可能是它可能优化分配的原因,但我认为这是错误的,因为编译器无法在编译时预测结果。
  • @Joachim:这个例子已经在这里讨论过stackoverflow.com/questions/25668420/…。根据我帖子中提到的n3664,该标准是否允许尚不清楚。但似乎很多编译器已经这样做了。

标签: c++ memory memory-management optimization allocation


【解决方案1】:

我发现了这个惊人的例子,它回答了最初问题的第一点。第 2 点和第 3 点都没有答案。

#include <iostream>
#include <vector>
#include <chrono>

std::vector<double> f_val(std::size_t i, std::size_t n) {
    auto v = std::vector<double>( n );
    for (std::size_t k = 0; k < v.size(); ++k) {
        v[k] = static_cast<double>(k + i);
    }
    return v;
}

void f_ref(std::size_t i, std::vector<double>& v) {
    for (std::size_t k = 0; k < v.size(); ++k) {
        v[k] = static_cast<double>(k + i);
    }
}

int main (int argc, char const *argv[]) {
    const auto n = std::size_t{10};
    const auto nb_loops = std::size_t{300000000};

    // Begin: Zone 1
    {
        auto v = std::vector<double>( n, 0.0 );
        auto start_time = std::chrono::high_resolution_clock::now();
        for (std::size_t i = 0; i < nb_loops; ++i) {
            auto w = f_val(i, n);
            for (std::size_t k = 0; k < v.size(); ++k) {
                v[k] += w[k];
            }
        }
        auto end_time = std::chrono::high_resolution_clock::now();
        auto time = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count();
        std::cout << time << std::endl;
        std::cout << v[0] << " " << v[n - 1] << std::endl;
    }
    // End: Zone 1

    {
        auto v = std::vector<double>( n, 0.0 );
        auto w = std::vector<double>( n );
        auto start_time = std::chrono::high_resolution_clock::now();
        for (std::size_t i = 0; i < nb_loops; ++i) {
            f_ref(i, w);
            for (std::size_t k = 0; k < v.size(); ++k) {
                v[k] += w[k];
            }
        }
        auto end_time = std::chrono::high_resolution_clock::now();
        auto time = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count();
        std::cout << time << std::endl;
        std::cout << v[0] << " " << v[n - 1] << std::endl;
    }

    return 0;
}

在带有 f_val 的 for 循环中没有发生单个内存分配。虽然这只发生在 Clang 中(Gcc 和 icpc 都失败了),并且在构建稍微复杂的示例时,优化没有完成。

【讨论】:

  • "in the for-loop with f_val": 你是指f_val里面的for循环,还是调用f_val的for循环?
  • @TonyK:在调用 f_val 的 for 循环中。在 1 区,似乎只有一个分配(用于 v)让我大吃一惊。我的猜测是 f_val 调用是内联的,然后融合了 k 循环,然后编译器删除了 w 的分配,这变得无用了!我已经检查了程序集。
猜你喜欢
  • 2011-08-03
  • 1970-01-01
  • 1970-01-01
  • 2018-08-22
  • 1970-01-01
  • 2018-12-30
  • 2018-04-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多