Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Julia FAQ


May 14, 2021 Julia


Table of contents


Problems

Sessions and REPL

How do I delete objects in memory?

Julia doesn't have a clear function Main MATLAB; clear

If you care about memory usage, you can replace the larger ones with small ones that take up memory. F or example, if A is a large array that you don't need, you can free up memory first with A s A = 0 The next time you garbage collect, memory is gc()

How do I modify the type/immutable declaration in a session?

Sometimes you define a type but then discover that you need to add a new domain. When you try to do this in rePL, something goes wrong

    ERROR: invalid redefinition of constant MyType

Main in the Main module cannot be redefined.

This can become extremely inconvenient when you are developing new code, and there is a good way to handle it. M odules can be replaced by redefined methods, so encapsulating all your code in one module can redefine types and constants. Y ou can't import a type Main and redefine it, but you can solve this problem with a module name. In other words, you can use this workflow when you develop it

    include("mynewcode.jl")              # this defines a module MyModule
    obj1 = MyModule.ObjConstructor(a, b)
    obj2 = MyModule.somefunction(obj1)
    # Got an error. Change something in "mynewcode.jl"
    include("mynewcode.jl")              # reload the module
    obj1 = MyModule.ObjConstructor(a, b) # old objects are no longer valid, must reconstruct
    obj2 = MyModule.somefunction(obj1)   # this time it worked!
    obj3 = MyModule.someotherfunction(obj2, c)
    ...

Function

I x argument x to a function and modified its value within the function, but x outside the function did not change, why?

Suppose you call a function like this:

julia> x = 10
julia> function change_value!(y) # Create a new function
           y = 17
       end
julia> change_value!(x)
julia> x # x is unchanged!
10

In Julia, all functions, including change_value!() modify the class to which the local variable belongs. I f x defined as an imm changeable object (such as a real number) when called by a function, it cannot be modified; S imilarly, if x defined as a Dict you cannot change it to ASCIIString. B ut the main thing to do x Suppose x is an array (or any variable type). You x no longer represent this array, but because the array is a variable object, you can modify the elements of the array:

    julia> x = [1,2,3]
    3-element Array{Int64,1}:
    1
    2
    3

    julia> function change_array!(A) # Create a new function
               A[1] = 5
           end
    julia> change_array!(x)
    julia> x
    3-element Array{Int64,1}:
    5
    2
    3

Here we define the change_array!() which assigns the 5 to the first element of the array. When we x to this function, we notice that x still the same array, but the elements of the array have changed.

Can I use using import

No, you can't use using in import If you want to import a module but only use it in some functions, you have two scenarios:

  1. Use import
        import Foo
        function bar(...)
            ... refer to Foo symbols via Foo.baz ...
        end
  1. Encapsulate the function in a module:
        module Bar
        export bar
        using Foo
        function bar(...)
            ... refer to Foo.baz as simply baz ....
        end
        end
        using Bar

Type, type declaration, and construction methods

What is Type Stability?

This means that the type of output can be predicted by the input type. I n particular, this means that the type of output cannot vary depending on the value of the input. The following code is not type stable

    function unstable(flag::Bool)
        if flag
            return 1
        else
            return 1.0
        end
    end

This code returns an Int or Float64 Int value of the parameter. Because Julia cannot predict the return value type of a function at compile time, any calculation that uses this function has to consider both possible return types, which makes it difficult to generate fast machine code.

Why does the seemingly reasonable operation Julia still DomainError ?

Some operations make mathematical sense but produce errors:

    julia> sqrt(-2.0)
    ERROR: DomainError
     in sqrt at math.jl:128

    julia> 2^-5
    ERROR: DomainError
     in power_by_squaring at intfuncs.jl:70
     in ^ at intfuncs.jl:84

This is caused by type stabilization. F or sqrt most users will use sqrt(2.0) a real number instead of a complex number of 1.4142135623730951 + 0.0im You can sqrt as a pluten number when the argument is negative, but this will no longer be type stable and sqrt will become slow.

In these cases, you can choose the input type to get the output type you want :

    julia> sqrt(-2.0+0im)
    0.0 + 1.4142135623730951im

    julia> 2.0^-5
    0.03125

Why does Julia use this integer operation?

Julia applies integer calculations for machine operations. This means that the range of int values is bounded and is valued between the two bounds, so adding, subtracting, multiplying and multiplying an integer can lead to overflow or downflow, which can lead to some bad consequences, which can be very disturbing at first.

    julia> typemax(Int)
    9223372036854775807

    julia> ans+1
    -9223372036854775808

    julia> -ans
    -9223372036854775808

    julia> 2*ans
    0

