нулевая конструкция функтора и накладные расходы даже с новым и удаленным? - PullRequest
1 голос
/ 11 мая 2009

Если у меня есть класс функторов без состояния, но я создаю его из кучи с новыми, достаточно ли типичных компиляторов достаточно, чтобы полностью оптимизировать затраты на создание?

Этот вопрос возник при создании группы функторов без сохранения состояния. Если они размещены в стеке, означает ли это, что их тело класса состояния 0 означает, что стек действительно не изменяется вообще? Кажется, это должно произойти, если позже вы возьмете адрес экземпляра функтора. То же самое для распределения кучи.

В этом случае функторы всегда добавляют (тривиальные, но ненулевые) издержки при их создании. Но, возможно, компиляторы могут видеть, используется ли адрес, и если нет, то могут устранить это выделение стека. (Или это может даже устранить куча выделение?)

А как насчет функтора, который создан как временный?

#include <iostream>

struct GTfunctor 
{
  inline bool operator()(int a, int b) {return a>b; }
};

int main()
{
  GTfunctor* f= new GTfunctor;
  GTfunctor g;

  std::cout<< (*f)(2,1) << std::endl;
  std::cout<< g(2,1) << std::endl;
  std::cout<< GTfunctor()(2,1) << std::endl;
  delete f;
}

Итак, в приведенном выше конкретном примере каждая из трех строк вызывает один и тот же функтор тремя разными способами. В этом примере, есть ли разница в эффективности между способами? Или же компилятор может оптимизировать каждую строку вплоть до оператора печати без вычислений?

Edit: В большинстве ответов говорится, что компилятор никогда не сможет встроить / устранить функтор, выделенный в куче. Но так ли это на самом деле? Большинство компиляторов (GCC, MS, Intel) также имеют оптимизацию времени соединения, которая действительно могла бы выполнить эту оптимизацию. (но делает ли это?)

Ответы [ 9 ]

3 голосов
/ 11 мая 2009

Очевидно, это зависит от вашего компилятора.

Я бы сказал

  • Нет компилятор оптимизирует удаление объекта в куче. (Это потому, что, как говорит ChrisW, компиляторы никогда не оптимизируют вызов new, что почти наверняка определено в другом модуле перевода.)

  • Некоторые компиляторы оптимизируют удаление именованного объекта в стеке. Я знаю, что gcc делает эту оптимизацию довольно часто.

  • Большинство компиляторов оптимизируют удаление безымянного объекта в стеке. Это одна из «стандартных» оптимизаций C ++, тем более что более продвинутые пользователи C ++, как правило, создают множество неназванных временных переменных.

К сожалению, это только практические правила. Оптимизаторы, как известно, непредсказуемы; на самом деле единственный способ узнать, что делает ваш компилятор, это прочитать вывод сборки.

3 голосов
/ 11 мая 2009

являются ли типичные компиляторы достаточно умными, чтобы полностью оптимизировать затраты на создание?

Когда вы создаете их в куче, я сомневаюсь, что компилятору это разрешено. IMO:

  • Вызов new означает вызов оператора new.
  • operator new - это нетривиальная функция, определенная в библиотеке времени выполнения.
  • Компилятору не разрешается решать, что вы на самом деле не хотели вызывать такую ​​функцию, и решать, что в качестве оптимизации он не будет вызывать ее молча.

Когда вы создаете их в стеке, а не берете их адрес, тогда, может быть ... или, возможно, нет: я предполагаю, что каждый объект имеет ненулевой размер, чтобы занимать некоторую память, чтобы иметь идентичность, даже когда объект не имеет состояния, кроме своей идентичности.

1 голос
/ 12 мая 2009

Стандарт C ++ гласит, что каждый объект (imho в куче) должен по крайней мере иметь размер в один байт, поэтому он может быть уникально адресован.

Генерация новых функторов может привести к двум проблемам:

  1. Конструкции обычно не оптимизированы. Новая функция со сложными побочными эффектами (bad_alloc).
  2. Поскольку вы обращаетесь к функтору косвенно, возможно, компилятор не сможет встроить функцию.

