Speedup compares how long a baseline execution takes with how long a parallel execution takes. Think of sorting cards alone versus handing independent piles to friends: only the separable work gets faster. section 2.11
The card-sorting bottleneck
A three-step algorithm takes 5 seconds sequentially: steps 1 and 3 each take 1 second, while step 2 takes 3 seconds. Two additional workers reduce step 2 to 1 second, making the total 3 seconds and producing a 40% speedup. Six people reduce it to 2.5 seconds, or 50%. Adding still more workers cannot remove the 2 seconds of sequential work. section 2.11
Speedup is useful, but its meaning depends on what stays constant:
- With a fixed problem size, speedup measures how much less time parallel execution requires; sequential portions impose a hard limit. section 2.11
- With a fixed time interval, speedup can instead describe increased throughput: more processors handle a larger parallel workload. section 2.12
- Parallelism does not automatically accelerate every program; sequential work and communication overhead can leave a latency floor. section 2.11
Password cracking with processes
A password-cracking implementation divides candidate ranges among workers in a process pool. Running the chunks concurrently produces more than a 3× speedup over the original sequential implementation, showing the practical payoff when the work divides cleanly across CPU cores. section 5.5
# Chapter 5/password_cracking_parallel.py
def crack_chunk(crypto_hash: str, length: int, chunk_start: int,
chunk_end: int) -> T.Union[str, None]:
print(f"Processing {chunk_start} to {chunk_end}")
(reformat)
combinations = get_combinations(
length=length,
min_number=chunk_ start,
max_number=chunk_end
for combination in combinations:
if check_password(crypto_hash, combination):
return combination #1
return #2
def crack_password_parallel(crypto_hash: str, length: int) -> None:
num_cores = os.cpu_count() #3
print("Processing number combinations concurrently")
start_time = time.perf_counter()
with Pool() as pool:
arguments = ((crypto_hash, length, chunk_start, chunk_end) for
chunk_start, chunk_end in
get_chunks(num_cores, length))
results = pool.starmap(crack_chunk, arguments) #4
print("Waiting for chunks to finish")
pool.close() #5
pool.join() #6
result = [res for res in results if res]
print(f"PASSWORD CRACKED: {result[0]}")
process_time = time.perf_counter() - start_time
print(f"PROCESS TIME: {process_time}")
if __name__ == "__main__":
crypto_hash = \
"e24df920078c3dd4e7e8d2442f00e5c9ab2a231bb3918d65cc50906e49ecaef4"
length = 8
crack_password_parallel(crypto_hash, length)
Inspect the Python process-pool listing that divides password candidates into chunks and processes them concurrently. section 5.5

