How Computers Work — CPU, Memory, OS, Processes and Threads
In short: The stack holds method call frames — parameters, local value-type variables and references — per thread;
- allocation and cleanup are automatic and very fast (push/pop) but it is small (≈1 MB per thread).
- The heap holds objects created with
new, shared by all threads, larger, and in .NET cleaned by the garbage collector. - Stack overflow comes from too-deep recursion; heap problems are leaks and fragmentation.
In short: A process is a running program with its own isolated virtual memory and resources.
- A thread is an execution path inside a process; threads of the same process share its heap and resources but each has its own stack and registers.
- Processes are isolated and heavier; threads are lighter but need synchronisation for shared data, and an unhandled exception in one thread kills the whole process.
In short: RAM is volatile main memory accessed electronically in ~100 ns;
- SSDs take tens of microseconds and HDDs milliseconds because of slower technology (and seek mechanics for HDDs).
- A cache is a small, faster copy of frequently used data closer to where it's needed — CPU L1/L2/L3 caches over RAM, SQL Server's buffer pool over disk, Redis over a database.
- Caches work because of locality: recently or nearby used data is likely to be used again.
In short: The width of CPU registers and memory addresses.
- A 32-bit process can address at most 4 GB (often 2–3 GB usable); a 64-bit process can address terabytes.
- 64-bit also has more registers.
- In .NET, "Any CPU" assemblies run as 64-bit on a 64-bit OS; pointers/references are 8 bytes, so objects use a bit more memory.
In short: Binary is base 2 (how hardware stores data); hexadecimal is base 16, a compact way to write bytes (0xFF = 255).
- ASCII maps 128 characters to 7-bit codes.
- UTF-8 encodes all Unicode characters in 1–4 bytes and is backward-compatible with ASCII — the web standard.
- UTF-16 uses 2 or 4 bytes per character and is what .NET
stringand Windows APIs use internally.
In short: An OS + CPU mechanism that gives each process its own large, private address space, mapped page by page (usually 4 KB) to physical RAM or to disk (page file/swap).
- It isolates processes, lets programs use more memory than physically available, and loads pages lazily.
- Accessing a page not in RAM causes a page fault; heavy paging (thrashing) makes a system extremely slow.
In short: When the result depends on the timing of threads accessing shared data, e.g. two threads doing count++ (read, add, write) and one update is lost.
- Prevent it with mutual exclusion (
lock,SemaphoreSlim), atomic operations (Interlocked), thread-safe collections (ConcurrentDictionary), immutable data, or by not sharing state (message passing). - In databases, transactions and isolation levels play the same role.
In short: Two or more threads (or transactions) each hold a resource the other needs and wait forever.
- Conditions: mutual exclusion, hold-and-wait, no preemption, circular wait.
- Avoid by acquiring locks in a consistent order, holding locks briefly, using timeouts (
Monitor.TryEnter,SemaphoreSlim.WaitAsync(timeout)), and avoiding sync-over-async (.Result) in .NET. - SQL Server detects deadlocks and kills one victim transaction.
In short: The OS saving the state (registers, program counter, stack pointer) of a running thread and loading another's so it can run on that core.
- Costs: the switch itself, kernel transitions, and colder CPU caches/TLB afterwards.
- Too many threads → too much switching; hence thread pools and async I/O.
In short: Concurrency is dealing with many tasks at once (they make progress by interleaving, possibly on one core) — e.g. a web server handling many requests with async I/O.
- Parallelism is executing many tasks at the same instant on multiple cores — e.g.
Parallel.Forover CPU-heavy work. - Async improves scalability for I/O-bound work; parallelism speeds up CPU-bound work.
In short: A compiler translates the whole program to machine code ahead of time (fast start, platform-specific).
- An interpreter executes code step by step at runtime (portable, slower).
- A JIT compiles intermediate code (IL/bytecode) to machine code at runtime when methods are first used, optimising hot code — .NET, Java and modern JavaScript engines combine portability with near-native speed after warm-up.
In short: Arrays store elements contiguously, so each 64-byte cache line brings several elements and the CPU prefetcher predicts the next access;
- linked-list nodes are scattered across the heap, so each step may be a cache miss costing ~100 ns.
- Big-O counts operations, not memory latency — cache locality often dominates real performance.
In short: Memory that stays reachable even though the program no longer needs it, so the GC can't free it: static collections that only grow, event handlers never unsubscribed, caches without size limits/expiry, timers holding references, closures capturing big objects, or unmanaged resources not disposed.
- Find it with memory snapshots/dumps (dotnet-gcdump, Visual Studio, PerfView) and compare which object types grow.
In short: Probably memory pressure and paging: processes (or SQL Server with no max memory limit, or a leaking app) use more than physical RAM, so the OS keeps swapping pages to disk (thrashing).
- Check memory usage per process, page faults/sec and page file usage; set SQL Server max server memory, fix leaks, reduce caching, or add RAM.
- Also check whether the disk activity is actually database reads due to missing indexes.
In short: Each recursive call adds a stack frame;
- very deep recursion exhausts the thread's stack (≈1 MB) and causes a
StackOverflowException, which .NET can't catch — the process dies. - Fix: rewrite iteratively using an explicit
Stack<T>/Queue<T>on the heap, or limit depth. - (
Directory.EnumerateFiles(..., SearchOption.AllDirectories)already does it iteratively.)
In short: A race condition: counter++ is read-modify-write, so two threads can read the same value and both write back value+1, losing an increment.
- Fix with
Interlocked.Increment(ref counter), alock, or by giving each thread its own local counter and summing at the end (Parallel.Forwith thread-local state) — the last is also fastest.