Статьи

Оптимизация ActionScript 3.0: практический пример

Целью оптимизации кода является максимизация производительности ваших Flash-ресурсов при минимальном использовании системных ресурсов — ОЗУ и ЦП. В этом учебном пособии, начиная с работающего, но ресурсоемкого приложения Flash, мы постепенно применим множество настроек оптимизации к его исходному коду, и в конечном итоге получим более быстрый и компактный SWF.


Давайте посмотрим на конечный результат, к которому мы будем стремиться:

Обратите внимание, что статистика «Используемая память» и «Загрузка ЦП» основана на всех SWF-файлах, открытых вами во всех окнах браузера, включая рекламные баннеры в формате Flash и тому подобное. Это может сделать SWF более ресурсоемким, чем на самом деле.


Фильм Flash имеет два основных элемента: имитация огня частиц и график, показывающий потребление ресурсов анимации с течением времени. Розовая линия графика отслеживает общий объем памяти, потребляемой фильмом в мегабайтах, а зеленая линия отображает загрузку процессора в процентах.

Объекты ActionScript занимают большую часть памяти, выделенной для Flash Player, и чем больше объектов ActionScript содержится в фильме, тем выше его потребление памяти. Чтобы поддерживать низкое потребление памяти программой, проигрыватель Flash Player регулярно выполняет сборку мусора , просматривая все объекты ActionScript и освобождая из памяти те, которые больше не используются.

График потребления памяти обычно показывает холмистый восходящий-нисходящий паттерн, который падает каждый раз, когда выполняется сборка мусора, а затем медленно растет по мере создания новых объектов. Линия графика, которая только поднимается вверх, указывает на проблему со сборкой мусора, поскольку это означает, что новые объекты добавляются в память, а ни один не удаляется. Если такая тенденция сохранится, Flash Player может в конечном итоге потерпеть крах, так как ему не хватает памяти.

Загрузка ЦП рассчитывается путем отслеживания частоты кадров фильма. Частота кадров Flash-фильма очень похожа на частоту его пульса С каждым ударом Flash Player обновляет и отображает все элементы на экране, а также выполняет все необходимые задачи ActionScript.

Именно частота кадров определяет, сколько времени проигрыватель Flash Player должен тратить на каждый удар, поэтому частота кадров 10 кадров в секунду (fps) означает не менее 100 миллисекунд на удар. Если все необходимые задачи будут выполнены в течение этого времени, Flash Player будет ждать оставшееся время, прежде чем перейти к следующему удару. С другой стороны, если требуемые задачи в конкретном такте слишком загружены процессором для выполнения в течение данного периода времени, то частота кадров автоматически замедляется, чтобы выделить дополнительное время. Когда нагрузка уменьшится, частота кадров снова увеличится до заданной.

(Частота кадров также может автоматически уменьшаться до 4 кадров в секунду с помощью проигрывателя Flash Player, когда родительское окно программы теряет фокус или выходит за пределы экрана. Это делается для сохранения системных ресурсов, когда внимание пользователя сосредоточено в другом месте.)

Все это означает, что на самом деле существует два типа частоты кадров: тот, который вы изначально установили и надеетесь, что ваш фильм всегда работает, и тот, на котором он действительно работает. Мы назовем тот, который установлен вами, целевую частоту кадров, и тот, который фактически работает с фактической частотой кадров.

Загрузка процессора графиком рассчитывается как отношение фактической частоты кадров к целевой. Формула, используемая для расчета этого:

CPU load = ( target frame rate - actual frame rate ) / actual frame rate * 100

Например, если целевая частота кадров установлена ​​на 50 кадров в секунду, но фильм на самом деле работает со скоростью 25 кадров в секунду, загрузка процессора составит 50%, то есть ( 50 - 25 )/ 50 * 100 50-25 ( 50 - 25 )/ 50 * 100 .

