性能和优化

在开发应用程序时,应始终考虑设计选择如何影响性能。尽管核心功能不断改进,但移动设备在处理能力、内存使用和电池续航时间方面仍然面临着根本性的限制。因此,性能和优化对于实现更快的响应时间、最大限度地减少内存使用和最大限度地延长电池续航时间至关重要。

高效使用内存

内存是移动设备上的关键资源。如果应用程序消耗过多内存,某些设备甚至可能会终止它。

通常**不建议**使用全局变量,但如果为了方便必须使用它们,请确保在不再需要它们时将它们从内存中移除(将它们设置为 `nil`).

降低功耗

由于外形尺寸小,移动设备的电池续航时间 inherently 有限。您可以通过遵循以下做法来提高电池续航时间。

过渡和动画

如果您需要将多个显示对象的特定属性设置为相同的值或对其进行过渡/补间(例如,将整个叠加菜单淡化到 `alpha=0`)—最好将对象添加到显示组中,并修改整个组的属性。这更容易编码,并且可以优化内存和速度。有关更多信息,请参阅组编程指南。

如果您使用的是精灵动画,一个常见的疏忽是允许屏幕外或不可见的精灵继续动画。虽然用户可能看不到这些精灵,但它们在动画过程中会继续使用处理器能力。我们建议您暂停所有移出屏幕或变得不活动的动画。

节省纹理内存

纹理内存经常被忽略,直到它达到“临界质量”,此时耗时才能对美术资源进行必要的更改。

作为一般实践,请记住以下关于管理纹理内存的技巧

  1. 当不再需要纹理时,始终卸载它们(将它们从显示层次结构中移除)。

  2. 如果您使用的是图像表,请考虑使用 TexturePacker 之类的工具将图像打包成尽可能小的配置。

重要

设备支持的最大纹理大小有限制。如果超过此限制,纹理会自动缩小以适应最大值。您可以使用`system.getInfo( "maxTextureSize" )`命令来确定特定设备的最大纹理大小。有关更多信息,请参阅system.getInfo()

绘制调用 / 批处理

在设备上,当您能够最大限度地减少状态更改时,OpenGL 的性能最佳。这是因为如果连续显示对象之间不需要状态更改,则可以将多个对象批处理到单个绘制调用中。

Solar2D 的渲染引擎会尝试识别可以在单个绘制调用中提交多个显示对象的情况。只要有可能,您就应该尝试排列显示对象层次结构,以便可以将**连续**显示对象(即它们的渲染顺序)批处理到单个绘制调用中。

在某些情况下可能会发生这种情况。一般规则是,可以使用相同纹理的连续显示对象可以进行批处理。这包括使用来自同一图像表的不同帧的显示对象,因为底层纹理是相同的。在这些情况下,您可以更改每个对象的位置、色调和 alpha,而不会破坏批处理,但请记住,某些操作可能会阻止批处理,例如向对象添加着色器效果

Lua 优化

在代码级别,您应尽可能多地遵循 Lua 优化。下面的大多数性能技巧主要适用于时间紧迫例程,即应用程序中发生大量事情或用户体验可能会受到性能迟缓的不利影响的地方。但是,每一点都有帮助,我们建议您养成这种习惯。

本地化,本地化

与应尽可能避免的全局变量相比,访问**局部**变量和函数的速度更快,尤其是在时间紧迫例程中。

-- Local (recommended)
local CCX = display.contentCenterX  -- Local variable

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

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

这也适用于核心 Lua 库,例如 `math` 库。在时间紧迫例程中,您应始终本地化库函数。

-- Local (recommended)
local sin = math.sin  -- Local reference to "math.sin"

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

最后,请记住,应尽可能地本地化函数。当然,这需要适当的作用域!

-- Local (recommended)
local function func2( y )  -- "func2()" properly scoped above "func1()"
    print( y )
end

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

func1()
-- Non-local (discouraged)
function func1()
    func2( "myValue" )
end

function func2( y )
    print( y )
end

func1()

避免使用 “table.insert()”

让我们比较四种都能实现相同目标的方法:将值插入表中的常见操作。在这四种方法中,Lua `table.insert()` 函数的性能平平,应避免使用。

-- Loop index method (recommended)
local a = {}

for i = 1,100 do
    a[i] = i
end
-- Counter method (recommended)
local a = {}
local index = 1

for i = 1,100 do
    a[index] = i
    index = index+1
end
-- Table size method (acceptable)
local a = {}

for i = 1,100 do
    a[#a+1] = i
end
-- "table.insert()" (discouraged)
local a = {}

for i = 1,100 do
    table.insert( a, i )
end

避免使用 “unpack()”

Lua `unpack()` 函数的性能不是很好。幸运的是,可以编写一个简单的循环来完成同样的事情

-- Loop method (recommended)
local a = { 100, 200, 300, 400 }

for i = 1,100 do
    print( a[1],a[2],a[3],a[4] )
end
-- "unpack()" (discouraged)
local a = { 100, 200, 300, 400 }

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

需要注意的是,您必须知道表的长度才能在循环方法中检索其所有值。因此,`unpack()` 仍然有用(例如,在长度未知的表中),但是应避免在例程中使用它时间紧迫例程中。

避免使用 “ipairs()”

迭代表时,Lua `ipairs()` 函数的开销并不能证明其使用是合理的,尤其是在可以使用 Lua 构造完成同样的事情时。

-- Lua construct (recommended)
local t1 = {}
local t2 = {}
local t3 = {}
local t4 = {}
local a = { t1, t2, t3, t4 }

for i = 1,#a do
    print( a[i] )
end
-- "ipairs()" (discouraged)
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

数学性能

某些数学函数和过程比其他函数和过程更快。例如,乘法比除法快,您通常应该乘以小数而不是除法。

-- Multiplication by decimal (recommended)
x * 0.5
y * 0.125
-- Division (acceptable)
x / 2
y / 8

乘法也比求幂快

-- Multiplication (recommended)
x * x * x
-- Exponentiation (acceptable)
x^3

最后,对于正数,避免使用 `math.fmod()`,而应使用模运算符

-- Modulus operator (recommended)
for i = 1,100 do
    if ( ( i%30 ) < 1 ) then
        local x = 1
    end
end
-- "math.fmod()" (discouraged)
local fmod = math.fmod
for i = 1,100 do
    if ( fmod( i,30 ) < 1 ) then
        local x = 1
    end
end

管理音频

使用音频时,在大多数情况下,您应该将声音压缩/采样到可接受的最小质量。此外,使用简单的、跨平台格式(如 `.wav`)不会对 CPU 造成很大的负担。

预加载音频

应用程序的音效几乎应始终预加载在**非**时间紧迫的代码中,例如,在场景或关卡开始之前。

如果需要,可以将音效组织在表中,如下所示,以便于参考和最终处理。

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"] )

释放音频

一样简单,请记住,当不再需要音频文件时,您**必须**处理它们**并**清除对它们的任何引用。假设使用上述表结构来组织和预加载声音,则以下循环将处理音频句柄

for s = #soundTable,1,-1 do
    audio.dispose( soundTable[s] ) ; soundTable[s] = nil
end