I've just heard something so crazy, I've had to test it myself (resulting in this post).


The problem

Below, you can find 3 snippets of code that all loop 2^24 times printing i:

First, classic cpp:

1
2
3
4
5
6
7
8
#include <iostream>
#include <cmath>
int main() {
	for (int i = 0; i < std::pow(2, 24); i++) {
		std::cout << i << std::endl;
	}
	return 0;
}

Secondly, only 6 years younger - but 6 lines more compact, python:

1
2
for n in range(2 ** 24):
    print(n)

Lastly, rust (should've heard about this 7 years ago, would make for a great finish):

1
2
3
4
5
fn main() {
	for n in 0..(2_i32.pow(24)) {
		print!("{}\n", n);
	}
}

In a vacuum, assuming I'm not playing any tricks to deceive you with your guess on purpose - which of these would be the fastest?

You can make a guess for yourself, and if you scroll down you can see the result:









































Wait a second... this graph feels wrong. Have I labeled it wrong?

I write a lot of python, so often I hear jokes how slow it is compared to other languages - and here, it is faster? Almost 2x the speed of rust, almost 3x the speed of cpp?!

But no, this isn't a mistake.

In reality, I have a second graph to present before explaining - with a curious green solution:

So yes - in the end, there is a trick.


The trick

I have written some cpp code, so even though I'm no expert, I know what an std::endl is - just a nice wrapper over \n on unix, \r\n on windows.

Right?

Well, if you read the documentation,

Inserts a newline character into the output sequence os...

...so far so good...

...and flushes it as if by calling os.put(os.widen('\n')) followed by os.flush().

...no, no, no no no No No NO! We were this close to greatness!

It doesn't even handle \r\n?! I was so sure of it!
And the main event, os.flush() on every call?
Do the cpp std developers think os.flush() calls grow on trees?

In all seriousness, I always heard that std::endl is better, cleaner, more compatible, and I've made assumptions - of which I was so sure, this surprised me greatly.

And you can even read, futher down, that:

Use of std::endl in place of '\n', encouraged by some sources, may significantly degrade output performance.

I always prefered to use "\n" in it's place, so if we do just that:

we can clearly see how cpp takes the crown in the end.


Epilogue

There's no great conclusions to this, except that you really should read the documentation and not make assumptions that solutions which seem generalised, are so for any case.

Also, I really couldn't believe rust was slower - so here's a revised implementation which too skips frequent flushing:

1
2
3
4
5
6
7
8
use std::io::{self, BufWriter, Write};

fn main() {
	let mut out = BufWriter::new(io::stdout().lock());
	for n in 0..(2_i32.pow(24)) {
		write!(out, "{}\n", n).unwrap();
	}
}

And the final graph:

So in the end we can sleep easy, knowing rust is still faster.

Defined below commands were used to compile and or run the programs:

  • rs: rustc -O -o out main.rs && ./out >/dev/null
  • py: python3.13 main.py >/dev/null
  • cpp: g++ -std=c++20 -O3 -o out main.cpp && ./out >/dev/null

Programs were each measured a 100 times, with worst 10 and best 10 times discarded - and the final time being the average of those remaining.

Specifically:

Language (approach) avg. Time (s)
Python (+flush) 9.759
Python 2.515
Rust (println!) 4.734
Rust (write!) 0.179
Cpp (endl) 6.228
Cpp (nl) 1.461

Python with explicit flushing isn't on the graph, as that's both an out of the way and worse approach

And we want to make our code better, not worse

Another funny addition: I hear a lot of complaining about compile times of rust;

But, if you time the compilation, cpp and rust both take ~0.4s.

And while rust takes a little under 0.1s on subsequent recompilations, cpp always takes 0.3-0.35s to compile.