Obviously, this is far from mathematical, and you might think that Julia is less than ideal when it's exposed to users by some advanced programming languages. However, this is a valuable numerical work for efficiency and transparency, and alternatives are even worse.

Here's an option to check for overflows for each integer operation, and to increase the result value to a bigger integer type because of the overflow, Int128 BigInt U nfortunately, this introduces a major burden on each integer operation (think adding a loop counter) - this requires an overflow check when the transmit code executes the program after the arithmetic instruction, and some branches are required to address potential overflow problems. W orse, this results in every calculation that is unstable when it comes to integers. A s we mentioned above, the stability of the type is the key to effective code generation. If you can't expect the result of integer operations to be integers, it's impossible to generate fast, simple code done as C and Fortran compilers do.

Another way to avoid changes in the appearance of unstable types is Int and BigInt into a single mixed integer type, which can be represented by internal changes when the results no longer fit the size of the machine integer. H owever, this is only a superficial solution to the level of instability in the Julia language, and it only solves a few minor problems by hardening all the same puzzles into the C language so that the hybrid integer type can be successfully implemented. T his approach can basically work and can even react fairly quickly in many cases, but there are several drawbacks. O ne problem is that the integer and integer arrays in memory are no longer corresponding to local representing methods in other languages with local machine integers, such as C, Fortran, and so on. T herefore, to interoperability with these languages, we ultimately need to introduce local integer types anyway. N o bounded integers have a fixed bit, so they cannot be stored inline in an array with fixed-size slots, and the values of larger integers are always stored in separate heap allocations. O f course, no matter how subtle the implementation of a mixed integer is, there will always be performance traps or performance declines. Complex representations, lack of interoperability with the C and Fortran languages, do not represent arrays of integers without additional heap storage, and unpredictable performance characteristics make it difficult to achieve high-performance computing even the most subtle mix of integers.

There is also an option to use a mixed integer or raise it to BigInts, which is implemented using a saturated integer operation that does not change the value even if a number is added to the largest integer value, and again, subtracts the value from the smallest integer value and the value remains the same. That's exactly what matlab ™ can do.

    >> int64(9223372036854775807)

    ans =

      9223372036854775807

    >> int64(9223372036854775807) + 1

    ans =

      9223372036854775807

    >> int64(-9223372036854775808)

    ans =

     -9223372036854775808

    >> int64(-9223372036854775808) - 1

    ans =

     -9223372036854775808

At first glance, this may seem reasonable, since 922337203685477580 is closer to 9223337203685477580 than 922337203685477580, and the integer is still represented in a local way of implementing fixed sizes compatible with the C and Fortran languages. H owever, saturation of integer operations is very problematic. T he first and most obvious problem is that it is not the integer arithmetic of the machine, so each machine performs integer operations to check for underflows or overflows and replaces the results appropriately with typemin (int) or typemax (int) before the instructions required to issue saturation operations can be implemented. T his scales each integer operation from a single, fast instruction to six instructions, possibly including branches. B ut it gets worse - saturated integer arithmetic is not associated. Consider this MATLAB calculation:

    >> n = int64(2)^62
    4611686018427387904

    >> n + (n - 1)
    9223372036854775807

    >> (n + n) - 1
    9223372036854775806

This makes it difficult to write many basic integer algorithms because many common techniques rely on the fact that machine additions and overflows are associated. Consider using the expression (lo and hi) in Julia to find the mid-point of the integer values lo and hi:

    julia> n = 2^62
    4611686018427387904

    julia> (n + 2n) >>> 1
    6917529027641081856

Do you see that? N o problem. T his is the correct mid-point between 2^62 and n+2n should actually be - 461168601842738790. Now try in MATLAB:

    >> (n + 2*n)/2

    ans =

      4611686018427387904

That's wrong. A dding an a-and-gt; operator to Matlab doesn't help. Saturation occurs because adding n and 2n already destroys the information that is necessary to calculate the correct mid-point.

This is not only because programmers lack the binding nature to rely on such techniques, but also to defeat the optimization integer operations that almost any compiler might want to do. F or example, since Julia's integers use normal machine integer operations, LLVM is free to actively and simply optimize small functions such as f(k) s 5k-1. The machine code for this function is like this:

    julia> code_native(f,(Int,))
        .section    __TEXT,__text,regular,pure_instructions
    Filename: none
    Source line: 1
        push    RBP
        mov RBP, RSP
    Source line: 1
        lea RAX, QWORD PTR [RDI + 4*RDI - 1]
        pop RBP
        ret

