"Графика для Windows средствами DirectDraw" - читать интересную книгу автора (Трухильо Стэн)

Глава 2. Проблемы быстродействия                 

Быстродействие никогда не выйдет из моды. Чтобы обеспечить максимальное быстродействие программы, ее необходимо оптимизировать. Об этом знают все программисты, занимающиеся разработкой аркадных игр. Их игры должны работать быстро, иначе они будут плохо продаваться. С каждым годом игроки желают иметь все более высокое быстродействие. Каждая новая игра-бестселлер устанавливает новые стандарты и поднимает планку еще выше — несомненно, в будущем эта тенденция только усилится.

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

Не стоит полагать, что каждый пользователь, знакомясь с новой программой, говорит себе: «Пусть эта штуковина работает побыстрее, а не то…» Быстродействие программы чаще оценивается на подсознательном уровне. Работа с быстрой программой, мгновенно реагирующей на все ваши желания, доставляет радость. Хорошее быстродействие вселяет в пользователя уверенность и желание работать дальше. Медленные программы лишь испытывают наше терпение. Каждый, кому приходится работать с ними, мечтают поскорее закончить свои мучения. Пользователи предпочитают, чтобы любая программа (текстовый или графический редактор, игра или любое другое приложение) быстро и адекватно реагировала на их действия.

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

Традиционная оптимизация

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

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

Не стоит просто предполагать, что некоторые части вашей программы работают медленно. Перед началом вскрытия расставьте таймеры в подозрительном фрагменте и воспользуйтесь профайлером Visual C++. Издевательства над невинными функциями не только ведут к напрасным затратам времени, но и повышают вероятность внесения новых ошибок.

Действительно ли C++ медленнее C?

Мы подходим к деликатной теме. Защитники C дружно объединяются для защиты своего любимого языка. Адепты C++ тоже настроены весьма воинственно. К сожалению, преданность такого рода нередко оборачивается неразумным фанатизмом.

Язык C известен своей эффективностью. Именно поэтому он стал первым языком высокого уровня, на котором была написана операционная система (Unix). И все же по скорости C уступает ассемблеру. Он создавался как высокоуровневая альтернатива той чрезмерной детализации, с которой связано программирование на ассемблере.

C++ был создан для того, чтобы обогатить существующий язык C конкретными функциональными возможностями и при этом воспользоваться преимуществами его скорости. Добавлений было несколько, но основным усовершенствованием стало появление объектов, или классов. Более того, первоначально C++ назывался C with classes.

Классы были включены в C++ для поддержки таких объектно-ориентированных принципов, как наследование и полиморфизм. Эти возможности уже присутствовали в других языках (например, в Smalltalk), но C++ был задуман как высокопроизводительный объектно-ориентированный язык. Smalltalk при всей своей мощности работает медленно.

Поддержка классов в C++ реализована с помощью дополнительного уровня косвенной адресации, отсутствующего в C. Доступ ко всем нестатическим переменным и функциям классов осуществляется через неявные указатели. Этот подход обеспечивает полезную возможность — полиморфизм, однако при этом возникают дополнительные накладные расходы, на которых и основано заявление «C++ медленнее C».

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

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

Программы в этой книге написаны на C++. Обратите внимание на умеренное использование классов. Например, вполне логично использовать класс для представления окна, потому что в MFC входит заранее написанный и протестированный оконный класс. Тем не менее способ применения этого класса более характерен для «просто C».

Вероятно, споры на тему «C против C++» продлятся еще несколько лет. После этого они утихнут, подобно тому, как утихли споры «C против ассемблера». В наши дни никто не спорит с тем, что ассемблер работает быстрее C, однако писать целые приложения на ассемблере оказывается невыгодно.

Не бойтесь плавающей точки

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

Аппаратная часть быстрее программной

Если вы уже видели хорошее приложение DirectDraw за работой, то уже знаете это. Приложение, которое работает в режиме 640#215;480#215;16 и при этом обеспечивает вывод 60 кадров в секунду, было бы невозможно написать без аппаратного ускорения. Так как DirectDraw автоматически использует все возможности для аппаратного ускорения, вам даже не придется беспокоиться на этот счет.

Я упоминаю об этом лишь по одной причине: если компьютер пользователя не обладает возможностями аппаратного ускорения (или такие возможности слабы), вам не удастся почти ничего сделать. Не стоит беспокоиться о подобной ситуации, потому что проблема заключается в аппаратной части, а не в вашей программе. Если можно, постарайтесь добиться оптимального быстродействия за счет поддержки видеорежимов с низким разрешением. После этого можете считать, что сделали все возможное.

Нехватка видеопамяти

Нехватка видеопамяти становится очевидной после написания первого приложения DirectDraw. На большинстве новых видеокарт установлено 4 Мбайт памяти, но многие карты имеют лишь 2 Мбайт. Это прискорбно, потому что при использовании режимов High и True Color даже 4 Мбайт оказывается не так уж много.

С увеличением разрешения проблема становится еще острее. Например, режим 800#215;600#215;24 использует почти 2 Мбайт видеопамяти даже без вторичного буфера. В зависимости от объема установленной памяти можно рекомендовать использование различных видеорежимов, обеспечивающих хорошее быстродействие.

Использование видеопамяти следует тщательно продумать. Например, для видеокарт с памятью в 2 и 4 Мбайт можно использовать различные схемы распределения поверхностей. Если ваше приложение работает со множеством мелких поверхностей, попробуйте протестировать его и выяснить, какие поверхности используются чаще других. Часто используемые поверхности следует по возможности размещать в видеопамяти.

