为什么没有取消边界检查?

马塔蒂努斯

我写了一个简单的基准测试,以找出在通过按位和计算数组时是否可以消除边界检查。基本上,这就是几乎所有哈希表的作用:它们计算

h & (table.length - 1)

作为一个指数到的table,其中hhashCode或派生值。结果表明,范围检查没有得到消除。

我的基准测试的想法很简单:计算两个值ij,保证两个值都是有效的数组索引。

  • i是循环计数器。当它用作数组索引时,将取消边界检查。
  • j将计算为x & (table.length - 1),其中x每次迭代都有一些值更改。当它用作数组索引时,不会消除边界检查。

相关部分如下:

for (int i=0; i<=table.length-1; ++i) {
    x += result;
    final int j = x & (table.length-1);
    result ^= i + table[j];
}

其他实验用途

    result ^= table[i] + j;

代替。时间上的差异可能约为15%(在我尝试过的各种变体中,它们始终保持一致)。我的问题:

  • 除了取消绑定检查外,还有其他可能的原因吗?
  • 是否有一些复杂的原因使我看不到为什么没有取消绑定检查的原因j

答案摘要

MarkoTopolnik的回答表明,这一切都更加复杂,取消边界检查并不能保证一定会成功,尤其是在他的计算机上,“常规”代码比“掩码”慢。我猜这是因为它允许进行一些其他的优化,在这种情况下,这实际上是有害的(考虑到当前CPU的复杂性,编译器甚至无法确定)。

leventov的答案清楚地表明,数组边界检查是在“ masked”中完成的,并且消除它使代码与“ normal”一样快。

Donal Fellows指出以下事实:对于x & (0-1)等于0的长度为零的表,屏蔽不起作用x因此,编译器可以做的最好的事情就是用零长度检查替换边界检查。但这仍然值得恕我直言,因为零长度检查可以轻松地移出循环。

拟议的优化

由于a[x & (a.length - 1)]当且仅当时等价抛出a.length == 0,编译器可以执行以下操作:

  • 对于每个数组访问,检查索引是否已按位和进行计算。
  • 如果是这样,请检查是否将两个操作数的长度都减去了。
  • 如果是这样,请将边界检查替换为零长度检查。
  • 让现有的优化来处理它。

这种优化应该非常简单且便宜,因为它仅查看SSA中的父节点与许多复杂的优化不同,它永远不会有害,因为它只会用稍微简单一些的支票代替它。因此没有问题,即使无法将其移出循环也没有问题。

我将其发布到hotspot-dev邮件列表。

新闻

约翰·罗斯(John Rose)提出了RFE,并且已经有一个“快速而肮脏的”补丁

列文托夫
  1. 不,这显然是无法消除智能边界检查的结果。

我已经扩展了Marko Topolnik的基准测试:

@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(BCElimination.N)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 10, time = 1)
@State(Scope.Thread)
@Threads(1)
@Fork(2)
public class BCElimination {
    public static final int N = 1024;
    private static final Unsafe U;
    private static final long INT_BASE;
    private static final long INT_SCALE;
    static {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            U = (Unsafe) f.get(null);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }

        INT_BASE = U.arrayBaseOffset(int[].class);
        INT_SCALE = U.arrayIndexScale(int[].class);
    }

    private final int[] table = new int[BCElimination.N];

    @Setup public void setUp() {
        final Random random = new Random();
        for (int i=0; i<table.length; ++i) table[i] = random.nextInt();
    }

    @GenerateMicroBenchmark public int normalIndex() {
        int result = 0;
        final int[] table = this.table;
        int x = 0;
        for (int i=0; i<=table.length-1; ++i) {
            x += i;
            final int j = x & (table.length-1);
            result ^= table[i] + j;
        }
        return result;
    }

    @GenerateMicroBenchmark public int maskedIndex() {
        int result = 0;
        final int[] table = this.table;
        int x = 0;
        for (int i=0; i<=table.length-1; ++i) {
            x += i;
            final int j = x & (table.length-1);
            result ^= i + table[j];
        }
        return result;
    }

    @GenerateMicroBenchmark public int maskedIndexUnsafe() {
        int result = 0;
        final int[] table = this.table;
        long x = 0;
        for (int i=0; i<=table.length-1; ++i) {
            x += i * INT_SCALE;
            final long j = x & ((table.length-1) * INT_SCALE);
            result ^= i + U.getInt(table, INT_BASE + j);
        }
        return result;
    }
}

结果:

