Чтение онлайн

ЖАНРЫ

Программирование мобильных устройств на платформе .NET Compact Framework
Шрифт:

1. Вычислительная неэффективность алгоритма. Этот вид неэффективности наблюдается в тех случаях, когда спроектированный вами алгоритм предусматривает интенсивные вычисления или выполнение большего количества циклов, чем это объективно необходимо, от чего можно было бы избавиться, используя более эффективные алгоритмы. В качестве классического примера можно привести сортировку массива данных. Иногда у вас может появляться возможность выбирать между несколькими возможными вариантами алгоритмов сортировки, отдельными частными случаями которых могут, например, быть алгоритмы "порядка N" (линейная зависимость времени вычислений от количества сортируемых элементов), "порядка N*Log(N)" (зависимость времени вычислений от количества сортируемых элементов отличается от линейной, но остается все же лучшей, чем экспоненциальная) или "порядка N^2" (экспоненциальная зависимость времени вычислений от количества сортируемых элементов). Кроме вышеперечисленных "порядков" возможно множество других (например, N^3). Выбор наиболее подходящего алгоритма зависит от объема данных, с которыми вы работаете, объема доступной памяти и ряда других факторов, например, от состояния рабочих данных. Отдельные стратегии, например, предварительная обработка данных перед отправкой их на устройство или хранение данных в формате, специфическом для использования памяти в качестве хранилища, способны обеспечить значительное повышение производительности алгоритма. Существует огромное количество компьютерной литературы, посвященной проектированию эффективных алгоритмов и оценке их быстродействия, поэтому никаких попыток более подробного анализа этих вопросов в данной книге не делается. Необходимо только отметить, что чем больше объем обрабатываемых данных, тем ответственнее необходимо отнестись к принятию решения относительно выбора вычислительного алгоритма. Во всех затруднительных случаях тщательно анализируйте алгоритм и обращайтесь к существующей литературе по этому вопросу. Очень часто оказывается так, что кто-то другой уже прошел этот путь, и вам остается лишь перенять их опыт.

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

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

Пишите аккуратные алгоритмы: не сорите!

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

1. Непосредственные факторы. Каждый раз, когда вы создаете объект, перед его использованием должна быть распределена и инициализирована память. Это прямые предварительные расходы, которые должен оплатить ваш алгоритм.

2. Косвенные факторы. После того как приложение освободило объект, он становится "мусором" Этот мусор накапливается в приложении, пока его не соберется так много, что для последующего распределения памяти для новых объектов потребуется ее предварительная очистка от старых. Конечно же, именно это и называется сборкой мусора. На сборку мусора уходит определенное время, и если мусора много, то эта операция заметно затормозит работу вашего приложения. Чем больше вы сорите, тем больше накапливается мусора и тем чаще приходится тратить время на уборку!

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

"Структуры" и .NET Compact Framework

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

Структуры обладают более простыми свойствами по сравнению с объектами, но могут "упаковываться" в объекты и передаваться внутри программы так же, как они, если в этом возникает необходимость. Использование структур предоставляет определенные удобства и может привести к некоторому увеличению производительности (по сравнению с вариантом, когда используются объекты), но поскольку они выглядят, а во многих случаях и действуют подобно объектам и могут заключаться в объекты-оболочки, необходимо тщательно взвешивать, когда их следует использовать, чтобы избежать дополнительных накладных расходов и не создать лишнего мусора. В сомнительных случаях тестируйте алгоритмы, используя как отдельные переменные (например, базовые типы, подобные int, string, double), так и структуры, чтобы сравнить производительность приложения в обоих случаях и убедиться в том, что она остается примерно одинаковой.

Более подробную информацию по этому вопросу вы можете получить, обратившись к разделам справочной документации .NET Compact Framework, посвященным типам значений ("value types") и структурам ("struct"). Ниже приводится пример с объявлениями структуры и класса:

//Примечание. В VB.NET это был бы тип (type), а не структура (struct)

//Это структура

struct MyRect_Type {

public int x;

public int у;

}

//Это класс

class MyRect_Class {

public int x;

public int у;

}

//Код примера

class TestClass {

public void foo {

//Требуется распределять как объект

MyRect_Class myRectClass = new MyRect_Class;

myRectClass.x = 1;

myRectClass.y = 2;

//Этот оператор распределяет новый объект

myRectClass = new MyRect_Class;

//Можно объявить как скалярный тип

MyRect_Type myRectType;

myRectType.x = 1;

myRectType.y = 2;

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

//распределяет память для нового объекта!

myRectType = new MyRect_Type;

}

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

Представленный ниже пример иллюстрирует несколько различных вариантов реализации одного и того же базового алгоритма. Алгоритм предназначен для обработки массива строк. Каждая строка в массиве состоит из трех частей, разделенных символом подчеркивания (например, big_shaggy_dog). Алгоритм предполагает просмотр каждого из элементов массива и проверку того, не является ли его средняя часть словом blue (например, my_blue_car). Если это так, то слово blue заменяется словом orange (например, my_blue_car становится my_orange_car).

Кроме того, в каждом из описанных алгоритмов используется вспомогательный класс, упрощающий разбиение строк и получение данных, содержащихся в каждом из трех сегментов. Первый алгоритм (листинги 8.3 и 8.4) представляет собой некое разумное первое приближение, а следующие два алгоритма (листинги 8.5 и 8.6 и листинги 8.7 и 8.8) — его оптимизированные варианты, улучшающие первоначальную тактику. Целью оптимизации являлось непосредственное улучшение производительности, а также уменьшение количества "мусора", вырабатываемого каждым из алгоритмов.

Листинг 8.2. Общий код, используемый во всех приведенных ниже вариантах тестов

//Желаемое число повторений теста

const int LOOP_SIZE = 8000;

//---------------------------------------------------

//Эта функция переустанавливает содержимое нашего тестового

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

//выполнения тестового алгоритма

//---------------------------------------------------

private void ResetTestArray(ref string[] testArray) {

if (testArray == null) {

testArray =new string[6];

}

testArray[0] = "big_blue_duck";

testArray[1] = "small_yellow_horse";

testArray[2] = "wide_blue_cow";

testArray[3] = "tall_green_zepplin";

testArray[4] = "short_blue_train";

testArray[5] = "short_purple_dinosaur";

}

Листинг 8.3. Тестовый пример, демонстрирующий неэкономное распределение памяти (типичный первоначальный вариант реализации интересующей нас функции)

Примечание. В этом примере используется класс PerformanceSampling, определенный ранее в данной книге.

private void button2_Click(object sender, System.EventArgs e) {

//Вызвать сборщик мусора, чтобы быть уверенными в том,

Поделиться с друзьями: