单核中的多线程与异步编程
Multithreaded vs Asynchronous programming in a single core
如果实时 CPU 一次只执行一项任务,那么多线程与单处理器系统中的异步编程(在效率方面)有何不同?
举个例子,我们必须从 1 数到 IntegerMax。在我的多核机器的以下程序中,双线程最终计数几乎是单线程计数的一半。如果我们在单核机器上 运行 怎么办?有什么办法可以达到同样的效果吗?
class Demonstration {
public static void main( String args[] ) throws InterruptedException {
SumUpExample.runTest();
}
}
class SumUpExample {
long startRange;
long endRange;
long counter = 0;
static long MAX_NUM = Integer.MAX_VALUE;
public SumUpExample(long startRange, long endRange) {
this.startRange = startRange;
this.endRange = endRange;
}
public void add() {
for (long i = startRange; i <= endRange; i++) {
counter += i;
}
}
static public void twoThreads() throws InterruptedException {
long start = System.currentTimeMillis();
SumUpExample s1 = new SumUpExample(1, MAX_NUM / 2);
SumUpExample s2 = new SumUpExample(1 + (MAX_NUM / 2), MAX_NUM);
Thread t1 = new Thread(() -> {
s1.add();
});
Thread t2 = new Thread(() -> {
s2.add();
});
t1.start();
t2.start();
t1.join();
t2.join();
long finalCount = s1.counter + s2.counter;
long end = System.currentTimeMillis();
System.out.println("Two threads final count = " + finalCount + " took " + (end - start));
}
static public void oneThread() {
long start = System.currentTimeMillis();
SumUpExample s = new SumUpExample(1, MAX_NUM );
s.add();
long end = System.currentTimeMillis();
System.out.println("Single thread final count = " + s.counter + " took " + (end - start));
}
public static void runTest() throws InterruptedException {
oneThread();
twoThreads();
}
}
输出:
Single thread final count = 2305843008139952128 took 1003
Two threads final count = 2305843008139952128 took 540
对于纯粹的 CPU 绑定操作,你是正确的。大多数(99.9999%)程序都需要进行输入、输出和调用其他服务。这些比 CPU 慢几个数量级,因此在等待外部操作的结果时,OS 可以在时间片中调度和 运行 其他(许多其他)进程。
硬件多线程主要在满足 2 个条件时受益:
- CPU密集型作业;
- 可以有效地划分成独立的子集
或者您有很多不同的任务要 运行 可以有效地分配给多个硬件处理器。
In the following program for my multicore machine, the two thread final count count is almost half of the single thread count.
当应用程序使用两个内核时,这就是我对有效基准测试的期望。
但是,查看您的代码,我对您获得这些结果感到有些惊讶……如此可靠。
您的基准测试没有考虑 JVM 预热效果,尤其是 JIT 编译。
您的基准测试的 add
方法可能会被 JIT 编译器优化以完全摆脱循环。 (但至少这些计数被“使用”了......通过打印出来。)
我猜你很幸运……但我不相信这些结果对于所有版本的 Java 都是可重现的,或者如果你调整了基准。
请阅读:
- How do I write a correct micro-benchmark in Java?
What if we ran this in a single core machine?
假设如下:
- 您重写了基准以纠正上述缺陷。
- 您运行使用的系统禁用了硬件超线程12.
然后...我希望它需要两个线程才能比 多 两倍于一个线程版本。
问:为什么是“不止”?
A:因为启动新线程的开销很大。根据您的硬件、OS 和 Java 版本,它可能超过一毫秒。当然,如果你重复使用和丢弃线程,所花费的时间是很重要的。
And is there any way we could achieve the same result there?
不确定你在这里问什么。但是,如果您询问如何在多核机器上模拟一个内核的行为,您可能需要在 OS 级别执行此操作。 Linux 见 https://superuser.com/questions/309617 for Windows and https://askubuntu.com/questions/483824。
1 - Hyperthreading 是一种硬件优化,其中单个内核的处理硬件支持(通常)两个超线程。每个超线程
有自己的寄存器组,但它与其他超线程共享功能单元,例如 ALU。因此,这两个超线程的行为(通常)类似于两个内核,只是它们可能更慢,具体取决于精确的指令组合。典型的 OS 将 将超线程视为 ,就好像它是一个常规核心一样。超线程通常在启动时启用/禁用;例如通过 BIOS 设置。
2 - 如果启用了超线程,在像这样的 CPU 密集型计算中,两个 Java 线程的速度可能不会是一个线程的两倍......由于“其他”超线程在各自的核心上。有人提到基准测试很复杂吗?
如果实时 CPU 一次只执行一项任务,那么多线程与单处理器系统中的异步编程(在效率方面)有何不同?
举个例子,我们必须从 1 数到 IntegerMax。在我的多核机器的以下程序中,双线程最终计数几乎是单线程计数的一半。如果我们在单核机器上 运行 怎么办?有什么办法可以达到同样的效果吗?
class Demonstration {
public static void main( String args[] ) throws InterruptedException {
SumUpExample.runTest();
}
}
class SumUpExample {
long startRange;
long endRange;
long counter = 0;
static long MAX_NUM = Integer.MAX_VALUE;
public SumUpExample(long startRange, long endRange) {
this.startRange = startRange;
this.endRange = endRange;
}
public void add() {
for (long i = startRange; i <= endRange; i++) {
counter += i;
}
}
static public void twoThreads() throws InterruptedException {
long start = System.currentTimeMillis();
SumUpExample s1 = new SumUpExample(1, MAX_NUM / 2);
SumUpExample s2 = new SumUpExample(1 + (MAX_NUM / 2), MAX_NUM);
Thread t1 = new Thread(() -> {
s1.add();
});
Thread t2 = new Thread(() -> {
s2.add();
});
t1.start();
t2.start();
t1.join();
t2.join();
long finalCount = s1.counter + s2.counter;
long end = System.currentTimeMillis();
System.out.println("Two threads final count = " + finalCount + " took " + (end - start));
}
static public void oneThread() {
long start = System.currentTimeMillis();
SumUpExample s = new SumUpExample(1, MAX_NUM );
s.add();
long end = System.currentTimeMillis();
System.out.println("Single thread final count = " + s.counter + " took " + (end - start));
}
public static void runTest() throws InterruptedException {
oneThread();
twoThreads();
}
}
输出:
Single thread final count = 2305843008139952128 took 1003
Two threads final count = 2305843008139952128 took 540
对于纯粹的 CPU 绑定操作,你是正确的。大多数(99.9999%)程序都需要进行输入、输出和调用其他服务。这些比 CPU 慢几个数量级,因此在等待外部操作的结果时,OS 可以在时间片中调度和 运行 其他(许多其他)进程。
硬件多线程主要在满足 2 个条件时受益:
- CPU密集型作业;
- 可以有效地划分成独立的子集
或者您有很多不同的任务要 运行 可以有效地分配给多个硬件处理器。
In the following program for my multicore machine, the two thread final count count is almost half of the single thread count.
当应用程序使用两个内核时,这就是我对有效基准测试的期望。
但是,查看您的代码,我对您获得这些结果感到有些惊讶……如此可靠。
您的基准测试没有考虑 JVM 预热效果,尤其是 JIT 编译。
您的基准测试的
add
方法可能会被 JIT 编译器优化以完全摆脱循环。 (但至少这些计数被“使用”了......通过打印出来。)
我猜你很幸运……但我不相信这些结果对于所有版本的 Java 都是可重现的,或者如果你调整了基准。
请阅读:
- How do I write a correct micro-benchmark in Java?
What if we ran this in a single core machine?
假设如下:
- 您重写了基准以纠正上述缺陷。
- 您运行使用的系统禁用了硬件超线程12.
然后...我希望它需要两个线程才能比 多 两倍于一个线程版本。
问:为什么是“不止”?
A:因为启动新线程的开销很大。根据您的硬件、OS 和 Java 版本,它可能超过一毫秒。当然,如果你重复使用和丢弃线程,所花费的时间是很重要的。
And is there any way we could achieve the same result there?
不确定你在这里问什么。但是,如果您询问如何在多核机器上模拟一个内核的行为,您可能需要在 OS 级别执行此操作。 Linux 见 https://superuser.com/questions/309617 for Windows and https://askubuntu.com/questions/483824。
1 - Hyperthreading 是一种硬件优化,其中单个内核的处理硬件支持(通常)两个超线程。每个超线程
有自己的寄存器组,但它与其他超线程共享功能单元,例如 ALU。因此,这两个超线程的行为(通常)类似于两个内核,只是它们可能更慢,具体取决于精确的指令组合。典型的 OS 将 将超线程视为 ,就好像它是一个常规核心一样。超线程通常在启动时启用/禁用;例如通过 BIOS 设置。
2 - 如果启用了超线程,在像这样的 CPU 密集型计算中,两个 Java 线程的速度可能不会是一个线程的两倍......由于“其他”超线程在各自的核心上。有人提到基准测试很复杂吗?