Computing cryptography hashes: Rust, F#, D and Scala
Let's compare how fast Rust, D and F# (.NET actually) at computing cryptography hashes, namely MD5, SHA1, SHA256 and SHA512. We're going to use rust-crypto cargo:
Results:
Now the F# code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extern crate crypto; | |
extern crate time; | |
use crypto::md5::Md5; | |
use crypto::sha1::Sha1; | |
use crypto::sha2::{Sha256, Sha512}; | |
use crypto::digest::Digest; | |
use time::now; | |
fn bench<T: Digest>(name: &str, digest: &mut T, input: &Vec<u8>) { | |
let started = time::now(); | |
for _ in 1..100 { | |
digest.input(&input); | |
digest.reset(); | |
} | |
let elapsed = time::now() - started; | |
println!("{:?} elapled {}", name, elapsed); | |
} | |
fn main() { | |
let input = vec![0u8; 10000000]; | |
bench("MD5", &mut Md5::new(), &input); | |
bench("SHA1", &mut Sha1::new(), &input); | |
bench("SHA256", &mut Sha256::new(), &input); | |
bench("SHA512", &mut Sha512::new(), &input); | |
} |
- MD5 - 3.39s
- SHA1 - 2.89s
- SHA256 - 6.97s
- SHA512 - 4.47s
Now the F# code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open System.Security.Cryptography | |
open System.Diagnostics | |
open System | |
let bench (input: byte[]) (digest: HashAlgorithm) = | |
let sw = Stopwatch.StartNew() | |
for _ in 1..100 do | |
digest.ComputeHash input |> ignore // |> M.toHex |> printfn "%s" | |
sw.Stop() | |
printfn "%s elapled %O" (digest.GetType().Name) sw.Elapsed | |
let input = Array.zeroCreate<byte> 10000000 | |
[ new MD5CryptoServiceProvider() :> HashAlgorithm | |
new SHA1CryptoServiceProvider() :> _ | |
SHA256.Create() :> _ | |
new SHA256CryptoServiceProvider() :> _ | |
new SHA256Cng() :> _ | |
SHA512.Create() :> _ | |
new SHA512CryptoServiceProvider() :> _ | |
new SHA512Cng() :> _ ] | |
|> List.iter (bench input) | |
Results (.NET 4.5, VS 2013, F# 3.1):
DMD
LDC2
- MD5CryptoServiceProvider - 2.32s (32% faster)
- SHA1CryptoServiceProvider - 2.92s (1% slower)
- SHA256Managed - 16.50s (236% slower)
- SHA256CryptoServiceProvider - 11.50s (164% slower)
- SHA256Cng - 11.71s (168% slower)
- SHA512Managed - 61.04s (1365% slower)
- SHA512CryptoServiceProvider - 21.88s (489% slower)
- SHA512Cng - 22.19s (496% slower)
(.NET 4.6, VS 2015, F# 4.0):
D:
- MD5CryptoServiceProvider elapled 2.55
- SHA1CryptoServiceProvider elapled 2.89
- SHA256Managed elapled 17.01
- SHA256CryptoServiceProvider elapled 8.74
- SHA256Cng elapled 8.75
- SHA512Managed elapled 23.42
- SHA512CryptoServiceProvider 5.81
- SHA512Cng elapled 5.79
D:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module main; | |
import std.stdio; | |
import std.digest.md; | |
import std.digest.sha; | |
import std.datetime; | |
void bench(T)(string name, T digest, const ubyte[] input) { | |
auto sw = StopWatch(AutoStart.yes); | |
for (auto i = 0; i < 100; i++) { | |
digest.put(input); | |
auto hash = digest.finish(); | |
} | |
sw.stop(); | |
writefln("%s - %s ms elapsed.", name, sw.peek.msecs); | |
} | |
void main(string[] args) | |
{ | |
auto input = new const ubyte[10_000_000]; | |
MD5 md5; bench("MD5", md5, input); | |
SHA1 sha1; bench("SHA1", sha1, input); | |
SHA256 sha256; bench("SHA256", sha256, input); | |
SHA512 sha512; bench("SHA512", sha512, input); | |
stdin.readln(); | |
} |
- MD5 - 16.05s (470% slower)
- SHA1 - 2.35s (19% faster)
- SHA256 - 47.96s (690% slower (!))
- SHA512 - 61.47s (1375% slower (!))
- MD5 - 2,18s (55% faster)
- SHA1 - 2.88s (same)
- SHA256 - 6,79s (3% faster)
- SHA512 - 4,6s (3% slower)
GDC
Scala:
Interesting things:
- MD5 - 2,43 (29% faster)
- SHA1 - 2,84 (2% faster)
- SHA256 - 12,62 (45% slower)
- SHA512 - 8,56 (48% slower)
Scala:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object Util { | |
def time[A](name: String) (f: => A) = { | |
val s = System.nanoTime | |
val ret = f | |
println(s"$name elapsed ${(System.nanoTime-s)/1e6} ms") | |
ret | |
} | |
} | |
private object Bench { | |
def bench(input: Array[Byte]) (digestName: String) = { | |
import java.security.MessageDigest | |
val digest = MessageDigest.getInstance(digestName) | |
Util.time(digest.getAlgorithm) { | |
(1 to 100).foreach(_ => digest.digest(input)) | |
} | |
} | |
} | |
object Main { | |
def main(args: Array[String]) = { | |
val input = new Array[Byte](10000000) | |
Seq("MD5", "SHA1", "SHA-256", "SHA-512") | |
.foreach { Bench.bench (input) } | |
} | |
} |
- MD5 - 4.2s (23% slower)
- SHA1 - 6.09s (110% slower)
- SHA256 - 9.96s (42% slower)
- SHA512 - 7.32s (63% slower)
- Rust and D (LDC2) show very close results. D is significantly faster on MD5, so it's the winner!
- D (DMD) has very bad performance on all algorithms, except SHA1, where it's won.
- SHA512Managed .NET class is very slow. Do not use it.
Comments
bench (new SHA256CryptoServiceProvider()) input
bench (new SHA512CryptoServiceProvider()) input
It probably works only on Windows, but it gives a huge performance improvement (nearly on par with rust).
So some D users use gdc(gcc)/ldc(llvm) for such cases, e.g. cryptography, machine learning, statistics and etc.
I tested your code and dmd / ldc results on my MBP are below:
dmd 2.067:
MD5 - 8363 ms elapsed.
SHA1 - 16611 ms elapsed.
SHA256 - 35451 ms elapsed.
SHA512 - 24743 ms elapsed.
ldc2-0.15.2-beta1:
MD5 - 1970 ms elapsed.
SHA1 - 1676 ms elapsed.
SHA256 - 4273 ms elapsed.
SHA512 - 2964 ms elapsed.
ldc's performance is quite similar to Rust :)
using LDC for D I had D come out faster in all four.
Could you please retry with LDC? dmd is just a reference implementation(ala cpython) with a very old, poorly optimizing backend based on Zortech/Digital Mars C++.
Suggested flags:
ldc -O5 -release
Bye,
d:\ldc2-0.15.2-beta1-win64-msvc\bin\ldc2.exe -O5 -release main.d
OPTLINK (R) for Win32 Release 8.00.17
Copyright (C) Digital Mars 1989-2013 All rights reserved.
http://www.digitalmars.com/ctg/optlink.html
OPTLINK : Warning 9: Unknown Option : NXCOMPAT
OPTLINK : Warning 9: Unknown Option : DYNAMICBASE
OPTLINK : Warning 9: Unknown Option : OUT
OPTLINK : Error 8: Illegal Filename
/NOLOGO /NXCOMPAT /DYNAMICBASE /LARGEADDRESSAWARE /OPT:REF /OPT:ICF /OUT:main.exe main.obj "/LIBPATH
:d:\ldc2-0.15.2-beta1-win64-msvc\bin/../lib" phobos2-ldc.lib druntime-ldc.lib kernel32.lib user32.li
b gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib
^
Error: d:\DMD\dmd2\windows\bin\link.exe failed with status: 1
__________________________
What am I doing wrong?
Looks like after the LDC inclusion, D wins 3 out of 4 times.
It would be interesting to see Bouncy Castle for both .NET and JVM implementations.
Read more