4

I am trying to create a class that times the average runtime of a method. I understand how to do that by running it 100 times and taking the average of it all. Example:

private long calculateAvg(){
    long totalTime = 0;

    for(int i = 0; i < ITERATIONS; i++){
        long startTime = System.nanoTime();
        testMethod();
        long endTime = System.nanoTime();

        totalTime += (endTime - startTime);  //divide by 1000000 to get milliseconds.
    }
    return (totalTime / ITERATIONS);

}

Now I can set this to work for one static method but is there a way to pass different static methods into this to calculate instead of creating one of these for each method I want to test? If not, is there a design pattern that may work here? As of now, I am creating one of these methods for every other method I want to time and it doesn't seem efficient because I am reusing so much code.

user081608
  • 1,093
  • 3
  • 22
  • 48

5 Answers5

4

You can accept and run a Runnable argument:

private long calculateAvg(Runnable r) {
    //...
    long startTime = System.nanoTime();
    r.run();
    long endTime = System.nanoTime();
    //...
}

And call it like this:

long avg1 = calculateAvg(() -> testMethod());
long avg2 = calculateAvg(() -> testMethod2());

You might also want to consider some tips from How do I write a correct micro-benchmark in Java?

Community
  • 1
  • 1
shmosel
  • 49,289
  • 6
  • 73
  • 138
  • Very interesting. Does `() -> ` make it a Runnable argument? – user081608 Mar 22 '17 at 02:27
  • Yes, it's a lambda. You can also use method references or anonymous classes if you prefer. – shmosel Mar 22 '17 at 02:27
  • @user081608 "Does () -> make it a Runnable argument?" No. This syntax is for lambda expressions. On the other hand, `Runnable` is an interface with a single method named `run()`. Lambdas are a shortcut to the "old school" way of creating a class that implements an interface, often an anonymous inner class. – Code-Apprentice Mar 22 '17 at 02:30
  • @Code-Apprentice http://stackoverflow.com/questions/27973294/function-with-no-args-and-no-return-type-void-in-java-8?lq=1#comment44339647_27973294 – shmosel Mar 22 '17 at 02:32
  • 2
    @Code-Apprentice **Wrong**. They are suitable for any use of a method that has no return value and takes no parameters. Note that when Java 8 introduced lambdas and method references, they also added a lot of standardized interface definitions for methods that take 0, 1, or 2 parameters and return or don't return a result. But they didn't define an interface for the case of methods with no parameters and no result. The reason they didn't? Because Java already had `Runnable`. – ajb Mar 22 '17 at 02:32
  • @ajb Wow, great timing :) – shmosel Mar 22 '17 at 02:33
  • @user081608 Not quite. `() ->` _code_ makes it a lambda expression. Roughly speaking, this lambda expression is a stand-in for any `interface` with one interface method that takes no parameters and returns no result (assuming the code doesn't have a `return` statement). `Runnable` is one such interface, but there may be others. Java actually looks at the type to expect from context, and based on that, it uses `Runnable` for the interface. – ajb Mar 22 '17 at 02:34
  • @ajb Thanks for the correction. I agree that lambdas are sutable when there is no return value, but they are not limited to methods with no parameters. You can also use lambdas for methods that take 1 or 2 parameters. Of course, in this instance, `()` means there are no parameters. – Code-Apprentice Mar 22 '17 at 02:36
  • 1
    Note that instead of `calculateAvg(() -> testMethod());`, you can also save `calculateAvg(TestClass::testMethod);` if `TestClass` defines the static method `testMethod()` with no parameters or result. – ajb Mar 22 '17 at 02:37
  • 1
    @shmosel To improve your answer, you can show both the "old school" anonymous inner class way along with the lambda way. Then explain a little more about the differences. (I am suggesting this since the OP seems to be very new to Java and definitely should learn more about interfaces and classes.) – Code-Apprentice Mar 22 '17 at 02:39
  • @Code-Apprentice Right, I hope nobody tried to imply that they're limited to that. You can use lambdas for methods that take any number of parameters, although for >= 3 parameters you won't find a built-in interface class in `java.util.function`. – ajb Mar 22 '17 at 02:39
  • @Code-Apprentice I did [mention](http://stackoverflow.com/questions/42941570/pass-static-methods-in-java/42941595?noredirect=1#comment72979975_42941595) the alternatives. I don't think they're relevant enough to include in the answer. – shmosel Mar 22 '17 at 02:59
3

You cannot "pass methods", static nor non-static. Instead, you need to create an object reference created from a class which contains the method. Then you call the method on the object reference. In order to provide different implementations of a method, you create an interface. Then you can have many classes which implement that interface and the method which it declares. Now your timing code uses that interface to access the correct method.

public interface TheMethod {
    public void foo();
}

public class Method1 implements TheMethod {
    public void foo() {
        // code goes here 
    }
}

public class Method2 implements TheMethod {
    public void foo() {
        // code goes here 
    }
}

Now modify your timing method to accept a TheMethod instance:

private long calculateAvg(TheMethod method){
    // ...
    method.foo();
    // ...
}

Finally, you can call this method with different instances of classes which implement TheMethod:

TheMethod m1 = new Method1();
TheMethod m2 = new Method2();

long x = calculateAvg(m1);
long y = calculateAvg(m2);
Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268
1

I got two solutions may help for this question:
1. Java reflection:

public long calculateAvg(Object obj, String methodName) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException{
    Method method = obj.getClass().getDeclaredMethod(methodName);
    method.setAccessible(true);

    long totalTime = 0;
    for(int i = 0; i < ITERATIONS; i++) {
        long startTime = System.nanoTime();
        method.invoke(obj);
        long endTime = System.nanoTime();

        totalTime += (endTime - startTime);  //divide by 1000000 to get milliseconds.
    }
    return (totalTime / ITERATIONS);
}
  1. Proxy Pattern

You can learn about proxy pattern, you don't need to call the real method yourself, the proxy does. you can get the time before and after calling the real method. proxy pattern tutorials

Dave Pateral
  • 1,415
  • 1
  • 14
  • 21
1

No you cann't pass method in Java because methods are not first class citizen in Java, they are not values. The pattern to resolve this kind of problem is to use anonymous class. Take your case as example, you can define an interface:

@FunctionalInterface  // this annotation is available since jdk1.8
public interface Measurable {
    void measure();
}

And another MeasureUtil util class, methods inside it accept the type Measurable:

    public class MeasureUtil {
    private static final long ITERATIONS = 1000;

    public static long calculateAvg(Measurable measurable) {
        long totalTime = 0;

        for(int i = 0; i < ITERATIONS; i++){
            long startTime = System.nanoTime();
            measurable.measure();
            long endTime = System.nanoTime();

            totalTime += (endTime - startTime);  //divide by 1000000 to get milliseconds.
        }
        return (totalTime / ITERATIONS);
    }
}

Then you new an instance of Measurable and pass it to MeasureUtil.calculateAvg each time:

    public class Main {
    public static void main(String[] args) {
        long avg = MeasureUtil.calculateAvg(new Measurable() {
            @Override
            public void measure() {
                method_under_mesure();
            }
        });
        System.out.println("Avg: " + avg);
    }

    private static void method_under_mesure() {
        System.out.println("call me");
    }
}

Since jdk1.8 Java starts to support lambda expression, which can make it much simpler and easier with the syntax sugar:

long avg = MeasureUtil.calculateAvg(() -> method_under_mesure());
shizhz
  • 11,715
  • 3
  • 39
  • 49
0

Based on your input, your method should be something like this

private long calculateAvg(Testable t){
  long totalTime = 0;

  for(int i = 0; i < ITERATIONS; i++){
    long startTime = System.nanoTime();
    t.testMethod();
    long endTime = System.nanoTime();

    totalTime += (endTime - startTime);  //divide by 1000000 to get milliseconds.
  }
  return (totalTime / ITERATIONS);
}

Then, you should have an interface with a single method:

public interface Testable {
    public void testMethod();
}

In your main method, you have 3 ways to pass the method. Let's say you want to test a static sort method in SelectionSort class.

First: anonymous class (the traditional way)

calculateAvg(new Testable(){
    @Override
    public void testMethod(){
       SelectionSort.sort();
    }
})

Second: Lambda (Java 8)

calculateAvg(() -> SelectionSort.sort())

Third: Method Reference Java 8 Method Reference: How to Use it

calculateAvg(SelectionSort::sort)
Balloon Wen
  • 61
  • 1
  • 2