Task Division in Parallel Processing
Equal-sized subproblems do not guarantee equal completion times, because the work inside each piece and the speed or availability of each processor can differ.
A Fair-Looking Split of the Work
A common first instinct when speeding up a job with several processors is to cut it into pieces of the same size and hand one piece to each processor. The reasoning seems sound. If every piece is the same amount of work, every processor should finish at the same moment, and the total time should drop roughly in proportion to the number of processors. Many beginners never question this belief. This article examines why it fails and what a better strategy looks like.
What do you think happens?
Four processors each receive a piece of exactly the same size. One piece contains much harder computation than the others. What happens to the total finish time?
Reveal answer
The processor with the harder piece finishes last and determines when the whole job ends.
Equal size describes the input to each subproblem, not how long each subproblem takes to run. The longest subproblem sets the finish time, so the other processors finish early and then wait.
Why Equal Size Is Not Equal Time
The source identifies two separate reasons why equal-sized pieces can finish at different times. Keeping them apart makes the problem easier to reason about. Equal size is a property of the input to each subproblem. It says nothing about how long each subproblem will run once a processor starts on it.
Where the Finish Time Really Comes From
The speedup from adding processors depends on how evenly the work finishes, not only on how many processors exist. The longest subproblem sets the finish time. Shortening the other pieces does not help once the longest one is still running, because the job is not complete until every piece is complete.
The timeline above uses illustrative numbers. Processor 4 holds the piece with the most work, or runs on the slowest or most heavily loaded resource. Its finish time of 9 units is the job's finish time, even though the other three processors completed their pieces much earlier. The idle time of those three processors is the cost of an unbalanced split.
Load Balancing by Handing Out Subtasks
The standard response to uneven work is not to find a perfect split, which is usually impossible to predict. Instead, the problem is divided into many more subproblems than there are processors. Each processor handles several subtasks. When a processor finishes one, it is assigned another, and this continues until every subtask has been given out.
The effect is that processors which finish quickly take on more work, and slow or busy processors take on less. Each processor effectively works at its own pace. The diagram below shows the flow of work rather than a fixed assignment. A processor that is slow on one subtask simply returns to the pool later, so it does not hold up the subtasks that remain.
Tracing a Pool of Subtasks Across Three Processors
A job is divided into 8 subtasks with the following work amounts, in time units: A=1, B=4, C=2, D=1, E=3, F=2, G=1, H=2. Three processors take subtasks from a shared pool in order whenever they become free. Trace the assignments and find when the last processor finishes.
- 1Start
All three processors are free at time 0. They take the first three subtasks in the pool: P1 takes A (1 unit), P2 takes B (4 units), P3 takes C (2 units).
- 2Time 1
P1 finishes A and becomes free. It takes the next subtask, D (1 unit), which will finish at time 2.
- 3Time 2
P3 finishes C and P1 finishes D, both at time 2. P3 takes E (3 units), finishing at time 5. P1 takes F (2 units), finishing at time 4.
- 4Time 4
P1 finishes F and becomes free. It takes G (1 unit), finishing at time 5.
- 5Time 5
P1 finishes G and P3 finishes E, both at time 5. P1 takes H (2 units), finishing at time 7. P2 is still working on B, which finishes at time 4 and was already complete before this point, so P2 has been free since time 4 with no subtask left to take.
- 6End
P1 finishes H at time 7. No subtasks remain, so the last finish time is 7. The total work is 16 units, so the ideal time with three processors would be about 5.3 units. The gap comes from the lumpy sizes of the final pieces.
The job finishes at time 7. No processor was handed a fixed share in advance; each took the next subtask as it became free.
Independent Subtasks Versus Dependent Ones
Load balancing is easiest when subtasks are independent. Independent subtasks can run in any order and at the same time, so a free processor can take any waiting subtask without checking anything. The source's image program is a very easy case because each of its subproblems is completely independent. Any number of them can run at once, and no subtask must wait for another.
Choosing How Big Each Subtask Should Be
Making subproblems small is what makes the many-subtasks approach work, but there is a limit. The source's guidance is that subproblems should be small but not too small. Subproblems should be small enough that processors stay busy, but not so small that the overhead of creating and assigning them becomes significant.
Subtask size involves a trade-off between two costs. If subtasks are too large, there are too few of them for the pool to smooth out differences, so some processors finish early and sit idle while a large subtask runs elsewhere. If subtasks are too small, the creation and assignment of each one adds a computational overhead, and when pieces are very small that overhead can add significantly to the total work.
Row-by-Row Splitting in the Image Example
The source's image program shows how the trade-off is resolved in practice. A pixel-by-pixel split would produce subtasks that are probably too small, because each one would carry very little work relative to the cost of creating and assigning it. Instead, each subtask computes one row of pixels. Since the image has several hundred rows, there are many subtasks, and each one is still substantial.
Choosing a Granularity for an Image Split
An image has 400 rows and 400 pixels per row. Compare three ways of dividing the work among 8 processors: one subtask per pixel, one subtask per row, and one subtask for each quarter of the image.
- 1Pixel granularity
There are 160,000 subtasks. Each one is tiny, so the cost of creating and assigning 160,000 pieces is likely to add significantly to the total work. This is the too-small side of the trade-off.
- 2Row granularity
There are 400 subtasks, each computing one row of 400 pixels. Each processor handles about 50 rows, the pool has plenty of pieces to smooth out differences, and each piece is still substantial. This matches the source's description of fairly good load balancing with a reasonable amount of overhead.
- 3Quarter granularity
There are only 4 subtasks for 8 processors. Four processors would stay idle for the whole job, and the remaining four would each run a large piece. This is the too-large side of the trade-off.
One row per subtask gives enough pieces for balancing while keeping each piece large enough that overhead stays modest.
Mistakes Beginners Make
Common mistake Assuming equal-sized pieces will finish at the same time
What it looks likeFour equal-sized pieces are given to four processors, and one piece contains much harder computation.
Why it failsEqual size describes the input, not the run time. The harder piece keeps its processor busy long after the others stop.
Do this insteadExpect variation in work content and processor speed, and design the split so that a slow piece cannot dominate the finish time.
Common mistake Dividing the job into exactly one subtask per processor
What it looks likeEight processors each receive exactly one of eight pieces.
Why it failsNo processor that finishes early has any other work to take, so it sits idle while the longest piece runs.
Do this insteadDivide the problem into many more subproblems than there are processors, and let processors take new subtasks as they become free.
Common mistake Making every subtask as small as possible
Why it failsThe overhead of creating and assigning very small subtasks can add significantly to the total work.
Common mistake Treating dependent subtasks like independent ones
Why it failsWhen subtasks depend on each other's results, their order matters, so a free processor cannot simply take any waiting subtask.
Practice: Predict the Finish Time
Two processors are available. The job is split into five subtasks with work amounts 6, 2, 2, 2, and 2 time units, handed out from a shared pool whenever a processor becomes free. Which processor takes which subtask, and what is the finish time of the whole job? Then consider whether splitting the 6-unit subtask into smaller pieces would help, and what it would cost.
Hints
- Start by giving the first two subtasks to the two free processors, then find which one becomes free first.
- The job ends when the last subtask finishes on any processor, not when the average processor finishes.
- Splitting the large subtask shortens the longest piece but adds creation and assignment overhead for each new piece.
Key Takeaways
- Equal-sized subproblems do not guarantee equal completion times, because the work inside each piece and the speed or availability of each processor can differ.
- The longest subproblem sets the finish time, so the speedup from adding processors depends on how evenly the work finishes.
- Load balancing divides a problem into many more subproblems than there are processors and assigns each new subtask to whichever processor becomes free next.
- Subtasks should be small enough to keep processors busy but not so small that the overhead of creating and assigning them becomes significant.
- Independent subtasks can run in any order at the same time, which makes load balancing much simpler than when subtasks depend on each other's results.