Benchmark                                Mean   Mean error    Units
BCElimination.maskedIndex               1,235        0,004    ns/op
BCElimination.maskedIndexUnsafe         1,092        0,007    ns/op
BCElimination.normalIndex               1,071        0,008    ns/op


2.第二个问题是热点开发邮件列表,而不是IMHO的StackOverflow。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

为什么没有Iterator或listIterator的next,hasprevious等方法检查ConcurrentModificationException

来自分类Dev

`std :: bitset`有和没有边界检查

来自分类Dev

为什么在没有任何未经检查的类型警告的情况下进行编译?

来自分类Dev

为什么Web Inspector向我显示“没有可检查的应用程序”?

来自分类Dev

为什么将Throwable抛出异常时没有给出未经检查的强制转换警告?

来自分类Dev

为什么没有输出?

来自分类Dev

为什么取消状态没有被取消后的任务状态更改为取消?

来自分类Dev

为什么我的NSOperation没有取消?

来自分类Dev

为什么Objective-C没有API可用性检查?

来自分类Dev

为什么没有dscanf()?

来自分类Dev

为什么Python的列表没有移位/取消移位方法?

来自分类Dev

为什么没有OutOfMemoryError

来自分类Dev

为什么在检查null的长度时没有出现错误

来自分类Dev

Bash(和其他语言)-为什么没有默认检查未声明的变量?

来自分类Dev

为什么没有对空指针取消引用的段错误?

来自分类Dev

为什么我没有从列表理解检查真值得到索引返回?

来自分类Dev

为什么我进行偶/奇检查时没有零选项?

来自分类Dev

什么是ng-reflect-router-link?为什么在检查元素部分中没有显示按钮

来自分类Dev

为什么对组件/应用程序进行“检查”而没有任何事情?

来自分类Dev

为什么没有碰撞?

来自分类Dev

为什么有时带有bar或hist的条没有边界?

来自分类Dev

为什么在使用组合框actionListerner之后没有取消tableModelListener代码

来自分类Dev

为什么没有输出?

来自分类Dev

为什么ImageView周围有这种奇怪的边界

来自分类Dev

为什么交换空间在启动时没有得到文件系统检查?

来自分类Dev

为什么没有执行?

来自分类Dev

为什么当我改变边界时视图没有改变?

来自分类Dev

为什么没有绘制超出我的自定义视图边界的子视图?

来自分类Dev

为什么我的取消令牌没有杀死这项工作?

Related 相关文章

  1. 1

    为什么没有Iterator或listIterator的next,hasprevious等方法检查ConcurrentModificationException

  2. 2

    `std :: bitset`有和没有边界检查

  3. 3

    为什么在没有任何未经检查的类型警告的情况下进行编译?

  4. 4

    为什么Web Inspector向我显示“没有可检查的应用程序”?

  5. 5

    为什么将Throwable抛出异常时没有给出未经检查的强制转换警告?

  6. 6

    为什么没有输出?

  7. 7

    为什么取消状态没有被取消后的任务状态更改为取消?

  8. 8

    为什么我的NSOperation没有取消?

  9. 9

    为什么Objective-C没有API可用性检查?

  10. 10

    为什么没有dscanf()?

  11. 11

    为什么Python的列表没有移位/取消移位方法?

  12. 12

    为什么没有OutOfMemoryError

  13. 13

    为什么在检查null的长度时没有出现错误

  14. 14

    Bash(和其他语言)-为什么没有默认检查未声明的变量?

  15. 15

    为什么没有对空指针取消引用的段错误?

  16. 16

    为什么我没有从列表理解检查真值得到索引返回?

  17. 17

    为什么我进行偶/奇检查时没有零选项?

  18. 18

    什么是ng-reflect-router-link?为什么在检查元素部分中没有显示按钮

  19. 19

    为什么对组件/应用程序进行“检查”而没有任何事情?

  20. 20

    为什么没有碰撞?

  21. 21

    为什么有时带有bar或hist的条没有边界?

  22. 22

    为什么在使用组合框actionListerner之后没有取消tableModelListener代码

  23. 23

    为什么没有输出?

  24. 24

    为什么ImageView周围有这种奇怪的边界

  25. 25

    为什么交换空间在启动时没有得到文件系统检查?

  26. 26

    为什么没有执行?

  27. 27

    为什么当我改变边界时视图没有改变?

  28. 28

    为什么没有绘制超出我的自定义视图边界的子视图?

  29. 29

    为什么我的取消令牌没有杀死这项工作?

热门标签

归档