Когда свободная видеопамять кончается, поверхности создаются в системной памяти. Хотя системная память и обладает некоторыми преимуществами по сравнению с видеопамятью, быстродействие не относится к их числу. Некоторые видеокарты поддерживают передачу данных из системной памяти через DMA (Direct Memory Access, прямой доступ к памяти), но это временная мера, потому что в ближайшем будущем будет реализована спецификация шины AGP (Accelerated Graphics Port, ускоренный графический порт). AGP позволит видеокарте обращаться к системной памяти с минимальными потерями или без потерь производительности. Более того, AGP-видеокарта может вообще обходиться без видеопамяти и работать исключительно с системной памятью.

Спецификация AGP проектировалась в первую очередь для 3D-приложений, но поскольку библиотека Direct3D построена на базе DirectDraw (и использует DirectDraw для внутреннего распределения памяти), от новых возможностей AGP выигрывает и DirectDraw. К сожалению, для выхода новых аппаратных спецификаций на массовый рынок требуется немало времени. К тому же нет никаких гарантий, что AGP победит — какой-нибудь конструктивный просчет или отсутствие поддержки со стороны производителей может подорвать ее успех.

FPS — еще не все

В наши дни часто приходится слышать о частоте вывода кадров, или FPS (Frames Per Second, количество кадров в секунду). Этот показатель стал мерой для сравнения графических Windows-приложений. Конечно, DirectDraw позволяет создавать приложения с высоким FPS, однако не стоит переоценивать важность этой характеристики.

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

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

Полезные хлопоты с палитрами

Не так давно в высокопроизводительных графических приложениях можно было сколько-нибудь приемлемо работать, лишь используя 8-битные режимы. Так как 8-битные режимы являются палитровыми, палитры были неотъемлемой частью жизни; программист был вынужден либо пользоваться ими, либо отказаться от попыток создания высокопроизводительных графических приложений.

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

И все же перед тем, как выбрасывать поддержку 8-битных режимов из своего приложения, подумайте о преимуществах палитровых видеорежимов. 8-битные режимы очень трудно превзойти в области быстродействия. Они используют вдвое меньше памяти по сравнению с режимами High color и вчетверо меньше — по сравнению с True Color. Меньшие затраты означают увеличение свободной видеопамяти, а это в свою очередь ведет к повышению быстродействия.

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

Долой аппаратную зависимость!

Если в приложении используются возможности, поддерживаемые лишь некоторыми видеокартами, такое приложение называется аппаратно-зависимым (device-dependent). Приложение не должно полагаться на присутствие конкретной видеокарты или ее специфические возможности, если только вы не пишете демонстрационную программу для производителя этих видеокарт. Подобная зависимость ограничивает рынок сбыта и раздражает пользователей, чьи видеокарты не обладают необходимыми аппаратными возможностями.

Единственные возможности видеокарт, на которые можно рассчитывать, — переключение страниц, блиттинг и работа с цветовыми ключами источника (если они не поддерживаются видеокартой, то будут эмулироваться в DirectDraw). Используя специфические возможности видеокарт (такие, как работа с цветовыми ключами приемника, альфа-наложение или оверлеи), вы лишаетесь потенциальных покупателей.

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

Перестановка кресел на «Титанике»

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

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

По сравнению с техникой, применявшейся в эпоху перфокарт, современный компьютер невероятно быстр и мощен. Экономить каждый байт в современном программном пакете — то же самое, что пытаться переставлять кресла на палубе «Титаника». Никакого толку от этого занятия не будет, хуже того, оно отвлечет вас от решения настоящих проблем с быстродействием.

Будущее DirectX

Сейчас можно сказать, что разработка приложений на базе DirectX оказалась сложнее, чем хотелось бы. Создание полноценных, переносимых приложений DirectX осложняется целым рядом проблем.

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

Изменяющиеся API — штука неприятная, но на самом деле все не так плохо, как кажется. В результате изменений может нарушиться компиляция ваших проектов, но это не значит, что существующие продукты перестанут работать. Достаточно понять, какая функция или флаг изменились, и программа снова легко приводится в рабочее состояние. Кроме того, для изменения API находятся веские (как правило) причины, и усовершенствования идут нам только на пользу.

С разными runtime-частями проблем оказывается несколько больше. COM гарантирует, что ваши программы будут работать с новыми версиями DirectX, но изменения в программной эмуляции и драйверах устройств могут отразиться на быстродействии. В некоторых устаревших runtime-частях отсутствует программная эмуляция возможностей, поддерживаемых в новых версиях. Если это произойдет и ваша программа не сможет работать, ей остается лишь вывести окно с сообщением и вежливо откланяться. Помните: хуже программы, которая отказывается работать на конкретной машине, может быть только такая, которая при попытке запуска приводит к зависанию системы.

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

Некоторые недостатки должны исчезнуть в ближайшем будущем, после интеграции DirectX в Windows. Будущие версии Windows 95, подобно Windows NT 4.0, будут содержать встроенную runtime-часть DirectX и драйверы устройств. Несомненно, дальнейшее развитие DirectX облегчит жизнь и пользователям, и разработчикам.

Заключение

Пора браться за программирование. В главе 3 мы подробно рассмотрим полноценную программу, а заодно познакомимся с важнейшими приемами программирования для DirectDraw.