Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

does anybody have any idea what's going on here?

If I had to guess I'd say that the code is doing too much synchronization between threads that, in the end, only one thread really gets to run at a time anyways at which point moving the process from CPU to CPU is the only thing going on besides running that one thread on the server, hence it's way faster if it doesn't have to do any moving around.

IMHO this is an indication of a bug in the code and not generic advice. Or does somebody see another issue that might cause this?



Sort of agree, although my intuition tells me not that there's an excessively-coarse-locking problem, but that there's most likely a contention problem.

The essence of a contention problem, for those who don't know, is that some shared structure (maybe a counter, or the head of a queue, or something) is polled and updated for every message that needs to be processed. Since processors don't (usually) share their caches, updating a value in shared memory causes that cache line on the other processors to be invalidated, so the next time the other processor needs to poll that value it has to retrieve it from memory, which takes a lot of extra time compared to getting it off the L1.

This problem often arises with shared work queues and counters. For work queues you can solve it by giving each processor its own work queue and having it steal work from other processors when it finishes its own queue.

A textbook by Herlihy and Shavit, The Art of Multiprocessor Programming, discusses this stuff in detail.


This hypothesis makes a lot of sense. One should be able to test it on a Nehalem architecture: benchmark the program running on two cores, once choosing them to be different physical cores versus the same physical core. Your theory predicts the latter option to be much faster, and faster than the single core mode, because the two virtual cores will share the same caches.


This is pretty much correct. Another solution to this is to have an array[#CPU] of counters/structures and you only update the counter/structure for that specific CPU.


Not quite -- you need to make sure that the counters are on separate cache lines, otherwise you'll still get the same issue.


Here is what is going on:

He's using Intel Core i7. This architecture is very similar to AMD Hypertransport in that each CPU is directly attached to a bank of memory, and has an on-die memory controller.

This has obvious performance benefits because smart operating system kernels can take advantage of this and locate a processes memory on the bank connected directly to the CPU the process is running on. There is a slight disadvantage though when running highly multithreaded applications: If a thread running on CPU A wants to access memory on CPU B, it takes an extra hop to get there. This increases memory latency.

What is happening is that he has a highly multithreaded application where threads are accessing memory attached to a different CPU. When he restricts the process to a single core, all memory is directly attached to the CPU and there are no extra hops, decreasing memory latency and increasing performance dramatically.

This trick only works on processes that can effectively run on only one CPU and within the memory limit of that CPU's attached RAM.


I may have to revise my theory because it seems like he is running a single CPU system (quad core, hyperthreaded to look like 8 cores). Without a multiple physical CPU system the extra memory hop wouldn't exist.

This could also be an issue caused by hyperthreading itself. The extra 4 cores are not real physical cores on the die. They are just an extra thread that runs on each of the existing cores. What could be happening is that there is some thread locking going on where one thread is waiting for a message or event from a thread that is executing on the virtual second core, which in turn isn't executing right at the moment because it's not a real core.

An interesting test would be to have him turn off Hyperthreading in the BIOS and see if that improves performance without limiting to only 1 core. Alternatively, he could restrict the process to run on CPUs 0, 2, 4, and 6, which should run it on only the "real" cores.

I always disliked Intel's hyperthreading. Don't try to fool me into thinking I have more cores than I really do. I know it speeds up some apps, but it hurts others.


If you disliked HT in the Pentium 4, take another look at it today. The Pentium 4 had four dispatch ports and not too many execution units - a single thread was able to exploit that. The i7, in contrast, has 6 dispatch ports feeding 12 execution units. It's extremely difficult to find instruction-level parallelism within a single thread that can exploit that architecture, hence the need for HT to keep the execution units cranking. These days it's quite rare to find applications which are slowed by HT.


You seem to think a hyperthreaded system exposes to the OS 4 "real" and 4 "virtual", slower cores. This is not true, if you have 2 threads executing on a single core at the same time each gets equal attention / performance.

Also, hyperthreading really does make a lot of sense on today's hardware. The gap between CPU and memory performance is wide and getting wider each year, when you have a cache miss and your core has several hundred cycles to get its data from memory, you's better have another thread that can keep it busy.


I get your point, but by turning off HT or only running processes on 1 of 2 threads you can ensure that only a single thread is executing on a single core at a time.

Your point about cache misses is well taken. HT is good overall.


I've done a lot of work on similar topics, building real-time messaging systems in C++. The author's findings are not unique and are very common to any producer-consumer messaging system. Essentially the issue is that you want your L2 to stay hot. If a single core is enough to satisfy your processing requirements, adding more threads and more cores isn't going to speed you up. Instead the parallelism will actually put more pressure on the scheduler, causing your threads to bounce around to cores with a cold L2 cache. If your cache is cold, your pipeline stalls out, and your performance is dead.


ding ding ding.

>Essentially the issue is that you want your L2 to stay hot

Exactly the reason a 'parallel' app can slow down as you get more cores. More contention means more ACTUAL locking which mean more cache flushes, from my understanding.


A bug in the code? That makes little sense.

The thing is this is a microoptimization, so the effect of taking a slightly different code path explodes and becomes significant. We could be measuring anything here.

Maybe we're measuring the internal locking mechanics of Java. Maybe we're measuring the TCP stack internals. Maybe it's just about the CPU cache. Maybe it's about context switching in his specific linux kernel.

It could be any number of things you're measuring here. Anything!

His idea of running apache tomcat and apache bench shows that he didn't really think it through, because OF COURSE a 6 microsecond difference is not going to show up because processing HTTP headers takes miliseconds! Orders of magnitude difference here!


If it was a mutex contention issue, moving it to a single core wouldn't change anything -- there would be still be contention. I'm guessing it has something to do with CPU cache hits.


No, that's not right. There is only one code path executing so there can never be another thread contending for the same lock.

A lock begins as a spinlock (busy wait loop) for a few thousand iterations before degrading to a full-blown lock that puts the thread to sleep. Because there is no other thread vying for the lock, the spinlock succeeds immediately and the thread keeps on running.


There is only one code path executing so there can never be another thread contending for the same lock.

Are you sure there is only one code path executing? Don't all the threads get allocated time slices of the single CPU? I believe it's true that there is only one concurrent code path, but a thread can still acquire a lock and then come off the CPU.


Yes, you are right - and my previous comment somewhat skims over the details. Sorry for that. :)