Обратите внимание, что это не фактический процент системных ресурсов ЦП, используемых фильмом, а скорее приблизительная оценка фактического значения. Для процесса оптимизации, описанного здесь, эта оценка является достаточно хорошим показателем для поставленной задачи. Чтобы получить фактическое использование процессора, используйте инструменты, предоставляемые вашей операционной системой, например, диспетчер задач в Windows. Если посмотреть прямо сейчас, это показывает, что неоптимизированный фильм использует 53% ресурсов ЦП, в то время как график фильма показывает загрузку ЦП 41,7%.

снимок экрана Flames.swf после того, как все вышеописанные шаги были применены.

ПОЖАЛУЙСТА, ОБРАТИТЕ ВНИМАНИЕ: Все скриншоты фильма в этом руководстве были взяты из автономной версии Flash Player. График, скорее всего, покажет разные цифры в вашей системе, в зависимости от вашей операционной системы, браузера и версии Flash Player. Наличие в настоящий момент любых других приложений Flash в разных окнах браузера или проигрывателях флэш-памяти также может повлиять на использование памяти, о котором сообщают некоторые системы. При анализе производительности вашей программы всегда проверяйте, не запущены ли другие программы Flash, поскольку они могут повредить ваши показатели.

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


Исходный код фильма показан ниже и содержит только один класс с именем Flames , который также является классом документа . Класс содержит набор свойств для отслеживания памяти фильма и истории загрузки процессора, который затем используется для рисования графика. Статистика загрузки памяти и процессора вычисляется и обновляется в Flames.getStats() , а график рисуется с помощью вызова Flames.drawGraph() для каждого кадра. Чтобы создать эффект огня, метод Flames.createParticles() сначала генерирует сотни частиц каждую секунду, которые сохраняются в массиве fireParticles . Затем этот массив проходит через Flames.drawParticles() , который использует свойства каждой частицы для создания эффекта.

Потратьте некоторое время на изучение класса Flames . Можете ли вы заметить какие-либо быстрые изменения, которые будут иметь большое значение для оптимизации программы?

