Here is the naive implementation of the gcd (Greatest Common Divisor) in Julia:
function mygcd(a::Int64, b::Int64)::Int64
if a == b
return a
elseif a > b
return mygcd(a-b, b)
else
return mygcd(a, b-a)
end
end
Note that the implementation is not the best one, the gcd()
function of Julia is far better than this implementation. My motivation here is just to make a fair (? is it really fair) comparison of languages.
The C version of the same code is
long gcd(long a, long b) {
if (a == b){
return a;
} else if (a > b) {
return gcd(a-b, b);
} else {
return gcd(a, b-a);
}
}
and here is the Rust implementation of the same function:
#[no_mangle]
pub unsafe extern "C" fn gcd(a: i64, b: i64) -> i64 {
if a == b {
return a;
}else if a > b {
return gcd(a - b, b);
}else{
return gcd(a, b - a);
}
}
For the comparison issues I start Julia with the highest optimization level 3
julia --optimize=3
and the Makefile
for C and Rust libraries have the similar compilation options:
cc -shared -fPIC -O3 -Wall -o libgcd.so gcd.c
cd rustgcd && cargo build --release & cd ..
and the Cargo.toml (of Rust) file includes the option
[profile.release]
opt-level = 3
so I think the optimization levels match well.
Thank to the beautiful FFI of Julia, I can call the compiled functions using the ccall
function. Here is how I use FFI for C and Rust implementations and compare these three implementations in Julia:
function cversion(a::Int64, b::Int64)::Int64
ccall((:gcd, "./libgcd.so"), Int64, (Int64, Int64), a, b)
end
function rustversion(a::Int64, b::Int64)::Int64
ccall((:gcd, "./rustgcd/target/release/librustgcd.so"), Int64, (Int64, Int64), a, b)
end
And here is the comparison of functions:
a = 10000 * 12 * 13 * 14
b = 512 * 12 * 13 * 14
@info mygcd(a, b) == cversion(a, b) == rustversion(a, b) == gcd(a, b)
@info "Custom Julia"
@btime mygcd(a, b);
@info "C"
@btime cversion(a, b);
@info "Rust"
@btime rustversion(a, b);
Note that the line
@info mygcd(a, b) == cversion(a, b) == rustversion(a, b) == gcd(a, b)
is not mandatory, I just wanted to be sure the functions work in the same way or at least produce the same output.
I get the following output:
[ Info: true
[ Info: Custom Julia
34.763 ns (0 allocations: 0 bytes)
[ Info: C
15.764 ns (0 allocations: 0 bytes)
[ Info: Rust
15.988 ns (0 allocations: 0 bytes)
Of course, in each run, I get different results, but the ratios remain almost the same. (I turned the performance option on in my Ubuntu distribution.)
I had expected the times consumed by C and Rust to be similar but the Julia implementation is constantly more than 2x slower than the C and Rust counterparts.
Are these results normal? If so, are they okay for us? No allocations, no external libraries. What is the reason of these time differences? Is it because of slow calling of functions repeatedly in Julia?
My Julia version used here is v1.11.1
.
Thank you in advance.