Текст книги "Программирование мобильных устройств на платформе .NET Compact Framework"
Автор книги: Иво Салмре
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 22 (всего у книги 69 страниц)
ГЛАВА 8
Производительность и управление памятью
"Человеческая голова подобна комнате, в которой необходимо держать только те вещи, которые вам непосредственно нужны, а все остальные – убирать подальше в чулан, откуда их можно будет в любой момент достать, когда они понадобятся."
Артур Конан-Дойль(Encarta 2004, Quotations)
Определение модели памяти для приложения
Согласно приведенному выше суждению автора рассказов о Шерлоке Холмсе, необходимо различать ресурсы, которые должны быть всегда под рукой, и ресурсы, которые лучше всего переместить в долговременное хранилище. Применительно к мобильным устройствам это означает, что вы должны хорошенько подумать над тем, какую модель памяти следует использовать.
Самое важное, что можно сказать о модели памяти, – это то, что она должна у вас быть. Было бы слишком легкомысленно допустить, чтобы в процессе разработки приложения разрастались сами по себе неконтролируемым образом, а не в соответствии с предварительно составленным планом. В случае приложений для настольных компьютеров это часто приводит к малопонятному коду, который трудно сопровождать и обновлять, и приложениям, которые функционируют не столь надежно и эффективно, как следовало бы. В случае мобильных приложений использование "сырой" модели памяти приводит к приложениям, которые обязательно упираются в "стену производительности", в результате чего они никогда не смогут функционировать так, как надо. В подобных ситуациях обычно трудно устранить проблемы, не прибегая к широкомасштабной переделке проекта. Разработка хорошо продуманной модели памяти позволяет избежать этой трясины и сделать проект более гибким. Целесообразно рассматривать использование памяти приложения на двух различных уровнях:
1. Управление памятью на макроскопическом "уровне приложения". Этот уровень относится к данным и ресурсам уровня приложения, которые поддерживаются вашим приложением в процессе выполнения. Эти данные обычно существуют в течение длительного времени, и их область видимости не ограничивается пределами отдельных функций. Для создания эффективно функционирующего мобильного приложения очень важно иметь надежную модель, управляющую объемом данных, подлежащих хранению в памяти в каждый момент времени, и удалением из памяти данных и ресурсов, непосредственное использование которых в ближайшее время не ожидается. Чрезмерный объем долгоживущих данных состояния загромождает память, которую можно было бы использовать для кэширования JIT-компилированного кода или как рабочую память для функций, и заставляет многократно и не самым эффективным образом очищать память от "мусора".
2. Распределение памяти на микроскопическом "уровне алгоритма". Временную память для выполнения команд, определяемых вашими алгоритмами, распределяют функции. Эффективность этого процесса зависит от вашей стратегии реализации алгоритмов. Например, при написании кода, который должен выполняться в циклах, необходимо как можно тщательнее продумывать его эффективность в отношении использования ресурсов, чтобы свести к минимуму непроизводительные накладные расходы. Уделяя пристальное внимание эффективности распределения памяти в создаваемых вами алгоритмах, вы сможете значительно повысить общую производительность приложения.
Приложения для настольных компьютеров, хранящие в памяти большие рабочие наборы (рабочий набор, равный всей используемой памяти), обычно выталкивают в дисковый файл подкачки все новые и новые данные. Это позволяет освобождать память от редко используемых данных, что несколько сглаживает отрицательные последствия неэкономного управления памятью на макроскопическом уровне приложения. Результирующее поведение производительности в зависимости от объема используемой памяти в широких пределах носит примерно линейный характер. Кроме того, среды выполнения управляемого кода на настольных компьютерах располагают сложными механизмами очистки памяти от неиспользуемых объектов, что может частично нивелировать недостатки расточительных в отношении использования памяти алгоритмов. Это способствует повышению производительности приложения на микроскопическом уровне. Неэффективное управление памятью имеет отрицательные последствия и в случае приложений, выполняющихся на настольных компьютерах, однако огромная емкость вычислительной среды сглаживает эти эффекты.
ОЗУ мобильных устройств имеют гораздо меньший объем и, как правило, не позволяют надлежащим образом реализовать механизмы, использующие дополнительные внешние накопители для организации быстрого обмена страницами с памятью. Кроме того, в условиях ограниченных ресурсных возможностей на мобильных устройствах могут быть реализованы лишь сравнительно простые механизмы очистки памяти от неиспользуемых объектов. Это означает, что небрежно организованное управление памятью в приложениях для мобильных устройств будет иметь весьма заметные отрицательные последствия как на макроскопическом, так и на микроскопическом уровнях. По сравнению с настольными компьютерами мобильные устройства гораздо менее терпимы к любым просчетам в управлении памятью.
Использование тщательно продуманного управления памятью обеспечивает значительные преимущества как при разработке приложений для настольных компьютеров, так и при разработке приложений для мобильных устройств, однако в последнем случае такой подход должен быть обязательно плановым. Отсутствие продуманного управления памятью в случае приложений для настольных компьютеров приводит к постепенному обрастанию приложения неиспользуемыми объектами и замедлению его работы. В противоположность этому мобильное приложение, работающее без применения выверенных стратегий управления памятью на макроскопическом и микроскопическом уровнях, очень быстро пересекает опасную черту, и его дальнейшее использование становится истинным мучением.
Управление памятью на макроскопическом "уровне приложения"
На рис. 8.1 в схематическом виде отображено, как снижается производительность приложения с увеличением объема используемой памяти.

