Kurz: Beim ersten Aufruf einer Funktion mit bestimmten Argumenttypen kompiliert Julia sie zu Maschinencode (JIT). Läuft sie danach in einer Schleife, ist sie so schnell wie C. Voraussetzung ist Typstabilität.
Teil des Kurses Julia
Warum ist Julia schnell?
Beim ersten Aufruf einer Funktion mit bestimmten Argumenttypen kompiliert Julia sie zu Maschinencode (JIT). Läuft sie danach in einer Schleife, ist sie so schnell wie C. Voraussetzung ist Typstabilität: Der Typ jeder Variablen soll sich aus den Argumenttypen ergeben und nicht während der Funktion wechseln.
function summe_schleife(v)
s = 0.0
for x in v
s += x
end
return s
end
function summe_instabil(v)
s = 0 # Int – wird beim ersten += zu Float64: Typ wechselt!
for x in v
s += x
end
return s
end
v = rand(1_000_000)
println(isapprox(summe_schleife(v), sum(v)))
println(isapprox(summe_instabil(v), sum(v)))
t = @elapsed for _ in 1:20 summe_schleife(v) end
println(t < 5.0 ? "schnell genug" : "langsam")
# Typinformation prüfen
println(Base.return_types(summe_schleife, (Vector{Float64},))) # Julia leitet Float64 ab
println(Base.return_types(summe_instabil, (Vector{Float64},))) # Union{Float64, Int64}: instabil
println(typeof(summe_schleife([1.0, 2.0])), " ", typeof(summe_instabil([1.0, 2.0])), " ", typeof(summe_instabil([1, 2])))Ausgabe:
true
true
schnell genug
Any[Float64]
Any[Union{Float64, Int64}]
Float64 Float64 Int64In der REPL siehst du mit @time summe_schleife(v) Zeit und Speicherverbrauch; der erste Aufruf enthält die Kompilierzeit.
Werkzeuge zur Leistungsanalyse:
| Werkzeug | Zweck |
|---|---|
@time, @elapsed | grobe Zeit/Allokationen (erster Aufruf enthält Kompilierzeit) |
| BenchmarkTools.jl | @btime, @benchmark messen genau |
@code_warntype f(x) | zeigt Typinstabilitäten (rot markiert) |
@code_llvm, @code_native | erzeugten Code ansehen |
| Profile, ProfileView.jl | Flammendiagramme |
Faustregeln: Globale Variablen vermeiden (in Funktionen packen), Arrays vorallokieren, mit .= und @. in-place arbeiten, @views statt Kopien von Ausschnitten, @inbounds nur wenn bewiesen sicher.
# Vorallokation und fusionierte Operationen
n = 1000
x = rand(n)
y = zeros(n)
@. y = 2x^2 + 3x + 1 # @. fügt überall Punkte hinzu, eine Schleife, kein Zwischenarray
println(y[1] ≈ 2x[1]^2 + 3x[1] + 1)
function normiere!(v)
m = maximum(v)
v ./= m # in-place
return v
end
println(maximum(normiere!(copy(x))))
teil = @view x[1:10] # Ansicht ohne Kopie
teil[1] = -1.0
println(x[1])
using LinearAlgebra
A = rand(200, 200)
b = rand(200)
loesung = A \ b
println(norm(A * loesung - b) < 1e-8)
println(Threads.nthreads() >= 1)Ausgabe:
true
1.0
-1.0
true
trueParallel rechnen
Julia unterstützt Threads (gemeinsamer Speicher), Prozesse (Distributed), Tasks und GPUs (CUDA.jl):
using Base.Threads
# Aufgaben nebenläufig starten
tasks = [Threads.@spawn begin
s = 0
for i in 1:100_000
s += i % (k + 1)
end
s
end for k in 1:4]
println(fetch.(tasks))
# Parallele Schleife mit festem Ergebnis pro Index
ergebnis = zeros(Int, 8)
@threads for i in 1:8
ergebnis[i] = i^2
end
println(ergebnis)
# Thread-sichere Summe mit Lock
summe = Ref(0)
l = ReentrantLock()
@threads for i in 1:1000
lock(l) do
summe[] += i
end
end
println(summe[])
# Channels zwischen Tasks
kanal = Channel{Int}(5)
produzent = @async begin
for i in 1:5 put!(kanal, i * i) end
close(kanal)
end
println(collect(kanal))Ausgabe:
[50000, 100000, 150000, 200000]
[1, 4, 9, 16, 25, 36, 49, 64]
500500
[1, 4, 9, 16, 25]Starte Julia mit julia --threads=auto (oder -t 4), damit @threads echte Parallelität bekommt. Das gleiche Programm funktioniert mit einem Thread weiter, nur langsamer.
Numerik in Kürze
using LinearAlgebra, Statistics
# Newton-Verfahren
function newton(f, df, x0; tol = 1e-12, maxiter = 50)
x = x0
for k in 1:maxiter
fx = f(x)
abs(fx) < tol && return x, k
x -= fx / df(x)
end
return x, maxiter
end
x, k = newton(x -> x^2 - 2, x -> 2x, 1.0)
println(x, " nach ", k, " Schritten, Fehler ", abs(x - √2))
# Numerische Integration (Trapezregel) und Ableitung
integriere(f, a, b, n = 1000) = (h = (b - a) / n; h * (f(a) / 2 + sum(f(a + i * h) for i in 1:n-1) + f(b) / 2))
println(integriere(sin, 0, π), " ", integriere(x -> x^2, 0, 3))
ableitung(f, x; h = 1e-6) = (f(x + h) - f(x - h)) / 2h
println(round(ableitung(sin, 0.0), digits = 6), " ", round(ableitung(x -> x^3, 2.0), digits = 4))
# Lineare Regression von Hand
xs = [1.0, 2, 3, 4, 5, 6]
ys = [2.2, 4.1, 6.2, 7.8, 10.1, 12.1]
X = [ones(length(xs)) xs]
beta = X \ ys
println(round.(beta, digits = 3), " R² = ", round(1 - sum((ys .- X * beta) .^ 2) / sum((ys .- mean(ys)) .^ 2), digits = 4))
# Differentialgleichung y' = -2y mit dem Euler-Verfahren
function euler(h, schritte)
y = 1.0
for _ in 1:schritte
y += h * (-2y)
end
return y
end
println(round(euler(0.001, 2000), digits = 4), " exakt ", round(exp(-4), digits = 4))Ausgabe:
1.4142135623730951 nach 6 Schritten, Fehler 0.0
1.9999983550656624 9.000004500000005
1.0 12.0
[0.173, 1.974] R² = 0.9986
0.0182 exakt 0.0183Für ernsthafte Numerik gibt es die Pakete DifferentialEquations.jl, Optim.jl, JuMP.jl, FFTW.jl, Zygote.jl (automatische Ableitung).
Merke
- JIT-Kompilierung: erster Aufruf langsam, danach C-Tempo; Voraussetzung ist Typstabilität
- In Funktionen arbeiten (keine Globalen), vorallokieren,
@.,@views - Messen mit
@time, BenchmarkTools@btime, analysieren mit@code_warntype - Parallel:
Threads.@spawn,@threads,Channel,Distributed, GPU - Numerik-Ökosystem: LinearAlgebra, DifferentialEquations.jl, Optim.jl, JuMP
Übungsaufgabe
Schreibe eine Funktion, die π per Monte-Carlo-Simulation schätzt, und messe die Zeit.
Quiz zur Selbstkontrolle
Was bedeutet Typstabilität?
- Der Typ jeder Variablen ergibt sich aus den Argumenttypen und wechselt nicht (richtig)
- Alle Variablen haben denselben Typ
- Typen sind unveränderlich
- Typen werden nie geprüft
Wofür steht @threads?
- Schleifendurchläufe auf mehrere Threads verteilen (richtig)
- Threads löschen
- Einen Server starten
- Zeit messen
Warum Funktionen statt globaler Variablen?
- Globale Variablen verhindern Typinferenz und damit schnellen Code (richtig)
- Globale Variablen sind verboten
- Funktionen sind kürzer
- Es gibt keinen Grund
Weiter im Kurs
Zurück: Structs, Module und Fehler
Weiter: Projekte, Pakete und Ökosystem
Alle Kapitel: Julia im Überblick