在字节代码级别,Java布尔值表示为0或1.我有一个表达式,结果为0或1,但它是使用int类型计算的.一个简单的例子是:
public static int isOdd_A(int value) {
return value & 1;
}
public static boolean isOdd_B(int value) {
return (value & 1) == 1;
}
Run Code Online (Sandbox Code Playgroud)
上述方法的字节代码如下所示:
public static int isOdd_A(int);
descriptor: (I)I
Code:
0: iload_0
1: iconst_1
2: iand
3: ireturn
public static boolean isOdd_B(int);
descriptor: (I)Z
Code:
0: iload_0
1: iconst_1
2: iand
3: iconst_1
4: if_icmpne 11
7: iconst_1
8: goto 12
11: iconst_0
12: ireturn
Run Code Online (Sandbox Code Playgroud)
返回布尔值的方法要大得多,并且包含一个分支,因此如果运行的机器代码是等效的,则它不是最佳的.
HotSpot JVM是否知道布尔版本可以优化为无网格机器代码?有没有办法欺骗Java使用基于int的字节代码来返回一个布尔值的方法(例如使用ASM)?
编辑:许多人认为这不值得担心,总的来说我同意.但是我确实创建了这个微基准测试并用jmh运行它,并注意到int版本大约10%的改进:
@Benchmark
public int countOddA() {
int odds = 0;
for (int n : numbers)
if (Test.isOdd_A(n) == 1)
odds++;
return odds;
}
@Benchmark
public int countOddB() {
int odds = 0;
for (int n : numbers)
if(Test.isOdd_B(n))
odds++;
return odds;
}
Benchmark Mode Cnt Score Error Units
OddBenchmark.countOddA thrpt 100 18393.818 ± 83.992 ops/s
OddBenchmark.countOddB thrpt 100 16689.038 ± 90.182 ops/s
Run Code Online (Sandbox Code Playgroud)
我同意代码应该是可读的(这就是为什么我想要具有适当布尔接口的无分支int版本的性能),并且大多数时候这种优化级别是不合理的.然而,在这种情况下,即使所讨论的方法甚至不占大多数代码,也有10%的增益.
所以我们在这里可能会有一个案例,可以让HotSpot了解这种模式并生成更好的代码.
首先,10%并不是一个值得付出任何努力的速度差异。
\n\n请注意,仅当存在显式赋值boolean(其中包括return声明为 return 的方法的语句boolean)时,才会发生到零或一的显式转换。当表达式是条件表达式或复合boolean表达式的一部分时,这种情况不会发生,例如
static boolean isOddAndShort(int i) {\n return (i&1)!=0 && (i>>>16)==0;\n}\nRun Code Online (Sandbox Code Playgroud)\n\n编译为
\n\nstatic boolean isOddAndShort(int);\ndescriptor: (I)Z\nflags: ACC_STATIC\nCode:\n stack=2, locals=1, args_size=1\n 0: iload_0\n 1: iconst_1\n 2: iand\n 3: ifeq 17\n 6: iload_0\n 7: bipush 16\n 9: iushr\n 10: ifne 17\n 13: iconst_1\n 14: goto 18\n 17: iconst_0\n 18: ireturn\nRun Code Online (Sandbox Code Playgroud)\n\n正如你所看到的,这两个表达式在运算之前并没有转换为0或1and,只是最终的结果。
同样地
\n\nstatic void evenOrOdd(int i) {\n System.out.println((i&1)!=0? "odd": "even");\n}\nRun Code Online (Sandbox Code Playgroud)\n\n编译为
\n\nstatic void evenOrOdd(int);\ndescriptor: (I)V\nflags: ACC_STATIC\nCode:\n stack=3, locals=1, args_size=1\n 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;\n 3: iload_0\n 4: iconst_1\n 5: iand\n 6: ifeq 14\n 9: ldc #3 // String odd\n 11: goto 16\n 14: ldc #4 // String even\n 16: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V\n 19: return\nRun Code Online (Sandbox Code Playgroud)\n\n不承担任何到0 或 1 的转换。
\n\ni&1(请注意,此处与零比较比与一比较更好地利用了有关返回零或一的知识)。
因此,当我们\xe2\x80\x99 谈论实际应用程序代码的 0.01%(甚至更少)并假设该特定代码的加速率为 10% 时,我们可以预期总体速度提高 0.001%(甚至更少) )。
\n\n尽管如此,只是为了好玩或作为一个小的代码压缩功能(可能作为更通用的代码压缩或字节代码混淆的一部分),这里是一个基于 ASM 的解决方案:
\n\n为了使转换更容易,我们定义了一个占位符方法,i2b执行inttoboolean转换并在预期位置调用它。变换器只是删除方法声明及其调用:
public class Example {\n private static boolean i2b(int i) {\n return i!=0;\n }\n public static boolean isOdd(int i) {\n return i2b(i&1);\n }\n public static void run() {\n for(int i=0; i<10; i++)\n System.out.println(i+": "+(isOdd(i)? "odd": "even"));\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n\n\npublic class Int2Bool {\n public static void main(String[] args) throws IOException {\n String clName = Example.class.getName();\n ClassReader cr = new ClassReader(clName);\n ClassWriter cw = new ClassWriter(cr, 0);\n cr.accept(new ClassVisitor(Opcodes.ASM5, cw) {\n @Override\n public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {\n if(name.equals("i2b") && desc.equals("(I)Z")) return null;\n return new MethodVisitor(Opcodes.ASM5, super.visitMethod(access, name, desc, signature, exceptions)) {\n @Override\n public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {\n if(opcode == Opcodes.INVOKESTATIC && name.equals("i2b") && desc.equals("(I)Z"))\n return;\n super.visitMethodInsn(opcode, owner, name, desc, itf);\n }\n };\n }\n }, 0);\n byte[] code = cw.toByteArray();\n if(writeBack(clName, code))\n Example.run();\n else\n runDynamically(clName, code);\n }\n private static boolean writeBack(String clName, byte[] code) {\n URL u = Int2Bool.class.getResource("/"+clName.replace(\'.\', \'/\')+".class");\n if(u==null || !u.getProtocol().equals("file")) return false;\n try {\n Files.write(Paths.get(u.toURI()), code, StandardOpenOption.TRUNCATE_EXISTING);\n return true;\n } catch(IOException|URISyntaxException ex) {\n ex.printStackTrace();\n return false;\n }\n }\n\n private static void runDynamically(String clName, byte[] code) {\n // example run\n Class<?> rtClass = new ClassLoader() {\n Class<?> get() { return defineClass(clName, code, 0, code.length); }\n }.get();\n try {\n rtClass.getMethod("run").invoke(null);\n } catch (ReflectiveOperationException ex) {\n ex.printStackTrace();\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n转换后的方法看起来像
\n\npublic static boolean isOdd(int);\ndescriptor: (I)Z\nflags: ACC_PUBLIC, ACC_STATIC\nCode:\n stack=2, locals=1, args_size=1\n 0: iload_0\n 1: iconst_1\n 2: iand\n 3: ireturn\nRun Code Online (Sandbox Code Playgroud)\n\n并且工作没有问题。但正如所说,\xe2\x80\x99 只是作为练习,没有多大实际价值。
\n| 归档时间: |
|
| 查看次数: |
702 次 |
| 最近记录: |