Lesson 18 / 18
Limits of Parallel Processing
The cost of distribution and when it is unnecessary are counted through the serial fraction and the speedup ceiling: of the 6,753 touches of the fictional report chain 310 are indivisible, the serial fraction is 4.59% and the ceiling is 21.78 times. At a serial fraction of 2% the ceiling is 50, at 30% it is 3.33 times, and even 4,096 units cannot pass the ceiling. The gain per added unit falls from 0.91 to 0.00011; after 256 units each new unit gains less than two thousandths. How the ceiling behaves with job size is an assumption decision: if the indivisible items are held fixed the ceiling rises to 206,388 times, if the coordination overhead item and the row group header grow with job size it saturates at 43.64 times. The course wrap-up gathers the source and read-back numbers of the eighteen lessons, and the curriculum wrap-up the six courses and 90 lessons of the Data Analytics curriculum in a single table.
Published Updated
In this lesson
- Computes the serial fraction of a report chain by the number of touches and writes the speedup ceiling
- Measures how speedup approaches the ceiling as the number of units is increased and how the gain of the added unit melts away
- Compares how far the assumption that the indivisible items grow or do not grow with job size moves the ceiling
- Gathers the eighteen lessons of the course and the six courses of the Data Analytics curriculum in a single table with their measure axis and unit
Contents
Four lessons counted the items of distribution one by one: the records moved in the shuffle, the coordination overhead item added per job, the values a file layout makes you read. All three could be reduced and each held two defensible decisions. This lesson measures the serial fraction, which cannot be reduced.
Not every step of a job is divisible. The schema of the source is read once, the job plan is derived once, the result table is built and written once; these steps run once however many units are added, and do not shorten. The measure of the lesson is the serial fraction — the ratio of the indivisible part of the job to the whole job — and the second number derived from it is the speedup ceiling: the highest speedup, in times, reachable even as the number of units goes to infinity.
- AS25. The report chain is built from the items the previous four lessons counted and is fiction: the row count is 3,199, the header value is 150 (from the best row group size of the fourth lesson), the coordination overhead item is 100 (from the four-stage, eight-partition chain of the third lesson), and the records moved in the shuffle are 40 (from the second lesson). It is a model.
- AS26. The unit of work is a touch; raw milliseconds are not written and the number measured is the speedup in times.
- AS27. Speedup is computed from the serial fraction and the number of units as ; as goes to infinity the ceiling becomes .
- AS28. The divisible steps are assumed spread equally among the units. Partition imbalance was measured in the Scale and Performance topic of the Data Analysis with Python course and is ignored here; this assumption gives the ceiling optimistically.
- AS29. Whether the indivisible items grow with job size is a decision, tested with two assumptions: a fixed series, and a series that grows by the formulas of the third and fourth lessons.
- AS30. The number of units needed to reach 90% of the ceiling is found by scanning multiples of two.
The Serial Fraction of the Job
# ceiling.py — the serial fraction of the job and the speedup ceiling. MODEL: the report # chain is split into steps, each step is given a number of touches and whether it is divisible; # the measure is the speedup in times, not raw time. N = 3199 STEPS = (("reading the source schema", 150, "no"), ("deriving the job plan and distributing tasks", 100, "no"), ("reading and filtering the rows", N, "yes"), ("local reduction", N, "yes"), ("records moved in the shuffle", 40, "yes"), ("final reduction (5 keys)", 5, "yes"), ("building and writing the single report table", 60, "no")) print(f"{'step':<46}{'touches':>9}{'divisible':>10}") for name, d, b in STEPS: print(f"{name:<46}{d:>9}{b:>10}") TOTAL = sum(d for _, d, _ in STEPS) SERIAL = sum(d for _, d, b in STEPS if b == "no") S = SERIAL / TOTAL print(f"total touches {TOTAL}, indivisible {SERIAL}, serial fraction " f"{100 * S:.2f} percent, speedup ceiling {1 / S:.2f} times")
step touches divisible reading the source schema 150 no deriving the job plan and distributing tasks 100 no reading and filtering the rows 3199 yes local reduction 3199 yes records moved in the shuffle 40 yes final reduction (5 keys) 5 yes building and writing the single report table 60 no total touches 6753, indivisible 310, serial fraction 4.59 percent, speedup ceiling 21.78 times
The serial fraction is not a property of code but a list of steps. The three steps total 310 touches, 4.59% of the job of 6,753 touches. Each has its own reason for not being divided: the schema sits in a single place of the source, the job plan has to see all the partitions, the result table has to be a single output.
4.59% looks like a small number. Its counterpart is 21.78 times: however many units you divide this job into, the speedup cannot pass it. A serial fraction of 4.59% cuts off one twenty-second of the speedup from the start.
Speedup Ceiling
# speedup.py — the same job is run with different serial fractions and an increasing number of # units. speedup(p) = 1 / (s + (1 - s) / p); the ceiling is 1 / s as p goes to infinity. UNITS = (1, 2, 4, 8, 16, 64, 256, 1024, 4096) def speedup(s, p): return 1.0 / (s + (1.0 - s) / p) def units_for_90(s): p = 1 while speedup(s, p) < 0.9 / s and p < 10 ** 7: p *= 2 return p SHARES = (0.02, S, 0.10, 0.30) print("units".ljust(8) + "".join(f"s={s:.4f}".rjust(10) for s in SHARES)) for p in UNITS: print(f"{p:<8}" + "".join(f"{speedup(s, p):>10.2f}" for s in SHARES)) print(f"{'ceiling':<8}" + "".join(f"{1 / s:>10.2f}" for s in SHARES)) print(f"{'p90':<8}" + "".join(f"{units_for_90(s):>10}" for s in SHARES))
units s=0.0200 s=0.0459 s=0.1000 s=0.3000 1 1.00 1.00 1.00 1.00 2 1.96 1.91 1.82 1.54 4 3.77 3.52 3.08 2.11 8 7.02 6.05 4.71 2.58 16 12.31 9.48 6.40 2.91 64 28.32 16.44 8.77 3.22 256 41.97 20.15 9.66 3.30 1024 47.72 21.35 9.91 3.33 4096 49.41 21.67 9.98 3.33 ceiling 50.00 21.78 10.00 3.33 p90 512 256 128 32
The right column is the most instructive. When the serial fraction is 30%, 4,096 units give 3.33 times: four thousand ninety-six units, three and a half times. With two units the same job speeds up 1.54 times; the last 4,094 units add only 1.79 times. A serial fraction of 30% means 70% of the job is divisible, which sounds like a job that can be distributed.
The left column shows the same thing from the other side. When the serial fraction is 2% the ceiling is 50 times and 4,096 units give 49.41 times. The difference between 2% and 30% is not fifteen times, it is a fifteen-times difference in the ceiling: 50 against 3.33. The serial fraction rises linearly; the ceiling falls in inverse proportion.
The last row gives the practical side. The number of units needed to reach 90% of the ceiling grows as the serial fraction gets smaller: at 30% 32 units are enough, at 2% 512 are needed. A small serial fraction gives a high ceiling, but it asks for more units to reach it.
The Added Unit and the Ceiling Itself
# limit.py — as the ceiling is approached the times gained by an added unit melt away; and the # serial fraction changes with job size because the divisible items grow and the # indivisible items do not. print(f"serial fraction {100 * S:.2f} percent; ceiling {1 / S:.2f} times") print(f"{'units':<8}{'speedup':>10}{'rise over previous':>23}" f"{'per added unit':>22}{'distance to ceiling':>21}") prev_p, prev_h = 1, 1.0 for p in UNITS[1:]: h = speedup(S, p) print(f"{p:<8}{h:>10.2f}{h - prev_h:>23.3f}" f"{(h - prev_h) / (p - prev_p):>22.5f}{1 / S - h:>21.2f}") prev_p, prev_h = p, h import math FIXED = sum(d for _, d, b in STEPS if b == "no") def growing(n): """The coordination overhead item of lesson 03 and the header value of lesson 04 grow with job size.""" partition = max(8, math.ceil(n / 400000)) return 4 * partition * 3 + 4 + 3 * math.ceil(n / 64) + 60 print(f"{'job size':<12}{'divisible':>13}{'fixed serial':>13}{'fixed ceiling':>15}" f"{'growing serial':>16}{'growing ceiling':>17}{'p90':>9}") for n in (3199, 31990, 319900, 3199000, 31990000): div = 2 * n + 45 s1, s2 = FIXED / (div + FIXED), growing(n) / (div + growing(n)) print(f"{n:<12}{div:>13}{FIXED:>13}{1 / s1:>15.1f}{growing(n):>16}" f"{1 / s2:>17.2f}{units_for_90(s2):>9}") LARGEST = 31990000 saturation = (2 * LARGEST + 45 + growing(LARGEST)) / growing(LARGEST) print(f"the fixed-serial assumption makes the ceiling grow in direct proportion to job size; under the " f"growing-serial assumption the ceiling saturates at {saturation:.2f} times and 90 percent of it " f"is reached at 512 units")
serial fraction 4.59 percent; ceiling 21.78 times units speedup rise over previous per added unit distance to ceiling 2 1.91 0.912 0.91222 19.87 4 3.52 1.604 0.80180 18.27 8 6.05 2.539 0.63466 15.73 16 9.48 3.421 0.42762 12.31 64 16.44 6.968 0.14517 5.34 256 20.15 3.704 0.01929 1.64 1024 21.35 1.202 0.00157 0.43 4096 21.67 0.323 0.00011 0.11 job size divisible fixed serial fixed ceiling growing serial growing ceiling p90 3199 6443 310 21.8 310 21.78 256 31990 64025 310 207.5 1660 39.57 512 319900 639845 310 2065.0 15157 43.21 512 3199000 6398045 310 20639.9 150115 43.62 512 31990000 63980045 310 206388.2 1500556 43.64 512 the fixed-serial assumption makes the ceiling grow in direct proportion to job size; under the growing-serial assumption the ceiling saturates at 43.64 times and 90 percent of it is reached at 512 units
The first table gives what the added unit gains. The second unit adds 0.91 times; each unit in the 4,096th unit group adds 0.00011 times. After 256 units each added unit gains less than two thousandths and the distance to the ceiling has fallen to 1.64 times. What follows is not a measurable gain, only an asymptotic approach to the ceiling.
The second table is the hidden decision of the lesson. “The bigger the dataset, the more distribution gains” is true, but how much it gains depends on an assumption. If the indivisible items are held fixed, the ceiling grows in direct proportion to job size and rises to 206,388 times at 31,990,000 rows. Yet the third lesson measured that the coordination overhead item grows with the number of partitions and the fourth that the row group header grows with the number of row groups. When these two formulas are taken into account the ceiling saturates at 43.64 times and enlarging the job size no longer moves it.
The ratio between the two numbers is 4,729 times, and both come from the same model, dataset and chain. The only difference is whether the indivisible items are assumed to grow with job size, an assumption no speedup report writes spontaneously.
When distribution is unnecessary can be read from here too. In the 3,199 rows of the fictional dataset the ceiling appears as 21.78 times, but the third lesson counted that at this job size only 61.5 rows fall to one coordination overhead item and that the target of 10,000 rows per item is met at 520,000 rows; the first lesson wrote that with the chosen decision the dataset would stop fitting on a single machine at 22,525,682 rows. The three numbers point in the same direction: this dataset is not distributed. A high ceiling does not call for distribution; what calls for it is the job itself exceeding a single machine.
Summary
- The serial fraction is not a property of code but a list of steps: reading the schema, the job plan, and writing the single report table total 310 touches, 4.59% of the job of 6,753 touches.
- A serial fraction of 4.59% pins the speedup ceiling to 21.78 times; at 2% the ceiling is 50, at 30% it is 3.33 times, and even 4,096 units cannot pass the ceiling.
- Reaching the ceiling is not free either: 90% of it takes 32 units at a 30% serial fraction and 512 units at a 2% serial fraction.
- The gain of the added unit melts away: the second unit adds 0.91 times, after 256 units each unit adds less than two thousandths, and the distance to the ceiling falls to 1.64 times.
- The behavior of the ceiling with job size is an assumption decision: if the indivisible items are held fixed the ceiling rises to 206,388 times, if they grow with the formulas of the third and fourth lessons it saturates at 43.64 times; there are 4,729 times between the two numbers.
- A high ceiling does not call for distribution: in the 3,199-row dataset, although the ceiling is 21.78 times, the item threshold is 520,000 and the volume threshold is 22,525,682 rows.
Course Wrap-Up
In each of the eighteen lessons two numbers stood side by side, what was in the data and what reached the reader, each read from its own lesson.
| Lesson | Number in the source | Number read back | Difference |
|---|---|---|---|
| Chart 01 Chart Type Selection | east - north 4.463 m³ | 2.500 m³ from the area channel | -1.963 m³ |
| Chart 02 Distribution Visualization | 2 peaks, outlier 45.47 m³ | 0 peaks in the box summary | 2 peaks lost |
| Chart 03 Categorical Visualization | 39 pairs that must be told apart | 35 pairs in the stacked chart | 4 pairs, error 47.2% |
| Chart 04 Relationship Visualization | 1,101 points | 309 filled cells | 792 points, 71.9% |
| Chart 05 Time Series Visualization | trend -0.140 m³/period | -0.1347 m³/period | 0.0053, cost 12 periods |
| Chart 06 Geographic Visualization | average 19.713 m³ | 18.484 m³ weighted by area | -1.228 m³ |
| Chart 07 Misleading Visuals | north 20.210 m³ | 8.019 m³ on the truncated axis | -12.191 m³ |
| Chart 08 Accessible Charts | 8 series | 4 series when tone is not read | 4 series, 0 with redundancy |
| Reporting 01 Dashboard Design | 12 criteria | 9 criteria in the layout by decision | 3 criteria left out |
| Reporting 02 Key Performance Indicators | coverage 60.948 | up to 93.766 by six routes | 32.818 points |
| Reporting 03 Business Intelligence Tools | final value 63.055 | 36.042 in the pre-aggregated one | 27.013 points |
| Reporting 04 Building Narrative with Data | 16 premises | 2-4 inferences when one step is missing | 11 wrongly assumed, 9 in favor |
| Reporting 05 Report Reproducibility | 59 items to pin | 10 of 11 items move it | 36.042 ↔ 100.000 |
| Scale 01 Big Data Concept | 286.0 bytes per row | threshold 22,525,682 rows | 7,508,560 ↔ 240,273,951 |
| Scale 02 Map–Reduce Model | 3,199 records | 40 records in the shuffle | 79.97 times |
| Scale 03 Distributed Analysis Engines | 8 operations | 4 stages, 100 items | 61.5 rows/item |
| Scale 04 Columnar Storage Formats | 22,393 values | 1,174 values in the best layout | 19.07 times |
| Scale 05 Limits of Parallel Processing | 6,753 touches | ceiling 21.78 times | 43.64 ↔ 206,388 |
The third column is the rule of the course: a display is as correct as the number the reader can read back from it; a display that cannot be read back counts as unreported. In the first eight rows what is read back is an m³ value, a number of peaks, or a number of series, and the difference column gives what the channel lost. In the middle five rows the reader is a decision maker, and what is read back is a decision, not a single number: how many criteria are seen, where a criterion can be moved without moving what it measures, whether the same report can be produced a second time.
In the last five rows the number read back comes not from a display but from the scale: the records moved, the coordination overhead item, the values read, the speedup in times. The rule does not change. The divisibility of a computation is known only as far as it is counted, and the item that is not counted turns up not inside the computation but inside the bill.
The six courses of the Data Analytics curriculum read the same axis in six different places.
| Course | Lessons | Measure axis | Unit |
|---|---|---|---|
| Introduction to Data Analytics | 9 | the number moves at the moment of definition | definition |
| Analysis with Spreadsheets | 11 | where the decision sits | recoverability of the decision |
| Data Collection and Preparation | 17 | the row count changes on the way | rows in and out |
| Data Analysis with Python | 22 | what the abstraction gains and hides | gain and hidden decision |
| Statistical Analysis and Exploratory Data Analysis | 13 | the margin of variation of the number | share |
| Visualization, Reporting and Analysis at Scale | 18 | the number the channel makes you read back | read-back error |
| Total | 90 |
Ninety lessons established a single rule six times: if the decision that produced a number is not written next to it, that number is not a finding but a choice. It was tested in a different place in each course — in the definition, the cell of the spreadsheet, the preparation step, the default of the call, the margin of variation, and the encoding channel — and each time it was paid in the same form: from the same data, to the same question, two defensible decisions gave two numbers, both printed side by side. The 4.463 m³ difference between the east and the north stood in all of these ninety lessons and said nothing by itself anywhere.
Ninety lessons measured every step from data to decision and showed at each that the decision moves the number. But all of these decisions were made by a human: which rows will be counted, which threshold will be chosen, which channel will be used, by which key the job will be divided. Which number a procedure that learns the decision from the data moves, and where the decisions of that procedure itself stand, was never asked. The next curriculum, Machine Learning and Data Science, opens with this question.
To keep your progress and take notes, Log in
My notes
Log in to take notes.