My rust code takes twice as long as my C++ one
I have decided, after reading lots of benchmarks and essays about Rust's performance, and seeing how the main Rust devs not only care about safety, but also performance, to build my own benchmarks to check how Rust performed vs. C++:
This is my Rust code:
use std::io;
pub fn half(mut num: i64) -> i64 {
if num > 1 {
num = half(num / 2);
}
num
}
pub fn main() {
let mut j: i64 = 0;
let mut lowerbound = String::new();
io::stdin().read_line(&mut lowerbound).expect("Error reading the line");
for i in 0..=lowerbound.trim().parse().expect("Please type in a valid number"){
j = j + half(i);
}
print!("The result is {}", j);
}
And this, my C++ code:
#include <iostream>
using namespace std;
long long int half(long long int num)
{
if (num > 1)
{
num = half(num / 2);
}
return num;
}
int main()
{
long long int j = 0;
long long int lowerbound = 0;
std::cin >> lowerbound;
for (long long int i = 0; i <= lowerbound; i++)
{
j = j + half(i);
};
cout << "The result is " << j;
}
Both should be fairly similar. Even if there was some overhead on the rust's line reading function, which I'm not sure of (I'd expect .expect() to introduce some overhead), the difference is just too big to make any sense to me:
$ clang++ -stdlib=libstdc++ -O3 C++.cpp
$ time ./c++
9999999999
The result is 9999999999
real 3m1,687s
user 2m58,392s
sys 0m0,012s
$ rustc -C opt-level=3 rust.rs
$ time ./rust
9999999999
The result is 9999999999
real 5m8,307s
user 5m4,601s
sys 0m0,015s
Even if the issue was the line reading code, if I try again without it, the difference still stands:
#include <iostream>
using namespace std;
long long int half(long long int index)
{
if (index > 1)
{
index = half(index / 2);
}
return index;
}
int main()
{
long long int j = 0;
for (long long int i = 0; i <= 9999999999; i++)
{
j = j + half(i);
};
cout << "The result is " << j;
}
$ time ./c++
The result is 9999999999
real 3m0,716s
user 2m59,783s
sys 0m0,007s
pub fn half(mut num: i64) -> i64 {
if num > 1 {
num = half(num / 2);
}
num
}
pub fn main() {
let mut j: i64 = 0;
for i in 0..=9999999999 {
j = j + half(i);
}
print!("The result is {}", j);
}
$ time ./rust
The result is 9999999999
real 5m15,243s
user 5m13,538s
sys 0m0,045s
In the case above, both pieces of code should be similar, and I would expect to reach similar levels of performance.
These are my specs, and the compiler versions I'm using so far:
$ neofetch
jorge@Raoden
------------
OS: Nobara Linux 39 (KDE Plasma) x86_64
Host: MS-7C37 3.0
Kernel: 6.8.12-200.fsync.fc39.x86_64
Uptime: 14 days, 16 hours, 26 mins
Packages: 8272 (rpm), 979 (flatpak)
Shell: bash 5.2.26
Resolution: 1920x1080
DE: GNOME 45.5
WM: Mutter
WM Theme: Adwaita
Theme: adw-gtk3 [GTK2/3]
Icons: Yaru [GTK2/3]
Terminal: BlackBox
CPU: AMD Ryzen 7 3700X (16) @ 3.600GHz
GPU: AMD ATI Radeon RX 6700/6700 XT/6750 XT / 6800M/6850M XT
Memory: 6785MiB / 15896MiB
$ clang -v
clang version 17.0.6 (Fedora 17.0.6-2.fc39)
Target: x86_64-redhat-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-redhat-linux/13
Selected GCC installation: /usr/bin/../lib/gcc/x86_64-redhat-linux/13
Candidate multilib: .;@m64
Candidate multilib: 32;@m32
Selected multilib: .;@m64
Found HIP installation: /opt/rocm, version 6.0.32831
$ rustc --version
rustc 1.76.0 (07dca489a 2024-02-04)
Is there anything wrong with my rust code?
Edit:
Indeed, as /u/CryZe92 pointed out, the issue is coming from inclusive ranges. When using exclusive ones, the time it took got down to 16s each (and both at pretty similar levels). I decided to try a larger number to see the difference, and both are definitely in the same ballpark:
#include <iostream>
using namespace std;
long long int half(long long int index)
{
if (index > 1)
{
index = half(index / 2);
}
return index;
}
int main()
{
long long int j = 0;
for (long long int i = 0; i < 10000000000; i++)
{
j = j + half(i);
};
cout << "The result is " << j;
}
$ time ./c++
The result is 9999999999
real 2m59,943s
user 2m58,978s
sys 0m0,032s
pub fn half(mut num: i64) -> i64 {
if num > 1 {
num = half(num / 2);
}
num
}
pub fn main() {
let mut j: i64 = 0;
for i in 0..10000000000 {
j = j + half(i);
}
print!("The result is {}", j);
}
$ time ./rust
The result is 9999999999
real 2m59,638s
user 2m58,664s
sys 0m0,020s