1

If in real time the CPU performs only one task at a time then how is multithreading different from asynchronous programming (in terms of efficiency) in a single processor system?

Lets say for example we have to count from 1 to IntegerMax. In the following program for my multicore machine, the two thread final count count is almost half of the single thread count. What if we ran this in a single core machine? And is there any way we could achieve the same result there?

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();

    }
}

Output:

Single thread final count = 2305843008139952128 took 1003
Two threads final count = 2305843008139952128 took 540
Neuron
  • 5,141
  • 5
  • 38
  • 59
Shikhar
  • 644
  • 1
  • 10
  • 20
  • 3
    IDK about the efficiency, but the original reason for writing threaded code instead of doing async stuff was for readability. Each thread could be just like the simple, single-threaded, procedural programs that we all learned to write when we were beginners. When you've got multiple async activities going on, the program must explicitly store the state of each one, and must explicitly switch from activity to activity. With threads, the state of each activity is _implicit_ in the local variables of its thread, and the scheduling is all taken care of for you by "the system." – Solomon Slow Oct 29 '21 at 02:13

2 Answers2

2

For a purely CPU-bound operation you are correct. Most (99.9999%) of programs need to do input, output, and invoke other services. Those are orders of magnitude slower than the CPU, so while waiting for the results of an external operation, the OS can schedule and run other (many other) processes in time slices.

Hardware multithreading benefits primarily when 2 conditions are met:

  1. CPU-intensive operations;
  2. That can be efficiently divided into independent subsets

Or you have lots of different tasks to run that can be efficiently divided among multiple hardware processors.

Jim Garrison
  • 85,615
  • 20
  • 155
  • 190
  • So can we conclude that unless multiprocessor systems were invented, there was no concept of using multiple threads right? Because asynchronous programming could have achieved the same result back then. – Shikhar Oct 29 '21 at 00:47
  • 2
    On the contrary, multiprogramming (multi-threading) was very much used, starting in the early 1960s, exactly because I/O is so slow. – Jim Garrison Oct 29 '21 at 00:49
  • So you mean to say it was used in way javascript (that doesn't support multithreading) uses asynchronous programming? – Shikhar Oct 29 '21 at 01:02
  • 2
    Partly. This isn't an education site and we can't explain all the details of how operating systems manage resources. There are lots of sites on the web that cover all this in extreme detail, you should start by doing some research. – Jim Garrison Oct 29 '21 at 01:05
  • Thanks I just wanted to clear my concepts after I ran through multiple articles explaining multithreading vs async vs concurrency etc. It got me a bit confused. Thats why i used stackoverflow as a medium. But think now Iam on the right track :) – Shikhar Oct 29 '21 at 01:10
2

In the following program for my multicore machine, the two thread final count count is almost half of the single thread count.

That is what I would expect from a valid benchmark when the application is using two cores.

However, looking at your code, I am somewhat surprised that you are getting those results ... so reliably.

  • Your benchmark doesn't take account of JVM warmup effects, particularly JIT compilation.

  • You benchmark's add method could potentially be optimized by the JIT compiler to get rid of the loop entirely. (But at least the counts are "used" ... by printing them out.)

I guess you got lucky ... but I'm not convinced those results will be reproducible for all versions of Java, or if you tweaked the benchmark.

Please read this:


What if we ran this in a single core machine?

Assuming the following:

  • You rewrote the benchmark to corrected the flaws above.
  • You are running on a system where hardware hyper-threading1 is disabled2.

Then ... I would expect it to take two threads to take more than twice as long as the one thread version.

Q: Why "more than"?

A: Because there is a significant overhead in starting a new thread. Depending on your hardware, OS and Java version, it could be more than a millisecond. Certainly, the time taken is significant if you repeatedly use and discard threads.


And is there any way we could achieve the same result there?

Not sure what you are asking here. But are if you are asking how to simulate the behavior of one core on a multi-core machine, you would probably need to do this at the OS level. See https://superuser.com/questions/309617 for Windows and https://askubuntu.com/questions/483824 for Linux.


1 - Hyperthreading is a hardware optimization where a single core's processing hardware supports (typically) two hyper-threads. Each hyperthread has its own sets of registers, but it shares functional units such as the ALU with the other hyperthread. So the two hyperthreads behave like (typically) two cores, except that they may be slower, depending on the precise instruction mix. A typical OS will treat a hyperthread as if it is a regular core. Hyperthreading is typically enabled / disabled at boot time; e.g. via a BIOS setting.
2 - If hyperthreading is enabled, it is possible that two Java threads won't be twice as fast as one in a CPU-intensive computation like this ... due to possible slowdown caused by the "other" hyperthread on respective cores. Did someone mention that benchmarking is complicated?

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216