Скорее всего, вы не увидите признака функтора, если создадите его в стеке.

Примечание: встроенное утверждение не обязательно. Каждая функция, определенная в определении класса, рассматривается как встроенная.

1 голос
/ 11 мая 2009

Простой способ ответить на кучу вопросов:

GTfunctor *f = new GTfunctor;

Значение f не должно быть нулевым, так что это должно быть? И у вас также было:

GTfunctor *g = new GTfunctor;

Теперь значение g не должно равняться значению f, так что должно быть у каждого?

Кроме того, ни f, ни g не могут быть равны любому другому указателю, полученному из new, если только какой-либо указатель в другом месте не каким-либо образом инициализирован равным f или g, что (в зависимости от код, который идет после) может включать в себя изучение того, что делает вся остальная часть программы.

Да, если при локальной проверке кода компилятор может увидеть, что вы никогда не полагаетесь на какое-либо из этих требований, он может выполнить перезапись так, чтобы не происходило выделения кучи. Проблема в том, что если бы ваш код был таким простым, вы могли бы сделать это, переписав себя, и в любом случае получить более читаемую программу, например. Ваша тестовая программа будет выглядеть как ваш пример g на основе стека. Таким образом, реальные программы не выиграют от такой оптимизации в компиляторе.

Предположительно, причина, по которой вы это делаете, заключается в том, что иногда функтор имеет данные в зависимости от того, какой тип выбран во время выполнения. Таким образом, анализ во время компиляции не может сработать здесь.

1 голос
/ 11 мая 2009

Компилятор не может оптимизировать вызов нового или удаления. Однако он может оптимизировать переменную, созданную в стеке, поскольку у него нет состояния.

1 голос
/ 11 мая 2009

Объект C ++ всегда имеет ненулевой размер. «Оптимизация пустого базового класса» позволяет пустому базовому классу иметь нулевой размер, но это здесь не применимо.

Я не работал ни с одним оптимизатором C ++, поэтому все, что я говорю, просто спекулирует. Я думаю, что 2-й и 3-й будут легко развернуты, и не будет никаких накладных расходов, и GTFunctor не будет создан. Однако указатель функтора - это отдельная история. В вашем примере это может показаться достаточно простым, и любой оптимизатор должен быть в состоянии устранить выделение кучи, но в нетривиальной программе вы можете создать функторы в одном блоке перевода и использовать его в другом. Или даже в другой библиотеке, в которой система компилятор / компоновщик / загрузчик / среда выполнения не имеет исходного кода, и оптимизировать ее практически невозможно. Учитывая тот факт, что оптимизация не легка, потенциальный выигрыш в производительности невелик, и число случаев, когда пустой кучный элемент выделяется в куче, вероятно, мало, я думаю, что большинство программистов-оптимизаторов, вероятно, не будут помещать эту оптимизацию высоко список дел.

1 голос
/ 11 мая 2009

Я очень сомневаюсь, что этот тип оптимизации разрешен, но если у вашего функтора нет состояния, зачем вам его инициализировать в куче? Использовать его так же просто, как и временное.

0 голосов
/ 11 мая 2009

Ответ на ваш вопрос имеет два аспекта.

  1. Оптимизирует ли компилятор распределение кучи: я сильно сомневаюсь в этом, но я не обычный парень, поэтому мне нужно его найти.

  2. Может ли компилятор оптимизировать, встроив оператор объекта ()? Да. Пока вы не укажете вызов как виртуальный, даже разыменование указателя фактически не выполняется.

0 голосов
/ 11 мая 2009

Компилятор, вероятно, может выяснить, что operator () не использует переменные-члены, и максимально оптимизировать его. Я бы не стал делать какие-либо предположения о локальных или выделенных куче переменных.

Редактировать: Если сомневаетесь, включите опцию вывода сборки на вашем компиляторе и посмотрите, что он на самом деле делает. Нет смысла слушать кучу идиотов в Интернете, когда вы сами видите реальный ответ.

...