Julia & graphic enthusiast. Interested in high performance computing, GPUs and machine learning.

Julia Workshop

1. Julia IDEs

1.1. Atom

https://atom.io/packages/language-julia

1.2. Visual Studio Code

https://github.com/JuliaEditorSupport/julia-vscode

1.3. Jupyter

https://github.com/JuliaLang/IJulia.jl

1.4. Nextjournal

you're looking at it right now!

• reproducible science in article form
• articles are interactive, fully versioned and automatically reproducible.
• supports Python, R, Clojure and Julia
# query the documentation of a function
doc(+)
# Install Packages
Base.disable_logging(Base.CoreLogging.Error) # disable warnings
# isolate code in Modules
module PlotExample
using Plots; pyplot()
function example()
plot(rand(100) / 3,reg = true,fill = (0, :green))
scatter!(rand(100), markersize = 6, c = :black)
end
end

using .PlotExample

PlotExample.example()

2. Julia in one Cell

0.9s
# Function defined for all types T
function func(a::T) where T
T
end

abstract type SuperType end

# inheritance
struct MyType <: SuperType
field::Int
end
# mutable type without inheritance but with Type Parameter
mutable struct OtherType{TypeParam}
field::TypeParam
end

# short function form
func(a::Float64, b::SuperType) = "a string"

func(b::OtherType{T}) where T = T

func(b::OtherType{Int}) = "oh hello there"

# tuple type
func(f, a::Tuple{Any, Any}) = (f(a), f(a))

@show func(1.0, MyType(1))
@show func(OtherType(1im))
@show func(OtherType(1))

# passing function
tuple = (OtherType(0), OtherType(0))
@show func(func, tuple)
# passing closure

closure = x-> x * 2
@show func(closure, (2, 3))
# the same as above, but with do syntax
result = func((2, 3)) do x
x * 2
end
@show result
# construct array
array = [OtherType(1.0), OtherType(1.0)]
# dot syntax - apply a function to all elements of the argument arrays
@show func.(array)
nothing

3. Julia Core Strengths

1) Multiple Dispatch + Introspection

2) Efficient Duck Typing

3) Efficient Higher order Functions

4) Interprocedural Optimization

5) Dynamism + Meta programing

==> Performance + High Level programing

3.1. Multiple Dispatch + Introspection

There are three core concepts to multiple dispatch:

• dispatch on all argument types
• specialize method body to argument types
• types are compile time constants
dispatch(x::Int) = 1
dispatch(x::String) = 2
dispatch(x::Complex) = 3

test() = dispatch(1) + dispatch("2") + dispatch(1im)
using InteractiveUtils
""" Helper to show the lowered code of a function"""
function show_asts(f, args...)
argtypes = typeof(args)
println("$f($args):")
println(code_typed(f, argtypes, optimize = false))
println("optimized:")
println(code_typed(f, argtypes))
println("------------------------")
return
end
show_asts(dispatch, 1)
show_asts(test)
function dispatch(x, ::Nothing)
isa(x, Int) && return 1
isa(x, String) && return 2
isa(x, Complex) && return 3
end
test(x) = dispatch(1, x) + dispatch("2", x) + dispatch(1im, x)
show_asts(dispatch, 1, nothing)
show_asts(test, nothing)
methods(dispatch)
# generic base
my_func(a, b) = a + b
my_func(a::Float32, b::Float32) = Intrinsics.fmul(a, b)

3.1.1. Enables you to:

• compiled code with static type information inside function body
• offer generic fallbacks for all kind of type combinations
• specialize to type combinations
• add new methods to existing libraries
• efficient duck typing

3.2. Duck Typing

In duck typing, an object's suitability is determined by the presence of certain methods and properties, rather than the type of the object itself.

function do_something(a)
quack(a)
end

struct Duck end
quack(::Duck) = println("quoaak")
# by overloading a function, we get quacking & hence can do_something
quack(x::Int) = println("quack")

do_something(Duck())
do_something(1)

3.2.1. Practical Examples

3.2.1.1. Algorithm Visualization
# untyped generic code: perfect to inject our own types :)
function magic(y::AbstractArray, func)
l = length(y)
k = ceil(Int, log2(l))
for j=1:k, i=2^j:2^j:min(l, 2^k)
y[i] = func(y[i-2^(j-1)], y[i])
end
for j=(k-1):-1:1, i=3*2^(j-1):2^j:min(l, 2^k)
y[i] = func(y[i-2^(j-1)], y[i])
end
y
end
X = rand(10)
Y = magic(copy(X), +)
import Base: getindex, setindex!, length, size
mutable struct AccessArray <: AbstractArray{Nothing, 1}
length::Int
history::Vector
end
function AccessArray(length, read = [], history = [])
end
length(A::AccessArray) = A.length
size(A::AccessArray) = (A.length,)
function getindex(A::AccessArray, i)
nothing
end
function setindex!(A::AccessArray, x, i)
end
import Base.+
+(a::Nothing, b::Nothing) = a
# we can now simply feed this new arrya type into magic - and record what it does
A = magic(AccessArray(8), +)
140.7s
using GeometryTypes, Makie

function render(A::AccessArray)
olast = depth = 0
for y in A.history
(any(y .≤ olast)) && (depth += 1)
olast = maximum(y)
end
maxdepth = depth
olast = depth = 0
C = []
for y in A.history
(any(y .≤ olast)) && (depth += 1)
push!(C, ((y...,), A.length, maxdepth, depth))
olast = maximum(y)
end
msize = 0.1
outsize = 0.15
x1 = Point2f0.(first.(first.(first.(C))), last.(C) .+ outsize .+ 0.05)
x2 = Point2f0.(last.(first.(first.(C))), last.(C) .+ outsize .+ 0.05)
x3 = Point2f0.(first.(last.(first.(C))), last.(C) .+ 1)
connections = Point2f0[]

yoff = Point2f0(0, msize / 2)
ooff = Point2f0(0, outsize / 2 + 0.05)
for i = 1:length(x3)
push!(connections, x3[i] .- ooff, x1[i] .+ yoff, x3[i] .- ooff, x2[i] .+ yoff)
end
node_theme = NT(
markersize = msize, strokewidth = 3,
strokecolor = :black, color = (:white, 0.0),
axis = NT(
ticks = NT(ranges = (1:8, 1:5)),
names = NT(axisnames = ("Array Index", "Depth")),
frame = NT(axis_position = :none)
)
)
s = Scene(resolution = (500, 500))
s = scatter!(s, node_theme, x1)
s = scatter!(s, node_theme, x2)
scatter!(x3, color = :white, markersize = 0.2, strokewidth = 4, strokecolor = :black)
scatter!(x3, color = :red, marker = '+', markersize = outsize)
linesegments!(connections, color = :red)
s
end
render(A)
# Check our theory:
Y - [0; Y[1:end-1];] ≈ X

3.2.1.2. Automatic Differentiation
using ForwardDiff

f(x) = x^2 + 2.0x + 3.0

val = 5
dual = ForwardDiff.Dual(val, 1)
dx = f(dual)
@show ForwardDiff.value(dx) == f(val)
@show ForwardDiff.partials(dx)[] == f_grad(val)
using BenchmarkTools
vals = fill(val, 10^6)
duals = ForwardDiff.Dual.(vals, 1)
@btime f($val) @btime f($dual)

@btime f.($vals) @btime f.($duals)
nothing

3.2.2. Enables you to

• compose functionality across packages
• low effort to extend packages
• use your own types that do extra work with generic APIs

3.3. Efficient higher order function

using BenchmarkTools
# we annotate mutating functions usually with '!'
function map1!(f::F, dest, A) where F
for i in 1:length(dest)
@inbounds dest[i] = f(A[i])
end
return dest
end
# This is usually how other languages implement higher order functions
function map2!(@nospecialize(f), dest, A)
for i in 1:length(dest)
@inbounds dest[i] = f(A[i])
end
return dest
end
# some heavy functions
@inline test1(x) = x / 10
@noinline test2(x) = test1(x)# some heavy functions
A, dest = rand(10^6), zeros(10^6);
@btime map1!($test1,$dest, $A) @btime map1!($test2, $dest,$A)
@btime map2!($test1,$dest, $A) nothing 3.3.1. Enables you to: • implement mapreduce workflows with optimal performance • compose functionality freely 3.4. Interprocedural optimization (IPO) 3.4.1. What would Python do? import Solver # Probably uses Numpy(C) + Numba import Arithmetic # Maybe CPython + Python? class MyType: ... # CPython vs Numba, # Numpy & CPython vs MyClass # some_function written in pure python? -> no specialization Solver.solve(Arithmetic.some_function, convert(MyType, data)) Julia Making a single Python algorithm fast in isolation is not that hard making those algorithms generic and interface with other packages is hard! • use cython - loose generics and most higher level constructs & needs compilation • use numba - loose substantial subset of language, no IPO • use C & Python - loose everything 😛 using Solver, Arithmetic, GPUArrays, PrecisionFloats data = convert(PrecisionFloats.Float, some_float_data) # fast higher order functions, duck typing, # constant propagation + inlining across packages Solver.solve(Arithmetic.some_function, GPUArray(data)) # Solver doesn't even need to know Arithmetic nor GPUArrays,  Julia 3.5. Dynamics str = "1 + 1" expr = Meta.parse(str) eval(expr) expr = quote 1 + 1 end # or the short form expr = :(1 + 1) dump(expr) # dump = detailed output of fields eval(Expr(:call, +, 1, 1)) macro replace_ab(expr, val) map!(expr.args, expr.args) do arg arg in (:a, :b) && return val arg end return expr end @replace_ab(a * b, "hi ") 3.5.1. Performance of dynamic constructs 3.5.1.1. Macros - no cost Macros are just a code transformation on the syntax level 3.5.1.2. Eval - scoped performance penalty using BenchmarkTools function worst(a, expr::Expr) f = eval(expr) map!(a, a) do val Base.invokelatest(f, val) end end function best(f, a) map!(f, a, a) end x = rand(10^6) expr = :((a)-> cos(a)) @btime worst($x, expr)

efunc = eval(:((a)-> cos(a)))
@btime best($efunc,$x)
nothing
test(x) = eval(x)
show_asts(test, :(1 + 1))

3.5.2. Generated Functions

@generated function test(a, b)
# Println is not really allowed here - Core.println works though
Core.println(a, " ", b)
return :() # Need to return an Expr
end
test(1, 2)
@generated function test(a, b)
Core.println(a, " ", b)
return :(a + b) # Need to return an Expr
end
test(1, 2)
test(1, 2)
expressions = []
@generated function map_unrolled(f, arg::NTuple{N, Any}) where N
args = map(1:N) do i
:(f(arg[\$i]))
end
expr = Expr(:tuple, args...)
push!(expressions, expr) # Core.println is really ugly for Expr
expr
end
map_unrolled(sin, (1.0, 2.0, 3.0))
expressions