编译器优化掉的内存分配

8
在他的演讲中“Efficiency with algorithms, Performance with data structures”,Chandler Carruth谈到了C++需要更好的分配器模型。当前的分配器模型侵入了类型系统,这使得在许多项目中几乎不可能工作。另一方面,Bloomberg分配器模型不会侵入类型系统,但是基于虚函数调用,这使得编译器无法“看到”分配并对其进行优化。在他的演讲中,他谈到了编译器去重内存分配(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的演讲中说编译器可以“去重”您的分配?

1
create函数是一个具有运行时副作用的函数,编译器无法在编译时知道这些运行时副作用的结果。它可以看到分配的内存实际上没有被任何地方使用,这可能是优化掉分配的原因,但我认为这是错误的,因为编译器无法在编译时预测结果。 - Some programmer dude
1
@Joachim:这个例子已经在这里讨论过了https://dev59.com/-YPba4cB1Zd3GeqPnhm9。根据我帖子中提到的n3664,标准并不清楚是否允许这样做。但是似乎很多编译器已经这样做了。 - InsideLoop
1个回答

2

我找到了一个很棒的例子,回答了最初问题的第一个点。第二个和第三个点目前还没有答案。

#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都无法完成此操作),且在构建稍微复杂一些的示例时,优化不会执行。


"在带有f_val的for循环中":您是指f_val内部的for循环,还是调用f_val的for循环? - TonyK
1
@TonyK:在调用f_val的for循环中。在区域1中,似乎只有一个分配(v)让我感到震惊。我的猜测是f_val调用被内联化,然后k循环被融合,编译器删除了w的分配,因此它变得无用!我已经检查了汇编代码。 - InsideLoop

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接