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