Nice to have a good example. I can’t build the Microbenchmarks currently (see Build fails on Arch Linux: deps/scratch/OpenBLAS.v0.3.10-0.x86_64-linux-gnu-libgfortran5 missing · Issue #43 · JuliaLang/Microbenchmarks · GitHub), but from the graph it looks like parseint
is the case where Julia lags C the most. If I take out a @static
version check, the Julia benchmark is
julia> function parseintperf(t)
local n, m
for i=1:t
n = rand(UInt32)
s = string(n, base = 16)
m = UInt32(parse(Int64, s, base = 16))
@assert m == n
end
return n
end
and the C one is
long parse_int(const char *s, long base) {
long n = 0;
for (; *s; ++s) {
char c = *s;
long d = 0;
if (c >= '0' && c <= '9') d = c-'0';
else if (c >= 'A' && c <= 'Z') d = c-'A' + (int) 10;
else if (c >= 'a' && c <= 'z') d = c-'a' + (int) 10;
else exit(-1);
if (base <= d) exit(-1);
n = n*base + d;
}
return n;
}
tmin = 10.0;
for (int i=0; i<NITER; ++i) {
t = clock_now();
char s[11];
for (int k=0; k<1000 * 100; ++k) {
uint32_t n = dsfmt_gv_genrand_uint32();
sprintf(s, "%x", n);
uint32_t m = (uint32_t)parse_int(s, 16);
assert(m == n);
}
t = clock_now()-t;
if (t < tmin) tmin = t;
}
(just snipping out the relevant bits). On my machine:
julia> @benchmark parseintperf(1000)
BenchmarkTools.Trial:
memory estimate: 93.75 KiB
allocs estimate: 2000
--------------
minimum time: 94.564 μs (0.00% GC)
median time: 103.344 μs (0.00% GC)
mean time: 111.886 μs (4.97% GC)
maximum time: 3.408 ms (96.54% GC)
--------------
samples: 10000
evals/sample: 1
and if I compile just the minimum I need to run parseint
$ gcc perfint.c -o perfint
tim@diva:~/src/Microbenchmarks$ ./perfint
c,parse_integers,0.118811
Note the C version returns the minimum time; for the Julia version, the “minimum”, “median”, and “mean” times are all faster than this. So despite what the benchmarks say, the Julia version is slightly faster. Moreover, if you look carefully, the C version parses immediately to long
which is the same as Int32
. If we define
julia> function parseintperf2(t)
local n, m
for i=1:t
n = rand(UInt32)
s = string(n, base = 16)
m = parse(UInt32, s, base = 16)
@assert m == n
end
return n
end
then I get
julia> @benchmark parseintperf2(1000)
BenchmarkTools.Trial:
memory estimate: 93.75 KiB
allocs estimate: 2000
--------------
minimum time: 87.846 μs (0.00% GC)
median time: 92.150 μs (0.00% GC)
mean time: 100.223 μs (5.38% GC)
maximum time: 3.157 ms (96.51% GC)
--------------
samples: 10000
evals/sample: 1
which is a considerable advantage for the Julia version.
This just illustrates my point: Julia is capable of matching C, it just comes down to how things are written. You can write great C code and lousy Julia code, or lousy C code and great Julia code, and when the quality difference is large the victor is predictable based on quality and not language. Fundamentally Julia is capable of everything C can do, and it can do oh-so-much-more besides.
EDIT: just remembered I should add optimization flags for C.
tim@diva:~/src/Microbenchmarks$ gcc -O3 perfint.c -o perfint
tim@diva:~/src/Microbenchmarks$ ./perfint
c,parse_integers,0.089710
So C is capable of tying Julia, but not beating it.