Julia is roughly as fast as C

due to

  1. just-in-time compilation
  2. type inference

Define a simple function and run it twice

In [1]:
f(x) = x^3 - 2
Out[1]:
f (generic function with 1 method)
In [2]:
@time f(0.3);
@time f(0.4);
  0.010177 seconds (1.74 k allocations: 72.704 KB)
  0.000004 seconds (5 allocations: 176 bytes)

The second evaluation is 4000 times faster than the first! Why? Just-in-time compilation

In [3]:
@code_lowered(f(7.0))   # show f(x) in Julia's abstract syntax tree
Out[3]:
LambdaInfo template for f(x) at In[1]:1
:(begin 
        nothing
        return x ^ 3 - 2
    end)
In [4]:
@code_llvm f(7.0)       # show f(x) in LLVM (compiler) intermediate language
define double @julia_f_71098(double) #0 {
top:
  %1 = fmul double %0, %0
  %2 = fmul double %1, %0
  %3 = fadd double %2, -2.000000e+00
  ret double %3
}
In [5]:
@code_native f(7.0)     # show f(x) in Intel IA-64 assembly language
	.text
Filename: In[1]
	pushq	%rbp
	movq	%rsp, %rbp
Source line: 1
	movapd	%xmm0, %xmm1
	mulsd	%xmm1, %xmm1
	mulsd	%xmm0, %xmm1
	movabsq	$139733259913552, %rax  # imm = 0x7F162F520950
	addsd	(%rax), %xmm1
	movapd	%xmm1, %xmm0
	popq	%rbp
	retq
	nopw	%cs:(%rax,%rax)

Compare intermediate code for f(x) on floats and ints

In [6]:
@code_llvm(f(7.0))
define double @julia_f_71098(double) #0 {
top:
  %1 = fmul double %0, %0
  %2 = fmul double %1, %0
  %3 = fadd double %2, -2.000000e+00
  ret double %3
}
In [7]:
@code_llvm(f(7))
define i64 @julia_f_71305(i64) #0 {
top:
  %1 = mul i64 %0, %0
  %2 = mul i64 %1, %0
  %3 = add i64 %2, -2
  ret i64 %3
}

type inference: figuring out the types of untyped variables, crucial for just-in-time compilation

In [8]:
sqrt(-1)
DomainError:
sqrt will only return a complex result if called with a complex argument. Try sqrt(complex(x)).

 in sqrt(::Int64) at ./math.jl:211

Redefine f(x), iterate it, and time for comparison to C

In [9]:
# define logistic map function
f(x) = 4*x*(1-x)

# define function that, given an f, returns iterated function f^N
function iterator(f, N)
    
    # construct f^N
    function fN(x)
      for i  1:N             
        x = f(x)
      end
      x
    end    
    
    fN     # return f^N
end

# use iterator function to constuct millionth iterate of logistic map
fᴺ  = iterator(f, 1000000)
WARNING: Method definition f(Any) in module Main at In[1]:1 overwritten at In[9]:2.
Out[9]:
(::fN) (generic function with 1 method)
In [10]:
@time x = fᴺ(0.34);
@time x = fᴺ(0.67);
  0.011611 seconds (2.60 k allocations: 125.642 KB)
  0.003487 seconds (5 allocations: 176 bytes)
In [11]:
@show x;
x = 0.10116885334547539

Timing of equivalent C++ code

note: starting semicolon tells Julia to execute Unix shell code

In [12]:
; pwd
/home/gibson/professional/talks/whyjulia/notebooks
In [13]:
; cat fmillion.cpp
#include <iostream>
#include <iomanip>
#include <ctime>

using namespace std;

double f(double x) {
  return 4*x*(1-x);
}

int main(int argc, char* argv[]) {
  double x = argc > 1 ? atof(argv[1]) : 0.0;

  double t0 = clock();
  for (int n=0; n<1000000; ++n)
    x = f(x);
  double t1 = clock();

  cout << "t = " << (t1-t0)/CLOCKS_PER_SEC << " seconds" << endl;
  cout << setprecision(17);
  cout << "x = " << x << endl;
  
  return 0;
}
  
In [14]:
; g++ -O3 -o fmillion fmillion.cpp

Execution time for C++

In [15]:
; fmillion 0.67
t = 0.007285 seconds
x = 0.10116885334547539

Execution time for Julia

In [16]:
print("t=")
@time x = fᴺ(0.67);
@show x;
t=  0.011716 seconds (5 allocations: 176 bytes)
x = 0.10116885334547539
In [17]:
fᴺ(0.0)
Out[17]:
0.0

Speeds are comparable. Sometimes Julia is faster, sometimes C.

Julia benchmarks versus other languages

Julia benchmarks

http://julialang.org/benchmarks