Честно говоря, это тривиально, чтобы написать программу для сравнения производительности:
#include <ctime>
#include <iostream>
namespace {
class empty { }; // even empty classes take up 1 byte of space, minimum
}
int main()
{
std::clock_t start = std::clock();
for (int i = 0; i < 100000; ++i)
empty e;
std::clock_t duration = std::clock() - start;
std::cout << "stack allocation took " << duration << " clock ticks\n";
start = std::clock();
for (int i = 0; i < 100000; ++i) {
empty* e = new empty;
delete e;
};
duration = std::clock() - start;
std::cout << "heap allocation took " << duration << " clock ticks\n";
}
Говорят, что a foolish consistency is the hobgoblin of little minds. По-видимому, оптимизация компиляторов - это хоббиглины умов многих программистов. Это обсуждение было в основе ответа, но люди, по-видимому, не могут потрудиться, чтобы это так далеко, поэтому я перехожу сюда, чтобы избежать вопросов, на которые я уже ответил.
Оптимизированный компилятор может заметить, что этот код ничего не делает и может оптимизировать его все. Задача оптимизатора состоит в том, чтобы делать такие вещи, и борьба с оптимизатором - это безумное поручение.
Я бы порекомендовал компилировать этот код с выключенной оптимизацией, потому что нет хорошего способа обмануть каждый оптимизатор, который в настоящее время используется или который будет использоваться в будущем.
Любой, кто включит оптимизатор, а затем жалуется на борьбу с ним, должен подвергаться публичным насмешкам.
Если бы я интересовался точностью наносекунд, я бы не использовал std::clock()
. Если бы я хотел опубликовать результаты в качестве докторской диссертации, я бы сделал большую сделку по этому поводу, и я бы, вероятно, сравнил GCC, Tendra/Ten15, LLVM, Watcom, Borland, Visual C++, Digital Mars, ICC и другие компиляторы. Как бы то ни было, распределение кучи требуется в сотни раз дольше, чем распределение стека, и я не вижу ничего полезного в дальнейшем изучении вопроса.
У оптимизатора есть задача избавиться от кода, который я тестирую. Я не вижу причин, чтобы сказать, что оптимизатор запускается, а затем попытаться обмануть оптимизатора, фактически не оптимизируя. Но если бы я увидел значение в этом, что я хотел бы сделать одно или несколько из следующих действий:
Добавить элемент данных в empty
, и доступ к этому элементу данных в цикле; но если я только когда-либо прочитал из элемента данных, оптимизатор может делать постоянную фальцовку и удалять петлю; если я только когда-либо напишу члену данных, оптимизатор может пропустить все, кроме самой последней итерации цикла. Кроме того, вопрос заключался не в «распределении стека и доступе к данным против распределения кучи и доступа к данным».
Объявление e
volatile
, but volatile
is often compiled incorrectly (PDF).
Возьмите адрес e
внутри цикла (и, возможно, назначьте его переменной, объявленной extern
и определенной в другом файле). Но даже в этом случае компилятор может заметить, что - в стеке по крайней мере - e
всегда будет выделяться по одному и тому же адресу памяти, а затем делать постоянную фальцовку, как в (1) выше. Я получаю все итерации цикла, но объект никогда не выделяется.
Помимо очевидного, этот тест имеет недостатки в том, что он измеряет как распределение и освобождение, и оригинальный вопрос не просил об освобождении.Разумеется, переменные, выделенные в стеке, автоматически освобождаются в конце своей области, поэтому не вызывать delete
(1) перекосить числа (освобождение стека включено в числа о распределении стека, поэтому справедливо оценивать освобождение кучи) и (2) вызывают довольно плохую утечку памяти, если мы не сохраним ссылку на новый указатель и не позвоним delete
после того, как получим наше измерение времени.
На моей машине, используя g ++ 3.4.4 в Windows, я получаю «0 тактов времени» для распределения стека и кучи для чего-либо менее 100000 распределений, и даже тогда я получаю «0 тактов времени» для распределения стека и «15 тактов» для распределения кучи. Когда я измеряю 10 000 000 распределений, распределение стека занимает 31 такт, а распределение кучи занимает 1562 такта.
Да, оптимизирующий компилятор может ускорить создание пустых объектов. Если я правильно понимаю, он может даже превысить весь первый цикл. Когда я натолкнулся на итерации до 10 000 000 распределений стека, ушло 31 такт, а распределение кучи заняло 1562 такта. Я думаю, что можно с уверенностью сказать, что, не сообщив g ++ об оптимизации исполняемого файла, g ++ не исключил конструкторы.
В годы, так как я написал это, предпочтение на переполнение стека было размещать производительность с оптимизированной сборкой. В общем, я думаю, что это правильно. Тем не менее, я по-прежнему думаю, что глупо просить компилятор оптимизировать код, когда вы на самом деле не хотите, чтобы этот код оптимизирован. Мне кажется, что я очень похож на оплату дополнительной парковки автомобилей, но отказываюсь сдавать ключи. В этом конкретном случае я не хочу, чтобы оптимизатор работал.
Использование слегка измененной версии теста (для указания действительной точки, что исходная программа не выделяла что-либо в стеке каждый раз через цикл) и компиляции без оптимизации, но связываясь с релиз-библиотеками (для обращения к действительным точка, что мы не хотим, чтобы включать в себя любое замедление, вызванное связав отлаживать библиотеки):
#include <cstdio>
#include <chrono>
namespace {
void on_stack()
{
int i;
}
void on_heap()
{
int* i = new int;
delete i;
}
}
int main()
{
auto begin = std::chrono::system_clock::now();
for (int i = 0; i < 1000000000; ++i)
on_stack();
auto end = std::chrono::system_clock::now();
std::printf("on_stack took %f seconds\n", std::chrono::duration<double>(end - begin).count());
begin = std::chrono::system_clock::now();
for (int i = 0; i < 1000000000; ++i)
on_heap();
end = std::chrono::system_clock::now();
std::printf("on_heap took %f seconds\n", std::chrono::duration<double>(end - begin).count());
return 0;
}
дисплеи:
on_stack took 2.070003 seconds
on_heap took 57.980081 seconds
на моей системе при компиляции с помощью командной строки cl foo.cc /Od /MT /EHsc
.
Возможно, вы не согласны с моим подходом к получению не оптимизированной сборки. Все в порядке: не стесняйтесь модифицировать бенчмарк столько, сколько хотите. Когда я перехожу по оптимизации, я получаю:
on_stack took 0.000000 seconds
on_heap took 51.608723 seconds
Не потому, что выделение стека фактически мгновенно, но из-за какой-либо наполовину приличной компилятор может заметить, что on_stack
ничего полезного не делает, и может быть оптимизирована прочь. GCC на моем Linux ноутбук также замечает, что on_heap
ничего полезного не делают, и оптимизирует его в сторону, а также:
on_stack took 0.000003 seconds
on_heap took 0.000002 seconds
Я знаю, что это довольно древний, но было бы неплохо увидеть некоторые фрагменты C/C++, демонстрирующие различные виды распределения. – 2011-06-05 15:48:58
Ваш орк коровы ужасно неосведомлен, но более важно, что он опасен, потому что он делает авторитетные заявления о вещах, о которых он ужасно не знает. Акцизируйте таких людей из своей команды как можно быстрее. – 2013-05-19 00:57:22
Обратите внимание, что куча обычно * много * больше, чем стек. Если вам выделены большие объемы данных, вам действительно нужно положить их в кучу или изменить размер стека из ОС. – 2013-11-04 06:00:53