单核中的多线程与异步编程

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 个条件时受益:

  1. CPU密集型作业;
  2. 可以有效地划分成独立的子集

或者您有很多不同的任务要 运行 可以有效地分配给多个硬件处理器。

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 线程的速度可能不会是一个线程的两倍......由于“其他”超线程在各自的核心上。有人提到基准测试很复杂吗?