14.8. Соображения эффективности A

14.8. Соображения эффективности A

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

bool sufficient_funds( Account acct, double );

то при каждом ее вызове требуется выполнить почленную инициализацию формального параметра acct значением фактического аргумента-объекта класса Account. Если же функция имеет любую из таких сигнатур:

bool sufficient_funds( Account *pacct, double );

bool sufficient_funds( Account &acct, double );

то достаточно скопировать адрес объекта Account. В этом случае никакой инициализации класса не происходит (см. обсуждение взаимосвязи между ссылочными и указательными параметрами в разделе 7.3).

Хотя возвращать указатель или ссылку на объект класса также более эффективно, чем сам объект, но корректно запрограммировать это достаточно сложно. Рассмотрим такой оператор сложения:

// задача решается, но для больших матриц эффективность может

// оказаться неприемлемо низкой

Matrix

operator+( const Matrix& m1, const Matrix& m2 )

{

Matrix result;

// выполнить арифметические операции ...

return result;

}

Этот перегруженный оператор позволяет пользователю писать

Matrix a, b;

// ...

// в обоих случаях вызывается operator+()

Matrix c = a + b;

a = b + c;

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

Следующая пересмотренная реализация намного увеличивает скорость:

// более эффективно, но после возврата адрес оказывается недействительным

// это может привести к краху программы

Matrix&

operator+( const Matrix& m1, const Matrix& m2 )

{

Matrix result;

// выполнить сложение ...

return result;

}

но при этом происходят частые сбои программы. Дело в том, что значение переменной result не определено после выхода из функции, в которой она объявлена. (Мы возвращаем ссылку на локальный объект, который после возврата не существует.)

Значение возвращаемого адреса должно оставаться действительным после выхода из функции. В приведенной реализации возвращаемый адрес не затирается:

// нет возможности гарантировать отсутствие утечки памяти

// поскольку матрица может быть большой, утечки будут весьма заметными

Matrix&

operator+( const Matrix& m1, const Matrix& m2 )

{

Matrix *result = new Matrix;

// выполнить сложение ...

return *result;

}

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

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

// это обеспечивает нужную эффективность,

// но не является интуитивно понятным для пользователя

void

mat_add( Matrix &result,

const Matrix& m1, const Matrix& m3 )

{

// вычислить результат

}

Таким образом, проблема производительности решается, но для класса уже нельзя использовать операторный синтаксис, так что теряется возможность инициализировать объекты

// более не поддерживается

Matrix c = a + b;

и использовать их в выражениях:

// тоже не поддерживается

if ( a + b c ) ...

Неэффективный возврат объекта класса - слабое место С++. В качестве одного из решений предлагалось расширить язык, введя имя возвращаемого функцией объекта:

Matrix&

operator+( const Matrix& m1, const Matrix& m2 )

name result

{

Matrix result;

// ...

return result;

}

Тогда компилятор мог бы самостоятельно переписать функцию, добавив к ней третий параметр-ссылку:

// переписанная компилятором функция

// в случае принятия предлагавшегося расширения языка

void

operator+( Matrix &result, const Matrix& m1, const Matrix& m2 )

name result

{

// вычислить результат

}

и преобразовать все вызовы этой функции, разместив результат непосредственно в области, на которую ссылается первый параметр. Например:

Matrix c = a + b;

было бы трансформировано в

Matrix c;

operator+(c, a, b);

Это расширение так и не стало частью языка, но предложенная оптимизация прижилась. Компилятор в состоянии распознать, что возвращается объект класса и выполнить трансформацию его значения и без явного расширения языка. Если дана функция общего вида:

classType

functionName( paramList )

{

classType namedResult;

// выполнить какие-то действия ...

return namedResult;

}

то компилятор самостоятельно трансформирует как саму функцию, так и все обращения к ней:

void

functionName( classType &namedResult, paramList )

{

// вычислить результат и разместить его по адресу namedResult

}

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

И последнее замечание об эффективности работы с объектами в C++. Инициализация объекта класса вида

Matrix c = a + b;

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

Matrix c;

c = a + b;

но объем требуемых вычислений значительно больше. Аналогично эффективнее писать:

for ( int ix = 0; ix

чем

Matrix matSum;

for ( int ix = 0; ix

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

Point3d p3 = operator+( p1, p2 );

можно безопасно трансформировать:

// Псевдокод на C++

Point3d p3;

operator+( p3, p1, p2 );

преобразование

Point3d p3;

p3 = operator+( p1, p2 );

в

// Псевдокод на C++

// небезопасно в случае присваивания

operator+( p3, p1, p2 );

небезопасно.

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

Что касается инициализируемого объекта, то отведенная под него память еще не подвергалась обработке. Если же объекту присваивается значение и в классе объявлены конструкторы (а именно этот случай мы и рассматриваем), можно утверждать, что эта память уже форматировалась одним из них, так что непосредственно передавать объект функции небезопасно.

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

Point3d p3;

p3 = operator+( p1, p2 );

трансформируется в такой:

// Псевдокод на C++

Point3d temp;

operator+( temp, p1, p2 );

p3.Point3d::operator=( temp );

temp.Point3d::~Point3d();

Майкл Тиманн (Michael Tiemann), автор компилятора GNU C++, предложил назвать это расширение языка именованным возвращаемым значением (return value language extension). Его точка зрения изложена в работе [LIPPMAN96b]. В нашей книге “Inside the C++ Object Model” ([LIPPMAN96a]) приводится детальное обсуждение затронутых в этой главе тем.

2012-11-28 08:45:13 vertical

эффективнее писать: for ( int ix = 0; ix size-2; ++ix ) { Matrix matSum = mat[ix] + mat[ix+1]; // ... } чем Matrix matSum; for ( int ix = 0; ix size-2; ++ix ) { matSum = mat[ix] + mat[ix+1]; // ... } Не всегда. На каждой итерации цикла будет создаваться и уничтожаться объект matSum. Тут производительность зависит еще и от вычислительной сложности конструктора и деструктора объекта.

2012-10-19 15:36:29 Ярослав

Спасибо, очень содержательно!

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг

Дополнительные соображения по оптимизации

Из книги Разгони свой сайт автора Мациевский Николай

Дополнительные соображения по оптимизации Одним из возможных выходов из сложившейся ситуации будет использование характерных для IE CSS-хаков, чтобы только для него подключить фоновые изображения. В итоге вышеприведенный пример будет выглядеть примерно так:ul {list-style: none;}ul


3.1.6 Формулировки эффективности

Из книги Пакеты программ. Требования к качеству и тестирование автора Автор неизвестен

3.1.6 Формулировки эффективности В описание продукта может быть включена информация о характере поведения продукта во времени, например времена ответа и оценки производительности для заданных функций при установленных условиях (например, для заданных конфигураций


Вопросы эффективности

Из книги Эффективное использование STL автора Мейерс Скотт

Вопросы эффективности Сначала я хотел включить в книгу отдельную главу, посвященную вопросам эффективности, но в итоге решил, что лучше оставить привычное деление на советы. Тем не менее многие советы посвящены минимизации затрат памяти и ресурсов на стадии исполнения.


1. Общие соображения об анонимности в Интернет

Из книги Установка и настройка Tor автора Стручков Юрий

1. Общие соображения об анонимности в Интернет Человек написал и отправил письмо по электронной почте, посетил какой-то сайт, оставил своё сообщение на форуме и т. д. Любое из указанных действий позволяет найти этого человека и узнать кто он такой. А при желании и привлечь


Размышления об эффективности

Из книги Основы объектно-ориентированного программирования автора Мейер Бертран

Размышления об эффективности [x]. Может ли элегантность и простота нанести удар по эффективности выполнения? Одна из причин широкого использования массивов состоит в том, что основные операции - доступ и изменение элемента - проходят быстро. Надо ли платить за каждый вызов


§ 160. Два соображения

Из книги Ководство автора Лебедев Артём Андреевич

§ 160. Два соображения 3 июня 2009— Хороший логотип должен быть таким, чтобы любой человек мог его по памяти от руки изобразить.— Это хорошо, если реклама не сразу понятна. Человек потратит время на понимание смысла и лучше запомнит такую рекламу.Из-за этих двух заблуждений


Соображения по поводу эффективности

Из книги Фундаментальные алгоритмы и структуры данных в Delphi автора Бакнелл Джулиан М.

Соображения по поводу эффективности Если бы это было все, что можно сказать о связных списках, то глава оказалась бы очень короткой. До сих пор была представлена только реализация класса, инкапсулирующего односвязный список. Но перед написанием класса связного списка


Соображения производительности

Из книги QNX/UNIX [Анатомия параллелизма] автора Цилюрик Олег Иванович

Соображения производительности Интересны не только затраты на порождение нового процесса (мы еще будем к ним неоднократно возвращаться), но и то, насколько «эффективно» сосуществуют параллельные процессы в ОС, насколько быстро происходит переключение контекста с


1.4.5. Заключительные соображения по поводу «GNU Coding Standards»

Из книги Учись программировать (на Ruby) автора Пайн Крис

1.4.5. Заключительные соображения по поводу «GNU Coding Standards» GNU Coding Standards является стоящим для прочтения документом, если вы хотите разрабатывать новое программное обеспечение GNU, обмениваться существующими программами GNU или просто научиться программировать лучше. Принципы


6.5. Проверка эффективности брандмауэра

Из книги Священные войны мира FOSS автора Федорчук Алексей Викторович

6.5. Проверка эффективности брандмауэра Давайте проверим эффективность вашего брандмауэра. Зайдите на страницу: http://www.pcflank.com/scanner1.htm?from=menu. Нажмите кнопку Start Test, затем – кнопку Continue. После этого выберите тип сканирования: TCP connect scanning или TCP SYN scanning. С первым тестом может


Соображения для преподавателей

Из книги автора

Соображения для преподавателей Было несколько руководящих принципов, которых я старался придерживаться. Я думаю, они делают процесс обучения гораздо более лёгким – ведь учиться программировать и так довольно тяжело. Если вы преподаёте или наставляете кого-то на путь


Отборочные соображения

Из книги автора

Отборочные соображения Тайм-аут, в течении которого я отвлёкся от проблем противостояния дистрибутивов, имена которых вынесены в заглавие цикла, подошёл к концу. И пора выполнять своё обещание – подвести окончательные (на сегодняшний день) итоги тому, что было сказано на