Germany | Finland | Saint Petersburg | Drive

Оптимизация в Lua

Опубликовано в QLua

Перевод статьи  на русский язык.

Сегодняшний урок посвящен оптимизации производительности, теме, которая интересует всех разработчиков. Некоторые из этих советов будут очевидны, другие, нет. Главное правило - необходимо соотносить затрата на оптимизацию с выгодой, получаемое в результате. Если оптимизация может дать 2% прирост производительности, но его реализация требует 50 часов дополнительного кодирования, то особого смысла тратить время нет. Однако, если 10 часов кодирования способны дать заметные улучшения производительности программы, установленной у массы пользователей, то задача является необходима.

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

 

1. Локализация, Локализация, Локализация... 

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

Неправильно

CCX = display.contentCenterX  --global variable
for i = 1,100 do
   local image = display.newImage( "myImage" )
   image.x = CCX
end

Правильно

local CCX = display.contentCenterX  --local variable
for i = 1,100 do
   local image = display.newImage( "myImage" )
   image.x = CCX
end

Это также относится к стандартным Lua библиотекам. В критичных по времени процедурах вы всегда должны локализовать библиотечные функции.

Неправильно

local function foo( x )
   for i = 1,100 do
      x = x + math.sin(i)
   end
   return x
end

Правильно

local sin = math.sin  -- локальная ссылка на math.sin
local function foo(x)
   for i = 1,100 do
      x = x + sin(i)
   end
   return x
end

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

Неправильно

function func1()
   func2( "myValue" )
end

function func2( y )
   print( y )
end

func1()

Правильно

local function func2( y )
   print( y )
end

local function func1()
   func2( "myValue" ) 
end

func1()

2. По возможности избегайте функции в качестве аргументов для других функций

В циклах или критическом по времени коде имеют значния функции, являющиеся параметрами других функций. Изучите эти два случая:

Определение в качестве аргумента другой функции - Неправильно

local func1 = function(a,b,func) 
   return func(a+b) 
end

for i = 1,100 do
   local x = func1( 1, 2, function(a) return a*2 end )
   print( x )
end

Локализованная - Правильно

local func1 = function( a, b, func )
   return func( a+b )
end
local func2 = function( c )
   return c*2
end

for i = 1,100 do
   local x = func1( 1, 2, func2 )
   print( x )
end

3. Избегайте table.insert()

Давайте сравним четыре метода, выполняющие одну и ту же задачу: заполнение таблицы значениями. Функция table.insert() дает самые плохие результаты из всех и её стоит избегать.

table.insert()

local a = {}
local table_insert = table.insert

for i = 1,100 do
   table_insert( a, i )
end

Метод прямой индексации - Правильно

local a = {}

for i = 1,100 do
   a[i] = i
end

Метод с размером таблицы- Допустимо

local a = {}

for i = 1,100 do
   a[#a+1] = i
end

Метод счетчика - Правильно

local a = {}
local index = 1

for i = 1,100 do
   a[index] = i
   index = index+1
end

4. Сведение к минимуму использования unpack()

Функция unpack() не является "скорострельной". Однако в большинстве случаев её можно написать простым и более эффективным циклом.

Метод Lua "unpack()" - Нежелательно

local a = { 100, 200, 300, 400 }

for i = 1,100 do
   print( unpack(a) )
end

Метод цикла - Желательно

local a = { 100, 200, 300, 400 }

for i = 1,100 do
   print( a[1],a[2],a[3],a[4] )
end

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

 

5. Кэширование доступа к элементам

Кэширование таблиц, особенно внутри циклов, также несколько повышает производительность.

Номера для кэширования - Допустимо

for i = 1,100 do
   for n = 1,100 do
      a[n].x = a[n].x + 1
      print( a[n].x )
   end
end

Сохраненная копия - Правильно

for i = 1,100 do
   for n = 1,100 do
      local y = a[n]
      y.x = y.x + 1
      print( y.x )
   end
end

6. Избегайте ipairs()

Если результат можно достичь средствами языка Lua, не стоит использовать ipairs()

ipairs () - Неправильно

local t1 = {}
local t2 = {}
local t3 = {}
local t4 = {}
local a = { t1, t2, t3, t4 }

for i,v in ipairs( a ) do
   print( i,v )
end

Правильно

local t1 = {}
local t2 = {}
local t3 = {}
local t4 = {}
local a = { t1, t2, t3, t4 }

for i = 1,#a do
   print( a[i] )
end

7. Производительность математических функций

Некоторые математические функции и процессы быстрее других. Стоит использовать более быстрые функции.

Избегайте math.fmod() для положительных чисел

-- Метод math.fmod
local fmod = math.fmod
for i = 1,100 do
   if ( fmod( i,30 ) < 1 ) then
      local x = 1
   end
end

Метод остатка от деления (рекомендуется)

-- Метод остатка от деления
for i = 1,100 do
   if ( ( i%30 ) < 1 ) then
      local x = 1
   end
end

Умножение происходит быстрее, чем деление

x * 0.5 ; x * 0.125  -- Правильно
x/2 ; x/8            -- Неправильно

Умножение происходит быстрее, чем возведение в степень

x * x * x  -- Правильно
x^3        -- Неправильно

 

10. Загрузка больших данных вне критических фрагментов

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

-- Загрузка звуков во время некритического по времени кода

local soundTable =
{  mySound1 = audio.loadSound( "a.wav" ),
   mySound2 = audio.loadSound( "b.wav" ),
   mySound3 = audio.loadSound( "c.wav" ),
   mySound4 = audio.loadSound( "d.wav" ),
   mySound5 = audio.loadSound( "e.wav" ),
   mySound6 = audio.loadSound( "f.wav" ),
   mySound7 = audio.loadSound( "g.wav" ),
   mySound8 = audio.loadSound( "h.wav" ),
}

В момент воспроизведения достаточно

local mySound = audio.play( soundTable["mySound1"] )

Естественно, не стоит забывать уничтожать объекты, когда они больше не нужны, очищая ссылку из таблицы:

local ST = soundTable
for s,v in pairs(ST) do
   audio.dispose( ST[s] ) 
ST[s] = nil end

 

Добавить комментарий


Архив QLua