Futures, and when to call get()
A future is a handle to a result that does not exist yet. This lesson is about one decision: where
you call get().
Getting it wrong costs more parallelism than any other mistake in this course.
What a future is
future = transform(record)
print(type(future)) # a handle, not your result
print(eugo.hpc.get(future)) # the value
Calling a distributed function returns immediately. The work happens on a worker while your code keeps
going. get() blocks until the result is ready.
The mistake
# WRONG: runs one task at a time
results = []
for record in records:
results.append(eugo.hpc.get(transform(record)))
Trace it: dispatch one task, wait for it, dispatch the next, wait again. Only one task is ever in flight. Your cluster of 32 workers has 31 idle at all times, and you are paying for all of them.
The fix
# RIGHT: every task in flight before waiting on any
futures = [transform(record) for record in records]
results = eugo.hpc.get(futures)
Build all the futures, then resolve once.
The difference is moving one call out of a loop. On a 32-worker cluster it is frequently the difference between no speedup and roughly 25x.
Build every future first. Call get() once, on the list.
Why this is so easy to get wrong
The broken version looks reasonable. It reads like ordinary Python, produces correct results, and nothing errors. It is only slow. In a distributed system, slowness is easy to blame on the cluster, the data, or the network rather than on your own loop.
When a run disappoints, check this first. It accounts for more missing speedup than everything else combined.
Passing futures to tasks
You can pass a future as an argument. Eugo resolves it before the body runs, which lets you express a dependency:
@eugo.hpc.distribute
def stage_one(x):
return x * 2
@eugo.hpc.distribute
def stage_two(value):
return value + 1
chained = stage_two(stage_one(10)) # no get() in between
print(eugo.hpc.get(chained)) # 21
The intermediate result never returns to your session. It moves directly between workers, saving a round trip.
Accidental serialization
The flip side: a genuine dependency chain cannot parallelize.
# Each task needs the previous result. Serial by construction.
running = seed
for x in items:
running = accumulate(running, x)
result = eugo.hpc.get(running)
No cluster size fixes this. The work has to be restructured, often as a tree reduction that combines pairs in parallel rather than folding left.
Partial results
To handle results as they arrive rather than waiting for all:
futures = [transform(r) for r in records]
while futures:
done, futures = eugo.hpc.wait(futures, num_returns=1)
for f in done:
handle(eugo.hpc.get(f))
Useful for streaming output or showing progress. For most work, one get() on the full list is
simpler and just as fast.
What to remember
- A future is a promise, not a value.
get()inside a loop serializes your run.- Build all futures, then
get()once. - Chained futures avoid a round trip through your session.
Video: Working with futures without blocking. The same material, with a transcript.