Performance — Haskell vs Rust Link to heading

In the previous article, we wrote a simple Haskell program that reverses characters within each word. We also wrote the same program in Rust for comparison. In terms of readability and conciseness, Haskell was the clear winner, but what about the performance? In this article, we want to have a look at how the performance compares between the two.

Haskell Implementation Link to heading

As a recap, below is the Haskell implementation of the program that reads in text, reverses characters in each word, and prints out.

-- reverse.hs
main = do   
    line <- getLine  
    if null line  
        then return ()  
    else do  
        putStrLn (reverseWords line)
        main  
  
reverseWords :: String -> String  
reverseWords = unwords . map reverse . words

Let’s compile with optimization and run on ~6MB text file.

$ ghc -O2 reverse
$ time ./reverse < input.txt > output1.txt
real 0m0.265s
user 0m0.236s
sys 0m0.027s

Rust Implementation Link to heading

Now, let’s do the same thing in Rust.

// reverse.rs

use std::io::{stdin, Result};

fn main() -> Result<()> {
    let input = stdin();
    let iter = input.lines().map(|line| line.map(reverse_words));
    for line in iter {
        println!("{}", line?);
    }
    Ok(())
}

fn reverse_words(line: String) -> String {
    line.split_whitespace()
        .map(|word| word.chars().rev().collect::<String>())
        .collect::<Vec<_>>()
        .join(" ")
}

Let’s compile with optimization and run

$ rustc -C opt-level=3 -o reverse_rs reverse.rs
$ time ./reverse_rs < input.txt > output
real 0m0.327s
user 0m0.108s
sys 0m0.219s

Hmm, somehow, Rust version runs much slower than Haskell. But wait, there is something peculiar with this. The system time is much faster with Haskell while user time much faster with Rust. Typically, user time is what measure the actual time spent within the user-written program, while system time typically is IO operations.

It turns out that writing each line directly to stdout() in Rust is very slow, taking up a lot of system time. One solution is to use BufWriter instead.

--- before
+++ after
@@ -1,10 +1,11 @@
-use std::io::{stdin, Result};
+use std::io::{stdin, stdout, BufWriter, Result, Write};

 fn main() -> Result<()> {
     let input = stdin();
     let iter = input.lines().map(|line| line.map(reverse_words));
+    let mut output = BufWriter::new(stdout());
     for line in iter {
-        println!("{}", line?);
+        writeln!(output, "{}", line?)?;
     }
     Ok(())
 }

With the change, let’s re-compile and re-run the program

$ rustc -C opt-level=3 -o reverse_rs reverse.rs
$ time ./reverse_rs < input.txt > output
real 0m0.093s
user 0m0.071s
sys 0m0.021s

Below chart summarizes our finding. The same program written in Rust runs ~2.5x faster than the same program written in Haskell.

Performance Chart

Conclusion Link to heading

Obviously, this is just a single data point, and we shouldn’t conclude all programs written in Rust are always faster than Haskell. However, this result is in ballpark with other benchmark results, such as Benchmarks Game.

Benchmarks Game

My conclusion is that though Haskell may not replace C/C++/Rust in high-performance applications, it still can be a good alternative in place of other slower but garbage-collected languages, such as C#, Java, or Go.