How is CFS efficient?
This lesson highlights the usage of red-black trees in CFS. Additionally, it also briefs about other CFS features and how CFS deals with I/O and sleeping processes.
We'll cover the following
Using red-black trees
One major focus of CFS is efficiency. For a scheduler, there are many facets of efficiency, but one of them is as simple as this: when the scheduler has to find the next job to run, it should do so as quickly as possible. Simple data structures like lists don’t scale: modern systems sometimes are composed of 1000s of processes, and thus searching through a long-list every so many milliseconds is wasteful.
CFS does not keep all processes in this structure; rather, only running (or runnable) processes are kept therein. If a process goes to sleep (say, waiting on an I/O to complete, or for a network packet to arrive), it is removed from the tree and kept track of elsewhere.
Example
Let’s look at an example to make this more clear. Assume there are ten jobs, and that they have the following values of vruntime
: 1, 5, 9, 10, 14, 18, 17, 21, 22, and 24. If we kept these jobs in an ordered list, finding the next job to run would be simple: just remove the first element. However, when placing that job back into the list (in order), we would have to scan the list, looking for the right spot to insert it, an operation. Any search is also quite inefficient, also taking linear time on average.
Keeping the same values in a red-black tree makes most operations more efficient, as depicted in the figure below:
Processes are ordered in the tree by vruntime
, and most operations (such as insertion and deletion) are logarithmic in time, i.e., . When is in the thousands, logarithmic is noticeably more efficient than linear.
Dealing with I/O and sleeping processes
One problem with picking the lowest vruntime
to run next arises with jobs that have gone to sleep for a long period of time. Imagine two processes, A and B, one of which (A) runs continuously, and the other (B) which has gone to sleep for a long period of time (say, 10 seconds). When B wakes up, its vruntime
will be 10 seconds behind A’s, and thus (if we’re not careful), B will now monopolize the CPU for the next 10 seconds while it catches up, effectively starving A.
CFS handles this case by altering the vruntime
of a job when it wakes up. vruntime
of that job to the minimum value found in the tree (remember, the tree only contains running jobs)
Other CFS fun
CFS has many other features, too many to discuss at this point in the course. It includes numerous heuristics to improve cache performance, has strategies for handling multiple CPUs effectively (as discussed later in the course), can schedule across large groups of processes (instead of treating each process as an independent entity), and many other interesting features.
TIP: USE EFFICIENT DATA STRUCTURES WHEN APPROPRIATE
In many cases, a list will do. In many cases, it will not. Knowing which data structure to use when is a hallmark of good engineering. In the case discussed herein, simple lists found in earlier schedulers simply do not work well on modern systems, particularly in the heavily loaded servers found in data centers. Such systems contain thousands of active processes; searching through a long list to find the next job to run on each core every few milliseconds would waste precious CPU cycles. A better structure was needed, and CFS provided one by adding an excellent implementation of a red-black tree. More generally, when picking a data structure for a system you are building, carefully consider its access patterns and its frequency of usage; by understanding these, you will be able to implement the right structure for the task at hand.
Get hands-on with 1400+ tech skills courses.