5  Arbeit mit Julia: REPL, Pakete, Introspection

5.1 Dokumentation

Die offizielle Julia-Dokumentation https://docs.julialang.org/ enthält zahlreiche Übersichten, darunter:

5.2 Julia REPL (Read - Eval - Print - Loop)

Nach dem Start von Julia in einem Terminal kann man neben Julia-Code auch verschiedene Kommandos eingeben

Kommando Wirkung
exit() oder Ctrl-d exit Julia
Ctrl-c interrupt
Ctrl-l clear screen
Kommando mit ; beenden Ausgabe unterdrückt
include("filename.jl") Datei mit Julia-Code einlesen und ausführen

Der REPL hat verschiedene Modi:

Modus Prompt Modus starten Modus verlassen
default julia> Ctrl-d (beendet Julia)
Package manager pkg> ] backspace
Help help?> ? backspace
Shell shell> ; backspace

5.3 Jupyter-Notebooks (IJulia)

In einem Jupyter-Notebook sind die Modi sind als Einzeiler in einer eigenen Input-Zelle nutzbar:

  1. ein Kommando des Paket-Managers:
] status
  1. eine Help-Abfrage:
?sin
  1. Ein Shell-Kommando:
;ls

5.4 Der Paketmanager

Wichtiger Teil des Julia Ecosystems sind die zahlreichen Pakete, die Julia erweitern.

  • Einige Pakete sind Teil jeder Julia-Installation und müssen nur mit einer using Paketname-Anweisung aktiviert werden.
    • Sie bilden die sogenannte Standard Library und dazu gehören
    • LinearAlgebra, Statistics, SparseArrays, Printf, Pkg und andere.
  • Über 9000 Pakete sind offiziell registriert, siehe https://julialang.org/packages/.
    • Diese können mit wenigen Tastendrücken heruntergeladen und installiert werden.
    • Dazu dient der package manager Pkg.
    • Man kann ihn auf zwei Arten verwenden:
      • als normale Julia-Anweisungen, die auch in einer .jl-Programmdatei stehen können:
      using Pkg
      Pkg.add("PaketXY")
      • im speziellen pkg-Modus des Julia-REPLs:
      ] add PaketXY
    • Anschließend kann das Paket mit using PaketXY verwendet werden.
  • Man kann auch Pakete aus anderen Quellen und selbstgeschriebene Pakete installieren.

5.4.1 Einige Funktionen des Paketmanagers

Funktion pkg - Mode Erklärung
Pkg.add("MyPack") pkg> add MyPack add MyPack.jl to current environment
Pkg.rm("MyPack") pkg> remove MyPack remove MyPack.jl from current environment
Pkg.update() pkg> update update packages in current environment
Pkg.activate("mydir") pkg> activate mydir activate directory as current environment
Pkg.status() pkg> status list packages
Pkg.instantiate() pg> instantiate install all packages according to Project.toml

5.4.2 Installierte Pakete und Environments

  • Julia und der Paketmanager verwalten
    1. eine Liste der mit dem Kommando Pkg.add() bzw. ]add explizit installierten Pakete mit genauer Versionsbezeichnung in einer Datei Project.toml und
    2. eine Liste aller dabei auch als implizite Abhängigkeiten installierten Pakete in der Datei Manifest.toml.
  • Das Verzeichnis, in dem diese Dateien stehen, ist das environment und wird mit Pkg.status() bzw. ]status angezeigt.
  • Im Normalfall sieht das so aus:
(@v1.10) pkg> status
      Status `~/.julia/environments/v1.10/Project.toml`
  [6e4b80f9] BenchmarkTools v1.5.0
  [5fb14364] OhMyREPL v0.5.24
  [91a5bcdd] Plots v1.40.4
  [295af30f] Revise v3.5.14
  • Man kann für verschiedene Projekte eigene environments benutzen. Dazu kann man entweder Julia mit
julia --project=path/to/myproject

starten oder in Julia das environment mit Pkg.activate("path/to/myproject") aktivieren. Dann werden Project.toml, Manifest.toml dort angelegt und verwaltet. (Die Installation der Paketdateien erfolgt weiterhin irgendwo unter $HOME/.julia)

5.4.3 Zum Installieren von Paketen auf unserem Jupyter-Server misun103:

  • Es gibt ein zentrales Repository, in dem alle in diesem Kurs erwähnten Pakete bereits installiert sind.
  • Dort haben Sie keine Schreibrechte.
  • Sie können aber zusätzliche Pakete in Ihrem HOME installieren. Dazu ist als erster Befehl nötig, das aktuelle Verzeichnis zu aktivieren:
] activate .

(Man beachte den Punkt!)

Danach können Sie mit add im Pkg-Modus auch Pakete installieren:

] add PaketXY

Achtung! Das kann dauern! Viele Pakete haben komplexe Abhängigkeiten und lösen die Installation von weiteren Paketen aus. Viele Pakete werden beim Installieren vorkompiliert. Im REPL sieht man den Installationsfortschritt, im Jupyter-Notebook leider nicht.

5.5 Der Julia JIT (just in time) Compiler: Introspection

Julia baut auf die Werkzeuge des LLVM Compiler Infrastructure Projects auf.

Stages of Compilation

stage & result introspection command
Parse \(\Longrightarrow\) Abstract Syntax Tree (AST) Meta.parse()
Lowering: transform AST \(\Longrightarrow\) Static Single Assignment (SSA) form @code_lowered
Type Inference @code_warntype, @code_typed
Generate LLVM intermediate representation @code_llvm
Generate native machine code @code_native
function f(x,y)
    z = x^2 + log(y)
    return 2z
end
f (generic function with 1 method)
p = Meta.parse( "function f(x,y); z=x^2+log(y); return 2x; end  ")
:(function f(x, y)
      #= none:1 =#
      #= none:1 =#
      z = x ^ 2 + log(y)
      #= none:1 =#
      return 2x
  end)
using TreeView

walk_tree(p)

@code_lowered f(2,4)
CodeInfo(
1 ─ %1  = Main.Notebook.:+
 %2  = Main.Notebook.:^
 %3  = Core.apply_type(Base.Val, 2)
 %4  = (%3)()
 %5  = Base.literal_pow(%2, x, %4)
 %6  = Main.Notebook.log
 %7  = (%6)(y)
       z = (%1)(%5, %7)
 %9  = Main.Notebook.:*
 %10 = z
 %11 = (%9)(2, %10)
└──       return %11
)
@code_warntype f(2,4)
MethodInstance for Main.Notebook.f(::Int64, ::Int64)
  from f(x, y) @ Main.Notebook ~/Julia/23/Book-ansipatch/chapters/5_TricksHelp.qmd:196
Arguments
  #self#::Core.Const(Main.Notebook.f)
  x::Int64
  y::Int64
Locals
  z::Float64
Body::Float64
1 ─ %1  = Main.Notebook.:+::Core.Const(+)
 %2  = Main.Notebook.:^::Core.Const(^)
 %3  = Core.apply_type(Base.Val, 2)::Core.Const(Val{2})
 %4  = (%3)()::Core.Const(Val{2}())
 %5  = Base.literal_pow(%2, x, %4)::Int64
 %6  = Main.Notebook.log::Core.Const(log)
 %7  = (%6)(y)::Float64
       (z = (%1)(%5, %7))
 %9  = Main.Notebook.:*::Core.Const(*)
 %10 = z::Float64
 %11 = (%9)(2, %10)::Float64
└──       return %11
@code_typed f(2,4)
CodeInfo(
1 ─ %1 = Base.mul_int(x, x)::Int64
 %2 = Base.sitofp(Float64, y)::Float64
 %3 = invoke Base.Math.log(%2::Float64)::Float64
 %4 = Base.sitofp(Float64, %1)::Float64
 %5 = Base.add_float(%4, %3)::Float64
 %6 = Base.mul_float(2.0, %5)::Float64
└──      return %6
) => Float64
@code_llvm f(2,4)
; Function Signature: f(Int64, Int64)
;  @ /home/hellmund/Julia/23/Book-ansipatch/chapters/5_TricksHelp.qmd:196 within `f`
define double @julia_f_16465(i64 signext %"x::Int64", i64 signext %"y::Int64") #0 {
top:
;  @ /home/hellmund/Julia/23/Book-ansipatch/chapters/5_TricksHelp.qmd:197 within `f`
; ┌ @ intfuncs.jl:370 within `literal_pow`
; │┌ @ int.jl:88 within `*`
    %0 = mul i64 %"x::Int64", %"x::Int64"
