Lecture 15: More on page replacement

Approximating LRU: second chance and clock

Instead of finding the least recently used page, we can simply find a page that was not "recently used" for some definition of "recently used". This requires only a bit per page, and makes finding a candidate to evict easy (since there are many we could choose).

To support these approximations, many TLBs support an additional "use bit" that is set automatically whenever a page is accessed.

second chance

The second chance algorithm (which some people call the clock algorithm) works just like FIFO, but it skips over any pages with the use bit set (and clears the use bit).

Example: Let's consider the same trace as above with the clock algorithm, for the first few steps:

Step 1 2 3 4 5 6 7 8 9 10 11 12
Access 1 2 3 4 1 2 5 1 2 3 4 5

Initially, memory is empty:

frame: 0 1 2
page:
use:
next: ^

In the first three steps, we incur four page faults and load pages 1, 2, and 3, advaning the next pointer. The use bit is set (since we're using the pages).

frame: 0 1 2
page: 1 2 3
use: 1 1 1
next: ^

In step 4, we incur a page fault. We look for an unused page, clearing bits as we go:

frame: 0 1 2
page: 1 2 3
use: 0 1 1
next: ^
frame: 0 1 2
page: 1 2 3
use: 0 0 1
next: ^
frame: 0 1 2
page: 1 2 3
use: 0 0 0
next: ^

Once we find one, we evict it:

frame: 0 1 2
page: 4 2 3
use: 1 0 0
next: ^

Step 5 is also a page fault; again we look for an unused page starting from the next pointer. In this case frame 1's use bit is clear, so we evict page 2.

frame: 0 1 2
page: 4 5 3
use: 1 1 0
next: ^

On step 6, we again have a page fault; we evict page 3 from frame 2.

frame: 0 1 2
page: 4 5 2
use: 1 1 1
next: ^

The rest is left as an exercise: in the end, we will have incurred 10 total page faults and end in the following state:

Step 1 2 3 4 5 6 7 8 9 10 11 12
Access 1 2 3 4 1 2 5 1 2 3 4 5
 F   F   F   F   F   F       F       F   F   F
frame: 0 1 2
page: 5 3 4
use: 1 0 0
next: ^

clock

Second chance resets the use bit when a page is considered for eviction. Depending on the locality and size of memory, we can end up in a state where almost every use bit is set (so that most accesses will cause us to loop over a large number of candidates) or almost every bit is clear (so that we degenerate to FIFO).

To solve this, we can "decouple" eviction from the clearing of the use bit. We can have two "hands" that traverse the frames:

The clearing hand should lead the eviction hand; the distance between them defines "recent". A page's use bit will be set if it has been accessed more recently then when the clearing hand passed.

The distance between the hands should be kept constant. If the distance is too long, the clock algorithm approximates second chance, so we may have to examine many entries before finding an unused one. If the distance is too short, then pages rarely get a chance to get used after the bit is cleared but before the eviction hand passes, so the clock algorithm devolves into FIFO.

The "correct" distance depends on the workload, and is a measure of how much locality the processes have. A longer distance means more history is used; the usefulness of the history depends on how much locality the programs have.

Thrashing

Thrashing occurs when processes are actively using more memory than is physically present. This causes a state of continuous paging; processes run for a short time, immediately try to page in some data, causing another process to run, which itself pages in data, and so forth.

Thrashing can be easily addressed: when the system starts thrashing, choose a process or set of processes, and either kill or suspend them (suspension may be sufficient if processes only need lots of memory for a short time; they can be finished one by one). A good choice for termination would be the process that is using the most memory.

The only difficulty is in estimating the amount of memory that is currently being used. Because processes share physical memory using a heuristic replacement algorithm instead of acquiring and releasing it, the total amount "in use" is a bit of a fuzzy concept.

A good approximation is to track the working set of each process: the set of pages that have been accessed with in the past n time units. The size of the working set gives a rough idea of how much memory the process is actively using.

By tracking working sets, we can detect thrashing by comparing the total working set size to the number of frames of available memory. We can also make use of the size of the working sets when determining which processes to suspend.

Disk

We discussed the physical properties of magnetic disks, and different algorithms for scheduling the movement of the disk head. These topics are well covered in the spring slides: