Multitasking: Greedy vs Fair Scheduling

If you have a bunch of tasks to do, and each has a bunch of independent steps, is it better to

  1. Fair: Do the tasks in parallel, or
  2. Greedy: Finish one task at a time as fast as possible?
The correct answer is B, Greedy. I've run into this many times, and I (and everyone else) always seem to choose A, Fair, because it's more fair and symmetrical and finishes all tasks at the same time. But with Greedy, the longest task takes just as long as it would have in Fair, and every other task finishes faster than it would have in Fair. You usually think of throughput versus latency needing tradeoffs, but here there is no tradeoff. Throughput is the same either way, but latency is better with the Greedy method.

Here's an example. You have 10 tasks Q ... Z each with 10 independent steps where each step takes a second. The tables below have time going down, top to bottom, with each horizontal row showing what work is done in parallel.

The Fair strategy will do tasks Q ... Z like so:
QRSTUVWXYZ
QRSTUVWXYZ
QRSTUVWXYZ
QRSTUVWXYZ
QRSTUVWXYZ
QRSTUVWXYZ
QRSTUVWXYZ
QRSTUVWXYZ
QRSTUVWXYZ
QRSTUVWXYZ
All tasks are done in parallel. Every one finishes after 10 seconds.

The Greedy strategy does tasks Q ... Z them like so:
QQQQQQQQQQ
RRRRRRRRRR
SSSSSSSSSS
TTTTTTTTTT
UUUUUUUUUU
VVVVVVVVVV
WWWWWWWWWW
XXXXXXXXXX
YYYYYYYYYY
ZZZZZZZZZZ
The longest wait is for task Z, which finished 10 seconds after the start, same as the Fair strategy. But every other task finished faster, in 1..9 steps, on average 5.5 steps. Same throughput, but Greedy has half the latency as Fair.

But it gets better. Suppose you always have tasks coming in at different times and you have to get them done. You could work on all the tasks you have in parallel, or you could finish each task as fast as possible. Now instead of tasks Q..Z, we will have Q1..Z1, Q2..Z2, and so forth, each with 10 independent 1-second steps, which Qn coming in 10 seconds after Qn-1. And the initial tasks come in at different times.

The Fair strategy will look like this (all the *0 tasks are already running, this starts when Q1 is received and is shown until Q2 ends):
Q1R0S0T0U0V0W0X0Y0Z0
Q1R1S0T0U0V0W0X0Y0Z0
Q1R1S1T0U0V0W0X0Y0Z0
Q1R1S1T1U0V0W0X0Y0Z0
Q1R1S1T1U1V0W0X0Y0Z0
Q1R1S1T1U1V1W0X0Y0Z0
Q1R1S1T1U1V1W1X0Y0Z0
Q1R1S1T1U1V1W1X1Y0Z0
Q1R1S1T1U1V1W1X1Y1Z0
Q1R1S1T1U1V1W1X1Y1Z1
Q2R1S1T1U1V1W1X1Y1Z1
Q2R2S1T1U1V1W1X1Y1Z1
Q2R2S2T1U1V1W1X1Y1Z1
Q2R2S2T2U1V1W1X1Y1Z1
Q2R2S2T2U2V1W1X1Y1Z1
Q2R2S2T2U2V2W1X1Y1Z1
Q2R2S2T2U2V2W2X1Y1Z1
Q2R2S2T2U2V2W2X2Y1Z1
Q2R2S2T2U2V2W2X2Y2Z1
Q2R2S2T2U2V2W2X2Y2Z2
A new job starts every second, every second 10 jobs are running in parallel. Q2 finished 10 seconds after Q1.

The Greedy strategy will look like this (this starts when Q1 is received and is shown until all *2 jobs end):
Q1Q1Q1Q1Q1Q1Q1Q1Q1Q1
R1R1R1R1R1R1R1R1R1R1
S1S1S1S1S1S1S1S1S1S1
T1T1T1T1T1T1T1T1T1T1
U1U1U1U1U1U1U1U1U1U1
V1V1V1V1V1V1V1V1V1V1
W1W1W1W1W1W1W1W1W1W1
X1X1X1X1X1X1X1X1X1X1
Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1
Z1Z1Z1Z1Z1Z1Z1Z1Z1Z1
Q2Q2Q2Q2Q2Q2Q2Q2Q2Q2
R2R2R2R2R2R2R2R2R2R2
S2S2S2S2S2S2S2S2S2S2
T2T2T2T2T2T2T2T2T2T2
U2U2U2U2U2U2U2U2U2U2
V2V2V2V2V2V2V2V2V2V2
W2W2W2W2W2W2W2W2W2W2
X2X2X2X2X2X2X2X2X2X2
Y2Y2Y2Y2Y2Y2Y2Y2Y2Y2
Z2Z2Z2Z2Z2Z2Z2Z2Z2Z2
A job comes in every 10 seconds as before. Q2 finished 10 seconds after Q1 as before. But only one job runs at a time, and each job finishes in 1 second, the second after it is received. The throughput is the same, but the latency is 10x better with the Greedy strategy than the Fair strategy. (User Q might want to use that by submitting Q2 one second after Q1, but they can't be allowed to, because throughput is the same as before.)

Suppose each job needs a fixed amount of space for temporary work-in-progress. With the Greedy strategy and jobs coming in all the time, you need 1/10th the space for holding that temporary work, because each job holds its space for 1/10th as long.


Jenny: a pairwise testing tool
Index of testing techniques
Ye Olde Catalogue of Boy Scout Skits
Index of Birds
Table of Contents