Насколько мне известно об управлении ресурсами, выделение чего-либо в кучу (оператор 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 & 42Программа завершилась с кодом выхода: 0
Во втором измерении, кроме вызова оператора new
конструктор std::shared_ptr
(auto sh_ptr1
) должен закончить. Я не могу понять, почему это быстрее, чем обычное распределение.
Чем это объясняется?
Проблема в том, что первый вызов конструктора std::packaged_task
отвечает за инициализацию нагрузки для каждого потока, которая затем несправедливо приписывается pt1
, Это общая проблема сравнительного анализа (в частности, микробенчмаркинга), которая облегчается благодаря прогреву; попробуйте прочитать Как мне написать правильный микро-тест в Java?
Если я копирую ваш код, но сначала запускаю обе части, результаты будут такими же, как в пределах разрешения системных часов. Это демонстрирует еще одну проблему микробенчмаркинга, которая заключается в том, что вы должны запускать небольшие тесты несколько раз, чтобы обеспечить точное измерение общего времени.
С прогревом и запуском каждой части 1000 раз получаю следующее (пример):
Regular: 132.986
Make shared: 211.889
Разница (около 80 нс) хорошо согласуется с эмпирическим правилом, согласно которому Malloc занимает 100 нс за звонок.
Это проблема с вашим микропроцессором: если вы поменяете местами порядок измерения времени, вы получите противоположные результаты (демонстрация).
Похоже, первый раз std::packaged_task
Конструктор вызывает большой успех. Добавление несвязанного
std::packaged_task<int()> ignore(bar);
перед измерением времени решает эту проблему (демонстрация):
Обычный: 505
Сделать общим: 937
Я попробовал ваш пример в 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
,