Java 强制隐式数组边界检查的最便宜方法
假设我有一个具有以下签名的方法:Java 强制隐式数组边界检查的最便宜方法,java,arrays,performance,jvm,micro-optimization,Java,Arrays,Performance,Jvm,Micro Optimization,假设我有一个具有以下签名的方法: public int indexOf(byte[] bytes, byte toFind, int offset, int length) { ... } 这个方法很简单,比如查找字节中[offset,offset+length]范围内的字节查找。我想预先检查offset和length是否对字节有效。也就是说,offset和offset+length以字节为单位 显式检查类似于: if (offset < 0 || offset > bytes
public int indexOf(byte[] bytes, byte toFind, int offset, int length) {
...
}
这个方法很简单,比如查找字节中[offset,offset+length]范围内的字节查找。我想预先检查offset和length是否对字节有效。也就是说,offset和offset+length以字节为单位
显式检查类似于:
if (offset < 0 || offset > bytes.length - length) {
throw ...; // bad santa!
}
如果可以的话,我想去掉int伪
和+
,或者降低它们的成本。编译器不喜欢像bytes[offset];
这样的独立访问,大概是因为这样的表达式通常没有副作用,而且毫无意义(但在这种情况下并非如此)。使用伪int还会导致编译器警告,必须抑制该警告
关于如何使用最少字节码进行更改的任何建议(运行时性能在这里也很重要,但我怀疑大多数解决方案在删除未使用的部分时都针对相同的问题进行了优化)。不确定字节码长度,但如何:
bytes[offset] |= bytes[offset];
bytes[offset + length - 1] |= bytes[offset + length - 1];
不确定字节码长度,但是:
bytes[offset] |= bytes[offset];
bytes[offset + length - 1] |= bytes[offset + length - 1];
这个怎么样
if ((bytes[offset] | bytes[offset+length-1])==0) { }
这个怎么样
if ((bytes[offset] | bytes[offset+length-1])==0) { }
我希望只执行bytes[offset]
和bytes[offset+length-1]
是最便宜的方法。JVM字节码中最短的方法就是执行这些表达式并将其留在操作数堆栈上
但是,您不能在Java中执行此操作。您也不能使用pop2
指令(或两条pop
指令),因为bytes[something]
不是有效的Java命令。有三种可能的最佳方法:
使用类似intjava.lang.Math.max(int,int)的方法调用
。这将添加一条3字节指令和一条1字节指令。因此,这是一个4字节的开销。如果您编写一个包含两个int
参数和void
结果的静态伪方法,则可以节省一个字节。智能JVM优化器可能会将此代码减少为一条指令,因为Math.max(…)
没有副作用,您可以通过指令放弃结果。但是,我不确定这是否适用于Hotspot
将其分配给局部变量。一个赋值意味着一个istore
指令。如果有五个参数(包括this
,因为该方法不是静态的),则使用通用的2字节版本而不是1字节(对于{0,1,2,3}中的n)。如果您最多有三个参数,则可能会通过缩小虚拟变量的范围来保存某些内容
比较它(=>generate boolean)并使用空分支,即if((bytes[offset]==bytes[offset+length-1]){}
。在这种情况下,您不需要任何额外的方法(如max或pop2)或任何额外的局部变量(这会放大局部变量表)
如果您不使用任何进一步的优化器,也不修改方法签名以使用更少的变量,那么第三种方法可能是赢家。在我的简单测试中,它只需要16字节的指令(其他一些实现是相同的,但不是更好的)并且不需要局部变量表或常量池中的任何内容。您可能可以通过手动字节码优化或Proguard节省几个字节。但请小心,Proguard可能会对其进行过多优化,并删除数组访问。(我不确定,但它在文档中声称可能会删除一些NullPointerException。)
请参阅我希望只执行字节[offset]
和字节[offset+length-1]
是最便宜的方法。JVM字节码中最短的方法就是执行这些表达式并将其保留在操作数堆栈上
但是,您不能在Java中执行此操作。您也不能使用pop2
指令(或两条pop
指令),因为bytes[something]
不是有效的Java命令。有三种可能的最佳方法:
使用类似intjava.lang.Math.max(int,int)的方法调用
。这将添加一条3字节指令和一条1字节指令。因此,这是一个4字节的开销。如果您编写一个包含两个int
参数和void
结果的静态伪方法,则可以节省一个字节。智能JVM优化器可能会将此代码减少为一条指令,因为Math.max(…)
没有副作用,您可以通过指令放弃结果。但是,我不确定这是否适用于Hotspot
将其分配给局部变量。一个赋值意味着一个istore
指令。如果有五个参数(包括this
,因为该方法不是静态的),则使用通用的2字节版本而不是1字节(对于{0,1,2,3}中的n)。如果您最多有三个参数,则可能会通过缩小虚拟变量的范围来保存某些内容
比较它(=>generate boolean)并使用空分支,即if((bytes[offset]==bytes[offset+length-1]){}
。在这种情况下,您不需要任何额外的方法(如max或pop2)或任何额外的局部变量(这会放大局部变量表)
如果您不使用任何进一步的优化器,也不修改方法签名以使用更少的变量,那么第三种方法可能是赢家。在我的简单测试中,它只需要16字节的指令(其他一些实现是相同的,但不是更好的)并且不需要局部变量表或常量池中的任何内容。您可能可以通过手动字节码优化或Proguard节省几个字节。但请小心,Proguard可能会对其进行过多优化,并删除数组访问。(我不确定,但它在文档中声称可能会删除一些NullPointerException。)
参见就字节码长度而言,最便宜的方法是几个JRE类使用的方法,例如:或:使用专用检查方法
从Java9开始,有一个用于此目的的标准方法,它也开始取代这些inter
0: aload_1
1: iload_3
2: baload
3: aload_1
4: iload_3
5: iload 4
7: iadd
8: iconst_1
9: isub
10: baload
11: iadd
12: istore 5
14: ...
public int indexOf2(byte[] bytes, byte toFind, int offset, int length) {
if ((bytes[offset] | bytes[offset+length-1])==0) { }
//...
}
0: aload_1
1: iload_3
2: baload
3: aload_1
4: iload_3
5: iload 4
7: iadd
8: iconst_1
9: isub
10: baload
11: ior
12: ifne 15
15: ...
public int indexOf3(byte[] bytes, byte toFind, int offset, int length) {
checkIndex(bytes, offset, length);
//...
}
private void checkIndex(byte[] bytes, int offset, int length) {
//...
}
0: aload_0
1: aload_1
2: iload_3
3: iload 4
5: invokespecial #23 // Method checkIndex:([BII)V
8: ...
private void checkIndex(byte[] bytes, int offset, int length) {
if((offset | length | (offset+length) | (bytes.length-(offset+length))) < 0)
throw new IndexOutOfBoundsException();
}