查找映射到小整数的常量时,使用case语句还是常量数组更快?

10
例如:我将数字1到7映射到一周中的每一天。我可以用一个七项的情况语句查找它们,或者使用一个七项的常数数组。哪个更快?
案例示例:
function GetDayNameBr(Num: Integer): String;
begin
  case Num of
    1: Result := 'Domingo';
    2: Result := 'Segunda';
    3: Result := 'Terça';
    4: Result := 'Quarta';
    5: Result := 'Quinta';
    6: Result := 'Sexta';
    7: Result := 'Sábado';
  end;       
end;

常量数组示例:

function GetDayNameBr(Num: Integer): String;
const
  DayNames: array [1..7] of String = (
    'Domingo',
    'Segunda',
    'Terça',
    'Quarta',
    'Quinta',
    'Sexta',
    'Sábado');
begin
  Result := DayNames[Num];       
end; 

6
  1. 这不是你的瓶颈。
  2. 关于哪个更快的答案总是涉及到时间。因此编写一个基准测试并计时。但请记住第一点,这不是你的瓶颈。
- David Heffernan
我没有任何瓶颈,我只是想知道在编写(或重构)类似代码时哪个更快。 - Daniel Santos
3
最快的方法是放弃Delphi,编写自己手工优化的汇编程序。也许您正在使用错误的因素来确定编写代码的最佳方法。我会基于可读性和可维护性做出这样的选择。 - David Heffernan
1
数组方法可以完全摆脱函数。 - Uwe Raabe
如果你关心性能,那么函数的存在形式(如此小)就是问题所在。如果它被调用了相当多的次数,那么函数本身的设置/拆卸代码将会花费大量时间。 - Glenn1234
3个回答

11
这两个函数表现特性不同的主要原因在于它们执行不同的操作。您不能拿不同类型的数据进行比较。当输入值在1到7范围内时,行为是相同的。但是,当输入值超出该范围时,行为会发生分歧。
第一个版本使用“case”语句,必须先检查值是否在1到7的范围内。只有在值在1到7的范围内时才允许实际分配给“Result”。如果值在1到7的范围内,则编译器将case语句转换为无条件的“jmp”语句,其格式如下:
jmp dword ptr [eax*4+$40428f]

这里的eax是天数索引。跳转的目标是仅仅将字符串文字赋值给Result变量的指令。

第二个版本使用数组,不检查输入值是否在范围内。即使输入值超出了范围,它也直接索引到数组中,当然这样的数组索引会导致未定义的行为。因此,这就是行为分歧的地方。

从性能角度来看,忽略函数语义上的差异,主要区别在于使用case语句的版本具有对输入值进行测试和分支的过程,在数组版本中不存在。此外,使用case语句的版本代码更长,因此可能缓存性能较差。因此,根据代码的分析,我们可以预期数组版本会更快。它需要做的比较少,不需要分支,代码更短。

如果性能确实很重要,那么您需要在代码实际运行的环境中执行一些真实的定时任务。我无法进行这些定时任务,因为它们是人工的。任何时间都只有在您的代码背景下才有真正的意义。非常可能,在您的程序设置中,您无法测量两个版本之间的差异。在这种情况下,上述分析将无意义。


如果数组索引超出范围,Delphi会抛出实时异常吗? - lurker
1
@mbratch 它“可以”,但默认情况下不会这样做。如果这样做,程序当然会变慢。 - Günther the Beautiful
1
仅当启用范围检查选项时,才使用@mrbatch。 - David Heffernan
好的,谢谢Dave和@GünthertheBeautiful。我以为这是默认行为。 - lurker
过早的优化是万恶之源。我同意!但在Win64下可能会有速度差异,据我所知。请看我的回答。 - Arnaud Bouchez

9

两者速度几乎相同,至少在 x86(即使用32位 Delphi 编译器)下是如此。

数组将生成一个索引查找,而 Case 语句将基于一个 查找表 生成跳转指令,当目标是32位时。数组会快一点,但只是略微更快。

但据我所知,在64位的情况下,针对 64位 的情况下,case 指令不会生成这样的 查找表。它会生成一系列比较和条件跳转的列表(类似于 if value=1 then ... else if value=2 then...),这会明显地降低速度。

在你的情况下,我建议使用数组查找和一个 枚举类型 ,而不是普通的整数值。虽然它将被编译为整数,但调试和更新会更加容易。如果枚举类型发生了变化,常量数组就无法编译了,因此您将能够在编译时避免某些问题,而不是在运行时。我尽可能地使用 枚举类型 来处理这种小列表,而不是整数。这是 Delphi/Pascal 的优势之一,我在 C# 或 Java 中非常想念。

type
  TDay = (dDomingo, dSegunda, dTerca, dQuarta, dSexta, dSabado);

function GetDayNameBr(Num: TDay): String; 
const
  DayNames: array [TDay] of String = (
    'Domingo',
    'Segunda',
    'Terça',
    'Quarta',
    'Quinta',
    'Sexta',
    'Sábado');
begin
  Result := DayNames[Num];       
end; 

或者,我认为更好的方法是在代码中直接使用DayNames[Num],这将是所有平台上最安全和最快的方法。

我不知道64位编译器有这种差异。你有任何想法为什么会有行为上的差异吗?是因为不同的ISA吗?还是开发人员选择不实现那个非常棘手的优化呢? - David Heffernan
1
@DavidHeffernan 我不知道 Embarcadero 编译器团队的意图,但我猜测 x64 后端编译器代码生成器在这一点上只是不够优化。 - Arnaud Bouchez

8

两种方法都非常快,但我认为数组方法在速度上可能更快,即使有任何区别也不会引起注意。

然而,我肯定会选择数组方法,因为它将逻辑与原始数据分离。(想象一下你需要支持两种不同的语言——比较一下在每种情况下你会如何做。)这也更符合习惯。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接