变量存储与冗余算法
Variable storage versus redundant arithmetic
我正在 Lua 中为我正在开发的 LÖVE 游戏编写一个非常简单的循环。我知道我会浪费更多的时间来担心这个,而不是花在任何 CPU 时钟周期上这个问题的答案拯救了我,但我想更深入地了解它是如何工作的。
当前的body循环是这样的:
local low = mid - diff
local high = mid + diff
love.graphics.line(low, 0, low, wheight)
love.graphics.line(high, 0, high, wheight)
我想知道保持原样或将其更改为计算效率更高:
love.graphics.line(mid - diff, 0, mid - diff, wheight)
love.graphics.line(mid + diff, 0, mid + diff, wheight)
对于第二个 body,我必须分别计算两次低差和高差。对于第一个,我必须将它们存储在内存中并每次访问它们两次。
哪个效率更高?
简短的回答是它不太可能产生任何影响。例如,即使存在任何差异,您旁边的代码也会画一条线。与加法和减法相比,即使使用本机代码实现的非常优化的 Bresenham 绘制一条别名线也非常昂贵。即使是单独的函数调用也可能使这个成本相形见绌。
With the second body, I have to calculate the low and high differences
twice each. With the first, I have to store them in memory and access
them twice each.
不一定如此。变量不一定 "store memory" 表达式不需要的方式。它们可以直接映射到一个寄存器。同样,避免变量并不一定 "avoid memory"。表达式同样会被计算并存储在寄存器中,无论您是否明确地将中间结果分配给变量。
因此,从内存的角度来看,您的代码的两个版本都需要使用寄存器来存储计算的中间结果。
当您只涉及简单变量时,记忆不一定有那种内存开销,主要是因为类型直接映射到寄存器而没有堆栈溢出。当您提前开始计算整个 arrays/tables 时,如果记忆化意味着更多的 DRAM 访问(在这种情况下,内存开销可能超过节省的成本),有时进行额外的计算会比记忆化更快。但是像数字这样简单的 POD 类型变量没有 DRAM 开销,它们直接映射到寄存器。换句话说,它们通常实际上是免费的:无论您是否将表达式的结果分配给局部变量,编译器都会发出相同的机器代码——需要相同数量的寄存器。
最好将直接映射到 GP 寄存器的数据类型的局部变量视为仅在您处于高级编码领域时才存在。当 JIT 或解释器将您的代码编译成机器可以理解的形式时,无论您是否创建了这些变量,它们都会消失并变成寄存器。
可能最终的问题是,如果有任何区别,是否可以消除冗余计算。只需要最简单的优化器就可以弄清楚在完全相同的语句中写入两次的 mid - diff
只需要计算一次。如果它在到达 IR 指令选择和寄存器分配阶段时没有得到优化,我会感到惊讶。
但即使结果出乎意料,而且 Lua 解释器效率低到无法识别完全冗余的计算并执行它,再次,你旁边有代码渲染一条线(涉及循环光栅化)。相对来说,即使有冗余,这实际上也是免费的。在这里不值得为这么小的东西出汗,这是来自痴迷于剃须时钟周期的人。
我正在 Lua 中为我正在开发的 LÖVE 游戏编写一个非常简单的循环。我知道我会浪费更多的时间来担心这个,而不是花在任何 CPU 时钟周期上这个问题的答案拯救了我,但我想更深入地了解它是如何工作的。
当前的body循环是这样的:
local low = mid - diff
local high = mid + diff
love.graphics.line(low, 0, low, wheight)
love.graphics.line(high, 0, high, wheight)
我想知道保持原样或将其更改为计算效率更高:
love.graphics.line(mid - diff, 0, mid - diff, wheight)
love.graphics.line(mid + diff, 0, mid + diff, wheight)
对于第二个 body,我必须分别计算两次低差和高差。对于第一个,我必须将它们存储在内存中并每次访问它们两次。
哪个效率更高?
简短的回答是它不太可能产生任何影响。例如,即使存在任何差异,您旁边的代码也会画一条线。与加法和减法相比,即使使用本机代码实现的非常优化的 Bresenham 绘制一条别名线也非常昂贵。即使是单独的函数调用也可能使这个成本相形见绌。
With the second body, I have to calculate the low and high differences twice each. With the first, I have to store them in memory and access them twice each.
不一定如此。变量不一定 "store memory" 表达式不需要的方式。它们可以直接映射到一个寄存器。同样,避免变量并不一定 "avoid memory"。表达式同样会被计算并存储在寄存器中,无论您是否明确地将中间结果分配给变量。
因此,从内存的角度来看,您的代码的两个版本都需要使用寄存器来存储计算的中间结果。
当您只涉及简单变量时,记忆不一定有那种内存开销,主要是因为类型直接映射到寄存器而没有堆栈溢出。当您提前开始计算整个 arrays/tables 时,如果记忆化意味着更多的 DRAM 访问(在这种情况下,内存开销可能超过节省的成本),有时进行额外的计算会比记忆化更快。但是像数字这样简单的 POD 类型变量没有 DRAM 开销,它们直接映射到寄存器。换句话说,它们通常实际上是免费的:无论您是否将表达式的结果分配给局部变量,编译器都会发出相同的机器代码——需要相同数量的寄存器。
最好将直接映射到 GP 寄存器的数据类型的局部变量视为仅在您处于高级编码领域时才存在。当 JIT 或解释器将您的代码编译成机器可以理解的形式时,无论您是否创建了这些变量,它们都会消失并变成寄存器。
可能最终的问题是,如果有任何区别,是否可以消除冗余计算。只需要最简单的优化器就可以弄清楚在完全相同的语句中写入两次的 mid - diff
只需要计算一次。如果它在到达 IR 指令选择和寄存器分配阶段时没有得到优化,我会感到惊讶。
但即使结果出乎意料,而且 Lua 解释器效率低到无法识别完全冗余的计算并执行它,再次,你旁边有代码渲染一条线(涉及循环光栅化)。相对来说,即使有冗余,这实际上也是免费的。在这里不值得为这么小的东西出汗,这是来自痴迷于剃须时钟周期的人。