001
002
003
004
005
006
007
008
009
010
011
012
013
014
+015
016
+017
018
019
020
021
022
023
024
025
026
027
028
029
+030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
+055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package com.pjtops{
import flash.display.MovieClip;
import flash.events.Event;
import fl.motion.Color;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.text.TextField;
import flash.system.System;
import flash.utils.getTimer;
 
public class Flames extends MovieClip{
 
    private var memoryLog = new Array();
    private var memoryMax = 0;
    private var memoryMin = 0;
    private var memoryColor;
     
    private var ticks = 0;
    private var frameRate = 0;
    private var cpuLog = new Array();
    private var cpuMax = 0;
    private var cpuMin = 0;
    private var cpuColor;
    private var cpu;
 
    private var lastUpdate = 0;
    private var sampleSize = 30;
    private var graphHeight;
    private var graphWidth;
 
    private var fireParticles = new Array();
    private var fireMC = new MovieClip();
    private var palette = new Array();
    private var anchors = new Array();
    private var frame;
     
    // class constructor.
    public function Flames() {
        addChildAt( fireMC, 1 );
        frame = new Rectangle( 2, 2, stage.stageWidth — 2, stage.stageHeight — 2 );
         
        var colWidth = Math.floor( frame.width / 10 );
        for( var i = 0; i < 10; i++ ){
            anchors[i] = Math.floor( i * colWidth );
        }
         
        setPalette();
 
        memoryColor = memoryTF.textColor;
        cpuColor = cpuTF.textColor;
        graphHeight = graphMC.height;
        graphWidth = graphMC.width;
         
        frameRate = stage.frameRate;
         
        addEventListener( Event.ENTER_FRAME, drawParticles );
        addEventListener( Event.ENTER_FRAME, getStats );
        addEventListener( Event.ENTER_FRAME, drawGraph );
    }
     
    //creates a collection of colors for the flame particles, and stores them in palette
    private function setPalette(){
        var black = 0x000000;
        var blue = 0x0000FF;
        var red = 0xFF0000;
        var orange = 0xFF7F00;
        var yellow = 0xFFFF00;
        var white = 0xFFFFFF;
        palette = palette.concat( getColorRange( black, blue, 10 ) );
        palette = palette.concat( getColorRange( blue, red, 30 ) );
        palette = palette.concat( getColorRange( red, orange, 20 ) );
        palette = palette.concat( getColorRange( orange, yellow, 20 ) );
        palette = palette.concat( getColorRange( yellow, white, 20 ) );
    }
     
    //returns a collection of colors, made from different mixes of color1 and color2
    private function getColorRange( color1, color2, steps){
        var output = new Array();
        for( var i = 0; i < steps; i++ ){
            var progress = i / steps;
            var color = Color.interpolateColor( color1, color2, progress );
            output.push( color );
        }
        return output;
    }
     
    // calculates statistics for the current state of the application, in terms of memory used and the cpu %
    private function getStats( event ){
        ticks++;
        var now = getTimer();
         
        if( now — lastUpdate < 1000 ){
            return;
        }else {
            lastUpdate = now;
        }
         
         
        cpu = 100 — ticks / frameRate * 100;
        cpuLog.push( cpu );
        ticks = 0;
        cpuTF.text = cpu.toFixed(1) + ‘%’;
        if( cpu > cpuMax ){
            cpuMax = cpu;
            cpuMaxTF.text = cpuTF.text;
        }
        if( cpu < cpuMin || cpuMin == 0 ){
            cpuMin = cpu;
            cpuMinTF.text = cpuTF.text;
        }
                 
         
        var memory = System.totalMemory / 1000000;
        memoryLog.push( memory );
        memoryTF.text = String( memory.toFixed(1) ) + ‘mb’;
        if( memory > memoryMax ){
            memoryMax = memory;
            memoryMaxTF.text = memoryTF.text;
        }
        if( memory < memoryMin || memoryMin == 0 ){
            memoryMin = memory;
            memoryMinTF.text = memoryTF.text;
        }
    }
     
    //render’s a graph on screen, that shows trends in the applications frame rate and memory consumption
    private function drawGraph( event ){
        graphMC.graphics.clear();
        var ypoint, xpoint;
        var logSize = memoryLog.length;
         
        if( logSize > sampleSize ){
            memoryLog.shift();
            cpuLog.shift();
            logSize = sampleSize;
        }
        var widthRatio = graphMC.width / logSize;
         
        graphMC.graphics.lineStyle( 3, memoryColor, 0.9 );
        var memoryRange = memoryMax — memoryMin;
        for( var i = 0; i < memoryLog.length; i++ ){
            ypoint = ( memoryLog[i] — memoryMin ) / memoryRange * graphHeight;
            xpoint = (i / sampleSize) * graphWidth;
            if( i == 0 ){
                graphMC.graphics.moveTo( xpoint, -ypoint );
                continue;
            }
            graphMC.graphics.lineTo( xpoint, -ypoint );
        }
         
        graphMC.graphics.lineStyle( 3, cpuColor, 0.9 );
        for( var j = 0; j < cpuLog.length; j++ ){
            ypoint = cpuLog[j] / 100 * graphHeight;
            xpoint = ( j / sampleSize ) * graphWidth;
            if( j == 0 ){
                graphMC.graphics.moveTo( xpoint, -ypoint );
                continue;
            }
            graphMC.graphics.lineTo( xpoint, -ypoint );
        }
    }
     
    //renders each flame particle and updates it’s values
    private function drawParticles( event ) {
        createParticles( 20 );
     
        fireMC.graphics.clear();
        for ( var i in fireParticles ) {
            var particle = fireParticles[i];
             
            if (particle.life == 0 ) {
                delete( fireParticles[i] );
                continue;
            }
             
            var size = Math.floor( particle.size * particle.life/100 );
            var color = palette[ particle.life ];
            var transperency = 0.3;
             
            if( size < 3 ){
                size *= 3;
                color = 0x333333;
                particle.x += Math.random() * 8 — 4;
                particle.y -= 2;
                transperency = 0.2;
            }else {
                particle.y = frame.bottom — ( 100 — particle.life );
                 
                if( particle.life > 90 ){
                    size *= 1.5;
                }else if( particle.life > 45){
                    particle.x += Math.floor( Math.random() * 6 — 3 );
                    size *= 1.2;
                }else {
                    transperency = 0.1;
                    size *= 0.3;
                    particle.x += Math.floor( Math.random() * 4 — 2 );
                }
            }
              
            fireMC.graphics.lineStyle( 5, color, 0.1 );
            fireMC.graphics.beginFill( color, transperency );
            fireMC.graphics.drawCircle( particle.x, particle.y, size );
            fireMC.graphics.endFill();
            particle.life—;
        }
    }
 
    //generates flame particle objects
    private function createParticles( count ){
        var anchorPoint = 0;
        for(var i = 0; i < count; i++){
            var particle = new Object();
            particle.x = Math.floor( Math.random() * frame.width / 10 ) + anchors[anchorPoint];
            particle.y = frame.bottom;
            particle.life = 70 + Math.floor( Math.random() * 30 );
            particle.size = 5 + Math.floor( Math.random() * 10 );
            fireParticles.push( particle );
             
            if(particle.size > 12){
                particle.size = 10;
            }
            particle.anchor = anchors[anchorPoint] + Math.floor( Math.random() * 5 );
             
            anchorPoint = (anchorPoint == 9)?
        }
    }
     
}
}

