我想测量,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倍?
答案 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设置 - 我不记得我的头脑。
当然,这与其他答案指出你实际上没有测量线程程序的问题完全不同。
编辑:另外要注意的是编译器意识到任何特定的变量/循环/整个程序都是完全没有意义的。在这些情况下,它可能只是完全删除它 - 您可能会发现需要使用res1
和res2
,否则您的循环可能会从已编译的代码中完全删除。
编辑:刚才意识到你确实使用了所有的计数变量 - 但是,知道它仍然是一件有用的事情,所以我会留下它。