C++ std::shared_ptr与std::make_shared:意外的缓存未命中和分支预测

C++ std::shared_ptr与std::make_shared:意外的缓存未命中和分支预测,c++,caching,valgrind,branch-prediction,C++,Caching,Valgrind,Branch Prediction,我试图测量std::shared_ptr和std::make_shared创建的指针的效率 我有下一个测试代码: #include <iostream> #include <memory> #include <vector> struct TestClass { TestClass(int _i) : i(_i) {} int i = 1; }; void sum(const std::vector<std::shared_ptr&

我试图测量std::shared_ptr和std::make_shared创建的指针的效率

我有下一个测试代码:

#include <iostream>
#include <memory>
#include <vector>


struct TestClass {
    TestClass(int _i) : i(_i) {}
    int i = 1;
};

void sum(const std::vector<std::shared_ptr<TestClass>>& v) {
    unsigned long long s = 0u;
    for(size_t i = 0; i < v.size() - 1; ++i) {
        s += v[i]->i * v[i + 1]->i;
    }
    std::cout << s << '\n';
}

void test_shared_ptr(size_t n) {
    std::cout << __FUNCTION__ << "\n";
    std::vector<std::shared_ptr<TestClass>> v;
    v.reserve(n);
    for(size_t i = 0u; i < n; ++i) {
        v.push_back(std::shared_ptr<TestClass>(new TestClass(i)));
    }
    sum(v);
}

void test_make_shared(size_t n) {
    std::cout << __FUNCTION__ << "\n";
    std::vector<std::shared_ptr<TestClass>> v;
    v.reserve(n);
    for(size_t i = 0u; i < n; ++i) {
        v.push_back(std::make_shared<TestClass>(i));
    }
    sum(v);
}

int main(int argc, char *argv[]) {
    size_t n = (argc == 3 ) ? atoi(argv[2]) : 100;
    if(atoi(argv[1]) == 1) {
        test_shared_ptr(n);
    } else {
        test_make_shared(n);
    }
    return 0;
}
使用std::make_共享:

正如您可能看到的,当我使用std::make_shared时,缓存未命中率和分支预测失误率更高。 我希望std::make_shared更有效,因为存储对象和控制块都位于同一个内存块中。或者至少性能应该是相同的

我错过了什么

环境详情:

$ g++ --version
g++ (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
cachegrind不只是模拟,而不是测量吗?表示使用16384个2位饱和计数器阵列预测条件分支。它应该代表2004年的典型桌面/服务器

采用2位饱和计数器的简单分支预测在现代标准中是一个笑话,即使在2004年,对于高性能CPU也是过于简单的;根据研究,奔腾II/III有一个2级自适应局部/全局预测器,每个本地历史记录条目有4位。另见;Agner的MicroachPDF在一开始就有一章是关于分支预测的

英特尔因为哈斯韦尔使用IT-TAGE,现代AMD也使用先进的分支预测技术

如果在valgrind的模拟中有两个分支碰巧互相别名,导致运行频率较低的分支预测失误,我不会感到惊讶

您是否尝试过使用真正的硬件性能计数器?e、 g.在Linux上: perf stat-d./cache_misses.bin 2 100000应该为实际硬件提供更真实的图片,包括实际L1d未命中率和分支预测未命中率。性能事件(如分支和分支未命中)根据CPU微体系结构映射到某些特定的硬件计数器。性能列表将显示可用的计数器

我经常使用taskset-c3 perf stat-etask clock:u、上下文切换、cpu迁移、页面错误、周期:u、指令:u、分支:u、分支未命中:u、发出的uops。任意:u、uops_已执行。线程:u-r2./program_在我的Skylake cpu上进行测试

实际上,我通常会忽略分支未命中,因为我经常调整没有不可预测分支的SIMD循环,并且可以编程计算不同事件的硬件计数器数量有限。

cachegrind不只是模拟,而不是测量吗?表示使用16384个2位饱和计数器阵列预测条件分支。它应该代表2004年的典型桌面/服务器

采用2位饱和计数器的简单分支预测在现代标准中是一个笑话,即使在2004年,对于高性能CPU也是过于简单的;根据研究,奔腾II/III有一个2级自适应局部/全局预测器,每个本地历史记录条目有4位。另见;Agner的MicroachPDF在一开始就有一章是关于分支预测的

英特尔因为哈斯韦尔使用IT-TAGE,现代AMD也使用先进的分支预测技术

如果在valgrind的模拟中有两个分支碰巧互相别名,导致运行频率较低的分支预测失误,我不会感到惊讶

您是否尝试过使用真正的硬件性能计数器?e、 g.在Linux上: perf stat-d./cache_misses.bin 2 100000应该为实际硬件提供更真实的图片,包括实际L1d未命中率和分支预测未命中率。性能事件(如分支和分支未命中)根据CPU微体系结构映射到某些特定的硬件计数器。性能列表将显示可用的计数器

我经常使用taskset-c3 perf stat-etask clock:u、上下文切换、cpu迁移、页面错误、周期:u、指令:u、分支:u、分支未命中:u、发出的uops。任意:u、uops_已执行。线程:u-r2./program_在我的Skylake cpu上进行测试


实际上,我通常忽略分支未命中,因为我经常调整没有不可预测分支的SIMD循环,并且可以编程计数不同事件的硬件计数器数量有限。

如果多次运行测试,是否会得到一致的结果?这取决于实现。试着用其他编译器进行测试。@FrançoisAndrieux,是的,我不只是模拟,而不是测量?表示使用16384个2位饱和计数器阵列预测条件分支。它应该代表2004年的典型桌面/服务器。按照现代标准,这种过于简单的分支预测是一个笑话,即使在2004年,对于高性能x86也是过于简单的;看见Intel因为Haswell使用IT-TAGE。对于实际的性能测量,您可能想看看这个:如果您多次运行测试,是否会得到一致的结果?这取决于实现。试着用其他编译器进行测试。@FrançoisAndrieux,是的,我不只是模拟,而不是测量?表示使用16384个2位饱和计数器阵列预测条件分支。那就是苏
PPOS代表了2004年的典型桌面/服务器。按照现代标准,这种过于简单的分支预测是一个笑话,即使在2004年,对于高性能x86也是过于简单的;看见英特尔,因为Haswell使用IT-TAGE。对于真实的性能测量,您可能想看看以下内容:
valgrind --tool=cachegrind --branch-sim=yes ./cache_misses.bin 2 100000
==2014== Cachegrind, a cache and branch-prediction profiler
==2014== Copyright (C) 2002-2017, and GNU GPL'd, by Nicholas Nethercote et al.
==2014== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==2014== Command: ./cache_misses.bin 2 100000
==2014==
--2014-- warning: L3 cache found, using its data for the LL simulation.
--2014-- warning: specified LL cache: line_size 64  assoc 12  total_size 9,437,184
--2014-- warning: simulated LL cache: line_size 64  assoc 18  total_size 9,437,184
test_make_shared
18107093611968
==2014==
==2014== I   refs:      41,283,983
==2014== I1  misses:         1,805
==2014== LLi misses:         1,696
==2014== I1  miss rate:       0.00%
==2014== LLi miss rate:       0.00%
==2014==
==2014== D   refs:      14,997,474  (8,834,690 rd   + 6,162,784 wr)
==2014== D1  misses:       241,781  (  164,368 rd   +    77,413 wr)
==2014== LLd misses:        84,413  (    7,943 rd   +    76,470 wr)
==2014== D1  miss rate:        1.6% (      1.9%     +       1.3%  )
==2014== LLd miss rate:        0.6% (      0.1%     +       1.2%  )
==2014==
==2014== LL refs:          243,586  (  166,173 rd   +    77,413 wr)
==2014== LL misses:         86,109  (    9,639 rd   +    76,470 wr)
==2014== LL miss rate:         0.2% (      0.0%     +       1.2%  )
==2014==
==2014== Branches:       7,031,695  (6,426,222 cond +   605,473 ind)
==2014== Mispredicts:      216,010  (   15,442 cond +   200,568 ind)
==2014== Mispred rate:         3.1% (      0.2%     +      33.1%   )
$ g++ --version
g++ (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.