; └└
; ┌ @ math.jl:1529 within `log`
; │┌ @ float.jl:374 within `float`
; ││┌ @ float.jl:348 within `AbstractFloat`
; │││┌ @ float.jl:239 within `Float64`
      %1 = sitofp i64 %"y::Int64" to double
; │└└└
; │ @ math.jl:1531 within `log`
   %2 = call double @j_log_16471(double %1)
; └
; ┌ @ promotion.jl:429 within `+`
; │┌ @ promotion.jl:400 within `promote`
; ││┌ @ promotion.jl:375 within `_promote`
; │││┌ @ number.jl:7 within `convert`
; ││││┌ @ float.jl:239 within `Float64`
       %3 = sitofp i64 %0 to double
; │└└└└
; │ @ promotion.jl:429 within `+` @ float.jl:491
   %4 = fadd double %2, %3
; └
;  @ /home/hellmund/Julia/23/Book-ansipatch/chapters/5_TricksHelp.qmd:198 within `f`
; ┌ @ promotion.jl:430 within `*` @ float.jl:493
   %5 = fmul double %4, 2.000000e+00
; └
  ret double %5
}
@code_native f(2,4)
    .text
    .file   "f"
    .globl  julia_f_16642                   # -- Begin function julia_f_16642
    .p2align    4, 0x90
    .type   julia_f_16642,@function
julia_f_16642:                          # @julia_f_16642
; Function Signature: f(Int64, Int64)
; ┌ @ /home/hellmund/Julia/23/Book-ansipatch/chapters/5_TricksHelp.qmd:196 within `f`
# %bb.0:                                # %top
; │ @ /home/hellmund/Julia/23/Book-ansipatch/chapters/5_TricksHelp.qmd within `f`
    #DEBUG_VALUE: f:x <- $rdi
    #DEBUG_VALUE: f:y <- $rsi
    push rbp
    mov  rbp, rsp
    push rbx
    push rax
    mov  rbx, rdi
; │ @ /home/hellmund/Julia/23/Book-ansipatch/chapters/5_TricksHelp.qmd:197 within `f`
; │┌ @ intfuncs.jl:370 within `literal_pow`
; ││┌ @ int.jl:88 within `*`
    imul rbx, rdi
; │└└
; │┌ @ math.jl:1529 within `log`
; ││┌ @ float.jl:374 within `float`
; │││┌ @ float.jl:348 within `AbstractFloat`
; ││││┌ @ float.jl:239 within `Float64`
    vcvtsi2sd    xmm0, xmm0, rsi
; ││└└└
; ││ @ math.jl:1531 within `log`
    movabs   rax, offset j_log_16648
    call rax
; │└
; │┌ @ promotion.jl:429 within `+`
; ││┌ @ promotion.jl:400 within `promote`
; │││┌ @ promotion.jl:375 within `_promote`
; ││││┌ @ number.jl:7 within `convert`
; │││││┌ @ float.jl:239 within `Float64`
    vcvtsi2sd    xmm1, xmm1, rbx
; ││└└└└
; ││ @ promotion.jl:429 within `+` @ float.jl:491
    vaddsd   xmm0, xmm0, xmm1
; │└
; │ @ /home/hellmund/Julia/23/Book-ansipatch/chapters/5_TricksHelp.qmd:198 within `f`
; │┌ @ promotion.jl:430 within `*` @ float.jl:493
    vaddsd   xmm0, xmm0, xmm0
; │└
    add  rsp, 8
    pop  rbx
    pop  rbp
    ret
.Lfunc_end0:
    .size   julia_f_16642, .Lfunc_end0-julia_f_16642
; └
                                        # -- End function
    .type   ".L+Core.Float64#16644",@object # @"+Core.Float64#16644"
    .section    .rodata,"a",@progbits
    .p2align    3, 0x0
".L+Core.Float64#16644":
    .quad   ".L+Core.Float64#16644.jit"
    .size   ".L+Core.Float64#16644", 8

.set ".L+Core.Float64#16644.jit", 128627131790624
    .size   ".L+Core.Float64#16644.jit", 8
    .section    ".note.GNU-stack","",@progbits