https://github.com/RohanVashisht1234/benchmarking
The rust version is taking 42 nanoseconds 🤐
The zig is taking 2000 something nanoseconds, am I doing something wrong here?
1 messages · Page 1 of 1 (latest)
https://github.com/RohanVashisht1234/benchmarking
The rust version is taking 42 nanoseconds 🤐
The zig is taking 2000 something nanoseconds, am I doing something wrong here?
how are you compiling these?
cargo run --release
and
zig build-exe ./zig/main.zig -Doptimize=ReleaseFast
0.13.0
same.
-Doptimize is only for the build.zig command line, with build-exe and co it's -OReleaseFast
cargo run is also impossible here, I assume there's some unchecked-in files
-D for build-* refers to defining C preprocessor defines for C source files
this made it a lot fast
actually my 3x is based on comparing full runs with hyperfine. Using the builtin timing, Zig is more like 38x faster
how to do this optimization?
optimize the Rust? Probably by making it use an array instead of a Vec
No, I want to optimize zig
it's already 38x faster 🙂
the largest fib in this array only needs 68 bits of representation. Maybe you can use that.
how are you building the rust one it wasnt even close to that much slower for me
rustc -O rust/main.rs
and comparing with the reported nanoseconds rather than just running the executables
yeah thats what i was doing
who knows
not like this really matters its such a small benchmark of something the compiler probably nearly optimizes away
you'll be pleased to know that it's SIGNIFICANTLY slower with u68 instead of u128
yeah that's definitely a danger here with no output
the slowdown is likely due to the usage of vec
rather than using an array
if rust was using an array like zig is, I imagine it would be identical
const LIMIT: usize = 100;
fn generate_fibonacci(fib: &mut [u128; LIMIT]) -> u128 {
fib[0] = 0;
fib[1] = 1;
for i in 2..LIMIT {
fib[i] = fib[i - 1] + fib[i - 2];
}
fib[LIMIT-1]
}
fn main() {
use std::time::Instant;
let now = Instant::now();
let res: u128;
// Code block to measure.
{
let mut fib = [0u128; LIMIT];
res = generate_fibonacci(&mut fib);
}
let elapsed = now.elapsed();
println!("Rust: {} in {} nanoseconds.", res, elapsed.as_nanos());
}
Rust: 218922995834555169026 in 621 nanoseconds.
Zig: 218922995834555169026 in 1633 nanoseconds.
forgot to revert u68
Zig: 218922995834555169026 in 131 nanoseconds.
still faster but not as bad
const std = @import("std");
const stdout = std.io.getStdOut().writer();
const LIMIT = 100;
fn generateFibonacci(fib: []u128, n: usize) u128 {
fib[0] = 0;
fib[1] = 1;
for (0..n - 2) |i| {
fib[i + 2] = fib[i + 1] + fib[i];
}
return fib[LIMIT - 1];
}
fn function_to_benchmark() u128 {
var fib: [LIMIT]u128 = undefined;
return generateFibonacci(&fib, LIMIT);
}
pub fn main() !void {
var t = std.time.Timer.start() catch unreachable;
const res = function_to_benchmark();
const elapsed = t.read();
std.debug.print("Zig: {} in {} nanoseconds.\n", .{ res, elapsed });
}
so Vec was killing Rust, and Zig wasn't cheating by not doing the work
for optimizing it further, no idea really. It's too small of a problem to probably benefit from parallelism.
that version with a slice is quite a bit faster than a version taking a pointer to an array like my Rust version
if you want a more challenging benchmark and one that'll benefit from parallelism, try a mandelbrot generator.
Or you could compare tail-recursive fibonacci functions 🙂
tail-recursive performance is about the same actually.
maybe a little more illustrative of the languages:
const std = @import("std");
fn fib(n: u32, a: u32, b: u32) u32 {
if (n == 0) return a;
return @call(.always_tail, fib, .{ n - 1, b, a + b });
}
pub fn main() !void {
var t = try std.time.Timer.start();
const res = fib(47, 0, 1);
const elapsed = t.read();
std.debug.print("Zig: {} in {} nanoseconds.\n", .{ res, elapsed });
}
and
use tailcall::tailcall;
#[tailcall]
fn fib(n: u32, a: u32, b: u32) -> u32 {
if n == 0 {
a
} else {
fib(n - 1, b, a + b)
}
}
fn main() {
use std::time::Instant;
let now = Instant::now();
let res = fib(47, 0, 1);
let elapsed = now.elapsed();
println!("Rust: {} in {} nanoseconds.", res, elapsed.as_nanos());
}
You'll need to actually build that with cargo to get the 'tailcall' dependency.
yeah, I mean, not much further that you can optimize the fibonacci sequence - not exactly what I'd call a computationally intensive program lol
these are all algorithmically optimized already, compared to a naive implementation
I thnk I have created exact replicas of both the src codes:
Rust:
use std::time::Instant;
const LIMIT: usize = 100;
fn generate_fibonacci() {
let mut fib = [0u128; LIMIT];
fib[0] = 0;
fib[1] = 1;
for i in 2..LIMIT {
fib[i] = fib[i - 1] + fib[i - 2];
}
}
fn main() {
let now = Instant::now();
// Code block to measure.
{
generate_fibonacci();
}
let elapsed = now.elapsed();
println!("Rust: in {} nanoseconds.", elapsed.as_nanos());
}
Zig:
const std = @import("std");
const LIMIT: usize = 100;
fn generate_fibonacci() void {
var fib: [LIMIT]u128 = undefined;
fib[0] = 0;
fib[1] = 1;
for (2..LIMIT) |i| {
fib[i] = fib[i - 1] + fib[i - 2];
}
}
pub fn main() !void {
var now = try std.time.Timer.start();
// Code block to measure.
{
generate_fibonacci();
}
const elapsed = now.read();
std.debug.print("Zig: in {} nanoseconds.", .{elapsed});
}
See, this is happening in the final run:
https://github.com/RohanVashisht1234/benchmarking/actions/runs/11981076694/job/33406619651
See, zig is 120 nanoseconds and rust is 91 nanoseconds
I am still thinking how is that possible, I had done all the R&D, I don't expect such results, am I doing something wrong here?
you're running this in some cloud job, instead of locally where you can control powersaving and such?
I want the results to be shown in a way that anyone can see them.
No, because some people distrust tests done on local machine
OK, the reverse is much more sensible.
you are just using timers instead of a tool like hyperfine?
what is hyperfine?
IMO the only way for these easy-to-see numbers to be worth anything, is to replicate hypefine more or less. Statistics, lots of runs.
benchmarking tool
And still I just wouldn't trust <100ns differences at all. It's cloud.
wait a minute
couldnt the results of the functions just be optimized out since they arent returned?
also, why bother doing a microbench?
anyway that's enough of my opinions. If the difference is meaningful and consistent my only guess would be that Zig and Rust aren't optimizing with the same CPU features in mind. Different detection of whatever this platform is.
only a limit of 100? thats tinsy-tiny
that's possible but not what's happening here, the numbers are the same with a version that includes the work in the output
fib(100) is already 68 bits long. You can't get big numbers with the function itself, without bignums and very different performance characteristics.
true, i didnt consider that
you can also compare the assembly to see generate_fibonacci to see if there's something interesting about the optimization. Zig has quite a lot going on with the loop, it might have an optimization that works a lot better on my machine than on what's exposed to this random cloud job.
using the same CPU target, and some old one, like -mcpu baseline, might help for more similar builds