Live data from Hacker News

Comparing Pythagorean triples in C++, D, and Rust

atilanevesoncode.wordpress.com

31–40 of 118 posts

Re: Comparing Pythagorean triples in C++, D, and Rust

#31

The C++ version is taking a compile and runtime short cut by using printf instead of iostream. This pull request has timings with this changed: https://github.com/atilaneves/pythagoras/pull/1#issuecomment...

It's valid C++ and requires no external dependencies.

Re: Comparing Pythagorean triples in C++, D, and Rust

#32
post #28

Earlier quoted context omitted.

I think the problem is flushing, not locking. Rust println!() flushes after each call, whereas more C-like io libraries flush once on program exit. Big difference in the number of system calls.

EDIT: THIS IS ALL WRONG AND BACKWARDS, SEE BELOW -------------------------- > Rust println!() flushes after each call, To be clear, println! does not flush after each call. Many terminals are line-buffered, and so when println! emits a \n, the terminal will flush. So, println! will probably cause a flush , but does not flush itself. println! is the exact same as `print!`, except for the additional printing of \n http…

Are you sure about this? AFAIK, println! uses io::stdout(), which is defined like so:

    pub struct Stdout {
        inner: Arc>>>>,
    }
In particular, it's specifically using LineWriter internally, which does flush whenever it sees a `\n`.

println! does not itself explicitly flush, but its current implementation does guarantee that it will. However, I don't think this is an API guarantee. In the definition of Stdout, there is this FIXME:

    // FIXME: this should be LineWriter or BufWriter depending on the state of
    //        stdout (tty or not). Note that if this is not line buffered it
    //        should also flush-on-panic or some form of flush-on-abort.
Which basically means that someone thought its buffering strategy should be determine by whether or not stdout is connected to a tty. This is what ripgrep does for example, but I had to build out my own infrastructure to do it: https://docs.rs/grep-cli/0.1.1/grep_cli/#coloring-and-buffer... --- Arguably, io::stdout() should do the same.

I don't think buffer configuration at the level of the terminal plays a role here. This is somewhat confused by programs like stdbuf[1] which purport to enable users to change the buffering strategy used by a program in their shell. But in reality, it appears tightly coupled with C's FILE data type and perhaps even tightly coupled with glibc itself? I'm not sure.

[1] - https://www.gnu.org/software/coreutils/manual/html_node/stdb...

Re: Comparing Pythagorean triples in C++, D, and Rust

#34

Earlier quoted context omitted.

EDIT: THIS IS ALL WRONG AND BACKWARDS, SEE BELOW -------------------------- > Rust println!() flushes after each call, To be clear, println! does not flush after each call. Many terminals are line-buffered, and so when println! emits a \n, the terminal will flush. So, println! will probably cause a flush , but does not flush itself. println! is the exact same as `print!`, except for the additional printing of \n http…

Are you sure about this? AFAIK, println! uses io::stdout(), which is defined like so: pub struct Stdout { inner: Arc >>>>, } In particular, it's specifically using LineWriter internally, which does flush whenever it sees a `\n`. println! does not itself explicitly flush, but its current implementation does guarantee that it will. However, I don't think this is an API guarantee. In the definition of Stdout, there is t…

Ugh, you're right. I got it backwards https://github.com/rust-lang/rust/issues/23818

thank you

Re: Comparing Pythagorean triples in C++, D, and Rust

#36
post #27

Earlier quoted context omitted.

> But I also note that the original version of your benchmark was calling `println!`, which means that you were essentially benchmarking high-level, thread-safe I/O. I don't think so. The program is only doing a thousand prints, which is far less than the total number of loop iterations. Try changing to locked stdout and writeln!---I doubt you'll notice a difference.

Yeah, I tried the locking version and didn't see a noticeable improvement.

Thank you for checking this, and determining it didn't actually apply in this case.

We do a lot of I/O intensive Rust at work, and I have a list of things to double-check when benchmarking. The most common slowdowns: debug builds, unlocked stdio, line iterators, and anything else which allocates in an inner loop.

If I can hit 250 MB/s throughput per core on my laptop, with some light data processing, I'm usually quite happy.

Re: Comparing Pythagorean triples in C++, D, and Rust

#37

If there are big differences in where you don't expect them (e.g. printf in a for loop being very slow), then it usually is a difference in implementation and possibly contract. E.g. I wouldn't be surprised if println! in Rust is synchronous, which tends to be slow on both terminals and redirections (i.e. file I/O). For these simple examples an strace may be enlightening in this regard. Edit: Indeed this seems to be…

Might be interesting if all the tests used the same library for output. After all both D and Rust can use C libraries.

Re: Comparing Pythagorean triples in C++, D, and Rust

#38
post #9
post #3

I would be really interested to know where the overhead in the simple rust version comes from. At first I thought it might be the println() being less efficient for whatever reason, but even without that line this[1] takes 300ms, compared to 184ms for this[2] (with the printf!). 1. https://raw.githubusercontent.com/atilaneves/pythagoras/mast... 2. https://raw.githubusercontent.com/atilaneves/pythagoras/mast...

Someone found a single character change providing a 2x speedup: https://www.reddit.com/r/rust/comments/ab7hsi/comparing_pyth...

To quote that reddit post, which I think has some good interesting details about why:

“I believe the problem with ..= is that x..(y + 1) is not identical with x..=y. They are nearly equivalent on most values for x and y, except when y == i32::max(). At that point, x..=y should still be able to function, while x..(y + 1) is allowed to do nothing.”

Re: Comparing Pythagorean triples in C++, D, and Rust

#39
Those of us who have compute-bound workloads (scientific simulations in my case, but this often comes up in gamedev and elsewhere) often complain about how unusably slow debug builds impede our workflow in C++. One fairly common solution to this in extreme cases is sticking to a mostly C-like subset of the language or just using C. I don't have much experience with Rust, but judging by the results here, debug builds in Rust are effectively useless, even compared to C++. How do Rust programmers deal with this? Do you just give up and resort to printf debugging, or are there ways to improve the situation?

Re: Comparing Pythagorean triples in C++, D, and Rust

#40

Those of us who have compute-bound workloads (scientific simulations in my case, but this often comes up in gamedev and elsewhere) often complain about how unusably slow debug builds impede our workflow in C++. One fairly common solution to this in extreme cases is sticking to a mostly C-like subset of the language or just using C. I don't have much experience with Rust, but judging by the results here, debug builds…

This is going to sound unbelievable, but I don't really ever do runtime debugging of my Rust code. Nearly all my debugging happens at compile-time (which is faster to iterate with thanks to cargo-check skipping codegen and linking). That said, I do know some people with large Rust codebases who do value the ability to run debug builds, and the slowness of -O0 binaries is a sore spot for them. As with C, you can try -O1 for a compromise between runtime and compiletime.
Post reply on HN