My rust code takes twice as long as my C++ one

87 points jorgesgk

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