Skip to content

Commit 2d15797

Browse files
committed
Finalize std::async and thread pool animations
1 parent 78abd38 commit 2d15797

2 files changed

Lines changed: 119 additions & 72 deletions

File tree

animation/projects/src/parallelism/scenes/why_not_others.tsx

Lines changed: 118 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export default makeScene2D(function* (view) {
6060
justifyContent={'center'}
6161
clip={true}
6262
>
63-
<Txt ref={osOverheadText} text="OS Overhead (Create Thread)" fill="#FFF" fontSize={24} fontWeight={600} opacity={0} />
63+
<Txt ref={osOverheadText} text="OS overhead (create thread)" fill="#FFF" fontSize={24} fontWeight={600} opacity={0} />
6464
</Rect>
6565
<Rect
6666
ref={taskBox}
@@ -71,7 +71,7 @@ export default makeScene2D(function* (view) {
7171
justifyContent={'center'}
7272
clip={true}
7373
>
74-
<Txt ref={taskText} text="Invert" fill="#1E1E1E" fontSize={24} fontWeight={600} opacity={0} />
74+
<Txt ref={taskText} text="Invert" fill="#1E1E1E" fontFamily={"Fira Mono"} fontSize={24} fontWeight={600} opacity={0} />
7575
</Rect>
7676
</Rect>
7777
</Node>
@@ -90,7 +90,7 @@ export default makeScene2D(function* (view) {
9090
timelineContainer().opacity(1, duration),
9191
incomingImage().y(-150, duration),
9292
incomingImage().scale(0.8, duration),
93-
titleText().text("std::async Execution Timeline", duration) // Animate writing the text
93+
titleText().text("std::async execution timeline", duration) // Animate writing the text
9494
);
9595
yield* waitFor(0.5);
9696

@@ -114,121 +114,168 @@ export default makeScene2D(function* (view) {
114114

115115
// --- Part 2: Standard Algorithms ---
116116
const algoContainer = createRef<Node>();
117-
const vectorBox = createRef<Rect>();
118-
const algoBox = createRef<Rect>();
119-
const waitingTxt = createRef<Txt>();
120-
const questionTxt = createRef<Txt>();
117+
118+
// Components
119+
const vectorBuffer = createRef<Rect>();
120+
const algoBlock = createRef<Rect>();
121+
122+
// Texts
123+
const algoStatusText = createRef<Txt>();
124+
const explanationTextLine1 = createRef<Txt>();
125+
const explanationTextLine2 = createRef<Txt>();
126+
const streamingText = createRef<Txt>();
121127

122128
yield view.add(
123129
<Node ref={algoContainer} opacity={0}>
124-
<Txt text="std::vector<Image>" fill="#FFF" fontSize={32} fontWeight={600} y={-300} />
125-
<Rect
126-
ref={vectorBox}
127-
width={800}
128-
height={140}
129-
radius={16}
130-
stroke={'#868E96'}
131-
lineWidth={4}
132-
y={-200}
130+
<Txt text="std::execution::par / raw TBB" fill="#4DABF7" fontSize={48} fontWeight={700} y={-350} textAlign={'center'} />
131+
<Txt text="When do we begin?" fill="#ADB5BD" fontSize={28} fontWeight={600} y={-290} textAlign={'center'} />
132+
133+
<Layout
133134
layout={true}
134135
direction={'row'}
135136
alignItems={'center'}
136-
padding={20}
137-
gap={20}
138-
>
139-
{/* We'll spawn images here */}
140-
<Txt ref={questionTxt} text="Size: ???" fill="#868E96" fontSize={32} fontWeight={600} position={[0, 0]} isPositionedAbsolute={true} />
141-
</Rect>
142-
143-
<Rect
144-
ref={algoBox}
145-
width={800}
146-
height={300}
147-
radius={16}
148-
fill={'#343A40'}
149-
stroke={'#4DABF7'}
150-
lineWidth={4}
151-
y={150}
152-
alignItems={'center'}
153137
justifyContent={'center'}
154-
layout={true}
155-
direction={'column'}
156-
gap={20}
138+
gap={80}
139+
y={50}
157140
>
158-
<Txt text="std::for_each ( std::execution::par )" fill="#4DABF7" fontSize={36} fontWeight={700} />
159-
<Txt text="or raw TBB" fill="#4DABF7" fontSize={28} fontWeight={600} opacity={0.8} />
160-
<Txt ref={waitingTxt} text="Waiting for all ??? images..." fill="#FFD43B" fontSize={28} fontWeight={600} marginTop={40} />
161-
</Rect>
141+
{/* Vector Buffer */}
142+
<Layout layout={true} direction={'column'} alignItems={'center'} gap={20}>
143+
<Txt text="std::vector<Image>" fill="#FFF" fontSize={28} fontWeight={400} fontFamily={"Fira Mono"} />
144+
<Rect
145+
ref={vectorBuffer}
146+
width={300}
147+
height={400}
148+
radius={16}
149+
fill={'#2C2E33'}
150+
stroke={'#868E96'}
151+
lineWidth={4}
152+
clip={true}
153+
layout={true}
154+
direction={'column'}
155+
alignItems={'center'}
156+
justifyContent={'flex-start'}
157+
padding={20}
158+
gap={0} // Gap is 0 to fluidly animate insertion margins instead
159+
>
160+
<Txt ref={streamingText} text="Streaming in..." fill="#868E96" fontSize={24} fontWeight={600} margin={['auto', 0, 20, 0]} />
161+
</Rect>
162+
</Layout>
163+
164+
{/* Arrow */}
165+
<Txt text="➔" fill="#868E96" fontSize={64} fontWeight={700} />
166+
167+
{/* Algo Block */}
168+
<Layout layout={true} direction={'column'} alignItems={'center'} gap={20}>
169+
<Txt text="Algorithm execution" fill="#FFF" fontSize={28} fontWeight={600} />
170+
<Rect
171+
ref={algoBlock}
172+
width={300}
173+
height={400}
174+
radius={16}
175+
fill={'#343A40'}
176+
stroke={'#495057'}
177+
lineWidth={4}
178+
layout={true}
179+
direction={'column'}
180+
alignItems={'center'}
181+
justifyContent={'center'}
182+
padding={20}
183+
gap={20}
184+
>
185+
<Txt text="⚙️" fontSize={80} opacity={0.5} />
186+
<Txt ref={algoStatusText} text="Blocked" fill="#FF6B6B" fontSize={36} fontWeight={700} />
187+
<Layout layout={true} direction={'column'} alignItems={'center'} gap={5}>
188+
<Txt ref={explanationTextLine1} text="Waiting for buffer" fill="#ADB5BD" fontSize={24} fontWeight={600} textAlign={'center'} />
189+
<Txt ref={explanationTextLine2} text="to be complete..." fill="#ADB5BD" fontSize={24} fontWeight={600} textAlign={'center'} />
190+
</Layout>
191+
</Rect>
192+
</Layout>
193+
</Layout>
162194
</Node>
163195
);
164196

165197
yield* algoContainer().opacity(1, duration);
166198
yield* waitFor(0.5);
167199

168200
// Stream images in
169-
for (let i = 0; i < 5; i++) {
201+
for (let i = 0; i < 3; i++) {
170202
const newImg = createRef<Rect>();
171-
vectorBox().add(
203+
vectorBuffer().insert(
172204
<Rect
173205
ref={newImg}
174-
width={100}
175-
height={100}
176-
radius={16}
206+
width={200}
207+
height={0} // Start at 0 height to animate insertion
208+
margin={[0, 0, 0, 0]} // Start at 0 margin
209+
radius={12}
177210
fill={'#495057'}
178211
stroke={'#ADB5BD'}
179-
lineWidth={4}
212+
lineWidth={2}
180213
alignItems={'center'}
181214
justifyContent={'center'}
182215
opacity={0}
183-
scale={0}
184-
zIndex={10}
216+
scale={0.8}
217+
clip={true} // Keep text inside while height is growing
185218
>
186-
<Txt text="🖼️" fill="#FFF" fontSize={64} />
187-
</Rect>
219+
<Txt text={`🖼️ Image ${i + 1}`} fill="#FFF" fontSize={32} />
220+
</Rect>,
221+
0
188222
);
189223

190224
yield* all(
225+
newImg().height(80, 0.4),
226+
newImg().margin([0, 0, 10, 0], 0.4), // Fluidly add the gap
191227
newImg().opacity(1, 0.4),
192-
newImg().scale(1, 0.4),
193-
questionTxt().opacity(0, 0.4) // Hide the "Size: ???" once images start coming
228+
newImg().scale(1, 0.4)
194229
);
195-
yield* waitFor(0.3);
230+
yield* waitFor(0.4);
196231
}
197232

198-
yield* waitFor(1.0);
233+
yield* waitFor(0.5);
199234

235+
// Highlight the problem
236+
explanationTextLine1().text("Images are streaming!");
237+
explanationTextLine2().text("When to start processing?");
200238
yield* all(
201-
waitingTxt().text("Still waiting... how many more?", 0.5),
202-
waitingTxt().fill("#FF6B6B", 0.5)
239+
algoBlock().stroke('#FF6B6B', 0.5),
240+
algoBlock().shadowBlur(20, 0.5),
241+
algoBlock().shadowColor('rgba(255, 107, 107, 0.3)', 0.5),
242+
algoStatusText().scale(1.2, 0.5),
243+
explanationTextLine1().fill("#FFF", 0.5),
244+
explanationTextLine2().fill("#FFF", 0.5)
203245
);
204246

205-
yield* waitFor(1.5);
247+
yield* waitFor(1.0);
206248

207-
// One more image arrives randomly late
208-
const lateImg = createRef<Rect>();
209-
vectorBox().add(
249+
// Animate another image arriving while it's blocked
250+
const newImg = createRef<Rect>();
251+
vectorBuffer().insert(
210252
<Rect
211-
ref={lateImg}
212-
width={100}
213-
height={100}
214-
radius={16}
253+
ref={newImg}
254+
width={200}
255+
height={0} // Start at 0 height
256+
margin={[0, 0, 0, 0]} // Start at 0 margin
257+
radius={12}
215258
fill={'#495057'}
216259
stroke={'#ADB5BD'}
217-
lineWidth={4}
260+
lineWidth={2}
218261
alignItems={'center'}
219262
justifyContent={'center'}
220263
opacity={0}
221-
scale={0}
222-
zIndex={10}
264+
scale={0.8}
265+
clip={true}
223266
>
224-
<Txt text="🖼️" fill="#FFF" fontSize={64} />
225-
</Rect>
267+
<Txt text="🖼️ Image 4" fill="#FFF" fontSize={32} />
268+
</Rect>,
269+
0
226270
);
227271

228272
yield* all(
229-
lateImg().opacity(1, 0.4),
230-
lateImg().scale(1, 0.4)
273+
newImg().height(80, 0.4),
274+
newImg().margin([0, 0, 10, 0], 0.4),
275+
newImg().opacity(1, 0.4),
276+
newImg().scale(1, 0.4)
231277
);
278+
232279
yield* waitFor(1.5);
233280

234281
// Fade out

lectures/parallelism.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ So now we know how to kick off long-running tasks and how to use parallel algori
399399

400400
Not quite. Imagine instead of one huge image, like in the previous example, we receive a stream of thousands tiny images that all need their colors inverted.
401401

402-
Our previous approaches are not well-suited for this. The `std::async` approach is too heavy. Spawning a brand new async task for every single tiny image would be devastating to performance, as every task would need a thread, and the OS overhead of creating a thread costs more computing time than actually inverting our tiny image. At the same time, the parallel versions of standard algorithms or even using raw TBB are a poor fit too, as they would need a complete vector of images to start processing, but here our images are streaming in one by one!
402+
Our previous approaches are not well-suited for this. The `std::async` approach is too heavy. Spawning a brand new async task for every single tiny image would be devastating to performance, as every task would need a thread, and the OS overhead of creating a thread costs more computing time than actually inverting our tiny image. At the same time, the parallel versions of standard algorithms or even using raw TBB are a poor fit too, as they work well when we provide them a bunch of available data. But our images are streaming one by one? How many should we pass? Is 3 enough? 4? More?
403403

404404
Instead, we can use a different paradigm that is used quite often in real life: a **thread pool** working on a **concurrent queue** that stores the data. In this paradigm, at the thread pool creation, we spawn a fixed number of threads (typically derived from the number of CPU cores we have), have them sleep in the background, and wake them up to process data from the queue, which is designed to work correctly when multiple threads read and write to it at the same time.
405405
<!-- TODO: add a diagram here -->

0 commit comments

Comments
 (0)