|
Программирование >> Оптимизация возвращаемого значения
становится ненужным. Чтобы уменьшить вероятность такой ситуации, надо определять объекты перед самым их использованием, но время от времени проблема все равно будет возникать. Если бы определение суммы матриц было единственным сл5аем, когда отложенное вычисление окупается, то едва ли стоило бы прикладывать столь значительные усилия. Чаше бывает, что нужна только часть вычисления. Например, предположим, что матрица тЗ используется после ее инициализации к сумме ml и т2 следующим образом: cout << тЗ[4]; Вывести 4-ую строку тЗ. Ясно, что нельзя отложить все вычисления - нужно определить значение в четвертой строке тЗ. Но при этом не нужно вычислять ничего, кроме четвертой строки матрицы тЗ; остальная часть матрицы тЗ может остаться не вычисленной до тех пор, пока не потребуется программе. При благоприятном стечении обстоятельств этого никогда не произойдет. Какова же вероятность такой удачи? Опыт в области матричных вычислений говорит, что преимущество на нашей стороне. Отложенное вычисление скрывается за чудом, которое называется язык программирования АРЕ . Этот язык был разработан в 1960-ых для интерактивного использования теми, кому приходилось выполнять матричные вычисления. Функционируя в компьютерах, имевших меньшую вычислительную мощность, чем чипы, применяемые сегодня в микроволновых печах, язык APL был способен складывать, умножать и даже делить большие матрицы, как тогда казалось, почти мгновенно! Его изюминка заключалась в отложенном вычислении. Этот подход был очень эффективным, так как пользователи APL в основном складывали, умножали или делили матрицы не потому, что им была нужна полная результирующая матрица, а потому, что требовалась только небольшая ее часть. Язык APL применял отложенное вычисление, задерживая осуществление операции до тех пор, пока не было точно известно, какая часть результирующей матрицы необходима, а затем вычислял только ее. Это позволяло выполнять сложные вычислительные задачи в интерактивном режиме на компьютере, мощность которого была бы совершенно недостаточна для реализации, использующей энергичное вычисление. Сегодня компьютеры работают быстрее, но наборы данных стали больше, а пользователи менее терпеливы, так что многие из современных библиотек матричных функций продолжают применять отложенные вычисления. Честно говоря, лень иногда не окупается. Если тЗ использовать так: cout тЗ; Вывести всю строку тЗ . то игра окончена, и придется полностью вычислять значение матрицы тЗ. Таким же образом, если одна из матриц, от которых зависит тЗ, должна быть изменена, потребуется выполнить действия немедленно: тЗ = ml + т2; Помните, что тЗ является / / суммой ml и т2 . ml = m4; Теперь тЗ является суммой т2 и старого значения ml! Здесь надо каким-то способом гарантировать, что присвоение нового значения ml не изменяет тЗ. Внутри оператора присваивания Matrix <int> можно было бы вычислить значение тЗ до изменения ml, или создать копию старого значения ml и сделать тЗ зависящим от нее. В любом случае нужно обеспечить, чтобы тЗ имела правильное значение после того, как матрица ml будет изменена. Другие функции, которые могли бы изменить матрицу, следует обрабатывать аналогично. Из-за необходимости сохранять зависимости между значениями, поддерживать структуры данных, сохраняющие значения и зависимости, или и то, и другое, а также перезагружать операторы (присваивания, копирования и сложения), отложенные вычисление требуют очень много работы. С другой стороны, они часто позволяют существенно сэкономить время и дисковое пространство при выполнении программы, и во многих приложениях это с лихвой окупает усилия, которые требуются для реализации отложенных вычислений. Резюме Приведенные четыре примера показывают, что отложенное вычисление может быть полезно в самых разных ситуациях: для того чтобы избежать напрасного копирования объектов, отличить чтение от записи при использовании operator [ ], не делать лишнего считывания информации из баз данных и многочисленных ненужных расчетов. Однако использование отложенного вычисления не всегда оправдано. Точно так же, как откладывание обычной уборки не спасет, если родители всегда проверяют вас, отложенное вычисление не сократит работу программы, если все вычисления необходимы. Действительно, в таком случае отложенное вычисление может даже замедлить выполнение программы и увеличить расход памяти, потому что вам придется не только проделывать все вычисления, которых вы надеялись избежать, но и управлять сложными структурами данных (они, собственно, и осуществляют отложенное вычисление). Отложенное вычисление полезно только тогда, когда точно известно, что ваша программа будет выполнять операции, без которых можно обойтись. Заметим, что нет никаких специфических особенностей отложенного вычисления в языке C-I-I-. Данная методика может применяться в любом языке программирования (хотя основные языки программирования обычно используют энергичное вычисление), и для некоторых из них - особенно APL, части диалектов языка Lisp и фактически всех языков, работающих с потоками данных, - эта идея является фундаментальной. Все же, С++ больше подходит в качестве средства для реализации пользователем отложенного вычисления. Это связано с тем, что поддержка инкапсуляции позволяет добавлять отложенное вычисление к классу без ведома его клиентов. Взгляните снова на приведенные выше фрагменты кода и вы убедитесь, что интерфейсы класса не содержат сведений о том, какое вычисление используется классами: энергичное или отложенное. Следовательно, можно реализовать класс с помощью прямой стратегии энергичного вычисления, а затем, если во время отладки (см. правило 16) выяснится, что от способа реализации класса зависит производительность, выполнить реализацию класса на основе отложенных вычислений. Единственным изменением, которое заметят ваши пользователи (после повторной компиляции или компоновки программы), будет улучшение производительности. Клиентам нравятся такие усовершенствования, и это может заставить вас гордиться своей ленью. Правило 18. Снижайте затраты на ожидаемые вычисления в правиле 17 я превозносил достоинства лени, откладывания дел в долгий яшик и объяснил, как лень может повысить эффективность ваших программ. В этом правиле я отстаиваю совершенно иную позицию. Здесь лени нет места! Сейчас я расскажу вам, как улучшить работу программы, заставив ее выполнять больше, чем от нее требуется. Философия этого правила может быть названа сверхэнергичным вычислением : выполнение заданий до того, как их пор5или выполнить. Рассмотрим, например, шаблон для классов, которые представляют большие наборы численных данных: template<class NumericalType> class DataCollection { public: NumericalType min() const; NumericalType max{) const; NumericalType avg() const ; Если предположить, что функции min, max и avg возвращают текущие минимальное, максимальное и среднее значения набора, то существуют три пути реализации этих функций. Используя энергичное вычисление, вы исследовали бы все данные в наборе при вызове min, max или avg и вернули бы соответствующее значение. При отложенном вычислении заставили бы функции возвращать структуры данных, с помощью которых можно было бы определять соответствующее значение, когда оно будет использоваться. Если же вы выберете предварительное вычисление, программа будет следить за текущими минимальным, максимальным и средним значениями набора данных, чтобы при вызове функций min, max или avg возвратить правильное значение немедленно - без вычислений. Если бы функции min, max и avg вызывались часто, можно было бы компенсировать затраты на слежение за соответствующими значениями данных для всех обращений к этим функциям, и средние затраты на вызов функции были бы ниже, чем при использовании энергичного или отложенного вычисления. Идея, лежащая в основе сверхэнергичного вычисления, такова: если вы ожидаете, что вычисление будет выполняться часто, вы можете снизить средние
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |