Julia language demo

Tony Kelman and Kyle Barbary, Feb 24th 2016

http://julialang.org, https://github.com/tkelman, https://github.com/kbarbary

2-language problem: performance OR productivity

  • C, C++, or Java for performance
  • Python, R, or Matlab for productivity

Julia: new (but familiar) language, best of both worlds

Microbenchmark performance, time relative to C

Intro syntax example - Dual Numbers

Of the form $a + b ε$, similar to complex numbers but with $ε² = 0$ instead of $i² = -1$


In [1]:
# type => by-reference semantics,
# immutable => by-value semantics
immutable DualNumber{T}
    value::T
    epsilon::T
end
# type parameter {T} constrains typeof(value) == typeof(epsilon)

In [2]:
# import addition and multiplication from Base to extend them with new methods
import Base: +, *
# one-liner function definition:
+(x::DualNumber, y::DualNumber) = DualNumber(x.value + y.value, x.epsilon + y.epsilon)
# longer form function definition:
function *(x::DualNumber, y::DualNumber)
    return DualNumber(x.value * y.value, x.value * y.epsilon + y.value * x.epsilon)
end


Out[2]:
* (generic function with 139 methods)

In [3]:
# compare LLVM and native code generated for DualNumber{Int64} vs DualNumber{Float64}
x_i64 = DualNumber(1, 2)


Out[3]:
DualNumber{Int64}(1,2)

In [4]:
y_i64 = DualNumber(3, 4)


Out[4]:
DualNumber{Int64}(3,4)

In [5]:
@code_llvm x_i64 * y_i64


define void @"julia_*_1488"(%DualNumber* sret, %DualNumber*, %DualNumber*) {
top:
  %3 = load %DualNumber* %1, align 8
  %4 = extractvalue %DualNumber %3, 0
  %5 = extractvalue %DualNumber %3, 1
  %6 = load %DualNumber* %2, align 8
  %7 = extractvalue %DualNumber %6, 0
  %8 = extractvalue %DualNumber %6, 1
  %9 = mul i64 %7, %4
  %10 = insertvalue %DualNumber undef, i64 %9, 0
  %11 = mul i64 %8, %4
  %12 = mul i64 %7, %5
  %13 = add i64 %11, %12
  %14 = insertvalue %DualNumber %10, i64 %13, 1
  store %DualNumber %14, %DualNumber* %0, align 8
  ret void
}

In [6]:
@code_native x_i64 * y_i64


	.text
Filename: In[2]
Source line: 7
	pushq	%rbp
	movq	%rsp, %rbp
Source line: 7
	movq	(%rdx), %r9
	movq	8(%r8), %r10
Source line: 7
	imulq	%r9, %r10
Source line: 7
	movq	(%r8), %rax
	movq	8(%rdx), %rdx
Source line: 7
	imulq	%rax, %rdx
	imulq	%r9, %rax
	movq	%rax, (%rcx)
	addq	%r10, %rdx
	movq	%rdx, 8(%rcx)
	movq	%rcx, %rax
	popq	%rbp
	ret

In [7]:
x_f64 = DualNumber(1.0, 2.0)


Out[7]:
DualNumber{Float64}(1.0,2.0)

In [8]:
y_f64 = DualNumber(3.0, 4.0)


Out[8]:
DualNumber{Float64}(3.0,4.0)

In [9]:
@code_llvm x_f64 * y_f64


define void @"julia_*_1598"(%DualNumber.8* sret, %DualNumber.8*, %DualNumber.8*) {
top:
  %3 = load %DualNumber.8* %1, align 8
  %4 = extractvalue %DualNumber.8 %3, 0
  %5 = extractvalue %DualNumber.8 %3, 1
  %6 = load %DualNumber.8* %2, align 8
  %7 = extractvalue %DualNumber.8 %6, 0
  %8 = extractvalue %DualNumber.8 %6, 1
  %9 = fmul double %4, %7
  %10 = insertvalue %DualNumber.8 undef, double %9, 0
  %11 = fmul double %4, %8
  %12 = fmul double %5, %7
  %13 = fadd double %11, %12
  %14 = insertvalue %DualNumber.8 %10, double %13, 1
  store %DualNumber.8 %14, %DualNumber.8* %0, align 8
  ret void
}

In [10]:
@code_native x_f64 * y_f64


	.text
Filename: In[2]
Source line: 7
	pushq	%rbp
	movq	%rsp, %rbp
Source line: 7
	movsd	(%rdx), %xmm1
	movsd	8(%r8), %xmm0
Source line: 7
	mulsd	%xmm1, %xmm0
Source line: 7
	movsd	(%r8), %xmm2