The actual body of the function is a single lea instruction that is multiplied and multiplied immediately when the integer is calculated. When f is embedded in another function, it is more advantageous:

    julia> function g(k,n)
             for i = 1:n
               k = f(k)
             end
             return k
           end
    g (generic function with 2 methods)

    julia> code_native(g,(Int,Int))
        .section    __TEXT,__text,regular,pure_instructions
    Filename: none
    Source line: 3
        push    RBP
        mov RBP, RSP
        test    RSI, RSI
        jle 22
        mov EAX, 1
    Source line: 3
        lea RDI, QWORD PTR [RDI + 4*RDI - 1]
        inc RAX
        cmp RAX, RSI
    Source line: 2
        jle -17
    Source line: 5
        mov RAX, RDI
        pop RBP
        ret

Since f call is inline, the end of the loop body is just a single lea instruction. Next, if we fix the number of iterations in the loop, we can consider what happened:

    julia> function g(k)
             for i = 1:10
               k = f(k)
             end
             return k
           end
    g (generic function with 2 methods)

    julia> code_native(g,(Int,))
        .section    __TEXT,__text,regular,pure_instructions
    Filename: none
    Source line: 3
        push    RBP
        mov RBP, RSP
    Source line: 3
        imul    RAX, RDI, 9765625
        add RAX, -2441406
    Source line: 5
        pop RBP
        ret

Because the compiler knows the connection between addition and multiplication of integers and multiplication is assigned a higher priority than division - both are true saturation operations - they can optimize the entire loop so that only multiplication and addition remain. The saturation algorithm completely defeats this optimization because binding and distribution can fail at each iteration in the loop, and the different consequences depend on which iteration fails.

The saturation integer algorithm is just one example of a really poor choice of language semantics, which prevents all effective performance optimization. T here are a lot of things that are difficult in C-language programming, but integer overflow is not one of them, especially in 64-bit systems. F or example, if I use an integer that might become bigger than 2-63-1, I can easily predict. D o you want to ask yourself if I'm traversing the actual things stored on your computer? T hen I can confirm that the numbers don't get that big. T hat's a guarantee, because I don't have that much storage space. A m I really counting the real thing? U nless they are sand or atomic particles in the universe, 2^63-1 is large enough. A m I calculating factority? T hen you can confirm that they may be particularly large - I should use BigInt. D o you understand? It's easy to tell.

How do "abstract" or ambiguous domains of type interact with the compiler?

A type can be declared without specifying the type of field:

    julia> type MyAmbiguousType
               a
           end

This a to be of any type. T his is often useful, but it has one drawback: for MyAmbiguousType the compiler will not be able to generate efficient code. T he reason is that the compiler uses the type of the object rather than the value to decide how to build the code. Unfortunately, MyAmbiguousType can only infer very little information:

    julia> b = MyAmbiguousType("Hello")
    MyAmbiguousType("Hello")

    julia> c = MyAmbiguousType(17)
    MyAmbiguousType(17)

    julia> typeof(b)
    MyAmbiguousType (constructor with 1 method)

    julia> typeof(c)
    MyAmbiguousType (constructor with 1 method)

b and c the same types, but the underlying represents of their data in memory are very different. E ven if you only store values in the domain of a the fact that the memory representations of Uint8 and Float64 are different means that the CPU needs to handle them with two different instructions. B ecause the required information in the type is not available, such a decision has to be made at runtime. T his slows down performance.

You can do better a type of a. H ere, we note that a may be any of several types, in which case the natural solution is to use parameters. For example:

    julia> type MyType{T<:FloatingPoint}
             a::T
           end

This is a better choice than the following code

    julia> type MyStillAmbiguousType
             a::FloatingPoint
           end

Because the first version specifies the type of wrapper object. For example:

    julia> m = MyType(3.2)
    MyType{Float64}(3.2)

    julia> t = MyStillAmbiguousType(3.2)
    MyStillAmbiguousType(3.2)

    julia> typeof(m)
    MyType{Float64} (constructor with 1 method)

    julia> typeof(t)
    MyStillAmbiguousType (constructor with 2 methods)

