views:

269

answers:

5

I'm experimenting with some multithreading constructions, but somehow it seems that multithreading is not faster than a single thread. I narrowed it down to a very simple test with a nested loop (1000x1000) in which the system only counts.
Below I posted the code for both single threading and multithreading and how they are executed.
The result is that the single thread completes the loop in about 110 ms, while the two threads also take about 112 ms.
I don't think the problem is the overhead of multithreading. If I only submit one of both Runnables to the ThreadPoolExecutor, it executes in half the time of the single thread, which makes sense. But adding that second Runnable makes it 10 times slower. Both 3.00Ghz cores are running 100%.
I think it may be pc-specific, as someone else's pc showed double-speed results on the multithreading. But then, what can I do about it? I have a Intel Pentium 4 3.00GHz (2 CPUs) and Java jre6.

Test code:

// Single thread:
long start = System.nanoTime(); // Start timer
final int[] i = new int[1];     // This is to keep the test fair (see below)
int i = 0;
for(int x=0; x<10000; x++)
{
    for(int y=0; y<10000; y++)
    {
        i++; // Just counting...
    }
}
int i0[0] = i;
long end = System.nanoTime();   // Stop timer

This code is executed in about 110 ms.

// Two threads:

start = System.nanoTime(); // Start timer

// Two of the same kind of variables to count with as in the single thread.
final int[] i1 = new int [1];
final int[] i2 = new int [1];

// First partial task (0-5000)
Thread t1 = new Thread() {
    @Override
    public void run() 
    {
        int i = 0;
        for(int x=0; x<5000; x++)
            for(int y=0; y<10000; y++)
                i++;
        i1[0] = i;
    }
};

// Second partial task (5000-10000)  
Thread t2 = new Thread() {
    @Override
    public void run() 
    {
        int i = 0;
        for(int x=5000; x<10000; x++)
            for(int y=0; y<10000; y++)
                i++;
        int i2[0] = i;
    }
};

// Start threads
t1.start();
t2.start();

// Wait for completion
try{
    t1.join();
    t2.join();
}catch(Exception e){
    e.printStackTrace();
}

end = System.nanoTime(); // Stop timer

This code is executed in about 112 ms.

Edit: I changed the Runnables to Threads and got rid of the ExecutorService (for simplicity of the problem).

Edit: tried some suggestions

+1  A: 

You don't do anything with i, so your loop is probably just optimised away.

Adrian Mouat
Actually, I printed the value of i at the bottom (but it's not shown in the code).
RemiX
The times are consistent with it being optimised, but not optimised away. I'd like to see the test repeated (without restarting the process). one issue threads can have in this context is that HotSpot runs in a different thread, and the additional thread may end up running the unoptimised code for some time.
Tom Hawtin - tackline
Another Thread doing exactly the same as t2 (only then 10000x10000) is completed in 107 ms (faster than t1 and t2 together), or isn't that what you meant?
RemiX
+2  A: 

I'm not at all surprised at the difference. You are using Java's concurrency framework to create your threads (although I don't see any guarantee that two threads are even created since the first job might complete before the second even starts.

There's probably all sorts of locking and synchronisation going on behind the scenes which you don't actually need for your simple test. In short I do think the problem is the overhead of multithreading.

JeremyP
I also tested it with just two Threads and using thread1.start(), showing the same result. Also, one Runnable in the ExecutorService works very quickly and finally, another machine works fine with this code.
RemiX
+4  A: 

Try increasing the size of the array somewhat. No, really.

Small objects allocated sequentially in the same thread will tend to be initially allocated sequentially. That's probably in the same cache line. If you have two cores access the same cache line (and then micro-benhcmark is essentially just doing a sequence of writes to the same address) then they will have to fight for access.

There's a class in java.util.concurrent that has a bunch of unused long fields. Their purpose is to separate objects that may be frequently used by different threads into different cache lines.

Tom Hawtin - tackline
I'm using a different array for each Thread, so I don't think they have to fight for access... or did I misunderstand?
RemiX
@RemiX: they're both allocated on the heap, i2 is allocated right after i1. There's a pretty high probability of them ending up in the same cacheline.
snemarch
+1 - 2200 ms to 280 ms by just increasing the size of the arrays to 10. Unfortunately, using your other suggestions the effect isn't that great anymore. Good to remember, though.
RemiX
+9  A: 

You definitely don't want to keep polling Thread.isAlive() - this burns a lot of CPU cycles for no good reason. Use Thread.join() instead.

Also, it's probably not a good idea having the threads increment the result arrays directly, cache lines and all. Update local variables, and do a single store when the computations are done.

EDIT:

Totally overlooked that you're using a Pentium 4. As far as I know, there's no multi-core versions of the P4 - to give the illusion of multicore, it has Hyper-Threading: two logical cores share the execution units of one physical core. If your threads depend on the same execution units, your performance will be the same as (or worse than!) single-threaded performance. You'd need, for instance, floating-point calculations in one thread and integer calcs in another to gain performance improvements.

The P4 HT implementation has been criticized a lot, newer implementations (recent core2) should be better.

snemarch
+1 - The first paragraph is probably where most of the difference is.
Stephen C
+1 - Actually, both suggestions speed up the process significantly, thanks. But there's something strange: using Thread.isAlive() in combination with incrementing arrays directly, is faster (800 ms) than using Thread.join() (2200 ms), but using isAlive() in combination with your second suggestion, is slower (190 ms) than join() (114 ms). Anyway, using both your suggestions speeds the system up from 2200 ms to 114 :D. However, your second suggestion also speeds up the single thread to about 110 ms so now there's just no difference yet.
RemiX
A difference of less than 10ms doesn't really tell you anything when running on a multitasking OS - you'll need to increase the iterations to measure speed difference more reliably :)
snemarch
I know, that's why I said there's no difference. But I'll look into the Pentium 4 issue. So what you're saying is that, even though dxdiag says there are 2 cores, it actually has one physical core and it cannot really speed up by multithreading? Sounds like a good explanation why there was in fact a speed increase on that other machine.
RemiX
Yes, unless there's a P4 version I don't know about, all you get is HyperThreading. The normal APIs don't report logical cores any differently from physical ones. If you want to detect HT, you'll need to go pretty specific - CPUID x86 instruction or the win32 NUMA APIs.
snemarch
+1  A: 

Have you checked the number of available cores on your PC with Runtime.getRuntime().availableProcessors() ?

Damien
Just did, and it says 2 processors. Also, I can see them working in the Task Manager.
RemiX