Шрифт:
Совет Разработчику
Не думайте об оптимизации игрового кода по скорости или размеру до тех пор, пока игра не заработала. Оптимизированный код зачастую может содержать хитрые алгоритмы, сложные для отладки. Следовательно, всегда следует начинать с работающего кода, когда приходит время внедрять приемы оптимизации.
Компиляция без отладочной информации
Возможно, самая простая оптимизация не подразумевает программирования вообще. Я говорю об исключении отладочной информации, которая по умолчанию включается в состав классов при использовании стандартного компилятора (javac). По умолчанию Java-компилятор включает дополнительную отладочную информацию в классы файлов, которая помогает отладчикам анализировать и идентифицировать код. По окончании отладки игры важно отключить отладочную информацию в компиляторе, используя ключ – g: none. Ниже приведен пример использования этого ключа:
javac -g:none MyMIDlet.java
К счастью, все примеры в этой книге, расположенные на прилагаемом компакт-диске, откомпилированы с выключенной отладочной информацией. Вам придется отключить опцию – g: none, если вы планируете отладить какой-либо из примеров.
Исключение ненужных вычислений
Следующая методика оптимизации – это простой прием программирования, исключающий ненужные вычисления. Такие вычисления проблематичны, поскольку они занимают время процессора. Ниже приведен пример кода, который выполняет ненужные вычисления:for (int i = 0; i < size; i++) a = (b + c)/i;
Несмотря на то что сложение (b + c) – это весьма эффективный фрагмент кода, лучше вынести его за пределы цикла:
int tmp = b + c;
for (int i = 0; i < size; i++)
a = tmp/i;Такое простое изменение может иметь значительный эффект, в зависимости от числа повторов выполнения цикла. Есть еще один прием оптимизации, который вы, вероятно, могли упустить. Обратите внимание на вызов метода size. С точки зрения производительности, лучше сохранить возвращаемое им значение в отдельную переменную, чтобы избежать вызова метода при каждом повторе цикла:
int s = size;
int tmp = b + c;
for (int i = 0; i < s; i++)
a = tmp/i;
Исключение общих выражений
Говоря об оптимизации выражений, рассмотрим еще одну проблему, которая снижает скорость выполнения кода: общие выражения. Вы можете часто использовать выражения в коде, не осознавая последствий. В разгар работы можно использовать одно и то же выражение повторно, вместо того чтобы вычислить его значение однажды и присвоить переменной:b = Math.abs(a) * c; d = e / (Math.abs(a) + b);
Повторный вызов метода abs – трудоемкая операция, вместо этого лучше вызвать метод однократно, а результат сохранить во временной переменной:
int tmp = Math.abs(a);
b = tmp * c;
d = e / (tmp + b);
Преимущества локальных переменных
Возможно, вы не задумывались, но Java-коду требуется больше времени обратиться к переменным класса, чем к локальным переменным. Это связано с тем, как осуществляется доступ к двум различным типам данных. На практике следует использовать локальные переменные, а не переменные класса, если вопрос производительности критичен. Например, если внутри цикла происходит постоянное обращение к переменной класса, то целесообразно присвоить локальной переменной значение переменной класса и внутри цикла работать с локальной переменной. Ниже приведен пример кода:for (int i = 0; i < 1000; i++) a = obj.b * i;
Как вы видите, внутри цикла обращение к переменной объекта obj выполняется 1000 раз. Оптимизация этого кода подразумевает замену переменной obj.b локальной переменной, к которой будет выполняться обращение в цикле:
int localb = obj.b;
for (int i = 0; i < 1000; i++)
a = localb * i;
Раскрытие циклов
Популярный «лобовой» прием оптимизации известен как раскрытие циклов, в результате которого исключается использование циклов. Даже простой цикл-счетчик перегружает процессор операциями сравнения и инкрементирования. Это может показаться неважным, однако в мобильных играх важен каждый бит оптимизации.
Раскрытие цикла подразумевает его замену «грубым» эквивалентом. Чтобы лучше понять это, давайте рассмотрим пример:for (int i = 0; i < 1000; i++) a[i] = 25;
Это, вероятно, выглядит как эффективный фрагмент кода, и на самом деле это так. Но если вы хотите ускорить его выполнение, то раскройте цикл:
int i = 0;
for (int j = 0; j < 100; j++) {
a[i++] = 25;
a[i++] = 25;
a[i++] = 25;
a[i++] = 25;
a[i++] = 25;
a[i++] = 25;
a[i++] = 25;
a[i++] = 25;
a[i++] = 25;
a[i++] = 25;
}В приведенном примере вы сократили число повторений цикла на порядок (с 1000 до 100), но вы загрузили процессор операцией инкрементирования внутри цикла. В целом, приведенный код работает быстрее исходного, однако не ждите чудес. Раскрытие циклов может быть полезным в ряде случаев, но я не советую вам ставить этот метод оптимизации на первое место. Такой метод следует применять в играх, в которых важна каждая миллисекунда производительности.
Сжатие и затенение кода
Когда ваша игра готова к распространению, для сокращения объема кода можно использовать автоматическое средство. Я говорю об инструментах сжатия и затенения кода, которые сжимают код Java-программы и переименовывают переменные, чтобы усложнить процесс восстановления кода. Даже самый тщательно оптимизированный код наверняка будет содержать несколько неиспользуемых пакетов, классов, методов и переменных – именно для этого и нужна программа сжатия кода (shrinker). Программа затенения кода (obfuscator) предназначена не для повышения эффективности кода, а для его защиты и копирования.
Большинство программ сжатия и затенения кода объединены в один инструмент. Например, открытый инструмент ProGuard, служит как для сжатия, так и для затенения кода. Эту программу можно загрузить с адресаТакие инструменты, как ProGuard вырезают комментарии из кода и неиспользуемый код, а также переименовывают идентификаторы, используя более короткие криптографические имена. В результате получается класс, который на 20–50 % меньше и более защищенный по сравнению с исходным.
Анализ кода мобильной игры
Программисты часто говорят, что 90 % времени выполнения игры тратится на выполнение 10 % игрового кода. Это означает, что лишь малая часть кода действительно отвечает за выполнение игры. Вам необходимо сосредоточить внимание лишь на 10 % кода. Вы можете направить усилия по оптимизации на небольшой фрагмент программы, тогда вероятность создания эффективного мидлета значительно возрастает.
Принципиальная трудность для большинства разработчиков мобильных игр при начале оптимизации заключается не в использовании приемов оптимизации, а в поиске тех 10 % кода, которые будут выполняться 90 % времени. Выявление малой части кода, определяющей быстродействие мобильной игры, – это самая сложная грань процесса оптимизации. К счастью, для решения этого вопроса можно использовать специальный инструмент.
Анализатор (profiler) – это инструмент, которой анализирует программу во время ее выполнения и сообщает, сколько процессорного времени и циклов заняло выполнение определенной части программы. Вы можете изучить данные, собранные анализатором и определить, какая часть вашей программы выполняется чаще всего. Эта информация может указать, где следует приложить усилия и провести оптимизацию, используя приемы и методы, описанные в этой главе.
Пакет J2ME Wireless Toolkit поставляется с анализатором Java, который достаточно прост в использовании. Для начала запустите приложение Preferences (Настройки) стандартной установки J2ME Wireless Toolkit. Перейдите на вкладку Monitor (Монитор), и вы увидите окно как на рис. 17.3.
Единственное отличие между окном, представленным на рисунке, и окном на экране вашего компьютера может заключаться в том, что у вас, вероятно, не поставлена галочка в окошке метки Enable Profiling (Включить анализ). Поставьте галочку, чтобы разрешить анализ мидлетов. Когда вы щелкнете по кнопке OK, анализатор Java готов, он запустится в следующий раз, когда вы будете использовать эмулятор J2ME.
Следующий шаг – это запустить игру в эмуляторе, например, Henway, разработанную в главе 7. По окончании работы эмулятора приложение анализатора автоматически запускается и показывает вам результаты анализа игры. На рис. 17.4 показан результат анализа игры Henway, проведенный на моем компьютере.