a type of a's domain can be easily m but not by the type of t In fact, in t is the type of domain that can change a

    julia> typeof(t.a)
    Float64

    julia> t.a = 4.5f0
    4.5f0

    julia> typeof(t.a)
    Float32

Conversely, m is constructed, m.a cannot be changed:

    julia> m.a = 4.5f0
    4.5

    julia> typeof(m.a)
    Float64

a that the type of a can be known from the type of m and m.a cannot be m t instead of a class like m

Of course, all this is true only when m with a specific type. We can break one point by explicitly constructing it with abstract classes:

    julia> m = MyType{FloatingPoint}(3.2)
    MyType{FloatingPoint}(3.2)

    julia> typeof(m.a)
    Float64

    julia> m.a = 4.5f0
    4.5f0

    julia> typeof(m.a)
    Float32

For all practical purposes, these objects behave MyStillAmbiguousType

It makes sense to compare all the code generated by a simple program:

    func(m::MyType) = m.a+1

Use:

    code_llvm(func,(MyType{Float64},))
    code_llvm(func,(MyType{FloatingPoint},))
    code_llvm(func,(MyType,))

The results are not shown here because of the length, but you might as well try it yourself. B ecause in the first case, the type is fully specified, the compiler does not need to generate any code at runtime to resolve the type's problems. This results in shorter code encoding faster.

How to declare the domain of the Abstract Container Type

The same examples as the best ones applied in the previous section also apply in container types:

    julia> type MySimpleContainer{A<:AbstractVector}
             a::A
           end

    julia> type MyAmbiguousContainer{T}
             a::AbstractVector{T}
           end

For example:

    julia> c = MySimpleContainer(1:3);

    julia> typeof(c)
    MySimpleContainer{UnitRange{Int64}} (constructor with 1 method)

    julia> c = MySimpleContainer([1:3]);

    julia> typeof(c)
    MySimpleContainer{Array{Int64,1}} (constructor with 1 method)

    julia> b = MyAmbiguousContainer(1:3);

    julia> typeof(b)
    MyAmbiguousContainer{Int64} (constructor with 1 method)

    julia> b = MyAmbiguousContainer([1:3]);

    julia> typeof(b)
    MyAmbiguousContainer{Int64} (constructor with 1 method)

For MySimpleContainer are fully specified by their type and parameters, so the compiler can generate optimized functionality. In most cases, this may be enough.

