dispatch_apply(3)
β¨ dispatch_apply β¨
okay bestie, we're diving into dispatch_apply(3) from the Library Functions Manual and honestly? this one is a whole vibe. it's a libc-level baddie from the dispatch framework and she is here to make your loops go fast. π
πΈ NAME πΈ
dispatch_apply β schedule blocks for iterative execution π¦
π SYNOPSIS π
#include <dispatch/dispatch.h>
void
dispatch_apply(size_t iterations, dispatch_queue_t queue,
void (^block)(size_t));
void
dispatch_apply_f(size_t iterations, dispatch_queue_t queue,
void *context, void (*function)(void *, size_t));
π DESCRIPTION π
so. dispatch_apply() gives you data-level concurrency through a little for (;;) loop lookalike primitive. it's giving "for loop but make it parallel". β¨
size_t iterations = 10;
// 'idx' is zero indexed, just like:
// for (idx = 0; idx < iterations; idx++)
dispatch_apply(iterations, DISPATCH_APPLY_AUTO, ^(size_t idx) {
printf("%zu\n", idx);
});
okay so you can pass any queue in there, but it is strongly recommended β like, fr fr, do this β to use DISPATCH_APPLY_AUTO as the queue argument for both dispatch_apply() and dispatch_apply_f(), exactly like the example up there βοΈ. why? because that lets the system automatically pick worker threads that match the configuration of the current thread as closely as it possibly can. iconic behaviour. π
and no, you should not make any assumptions about which global concurrent queue will be used. that's not your business, bestie, that's the system's business. π
just like a for (;;) loop, dispatch_apply() is synchronous. she blocks, she waits, she does the thing. if you actually want asynchronous behaviour, wrap the call to dispatch_apply() in a call to dispatch_async() against another queue. slay. π
sometimes, when the block you pass to dispatch_apply() is simple, using striding can tune performance. calculating the optimal stride is best left to experimentation β no cap, just vibe it out. start with a stride of one and work upwards until you get the performance you want (maybe using a power of two search):
#define STRIDE 3
dispatch_apply(count / STRIDE, DISPATCH_APPLY_AUTO, ^(size_t idx) {
size_t j = idx * STRIDE;
size_t j_stop = j + STRIDE;
do {
printf("%zu\n", j++);
} while (j < j_stop);
});
size_t i;
for (i = count - (count % STRIDE); i < count; i++) {
printf("%zu\n", i);
}
π§ IMPLIED REFERENCES π§
synchronous functions within the dispatch framework hold an implied reference on the target queue. in other words: the synchronous function borrows the reference of the calling function. and that's valid, okay, because the calling function is blocked waiting for the result of the synchronous function, so it literally cannot modify the reference count of the target queue until after the synchronous function has returned. the math is mathing. π₯
this is in contrast to asynchronous functions, which must retain both the block and the target queue for the duration of the asynchronous operation (because the calling function may immediately release its interest in these objects β rude, but valid). π
π FUNDAMENTALS π
dispatch_apply() and dispatch_apply_f() attempt to quickly create enough worker threads to efficiently iterate work in parallel. β
by contrast, a loop that passes work items individually to dispatch_async() or dispatch_async_f() will incur more overhead and does not express the desired parallel execution semantics to the system β so it may not create an optimal number of worker threads for a parallel workload. π
for this reason, prefer to use dispatch_apply() or dispatch_apply_f() when parallel execution is important. periodt. π
also, cute detail: the dispatch_apply() function is a wrapper around dispatch_apply_f(). π«Ά
β οΈ CAVEATS β οΈ
unlikedispatch_async(), a block submitted todispatch_apply()is expected to be either independent or dependent only on work already performed in lower-indexed invocations of the block.
if the block's index dependency is non-linear, it is recommended to use a for-loop around invocations of dispatch_async(). don't fight the system, bestie. π₯²
π SEE ALSO π
okay that's the whole page, you ate that. π this one's from Darwin, dated May 1, 2009 β vintage but still absolutely running the game. mwah. πβ¨π
π¦ where this page comes from
- alpine 3.24: libdispatch-doc 6.1.2-r0 Β· Apache-2.0
- arch rolling: libdispatch 6.2.2-1 Β· Apache-2.0
- epel 10: libdispatch 1:6.0.2-1.el10_0 Β· Apache-2.0
- epel 9: libdispatch 1:6.0.2-1.el9 Β· Apache-2.0
- fedora 43: libdispatch 1:6.0.2-3.fc43 Β· Apache-2.0
- fedora 44: libdispatch 1:6.0.2-3.fc43 Β· Apache-2.0
- fedora 44: libdispatch 1:6.0.2-4.fc44 Β· Apache-2.0
- nixos 26.05: swift-corelibs-libdispatch 5.10.1
- opensuse-leap 16.0: libdispatch-devel 5.6.3-bp160.1.1 Β· Apache-2.0
- Copyright (c) 2008-2017 Apple Inc. All rights reserved
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms