{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Advanced Speed and Accuracy Topics" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is going to be a bird's eye view of some of the advanced topics in Julia, particularly focusing on issues of speed and performance.\n", "\n", "Julia is built with a number of components which enable it to obtain massive performance, despite its scripted nature.\n", "You've already seen \n", "- multiple dispatch\n", "- types, when necessary\n", "\n", "We are skipping the built-in parallelism. This is for the very good reason that, even though it's designed-in from the outset of the language the syntax is currently being stabilised and should be final from the next release (_the disadvantage of being an early adopter_)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Calling a C function natively" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can call **C** and **Fortran** functions natively. That means that you can potentially have an ultra high-performance library, which has been worked on for years, and you get to access its functions directly from Julia at **0** computational overhead.\n", "\n", ">ccall((:function_name, \"library_name\"), return_type, (argument_types), argument_values)\n", "\n", "Current requirements: the library must be *dynamically* linkable (not static).\n", "\n", "Note that, getting the function call syntax correct is typically the biggest stumbling block to working with C and Fortran libraries from Julia. In addition, you don't have the benefit of interfacing via header files so the handling of returned values can be tricky if they're not what you were expecting.\n", "\n", "Also, for Fortran users, you should note that Fortran compilers often change the case of a function call. From Julia you need to call the *compiled* version of the function call, not the original source code spelling." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@show ccall((:clock,),Int64,())\n", "@show ccall((:clock,),Int64,())\n", "@show ccall((:clock,),Int64,())" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@show ccall((:rand,), Int64, ())\n", "@show ccall((:rand,), Int64, ())\n", "@show ccall((:rand,), Int64, ())" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "ccall((:printf,), Int64, (Cstring,), \"blah\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "ccall((:printf,), Int64, (Cstring,Int64), \"blah %d \\n\", 999)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "function getenv(var::AbstractString)\n", " val = ccall((:getenv, \"libc\"),\n", " Cstring, (Cstring,), var)\n", " if val == C_NULL\n", " error(\"getenv: undefined variable: \", var)\n", " end\n", " unsafe_string(val)\n", "end" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "getenv(\"PATH\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "getenv(\"THIS_value_DOESNT_exist\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In practice, you may never need to use this functionality depending on your position on the power-user spectrum.\n", "\n", "But even if you're not using it directly, under the hood this is how many of the Packages are interfacing with numerical libraries which have been developed and optimised in C and Fortran since the 70's.\n", "\n", "https://docs.julialang.org/en/stable/manual/calling-c-and-fortran-code/" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## OpenCL" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "I use OpenCL extensively in my work. You may be familiar with CUDA. There is also a CUDAnative package for Julia. The principles I'm going to present are similar for both interfaces.\n", "\n", "OpenCL will work out of the box for Mac users. For Windows and Linux users it typically requires some external packages be installed. This is not a workshop on OpenCL, so we'll provide a notebook with the calculated outputs which you can follow, but please note when installing OpenCL that you need a **driver** or a **library** which provides OpenCL support directly for your system and not just the **header** files. (apt-get install opencl typically only grabs the headers)\n", "\n", "If you want to install OpenCL (after the workshop) there are a number of options:\n", "1. If you have an NVIDIA GPU then you automatically have OpenCL installed if you install CUDA development system. Note that the NVIDIA implementation can only compile OpenCL for the GPU device.\n", "2. You can install ATIs APP SDK, which is natively OpenCL, for ATI GPU devices. _This system can also compile for CPU giving you the best of both worlds!_\n", "3. Intel provide OpenCL compilers for CPUs.\n", "4. Apple pre-install OpenCL support on their computers, for both CPU and GPU." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "Pkg.add(\"OpenCL\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "using OpenCL" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The writers of the OpenCL package have made extensive use of the ccall( ) approach to directly interface with OpenCL (which is typically accessed from C).\n", "\n", "Here I will present a few wrapper functions, which I have written, which most OpenCL developers typically need to write themselves when they begin using the langauge.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "function print_cl_stuff()\n", " print(\"Number of Platforms: \", cl.num_platforms(), \"\\n\")\n", " for plat in cl.num_platforms()\n", " print(\"Platform[\", plat, \"]: \", cl.platforms()[plat], \"\\n\")\n", " #print(\"Platforms: \", cl.platforms())\n", " numdevices = length(cl.devices())\n", " print(\"\\tNumber of Devices: \", numdevices, \"\\n\")\n", " for dev = 1:numdevices\n", " print(\"\\tDevice[\", dev, \"]: \", cl.devices()[dev], \"\\n\")\n", " end\n", " #print(\"\\nDevices: \", cl.devices(), \"\\n\")\n", " end\n", "end" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here we're making use of the following functions.\n", "\n", "> cl.num_platforms( )\n", "\n", "> cl.platforms( )[index]\n", "\n", "> cl.devices( )\n", "\n", "> cl.devices( )[index]\n", "\n", "These are **very** lightly wrapped versions of the C functions which are part of the OpenCL API." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print_cl_stuff()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In OpenCL you may have multiple platforms installed on your system (these are the different implementations mentioned in the install notes above).\n", "\n", "When you're setting up a computation, you need to choose:\n", "- platform\n", "- device\n", "\n", "and create a memory (plus device)\n", "- context\n", "\n", "This latter is basically the namespace within which your computation will operate." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "function set_device(device_id, platform)\n", " device = cl.devices()[device_id]\n", " return device\n", "end\n", "\n", "function set_device(device_id)\n", " platform = set_platform()\n", " return set_device(device_id, platform)\n", "end\n", "\n", "function set_context(device)\n", " ctx = cl.Context(device)\n", " return ctx\n", "end\n", "\n", "function set_platform(platform_id=0)\n", " platform = cl.Platform(platform_id)\n", " return platform\n", "end;" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "our_platform = set_platform()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "cl.Platform(0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Setting the platform defaults to the first one (since there is often only one installed platform)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "our_device = set_device(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that the indexing of the array here is 1 based, as it's still in Julia. However the platform id above is indexed from 0 as it's a value which is directly passed to the C function." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "cl.devices()[2]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "our_context = set_context(our_device)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now have access to device 1, via our_context, and we have a handle for our_platform. These are all necessary components for running an OpenCL kernel." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "cl.CLError(-10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Many functions in OpenCL can return an error code. This has its own format but you can access it via CLError." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "cl.error_description(cl.CLError(-10))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Normally the above line will work. But the version of the package that I was working with had a bug. I've submitted a pull request so it will hopefully work by the time of the workshop. \n", "\n", "In any case, the expected behaviour is to print out a useful string regarding the meaning of any returned error code.\n", "\n", "Note that OpenCL does not provide this functionality, this is a feature of the Julia OpenCL package." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## OpenCL showcase example\n", "\n", "*based on code written by Thomas McColgan and Rike Schuppner at a workshop in Lychen, 2017*\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "diff_kernel = \"\n", "inline int xy_to_i(int x, int y, int col_size, int mesh_size) {\n", " return (x % col_size) + (y % col_size) * col_size; \n", "}\n", "\n", "__kernel void diff(__global const float *a,\n", "__global float *diff,\n", "int col_size,\n", "int mesh_size)\n", " {\n", " int gid = get_global_id(0);\n", " int x_idx = gid % col_size;\n", " int y_idx = gid / col_size;\n", " \n", " diff[gid] = - 4. * a[gid]\n", " + a[xy_to_i(x_idx - 1, y_idx, col_size, mesh_size)]\n", " + a[xy_to_i(x_idx + 1, y_idx, col_size, mesh_size)]\n", " + a[xy_to_i(x_idx, y_idx - 1, col_size, mesh_size)]\n", " + a[xy_to_i(x_idx, y_idx + 1, col_size, mesh_size)];\n", " }\n", "\";" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "sum_kernel = \"\n", " __kernel void sum(__global float *a,\n", "__global const float *diff,\n", "float step_size)\n", " {\n", " int gid = get_global_id(0);\n", " a[gid] += step_size * diff[gid];\n", " }\n", "\";" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "OpenCL allows us to run **kernels** on the GPU/CPU device.\n", "\n", "In our example each parallel computational unit operates its own version of the *kernel* on a different unit in an array." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "function initial_cond(big_array=true)\n", " if big_array\n", " a = zeros(Float32, (5000, 5000))\n", " else\n", " a = zeros(Float32, (1000, 1000))\n", " end\n", " a[Int(size(a)[1] / 2), Int(size(a)[2] / 2)] = 10000\n", " return a\n", "end;" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "function do_openCL(input, device_index=1)\n", "# device, ctx, queue = cl.create_compute_context()\n", "# print(device, ctx, queue)\n", " device = set_device(device_index)\n", " # on my system device 1 is CPU, device 2 is GPU\n", " ctx = set_context(device)\n", " queue = cl.CmdQueue(ctx)\n", " print(ctx, device, queue)\n", "\n", " input_buff = cl.Buffer(Float32, ctx, (:rw, :copy), hostbuf=input)\n", " diff_buff = cl.Buffer(Float32, ctx, :rw, length(input))\n", "\n", " diff_p = cl.Program(ctx, source=diff_kernel) |> cl.build!\n", " diff_k = cl.Kernel(diff_p, \"diff\")\n", "\n", " sum_p = cl.Program(ctx, source=sum_kernel) |> cl.build!\n", " sum_k = cl.Kernel(sum_p, \"sum\")\n", "\n", " step_size = 0.001\n", "\n", " for i in 1:1000\n", " queue(diff_k, length(input), nothing, input_buff, diff_buff, size(input)[1], length(input))\n", " queue(sum_k, length(input), nothing, input_buff, diff_buff, Float32(step_size))\n", " end\n", " r = cl.read(queue, input_buff);\n", " r = reshape(r, size(input))\n", " return r\n", "end" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@time do_openCL(initial_cond(true), 2);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Running on the GPU is reasonably quick, considering the size of the system. My system typically takes 35secs.\n", "\n", "The big array system has a 5000x5000 array and we loop 1000 times across this, performing a number of summation and multiplication operations on every element." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@time plot_result_cpu = do_openCL(initial_cond(false), 1);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Running on the CPU is very slow. The big array takes >5 mins on my laptop.\n", "\n", "So here I've set a flag to false to run an array which is 25 times smaller. It still takes 15 secs on the CPU." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "(25*15) / 60" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@time plot_result_gpu = do_openCL(initial_cond(false), 2);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can run that small system here on the GPU. We see a decent speed-up relative to CPU, on my system the time is now 2 secs, but on such a small system it is no longer dramatic." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "function diff_step(input, output)\n", " maxX, maxY = size(input)\n", " for iter in eachindex(input)\n", " x, y = ind2sub(input, iter)\n", " output[iter] = - 4. * input[iter] + input[x, y - 1 == 0 ? maxY : y - 1] + \n", " input[x, y + 1 > maxY ? 1 : y + 1] +\n", " input[x - 1 == 0 ? maxX : x - 1, y] +\n", " input[x + 1 > maxX ? 1 : x + 1, y]\n", " end\n", " return output\n", "end\n", "\n", "function sum_step(input, diff, step_size)\n", " return input + (step_size * diff)\n", "end\n", "\n", "function do_naive(input)\n", " step_size = 0.001\n", "\n", " diff = similar(input)\n", " for i in 1:1000\n", " diff = diff_step(input, diff)\n", " input = sum_step(input, diff, step_size)\n", " end\n", " return input\n", "end;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can implement a *very* naive version here in non-optimised pure Julia, to see if there is much overhead using OpenCL." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@time naive_result = do_naive(initial_cond(false));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This comes in at 26 secs on my machine. Compared with 2 secs for OpenCL on GPU and 15 secs for OpenCL on CPU.\n", "\n", "Let's be clear, this is because we are operating in a regime which is particularly suitable for GPU computing here!" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "initial_conditions_array = initial_cond(false)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "extrema(initial_conditions_array)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "size(initial_conditions_array)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "plot_result_cpu" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "extrema(plot_result_cpu)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "size(plot_result_cpu)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "initial_conditions_array[initial_conditions_array.>0]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "plot_result_cpu[plot_result_cpu.>0]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "using PyPlot" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "plot(initial_conditions_array);" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "imshow(initial_conditions_array[480:520,480:520])" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "imshow(plot_result_cpu[480:520,480:520])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We've focused our plot on the area where something is happening. For this particular process, for only 1000 time steps, we could have simulated a 40x40 area instead of 1000x1000." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Timing, Benchmarking & Profiling" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Timing" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "function do_my_loop(value)\n", " A = zeros(value, value)\n", " for i = 1:value, j = 1:value\n", " A[i,j] = i+j\n", " end\n", " return A;\n", "end" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@time do_my_loop(1000);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By now you've seen me using the @time macro" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@time do_my_loop(10000);" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "function alternative_do_my_loop(value)\n", " A = Array{Float64,2}(value, value)\n", " for i = 1:value, j = 1:value\n", " A[i,j] = i+j\n", " end\n", " return A;\n", "end" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@time alternative_do_my_loop(10000);" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "function final_alternative_do_my_loop(value)\n", " A = Array{Float64,2}(value, value)\n", " A = [i+j for i=1:value, j=1:value]\n", " return A;\n", "end" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@time final_alternative_do_my_loop(10000);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In most cases you need to be careful where and how you use @time. It is unreliable in the REPL due to the global namespace requiring ongoing type inference. Also, functions are compiled the first time that they are called so the first run should often be discarded." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Benchmarking" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "Pkg.add(\"BenchmarkTools\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "using BenchmarkTools" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "BenchmarkTools is an incredibly sophisticated benchmarking package. We will use only the simplest features here but please check out the project if you have deeper needs\n", "\n", "https://github.com/JuliaCI/BenchmarkTools.jl" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@benchmark do_my_loop(10000)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@benchmark alternative_do_my_loop(10000)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@benchmark final_alternative_do_my_loop(10000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The simple @benchmark macro runs the function a number of times and analyses the processing requirements." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "X, Y = rand(1000,1000), rand(1000,1000);" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@benchmark max.(abs.(X), abs.(Y))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Profiling" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "Profile.init(delay=0.01)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "do_my_loop(10000);" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "Profile.clear()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We clear everything that's currently in the profiler to prepare for a run." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "@profile do_my_loop(10000);\n", "@profile alternative_do_my_loop(10000);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We then run our code prepended by the @profile macro.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "using ProfileView" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can view the Profiler output using the package ProfileView" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "ProfileView.view()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The bottom of the graph is the outermost code and the top is the innermost function calls.\n", "\n", "\n", "Ctrl+scroll to zoom vertically\n", "\n", "Ctrl+Shift+scroll to zoom horizontally\n", "\n", "Double-click to revert" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "r = Profile.retrieve()\n", "f = open(\"profile.bin\", \"w\")\n", "serialize(f,r)\n", "close(f)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Often when profiling it's more useful to export the data for later analysis.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "using ProfileView\n", "f = open(\"profile.bin\")\n", "r = deserialize(f)\n", "ProfileView.view(r[1], lidict=r[2])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can then import and plot it in a separate instance of Julia if we wish." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For tracking of memory allocations, load julia with the command-line flag \n", "> --track-allocations = user\n", "\n", "for malloc's from your own code, or\n", "> --track-allocations = all\n", "\n", "to include all Julia memory allocations during the run. You can access the .mem files in the host folder after julia has exited." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Debugging" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Gallium is the package provided for debugging Julia. Unfortunately, due to some fundamental under the hood changes to Julia core, it is not yet functional for Julia 0.6 (released only 1 week ago)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "using Gallium" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "function my_func_2(var::Int)\n", " print(\"in my_func_2()\")\n", " print(\" the value of var is $var\\n\")\n", "end\n", "\n", "function my_func_1(var)\n", " print(\"in my_func_1()\\n\");\n", " if typeof(var) == Int64\n", " my_func_2(var);\n", " end\n", "end;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We make some dummy functions that we might want to debug." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "my_func_1(2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Check that they seem to work." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "breakpoint(my_func_1, (Int64,))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let's set a breakpoint on any call to my_func_1( ) which takes an Int64 as a parameter. Remember, the declaration of my_func_1( ) allows for any variable types to be passed to it." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "my_small_int = Int32(3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "typeof(my_small_int)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "my_bigger_int = Int64(3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "typeof(my_bigger_int)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "my_func_1(my_small_int)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The call with 'my_small_int' did not match the breakpoint, so the function ran and exited as normal." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "Gallium.list_breakpoints()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "Gallium.breakpoint_on_error()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "my_func_1(my_bigger_int)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "At this point we've reached the limit of what we can do in a Jupyter notebook. Debugging is not available here.\n", "\n", "So if you want to try a real debug session, open a REPL (or paste everthing into a file and run julia against the file) and paste the following code cell entry into it.\n", "\n", "The commands for the debugger are as follows:\n", "\n", "Basic Commands:\n", "\n", "- n steps to the next line\n", "- s steps into the next call\n", "- finish runs to the end of the function\n", "- bt shows a simple backtrace\n", "- `stuff runs stuff in the current frame's context\n", "- fr v will show all variables in the current frame\n", "- f n where n is an integer, will go to the n-th frame.\n", "\n", "Advanced commands:\n", "\n", "- nc steps to the next call\n", "- ns steps to the next statement\n", "- se does one expression step\n", "- si does the same but steps into a call if a call is the next expression\n", "- sg steps into a generated function\n", "- shadow shows the internal representation of the expression tree (for debugger debugging only)\n", "- loc shows the column data for the current top frame, in the same format as JuliaParsers's testshell.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "using Gallium\n", "function my_func_2(var::Int)\n", " print(\"in my_func_2()\")\n", " print(\" the value of var is $var\\n\")\n", "end\n", "\n", "function my_func_1(var)\n", " print(\"in my_func_1()\\n\");\n", " if typeof(var) == Int64\n", " my_func_2(var);\n", " end\n", "end;\n", "\n", "breakpoint(my_func_1, (Int64,))\n", "\n", "my_small_int = Int32(3)\n", "my_bigger_int = Int64(3)\n", "\n", "my_func_1(my_small_int)\n", "my_func_1(my_bigger_int)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# FIN" ] } ], "metadata": { "kernelspec": { "display_name": "Julia 0.6.0", "language": "julia", "name": "julia-0.6" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "0.6.0" } }, "nbformat": 4, "nbformat_minor": 2 }