Although the compiler can now do its job perfectly, there are times when you might want your a on the type of element of a. In general, the best way to do this is to wrap your specific actions (in this place foo in a separate function:

    function sumfoo(c::MySimpleContainer)
        s = 0
    for x in c.a
        s += foo(x)
    end
    s
    end

    foo(x::Integer) = x
    foo(x::FloatingPoint) = round(x)

This allows the compiler to generate optimized code in all cases while keeping it simple.

However, sometimes you need to declare different versions of external functions based on the different element types of a You can do it like this:

    function myfun{T<:FloatingPoint}(c::MySimpleContainer{Vector{T}})
        ...
    end
    function myfun{T<:Integer}(c::MySimpleContainer{Vector{T}})
        ...
    end

Vector{T} but we'll also want UnitRange{T} or other abstract classes. To prevent this tedious situation, you can use two variables in MyContainer declaration:

    type MyContainer{T, A<:AbstractVector}
        a::A
    end
    MyContainer(v::AbstractVector) = MyContainer{eltype(v), typeof(v)}(v)

    julia> b = MyContainer(1.3:5);

    julia> typeof(b)
    MyContainer{Float64,UnitRange{Float64}}

Notice a somewhat surprising fact that T is T a and we'll be back to that in a minute. In this way, one can write functions like this:

    function myfunc{T<:Integer, A<:AbstractArray}(c::MyContainer{T,A})
        return c.a[1]+1
    end
    # Note: because we can only define MyContainer for
    # A<:AbstractArray, and any unspecified parameters are arbitrary,
    # the previous could have been written more succinctly as
    #     function myfunc{T<:Integer}(c::MyContainer{T})

    function myfunc{T<:FloatingPoint}(c::MyContainer{T})
        return c.a[1]+2
    end

    function myfunc{T<:Integer}(c::MyContainer{T,Vector{T}})
        return c.a[1]+3
    end

    julia> myfunc(MyContainer(1:3))
    2

    julia> myfunc(MyContainer(1.0:3))
    3.0

    julia> myfunc(MyContainer([1:3]))
    4

As you can see, this approach allows you to focus on both element type T and array A

However, there is one remaining problem: we did A to include element type T so it is entirely possible to construct such an object:

  julia> b = MyContainer{Int64, UnitRange{Float64}}(1.3:5);

  julia> typeof(b)
  MyContainer{Int64,UnitRange{Float64}}

To prevent this, we can add an internal constructor:

    type MyBetterContainer{T<:Real, A<:AbstractVector}
        a::A

        MyBetterContainer(v::AbstractVector{T}) = new(v)
    end
    MyBetterContainer(v::AbstractVector) = MyBetterContainer{eltype(v),typeof(v)}(v)

    julia> b = MyBetterContainer(1.3:5);

    julia> typeof(b)
    MyBetterContainer{Float64,UnitRange{Float64}}

    julia> b = MyBetterContainer{Int64, UnitRange{Float64}}(1.3:5);
    ERROR: no method MyBetterContainer(UnitRange{Float64},)

The internal constructor A element type of T

None and no values

How does "null" and "nothingness" work in Julia?

Unlike in many other languages, such as C and Java, there is no "null" value in Julia. W hen a reference (a variable, a domain of an object, or an array element) is unitialized, accessing it immediately throws an error. This can be isdefined function.

Some functions are used only for their side effects and do not require a return value. I n this case, the convention nothing which is just a Nothing object. T his is a normal type without a domain; S ome language structures that cannot be valued are also unified as nothing such as if false; end

Note Nothing (capital) is the type of nothing and should only be used in a type-by-demand environment (for example, a declaration).

You may occasionally see None which is completely different. I t is an empty, or "bottom" type, a type that has no value and no subtypes (subtypes, except for itself). You generally do not need to use this type.

An empty group () is another type of none. But it shouldn't really be considered nothing but a zero-value group.

Julia release

Do I want to use a Julia release, beta, or night version?

If you want a stable code base, you might prefer Julia's release. Typically released every 6 months, gives you a stable writing platform.

If you don't mind falling slightly behind the latest error fixes and changes, but find more attractive changes a little faster, you might prefer the Julia beta version. In addition, these binary files are tested before they are published to ensure that they are fully functional.

If you want to take advantage of the latest updates in your language, you might prefer to use Julia's nightly version and don't mind that it doesn't work occasionally.

Finally, you can also consider building Julia for yourself at the source. T his option is primarily for individuals who are comfortable with the command line or interested in learning. If this describes you, you may also be interested in reading our guidelines.

Links to these download types can be found on the http://julialang.org/downloads/ page. Note that not all versions of Julia are available on all platforms.

When do I remove discarded functions?

Outdated features are removed after subsequent releases. For example, features marked as obsolete in a 0.1 release will not be used in a 0.2 release.

Develop Julia

How do I debug Julia's C code? (Run Julia REPL from inside a debugger like gdb)

First you should build the make debug make debug. Below, the (gdb) means that you need to enter it under gdb prompt.

Start with the shell

The main challenge is that both Julia and gdb need to have their own terminals to allow you to interact with them. O ne approach is to use gdb's attach feature to debug a running Julia session. H owever, in many systems, you need to use root access to make this work. Here's a way to implement it using only user-level permissions.

The first time you do this, you need to define a script, here called oterm that contains the following lines:

    ps
    sleep 600000

Let it chmod +x oterm

Right now:

  • Start with a shell (called shell 1), xterm -e oterm & You will see a new window pop up, which will be called Terminal 2.
  • From within shell 1, gdb julia-debug . You will julia/usr/bin
  • From within shell 1, (gdb) tty /dev/pts/# is the number that is displayed after pts/ terminal 2. #
  • From within shell 1, (gdb) run
  • From within terminal 2, publish any commands you need in Julia to get to the steps you want to debug
  • From within shell 1, press Ctrl-C
  • Insert your break point from shell 1, for example (gdb) b codegen.cpp:2244
  • From within shell 1, (gdb) c Julia's execution
  • From within terminal 2, publish the command you want to debug, and shell 1 will stop at your break point.

Within emacs

  • M-x gdb then go julia-debug (this can be easiest to find from julia/usr/bin, or you can specify the full path)
  • (gdb) run
  • Now you'll see Julia prompt. Run any command you need in Julia to get to the steps you want to debug.
  • Select BREAK under the "Signals" menu at emacs - this will take you back (gdb) prompt
  • Set a break point, for example, (gdb) b codegen.cpp:2244
  • Return to Julia prompt (gdb) c c
  • Execute the Julia command that you want to run.