Getting Started with Julia
上QQ阅读APP看书,第一时间看更新

How Julia works

(You can safely skip this section on a first reading.)

Julia works with an LLVM JIT compiler framework that is used for just-in-time generation of machine code. The first time you run a Julia function, it is parsed and the types are inferred. Then, LLVM code is generated by the JIT (just-in-time) compiler, which is then optimized and compiled down to native code. The second time you run a Julia function, the native code already generated is called. This is the reason why, the second time you call a function with arguments of a specific type, it takes much less time to run than the first time (keep this in mind when doing benchmarks of Julia code). This generated code can be inspected. Suppose, for example, we have defined a f(x) = 2x + 5 function in a REPL session. Julia responds with the message, f (generic function with 1 method); the code is dynamic because we didn't have to specify the type of x or f. Functions are by default generic because they are ready to work with different data types for their variables. The code_llvm function can be used to see the JIT bytecode, for example, the version where the x argument is of type Int64:

julia> code_llvm(f, (Int64,))

define i64 @"julia_f;1065"(i64) {
top:
  %1 = shl i64 %0, 1, !dbg !3248
  %2 = add i64 %1, 5, !dbg !3248
  ret i64 %2, !dbg !3248
}

The code_native function can be used to see the assembly code generated for the same type of x:

julia> code_native(f, (Int64,))
        .text
Filename: none
Source line: 1
        push    RBP
        mov     RBP, RSP
Source line: 1
        lea     RAX, QWORD PTR [RCX + RCX + 5]
        pop     RBP
        ret

Compare this with the code generated when x is of type Float64:

julia> code_native(f, (Float64,))
        .text
Filename: none
Source line: 1
        push    RBP
        mov     RBP, RSP
Source line: 1
        vaddsd  XMM0, XMM0, XMM0
        movabs  RAX, 48532256
        vaddsd  XMM0, XMM0, QWORD PTR [RAX]
        pop     RBP
        ret

Julia code is fast because it generates specialized versions of functions for each data type. Julia implements automatic memory management. The user doesn't have to worry about allocating and keeping track of the memory for specific objects. Automatic deletion of objects that are not needed any more (and hence, reclamation of the memory associated with those objects) is done using a garbage collector (GC). The garbage collector runs at the same time as your program. Exactly when a specific object is garbage collected is unpredictable. In Version 0.3, the GC is a simple mark-and-sweep garbage collector; this will change to an incremental mark-and-sweep GC in Version 0.4. You can start garbage collection yourself by calling gc(), or if it runs in the way you can disable it by calling gc_disable().

The standard library is implemented in Julia itself. The I/O functions rely on the libuv library for efficient, platform-independent I/O. The standard library is also contained in a package called Base, which is automatically imported when starting Julia.