{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Lecture 2: Code specialization\n", "\n", "In lecture 1, we learned how the semantics of types affect performance — it was not *possible* to implement a C-speed `sum` function, for example, unless (a) the element type is attached to the array as a whole so that (b) the element data can be stored consecutively in memory and (b) the computional code can be specialized at compile-time for `+` on this element type.\n", "\n", "For code to be fast, however, it must also be possible to take advantage of this information in your own code. This is obviously possible in statically typed languages like C, which are compiled to machine instructions ahead of time with type information declared explicitly to the compiler. It is more subtle in a language like Julia, where the programmer does *not* usually explicitly declare type information.\n", "\n", "In particular, we will talk about how Julia achieves both fast code and *type-generic code* (e.g. a `sum` function that works on any container of any type supporting `+`, even user-defined types), by aggressive automated **code specialization** by the Julia compiler." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A type-generic sum function\n", "\n", "Let's again look at our simple hand-written `sum` function." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "mysum (generic function with 1 method)" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "function mysum(a)\n", " s = zero(eltype(a))\n", " for x in a\n", " s += x\n", " end\n", " return s\n", "end" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5.0008825087619815e6" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = rand(10^7)\n", "mysum(a)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mysum(a) ≈ sum(a)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 3.908 ms (0 allocations: 0 bytes)\n", " 12.330 ms (0 allocations: 0 bytes)\n" ] }, { "data": { "text/plain": [ "5.0008825087619815e6" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "using BenchmarkTools\n", "@btime sum($a)\n", "@btime mysum($a)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Hooray! Basically the same speed as Julia's built-in `sum` function and `numpy.sum`! And it only required **7 lines of code**, some care with types, and a very minor bit of wizardry with the `@simd` tag to get the last factor of two.\n", "\n", "Moreover, the code is still **type generic**: it can sum any container of any type that works with addition. For example, it works for complex numbers, which are about two times slower as you might expect (since each complex addition requires two real additions):" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 12.041 ms (0 allocations: 0 bytes)\n" ] }, { "data": { "text/plain": [ "4.997905404810494e6 + 4.999604643441703e6im" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "z = rand(Complex{Float64}, length(a));\n", "@btime mysum($z)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And we **didn't have to declare any types** of any arguments or variables; the compiler figured everything out. How?" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Set([2, 17, 6, 24])" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s = Set([2, 17, 6 , 24])" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Set{Int64}" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "typeof(s)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(true, false)" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "2 in s, 13 in s" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "49" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mysum(s)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Type inference and specialization\n", "\n", "To go any further, you need to understand something very basic about how Julia works. Suppose we define a very simple function:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "f (generic function with 1 method)" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f(x) = x + 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We didn't declare the type of `x`, and so our function `f(x)` will work with **any type of `x`** (as long as the `+ 1` operation is defined for that type):" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f(3) # x is an integer (technically, a 64-bit integer)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4.1" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f(3.1) # x is a floating-point value (Float64)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "ename": "MethodError", "evalue": "MethodError: no method matching +(::Array{Int64,1}, ::Int64)\nClosest candidates are:\n +(::Any, ::Any, !Matched::Any, !Matched::Any...) at operators.jl:502\n +(!Matched::Complex{Bool}, ::Real) at complex.jl:292\n +(!Matched::Missing, ::Number) at missing.jl:93\n ...", "output_type": "error", "traceback": [ "MethodError: no method matching +(::Array{Int64,1}, ::Int64)\nClosest candidates are:\n +(::Any, ::Any, !Matched::Any, !Matched::Any...) at operators.jl:502\n +(!Matched::Complex{Bool}, ::Real) at complex.jl:292\n +(!Matched::Missing, ::Number) at missing.jl:93\n ...", "", "Stacktrace:", " [1] f(::Array{Int64,1}) at ./In[10]:1", " [2] top-level scope at In[13]:1" ] } ], "source": [ "f([1,2,3]) # x is an array of integers" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "ename": "MethodError", "evalue": "MethodError: no method matching +(::String, ::Int64)\nClosest candidates are:\n +(::Any, ::Any, !Matched::Any, !Matched::Any...) at operators.jl:502\n +(!Matched::Complex{Bool}, ::Real) at complex.jl:292\n +(!Matched::Missing, ::Number) at missing.jl:93\n ...", "output_type": "error", "traceback": [ "MethodError: no method matching +(::String, ::Int64)\nClosest candidates are:\n +(::Any, ::Any, !Matched::Any, !Matched::Any...) at operators.jl:502\n +(!Matched::Complex{Bool}, ::Real) at complex.jl:292\n +(!Matched::Missing, ::Number) at missing.jl:93\n ...", "", "Stacktrace:", " [1] f(::String) at ./In[10]:1", " [2] top-level scope at In[14]:1" ] } ], "source": [ "f(\"hello\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "How can a function like `f(x)` work for any type? In Python, `x` would be a \"box\" that could contain anything, and it would then look up *at runtime* how to compute `x + 1`. But we just saw that untyped Julia `sum` code could be fast.\n", "\n", "The secret is **just-in-time (JIT) compilation**. The first time you call `f(x)` **with a new type of argument** `x`, it will **compile a new version of `f` specialized for that type**. The *next* time it calls `f(x)` with the same argument type\n", "\n", "So, right now, after evaluating the above code, we have *three* versions of `f` compiled and sitting in memory: one for `x` of type `Int` (we say `x::Int` in Julia), one for `x::Float64`, and one for `x::Vector{Int}`.\n", "\n", "We can even see what the compiled code for `f(x::Int)` looks like, either the [compiler (LLVM) bytecode](https://en.wikipedia.org/wiki/LLVM) or the low-level (below C!) [assembly code](https://en.wikipedia.org/wiki/Assembly_language):" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "; Function f\n", "; Location: In[10]:1\n", "define i64 @julia_f_35737(i64) {\n", "top:\n", "; Function +; {\n", "; Location: int.jl:53\n", " %1 = add i64 %0, 1\n", ";}\n", " ret i64 %1\n", "}\n" ] } ], "source": [ "@code_llvm f(1)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\t.section\t__TEXT,__text,regular,pure_instructions\n", "; Function f {\n", "; Location: In[10]:1\n", "; Function +; {\n", "; Location: In[10]:1\n", "\tdecl\t%eax\n", "\tleal\t1(%edi), %eax\n", ";}\n", "\tretl\n", "\tnopw\t%cs:(%eax,%eax)\n", ";}\n" ] } ], "source": [ "@code_native f(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's break this down. When you tell Julia's compiler that `x` is an `Int`, it:\n", "\n", "* It knows `x` fits into a 64-bit CPU register (and is passed to the function via a register).\n", "\n", "* Looks at `x + 1`. Since `x` and `1` are both `Int`, it knows it should call the `+` function for two `Int` values. This corresponds to *one machine instruction* `leaq` to add two 64-bit registers.\n", "\n", "* Since the `+` function here is so simple, it won't bother to do a function call. It will [inline](https://en.wikipedia.org/wiki/Inline_expansion) the `(+)(Int,Int)` function into the compiled `f(x)` code.\n", "\n", "* Since it now knows what `+` function it is calling, it knows that the *result* of the `+` is *also* an `Int`, and it can return it via register.\n", "\n", "This process works recursively if we define a new function `g(x)` that calls `f(x)`:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "g(x) = f(x) * 2\n", "g(1)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "; Function g\n", "; Location: In[17]:1\n", "define i64 @julia_g_35851(i64) {\n", "top:\n", "; Function *; {\n", "; Location: int.jl:54\n", " %1 = shl i64 %0, 1\n", " %2 = add i64 %1, 2\n", ";}\n", " ret i64 %2\n", "}\n" ] } ], "source": [ "@code_llvm g(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When it specialized `g` for `x::Int`, it not only figured out what `f` function to call, it not only *inlined* `f(x)` *into `g`*, but the compiler was smart enough to *combine the two additions* into a single addition `x + 4`." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "; Function h\n", "; Location: In[19]:1\n", "define i64 @julia_h_35853(i64) {\n", "top:\n", "; Function g; {\n", "; Location: In[17]:1\n", "; Function *; {\n", "; Location: int.jl:54\n", " %1 = shl i64 %0, 2\n", ";}}\n", "; Function *; {\n", "; Location: int.jl:54\n", " %2 = add i64 %1, 4\n", ";}\n", " ret i64 %2\n", "}\n" ] } ], "source": [ "h(x) = g(x) * 2\n", "@code_llvm h(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Julia's type inference is smart enough that it can figure out the return type even for recursive functions:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "fib (generic function with 1 method)" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fib(n::Integer) = n < 3 ? 1 : fib(n-1) + fib(n-2)" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10-element Array{Int64,1}:\n", " 1\n", " 1\n", " 2\n", " 3\n", " 5\n", " 8\n", " 13\n", " 21\n", " 34\n", " 55" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "[fib(n) for n = 1:10]" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10-element Array{Int64,1}:\n", " 1\n", " 1\n", " 2\n", " 3\n", " 5\n", " 8\n", " 13\n", " 21\n", " 34\n", " 55" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fib.(1:10)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Body\u001b[36m::Int64\u001b[39m\n", "\u001b[90m\u001b[72G│╻ <\u001b[1G\u001b[39m\u001b[90m1 \u001b[39m1 ─ %1 = (Base.slt_int)(n, 3)\u001b[36m::Bool\u001b[39m\n", "\u001b[90m\u001b[72G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m└── goto #3 if not %1\n", "\u001b[90m\u001b[72G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m2 ─ return 1\n", "\u001b[90m\u001b[72G│╻ -\u001b[1G\u001b[39m\u001b[90m \u001b[39m3 ─ %4 = (Base.sub_int)(n, 1)\u001b[36m::Int64\u001b[39m\n", "\u001b[90m\u001b[72G││╻ <\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %5 = (Base.slt_int)(%4, 3)\u001b[36m::Bool\u001b[39m\n", "\u001b[90m\u001b[72G││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m└── goto #5 if not %5\n", "\u001b[90m\u001b[72G││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m4 ─ goto #6\n", "\u001b[90m\u001b[72G││╻ -\u001b[1G\u001b[39m\u001b[90m \u001b[39m5 ─ %8 = (Base.sub_int)(%4, 1)\u001b[36m::Int64\u001b[39m\n", "\u001b[90m\u001b[72G││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %9 = invoke Main.fib(%8::Int64)\u001b[36m::Int64\u001b[39m\n", "\u001b[90m\u001b[72G││╻ -\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %10 = (Base.sub_int)(%4, 2)\u001b[36m::Int64\u001b[39m\n", "\u001b[90m\u001b[72G││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %11 = invoke Main.fib(%10::Int64)\u001b[36m::Int64\u001b[39m\n", "\u001b[90m\u001b[72G││╻ +\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %12 = (Base.add_int)(%9, %11)\u001b[36m::Int64\u001b[39m\n", "\u001b[90m\u001b[72G││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m└── goto #6\n", "\u001b[90m\u001b[72G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m6 ┄ %14 = φ (#4 => 1, #5 => %12)\u001b[36m::Int64\u001b[39m\n", "\u001b[90m\u001b[72G│╻ -\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %15 = (Base.sub_int)(n, 2)\u001b[36m::Int64\u001b[39m\n", "\u001b[90m\u001b[72G││╻ <\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %16 = (Base.slt_int)(%15, 3)\u001b[36m::Bool\u001b[39m\n", "\u001b[90m\u001b[72G││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m└── goto #8 if not %16\n", "\u001b[90m\u001b[72G││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m7 ─ goto #9\n", "\u001b[90m\u001b[72G││╻ -\u001b[1G\u001b[39m\u001b[90m \u001b[39m8 ─ %19 = (Base.sub_int)(%15, 1)\u001b[36m::Int64\u001b[39m\n", "\u001b[90m\u001b[72G││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %20 = invoke Main.fib(%19::Int64)\u001b[36m::Int64\u001b[39m\n", "\u001b[90m\u001b[72G││╻ -\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %21 = (Base.sub_int)(%15, 2)\u001b[36m::Int64\u001b[39m\n", "\u001b[90m\u001b[72G││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %22 = invoke Main.fib(%21::Int64)\u001b[36m::Int64\u001b[39m\n", "\u001b[90m\u001b[72G││╻ +\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %23 = (Base.add_int)(%20, %22)\u001b[36m::Int64\u001b[39m\n", "\u001b[90m\u001b[72G││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m└── goto #9\n", "\u001b[90m\u001b[72G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m9 ┄ %25 = φ (#7 => 1, #8 => %23)\u001b[36m::Int64\u001b[39m\n", "\u001b[90m\u001b[72G│╻ +\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %26 = (Base.add_int)(%14, %25)\u001b[36m::Int64\u001b[39m\n", "\u001b[90m\u001b[72G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m└── return %26\n" ] } ], "source": [ "@code_warntype fib(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Dispatch on the argument type\n", "\n", "It is often useful to declare the argument types in Julia. For example, above, we define `fib(n::Integer)`. This says that the argument must be *some* type of integer. If we give it a different number type, it will now give an error:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "ename": "MethodError", "evalue": "MethodError: no method matching fib(::Float64)\nClosest candidates are:\n fib(!Matched::Integer) at In[20]:1", "output_type": "error", "traceback": [ "MethodError: no method matching fib(::Float64)\nClosest candidates are:\n fib(!Matched::Integer) at In[20]:1", "", "Stacktrace:", " [1] top-level scope at In[24]:1" ] } ], "source": [ "fib(3.7)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`Integer` is an \"abstract\" type in Julia. There are many subtypes of `Integer` in Julia, including `Int64` (64-bit signed integers), `UInt8` (8-bit unsigned integers), and `BigInt` (arbitrary-precision integers: the number of digits grows as the numbers get larger and larger).\n", "\n", "Declaring the argument type has **no effect on performance** in Julia: the compiler automatically specializes the function when it is called, even if we declare no type at all. There are three main reasons to declare an argument type in Julia:\n", "\n", "* *Clarity*: Declaring the argument type can help readers understand the code. (However, *over-specifying* the type may make the function less general than it has to be!)\n", "\n", "* *Correctness*: Our `fib` function above would have given *some* answer if we allowed the user to pass `3.7`, but it probably wouldn't be the intended answer.\n", "\n", "* *Dispatch*: Julia allows you to define **different versions of a function** (different **methods**) for different argument types.\n", "\n", "For example, let's use this to define two versions of a [factorial](https://en.wikipedia.org/wiki/Factorial) function `myfact`, one for integers that recursively multiplies its arguments:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "myfact (generic function with 1 method)" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "function myfact(n::Integer) \n", " n < 0 && throw(DomainError(\"n must be positive\"))\n", " return n < 2 ? one(n) : n * myfact(n-1)\n", "end" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3628800" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "myfact(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You need `BigInt` for factorials of larger arguments, since factorials grow rapidly (faster than exponentially with `n`):" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "myfact(BigInt(100))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since we said `n::Integer`, this will give an error for a floating-point argument:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "ename": "MethodError", "evalue": "MethodError: no method matching myfact(::Float64)\nClosest candidates are:\n myfact(!Matched::Integer) at In[25]:2", "output_type": "error", "traceback": [ "MethodError: no method matching myfact(::Float64)\nClosest candidates are:\n myfact(!Matched::Integer) at In[25]:2", "", "Stacktrace:", " [1] top-level scope at In[28]:1" ] } ], "source": [ "myfact(3.7)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, it turns out that there is a very natural extension of the factorial to arbitrary real and complex numbers, via the [gamma function](https://en.wikipedia.org/wiki/Gamma_function): $n! = \\Gamma(n+1)$. Julia has a built-in `gamma(x)` function, so we can define `myfact` for other number types based on that:" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "myfact (generic function with 2 methods)" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "using SpecialFunctions\n", "myfact(x::Number) = gamma(x+1)" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "120.0" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "myfact(5.0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The \"factorial\" of $-\\frac{1}{2}$ is then $\\sqrt{\\pi}$:" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3.1415926535897936" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "myfact(-0.5)^2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now there are two different *methods* of `myfact`. You can get a list of the methods of any function by calling `methods`:" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/html": [ "2 methods for generic function myfact:" ], "text/plain": [ "# 2 methods for generic function \"myfact\":\n", "[1] myfact(n::Integer) in Main at In[25]:2\n", "[2] myfact(x::Number) in Main at In[29]:2" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "methods(myfact)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Multiple dispatch\n", "\n", "A key property of Julia is that is based around the principle of [multiple dispatch](https://en.wikipedia.org/wiki/Multiple_dispatch): when you call a function `f(arguments...)` in Julia, it picks the **most specific method of f** based on **the types of *all* of the arguments**.\n", "\n", "This can be thought of as a generalization of object-oriented programming (OOP). In an OOP language like Python, you would typically write `object.method(arguments...)`: based on the type of `object`, it will decide which version of `method` to call (it \"dispatches\" to the correct `method`). The Julia analogue is `method(object, arguments...)`. But whereas an OOP language would only look at the type of `object` (*single* dispatch), Julia looks at both the types of `object` *and* the other `arguments` (*multiple* dispatch).\n", "\n", "Multiple dispatch is very natural for talking about mathematical operations like `a + b`, which is just a call to the function `+` in Julia. In an OOP language like C++, `a + b` decides what `+` function to call based on the type of `a`, which is rather weird: the function is \"owned\" by its first argument. In Julia, it looks at both arguments, and there are in fact as huge number of `+` functions for different argument types:" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "scrolled": false }, "outputs": [ { "data": { "text/html": [ "163 methods for generic function +:" ], "text/plain": [ "# 163 methods for generic function \"+\":\n", "[1] +(x::Bool, z::Complex{Bool}) in Base at complex.jl:277\n", "[2] +(x::Bool, y::Bool) in Base at bool.jl:104\n", "[3] +(x::Bool) in Base at bool.jl:101\n", "[4] +(x::Bool, y::T) where T<:AbstractFloat in Base at bool.jl:112\n", "[5] +(x::Bool, z::Complex) in Base at complex.jl:284\n", "[6] +(a::Float16, b::Float16) in Base at float.jl:392\n", "[7] +(x::Float32, y::Float32) in Base at float.jl:394\n", "[8] +(x::Float64, y::Float64) in Base at float.jl:395\n", "[9] +(z::Complex{Bool}, x::Bool) in Base at complex.jl:278\n", "[10] +(z::Complex{Bool}, x::Real) in Base at complex.jl:292\n", "[11] +(::Missing, ::Missing) in Base at missing.jl:92\n", "[12] +(::Missing) in Base at missing.jl:79\n", "[13] +(::Missing, ::Number) in Base at missing.jl:93\n", "[14] +(level::Base.CoreLogging.LogLevel, inc::Integer) in Base.CoreLogging at logging.jl:106\n", "[15] +(c::BigInt, x::BigFloat) in Base.MPFR at mpfr.jl:353\n", "[16] +(a::BigInt, b::BigInt, c::BigInt, d::BigInt, e::BigInt) in Base.GMP at gmp.jl:443\n", "[17] +(a::BigInt, b::BigInt, c::BigInt, d::BigInt) in Base.GMP at gmp.jl:442\n", "[18] +(a::BigInt, b::BigInt, c::BigInt) in Base.GMP at gmp.jl:441\n", "[19] +(x::BigInt, y::BigInt) in Base.GMP at gmp.jl:412\n", "[20] +(x::BigInt, c::Union{UInt16, UInt32, UInt64, UInt8}) in Base.GMP at gmp.jl:449\n", "[21] +(x::BigInt, c::Union{Int16, Int32, Int64, Int8}) in Base.GMP at gmp.jl:455\n", "[22] +(a::BigFloat, b::BigFloat, c::BigFloat, d::BigFloat, e::BigFloat) in Base.MPFR at mpfr.jl:503\n", "[23] +(a::BigFloat, b::BigFloat, c::BigFloat, d::BigFloat) in Base.MPFR at mpfr.jl:496\n", "[24] +(a::BigFloat, b::BigFloat, c::BigFloat) in Base.MPFR at mpfr.jl:490\n", "[25] +(x::BigFloat, c::BigInt) in Base.MPFR at mpfr.jl:349\n", "[26] +(x::BigFloat, y::BigFloat) in Base.MPFR at mpfr.jl:318\n", "[27] +(x::BigFloat, c::Union{UInt16, UInt32, UInt64, UInt8}) in Base.MPFR at mpfr.jl:325\n", "[28] +(x::BigFloat, c::Union{Int16, Int32, Int64, Int8}) in Base.MPFR at mpfr.jl:333\n", "[29] +(x::BigFloat, c::Union{Float16, Float32, Float64}) in Base.MPFR at mpfr.jl:341\n", "[30] +(x::Dates.CompoundPeriod, y::Dates.CompoundPeriod) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/periods.jl:349\n", "[31] +(x::Dates.CompoundPeriod, y::Dates.Period) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/periods.jl:347\n", "[32] +(x::Dates.CompoundPeriod, y::Dates.TimeType) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/periods.jl:385\n", "[33] +(x::Dates.Date, y::Dates.Day) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/arithmetic.jl:78\n", "[34] +(x::Dates.Date, y::Dates.Week) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/arithmetic.jl:76\n", "[35] +(dt::Dates.Date, z::Dates.Month) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/arithmetic.jl:59\n", "[36] +(dt::Dates.Date, y::Dates.Year) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/arithmetic.jl:32\n", "[37] +(dt::Dates.Date, t::Dates.Time) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/arithmetic.jl:20\n", "[38] +(t::Dates.Time, dt::Dates.Date) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/arithmetic.jl:24\n", "[39] +(x::Dates.Time, y::Dates.TimePeriod) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/arithmetic.jl:82\n", "[40] +(dt::Dates.DateTime, z::Dates.Month) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/arithmetic.jl:52\n", "[41] +(dt::Dates.DateTime, y::Dates.Year) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/arithmetic.jl:28\n", "[42] +(x::Dates.DateTime, y::Dates.Period) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/arithmetic.jl:80\n", "[43] +(B::BitArray{2}, J::LinearAlgebra.UniformScaling) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/uniformscaling.jl:88\n", "[44] +(a::Pkg.Resolve.VersionWeights.VersionWeight, b::Pkg.Resolve.VersionWeights.VersionWeight) in Pkg.Resolve.VersionWeights at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Pkg/src/resolve/VersionWeights.jl:26\n", "[45] +(a::Pkg.Resolve.MaxSum.FieldValues.FieldValue, b::Pkg.Resolve.MaxSum.FieldValues.FieldValue) in Pkg.Resolve.MaxSum.FieldValues at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Pkg/src/resolve/FieldValues.jl:49\n", "[46] +(y::AbstractFloat, x::Bool) in Base at bool.jl:114\n", "[47] +(x::T, y::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at int.jl:53\n", "[48] +(c::Union{UInt16, UInt32, UInt64, UInt8}, x::BigInt) in Base.GMP at gmp.jl:450\n", "[49] +(c::Union{Int16, Int32, Int64, Int8}, x::BigInt) in Base.GMP at gmp.jl:456\n", "[50] +(a::Integer, b::Integer) in Base at int.jl:800\n", "[51] +(x::Integer, y::Ptr) in Base at pointer.jl:157\n", "[52] +(z::Complex, w::Complex) in Base at complex.jl:266\n", "[53] +(z::Complex, x::Bool) in Base at complex.jl:285\n", "[54] +(x::Real, z::Complex{Bool}) in Base at complex.jl:291\n", "[55] +(x::Real, z::Complex) in Base at complex.jl:303\n", "[56] +(z::Complex, x::Real) in Base at complex.jl:304\n", "[57] +(x::Rational, y::Rational) in Base at rational.jl:248\n", "[58] +(x::Integer, y::AbstractChar) in Base at char.jl:208\n", "[59] +(c::Union{UInt16, UInt32, UInt64, UInt8}, x::BigFloat) in Base.MPFR at mpfr.jl:329\n", "[60] +(c::Union{Int16, Int32, Int64, Int8}, x::BigFloat) in Base.MPFR at mpfr.jl:337\n", "[61] +(c::Union{Float16, Float32, Float64}, x::BigFloat) in Base.MPFR at mpfr.jl:345\n", "[62] +(x::AbstractIrrational, y::AbstractIrrational) in Base at irrationals.jl:133\n", "[63] +(x::Number) in Base at operators.jl:477\n", "[64] +(x::T, y::T) where T<:Number in Base at promotion.jl:411\n", "[65] +(x::Number, y::Number) in Base at promotion.jl:313\n", "[66] +(r1::OrdinalRange, r2::OrdinalRange) in Base at range.jl:978\n", "[67] +(r1::LinRange{T}, r2::LinRange{T}) where T in Base at range.jl:985\n", "[68] +(r1::StepRangeLen{T,R,S} where S, r2::StepRangeLen{T,R,S} where S) where {R<:TwicePrecision, T} in Base at twiceprecision.jl:557\n", "[69] +(r1::StepRangeLen{T,S,S1} where S1, r2::StepRangeLen{T,S,S1} where S1) where {T, S} in Base at range.jl:1001\n", "[70] +(r1::Union{LinRange, OrdinalRange, StepRangeLen}, r2::Union{LinRange, OrdinalRange, StepRangeLen}) in Base at range.jl:993\n", "[71] +(x::Ptr, y::Integer) in Base at pointer.jl:155\n", "[72] +(x::Base.TwicePrecision, y::Number) in Base at twiceprecision.jl:265\n", "[73] +(x::Number, y::Base.TwicePrecision) in Base at twiceprecision.jl:268\n", "[74] +(x::Base.TwicePrecision{T}, y::Base.TwicePrecision{T}) where T in Base at twiceprecision.jl:271\n", "[75] +(x::Base.TwicePrecision, y::Base.TwicePrecision) in Base at twiceprecision.jl:275\n", "[76] +(A::Array, Bs::Array...) in Base at arraymath.jl:44\n", "[77] +(A::BitArray, B::BitArray) in Base at bitarray.jl:1074\n", "[78] +(A::Array{T,2} where T, B::LinearAlgebra.Diagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:88\n", "[79] +(A::Array{T,2} where T, B::LinearAlgebra.Bidiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:88\n", "[80] +(A::Array{T,2} where T, B::LinearAlgebra.Tridiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:88\n", "[81] +(A::Array{T,2} where T, B::LinearAlgebra.SymTridiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:97\n", "[82] +(A::Array{T,2} where T, B::LinearAlgebra.AbstractTriangular) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:125\n", "[83] +(A::Array, B::SparseArrays.SparseMatrixCSC) in SparseArrays at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/SparseArrays/src/sparsematrix.jl:1574\n", "[84] +(x::Union{DenseArray{#s561,N}, ReinterpretArray{#s561,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray}, ReshapedArray{#s561,N,A,MI} where MI<:Tuple{Vararg{SignedMultiplicativeInverse{Int64},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray}, SubArray{#s561,N,A,I,L} where L where I<:Tuple{Vararg{Union{Int64, AbstractRange{Int64}, AbstractCartesianIndex},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, ReshapedArray{T,N,A,MI} where MI<:Tuple{Vararg{SignedMultiplicativeInverse{Int64},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, DenseArray}} where N where #s561<:Union{CompoundPeriod, Period}) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/periods.jl:358\n", "[85] +(X::Union{DenseArray{#s561,N}, ReinterpretArray{#s561,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray}, ReshapedArray{#s561,N,A,MI} where MI<:Tuple{Vararg{SignedMultiplicativeInverse{Int64},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray}, SubArray{#s561,N,A,I,L} where L where I<:Tuple{Vararg{Union{Int64, AbstractRange{Int64}, AbstractCartesianIndex},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, ReshapedArray{T,N,A,MI} where MI<:Tuple{Vararg{SignedMultiplicativeInverse{Int64},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, DenseArray}} where N where #s561<:Union{CompoundPeriod, Period}, Y::Union{DenseArray{#s560,N}, ReinterpretArray{#s560,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray}, ReshapedArray{#s560,N,A,MI} where MI<:Tuple{Vararg{SignedMultiplicativeInverse{Int64},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray}, SubArray{#s560,N,A,I,L} where L where I<:Tuple{Vararg{Union{Int64, AbstractRange{Int64}, AbstractCartesianIndex},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, ReshapedArray{T,N,A,MI} where MI<:Tuple{Vararg{SignedMultiplicativeInverse{Int64},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, DenseArray}} where N where #s560<:Union{CompoundPeriod, Period}) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/periods.jl:362\n", "[86] +(x::Union{DenseArray{#s561,N}, ReinterpretArray{#s561,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray}, ReshapedArray{#s561,N,A,MI} where MI<:Tuple{Vararg{SignedMultiplicativeInverse{Int64},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray}, SubArray{#s561,N,A,I,L} where L where I<:Tuple{Vararg{Union{Int64, AbstractRange{Int64}, AbstractCartesianIndex},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, ReshapedArray{T,N,A,MI} where MI<:Tuple{Vararg{SignedMultiplicativeInverse{Int64},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, DenseArray}} where N where #s561<:Union{CompoundPeriod, Period}, y::Dates.TimeType) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/arithmetic.jl:87\n", "[87] +(r::AbstractRange{#s561} where #s561<:Dates.TimeType, x::Dates.Period) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/ranges.jl:58\n", "[88] +(A::LinearAlgebra.SymTridiagonal, B::LinearAlgebra.SymTridiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/tridiag.jl:158\n", "[89] +(A::LinearAlgebra.Tridiagonal, B::LinearAlgebra.Tridiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/tridiag.jl:644\n", "[90] +(A::LinearAlgebra.UpperTriangular, B::LinearAlgebra.UpperTriangular) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/triangular.jl:487\n", "[91] +(A::LinearAlgebra.LowerTriangular, B::LinearAlgebra.LowerTriangular) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/triangular.jl:488\n", "[92] +(A::LinearAlgebra.UpperTriangular, B::LinearAlgebra.UnitUpperTriangular) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/triangular.jl:489\n", "[93] +(A::LinearAlgebra.LowerTriangular, B::LinearAlgebra.UnitLowerTriangular) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/triangular.jl:490\n", "[94] +(A::LinearAlgebra.UnitUpperTriangular, B::LinearAlgebra.UpperTriangular) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/triangular.jl:491\n", "[95] +(A::LinearAlgebra.UnitLowerTriangular, B::LinearAlgebra.LowerTriangular) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/triangular.jl:492\n", "[96] +(A::LinearAlgebra.UnitUpperTriangular, B::LinearAlgebra.UnitUpperTriangular) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/triangular.jl:493\n", "[97] +(A::LinearAlgebra.UnitLowerTriangular, B::LinearAlgebra.UnitLowerTriangular) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/triangular.jl:494\n", "[98] +(A::LinearAlgebra.AbstractTriangular, B::LinearAlgebra.AbstractTriangular) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/triangular.jl:495\n", "[99] +(Da::LinearAlgebra.Diagonal, Db::LinearAlgebra.Diagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/diagonal.jl:152\n", "[100] +(A::LinearAlgebra.Bidiagonal, B::LinearAlgebra.Bidiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/bidiag.jl:304\n", "[101] +(UL::LinearAlgebra.UnitUpperTriangular, J::LinearAlgebra.UniformScaling) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/uniformscaling.jl:104\n", "[102] +(UL::LinearAlgebra.UnitLowerTriangular, J::LinearAlgebra.UniformScaling) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/uniformscaling.jl:104\n", "[103] +(A::SparseArrays.SparseMatrixCSC, J::LinearAlgebra.UniformScaling) in SparseArrays at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/SparseArrays/src/sparsematrix.jl:3476\n", "[104] +(A::AbstractArray{T,2} where T, J::LinearAlgebra.UniformScaling) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/uniformscaling.jl:114\n", "[105] +(A::LinearAlgebra.Diagonal, B::LinearAlgebra.Bidiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:87\n", "[106] +(A::LinearAlgebra.Bidiagonal, B::LinearAlgebra.Diagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:88\n", "[107] +(A::LinearAlgebra.Diagonal, B::LinearAlgebra.Tridiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:87\n", "[108] +(A::LinearAlgebra.Tridiagonal, B::LinearAlgebra.Diagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:88\n", "[109] +(A::LinearAlgebra.Diagonal, B::Array{T,2} where T) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:87\n", "[110] +(A::LinearAlgebra.Bidiagonal, B::LinearAlgebra.Tridiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:87\n", "[111] +(A::LinearAlgebra.Tridiagonal, B::LinearAlgebra.Bidiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:88\n", "[112] +(A::LinearAlgebra.Bidiagonal, B::Array{T,2} where T) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:87\n", "[113] +(A::LinearAlgebra.Tridiagonal, B::Array{T,2} where T) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:87\n", "[114] +(A::LinearAlgebra.SymTridiagonal, B::LinearAlgebra.Tridiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:96\n", "[115] +(A::LinearAlgebra.Tridiagonal, B::LinearAlgebra.SymTridiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:97\n", "[116] +(A::LinearAlgebra.SymTridiagonal, B::Array{T,2} where T) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:96\n", "[117] +(A::LinearAlgebra.Diagonal, B::LinearAlgebra.SymTridiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:105\n", "[118] +(A::LinearAlgebra.SymTridiagonal, B::LinearAlgebra.Diagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:106\n", "[119] +(A::LinearAlgebra.Bidiagonal, B::LinearAlgebra.SymTridiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:105\n", "[120] +(A::LinearAlgebra.SymTridiagonal, B::LinearAlgebra.Bidiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:106\n", "[121] +(A::LinearAlgebra.Diagonal, B::LinearAlgebra.UpperTriangular) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:117\n", "[122] +(A::LinearAlgebra.UpperTriangular, B::LinearAlgebra.Diagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:118\n", "[123] +(A::LinearAlgebra.Diagonal, B::LinearAlgebra.UnitUpperTriangular) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:117\n", "[124] +(A::LinearAlgebra.UnitUpperTriangular, B::LinearAlgebra.Diagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:118\n", "[125] +(A::LinearAlgebra.Diagonal, B::LinearAlgebra.LowerTriangular) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:117\n", "[126] +(A::LinearAlgebra.LowerTriangular, B::LinearAlgebra.Diagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:118\n", "[127] +(A::LinearAlgebra.Diagonal, B::LinearAlgebra.UnitLowerTriangular) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:117\n", "[128] +(A::LinearAlgebra.UnitLowerTriangular, B::LinearAlgebra.Diagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:118\n", "[129] +(A::LinearAlgebra.AbstractTriangular, B::LinearAlgebra.SymTridiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:124\n", "[130] +(A::LinearAlgebra.SymTridiagonal, B::LinearAlgebra.AbstractTriangular) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:125\n", "[131] +(A::LinearAlgebra.AbstractTriangular, B::LinearAlgebra.Tridiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:124\n", "[132] +(A::LinearAlgebra.Tridiagonal, B::LinearAlgebra.AbstractTriangular) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:125\n", "[133] +(A::LinearAlgebra.AbstractTriangular, B::LinearAlgebra.Bidiagonal) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:124\n", "[134] +(A::LinearAlgebra.Bidiagonal, B::LinearAlgebra.AbstractTriangular) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:125\n", "[135] +(A::LinearAlgebra.AbstractTriangular, B::Array{T,2} where T) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/special.jl:124\n", "[136] +(A::SparseArrays.SparseMatrixCSC, B::SparseArrays.SparseMatrixCSC) in SparseArrays at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/SparseArrays/src/sparsematrix.jl:1570\n", "[137] +(A::SparseArrays.SparseMatrixCSC, B::Array) in SparseArrays at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/SparseArrays/src/sparsematrix.jl:1573\n", "[138] +(x::SparseArrays.AbstractSparseArray{Tv,Ti,1} where Ti where Tv, y::SparseArrays.AbstractSparseArray{Tv,Ti,1} where Ti where Tv) in SparseArrays at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/SparseArrays/src/sparsevector.jl:1290\n", "[139] +(x::AbstractArray{#s57,N} where N where #s57<:Number) in Base at abstractarraymath.jl:98\n", "[140] +(A::AbstractArray, B::AbstractArray) in Base at arraymath.jl:38\n", "[141] +(x::T, y::Integer) where T<:AbstractChar in Base at char.jl:207\n", "[142] +(index1::CartesianIndex{N}, index2::CartesianIndex{N}) where N in Base.IteratorsMD at multidimensional.jl:106\n", "[143] +(::Number, ::Missing) in Base at missing.jl:94\n", "[144] +(x::P, y::P) where P<:Dates.Period in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/periods.jl:69\n", "[145] +(x::Dates.Period, y::Dates.Period) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/periods.jl:346\n", "[146] +(y::Dates.Period, x::Dates.CompoundPeriod) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/periods.jl:348\n", "[147] +(x::Union{CompoundPeriod, Period}) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/periods.jl:357\n", "[148] +(x::Dates.TimeType) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/arithmetic.jl:8\n", "[149] +(a::Dates.TimeType, b::Dates.Period, c::Dates.Period) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/periods.jl:376\n", "[150] +(a::Dates.TimeType, b::Dates.Period, c::Dates.Period, d::Dates.Period...) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/periods.jl:377\n", "[151] +(x::Dates.TimeType, y::Dates.CompoundPeriod) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/periods.jl:380\n", "[152] +(x::Dates.Instant) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/arithmetic.jl:4\n", "[153] +(y::Dates.Period, x::Dates.TimeType) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/arithmetic.jl:84\n", "[154] +(x::AbstractArray{#s561,N} where N where #s561<:Dates.TimeType, y::Union{CompoundPeriod, Period}) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/arithmetic.jl:86\n", "[155] +(x::Dates.Period, r::AbstractRange{#s561} where #s561<:Dates.TimeType) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/ranges.jl:57\n", "[156] +(y::Union{CompoundPeriod, Period}, x::AbstractArray{#s561,N} where N where #s561<:Dates.TimeType) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/arithmetic.jl:88\n", "[157] +(y::Dates.TimeType, x::Union{DenseArray{#s561,N}, ReinterpretArray{#s561,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray}, ReshapedArray{#s561,N,A,MI} where MI<:Tuple{Vararg{SignedMultiplicativeInverse{Int64},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray}, SubArray{#s561,N,A,I,L} where L where I<:Tuple{Vararg{Union{Int64, AbstractRange{Int64}, AbstractCartesianIndex},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, ReshapedArray{T,N,A,MI} where MI<:Tuple{Vararg{SignedMultiplicativeInverse{Int64},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, DenseArray}} where N where #s561<:Union{CompoundPeriod, Period}) in Dates at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/Dates/src/arithmetic.jl:89\n", "[158] +(J::LinearAlgebra.UniformScaling, x::Number) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/uniformscaling.jl:82\n", "[159] +(x::Number, J::LinearAlgebra.UniformScaling) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/uniformscaling.jl:83\n", "[160] +(J1::LinearAlgebra.UniformScaling, J2::LinearAlgebra.UniformScaling) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/uniformscaling.jl:87\n", "[161] +(J::LinearAlgebra.UniformScaling, B::BitArray{2}) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/uniformscaling.jl:89\n", "[162] +(J::LinearAlgebra.UniformScaling, A::AbstractArray{T,2} where T) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.0/LinearAlgebra/src/uniformscaling.jl:90\n", "[163] +(a, b, c, xs...) in Base at operators.jl:502" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "methods(+)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can use the `@which` method to see which method is called for a specific set of arguments:" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/html": [ "+{T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8}}(x::T, y::T) in Base at int.jl:53" ], "text/plain": [ "+(x::T, y::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at int.jl:53" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "@which (+)(3,4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here is a somewhat artificial example where we define three methods for `f(x,y)` depending on the argument types, and we can see how Julia picks which one to call:" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "f (generic function with 4 methods)" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f(x,y) = 3\n", "f(x::Integer,y::Integer) = 4\n", "f(x::Number,y::Integer) = 5" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(4, 5, 3)" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f(3,4), f(3.5,4), f(4,3.5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One nice thing about Julia's \"verb-centric\" multiple-dispatch approach (as opposed to OOP's \"noun-centric\" approach) is that we can add new methods and functions to existing types (unlike OOP where you need to create a subclass to add new methods).\n", "\n", "For example, string concatenation in Julia is performed by the `*` operator:" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"37\"" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"3\" * \"7\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `+` operator is not defined for strings:" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "ename": "MethodError", "evalue": "MethodError: no method matching +(::String, ::String)\nClosest candidates are:\n +(::Any, ::Any, !Matched::Any, !Matched::Any...) at operators.jl:502", "output_type": "error", "traceback": [ "MethodError: no method matching +(::String, ::String)\nClosest candidates are:\n +(::Any, ::Any, !Matched::Any, !Matched::Any...) at operators.jl:502", "", "Stacktrace:", " [1] top-level scope at In[38]:1" ] } ], "source": [ "\"3\" + \"7\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But we can define it if we want! Here, instead of defining our own function like `myfact`, we are adding a new method of an *existing* function that was defined in `Base` (Julia's standard library). So, we need to explicitly tell Julia that we are extending `Base.+`, not defining a new function that happens to be called `+`:" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [], "source": [ "Base.:+(a::AbstractString, b::AbstractString) = a * \" + \" * b" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"3 + 7\"" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"3\" + \"7\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have defined `+`, we can even sum arrays of strings, because `sum` works for anything that defines `+` (and `zero` for summing empty arrays):" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"The + quick + brown + fox + jumps + over + the + lazy + dog.\"" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sum([\"The\", \"quick\", \"brown\", \"fox\", \"jumps\", \"over\", \"the\", \"lazy\", \"dog.\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Defeating type inference: Type instabilities\n", "\n", "To get good performance, there are some fairly simple rules that you need to follow in Julia code to avoid defeating the compiler's type inference. See also the [performance tips section of the Julia manual](http://docs.julialang.org/en/stable/manual/performance-tips/).\n", "\n", "Three of the most important are:\n", "\n", "* Don't use (non-constant) global variables in critical code — put your critical code into a function (this is good advice anyway, from a software-engineering standpoint). The compiler assumes that a **global variable can change type at any time**, so it is always stored in a \"box\", and \"taints\" anything that depends on it.\n", "\n", "* Local variables should be \"type-stable\": **don't change the type of a variable inside a function**. Use a new variable instead.\n", "\n", "* Functions should be \"type-stable\": **a function's return type should only depend on the argument types, not on the argument values**.\n", "\n", "To diagnose all of these problems, the `@code_warntype` macro that we used above is your friend. If it labels any variables (or the function's return value) as `Any` or `Union{...}`, it means that the compiler couldn't figure out a precise type.\n", "\n", "The third point, type-stability of functions, leads to lots of important but subtle choices in library design. For example, consider the (built-in) `sqrt(x)` function, which computes $\\sqrt{x}$:" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2.0" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sqrt(4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You might think that `sqrt(-1)` should return $i$ (or `im`, in Julia syntax). (Matlab's `sqrt` function does this.) Instead, we get:" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "ename": "DomainError", "evalue": "DomainError with -1.0:\nsqrt will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).", "output_type": "error", "traceback": [ "DomainError with -1.0:\nsqrt will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).", "", "Stacktrace:", " [1] throw_complex_domainerror(::Symbol, ::Float64) at ./math.jl:31", " [2] sqrt at ./math.jl:492 [inlined]", " [3] sqrt(::Int64) at ./math.jl:518", " [4] top-level scope at In[43]:1" ] } ], "source": [ "sqrt(-1)" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.0 + 1.0im" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sqrt(-1 + 0im)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Why did Julia implement `sqrt` in this silly way, throwing an error for negative arguments unless you add a zero imaginary part? Any reasonable person wants an imaginary result from `sqrt(-1)`, surely?\n", "\n", "The problem is that defining `sqrt` to return an imaginary result from `sqrt(-1)` would **not be type stable**: `sqrt(x)` would return a real result for non-negative real `x`, and a complex result for negative real `x`, so the **return type would depend on the value of `x`** and **not just its type.**\n", "\n", "That would defeat type inference, not just for the `sqrt` function, but for **anything the sqrt function touches**. Unless the compiler can somehow figure out `x ≥ 0`, it will have to either store the result in a \"box\" or compile two branches of the result. Let's see how that works by defining our own square-root function:" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "mysqrt (generic function with 2 methods)" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mysqrt(x::Complex) = sqrt(x)\n", "mysqrt(x::Real) = x < 0 ? sqrt(complex(x)) : sqrt(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This definition is an example of Julia's [multiple dispatch style](http://docs.julialang.org/en/stable/manual/methods/), which in some sense is a generalization of object-oriented programming but focuses on \"verbs\" (functions) rather than nouns. We will discuss this more in a later lecture.\n", "\n", "The `::Complex` and `::Real` are argument-type declarations. Such declarations are **not related to performance**, but instead **act as a \"filter\"** to allow us to have one version of `mysqrt` for complex arguments and another for real arguments." ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1.4142135623730951" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mysqrt(2)" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.0 + 1.4142135623730951im" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mysqrt(-2)" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.0 + 1.4142135623730951im" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mysqrt(-2+0im)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Looks great, right? But let's see what happens to type inference in a function that calls `mysqrt` instead of `sqrt`:" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Body\u001b[91m\u001b[1m::Union{Complex{Float64}, Float64}\u001b[22m\u001b[39m\n", "\u001b[90m\u001b[57G│╻╷ mysqrt\u001b[1G\u001b[39m\u001b[90m1 \u001b[39m1 ── %1 = (Base.slt_int)(x, 0)\u001b[36m::Bool\u001b[39m\n", "\u001b[90m\u001b[57G││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m└─── goto #3 if not %1\n", "\u001b[90m\u001b[57G││╻╷╷╷ sqrt\u001b[1G\u001b[39m\u001b[90m \u001b[39m2 ── %3 = (Base.sitofp)(Float64, x)\u001b[36m::Float64\u001b[39m\n", "\u001b[90m\u001b[57G│││╻╷ float\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %4 = (Base.sitofp)(Float64, 0)\u001b[36m::Float64\u001b[39m\n", "\u001b[90m\u001b[57G││││╻ Type\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %5 = %new(Complex{Float64}, %3, %4)\u001b[36m::Complex{Float64}\u001b[39m\n", "\u001b[90m\u001b[57G│││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %6 = invoke Base.sqrt(%5::Complex{Float64})\u001b[36m::Complex{Float64}\u001b[39m\n", "\u001b[90m\u001b[57G││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m└─── goto #8\n", "\u001b[90m\u001b[57G│││╻╷╷ float\u001b[1G\u001b[39m\u001b[90m \u001b[39m3 ── %8 = (Base.sitofp)(Float64, x)\u001b[36m::Float64\u001b[39m\n", "\u001b[90m\u001b[57G││││╻ <\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %9 = (Base.lt_float)(%8, 0.0)\u001b[36m::Bool\u001b[39m\n", "\u001b[90m\u001b[57G││││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m└─── goto #5 if not %9\n", "\u001b[90m\u001b[57G││││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m4 ── invoke Base.Math.throw_complex_domainerror(:sqrt::Symbol, %8::Float64)\n", "\u001b[90m\u001b[57G││││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m└─── $(Expr(:unreachable))\n", "\u001b[90m\u001b[57G││││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m5 ── %13 = (Base.Math.sqrt_llvm)(%8)\u001b[36m::Float64\u001b[39m\n", "\u001b[90m\u001b[57G││││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m└─── goto #6\n", "\u001b[90m\u001b[57G│││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m6 ── goto #7\n", "\u001b[90m\u001b[57G││ \u001b[1G\u001b[39m\u001b[90m \u001b[39m7 ── goto #8\n", "\u001b[90m\u001b[57G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m8 ┄─ %17 = φ (#2 => %6, #7 => %13)\u001b[91m\u001b[1m::Union{Complex{Float64}, Float64}\u001b[22m\u001b[39m\n", "\u001b[90m\u001b[57G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %18 = (isa)(%17, Complex{Float64})\u001b[36m::Bool\u001b[39m\n", "\u001b[90m\u001b[57G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m└─── goto #10 if not %18\n", "\u001b[90m\u001b[57G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m9 ── %20 = π (%17, \u001b[36mComplex{Float64}\u001b[39m)\n", "\u001b[90m\u001b[57G││╻╷ real\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %21 = (Base.getfield)(%20, :re)\u001b[36m::Float64\u001b[39m\n", "\u001b[90m\u001b[57G│││╻╷╷╷ promote\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %22 = (Base.sitofp)(Float64, 1)\u001b[36m::Float64\u001b[39m\n", "\u001b[90m\u001b[57G│││╻ +\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %23 = (Base.add_float)(%22, %21)\u001b[36m::Float64\u001b[39m\n", "\u001b[90m\u001b[57G│││╻ getproperty\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %24 = (Base.getfield)(%20, :im)\u001b[36m::Float64\u001b[39m\n", "\u001b[90m\u001b[57G│││╻ Type\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %25 = %new(Complex{Float64}, %23, %24)\u001b[36m::Complex{Float64}\u001b[39m\n", "\u001b[90m\u001b[57G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m└─── goto #13\n", "\u001b[90m\u001b[57G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m10 ─ %27 = (isa)(%17, Float64)\u001b[36m::Bool\u001b[39m\n", "\u001b[90m\u001b[57G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m└─── goto #12 if not %27\n", "\u001b[90m\u001b[57G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m11 ─ %29 = π (%17, \u001b[36mFloat64\u001b[39m)\n", "\u001b[90m\u001b[57G││╻╷╷╷ promote\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %30 = (Base.sitofp)(Float64, 1)\u001b[36m::Float64\u001b[39m\n", "\u001b[90m\u001b[57G││╻ +\u001b[1G\u001b[39m\u001b[90m \u001b[39m│ %31 = (Base.add_float)(%29, %30)\u001b[36m::Float64\u001b[39m\n", "\u001b[90m\u001b[57G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m└─── goto #13\n", "\u001b[90m\u001b[57G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m12 ─ (Core.throw)(ErrorException(\"fatal error in type inference (type bound)\"))\n", "\u001b[90m\u001b[57G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m└─── $(Expr(:unreachable))\n", "\u001b[90m\u001b[57G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m13 ┄ %35 = φ (#9 => %25, #11 => %31)\u001b[91m\u001b[1m::Union{Complex{Float64}, Float64}\u001b[22m\u001b[39m\n", "\u001b[90m\u001b[57G│ \u001b[1G\u001b[39m\u001b[90m \u001b[39m└─── return %35\n" ] } ], "source": [ "slowfun(x) = mysqrt(x) + 1\n", "@code_warntype slowfun(2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because the compiler **doesn't know at compile-time that x is positive** (at compile-time it **uses only types, not values**, it doesn't know whether the result is real (`Float64`) or complex (`Complex{Float64}`) and has to store it in a \"box\". This kills performance." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Defining our own types\n", "\n", "Let's define our own type to represent a **\"point\" in two dimensions**. Each point will have an $(x,y)$ location. So that we can use the points with our `sum` functions above, we'll also define `+` and `zero` functions to do the obvious **vector addition**.\n", "\n", "One such definition in Julia is:" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Point1(3, 4)" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mutable struct Point1\n", " x\n", " y\n", "end\n", "Base.:+(p::Point1, q::Point1) = Point1(p.x + q.x, p.y + q.y)\n", "Base.zero(::Type{Point1}) = Point1(0,0)\n", "\n", "Point1(3,4)" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Point1(8, 10)" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Point1(3,4) + Point1(5,6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Our type is very generic, and can hold any type of `x` and `y` values:" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Point1(3.7, 4 + 5im)" ] }, "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Point1(3.7, 4+5im)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Perhaps too generic:" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Point1(\"x\", [3, 4, 5])" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Point1(\"x\", [3,4,5])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since `x` and `y` can be *anything*, they must be **pointers to \"boxes\"**. This is **bad news for performance**.\n", "\n", "A `mutable struct` is *mutable*, which means we can create a `Point1` object and then change `x` or `y`:" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Point1(7, 4)" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p = Point1(3,4)\n", "p.x = 7\n", "p" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This means that every reference to a `Point1` object must be a *pointer* to an object stored elsewhere in memory, because *how else would we \"know\" when an object changes?* Furthermore, an **array of `Point1` objects must be an array of pointers** (which is **bad news for performance** again):" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3-element Array{Point1,1}:\n", " Point1(7, 4)\n", " Point1(7, 4)\n", " Point1(7, 4)" ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" } ], "source": [ "P = [p,p,p]" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3-element Array{Point1,1}:\n", " Point1(7, 8)\n", " Point1(7, 8)\n", " Point1(7, 8)" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p.y = 8\n", "P" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's test this out by creating an array of `Point1` objects and summing it. Ideally, this would be about twice as slow as summing an equal-length array of numbers, since there are twice as many numbers to sum. But because of all of the boxes and pointer-chasing, it should be far slower.\n", "\n", "To create the array, we'll call the `Point1(x,y)` constructor with our array `a`, using Julia's [\"dot-call\" syntax](http://docs.julialang.org/en/stable/manual/functions/#dot-syntax-for-vectorizing-functions) that applies a function \"element-wise\" to arrays:" ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10000000-element Array{Point1,1}:\n", " Point1(0.41611080115182997, 0.41611080115182997) \n", " Point1(0.09121781540916163, 0.09121781540916163) \n", " Point1(0.5821689193788118, 0.5821689193788118) \n", " Point1(0.4450648623203812, 0.4450648623203812) \n", " Point1(0.31025284561131117, 0.31025284561131117) \n", " Point1(0.13470267547220938, 0.13470267547220938) \n", " Point1(0.9686840088959152, 0.9686840088959152) \n", " Point1(0.017240806807339526, 0.017240806807339526)\n", " Point1(0.03285701827401222, 0.03285701827401222) \n", " Point1(0.8007999312473584, 0.8007999312473584) \n", " Point1(0.6967238705169183, 0.6967238705169183) \n", " Point1(0.6365055896997436, 0.6365055896997436) \n", " Point1(0.8960119603293422, 0.8960119603293422) \n", " ⋮ \n", " Point1(0.33070791321634574, 0.33070791321634574) \n", " Point1(0.6843509271585413, 0.6843509271585413) \n", " Point1(0.5179481631937215, 0.5179481631937215) \n", " Point1(0.26483888958030555, 0.26483888958030555) \n", " Point1(0.7830762795414743, 0.7830762795414743) \n", " Point1(0.9102152866041469, 0.9102152866041469) \n", " Point1(0.5857308463542181, 0.5857308463542181) \n", " Point1(0.192522005018664, 0.192522005018664) \n", " Point1(0.7633399781271266, 0.7633399781271266) \n", " Point1(0.7675359710514917, 0.7675359710514917) \n", " Point1(0.6697973896081715, 0.6697973896081715) \n", " Point1(0.9035572561313561, 0.9035572561313561) " ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a1 = Point1.(a, a)" ] }, { "cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 503.527 ms (29999997 allocations: 610.35 MiB)\n" ] }, { "data": { "text/plain": [ "Point1(5.000882508762066e6, 5.000882508762066e6)" ] }, "execution_count": 58, "metadata": {}, "output_type": "execute_result" } ], "source": [ "@btime sum($a1)" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 733.217 ms (30000001 allocations: 610.35 MiB)\n" ] }, { "data": { "text/plain": [ "Point1(5.0008825087619815e6, 5.0008825087619815e6)" ] }, "execution_count": 59, "metadata": {}, "output_type": "execute_result" } ], "source": [ "@btime mysum($a1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The time is at least **50× slower** than we would like, but consistent with our other timing results on \"boxed\" values from last lecture." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### An imperfect solution: A concrete immutable type\n", "\n", "We can avoid these two problems by:\n", "\n", "* Declare the types of `x` and `y` to be *concrete* types, so that they don't need to be pointers to boxes.\n", "* Declare our Point to be an [immutable](https://en.wikipedia.org/wiki/Immutable_object) type (`x` and `y` cannot change), so that Julia is not forced to make every reference to a Point into a pointer: just `struct`, not `mutable struct`:" ] }, { "cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Point2(3.0, 4.0)" ] }, "execution_count": 60, "metadata": {}, "output_type": "execute_result" } ], "source": [ "struct Point2\n", " x::Float64\n", " y::Float64\n", "end\n", "Base.:+(p::Point2, q::Point2) = Point2(p.x + q.x, p.y + q.y)\n", "Base.zero(::Type{Point2}) = Point2(0.0,0.0)\n", "\n", "Point2(3,4)" ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Point2(8.0, 10.0)" ] }, "execution_count": 61, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Point2(3,4) + Point2(5,6)" ] }, { "cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3-element Array{Point2,1}:\n", " Point2(3.0, 4.0)\n", " Point2(3.0, 4.0)\n", " Point2(3.0, 4.0)" ] }, "execution_count": 62, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p = Point2(3,4)\n", "P = [p,p,p]" ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [ { "ename": "ErrorException", "evalue": "type Point2 is immutable", "output_type": "error", "traceback": [ "type Point2 is immutable", "", "Stacktrace:", " [1] setproperty!(::Point2, ::Symbol, ::Int64) at ./sysimg.jl:19", " [2] top-level scope at In[63]:1" ] } ], "source": [ "p.x = 6 # gives an error since p is immutable" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If this is working as we hope, then summation should be much faster:" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 11.253 ms (0 allocations: 0 bytes)\n" ] }, { "data": { "text/plain": [ "Point2(5.000882508762066e6, 5.000882508762066e6)" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a2 = Point2.(a,a)\n", "@btime sum($a2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now the time is **only about 10ms**, only slightly more than twice the cost of summing an array of individual numbers of the same length!\n", "\n", "Unfortunately, we paid a big price for this performance: our `Point2` type only works with *a single numeric type* (`Float64`), much like a C implementation." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The best of both worlds: Parameterized immutable types\n", "\n", "How do we get a `Point` type that works for *any* type of `x` and `y`, but at the same time allows us to have an array of points that is concrete and homogeneous (every point in the array is forced to be the same type)? At first glance, this seems like a contradiction in terms.\n", "\n", "The answer is not to define a *single* type, but rather to **define a whole family of types** that are *parameterized* by the type `T` of `x` and `y`. In computer science, this is known as [parametric polymorphism](https://en.wikipedia.org/wiki/Parametric_polymorphism). (An example of this can be found in [C++ templates](https://en.wikipedia.org/wiki/Template_%28C%2B%2B%29).)\n", "\n", "In Julia, we will define such a family of types as follows:" ] }, { "cell_type": "code", "execution_count": 69, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Point3{Int64}(3, 4)" ] }, "execution_count": 69, "metadata": {}, "output_type": "execute_result" } ], "source": [ "struct Point3{T<:Real}\n", " x::T\n", " y::T\n", "end\n", "Base.:+(p::Point3, q::Point3) = Point3(p.x + q.x, p.y + q.y)\n", "Base.zero(::Type{Point3{T}}) where {T} = Point3(zero(T),zero(T))\n", "\n", "Point3(3,4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here, `Point3` is actually a family of subtypes `Point{T}` for different types `T`. The notation `<:` in Julia means \"is a subtype of\", and hence `T<:Real` means that we are constraining `T` to be a `Real` type (a built-in *abstract type* in Julia that includes e.g. integers or floating point)." ] }, { "cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Point3{Float64}(8.6, 11.8)" ] }, "execution_count": 66, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Point3(3,4) + Point3(5.6, 7.8)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Now, let's make an array:" ] }, { "cell_type": "code", "execution_count": 67, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10000000-element Array{Point3{Float64},1}:\n", " Point3{Float64}(0.41611080115182997, 0.41611080115182997) \n", " Point3{Float64}(0.09121781540916163, 0.09121781540916163) \n", " Point3{Float64}(0.5821689193788118, 0.5821689193788118) \n", " Point3{Float64}(0.4450648623203812, 0.4450648623203812) \n", " Point3{Float64}(0.31025284561131117, 0.31025284561131117) \n", " Point3{Float64}(0.13470267547220938, 0.13470267547220938) \n", " Point3{Float64}(0.9686840088959152, 0.9686840088959152) \n", " Point3{Float64}(0.017240806807339526, 0.017240806807339526)\n", " Point3{Float64}(0.03285701827401222, 0.03285701827401222) \n", " Point3{Float64}(0.8007999312473584, 0.8007999312473584) \n", " Point3{Float64}(0.6967238705169183, 0.6967238705169183) \n", " Point3{Float64}(0.6365055896997436, 0.6365055896997436) \n", " Point3{Float64}(0.8960119603293422, 0.8960119603293422) \n", " ⋮ \n", " Point3{Float64}(0.33070791321634574, 0.33070791321634574) \n", " Point3{Float64}(0.6843509271585413, 0.6843509271585413) \n", " Point3{Float64}(0.5179481631937215, 0.5179481631937215) \n", " Point3{Float64}(0.26483888958030555, 0.26483888958030555) \n", " Point3{Float64}(0.7830762795414743, 0.7830762795414743) \n", " Point3{Float64}(0.9102152866041469, 0.9102152866041469) \n", " Point3{Float64}(0.5857308463542181, 0.5857308463542181) \n", " Point3{Float64}(0.192522005018664, 0.192522005018664) \n", " Point3{Float64}(0.7633399781271266, 0.7633399781271266) \n", " Point3{Float64}(0.7675359710514917, 0.7675359710514917) \n", " Point3{Float64}(0.6697973896081715, 0.6697973896081715) \n", " Point3{Float64}(0.9035572561313561, 0.9035572561313561) " ] }, "execution_count": 67, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a3 = Point3.(a,a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the type of this array is `Array{Point3{Float64},1}` (we could equivalently write this as `Vector{Point3{Float64}}`, since `Vector{T}` is a synonym for `Array{T,1}`). You should learn a few things from this:\n", "\n", "* An `Array{T,N}` in Julia is itself a parameterized type, parameterized by the element type `T` and the dimensionality `N`.\n", "\n", "* Since the element type `T` is encoded in the `Array{T,N}` type, the element type does not need to be stored in each element. That means that the `Array` is free to store an array of \"inlined\" elements, rather than an array of pointers to boxes. (This is why `Array{Float64,1}` earlier could be stored in memory like a C `double*`.\n", "\n", "* It is still important that the element type be `immutable`, since an array of mutable elements would still need to be an array of pointers (so that it could \"notice\" if another reference to an element mutates it)." ] }, { "cell_type": "code", "execution_count": 70, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 11.314 ms (0 allocations: 0 bytes)\n", " 11.629 ms (0 allocations: 0 bytes)\n" ] }, { "data": { "text/plain": [ "Point3{Float64}(5.0008825087619815e6, 5.0008825087619815e6)" ] }, "execution_count": 70, "metadata": {}, "output_type": "execute_result" } ], "source": [ "@btime sum($a3)\n", "@btime mysum($a3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Hooray! It is again **only about 10ms**, the same time as our completely concrete and inflexible `Point2`." ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Julia 1.0.0", "language": "julia", "name": "julia-1.0" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.0.1" } }, "nbformat": 4, "nbformat_minor": 1 }