Рис. 8.1. Изменение производительности приложения с увеличением объема используемой памяти
Из поведения графиков видно, что в случае приложений для настольных компьютеров диапазон объемов используемой памяти, при которых производительность остается в допустимых пределах, является намного более широким. При превышении некоторого порогового значения производительность резко снижается, поскольку начинается свопинг данных между памятью и файлом подкачки, а сборщик мусора все чаще пытается освобождать и уплотнять память; вместе с тем, падение производительности происходит гораздо медленнее, чем в случае мобильных устройств. Для мобильных устройств диапазон используемых объемов памяти, при которых производительность остается на приемлемом уровне, оказывается более узким. При превышении некоторого порогового значения наблюдается резкое падение производительности мобильного приложения, поскольку сборщик мусора работает почти непрерывно, пытаясь высвободить все большие объемы памяти. На графиках отмечены участки, соответствующие использованию объемов памяти, при которых приложение еще способно нормально функционировать. Коль скоро потребление памяти не превышает некоторого критического значения, ничего особенного в целом не происходит. Однако при пересечении критической точки производительность приложения резко падает, поскольку сборщик мусора вынужден работать все чаще, изо всех сил пытаясь высвободить память для нужд приложения.
Будут ли меня преследовать те же проблемы, если я пишу приложение на основе собственного кода?
В отличие от сред выполнения управляемого кода (.NET Compact Framework, Java и так далее), при разработке приложений в собственных кодах сборка мусора не используется. Однако это вовсе не означает, что в этом случае вам удается соскочить с крючка; напротив – нагрузка на стадии проектирования, вероятнее всего, только увеличится! При выполнении на настольных компьютерах приложения, основанные на собственных кодах, пользуются всеми преимуществами, которые неявно предоставляет операционная система, осуществляя обмен страницами ОЗУ с дисковым файлом подкачки. В результате этого, хотя выполнение приложения и может замедляться, оно при любых условиях сохраняет свою работоспособность. Если же ваше мобильное приложение превысит допустимые пределы расхода памяти, то оно просто-напросто исчерпает все ее резервы и завершится сбоем. Отсюда следует, что при разработке мобильных приложений, основанных на собственных кодах, разработчик должен заранее предпринять меры, позволяющие избежать возникновения подобных ситуаций. Кроме того, алгоритмы на основе собственного кода, которые без особой на то необходимости распределяют и освобождают память лишь потому, что были неудачно спроектированы, будут вынуждены бороться с проблемами фрагментации памяти, что также приведет к снижению производительности. В то время как среды времени выполнения управляемого кода могут справляться с фрагментацией памяти за счет ее уплотнения в процессе сборки мусора, в случае собственных кодов аналогичные встроенные возможности отсутствуют. А это означает, что вы должны сами продумать детальную схему управления памятью и самостоятельно ее реализовать.
Перед вами, как перед проектировщиком мобильного приложения, не стоит задача найти способы, позволяющие использовать всю свободную память вплоть до некоторой пороговой величины, при превышении которой производительность начинает резко ухудшаться. Вместо этого вы должны стремиться к тому, чтобы у вас всегда оставался некоторый резервный запас свободной памяти (резерв безопасности), оставляющий простор для маневров, что будет гарантией эффективного выполнения вашего приложения в самых различных ситуациях. Необходимость такого подхода диктуется тем, что в типичных условиях ваше приложение не имеет возможности полностью контролировать всю установленную на устройстве память; память находится в совместном распоряжении операционной системы и всех приложений, которые могут одновременно выполняться пользователем. Если какое-то приложение распределяет и удерживает лишние объемы памяти, то от этого будет страдать производительность всех приложений. Не занимайте больше памяти, нежели требуется, и освобождайте те ее излишки, непосредственная необходимость в которых отпала
Как и в случае других аспектов проектирования мобильных приложений, разработка удачной модели памяти, удовлетворяющей всем вашим требованиям, возможна лишь в результате проведения экспериментов и при наличии опыта. Вы должны экспериментально проверить самые различные подходы, и довести характеристики модели до нужных, исходя из доступных ресурсов мобильного устройства и требований приложения. Залогом успешной разработки мобильных приложений является творческий подход к решению проблем, открывающий широкий простор для экспериментов.
Не решатся ли обсуждаемые проблемы сами по себе с приходом мобильных устройств "следующего поколения"?
Добавочная память стоит денег, увеличивает тепловыделение, потребляет электроэнергию и занимает в устройстве дополнительное место. Со временем ситуация в отношении каждого из этих факторов будет только улучшаться, но с ними всегда надо будет считаться. В соответствии с законом Мура, согласно которому каждое очередное поколение вычислительных устройств будет предоставлять нам все более широкие возможности при меньшей стоимости, для мобильных устройств всегда будет характерна ярко выраженная тенденция к снижению стоимости, уменьшению размеров и увеличению продолжительности непрерывной работы после однократной зарядки батарей.
Большинство современных мобильных устройств уже располагают вполне приличными объемами памяти, обеспечивающими решение широкого круга задач, но этой памятью необходимо распоряжаться благоразумно. Очень часто плохие привычки, приобретенные в процессе проектирования приложений для настольных компьютеров, переносятся и на работу с мобильными устройствами. Это приводит к отсутствию строгого подхода к распределению памяти в алгоритмах и неэффективному управлению памятью, допускающему хранение в памяти ресурсов, которые больше не используются.
Ранее в этой книге мы уже использовали аналогию, в которой позволили себе сравнить современные настольные компьютеры с большими особняками в сельской местности, а мобильные устройства – с шикарно выглядящими, но небольшими городскими квартирами. Очень важно, чтобы вы никогда не забывали об этом образном примере, иллюстрирующем суть "размерных ограничений". Вы можете реализовать на мобильном устройстве почти все, что хотите, но вам не удастся сделать это сразу или немедленно завладеть всей памятью. Вы должны располагать набором определенных правил, регламентирующих то, какие вещи должны находиться в квартире, а какие должны быть вынесены из нее.
Последующие поколения мобильных устройств будут гораздо мощнее, и будут располагать большими объемами памяти, но от них все равно будет требоваться еще большее. Продуманные стратегии управления памятью будут по-прежнему оставаться ключевым фактором создания отличных мобильных приложений.
Хранить в памяти неиспользуемые объекты слишком расточительно, поскольку они занимают место, которое можно было бы использовать с большей эффективностью для активных составляющих вашего приложения. С другой стороны, было бы слишком легкомысленно освобождать память от объектов сразу же после того, как непосредственная необходимость в них исчезла.
Обратимся к метафоре. Предположим, что вы живете в небольшой квартире и для скрепления бумажных листов вам потребовался скоросшиватель, который вы должны купить, сходив для этого в магазин. Это займет у вас время и ресурсы – не очень много, но вполне достаточно, чтобы вы это ощутили. Выбросив скоросшиватель сразу же после того, как он будет использован, вы освободите в квартире немного места (фактически это произойдет лишь после того, как вы избавитесь от мусора в ведре, в которое выбросили скоросшиватель), но останетесь без скоросшивателя. Если впоследствии вам вновь понадобится что-либо скрепить, вы должны будете опять потратить время на посещение магазина и понести расходы, покупая новый скоросшиватель. Если бы все происходило именно так, то ваши действия следовало бы рассматривать как опрометчивые; зная, что на протяжении ближайшего года-двух скоросшиватель вам вновь может понадобиться, его лучше было бы держать где-то поблизости. Однако если вы абсолютно уверены в том, что необходимость в скоросшивателе повторно никогда не возникнет (возможно, с этого момента вы решили пользоваться только зажимами), то вы должны выбросить его, как бы мало места он ни занимал. Кому охота загромождать квартиру бесполезными вещами?
Во всем должен существовать определенный баланс, который вам предстоит определить. Поскольку скоросшиватель – вещь довольно полезная, по своим размерам не очень габаритная и время от времени может использоваться, то его следует оставить под рукой. А что можно сказать по поводу большого парового пылесоса для чистки ковров? Опять-таки, вы могли бы сходить в магазин, купить пылесос, затратив на это значительные денежные и временные ресурсы, и держать его где-то в квартире; в то же время, если вы живете в небольшой квартирке и чистите свои ковры сравнительно редко, то такое решение было бы не самым разумным. Вместо этого вам следовало бы взять пылесос напрокат, сделать все, что надо, а затем отнести его обратно в пункт проката. Вы всегда должны планировать, какие вещи лучше держать при себе, а от каких лучше избавляться, когда они сделали свое дело.
Сложнее всего принимать решения относительно объектов, которые попадают в промежуточную категорию. Сами по себе эти объекты не отличаются большими размерами или высокой стоимостью, каждый из них занимает сравнительно немного места и они могут оказаться потенциально полезными в не столь отдаленном будущем. Эти объекты можно уподобить одежде, которую вы почти никогда не носите. Ее можно было бы и выбросить, но также неплохо оставить у себя на тот случай, если вы измените свое мнение. Если такие объекты немногочисленны, то держать их поблизости не составляет труда, но в эту категорию попадает так много объектов, что легко заполнить ими все имеющиеся помещения, что не оставит вам места для тех вещей, которыми вы пользуетесь наиболее часто. Может так оказаться, что вся ваша квартира будет забита одеждой, которую вы редко носите, и ящиками с барахлом, которое вы почти никогда не используете. Если у вас громадный дом с множеством помещений, то ничего ужасного в этом нет, но если вы живете в квартире, то она окажется забитой вещами до потолка. Настольный компьютер – суть "большой дом". Мобильное устройство – суть "квартира".
Очевидно, что должны существовать какие-то правила, регламентирующие, что следует держать под рукой, а от чего необходимо избавляться. Действительно, такие правила существуют но, к счастью, они основываются на здравом смысле, и главное заключается в том, чтобы их систематически применять. Именно в том и состоит на значение явной модели использования памяти приложения, чтобы гарантировать последовательное применение основанных на здравом смысле соображений, обеспечивающих эффективное выполнение мобильного приложения.
Как и все другие проблемы технического характера, эта проблема также должна решаться на основе достижения определенного компромисса. Нежелание отказываться от хранения объектов, необходимость в которых впоследствии вновь может возникнуть, должно сопоставляться с теми преимуществами в отношении производительности приложения, которые обеспечиваются наличием свободной памяти.
Информация какого типа должна охватываться вашей макромоделью памяти? Полезно рассортировать данные и ресурсы приложения, с которыми вы работаете, на две разновидности: 1) объекты и ресурсы, которые необходимы приложению для эффективного выполнения, и 2) фактическая пользовательская информация, с которой работает приложение.
1. Необходимые служебные данные приложения. В эту разновидность входят ресурсы, необходимые приложению для поддержания пользовательского интерфейса и других аспектов выполнения приложения. В качестве примера можно привести открытые соединения с базами данных, открытые файлы, потоки выполнения, графические объекты, а также такие объекты пользовательского интерфейса, как кисти, перья, элементы управления форм и формы как таковые. Все эти объекты не имеют непосредственного отношения к данным, с которыми фактически работает пользователь, но жизненно необходимы для фактического взаимодействия приложения с пользователем или с внешними источниками информации.
2. Пользовательские данные. К этой разновидности относятся фактические данные, в работе с которыми заинтересован пользователь. Под этими данными подразумевается та часть данных, которая удерживается в памяти, а не хранится в базе данных или файле на устройстве или вне его. Например, если мобильное приложение предназначено для того, чтобы облегчить пользователю проезд по улицам Лондона, то пользовательскими данными является информация о расположении улиц, которая в данный момент загружена в память. Если приложение предназначено для ведения инвентарного учета, то пользовательскими данными являются загруженные инвентаризационные данные. Если приложение представляет собой игру в шахматы, то пользовательскими данными является представление состояния шахматной доски в памяти машины.
И здесь вам вновь могут пригодиться конечные автоматы. Предусмотрите для своего приложения два высокоуровневых конечных автомата, каждый из которых предназначен для управления одной из двух вышеупомянутых разновидностей данных. Служебные данные приложения и пользовательские данные целесообразно разделить и предусмотреть для управления каждой из этих разновидностей данных свой конечный автомат.
Являются ли растровые изображения "пользовательскими" данными или "служебными" данными приложения?
Поскольку данные растровых изображений представляют загруженные в память и готовые для использования в графике приложения изображения, они могут занимать много места. Держать растровые данные больших изображений может оказаться дорогим удовольствием, и поэтому следует уделить должное внимание тому, когда их следует загружать в память, а когда – освобождать память от них.
Некоторые типы растровых изображений являются пользовательскими данными. Например, если приложение предназначено для обслуживания сделок по продаже недвижимости и загружает в память изображения домов, то каждая фотография является уникальной и соответствует пользовательским данным, относящимся к данному объекту недвижимости. Наряду с адресом этого дома и его ценой она загружается вместе с остальной информацией об объекте. To же самое можно сказать об изображениях используемых в медицине, которые прилагаются к карточке пациента; каждое изображение связывается с пользовательскими данными.
Некоторые типы растровых данных являются служебными данными приложения. Растровые изображения, используемые для создания кнопок с привлекательным внешним видом, хранящиеся в кэш-памяти фоновые изображения, стандартные заготовки графических диаграмм – все эти объекты являются служебными данными приложения, поскольку они никак конкретно не связаны с теми данными, с которыми работает пользователь. К этой же категории следовало бы отнести и типовое растровое изображение человеческого тела, которое может использоваться в медицинском приложении для указания очагов болезни. Все эти ресурсы являются общими, и они не связаны ни с какими конкретными данными. Одни формы могут использовать эти ресурсы, другие – могут не использовать; данные изображений могут загружаться, а могут и игнорироваться в зависимости от режима работы приложения и не зависят от того, с каким именно набором данных работает пользователь
Некоторые типы растровых изображений могут быть отнесены к любой из двух названных категорий. Если у вас есть приложение для торговли недвижимостью, которое может отображать три разновидности изображений поэтажного плана в зависимости от того, какие пользовательские данные загружены, или медицинское приложение, в котором имеется шесть различных разновидностей изображений человеческого тела, соответствующих различным комбинациям пола, размеров и веса то перечисленные изображения можно рассматривать либо в качестве служебных данных приложения, либо в качестве пользовательских данных. Аналогично, если в вашей игре имеются растровые изображения для всех персонажей на экране, и эти изображения меняют свой внешний вид по мере перехода пользователя на более высокие игровые уровни, то одинаково убедительные аргументы могут быть выдвинуты и в пользу того, что данные изображения относятся к служебным данным приложения, и в пользу того, что они относятся к пользовательским данным. В подобных случаях вы должны выбирать для ресурсов ту модель памяти, которая больше всего подходит для вашего приложения. Помещайте изображения рассматриваемой разновидности в ту схему управления памятью, которая наилучшим образом соответствует вашим запросам.







