At the byte code level a Java boolean is represented as either 0 or 1. I have an expression that results in 0 or 1 but it is computed with the int type. A simple example would be:
public static int isOdd_A(int value) {
return value & 1;
}
public static boolean isOdd_B(int value) {
return (value & 1) == 1;
}
The byte code for the above methods looks like this:
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
The method that returns a boolean is much larger and contains a branch so it is less optimal if the machine code that runs is equivalent.
Will the HotSpot JVM know that the boolean version can be optimized to branchless machine code? Is there a way to trick Java into using the int-based byte code for a method that returns a boolean (e.g. using ASM) ?
EDIT: Many have suggested this isn't worth worrying about, and in general I agree. However I did created this micro benchmark and ran it with jmh and noticed an improvement with the int version of around 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
I agree the code should be readable (that's why I want the performance of the branchless int version with the proper boolean interface), and most of the time this level of optimization is not warranted. However in this case there was a 10% gain even when the method in question doesn't even account for a majority of the code.
So perhaps what we have here is a case where HotSpot can be made aware of this pattern and generate better code.
In Java, you must declare a method of the boolean type in order for it to return a boolean value. The boolean method will return the boolean value, true or false. You can either return the variable containing a boolean value or use conditional statements to decide the returned value.
You declare a method's return type in its method declaration. Within the body of the method, you use the return statement to return the value. Any method declared void doesn't return a value. It does not need to contain a return statement, but it may do so.
In Java, a null value can be assigned to an object reference of any type to indicate that it points to nothing. The compiler assigns null to any uninitialized static and instance members of reference type. In the absence of a constructor, the getArticles() and getName() methods will return a null reference.
To display Boolean type, firstly take two variables and declare them as boolean. val1 = true; Now, use if statement to check and display the Boolean true value. if(val1) System.
First of all, 10% is not a speed difference that is worth any effort.
Note that explicit conversions to zero or one only happen when there is an explicit assignment to boolean
(which includes return
statements of methods declared to return boolean
). When the expression is part of a conditional or a compound boolean
expression, this will not happen, e.g.
static boolean isOddAndShort(int i) {
return (i&1)!=0 && (i>>>16)==0;
}
compiles to
static boolean isOddAndShort(int);
descriptor: (I)Z
flags: ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: iload_0
1: iconst_1
2: iand
3: ifeq 17
6: iload_0
7: bipush 16
9: iushr
10: ifne 17
13: iconst_1
14: goto 18
17: iconst_0
18: ireturn
As you see, the two expressions are not converted to zero or one before the and
operation, only the final result.
Likewise
static void evenOrOdd(int i) {
System.out.println((i&1)!=0? "odd": "even");
}
compiles to
static void evenOrOdd(int);
descriptor: (I)V
flags: ACC_STATIC
Code:
stack=3, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: iload_0
4: iconst_1
5: iand
6: ifeq 14
9: ldc #3 // String odd
11: goto 16
14: ldc #4 // String even
16: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
19: return
not bearing any conversion to zero or one.
(Note that comparing with zero here utilizes the knowledge about i&1
returning zero or one better than comparing with one).
So when we’re talking about, e.g. 0.01% of an actual application code (or even less) and assume a 10% speedup of that particular code, we can expect an overall speed improvement of 0.001% (or even less).
Still, just for fun or as a small code compression feature (maybe as part of a more general code compression or byte code obfuscation), here, an ASM based solution:
To make the transformation easier, we define a place-holder method, i2b
performing an int
to boolean
transformation and invoke it at the intended place(s). The transformator simply removes both, the method declaration and its invocations:
public class Example {
private static boolean i2b(int i) {
return i!=0;
}
public static boolean isOdd(int i) {
return i2b(i&1);
}
public static void run() {
for(int i=0; i<10; i++)
System.out.println(i+": "+(isOdd(i)? "odd": "even"));
}
}
public class Int2Bool {
public static void main(String[] args) throws IOException {
String clName = Example.class.getName();
ClassReader cr = new ClassReader(clName);
ClassWriter cw = new ClassWriter(cr, 0);
cr.accept(new ClassVisitor(Opcodes.ASM5, cw) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if(name.equals("i2b") && desc.equals("(I)Z")) return null;
return new MethodVisitor(Opcodes.ASM5, super.visitMethod(access, name, desc, signature, exceptions)) {
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
if(opcode == Opcodes.INVOKESTATIC && name.equals("i2b") && desc.equals("(I)Z"))
return;
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
};
}
}, 0);
byte[] code = cw.toByteArray();
if(writeBack(clName, code))
Example.run();
else
runDynamically(clName, code);
}
private static boolean writeBack(String clName, byte[] code) {
URL u = Int2Bool.class.getResource("/"+clName.replace('.', '/')+".class");
if(u==null || !u.getProtocol().equals("file")) return false;
try {
Files.write(Paths.get(u.toURI()), code, StandardOpenOption.TRUNCATE_EXISTING);
return true;
} catch(IOException|URISyntaxException ex) {
ex.printStackTrace();
return false;
}
}
private static void runDynamically(String clName, byte[] code) {
// example run
Class<?> rtClass = new ClassLoader() {
Class<?> get() { return defineClass(clName, code, 0, code.length); }
}.get();
try {
rtClass.getMethod("run").invoke(null);
} catch (ReflectiveOperationException ex) {
ex.printStackTrace();
}
}
}
The transformed method looks like
public static boolean isOdd(int);
descriptor: (I)Z
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: iload_0
1: iconst_1
2: iand
3: ireturn
and works without problems. But as said, that’s just as an exercise, not of much practical worth.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With