测量VS2013上vector <unique_ptr>的性能?</unique_ptr>

时间:2014-01-16 20:56:19

标签: c++ performance visual-c++ boost-ptr-container

TL; DR VS2013的优化器是否混淆或者我的测量是错误的还是全局虚拟实际上需要是易失性才能使测试有效或____?

免责声明:这主要是出于“学术”兴趣,我不认为我看到的差异会影响任何生产代码。


简介:我最近的一些测量结果导致我this question,因为我发现VS2013上的std::vector<std::unique_ptr<T> >boost::ptr_vector之间存在显着差异。 (另见评论there

对于我的特定测试用例,似乎访问boost :: ptr_vector中的元素比使用unique_ptr的向量快50%!

我的测试代码在这里:http://coliru.stacked-crooked.com/a/27dc2f1b91380cca(我将不再在此问题中包含它,我将在下面添加代码段)

  • gcc 4.8没有报告任何差异,所以这是VS2013的事情。

    Start...
    The timings are as follows for accessing all (1000000) elements 200 times:
    * St6vectorISt10unique_ptrIjSt14default_deleteIjEESaIS3_EE: 1764 ms
    * N5boost10ptr_vectorIjNS_20heap_clone_allocatorESaIPvEEE: 1781 ms
    Dummy output: 500000
    
  • 我完全连接的测试代码的时间是:

    Start...
    The timings are as follows for accessing all (1.000.000) elements 200 times:
    * class std::vector<....>: 344 ms
    * class boost::ptr_vector<unsigned int,....>: 216 ms
    Dummy output: 500.000
    

测试循环看起来像这样,我还会在那里保留冗长的评论,这解释了我看到的内容:

template<typename C>
void RunContainerAccess(C& c) {
    for (size_t i = 0; i != loop_count; ++i) {
        for (auto& e : c) {
            // This is relevant: 
            // If the if-condition is present, VC++2013 will show 
            // approx. the same runtime for both cases. However,
            // if the only line in this loop is assigning the element
            // to the pGlobalDummy, *then* ptr_vector will run approx. 50%
            // faster than the unique_vector version!
            //
            // g++-4.8 does not show this behaviour
            //
            // Note: VS2013 commmand line: (release; /O2; no whole prg opt)
            //   /GS /analyze- /W3 /Gy /Zc:wchar_t /Zi /Gm- /O2 /sdl /Fd"Release\vc120.pdb" /fp:precise /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_LIB" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /Gd /Oy- /Oi /MD /Fa"Release\" /EHsc /nologo /Fo"Release\" /Fp"Release\simple.pch" 
            //
            // Note: http://coliru.stacked-crooked.com/ command line:
            //   g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

            // if (pGlobalDummy)
                pGlobalDummy = PtrShim(e);
        }
    }
}

如果循环中的 only 行正在访问该元素(将ptr放入一个全局虚拟对象),那么看起来VS2013优化器会做一些奇怪的事情。当if (pGlobalDummy)存在时,两种情况都是相同的。

任何人都可以对此有所了解吗?

感谢霍华德的回答,我确实发现将volatile添加到全局虚拟对象中会有所不同,即当全局模糊不稳定时如此:

extern MyType* volatile pGlobalDummy;
MyType* volatile pGlobalDummy = nullptr;

循环运行速度稍慢,但运行完全相同。 这里的volatile是否会产生影响?也就是说,测试是否在没有volatile的情况下有效?

1 个答案:

答案 0 :(得分:6)

我在测试中发现了一个错误,它允许优化器以不同且不可预测的方式进行优化。我不确定这会影响您的结果。但它肯定会影响我的。

我正在使用tip-of-trunk clang + libc ++ -O3。

当我未经修改地运行您的代码时,我得到:

Start...
The timings are as follows for accessing all (1,000,000) elements 200 times:
* NSt3__16vectorINS_10unique_ptrIjNS_14default_deleteIjEEEENS_9allocatorIS4_EEEE: 0 ms
* N5boost10ptr_vectorIjNS_20heap_clone_allocatorENSt3__19allocatorIPvEEEE: 0 ms
Dummy output: 500,000

我将输出单位更改为纳秒并得到:

Start...
The timings are as follows for accessing all (1,000,000) elements 200 times:
* NSt3__16vectorINS_10unique_ptrIjNS_14default_deleteIjEEEENS_9allocatorIS4_EEEE: 32 ns
* N5boost10ptr_vectorIjNS_20heap_clone_allocatorENSt3__19allocatorIPvEEEE: 32 ns
Dummy output: 500,000

可疑,我在这里插入了volatile:

extern MyType* <ins>volatile</ins> pGlobalDummy;
MyType* <ins>volatile</ins> pGlobalDummy = nullptr;

但没有变化。

然后我注意到time[2]没有被初始化,所以我:

chron::nanoseconds time[2]<ins> = {}</ins>;

那就做到了。现在将单位设置回毫秒我得到:

Start...
The timings are as follows for accessing all (1,000,000) elements 200 times:
* NSt3__16vectorINS_10unique_ptrIjNS_14default_deleteIjEEEENS_9allocatorIS4_EEEE: 394 ms
* N5boost10ptr_vectorIjNS_20heap_clone_allocatorENSt3__19allocatorIPvEEEE: 406 ms
Dummy output: 500,000

所以我很好奇,如果你明确归零time[2],你可能需要:

chron::nanoseconds time[2] = {chron::nanoseconds(0), chron::nanoseconds(0)};

这会影响您看到的结果吗?

<强>澄清

std::chrono::duration默认构造函数指定为:

constexpr duration() = default;

如果客户端未指定 list-initialization ,则默认初始化 duration的{​​{1}},例如:

rep

chrono::nanoseconds ns; // default-initialized 是算术类型时,不执行初始化([dcl.init] / p7 / b3)。

如果客户列表初始化,例如:

rep

然后chrono::nanoseconds ns{}; // list-initialized 值初始化([dcl.init.list] / p3 / b7),对于算术类型, value-initialization 是与零初始化([dcl.init] / p8 / b4)相同。

完整的工作示例:

rep

对我来说,当用-O0编译时,我得到:

#include <iostream>
#include <chrono>

int
main()
{
    std::chrono::nanoseconds n1;
    std::chrono::nanoseconds n2{};
    std::chrono::nanoseconds n3 = {};
    std::cout << "n1 = " << n1.count() << "ns\n";
    std::cout << "n2 = " << n2.count() << "ns\n";
    std::cout << "n3 = " << n3.count() << "ns\n";
}

但是用-O3编译同样的东西,这会改为:

n1 = 0ns
n2 = 0ns
n3 = 0ns