In terms of readability and exposing responsibility of method immediately, my personal preference is to return immediately from a method once task is done. Based on my experience with dealing complex, long methods with limited documentation, multiple returns in a method helped me to understand code easier in past.
But readability is not always the only factor when I write code in my current job. Performance (low latency) beats readability. So, I decided to benchmark these two methods:
Multiple return points
public Number multipleReturnPoints(Number num) {
if (num instanceof Integer) {
return Integer.valueOf(num.intValue());
}
if (num instanceof Float) {
return Float.valueOf(num.floatValue());
}
if (num instanceof Double) {
return Double.valueOf(num.doubleValue());
}
return null;
}
Single returns in the end
public Number singleReturnPoint(Number num) {
Number val = null;
if (num instanceof Integer) {
val = Integer.valueOf(num.intValue());
}
if (num instanceof Float) {
val = Float.valueOf(num.floatValue());
}
if (num instanceof Double) {
val = Double.valueOf(num.doubleValue());
}
return val;
}
I used following setup for testing:
- Both method is called 5 million times, after 500K warm up
- In order to asses JIT compile's affect, two different schemes is used for methods parameter selection:
- Clustered (Batch): Same type parameter called in batch sequentially. In other words first Double, then Integer and finally Float type is chosen.
- Random : Type parameter is chosen from a uniform distribution. This is more realistic case would happen in a system.
- Following JRE are tested:
- Oracle JRE 1.6.0_20
- Oracle JRE 1.7.0_15
- Oracle JRE 1.8.0_11
- Azur Zing 1.7.0-5.9.5.0
- Garbage collection is prevented by allocating large amount of heap size
- Tests are run on Redhat Linux (64 bit,2.93GHz).
Results
Above benchmark test is done 7 times and in below charts, average latency (mSec) is illustrated .
Oracle JDK, although single
returns perform marginally better , there is not much difference between
multiple or single returns for both clustered random parameter selection. Whereas for Zing JVM, there is not a definitive conclusion. But given that random parameter selection is more realistic, single return is the winner, I think.
In conclusion, I could not find a definitive answer which
method exit case performs better in terms of low latency.
package com.denizstij;
public interface Simulation {
Number run(Number num);
String getName();
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
package com.denizstij;
import java.util.Arrays;
import java.util.Random;
public class SingleOrMultipleExitPoints {
public static class MultipleReturns implements Simulation {
public Number run(Number num) {
if (num instanceof Integer) {
return Integer.valueOf(num.intValue());
}
if (num instanceof Float) {
return Float.valueOf(num.floatValue());
}
if (num instanceof Double) {
return Double.valueOf(num.doubleValue());
}
return null;
}
@Override
public String getName() {
return "Multiple Returns";
}
}
public static class SingleReturn implements Simulation {
public Number run(Number num) {
Number val = null;
if (num instanceof Integer) {
val = Integer.valueOf(num.intValue());
}
if (num instanceof Float) {
val = Float.valueOf(num.floatValue());
}
if (num instanceof Double) {
val = Double.valueOf(num.doubleValue());
}
return val;
}
@Override
public String getName() {
return "Single Return";
}
}
public static void main(String[] args) {
Random random = new Random(0);
int warmUpLoopSize = 50000;
int loopSize = warmUpLoopSize * 10;
if (args.length==2){
warmUpLoopSize=Integer.parseInt(args[0]);
loopSize=Integer.parseInt(args[1]);
System.out.println("Number of warmUpLoopSize:"+warmUpLoopSize);
System.out.println("Number of loopSize :"+loopSize);
}
// Arrays for warming up JVM
Integer warmUpIntArr[] = new Integer[warmUpLoopSize];
Float warmUpFloatArr[] = new Float[warmUpLoopSize];
Double warmUpDoubleArr[] = new Double[warmUpLoopSize];
// Simulation arrays
Integer intArr[] = new Integer[loopSize];
Float floatArr[] = new Float[loopSize];
Double doubleArr[] = new Double[loopSize];
// Creatring random numbers
createRandomNumbers(random, warmUpIntArr, warmUpFloatArr, warmUpDoubleArr,warmUpLoopSize);
createRandomNumbers(random, intArr, floatArr, doubleArr, loopSize);
// Creating test cases
MultipleReturns multipleReturns = new MultipleReturns();
SingleReturn singleReturn= new SingleReturn();
int[] warmUpRandomCallSeq = generateRandomCallSequence(warmUpLoopSize,3,random);
int[] warmUpClusteredCallSeq = generateClusteredCallSequence(warmUpLoopSize,3,random);
int[] randomCallSeq = generateRandomCallSequence(loopSize,3,random);
int[] clusteredCallSeq = generateClusteredCallSequence(loopSize,3,random);
//System.out.println(Arrays.toString(warmUpRandomCallSeq));
//System.out.println(Arrays.toString(warmUpClusteredCallSeq));
String simName="Clustered Warmup";
callSimulation(warmUpIntArr, warmUpFloatArr, warmUpDoubleArr,simName, multipleReturns,warmUpClusteredCallSeq);
callSimulation(warmUpIntArr, warmUpFloatArr, warmUpDoubleArr,simName, singleReturn,warmUpClusteredCallSeq);
simName="Clustered Calls";
callSimulation(intArr, floatArr, doubleArr,simName, multipleReturns,clusteredCallSeq);
callSimulation(intArr, floatArr, doubleArr,simName, singleReturn,clusteredCallSeq);
System.out.println("==============================");
simName="Random Warmup";
callSimulation(warmUpIntArr, warmUpFloatArr, warmUpDoubleArr,simName, multipleReturns,warmUpRandomCallSeq);
callSimulation(warmUpIntArr, warmUpFloatArr, warmUpDoubleArr,simName, singleReturn,warmUpRandomCallSeq);
simName="Random Calls";
callSimulation(intArr, floatArr, doubleArr,simName, multipleReturns,randomCallSeq);
callSimulation(intArr, floatArr, doubleArr,simName, singleReturn,randomCallSeq);
System.out.println("==============================");
}
private static void callSimulation(Integer[] intArr, Float[] floatArr, Double[] doubleArr, String msg, Simulation sim, int[] callSeq) {
long now = System.currentTimeMillis();
int clusterIndexes[]= new int[3];
int index=0;
for (int i = 0; i < callSeq.length; i++) {
int callerIndex = callSeq[i];
index=clusterIndexes[callerIndex]++;
if (index>=intArr.length){
continue;
}
switch (callerIndex){
case 0:
doubleArr[index] = (Double) sim.run(doubleArr[index]);
break;
case 1:
intArr[index] = (Integer) sim.run(intArr[index]);
break;
case 2:
floatArr[index] = (Float) sim.run(floatArr[index]);
break;
default:
System.out.println("Unknown cluster :("+callerIndex);
}
}
long finish = System.currentTimeMillis();
long elapsedTime = finish - now;
System.out.println(elapsedTime + ": " +msg+" :"+ sim.getName());
System.out.println(Arrays.toString(clusterIndexes));
System.out.flush();
}
static void createRandomNumbers(Random random, Integer intArr[],
Float floatArr[], Double doubleArr[], int size) {
for (int i = 0; i < size; i++) {
intArr[i] = Integer.valueOf(random.nextInt(size));
floatArr[i] = Float.valueOf(random.nextFloat() * size);
doubleArr[i] = Double.valueOf(random.nextDouble() * size);
}
}
static int[] generateClusteredCallSequence(int simSize,int numOfCluster,Random random){
int totalSize=numOfCluster*simSize;
int callSeq[]= new int[totalSize];
int index=0;
for (int j=0;j < numOfCluster;j++){
for (int i=0;i < simSize;i++){
callSeq[index++]=j;
}
}
return callSeq;
}
static int[] generateRandomCallSequence(int simSize,int numOfCluster,Random random){
int totalSize=numOfCluster*simSize;
int callSeq[]= new int[totalSize];
for (int i=0;i < totalSize;i++){
callSeq[i]=random.nextInt(numOfCluster);
}
return callSeq;
}
}

