Loading content...
The working set model's elegance lies in its simplicity: track the pages used in the last Δ references, and allocate frames accordingly. But this simplicity conceals a profound challenge—the selection and tracking of Δ itself.
The window size Δ isn't just a tuning knob; it's the linchpin that determines whether the working set model succeeds or fails in practice. Too small, and processes suffer excessive page faults as needed pages are prematurely dropped. Too large, and memory is wasted on stale pages while other processes starve. Worse still, tracking exact working sets requires knowing the precise order of all memory references—information that's expensive to collect at the hardware level.
This page explores the working set window in comprehensive depth: its theoretical properties, the tradeoffs in its selection, practical tracking mechanisms, and the implementation challenges that motivated alternative approaches.
By the end of this page, you will understand: (1) How Δ affects working set size and system behavior, (2) The fundamental tradeoff spectrum in Δ selection, (3) Timer-based approximations to the reference-counting window, (4) Hardware and software techniques for tracking working sets, and (5) Why exact working set tracking is impractical and what alternatives exist.
In the formal working set model, the window Δ is measured in memory references—each load or store operation advances the virtual time counter by one. This reference-based measurement has important properties:
Virtual Time vs. Wall-Clock Time:
| Aspect | Reference-Based Δ | Wall-Clock-Based Δ |
|---|---|---|
| Unit | Memory references | Seconds or milliseconds |
| Advances when | Process accesses memory | Time passes (regardless of activity) |
| Blocked process | Virtual time stops | Time continues passing |
| High CPU burst | Many references counted | Same calendar time |
| I/O-bound process | Few references | Same calendar time |
Why Reference-Based is Theoretically Correct:
Imagine two processes:
With wall-clock Δ = 1 second:
This disparity means the same Δ represents vastly different amounts of actual program behavior. Reference-based counting ensures both processes have working sets computed over the same amount of behavioral history, not elapsed time.
While reference-based Δ is theoretically superior, it requires counting every memory reference—an operation that occurs billions of times per second on modern processors. No practical system can afford this overhead, which is why real implementations use timer-based approximations.
The Relationship Between Δ and Working Set Size:
As Δ increases, the working set size |W(t, Δ)| can only grow (or stay the same). This relationship is governed by the working set size function WSS(Δ), which for a given process at time t is:
WSS(Δ) = |W(t, Δ)|
This function has characteristic properties:
The shape of this curve varies by program but typically shows:
Selecting Δ involves navigating a fundamental tradeoff spectrum. Let's explore the extremes and the balanced middle ground.
Case 1: Δ Too Small
Symptom: Working sets are smaller than actual memory needs
Mechanism:
Consequences:
When Δ is too small, pages 'fall off the edge' of the window while still being part of the active locality. This creates oscillation: page enters working set → gets allocated → ages out → gets evicted → is needed → page fault → enters working set again. This cycle consumes enormous I/O without accomplishing useful work.
Case 2: Δ Too Large
Symptom: Working sets include many pages no longer needed
Mechanism:
Consequences:
With overly large Δ, working sets become 'haunted' by ghost pages—pages that were important in previous phases but are now irrelevant. These ghosts occupy frames and allocation budgets, preventing the system from responding to current needs.
Case 3: Δ Optimal
Characteristic: Δ spans exactly one 'locality region'—the set of pages needed for the current program phase
Properties:
The Challenge: The optimal Δ varies:
No single Δ is optimal for all situations, which is why some systems use adaptive Δ or abandon explicit windows entirely (using page fault frequency instead).
| Aspect | Δ Too Small | Δ Optimal | Δ Too Large |
|---|---|---|---|
| Working Set Size | Underestimates needs | Matches needs | Overestimates needs |
| Page Fault Rate | High (edge effect) | Low (phase transitions only) | Low but wasteful |
| Memory Efficiency | Poor (constant swapping) | Excellent | Poor (wasted frames) |
| Multiprogramming | High but thrashing | Optimal balance | Artificially limited |
| Adaptation Speed | Too fast (unstable) | Appropriate | Too slow (sluggish) |
Given that counting every memory reference is prohibitively expensive, practical systems use timer-based approximations. Instead of tracking exact reference counts, they use elapsed time as a proxy.
The Approximation:
Replace the reference-based window Δ (in memory references) with a time-based window τ (in time units, typically timer interrupts).
W(t, τ) ≈ set of pages referenced since the last τ timer ticks
How Timer-Based Tracking Works:
Multi-Interval Approach:
A single interval is too coarse. Most implementations use multiple intervals:
| Interval | Reference Bit History | Working Set Status |
|---|---|---|
| Current | 1 (just accessed) | Definitely in |
| Last | 1 (accessed last interval) | Probably in |
| 2 intervals ago | 1 | Marginally in |
| 3+ intervals ago | 0, 0, 0, ... | Outside working set |
By tracking a history of reference bits across multiple intervals, the system builds a more accurate picture of the working set.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
// Timer-based working set tracking (conceptual implementation) #define HISTORY_LENGTH 8 // Track 8 intervals of history#define TIMER_INTERVAL 10 // 10ms between timer ticks typedef struct { uint8_t reference_history; // Bit vector: bit i = reference in interval i // bit 0 = most recent, bit 7 = oldest} PageMetadata; PageMetadata page_table[MAX_PAGES]; // Called on every timer interruptvoid timer_interrupt_handler(Process* proc) { for (int page = 0; page < proc->num_pages; page++) { PageTableEntry* pte = &proc->page_table[page]; PageMetadata* meta = &page_table[page]; // Shift history right (age the bits) meta->reference_history >>= 1; // If page was referenced this interval, set MSB if (pte->reference_bit) { meta->reference_history |= 0x80; // Set bit 7 pte->reference_bit = 0; // Clear for next interval } }} // Determine if page is in working setbool is_in_working_set(int page, int window_intervals) { PageMetadata* meta = &page_table[page]; // Check if any of the last 'window_intervals' bits are set uint8_t mask = (1 << window_intervals) - 1; // e.g., 0x0F for 4 intervals mask <<= (8 - window_intervals); // Shift to check MSBs return (meta->reference_history & mask) != 0;} // Calculate working set sizeint calculate_working_set_size(Process* proc, int window_intervals) { int wss = 0; for (int page = 0; page < proc->num_pages; page++) { if (is_in_working_set(page, window_intervals)) { wss++; } } return wss;}The code above uses an 8-bit history per page, allowing tracking of 8 intervals (~80ms with 10ms timer). Each bit records whether the page was accessed during that interval. This technique provides granularity between 'accessed ever' and 'accessed right now' without storing complete reference traces.
Accuracy of Timer-Based Approximation:
The timer approach introduces two sources of error:
1. Quantization Error:
2. Rate Sensitivity:
Despite these limitations, timer-based working set tracking is accurate enough for practical use because:
Working set tracking relies heavily on hardware support. The reference bit (also called the access bit or used bit) in each page table entry is the fundamental enabler.
The Reference Bit:
| Property | Description |
|---|---|
| Location | Each page table entry (PTE) contains this bit |
| Set by | Hardware (MMU), automatically on any page access |
| Cleared by | Software (OS), typically on timer interrupt |
| Purpose | Indicates whether page was accessed since last clear |
| Cost | Zero runtime overhead — hardware sets it automatically |
x86/x64 Page Table Entry Structure:
63 52 51 12 11 9 8 7 6 5 4 3 2 1 0
+-------+------------+-----+---+-+-+-+-+-+-+-+-+
| NX | PFN | AVL | |A|D|.|U|W|P| |
+-------+------------+-----+---+-+-+-+-+-+-+-+-+
| ↑ ↑
| | └─ Dirty bit (D)
| └─── Accessed bit (A) ← Reference bit
└─────── Available for OS use
This division of labor is crucial for efficiency. Hardware can set bits at memory-access speed (every few nanoseconds) without OS involvement. Software clears bits periodically (every few milliseconds) in batches. If software had to set bits, every memory access would require an OS trap — making the system 1000x slower.
TLB Complications:
The Translation Lookaside Buffer (TLB) caches page table entries for fast address translation. This creates complications for reference bit tracking:
Problem: When a page is accessed via TLB hit, the PTE in the page table might not be updated immediately. Some architectures:
Consequence: When the OS clears reference bits, it must also:
This adds overhead but is necessary for accurate tracking.
Software-Managed TLB Architectures:
Some architectures (notably MIPS) use software-managed TLBs where the OS handles TLB misses. On these systems:
| Architecture | Reference Bit Name | TLB Behavior | Dirty Bit Handling |
|---|---|---|---|
| x86/x64 | Accessed (A) | Hardware-managed, writes through to PTE | Dirty (D) bit, hardware-set |
| ARM | Access Flag (AF) | Hardware-managed or software-managed | Dirty Bit modifier (DBM) |
| MIPS | None (software) | Software-managed TLB | OS tracks via faults |
| RISC-V | Accessed (A) | Configurable | Dirty (D) bit available |
| PowerPC | Referenced (R) | Software-managed | Changed (C) bit |
Implementing working set tracking in a production operating system involves numerous practical challenges beyond the basic algorithm.
Challenge 1: Page Table Scanning Overhead
To calculate working set sizes, the OS must scan page tables:
Mitigation Strategies:
123456789101112131415161718192021222324252627282930313233343536373839404142434445
// Incremental page table scanning to reduce overhead #define PAGES_PER_SCAN 1024 // Scan 1024 pages per timer tick typedef struct { int scan_position; // Where to resume scanning int working_set_size; // Accumulated count int scan_complete; // Full scan finished this cycle?} ProcessScanState; void incremental_working_set_scan(Process* proc) { ProcessScanState* state = &proc->scan_state; int pages_scanned = 0; int start = state->scan_position; // Scan up to PAGES_PER_SCAN pages while (pages_scanned < PAGES_PER_SCAN && state->scan_position < proc->num_pages) { int page = state->scan_position; if (proc->page_table[page].present) { // Only scan resident pages if (is_in_working_set(page, WINDOW_INTERVALS)) { state->working_set_size++; } // Age the reference history age_reference_history(page); } state->scan_position++; pages_scanned++; } // Check if we've completed a full scan if (state->scan_position >= proc->num_pages) { state->scan_complete = 1; state->scan_position = 0; // Report working set size proc->current_wss = state->working_set_size; state->working_set_size = 0; // Reset for next cycle }}Challenge 2: Context Switch Handling
When a process is context-switched out, its reference bits are frozen, but time continues:
Question: Does Process A's working set include pages referenced before the switch?
Options:
Challenge 3: Shared Pages
Pages shared between processes complicate working set tracking:
Typically, working sets are per-process with independent reference bits per mapping, but physical frame allocation considers sharing.
On multi-core systems, a process may run on different cores over time, and different cores cache TLB entries separately. Clearing reference bits requires TLB shootdowns (inter-processor interrupts to invalidate remote TLBs). This synchronization overhead can be significant on systems with many cores.
Challenge 4: Determining When to Act
Knowing working set sizes is only useful if the OS uses this information effectively:
Too aggressive reallocation causes thrashing during temporary WSS fluctuations. Too conservative reallocation wastes memory. Finding the right balance requires:
Challenge 5: Global vs. Local Decisions
The working set model tells us each process's needs, but global decisions must balance:
These policy decisions, while informed by working set data, require additional heuristics and priorities.
Several algorithms have been developed to track working sets with varying degrees of accuracy and overhead.
The WSClock Algorithm:
A particularly elegant approach combines clock-style replacement with working set semantics:
Advantages of WSClock:
Comparison of Tracking Approaches:
| Algorithm | Accuracy | Overhead | Granularity | Best For |
|---|---|---|---|---|
| Timer Sampling | Medium | Low | Timer interval | General purpose |
| Bit Aging | Good | Medium | Timer interval | When history matters |
| WSClock | Good | Low | Per-eviction | Unified approach |
| Sampling | Statistical | Very Low | Configurable | Large address spaces |
| Fault Counting | Indirect | Very Low | Per-fault | Adaptive allocation |
Modern systems increasingly favor indirect measurement (like page fault rates) over direct working set tracking. The insight: if a process isn't faulting, it has enough frames — regardless of what the 'true' working set might be. This leads to Page Fault Frequency (PFF) allocation, which we'll explore in detail in a later page.
Despite its elegance and influence, the pure working set model has significant limitations that have led to alternative approaches in practice.
The Fundamental Tension:
The working set model embodies a tension between:
Real implementations compromise on purity to achieve feasibility. But once we're approximating anyway, alternative models (like PFF) may achieve better results with simpler mechanisms.
Why PFF Often Wins:
Page Fault Frequency (PFF) addresses many working set limitations:
| Issue | Working Set Approach | PFF Approach |
|---|---|---|
| Parameter selection | Must choose Δ | Uses observed fault rate |
| Tracking overhead | Scan all pages | Only count faults |
| Correctness metric | Indirect (WSS ≈ frames needed) | Direct (fault rate = pain) |
| Implementation | Complex reference tracking | Simple counter |
| Self-adjusting | No | Yes (fault rate drives adjustment) |
PFF's insight: We don't need to know the working set; we just need to know if the process is suffering. If it's faulting too much, give it more frames. If it's faulting rarely, take some frames away. This behavioral approach sidesteps much of the working set model's complexity.
Despite its practical limitations, the working set model remains foundational. It provided the conceptual framework—locality, phase behavior, per-process allocation based on need—that informs all modern memory management. Even systems that don't implement working set tracking explicitly are designed with its principles in mind.
We've explored the working set window in comprehensive depth, from its theoretical properties to practical implementation challenges. Let's consolidate the key insights:
What's Next:
In the next page, we'll explore Page Fault Frequency (PFF)—an alternative approach to dynamic allocation that sidesteps many working set limitations by focusing directly on observable behavior (fault rates) rather than inferred state (working set membership).
You now understand the working set window in depth—its definition, the tradeoffs in selecting it, how systems track it in practice, and why this complexity motivated simpler alternatives. This understanding prepares you for the next evolution in memory management: the Page Fault Frequency approach.