The difference in a single-core scenario is that the threads can't all become starved because a thread on another CPU holds the lock - the VM will simply cycle to the next thread and continue. In interpreted mode you don't even suffer a context switch.

One edge case is where a thread acquires a lock and is then itself put to sleep because of blocking I/O or trying to obtain another lock. But that is simply a bad practice.


Inter-core mutex is MUCH slower than single-core. Because of the "memory boundary" problem. See, any lock depends upon a single memory location that is atomically test and set. When more than one cpu is involved, then the memory has to be plumbed down thru caches until its in memory common to all involved cpus - which is probably main memory when more than 2 cores are involved. On a single CPU its the L2 cache.


I think you are on to something. His example code uses AtomicLong, a class that needs to synchronize on itself if the architecture has no native method to update 64 bits integers. Too bad Paul doesn't mention if he is running a 32 bits or 64 bits VM.


I'm very sure he has the code he haven't really made to properly scale with more cores or processors. The fact that he doesn't write much about the code in the article suggests me that he doesn't really know how to make scalable processing -- for anything nontrivial it's not easy at all, and I'd personally be able to make it more optimal in C than in Java, simply because there's more that I don't directly control in Java.


The fact that he doesn't write much about the code in the article suggests me that he doesn't really know how to make scalable processing

If you read his blog, you may revist that opinion. He wrote the custom email server that powers mailinator which processes over 2 million emails an hour.

Take a look at http://mailinator.blogspot.com/2009/06/beautiful-race-condit... or http://mailinator.blogspot.com/2009/06/on-java-visibility.ht... for example.

And I bet you couldn't make it more optimal in C.


I still claim he doesn't understand scalability for multi processor platforms. I don't claim he didn't made fast processing by writing his custom code, but he writes, his code is fastest on a single core, and as far as I see, he doesn't know why. So I conclude he knows to code but only non-parallel.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: