Why Intel Added Cache Partitioning
danluu.com
danluu.com
After analyzed the data, I found branch stall cycles and data access stall cycles were causing huge number of delays in the critical code path.
I used the following tricks to get rid of the stall cycles.
1) Use branch_likely to force gcc to make sure there is no branch at all in the critical path of executions. (save 30+ cpu cycle per branch, there are a lot of branch stall cycles if one just simplely follow the gcc generated "optimized" code. MIPS CPU 200Mhz)
2) Use prefetch ahead of data structure access to get rid of un-cache data delay. (save ~50+cpu cycle per data stall, also, there are lot of them in the critical path.)
3) Use inline functions, etc to get rid of call stalls in critical path.
The system got ~100x increase on the overall system thru-put with those techniques with just pure C optimization from standard -O2 build.
I think it might be possible to create a build system that can automatically collect the profiling data (branch stall cycles and data stall cycles) and use the branch likely and prefetch instructions to auto-optimized the critical path code.
Specifying which code path / function call sequences are the real critical path probably still require programmer's touch.
As result of using data prefetch code in proper place, I don't used cache locking nor doing any kind of CPU affinity trick to generated the optimized obj code without any stall cycles for critical code path.
Would still love to have a great open-source tool for this.
I meant regarding the automatic application of rules based on profiling.
> I think it might be possible to create a build system that can automatically
> collect the profiling data (branch stall cycles and data stall cycles)
> and use the branch likely and prefetch instructions to auto-optimized the
> critical path code.
Recent versions of Clang and GCC actually support profile guided optimizations using gcov format files to trace executions. There are a number optimizations that the compiler can determine profitable knowing how something typically executes. They don't use sampled or simulated metrics though, and I'm not sure if they add prefetch or non-temporal optimizations.The ones you mentioned aren't exactly equivalent to cache partitioning (etc...) though. Partitioning allows explicit allocation and prioritization of a contended resource instead of only improving utilization for a single process. So, for example if two threads/processes have an 8MiB working set, and they run on the same cpu, they can easily step on eachother's data in the cache. If you partition it though, less frequently used data doesn't have to step on more frequently used data.
[0]: At least, this is true for x86 CPUs.
https://lwn.net/Articles/444336/
A MIPS is probably the exact opposite to modern (which actually means anything P6 and above) x86 CPUs in terms of performance characteristics. If I were to guess what member of the x86 family might actually benefit from such optimisation, it would be NetBurst (which itself has very different performance characteristics from every other x86 family that came before or after it.)
That is, what is the shelf life of a very low level CPU optimization for Intel hardware.
- http://blog.cr.yp.to/20150314-optimizing.html
- (PDF) http://cr.yp.to/talks/2015.04.16/slides-djb-20150416-a4.pdf
Discussions:
At lease in my case of networking packet forwarding app, I had the profiling data to prove that was an issue.
The app code is not that long ~2000 lines of code after clean up. But it have a lot of table looks up (DDR stall) and branches for error condition checks.
Another performance enhancement lol. That's good, too, but damnit I was hoping they mentioned timing channels. It will have to be thoroughly scrutinized before relied on for that but it's a start for sure. Hopefully, more than that. :)
> It’s curious that we have low cache hit rates, a lot of time stalled on cache/memory, and low bandwidth utilization.
That's typical for most workloads! Software is almost never compute or bandwidth bound in my experience, but instead spends most of its time waiting on memory in pointer chasing code. This is especially true for code written in managed languages like Java (since everything typically is boxed and allocated all over the heap).
Perhaps random access of small data causes frequent waits without utilizing the bandwidth in a way that block copies would.
(If I recall correctly, SPECjbb spins up an app that looks a lot like "java pet store" and runs clients against it.)
1: https://code.google.com/p/go/source/browse/src/pkg/runtime/t...
What about changing GOMAXPROCS once per minute in a goroutine that calls NumCPU()?
BTW the same problem also happens for determining available memory in containers.
Summary: because Google asked for it.
Xkcd doesn't sound like a very convincing source. And at least this estimation should have some error-bars.
Anyway, it's an offhanded comment and its precision is almost completely irrelevant to the article. I don't fault the author for not spending too much time on it.