【问题标题】:Implementing loop bounds check via overflow interrupt通过溢出中断实现循环边界检查
【发布时间】:2019-11-10 17:09:19
【问题描述】:

我今天有一个想法,可以通过构造一个迭代计数器来实现对数组的边界检查循环,该计数器将在最后一次递增时溢出并根据生成的溢出中断停止执行。

所以假设你有一个数组,例如int[32] 并且您想要迭代它。为了避免每次循环运行中的边界检查,您现在可以做的是为溢出中断注册一个中断处理程序并将一个寄存器分配给值MAX - 32。该寄存器在每次循环迭代运行时递增,最后一次迭代运行将溢出,即触发中断处理程序。假设中断处理程序可以增加原始函数的程序计数器,这种机制可以用来避免边界检查。

这样的代码

for (int i = 0; i < array.length; i++) {
  // do something
}

可以像这样实现

// setup interrupt somehow
SOME_REGISTER = MAX - array.length;
while (true) {
  // do something
  SOME_REGISTER++;
}

我不知道这是否可行,但我听说 Java 正在做类似的事情来避免在运行时生成的代码中进行空检查。您认为这是可行的,还是任何语言运行时都考虑过/尝试过?

【问题讨论】:

  • 这个明显的问题是大多数系统没有在溢出时自动生成中断的方法。 Java 依靠地址 0 未映射到有效内存这一事实来避免空检查,并且任何访问内存中此位置(以及任何更高的地址直到某个点)的尝试都将导致 Java 运行时可以捕获的页面错误.这得到了广泛的 CPU 的支持,并且是现代非嵌入式操作系统的默认行为。

标签: arrays assembly runtime interrupt bounds-check-elimination


【解决方案1】:

处理异常很慢;一个数组必须是巨大的,以分摊因错误而离开循环的额外成本,即使你可以在循环中保存一条指令。

您可能已经阅读到Array bounds checks on 64-bit hardware using hardware memory-protection使数组在虚拟页面的末尾结束,而下一页未映射。如果发生越界异常,VM 会捕获 SIGSEGV 并将异常传递给来宾代码。 这消除了对随机访问进行边界检查的需要,而不是在循环中。

此技术仅在客户代码实际发生数组边界异常时导致无效页面错误(segfault),不会用于退出数组循环的正常情况。强>


让我们看看你的想法如何(不)起作用:

我认为您在谈论 MIPS,其中有一条 addi 指令会在有符号溢出时陷入陷阱。 (而不是普通的addiu,它在没有条件陷阱的情况下进行加法)。

大多数其他 ISA 没有任何有效的方法来解决有符号溢出问题。 x86 有into(设置了OF 中断),但这是与add 不同的指令,可能会设置标志。您不妨使用条件分支,而不是引发异常并捕获它。或者只是使用dec ecx / jnz 作为循环计数器,或者将负索引向上计数到零并从数组末尾开始索引。

我认为 MIPS 需要一个额外的 addi 循环中用于专用计数器:MIPS 的唯一寻址模式是 reg+16_bit_constant

所以如果你想迭代一个数组,你需要一个指针增量,否则你需要一个额外的 add tmp, base, index 在循环内。

循环需要在底部有跳转或分支指令,它可以是一个条件分支,基本上没有额外的成本。所以在 MIPS 中,你会在循环外计算数组的结尾,然后编写一个普通循环,如

    addu   $t5, $t0, $t4     ; t5 = int *endp = start + byte_length
.loop:                       ; do{
    addiu  $t0, $t0, 4         ;  p++
    lw     $t1, ($t0)          ; *p
    ...
    bne    $t0, $t5, .loop    ; }while(p != endp)

循环内没有浪费的指令。任何数组边界检查都可以在循环之外完成,方法是检查endp 不超过数组末尾的一倍。

(在现代 x86 上,cmp/jne 等效地工作,解码为单个比较和分支微指令。许多其他 ISA 具有递减和分支或类似指令,允许计数器循环条件只有一个开销指令.)

i &lt; array.length 作为循环条件不仅仅是数组边界检查。

正如我上面所说,在一个迭代 arr[i] 的简单循环中,您将边界检查提升到循环之外以保持其效率。

【讨论】:

  • 我的想法是通过一个无条件的跳转来移除条件跳转,并通过一些技巧(触发溢出),依靠一些处理器机制(中断)来退出循环。感谢您的精彩解释。我想这还没有尝试过,因为与相等检查相比,中断处理程序调用非常慢,并且可能还因为分支预测在那里有很大帮助?
  • @ChristianBeikov:对,循环分支几乎每次都能正确预测。通常的成本是对最终迭代的 1 次错误预测(它通过而不是被采用)。这种错误预测比接受例外要便宜得多。根据循环和以下代码,它可以在具有快速恢复的现代 CPU 上被乱序 exec 隐藏。 Avoid stalling pipeline by calculating conditional early 异常必须耗尽整个后端。
  • @ChristianBeikov:正确预测的条件分支与无条件分支的成本相同。 (并且两者都需要在前端进行预测。在当前块完成解码之前,获取阶段需要知道下一个要获取的块。见Slow jmp-instruction
猜你喜欢
  • 2011-10-06
  • 1970-01-01
  • 1970-01-01
  • 2011-04-08
  • 2013-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多