【问题标题】:Passing methods to a benchmark program in Java将方法传递给 Java 中的基准程序
【发布时间】:2017-09-04 15:08:11
【问题描述】:

我正在用 Java 实现一些排序算法,并希望能够对每个算法进行计时和比较。目前,我有一个脚本分别对每个算法进行计时,代码冗余量很大:

long min = 1000000;
long startTime;
long endTime;

QuickSort quicksorter = new QuickSort();

for (int i = 0; i != 10000; ++i) {
    startTime = System.nanoTime();
    quicksorter.sort();
    endTime = System.nanoTime();
    if ((endTime - startTime) < min)
        min = endTime - startTime;
}

System.out.printf("Quicksort min time is %d ns.\n", min);


BinaryInsertionSort binarysorter = new BinaryInsertionSort();
min = 1000000;

for (int i = 0; i != 10000; ++i) {
    startTime = System.nanoTime();
    binarysorter.sort();
    endTime = System.nanoTime();
    if ((endTime - startTime) < min)
        min = endTime - startTime;
}

System.out.printf("Binary insertion sort min time is %d ns.\n", min);

使用六七种不同的排序算法,这开始让人感觉效率极低。显而易见的“python-esque”解决方案是定义一个方法,它将方法作为参数并像这样对它们进行计时:

long timeit(function func) {
    min = 1000000;

    for (int i = 0; i != 10000; ++i) {
        startTime = System.nanoTime();
        func();
        endTime = System.nanoTime();
        if ((endTime - startTime) < min)
            min = endTime - startTime;
    }

    System.out.printf("Min execution time is %d ns.\n", min);
}

但是,我认为在 Java 中将方法作为参数传递是不可取的(并且不方便),而且通常这样做可能是不可能的。我想每个排序器类都可以继承自一个实现基准测试方法的抽象类,但是在 Java 中是否有更好的方法来实现这一点?

【问题讨论】:

标签: java algorithm parameter-passing benchmarking timing


【解决方案1】:

但是,我认为在 Java 中将方法作为参数传递是不可取的(并且不方便),而且通常这样做可能是不可能的

嗯?

从 Java 8 开始,您可以将此类泛型方法作为参数传递;你想要的是Runnable

假设你在同一个类中有两个方法:

public final class Foo
{
    void f1() { /* whatever */ }
    void f2() { /* whatever */ }
}

然后您可以使用方法引用在代码中计时,如下所示:

final Runnable r1 = Foo::f1;
final Runnable r2 = Foo::f2;

然后将这些方法引用(它们是有效的 lambda)用于一些计时代码:

long start, end;

Stream.of(r1, r2).forEach(r -> {
    start = System.nanoTime();
    r.run();
    end = System.nanoTime();
    System.out.println("Time spent in ns: " + (end - start));
});

之后,您可以进一步细化您的代码,以便使用例如IntConsumers


现在,诚然,出于基准测试的目的,一次运行是行不通的。有 JIT 需要考虑,对于“一定数量”的某些定义,JIT 只会在相同代码路径的一定数量的执行后启动。所以,虽然上面的代码会给你一个指示(这个指示的价值),但无论如何你都不应该把它当作绝对的。

为了进行基准测试,请考虑使用jmh...并仔细阅读所有文档! JVM 同时也是运行代码的高效而复杂的设备……

【讨论】:

    【解决方案2】:

    您在 Java 世界中的思路是正确的。但是,如果您不想在这种情况下弄乱继承,则可以通过反射来实现所需的一种方法,方法如下:

    public static long timeit(Class c) {
        long startTime, endTime, min;
    
        // new object and the method to call
        Object instance = c.newInstance();
        Method method = getClass().getDeclaredMethod("sort");
    
        min = Long.MAX_VALUE;
    
        for (int i = 0; i != 10000; ++i) {
            startTime = System.nanoTime();
            // call the function
            method.invoke(obj);
            endTime = System.nanoTime();
            min = endTime - startTime < min ? endTime - startTime : min;
        }
        return min;
    }
    
    
    //then you call it like this:
    long time1 = timeit(QuickSort.class);
    

    话虽如此,与此相关的方法可能会引入一些开销,但它仍然应该为您提供一个很好的比较图。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-04
    • 2012-02-09
    • 2012-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多