Source line: 7
	mulsd	%xmm2, %xmm1
	mulsd	8(%rdx), %xmm2
	movsd	%xmm1, (%rcx)
	addsd	%xmm0, %xmm2
	movsd	%xmm2, 8(%rcx)
	movq	%rcx, %rax
	popq	%rbp
	ret

In [11]:
# custom printing for a type can be achieved by extending the `show` function:
function Base.show(io::IO, x::DualNumber)
    print(io, string(x.value, "+", x.epsilon, "ε"))
end
DualNumber(1, 2)


Out[11]:
1+2ε

In [12]:
# To use DualNumber in linear algebra operations, just need to define `zero` value
Base.zero{T}(::Type{DualNumber{T}}) = DualNumber(zero(T), zero(T))


Out[12]:
zero (generic function with 14 methods)

In [13]:
values1 = rand(1:10, 6, 4);
epsilon1 = rand(1:10, 6, 4);
dualmat1 = map(DualNumber, values1, epsilon1)


Out[13]:
6x4 Array{DualNumber{Int64},2}:
 1+10ε  6+7ε   3+8ε   3+5ε 
 7+2ε   5+1ε   6+9ε   8+8ε 
 1+3ε   1+2ε   6+5ε   5+10ε
 1+9ε   10+7ε  3+3ε   6+2ε 
 2+10ε  5+6ε   10+8ε  1+2ε 
 8+7ε   9+2ε   6+1ε   9+9ε 

In [14]:
values2 = rand(1:10, 4, 3);
epsilon2 = rand(1:10, 4, 3);
dualmat2 = map(DualNumber, values2, epsilon2)


Out[14]:
4x3 Array{DualNumber{Int64},2}:
 4+4ε   2+7ε  3+3ε
 10+9ε  3+1ε  9+9ε
 3+2ε   7+7ε  8+4ε
 6+9ε   4+9ε  5+4ε

In [15]:
# matrix multiplication
dualmat1 * dualmat2


Out[15]:
6x3 Array{DualNumber{Int64},2}:
 91+255ε   53+178ε   96+263ε 
 144+250ε  103+270ε  154+249ε
 62+177ε   67+182ε   85+173ε 
 149+281ε  77+160ε   147+253ε
 94+218ε   93+200ε   136+253ε
 194+311ε  121+251ε  198+257ε

In [16]:
# elementwise multiplication
dualmat1 .* dualmat1


Out[16]:
6x4 Array{DualNumber{Int64},2}:
 1+20ε    36+84ε    9+48ε     9+30ε  
 49+28ε   25+10ε    36+108ε   64+128ε
 1+6ε     1+4ε      36+60ε    25+100ε
 1+18ε    100+140ε  9+18ε     36+24ε 
 4+40ε    25+60ε    100+160ε  1+4ε   
 64+112ε  81+36ε    36+12ε    81+162ε

In [17]:
# calling into C libraries
ccall(:sin, Float64, (Float64,), 1.0)


Out[17]:
0.8414709848078965

In [18]:
buffer = Array(UInt8, 25)
ccall(:sprintf, Void, (Ptr{UInt8}, Ptr{UInt8}, Ptr{UInt8}), buffer, "%s\n", "Hello from C sprintf!")
len = ccall(:strlen, Cint, (Ptr{UInt8},), buffer)
bytestring(pointer(buffer), len)


Out[18]:
"Hello from C sprintf!\n"

In [19]:
# calling into Python libraries - https://github.com/stevengj/PyCall.jl
Pkg.add("PyCall")
using PyCall
@pyimport scipy.optimize as so
# (see http://www.juliaopt.org for Julia optimization packages)
so.newton(x -> cos(x) - 3x, 1)


INFO: Nothing to be done
Out[19]:
0.31675082877125116

In [21]:
# access, and modify, the syntax tree of code as a data structure
ex = :(cos(x) - 3x)
dump(ex)


Expr 
  head: Symbol call
  args: Array(Any,(3,))
    1: Symbol -
    2: Expr 
      head: Symbol call
      args: Array(Any,(2,))
        1: Symbol cos
        2: Symbol x
      typ: Any
    3: Expr 
      head: Symbol call
      args: Array(Any,(3,))
        1: Symbol *
        2: Int64 3
        3: Symbol x
      typ: Any
  typ: Any

In [6]:
# macros begin with @, take expression structure of input,
# modify and return a different expression to execute
ex2 = copy(ex);
ex2.args[1] = :*
show(ex2)


:(cos(x) * (3x))

In [23]:
macro multiply(foo)
    foo.args[1] = :*
    return foo
end
@multiply cos(1) - 3


Out[23]:
1.6209069176044193

In [24]:
cos(1) - 3


Out[24]:
-2.4596976941318602

In [25]:
cos(1) * 3


Out[25]:
1.6209069176044193

In [ ]: