3

我想测量 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

4 回答 4

8

在行中

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

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

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

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

于 2013-01-13T22:21:40.493 回答
5

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

在之后添加这些行t2.start();

     t1.join();  // wait until thread t1 terminates
     t2.join(); // wait until thread t2 terminates
于 2013-01-13T22:19:45.817 回答
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 中重复运行测试会得到不同的结果

于 2013-01-13T22:43:29.160 回答
1

在 Java 中进行基准测试时,您需要记住一些技巧。

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

第二个可能不是 java 独有的,但可能令人惊讶:java VM 可能需要一些时间来“预热”——如果你运行你的代码一百次,编译的代码可以change根据哪些代码路径非常常见。要解决这个问题,请在开始获取 stats 之前多次运行代码。

预热需要多长时间取决于您的 JVM 设置 - 我不太记得了。

当然,这与其他答案指出的问题完全不同,即您实际上并未测量线程程序。

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

编辑:刚刚意识到你确实使用了所有的计数变量——不过,知道它仍然是一件有用的事情,所以我会把它留在里面。

于 2013-01-13T22:25:07.800 回答