好吧,让我们想想。首先,请编辑这一行:
For I := Low( List ) to High( List ) do
(你最后忘记了“做”)。当我们在没有优化的情况下编译它时,这里是这个循环的汇编代码:
Unit1.pas.29: If List [I] = Value then
005C5E7A 8B45FC mov eax,[ebp-$04]
005C5E7D 8B55F0 mov edx,[ebp-$10]
005C5E80 8A0410 mov al,[eax+edx]
005C5E83 3A45FB cmp al,[ebp-$05]
005C5E86 7508 jnz $005c5e90
Unit1.pas.30: Exit (I);
005C5E88 8B45F0 mov eax,[ebp-$10]
005C5E8B 8945F4 mov [ebp-$0c],eax
005C5E8E EB0F jmp $005c5e9f
005C5E90 FF45F0 inc dword ptr [ebp-$10]
Unit1.pas.28: For I := Low (List) to High (List) do
005C5E93 FF4DEC dec dword ptr [ebp-$14]
005C5E96 75E2 jnz $005c5e7a
这段代码远非最优:局部变量i是真正的局部变量,即:它存储在RAM中,在堆栈中(通过[ebp-$10]地址可以看到,ebp是堆栈指针) .
所以在每次新的迭代中,我们都会看到如何将数组的地址加载到 eax 寄存器中 (mov eax, [ebp-$04]),
然后我们将 i 从堆栈加载到 edx 寄存器 (mov edx, [ebp-$10]),
然后我们至少将 List[i] 加载到 al 寄存器中,它是 eax 的低字节 (mov al, [eax+edx])
之后将其与再次从内存而不是寄存器中获取的参数“值”进行比较!
这个实现非常慢。
但让我们最后打开优化!它在项目选项 -> 编译 -> 代码生成中完成。让我们看一下新代码:
Unit1.pas.29: If List [I] = Value then
005C5E5A 3A1408 cmp dl,[eax+ecx]
005C5E5D 7504 jnz $005c5e63
Unit1.pas.30: Exit (I);
005C5E5F 8BC1 mov eax,ecx
005C5E61 5E pop esi
005C5E62 C3 ret
005C5E63 41 inc ecx
Unit1.pas.28: For I := Low (List) to High (List) do
005C5E64 4E dec esi
005C5E65 75F3 jnz $005c5e5a
现在只有 4 行代码不断重复。
值存储在 dl 寄存器中(edx 寄存器的低字节),
数组的第 0 个元素的地址存储在 eax 寄存器中,
i 存储在 ecx 寄存器中。
因此,'if List[i] = Value' 行仅转换为 1 条装配线:
005C5E5A 3A1408 cmp dl,[eax+ecx]
下一行是条件跳转,之后的3行只执行一次或从不执行(如果条件为真),最后增加i,
循环变量的递减(将其与零进行比较,然后与其他任何东西进行比较更容易)
所以,我们能做的只有带优化器的 Delphi 编译器没有的事情!
如果你的程序允许,你可以尝试反向搜索,从最后一个元素到第一个元素:
For I := High( List ) downto Low( List ) do
这样编译器将很乐意将 i 与零进行比较,以表明我们检查了所有内容(此操作是免费的:当我们减少 i 并得到零时,CPU 零标志打开!)
但在这样的实现中,行为可能会有所不同:如果您有多个条目 = 值,那么您将得到的不是第一个,而是最后一个!
另一个非常简单的事情是将这个 IndexOf 函数声明为内联函数:这样您可能不会在这里调用函数:这段代码将插入到您调用它的每个位置。函数调用是相当慢的事情。
Knuth 中还描述了一些疯狂的方法,如何尽可能快地在简单数组中搜索,他引入了数组的“虚拟”最后一个元素,它等于你的“值”,这样你就不必检查边界(它总是会在超出范围之前找到一些东西),因此循环内只有 1 个条件而不是 2 个。另一种方法是循环的“展开”:您在循环内写下 2 或 3 次或更多次迭代,因此更少每次检查都会跳转,但这有更多的缺点:它仅对相当大的数组有益,而对于具有 1 或 2 个元素的数组可能会更慢。
正如其他人所说:最大的改进是了解您存储的数据类型:它是否经常更改或长时间保持不变,您是否寻找随机元素或有一些“领导者”获得最多注意力。这些元素必须与您放置它们的顺序相同,还是可以根据需要重新排列它们?然后您可以相应地选择数据结构。如果您一直在寻找 1 或 2 个相同的条目并且可以重新排列它们,那么一个简单的“移动到前面”方法会很棒:您不仅返回索引,而且首先将元素移动到第一位,所以它下次会很快找到的。