Это много, чтобы принять, так что не волнуйтесь — мы пройдем через различные улучшения в оставшейся части этого урока.


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

Например, изменение этого

1
2
protected var memoryLog = new Array();
protected var memoryMax = 0;

к этому.

1
2
protected var memoryLog:Array = new Array();
protected var memoryMax:Number = 0;

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


снимок экрана Flames.swf после того, как все вышеописанные шаги были применены.

На этом снимке экрана показан новый Flash-фильм после применения строгой типизации. Мы видим, что хотя это не повлияло на текущую или максимальную загрузку процессора, минимальное значение упало с 8,3% до 4,2%. Максимальное потребление памяти уменьшилось с 9 МБ до 8,7 МБ.

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


Actionscript предоставляет три числовых типа данных: Number , uint и int . Number из трех типов использует больше всего памяти, поскольку может хранить большие числовые значения, чем два других. Это также единственный тип, способный хранить числа с десятичными дробями.

Класс Flames имеет много числовых свойств, каждый из которых использует тип данных Number . Поскольку int и uint являются более компактными типами данных, мы можем сэкономить часть памяти, используя их вместо Number s во всех ситуациях, когда нам не нужны десятичные дроби.

Хорошим примером являются циклы и индексы Array, поэтому, например, мы собираемся изменить

1
2
3
for( var i:Number = 0; i < 10; i++ ){
    anchors[i] = Math.floor( i * colWidth );
}

в

1
2
3
for( var i:int = 0; i < 10; i++ ){
    anchors[i] = Math.floor( i * colWidth );
}

Свойства cpu , cpuMax и memoryMax останутся числами, поскольку они, скорее всего, будут хранить дробные данные, тогда как cpuColor , cpuColor и ticks могут быть изменены на uints, поскольку они всегда будут хранить положительные целые числа.


Вызовы метода дороги, особенно вызов метода из другого класса. Хуже, если этот класс принадлежит другому пакету или является статическим методом. Лучшим примером здесь является метод Math.floor() , используемый в классе Flames для округления дробных чисел. Этого вызова метода можно избежать, используя uints вместо Numbers для хранения целых чисел.

01
02
03
04
05
06
07
08
09
10
// So instead of having:
anchors[i] = Math.floor( i * colWidth );
// we instead cast the value to a uint
anchors[i] = uint( i * colWidth );
 
 
// The same optimization can be performed by simply assigning the uint data type, for example changing
var size:uint = Math.floor( particle.size * particle.life/100 );
// into
var size:uint = particle.size * particle.life/100;

В приведенном выше примере вызов Math.floor() не нужен, поскольку Flash автоматически округляет любое значение дробного числа, назначенное для uint.


Flash Player, очевидно, считает, что умножение проще, чем деление, поэтому мы пройдемся по классу Flames и преобразуем любую математику деления в эквивалентную математику умножения. Формула преобразования включает в себя получение обратного числа в правой части операции и умножение его на число в левой части. Обратное число вычисляется путем деления 1 на это число.

1
2
var colWidth:uint = frame.width / 10;
var colWidth:uint = frame.width * 0.1;

Давайте кратко рассмотрим результаты наших недавних усилий по оптимизации. Загрузка ЦП наконец-то улучшилась, снизившись с 41,7% до 37,5%, но потребление памяти говорит о другом. Максимальный объем памяти увеличился до 9,4 МБ, что является самым высоким уровнем, а острые острые углы на графике показывают, что сборка мусора выполняется чаще. Некоторые методы оптимизации будут иметь это обратное влияние на память и загрузку процессора, улучшая одно за счет другого. Поскольку потребление памяти почти вернулось на круги своя, еще предстоит проделать большую работу.

снимок экрана Flames.swf после того, как все вышеописанные шаги были применены.

Вы тоже можете сыграть свою роль в сохранении окружающей среды. Перерабатывайте свои объекты при написании кода AS3, чтобы уменьшить количество энергии, потребляемой вашими программами. Как создание, так и уничтожение новых объектов являются дорогостоящими операциями. Если ваша программа постоянно создает и уничтожает объекты одного и того же типа, можно добиться большого прироста производительности, переработав эти объекты. Глядя на класс Flames , мы видим, что множество объектов частиц создается и уничтожается каждую секунду:

