#Trying to fix old code - std.mem.set to std.mem.zeroes

1 messages · Page 1 of 1 (latest)

graceful stone
#

Hi there, I try to get this old zig code to work:
https://gist.github.com/svaniksharma/9ad2fa148254ac74b02940326090b18d

it contains the following for reserving memory for a matrix:

var matrix: [][]f64 = undefined;
        matrix = try allocator.alloc([]f64, N);
        for (matrix) |*row| {
            row.* = try allocator.alloc(f64, N);
            std.mem.set(f64, row.*, 0);
        }

Now the std.mem.set is deprecated and one should use std.mem.zeroes. Sadly I have no idea how to do that.
I tried std.mem.zeroes(f64) as well as row.* = std.mem.zeroes([]f64) but I think I don't understand the syntax.

Help appreciated!

Gist

Optimizing Matrix Multiplication in Zig. GitHub Gist: instantly share code, notes, and snippets.

peak sparrow
#

if N is comptime known you can use @splat:

row.* = @splat(0);

otherwise i think @memset might be a good choice.

graceful stone
#

like this? @memset(row.*, 0b0);

peak sparrow
#

i think so. is N comptime known?

graceful stone
#

well, the maximum value probably is

peak sparrow
#

ok. if N is runtime known, then likely @memset(row, 0)

graceful stone
#

not the pointer?

peak sparrow
#

alloc() returns a slice. so i assume row is a slice.

#

oic. you're looping and assigning. yeah then you'll have to deref in think.

graceful stone
#

N is just a constant, so I want to make it work with @splat, but it yields: expected array or vector type, found '[]f64' row.* = @splat(0);

#

sry, like this

peak sparrow
#

ok since N is constant, i would maybe alloc the whole thing at once as a allocator.create([N][N]f64). then you should be able to do matrix.* = @splat(0);

#

the error is because splat can only be used on array or vector types. but you gave a slice.

#

ok i guess for splat, you must create an [N*N]f64

#
    const N = 4;
    const matrix = try allocator.create([N * N]f64);
    matrix.* = @splat(0);
#

but know that [N][N]f64 can be bitcast to an [N * N]f64

graceful stone
#

I think I just use the working version. This does not work for me

peak sparrow
#

if you want to use a different indexing.

peak sparrow
graceful stone
#

mm.....it does not seem to like that at all

peak sparrow
#
$ zig test /tmp/tmp.zig
{ { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } }
graceful stone
#

well, I see. but I would have to change all the indexing in the multiplication function

peak sparrow
#

if you want to use double indexing, bitcast to a [N][N]f64 first.

#

bitcast is free. no runtime cost.

#

i would argue its better to allocate your vectors with a single allocation like this.

#

smaller and fewer allocations needed

graceful stone
#

totally true. but I can't get it to work.

peak sparrow
#

let me know if you need help with the indexing part. but its prolly just going to be to bitcast.

#

oh, one thing you may run into is that allocator.create() returns a pointer. so in that case i guess you would need to @ptrCast to a *[N][N]f64

graceful stone
#

I'm very sry, I know it seems obvious for you, but for me it is not. I just want my 2d matrix to be addressable with 2 variables, not with one. I can see that it does not make a difference for 1d memory

peak sparrow
#

i'm working on translating your code to use single allocations. give me a few minutes and i maybe i can share...

graceful stone
peak sparrow
#

no worries. i'm just bored.

#

its close. something wierd happening tho. some bus error. i'll bet its because the matrices are so big, 1000*1000

graceful stone
#

oh sry, try with 16x16

#

the max I'll go to is 32x32

peak sparrow
#

ok well i got something working. main() runs now atleast.

#

i think these two functions are the only ones i changed

fn generateSquareMatrix(comptime N: usize, allocator: mem.Allocator, gen_rand: bool) ![]f64 {
    const matrix = try allocator.alloc(f64, N * N);
    if (gen_rand) {
        var prng = std.Random.DefaultPrng.init(blk: {
            var seed: u64 = undefined;
            try std.posix.getrandom(std.mem.asBytes(&seed));
            break :blk seed;
        });
        const rand = prng.random();
        for (0..N * N) |i| matrix[i] = rand.float(f64);
    }
    return matrix;
}

fn naiveMatrixMultiply(comptime N: comptime_int, Child: type, C: anytype, A: anytype, B: anytype) void {
    const a: *[N][N]Child = @ptrCast(A.ptr);
    const b: *[N][N]Child = @ptrCast(B.ptr);
    const c: *[N][N]Child = @ptrCast(C.ptr);
    for (0..N) |i| {
        for (0..N) |j| {
            for (0..N) |k| {
                c[i][j] += a[i][k] * b[k][j];
            }
        }
    }
}
$ zig run /tmp/tmp.zig -OReleaseFast
6.60656211e2 ms
#

haven't tried running the tests yet.

#

oh, i should have asked, are you using 0.15? this is working with 0.15.2.

graceful stone
#

yes, this is 0.15.2

peak sparrow
#

do my changes make any sense? pretty much just doing the ptrCast and allocating a single slice.

graceful stone
#

gimme a sec to digest

peak sparrow
#

but this requires you to pass in N and Child

graceful stone
#

what Child?

peak sparrow
#

Child = f64

graceful stone
#

ah I see. It works for me as well!

#

sry, a more or less important question

#

the sole reason I wanted to use it was the Simd matrix multiplication. but now my ReleaseFast version using Simd seems to be slower than the naive multiplication with your implementation

peak sparrow
#

i guess auto vectorization is working now since the allocations are contiguous maybe?

#

what dims are you using? maybe simd is slower for large dims

graceful stone
#

32x32

peak sparrow
#

hmm not sure why that would be the case. i'd just be guessing. and i'm not great at reading assembly. maybe someone could help if you post on godbolt.

graceful stone
#

ah, no need to go as far as that!

#

I'm now very happy. And it has the upside the naive implementation is easily readable

peak sparrow
#

nice well i'm glad to help. let us know if you have anything else!

#

i would suggest that my ptr casting isn't the best, its too easy to mess up. i'll let you know if i have a better solution. not sure what that bus error was earler... maybe the new x86 backend in debug mode? idk.

graceful stone
#

thanks a lot! And sorry about my dim-wittedness, programming is hard 🙁

peak sparrow
#

i know! don't need be sorry. 🙂

graceful stone
#

I'll mark this as solved! Thanks again

peak sparrow
# graceful stone I'll mark this as solved! Thanks again

i think that bus error i was seeing before was my mistake. i think this is cleaner. note that i'm returning *[N][N]f64 as a pointer since returning by value is almost certainly a bad idea. and now no need to pass in N and Child too.

fn generateSquareMatrix(comptime N: usize, allocator: mem.Allocator, gen_rand: bool) !*[N][N]f64 {
    const matrix = try allocator.create([N][N]f64);
    if (gen_rand) {
        var prng = std.Random.DefaultPrng.init(blk: {
            var seed: u64 = undefined;
            try std.posix.getrandom(std.mem.asBytes(&seed));
            break :blk seed;
        });
        const rand = prng.random();
        for (0..N) |i| {
            for (0..N) |j| matrix[i][j] = rand.float(f64);
        }
    }
    return matrix;
}

fn naiveMatrixMultiply(C: anytype, A: anytype, B: anytype) void {
    const N = A.len;
    for (0..N) |i| {
        for (0..N) |j| {
            for (0..N) |k| {
                C[i][j] += A[i][k] * B[k][j];
            }
        }
    }
}
graceful stone
#

oh nice! thanks a lot! but one question: what happens on gen_rand=false. I wanted to have a zeroed matrix, now it is just whatever memory was at that place?

#

apperently not, but why

peak sparrow
#

oops. in that case you could loop from 0..N and use @splat on each row like before.

graceful stone
#

either im lucky or stupid. but it always puts out a zeroed matrix

peak sparrow
#

just lucky

#

you using page allocator? i think it might be doing that.

graceful stone
#

sry what?

peak sparrow
#

std.heap.page_allocator. i think it (or whatever allocator you're using) is zeroing the memory.

graceful stone
#

how nice and forthcoming!

peak sparrow
#

this seems to compile

     else {
        for (0..N) |i| matrix[i] = @splat(0);
    }
graceful stone
#

seems to work, yes, thanks!

#

and just for my understanding: these matrices are mutable because ?

peak sparrow
#

because were passing pointers to them around. they would be immutable if we passed around *const [N][N]f64 for instance.

graceful stone
#

thought so. thank you

peak sparrow
#

but allocator.create and alloc always return you mutable memory

graceful stone
#

ah I see!

#

wait, how do I then set matrix A = C

peak sparrow
#

that would be A.* = C.*

#

btw, not sure if you're using your transpose methods, but i would replace that hidden page allocator arena by just passing in tmp like this:

fn transposeMatrixMultiply(C: anytype, A: anytype, B: anytype, tmp: @TypeOf(C)) !void {
#

then it can't error too

peak sparrow
graceful stone
#

now checking the transpose fn

peak sparrow
#

oh and we have a testing method for this:

// try expect(@abs(C[i][j] - D[i][j]) < 1e-10);
try std.testing.expectApproxEqAbs(C[i][j], D[i][j], 1e-10);
graceful stone
#

youre moving to fast

#

still trying to understand the hidden page allocator

#

so youre saying I should just pass a matrix as tmp?

peak sparrow
#

i mean that many here considered it bad form to do 'hidden' allocations like this. i would either pass in an allocator so the user can choose how to allocate. or better yet don't require an allocation and let the user pass in tmp.

#

yes

#

and i would also suggest passing in a Random instead of creating one in generateSquareMatrix. that is a similar thing. you're doing a bunch of syscalls there.

graceful stone
#

for me, that makes the code more unreadable

peak sparrow
#

yeah but you're trading readabiltiy for poor perf

graceful stone
#

still trying to get transposematrix to work

peak sparrow
#

if you want a spoiler, might look at what i've done.

graceful stone
#

ah, I mixed a "!" in there

peak sparrow
#

not sure its correct, but it seems to be working.

graceful stone
#

yes, thanks very much

#

I was so confused that there is no math library with matrices yet

peak sparrow
#

i guess i should have done @TypeOf on B instead of C there.

#

for tmp i mean

graceful stone
#

well, they have all the same dimensions anyway

peak sparrow
#

i know there are some of matrix libs out there if you search. maybe zlm? idk.

graceful stone
#

I foolishly thought that implementing matrices would be easy

peak sparrow
graceful stone
#

thanks. I find this very hard to understand though

#

for me, the library straight up does not want to build 🙁

#
module.addImport("zm", zm.module("zm"));```
#

and I have been running into these sort of problems left and right. It might be due to developer branch, and im still on 0.15.2-2

peak sparrow
#

module should be exe maybe?

#

rather exe.root_module? idk