How L1 and L2 CPU caches work, and why they’re an essential part of modern chips
extremetech.com
extremetech.com
CPUs generally hide latency by auto-prefetching the instruction stream into cache, and memory bandwidth has been keeping up just fine with CPU speed. Hence memory latency rarely hinders instruction throughput, unless your code jumps around unpredictably every few instructions (most doesn't; the only common class of program I can think of which does are emulators/interpreters).
Probably the only differences in ISA design/implementation between this universe and one where memory latency tracked CPU speed would be that (a) instructions would need not be prefetched into cache (possibly there'd be no cache), and (b) we'd still have wacky instructions like the TMS9900's X instruction, which executes the instruction at a given memory address. [1]
[1] http://en.wikipedia.org/wiki/Texas_Instruments_TMS9900#Instr...
- This depends on correct branch prediction, otherwise you could easily wind up having fetched the wrong fork
- L2 latency is, what, 12 cyles? L3 is 28? (Sandy Bridge). To completely hide this latency, we would need the prefetcher to run 28 cycles ahead. Considering caches work in cachelines and not instructions, this means we have to run potentially hundreds of instructions ahead of execution, without mispredicting one branch...
Whether your code is going to bottleneck all depends on size and code patterns. If your software fits in the L1 icache, you will only miss when context-switching. If your software is 100MB and does not exhibit good code locality, you might be stalling all over the place.
* Easier parallelisation (although compiler auto-parallelisation has turned out to not work well in practice).
* Compacting garbage collectors can mimimise fragmentation, and thus improve locality. This is not specific to functional languages, though, but a general benefit of automatic memory management.
* For pure languages, you may be able to implement generational garbage collectors slightly more efficiently, as no object of an older generation will have references to objects in a young generation. This is a very fragile property though - even laziness breaks it - and I'm not sure anyone has tried to use it in practice.
* Purity means that the compiler has to be less careful about optimisations and can make more assumptions about the code. This is part of what makes automatic (or semi-automatic) loop fusion practical in functional languages.
In general, I would not say that functional-style languages "perform well". They perform "well enough", but naive performance is not the reason to pick a functional language over an imperative one.
That's how it works in Haskell, despite the laziness: https://www.haskell.org/haskellwiki/GHC/Memory_Management
Although I doubt code locality differs much between the two (although FP might fare worse due to the code space overhead imposed by polymorphism), data locality is generally much worse in FP languages due to poorer control over data layout. (Garbage collection, run-time type tagging, excessive indirection, and inability to specify structure layout all contribute to this.)
The purported benefits of FP performance generally stem from either (a) the ability to ignore pointer aliasing effects when optimizing, which is not unique to FP (see Fortran, or C's restrict keyword), (b) the ability to automatically parallelize code due to immutability of data, which is generally far less a benefit than it sounds, or (c) the ability for the compiler or runtime to optimize away computations which are never used, which, while easier with a pure FP language, is again not unique to FP (see most modern C compilers).
I already read the classical argument of having data oriented designs and to avoid linked lists, but I've never really heard of anything concrete on performance in programming.
All I hear is "profile profile profile". I guess most of the time it will point out a place where my code is slow, but if I find one, how do I solve it ?
For example, can I create some simple bit of code that do a cache miss at will ? Any example of such code ? How do I detect a cache miss ?
To create a cache miss in one processor all you need to force the processor to look at M different memory addresses that map to the same cache location, where M is higher than the N-way associativity for your processor. The specifics of this differ for each processor, but as an example, Intel Sandy Bridge CPUs have 8-way associativity, so if you fetch 9 addresses that are 4096 bytes apart you will cause a cache miss.
Lots of small tips and links if you Google, e.g.
http://stackoverflow.com/questions/8744088/what-is-the-best-...
http://stackoverflow.com/questions/3359524/detecting-cache-m...
The full detail is in books like http://www.intel.co.uk/content/www/uk/en/architecture-and-te...
So, "profile, profile, profile", but also understand factors like this that pull in different directions in different scenarios.
Detecting cache misses can be done with valgrind: http://valgrind.org/docs/manual/cg-manual.html
There are architecture-specific instruction-level profilers as well. This video mentions a couple and is also a great overview of how non-intuitive performance can be with modern CPUs: http://channel9.msdn.com/Events/Build/2014/4-587
In this case it is useful to look at "Cache-oblivious algorithms": Cache oblivious algorithms allow you to write code that performs well regardless of the architecture and processor model they are run on. From Wikipedia: «a cache-oblivious algorithm (or cache-transcendent algorithm) is an algorithm designed to take advantage of a CPU cache without having the size of the cache (or the length of the cache lines, etc.) as an explicit parameter.» http://en.wikipedia.org/wiki/Cache-oblivious_algorithm
Sadly the field is new and there are not many such algorithms around, but you can already find algorithms for many common tasks such as traversing linked lists and calculating FFTs.
This is a good intro video on the subject: https://www.youtube.com/watch?v=16ZF9XqkfRY
http://tacc-web.austin.utexas.edu/veijkhout/public_html/istc...
The L3 cache is 'integrated', which I presume means it's different names for the same thing.
Looking back at caches that used to be 4k or 64k (external) and are now 256k and measured in megabytes (external), I suppose that could be the case.
If you're referring to kernel-level multitasking, then these processes don't really run concurrently from the CPUs perspective; in fact, they're switching interval is quite long (relative to the processor speed).
However, the cost of bringing in a thread's data into the cache once it's been scheduled by the kernel is not negligible at all, and is part of the significant total task-switching cost.
Really fascinating and reinforces how no matter how one can imagine how much "faster" computers will get in the future, there is still that speed limit of information.
This should be in bold: besides the 6 transistors per bit for L1, there is so much space to fit on a die that's close enough to the core.
Also, its a lot easier to scale outside of a chip with external, or on-package, DRAM which allows for modularity within the the same cpu segment.