01
02
03
04
05
06
07
08
09
10
11
private function drawParticles( event ):void {
    createParticles( 20 );
 
    fireMC.graphics.clear();
    for ( var i:* in fireParticles ) {
        var particle:Object = fireParticles[i];
         
        if (particle.life == 0 ) {
            delete( fireParticles[i] );
            continue;
        }

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

Сначала мы создаем новый массив с именем inactiveFireParticles[] , в котором хранятся ссылки на частицы, свойство жизни которых равно нулю (мертвые частицы). В drawParticles() вместо удаления мертвой частицы мы добавляем ее в массив inactiveFireParticles[] .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
private function drawParticles( event ):void {
    createParticles( 20 );
 
    fireMC.graphics.clear();
    for ( var i:* in fireParticles ) {
        var particle:Object = fireParticles[i];
         
        if( particle.life <= 0 ) {
            if( particle.life == 0 ){
                particle.life = -1;
                inactiveFireParticles.push( particle );
            }
            continue;
        }

Затем мы модифицируем метод createParticles() чтобы сначала проверить наличие любых сохраненных частиц в массиве inactiveFireParticles[] , и использовать их все перед созданием новых частиц.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private function createParticles( count ):void{
    var anchorPoint = 0;
    for(var i:uint = 0; i < count; i++){
     
        var particle:Object;
        if( inactiveFireParticles.length > 0 ){
            particle = inactiveFireParticles.shift();
        }else {
            particle = new Object();
            fireParticles.push( particle );
        }
         
        particle.x = uint( Math.random() * frame.width * 0.1 ) + anchors[anchorPoint];
        particle.y = frame.bottom;
        particle.life = 70 + uint( Math.random() * 30 );
        particle.size = 5 + uint( Math.random() * 10 );
         
        if(particle.size > 12){
            particle.size = 10;
        }
        particle.anchor = anchors[anchorPoint] + uint( Math.random() * 5 );
         
        anchorPoint = (anchorPoint == 9)?
    }
}

При создании новых объектов или массивов использование буквального синтаксиса происходит быстрее, чем использование оператора new .

1
2
3
4
5
private var memoryLog:Array = new Array();
private var memoryLog:Array = [];
 
particle = new Object();
particle = {};

Классы в ActionScript могут быть как герметичными, так и динамическими. По умолчанию они запечатаны. Это означает, что в классе должны быть определены только те свойства и методы, которые могут быть получены из объекта. С динамическими классами новые свойства и методы могут быть добавлены во время выполнения. Запечатанные классы более эффективны, чем динамические, потому что некоторые оптимизации производительности Flash Player могут быть выполнены, когда все возможные функциональные возможности, которые может когда-либо иметь класс, известны заранее.

Внутри класса Flames тысячи частиц расширяют встроенный класс Object, который является динамическим. Поскольку не требуется добавлять новые свойства к частице во время выполнения, мы сэкономим больше ресурсов, создав специальный закрытый класс для частиц.

Вот новая Particle, которая была добавлена ​​в тот же файл Flames.as.

1
2
3
4
5
6
7
class Particle{
    public var x:uint;
    public var y:uint;
    public var life:int;
    public var size:Number;
    public var anchor:uint;
}

Метод createParticles () также будет createParticles , изменив строку

1
2
var particle:Object;
particle = {};

вместо этого читать:

1
2
var particle:Particle;
particle = new Particle();

Как и класс Object, MovieClips являются динамическими классами. Класс MovieClip наследуется от класса Sprite, и основное различие между ними заключается в том, что MovieClip имеет временную шкалу. Поскольку спрайты имеют все функции MovieClips за вычетом временной шкалы, используйте их всякий раз, когда вам нужен объект DisplayObject, который не нуждается во временной шкале. Класс Flames расширяет MovieClip, но не использует временную шкалу, поскольку вся его анимация контролируется с помощью ActionScript. fireMC частицы рисуются на fireMC , который также является MovieClip, который не использует свою временную шкалу.

Вместо этого мы изменили Flames и fireMC для расширения Sprite, заменив:

1
2
3
import flash.display.MovieClip;
private var fireMC:MovieClip = new MovieClip();
public class Flames extends MovieClip{

с

1
2
3
import flash.display.Sprite;
private var fireMC:Sprite = new Sprite();
public class Flames extends Sprite{

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

1
2
import flash.display.Shape;
private var fireMC:Shape = new Shape();
снимок экрана Flames.swf после того, как все вышеописанные шаги были применены.

График показывает значительные улучшения в потреблении памяти, при этом он падает и остается стабильным на уровне 4,8 МБ. Края зубьев пилы были заменены почти прямой горизонтальной линией, что означает, что сбор мусора в настоящее время проводится редко. Но загрузка ЦП в основном снова вернулась к своему первоначальному высокому уровню 41,7%.


Они говорят, что более 50% времени программы тратится на выполнение 10% ее кода, и большая часть этих 10%, скорее всего, будет занята циклами. Многие методы оптимизации цикла предполагают размещение как можно большего количества интенсивных операций ЦП вне тела цикла. Эти операции включают создание объектов, поиск переменных и вычисления.

1
2
3
for( var i = 0; i < memoryLog.length; i++ ){
    // loop body
}

Первый цикл в drawGraph() показан выше. Цикл проходит через каждый элемент массива memoryLog , используя каждое значение для построения точек на графике. В начале каждого прогона он ищет длину массива memoryLog и сравнивает ее со счетчиком цикла. Если массив memoryLog содержит 200 элементов, цикл запускается 200 раз и выполняет тот же поиск 200 раз. Поскольку длина memoryLog не изменяется, повторные поиски расточительны и не нужны. Лучше посмотреть значение memoryLog.length только один раз до начала поиска и сохранить его в локальной переменной, поскольку доступ к локальной переменной будет быстрее, чем доступ к свойству объекта.

1
2
3
4
var memoryLogLength:uint = memoryLog.length;
for( var i = 0; i < memoryLogLength; i++ ){
    // loop body
}

В классе Flames мы настраиваем два цикла в drawGraph() как показано выше.


Рассмотрим блок if..else условий if..else ниже, полученный из drawParticles ():

01
02
03
04
05
06
07
08
09
10
if( particle.life > 90 ){ // a range of 10 values, between 91 — 100
    size *= 1.5;
}else if( particle.life > 45){ // a range of 45 values, between 46 — 90
    particle.x += Math.random() * 6 — 3;
    size *= 1.2;
}else { // a range of 45 values, values between 0 — 45
    transperency = 0.1;
    size *= 0.3;
    particle.x += Math.random() * 4 — 2;
}

Значение жизни частицы может быть любым числом от 0 до 100. Предложение if проверяет, является ли жизнь текущей частицы между 91 и 100, и если да, то выполняет код в этом блоке. Предложение else-if проверяет значение между 46 и 90, в то время else предложение else принимает оставшиеся значения, между 0 и 45. Учитывая, что первая проверка также имеет наименьшую вероятность успеха, так как она имеет наименьший диапазон чисел, она должно быть последним проверенным условием. Блок переписывается, как показано ниже, так что наиболее вероятные условия оцениваются первыми, что делает оценки более эффективными.

01
02
03
04
05
06
07
08
09
10
if( particle.life < 46 ){
    transperency = 0.1;
    size *= 0.3;
    particle.x += Math.random() * 4 — 2;
}else if( particle.life < 91 ){
    particle.x += Math.random() * 6 — 3;
    size *= 1.2;
}else {
    size *= 1.5;
}

Метод Array.push() довольно часто используется в классе Flames . Он будет заменен более быстрой техникой, которая использует свойство length массива.

1
2
cpuLog.push( cpu );
cpuLog[ cpuLog.length ] = cpu;

Когда мы знаем длину массива, мы можем заменить Array.push() еще более быстрой техникой, как показано ниже.

1
2
3
4
5
6
var output:Array = [];
for( var i:uint = 0; i < steps; i++ ){ // the value of i also starts at zero.
    var progress:Number = i / steps;
    var color:uint = Color.interpolateColor( color1, color2, progress );
    output[i] = color;
}

Классы Array и Vector очень похожи, за исключением двух основных отличий: Векторы могут хранить объекты только одного типа, и они более эффективны и быстрее, чем массивы. Поскольку все массивы в классе Flames либо хранят переменные только одного типа — ints, uints или Particles, как требуется — мы преобразуем их все в Векторы.

Эти массивы:

1
2
3
4
5
6
private var memoryLog:Array = [];
private var cpuLog:Array = [];
private var fireParticles:Array = [];
private var palette:Array = [];
private var anchors:Array = [];
private var inactiveFireParticles:Array = [];

… заменены их векторными эквивалентами:

1
2
3
4
5
6
private var memoryLog:Vector.<Number> = new Vector.<Number>();
private var cpuLog:Vector.<Number> = new Vector.<Number>();
private var fireParticles:Vector.<Particle> = new Vector.<Particle>();
private var palette:Vector.<uint> = new Vector.<uint>();
private var anchors:Vector.<uint> = new Vector.<uint>();
private var inactiveFireParticles:Vector.<Particle> = new Vector.<Particle>();

Затем мы модифицируем метод getColorRange() для работы с векторами, а не с массивами.

1
2
3
4
5
6
7
8
9
private function getColorRange( color1, color2, steps):Vector.<uint>{
    var output:Vector.<uint> = new Vector.<uint>();
    for( var i:uint = 0; i < steps; i++ ){
        var progress:Number = i / steps;
        var color:uint = Color.interpolateColor( color1, color2, progress );
        output[i] = color;
    }
    return output;
}

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

1
2
3
addEventListener( Event.ENTER_FRAME, drawParticles );
addEventListener( Event.ENTER_FRAME, getStats );
addEventListener( Event.ENTER_FRAME, drawGraph );

Класс Flames имеет три прослушивателя событий, вызывающих три разных метода, и все они связаны с событием ENTER_FRAME . В этом случае мы можем сохранить первый прослушиватель событий и избавиться от двух других, а затем drawParticles метод getStats() , который в свою очередь вызывает drawGraph() . В качестве альтернативы, мы можем просто создать новый метод, который getStats() вызывает getStats() , drawGraph() и drawParticles () для нас, затем иметь только один прослушиватель событий, связанный с новым методом. Однако второй вариант более дорогой, поэтому мы будем придерживаться первого.

1
2
3
4
// this line is added before the end of the <code> drawParticles </code>() method
getStats();
// this line is added before the end of the <code> getStats() </code> method
drawGraph();

Мы также удаляем параметр события (который содержит объект Event ) из drawGraph() и getStats() , так как они больше не нужны.


Поскольку эта Flash-анимация не требует взаимодействия с пользователем, мы можем освободить ее экранный объект от отправки ненужных событий мыши. В классе Flames мы делаем это, устанавливая mouseEnabled свойства mouseEnabled значение false . Мы также делаем то же самое для всех его потомков, устанавливая свойство mouseChildren в false . Следующие строки добавлены в конструктор Flames :

1
2
mouseEnabled = false;
mouseChildren = false;

Graphics.drawPath() оптимизирован для производительности при рисовании сложных контуров с множеством линий или кривых. В Flames.drawGraph() линии графика загрузки ЦП и потребления памяти рисуются с использованием комбинации методов Graphics.moveTo() и Graphics.lineTo() .

1
2
3
4
5
6
7
8
9
for( var i = 0; i < memoryLogLength; i++ ){
    ypoint = ( memoryLog[i] — memoryMin ) / memoryRange * graphHeight;
    xpoint = (i / sampleSize) * graphWidth;
    if( i == 0 ){
        graphMC.graphics.moveTo( xpoint, -ypoint );
        continue;
    }
    graphMC.graphics.lineTo( xpoint, -ypoint );
}

Мы заменяем оригинальные методы рисования вызовами Graphics.drawPath() . Дополнительным преимуществом пересмотренного кода ниже является то, что мы также можем удалить команды рисования из циклов.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
var commands:Vector.<int> = new Vector.<int>();
var data:Vector.<Number> = new Vector.<Number>();
 
for( var i = 0; i < memoryLogLength; i++ ){
    ypoint = ( memoryLog[i] — memoryMin ) / memoryRange * graphHeight;
    xpoint = (i / sampleSize) * graphWidth;
     
    if( i == 0 ){
        data[ data.length ] = xpoint;
        data[ data.length ] = -ypoint;
        commands[ commands.length ] = 1;
    }
 
    data[ data.length ] = xpoint;
    data[ data.length ] = -ypoint;
    commands[ commands.length ] = 2;
}
graphMC.graphics.drawPath( commands, data );

final атрибут указывает, что метод не может быть переопределен или класс не может быть расширен. Это также может сделать класс более быстрым, поэтому мы сделаем оба класса Flames и Particle окончательными.

Редактировать: Читатель Моко указал нам на эту замечательную статью Джексона Данстана, которая отмечает, что final ключевое слово на самом деле не влияет на производительность.

снимок экрана Flames.swf после того, как все вышеописанные шаги были применены.

Загрузка процессора теперь составляет 33,3%, а общий объем используемой памяти остается между 4,8 и 5 МБ. Мы прошли долгий путь от загрузки ЦП 41,7% и пикового объема памяти до 9 МБ!

Что приводит нас к одному из самых важных решений, которые необходимо принять в процессе оптимизации: знать, когда остановиться. Если вы остановитесь слишком рано, ваша игра или приложение могут работать плохо на слабых системах, а если вы зайдете слишком далеко, ваш код может стать более запутанным и сложным в обслуживании. В этом конкретном приложении анимация выглядит плавной и плавной, а загрузка процессора и памяти находится под контролем, поэтому на этом мы остановимся.


Мы только что посмотрели на процесс оптимизации на примере класса Flames. Хотя многие советы по оптимизации были представлены поэтапно, порядок на самом деле не имеет значения. Важно знать о многих проблемах, которые могут замедлить нашу программу, и принимать меры для их устранения.

Но не забудьте остерегаться преждевременной оптимизации; Сосредоточьтесь сначала на создании своей программы и обеспечении ее работы, а затем начните настройку производительности.