#Parallel processing a loop

1 messages · Page 1 of 1 (latest)

tall gorge
#

does zig have a built-in way to parallelize over for loops? like if i have some computation i wanna do for a bunch of elements in the loop,

for (xs) |x| {
    x.* = doStuff()
}

can i somehow make this faster? i've heard zig can go fast but the rust people say that their multithreading is better but they're bullies

warm moth
#

Welll

#

no

#

You need to design this

tall gorge
#

like design multithreading for zig

warm moth
#

We have threads in std

#

and a threadpool

#

but we can't help in any other way to make it so

tall gorge
#

i'm kinda of a noob... can i use that for this purpose

#

oh sick that's cool

warm moth
#

You can @ us at any question you have for std.Thread stuff

tall gorge
#

what does that mean

#

that last thing you said

warm moth
#

std.Thread is the most likely thing that you will use for making this paralle

#

that and std.Thread.Pool

#

so we are receptive on any question you might have

#

lol

tall gorge
#

ah word you mean you won't banish me to the help channel

warm moth
#

exactly

#

this is a fun topic, but is not as easy as in rust, importing a few libs, making a few things then hoping your design escaltes 😛

#

Or having a C macro that does it for you A-LA mpi

tall gorge
#

so this is mostly just a wiener-measuring contest (with myself) because it's already decently fast and i'm not sure how much faster it can realistically go

#

(i'm watching that talk rn btw)

warm moth
#

Generally the recomendations is to get the max single threaded perf

tall gorge
#

yea

#

so i've been profiling

warm moth
#

then try to get a way to make it easy to multithread

tall gorge
#

all my time is spent in these two function calls

#

because for every particle in my array, i gotta compute those bad boys

#

i've used some physical reasoning to cut down the number of times i call them

#

but i'm not sure how much lower i can go

#

anyway, that's where i'm at

#

but i'm still very much a programming noob/boob

warm moth
#

Can you do it in each individual thing?

tall gorge
#

honestly maybe it can't go faster

warm moth
tall gorge
warm moth
#

as in, are you calculating individually in every element?

tall gorge
#

yea

warm moth
#

they are all independent otherwise?

tall gorge
#

i think so, yea

#

i say i think* because maybe there's some physical argument i can use to make this simpler

#

but currently, yea, each one needs its own calculation

tall gorge
warm moth
#

yeah, it's a thing to take into account

#

because here comes the good part

#

First, lets start with single thread performance

#

do you need to change EVERY particle position

#

or can you reduce the sample set to what is in front of you and visible

tall gorge
#

yea that's exactly the right question to ask

#

so i worked this out earlier

#

so yea, physical reasoning is for sure the way to go

#

well that's good to know, maybe i can think of some more optimizations by adding in some more assumptinos

#

but i think the good news is that i pretty much did this right

warm moth
#

Yeah

#

Basically you try to squeeze perf before deciding creating threads is worth it

#

they are expensive thigns

tall gorge
#

i have expensive taste

warm moth
#

After the question is yes, you would divide the data in some way that is easy to make the calculation in a way each particle is independent

#

if you can then you would do a previous step to separate in a broad range of interactions and reduce this interactions in parallel

#

as in, faster math to decide if an area is interacting then from there resolve only the interactions

tall gorge
#

but i'll read that blog post

candid lily
#

if you're ok with static chunking, here's an idea to try out:

fn join(
    comptime func_a: anytype,
    args_a: anytype,
    comptime func_b: anytype,
    args_b: anytype,
) !struct {
    @TypeOf(@call(.auto, func_a, args_a)),
    @TypeOf(@call(.auto, func_b, args_b)),
} {
    const ArgsB = @TypeOf(args_b);
    const RetB = @TypeOf(@call(.auto func_b, args_b));
    const callback_b = struct {
        fn callback(wg: *std.Thread.WaitGroup, args: ArgsB, ret: *RetB) void {
            ret.* = @call(.auto, func_b, args);
            wg.finish();
        }
    }.callback;

    var ret_b: RetB = undefined;
    var wg = std.Thread.WaitGroup{ .state = .{ .raw = 1 } };
    try thread_pool.spawn(callback_b, .{ &wg, args_b, &ret_b });

    const ret_a = @call(.auto, func_a, args_a);
    thread_pool.waitAndWork(&wg);
    return .{ ret_a, ret_b };
}

fn forEach(window: usize, slice: anytype, context: anytype) !void {
    if (slice.len <= window) return context.handle(slice);
    const mid = slice.len / 2;
    _ = try join(
        forEach, .{window, slice[0..mid], context},
        forEach, .{window, slice[mid..], context},
    );
}
warm moth
#

I have to suppose the threadpool is a global?

candid lily
polar tangle
# tall gorge

I'm curious; how much difference does it make to just both of these inline? (inline fn foo(...))

tall gorge