【问题标题】:std::make_unique causes big slowdown?std::make_unique 导致大幅减速?
【发布时间】:2018-09-23 21:42:21
【问题描述】:

我最近开始使用 C++14 而不是 C++11 对我的 C++ 代码库进行现代化改造。

在用 C++14 中的 std::make_unique 替换单个出现的 std::unique_ptr.reset(new ...) 后,我意识到我的测试套件(由大约 30 个 C++ 测试程序组成)运行速度慢了大约 50%。

旧的 C++11 代码(快速):

class Foo
{
  public:
    Foo(size_t size)
    {
        array.reset(new char[size]);
    }
  private:
    std::unique_ptr<char[]> array;
};

新的 C++14 代码(慢):

class Foo
{
  public:
    Foo(size_t size)
    {
        array = std::make_unique<char[]>(size);
    }
  private:
    std::unique_ptr<char[]> array;
};

使用带有std::make_unique 的 C++14 代码时,GCC 和 Clang 的运行速度要慢得多。当我使用 valgrind 测试两个版本时,它报告 C++11 和 C++14 代码使用相同数量的分配和相同数量的分配内存,并且没有内存泄漏。

当我查看上面生成的测试程序程序集时,我怀疑使用 std::make_unique 的 C++14 版本会在使用 memset 分配后重置内存。 C++11版本不这样做:

C++11 程序集(GCC 7.4、x64)

main:
sub rsp, 8
movsx rdi, edi
call operator new[](unsigned long)
mov rdi, rax
call operator delete[](void*)
xor eax, eax
add rsp, 8
ret

C++14 程序集(GCC 7.4、x64)

main:
push rbx
movsx rbx, edi
mov rdi, rbx
call operator new[](unsigned long)
mov rcx, rax
mov rax, rbx
sub rax, 1
js .L2
lea rax, [rbx-2]
mov edx, 1
mov rdi, rcx
cmp rax, -1
cmovge rdx, rbx
xor esi, esi
call memset
mov rcx, rax
.L2:
mov rdi, rcx
call operator delete[](void*)
xor eax, eax
pop rbx
ret

问题:

初始化内存是std::make_unique 的已知功能吗?如果不是,还有什么可以解释我遇到的性能下降?

【问题讨论】:

  • 这确实看起来像是“我不喜欢这个,我为什么要为此付出代价”的情况......
  • @Richard Compiler Explorer 即使使用 -O3 也会重现该问题。您的示例未显示此问题,因为您的大小已硬编码为 10。
  • std::make_unique 问题的编译器浏览器链接:godbolt.org/g/7URbfP

标签: c++ stl g++ c++14 clang++


【解决方案1】:

初始化内存是std::make_unique 的已知功能吗?

这取决于您所说的“已知”是什么意思。但是,是的,这就是您的案例之间的区别。来自cppreferencemake_unique&lt;T&gt;(size) 调用:

unique_ptr<T>(new typename std::remove_extent<T>::type[size]())
//                                                         ~~~~

就是这样specified

new char[size] 分配内存并默认初始化它。 new char[size]() 分配内存并对其进行值初始化,在char 的情况下为零初始化。默认情况下,标准库中的很多东西都会值初始化而不是默认初始化。

同样,make_unique&lt;T&gt;() 提供 new T() 而不是 new T...make_unique&lt;char&gt;() 给你 0,new char 给你一个不确定的值。

作为一个类似的例子,如果我想将 vector&lt;char&gt; 调整为给定大小的 未初始化 缓冲区(立即被其他东西填充),我必须使用我自己的分配器或使用不是 char 的类型,这与它的初始化有关。


C++20 将引入新的辅助函数来缓解这个问题,感谢P11020R1

  • make_unique_default_init
  • make_shared_default_init
  • allocate_shared_default_init

make_unique&lt;T&gt;() 会做new T(),而这些会做new T。也就是说,没有额外的归零。在 OP 的具体情况下,std::make_unique_default_init&lt;char[]&gt;(size) 就是你想要的。

【讨论】:

  • char 谎报自己的初始化是什么意思?
  • @rubenvb 例如struct my_char { my_char() { /* nothing */ } char c; };
  • @rubenvb:我想谎言只是像my_char() 这样的构造函数通常会让你相信它负责正确初始化所有数据成员。
  • @rubenvb 我没说是,Barry 说了。你肯定明白他为什么这么称呼它吗?我们通常不喜欢在未定义状态下构造对象。
  • @rubenvb • 这不是搞砸了,这是一个故意的谎言,以便在不需要时不支付初始化性能损失。
猜你喜欢
  • 2014-03-30
  • 2016-05-20
  • 1970-01-01
  • 1970-01-01
  • 2018-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-11
相关资源
最近更新 更多