JM

Table of Contents

Exercises

Question 1

Take the following workload that scales linearly:

julia
function example_inner_loop(k)
    s = zero(Float64)
    for _ in 1:k
        s += rand(Float64)
    end
    s
end
function example_workload(n, k=100)
    results = Vector{Float64}(undef, n)
    for i in 1:n
        results[i] = example_inner_loop(k)
    end
    return results
end

(i). What does the parameter k control?

(ii). Implement a multithreaded version of example_workload.

(iii). Implement a parallel version of example_workload using multiprocessing.

(iv). Design an experiment to compare the two implementations of example_workload, and the original, serial, implementation, as you vary n.

(v). Use the results of the previous experiment to estimate the latency relationship of both Threads.@threads and Distributed.pmap to the variables n and k.

Question 2

If you have an embarrassingly parallel problem, which relies on non-thread safe code, which parallel programming paradigm is most suitable?

Answer: Multiprocessing - as each process will have its own isolated memory copy which can ensure parallel processing without race conditions.

Question 3

Look at the following struct:

julia
struct RunningStats{T}
    min::T
    max::T
    mean::T
    num_samples::Int
end

(i). Define a function which reduces two samples of type T into a single RunningStats.

(ii). Extend the previous function to define reductions between single values of type T, and RunningStats, in any order, as well as between two RunningStats.

(iii). Test this custom reduction using the reduce function and the Statistics.jl standard library on a vector of random floats. Calculate the statistics of this vector and compare them to the reduced statistics output.

(iv). Write a multiprocessing parallel implementation of the reduction calculated in the previous part, using a Monte-Carlo process of your choice which produces a single value.

Question 4

Take the following setup:

julia
using Random
using LinearAlgebra
using Statistics

const rng = Random.Xoshiro(1234)
function generate_seeds(rng, n)
    seeds = zeros(Int, n)
    for i in 1:n
        seeds[i] = rand(rng, Int)
    end
    return seeds
end
const n = 32
const seeds = generate_seeds(rng, n)

function long_calculation(seed; l=10)
    c_rng = Random.Xoshiro(seed)

    k = 12
    s = 2^k
    matrix_size = (s,s)

    matrix = rand(c_rng, Float64, matrix_size) .* 2 .- 1
    cache = similar(matrix)

    for j = 1:l
        mul!(cache, matrix, matrix)
        matrix .= cache ./ s .- matrix
    end

    return Statistics.mean(cache)
end

const results = map(long_calculation, seeds)

(i). Use multiprocessing to parallelise this calculation and make sure the number of BLAS threads is set to 11.

(ii). Implement memoised caching on the long calculation, which uses the disk as a cache.

(iii). Use the previous parts to create a script which can recover from failure, without losing too much progress.