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();
    }