参考:dave.cheney.net/2014/06/07/…
1.compact data structures
Go lets you create compact data structures, avoiding unnecessary indirection.
Compact data structures utilise the cache better.
Better cache utilisation leads to better performance.
2.inlining
Function call procedure
A new stack frame is created, and the details of the caller recorded.
Any registers which may be overwritten during the function call are saved to the stack.
The processor computes the address of the function and executes a branch to that new address.
inlining
The Go compiler inlines a function by treating the body of the function as if it were part of the caller.
Inlining has a cost; it increases binary size.
It only makes sense to inline when the overhead of calling a function is large relative to the work the function does, so only simple functions are candidates for inlining.
Complicated functions are usually not dominated by the overhead of calling them and are therefore not inlined.
Dead code elimination
In this example, although the function Test always returns false, Expensive cannot know that without executing it.
When Test is inlined, we get something like this
The compiler now knows that the expensive code is unreachable.
Not only does this save the cost of calling Test, it saves compiling or running any of the expensive code that is now unreachable.
3.Escape analysis
Escape analysis determines whether any references to a value escape the function in which the value is declared.
If no references escape, the value may be safely stored on the stack.
Values stored on the stack do not need to be allocated or freed.
Because escape analysis is performed at compile time, not run time, stack allocation will always be faster than heap allocation, no matter how efficient your garbage collector is.??
4.goroutines
process switching cost
First is the kernel needs to store the contents of all the CPU registers for that process, then restore the values for another process.
The kernel also needs to flush the CPU’s mappings from virtual memory to physical memory as these are only valid for the current process.
Finally there is the cost of the operating system context switch, and the overhead of the scheduler function to choose the next process to occupy the CPU.
Processor registers
There are a surprising number of registers in a modern processor. I have difficulty fitting them on one slide, which should give you a clue how much time it takes to save and restore them.
Because a process switch can occur at any point in a process’ execution, the operating system needs to store the contents of all of these registers because it does not know which are currently in use.
threads
Threads are conceptually the same as processes, but share the same memory space.
As threads share address space, they are lighter than processes so are faster to create and faster to switch between.
goroutines
Goroutines take the idea of threads a step further.
Goroutines are cooperatively scheduled, rather than relying on the kernel to manage their time sharing.
The switch between goroutines only happens at well defined points, when an explicit call is made to the Go runtime scheduler.
The compiler knows the registers which are in use and saves them automatically.
Goroutine scheduling points
- Channel send and receive operations, if those operations would block.
- The Go statement, although there is no guarantee that new goroutine will be scheduled immediately.
- Blocking syscalls like file and network operations.
- After being stopped for a garbage collection cycle.(2014的文章)
5.Segmented and copying stacks
Traditionally inside the address space of a process, the heap is at the bottom of memory, just above the program (text) and grows upwards.
The stack is located at the top of the virtual address space, and grows downwards.
Because the heap and stack overwriting each other would be catastrophic, the operating system usually arranges to place an area of unwritable memory between the stack and the heap to ensure that if they did collide, the program will abort.
This is called a guard page, and effectively limits the stack size of a process, usually in the order of several megabytes.
We’ve discussed that threads share the same address space, so for each thread, it must have its own stack.
Because it is hard to predict the stack requirements of a particular thread, a large amount of memory is reserved for each thread’s stack along with a guard page.
The hope is that this is more than will ever be needed and the guard page will never be hit.
The downside is that as the number of threads in your program increases, the amount of available address space is reduced.
Goroutine stacks
- No guard pages
- Check for available stack space is done as part of the function call
- Initial stack size very small, currently 2kb,grows as needed
conclusion
As powerful as these five features are individually, they do not exist in isolation.
For example, the way the runtime multiplexes goroutines onto threads would not be nearly as efficient without growable stacks.
Inlining reduces the cost of the stack size check by combining smaller functions into larger ones.
Escape analysis reduces the pressure on the garbage collector by automatically moving allocations from the heap to the stack.
Escape analysis is also provides better cache locality.
Without growable stacks, escape analysis might place too much pressure on the stack.