I've searched for it several times and I don't think it's a duplicate.
AFAIU, context switches typically (e.g. on Linux) happen in kernel mode, and there are a few answers on SO stating that mode switch is typically much more lightweight than a context switch.
Why is it so? At least when it comes to context switches within a given process.
As I see it, the CPU state is what needs to be saved in both cases:
- switching from the user stack to the kernel stack for the mode switch
- switching the entire state for the context switch
(and switching page tables and related data for the inter-process context switch)
It doesn't look like much of a difference; am I missing something substantial? Is it the registers that are not saved in a mode switch (incl. AFAIU SSE/AVX registers on x86 etc.) that make a performance difference between a mode switch and a (intra-process) context switch?..
Or do they just mean that a context switch 'involves' 2 mode switches (to/from the kernel) and is costlier is that sense?..
A mode switch involves changing the CPU execution level between user mode and kernel mode, which is necessary when a user program requires services provided by the kernel (like system calls or hardware access).
A mode switch is lighter because it does not involve changing the execution context of the CPU. Essentially, the CPU continues executing the same process, just in a different mode.
An intra-process context switch, on the other hand, involves switching the CPU from executing one thread to another within the same process.
That is heavier than a mode switch because it requires saving and restoring more state information to make sure each thread can resume execution as if it were never interrupted. That state includes not just the CPU registers but also potentially thread-specific data like stack pointers, program counters, and other architecture-specific registers.
Regarding the performance difference, you are correct that the act of saving and restoring the CPU state (including SSE/AVX registers on x86, for example) is more substantial in a context switch. The additional overhead comes from not just saving more state but also the potential for cache misses, TLB flushes (in the case of inter-process context switches), and the scheduling logic that decides which thread to run next.
When applied to programming, for instance using Golang, you would get:
Mode Switch in Go
A mode switch in Go can be illustrated by invoking a system call, which is a request to the kernel for some service. System calls inherently require switching from user mode to kernel mode. In Go, making a file operation or network request can serve as examples of operations that involve system calls.
In this example,
ioutil.ReadFiletriggers system calls to open, read from, and close a file, each involving mode switches. However, the Go standard library abstracts away the details, and the overhead of these mode switches is minimized through efficient system call interfaces provided by the operating system.Intra-process Context Switch in Go
An intra-process context switch in Go can be illustrated by switching between goroutines, which are lightweight threads managed by the Go runtime. The Go scheduler handles the context switching between goroutines without involving the operating system's thread scheduler directly for each switch.
Here,
runtime.Gosched()is explicitly called to yield the processor, allowing the Go scheduler to switch to another goroutine. That is a cooperative scheduling mechanism and illustrates an intra-process context switch within the Go runtime. Unlike mode switches, context switches between goroutines involve saving and restoring more state (e.g., stack pointers, registers) managed by the Go runtime, but Go's efficient scheduler minimizes the overhead, allowing thousands of goroutines to be multiplexed onto a smaller number of operating system threads.You can create a Go benchmark to directly measure the overhead of an intra-process context switch over a mode switch on Linux:
That code defines two benchmarks:
BenchmarkSyscallfor measuring the overhead of a system call (representative of a mode switch) andBenchmarkGoroutineSwitchfor measuring the overhead of switching between two goroutines (an approximation of an intra-process context switch).To run these benchmarks, save the code in a file (e.g.,
benchmark_test.go), and execute the benchmarks using the Go testing tool:That would not precisely isolate the costs of mode switches and context switches at the hardware or kernel level but provides a rough comparison from the perspective of a Go application. The actual overhead and behavior might vary based on the Go runtime's scheduling, the system's state, and the hardware capabilities.
With
benchstat(for comparing the performance of the two benchmarks (BenchmarkSyscallandBenchmarkGoroutineSwitch)):The results:
Syscall-12: On average, each syscall operation takes approximately
3.765 nanosecondsper operation. The± 26%indicates the variability or standard deviation from the mean time as a percentage. That high percentage suggests that the execution time of syscall operations has a significant variation across runs.GoroutineSwitch-12: On average, switching between goroutines takes about
314.7 nanosecondsper operation. The± 10%indicates lower variability compared to the syscall benchmark, suggesting that goroutine switch times are more consistent across runs.Geomean: The geometric mean of the execution times,
34.42 nanoseconds, provides an overall measure of the performance across both benchmarks. That value is useful for comparing the performance of different sets of benchmarks as a whole.Key takeaway: Goroutine switching is more expensive than syscalls by about two orders of magnitude based on the mean times reported. That confirms that context switching, even within the same process, is more expensive due to the overhead of saving and restoring state, scheduling, and potentially cache effects.
If your application is sensitive to latency or throughput, minimizing goroutine switches or optimizing the parts of your code that rely heavily on syscalls could be beneficial.