适当的基准?

时间:2013-01-13 22:12:35

标签: java multithreading benchmarking

我想测量,2个不同的程序需要多长时间才能执行1个任务。一个程序使用线程,另一个程序没有使用。任务是计数到2000000。

使用线程的类:

public class Main {
    private int res1 = 0;
    private int res2 = 0;

    public static void main(String[] args) {
        Main m = new Main();

        long startTime = System.nanoTime();
        m.func();
        long endTime = System.nanoTime();

        long duration = endTime - startTime;
        System.out.println("duration: " + duration);
    }

    public void func() {
        Thread t1 = new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 1000000; i++) {
                    res1++;
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 1000000; i < 2000000; i++) {
                    res2++;
                }
            }
        });

        t1.start();
        t2.start();

        System.out.println(res1 + res2);
    }
}

没有线程的类:

public class Main {

    private int res = 0;

    public static void main(String[] args) {
        Main m = new Main();

        long startTime = System.nanoTime();
        m.func();
        long endTime = System.nanoTime();

        long duration = endTime - startTime;
        System.out.println("duration: " + duration);

    }

    public void func() {

        for (int i = 0; i < 2000000; i++) {
            res++;
        }
        System.out.println(res);
    }
}

10次测量后,平均结果(以纳秒为单位)为:

With threads:    1952358
Without threads: 7941479

我做得对吗? 怎么来,2个线程的速度快4倍而不仅仅是2倍?

4 个答案:

答案 0 :(得分:8)

在行

    t1.start();
    t2.start();

您正在开始执行线程,但在进行时间测量之前,您实际上并没有等待它们完成。要等到线程完成,请调用

   t1.join();
   t2.join();

join方法将一直阻塞,直到线程结束。然后测量执行时间。

答案 1 :(得分:5)

在并行版本中,您将测量主线程创建其他两个线程的数量。您没有测量他们的执行时间。这就是你获得超线性加速的原因。为了包括它们的执行时间,你必须将它们与主线程连接起来。

t2.start();

之后添加这些行
     t1.join();  // wait until thread t1 terminates
     t2.join(); // wait until thread t2 terminates

答案 2 :(得分:2)

多线程版本更快的主要原因是您不必等待循环完成。你只等待线程开始。

您需要在start();

之后添加
    t1.join();
    t2.join();

一旦你这样做,你会注意到启动线程需要很长时间,因为它的速度要慢得多。如果你的测试时间延长100倍,启动线程的成本就不那么重要了。

单线程示例需要更长时间才能正确进行JItted。您需要确保重复运行测试至少2秒

我的多线程版本是

public class Main {
    private long res1 = 0;
    public long p0, p1, p2, p3, p4, p5, p6, p7;
    private long res2 = 0;

    public static void main(String[] args) throws InterruptedException {
        Main m = new Main();

        for (int i = 0; i < 10; i++) {
            long startTime = System.nanoTime();
            m.func();
            long endTime = System.nanoTime();

            long duration = endTime - startTime;
            System.out.println("duration: " + duration);
        }
        assert m.p0 + m.p1 + m.p2 + m.p3 + m.p4 + m.p5 + m.p6 + m.p7 == 0;
    }

    public void func() throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000000000; i++) {
                    res1++;
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1000000000; i < 2000000000; i++) {
                    res2++;
                }
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(res1 + res2);
    }
}

为多线程测试打印以下内容。

2000000000
duration: 179014396
4000000000
duration: 148814805
.. deleted ..
18000000000
duration: 61767861
20000000000
duration: 72396259

对于单线程版本,我注释掉一个线程并获取

2000000000
duration: 266228421
4000000000
duration: 255203050
... deleted ...
18000000000
duration: 125434383
20000000000
duration: 125230354

正如预期的那样,当运行时间足够长时,两个线程的速度几乎是一个线程的两倍。

简而言之,

  • 如果您不等待这些操作完成,则多线程代码可以对当前线程具有较小的延迟,例如异步日志记录和消息传递。

  • 单线程编码可以比多线程代码快得多(也更简单),除非你有一个重要的CPU绑定任务要执行(或者你可以做并发IO)

  • 在同一JVM中重复运行测试可能会产生不同的结果

答案 3 :(得分:1)

在java中进行基准测试时,需要记住几个技巧。

在对任何事情进行基准测试时,第一个是相同的:一次运行可能发生比另一次运行慢,没有任何意义。为避免这种情况,请多次运行并取平均值(我的意思是很多次)。

第二个可能不是java独有的,但可能会令人惊讶:java VM可能需要一些时间来“预热” - 如果你运行你的代码一百次,编译后的代码可以change根据什么代码路径非常普遍。为了解决这个问题,请在开始统计之前多次运行代码

预热需要多长时间取决于你的JVM设置 - 我不记得我的头脑。

当然,这与其他答案指出你实际上没有测量线程程序的问题完全不同。

编辑:另外要注意的是编译器意识到任何特定的变量/循环/整个程序都是完全没有意义的。在这些情况下,它可能只是完全删除它 - 您可能会发现需要使用res1res2,否则您的循环可能会从已编译的代码中完全删除。

编辑:刚才意识到你确实使用了所有的计数变量 - 但是,知道它仍然是一件有用的事情,所以我会留下它。