我写了一个简单的基准测试,以找出在通过按位和计算数组时是否可以消除边界检查。基本上,这就是几乎所有哈希表的作用:它们计算
h & (table.length - 1)
作为一个指数到的table
,其中h
是hashCode
或派生值。该结果表明,范围检查没有得到消除。
我的基准测试的想法很简单:计算两个值i
和j
,保证两个值都是有效的数组索引。
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的答案清楚地表明,数组边界检查是在“蒙版”中完成的,并且消除它使代码与“普通”一样快。
Donal Fellows指出以下事实:对于x & (0-1)
等于0的长度为零的表,屏蔽不起作用x
。因此,编译器可以做的最好的事情是用零长度检查替换边界检查。但这仍然值得恕我直言,因为零长度检查可以轻松地移出循环。
由于a[x & (a.length - 1)]
当且仅当时等价抛出a.length == 0
,编译器可以执行以下操作:
这种优化应该非常简单且便宜,因为它仅查看SSA图中的父节点。与许多复杂的优化不同,它永远不会有害,因为它只能用稍微简单一些的支票代替它。因此没有问题,即使无法将其移出循环也没有问题。
我将其发布到hotspot-dev邮件列表。
约翰·罗斯(John
Rose)提出了RFE,并且已经有一个“快速而肮脏的”
补丁。
我已经扩展了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.第二个问题是热点开发邮件列表,而不是恕我直言的StackOverflow。
今天我开始玩分支,检查两个布尔值。我很确定,在某些优化级别上,它们将简单地添加并检查,但gcc和CLANG不是这样。为什么gcc不优化两个bool检查,用addition和一个check替换它们?让我给你看一个例子: 两个分支(test+je)不应该比加法和分支(add+jne)慢吗? 编辑:我真正的意思是乘法,因为在true和false的情况下(1+0),加法给出true(1),但乘法给出正确的
问题内容: 我正在尝试更新PKHUD(https://github.com/pkluz/PKHUD)以与Xcode 6 beta 5一起使用,并且除了一个小细节外,几乎可以通过: Xcode给我错误。我敢肯定这是与类型转换有关的小错误,但是我已经几个小时找不到答案了。 另外,此错误仅在Xcode 6 beta 5中发生,这意味着答案在于Apple最近更改的内容。 非常感谢所有帮助。 问题答案: 协
我不得不问:从逻辑上讲,为什么地址边界本身在什么上可分很重要?使用地址上的整数将一组内存分配给的调优有什么问题? 我知道指针算术是如何工作的,但我无法计算边界的重要性······
问题内容: 我正在尝试做这样的事情: 不幸的是,即使在Java 9中也不存在。 为什么它被遗漏了? 建议的解决方法是什么? 问题答案: 为什么它被遗漏了? 该API提供了可重用的构建块。这里的相关积木是,,。通过这些,您可以实现所需的功能:将流内映射到对象,然后获得平面图。提供构建基块的排列是不切实际的,并且很难扩展。 建议的解决方法是什么? 如前所述,使用可用的构建基块(+ ):
许多编译器都提供128位整数类型,但我使用过的编译器都没有提供typedefs。为什么? 据我回忆,标准 用于此目的的储量 鼓励提供此类类型的实现提供typedef 要求此类实现提供至少128位的intmax_t (而且,我不相信我使用了实际上符合最后一点的实现)
问题内容: 我有以下CSS: 添加边框半径:5px似乎没有任何作用,我认为这是因为我使用的是边框渐变,我是否有办法完全实现所需的5px边框半径? 问题答案: You cannot use with gradient. Here is another idea where you can rely on multiple background and adjust the : 如果需要透明性,可以考