为什么在堆上分配内存比在栈上分配更快?

19

就我所知,涉及资源管理时,在堆上分配内存(使用 new 运算符)通常比在栈上(自动存储)分配要慢,因为栈是基于 LIFO 结构的,因此需要做最少的记录,下一个可用地址的指针很容易确定。

到目前为止,一切都好。现在看看以下代码:

/* ...includes... */

using std::cout;
using std::cin;
using std::endl;

int bar() { return 42; }

int main()
{
    auto s1 = std::chrono::steady_clock::now();
    std::packaged_task<int()> pt1(bar);
    auto e1 = std::chrono::steady_clock::now();

    auto s2 = std::chrono::steady_clock::now();
    auto sh_ptr1 = std::make_shared<std::packaged_task<int()> >(bar);
    auto e2 = std::chrono::steady_clock::now();

    auto first = std::chrono::duration_cast<std::chrono::nanoseconds>(e1-s1);
    auto second = std::chrono::duration_cast<std::chrono::nanoseconds>(e2-s2);

    cout << "Regular: " << first.count() << endl
         << "Make shared: " << second.count() << endl;

    pt1();
    (*sh_ptr1)();

    cout << "As you can see, both are working correctly: " 
         << pt1.get_future().get() << " & " 
         << sh_ptr1->get_future().get() << endl;

    return 0;
}

结果似乎与上面解释的内容相矛盾:

常规:6131

使共享:843

正如您所看到的,两者都在正常工作: 42&amp; 42

程序以退出代码0结束

在第二次测量中,除了调用运算符 new 之外, std :: shared_ptr auto sh_ptr1 )的构造函数也必须完成。我似乎无法理解为什么这比常规分配更快。

这是什么原因呢?


5
你尝试过交换操作顺序吗? - Petr
3个回答

29
问题在于对 std::packaged_task 构造函数的第一次调用负责初始化大量的线程本地状态,然后这些状态被不公平地归因于 pt1。这是基准测试(特别是微基准测试)的常见问题,可以通过预热来缓解;请参阅如何编写正确的Java微基准测试? 如果我复制您的代码但首先运行两部分,则结果相同,只限于系统时钟分辨率的限制。这证明了微基准测试的另一个问题,即应该多次运行小型测试以允许准确测量总时间。
经过预热并运行每个部分1000次,我得到以下结果(示例):
Regular: 132.986
Make shared: 211.889

这个差异(大约80ns)与经验法则相符,即malloc每次调用需要100ns


8

您的微基准测试存在问题:如果您交换测量计时顺序,则会得到相反的结果(演示)。

看起来第一次调用std::packaged_task构造函数会带来很大的开销。添加一个未计时的

std::packaged_task<int()> ignore(bar);

在测量时间之前解决这个问题(演示):

常规: 505
共享: 937


5

我已经在ideone上尝试了你的示例,并得到了类似于你的结果:

Regular: 67950 
Make shared: 696

然后我颠倒了测试的顺序:

auto s2 = std::chrono::steady_clock::now();
auto sh_ptr1 = std::make_shared<std::packaged_task<int()> >(bar);
auto e2 = std::chrono::steady_clock::now();

auto s1 = std::chrono::steady_clock::now();
std::packaged_task<int()> pt1(bar);
auto e1 = std::chrono::steady_clock::now();

并得到了相反的结果:

Regular: 548
Make shared: 68065

所以这不是堆栈与堆的区别,而是第一次和第二次调用的区别。也许你需要深入了解std::packaged_task的内部机制。

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