In [1]:
## Sparse vs. Dense matrices in Julia
using SparseArrays # This loads the sparse matrix package
In [2]:
## Compare the performance of matrix-vector products for sparse and dense matrices
# We'll do this as a function of how many entries are non-zero in the matrix
using Random, BenchmarkTools, LinearAlgebra 
# n is the size of the sparse matrix
# p is the fraction of entries that are non-zero. 
function trial(n, p)
    Random.seed!(0)
    A = sprand(n, n, p) # Generate a random sparse matrix
    x = rand(n) # Generate a random vector
    y = zeros(n) # Allocate space for the result
    return (@btime(mul!($y, $A, $x))) # this does an in-place multiplication 
end
function dense_trial(n, p)
  Random.seed!(0)
  A = Matrix(sprand(n, n, p)) # Generate a random sparse matrix, but as dense 
  x = rand(n) # Generate a random vector
  y = zeros(n) # Allocate space for the result
  return (@btime(mul!($y, $A, $x))) # this does an in-place multiplication 
end
trial(100, 0.01) # Warm up the JIT compiler
dense_trial(100, 0.01) # Warm up the JIT compiler
trial(1000, 0.1) # Run a trial 
dense_trial(1000, 0.1) # Run a trial 
  129.843 ns (0 allocations: 0 bytes)
  1.350 μs (0 allocations: 0 bytes)
  45.500 μs (0 allocations: 0 bytes)
  89.041 μs (0 allocations: 0 bytes)
Out[2]:
1000-element Vector{Float64}:
 24.380572601576063
 29.078132968701016
 26.575361707841537
 21.18796263382051
 29.670362571076172
 36.14012297927813
 26.551888619216744
 31.663151968139935
 23.66509132583557
 24.740730204397682
 20.566221249206468
 24.228092840903123
 25.384055293445066
  ⋮
 22.399432870698334
 26.224067484467533
 22.659537327151032
 29.90517791786851
 27.66856355647173
 22.674037125669766
 25.762337883311627
 21.329546462870077
 29.628815658532766
 21.70315680771119
 27.30104276399619
 23.890768516450965
In [3]:
##
trial(1000, 0.33) # Run a trial 
dense_trial(1000, 0.33) # Run a trial 
  143.000 μs (0 allocations: 0 bytes)
  88.792 μs (0 allocations: 0 bytes)
Out[3]:
1000-element Vector{Float64}:
 87.65879414785158
 84.49985208695149
 81.23015351551086
 79.25604914256748
 75.8174366973627
 79.82391725779192
 90.11858231311294
 79.79555198500297
 74.89506475846503
 95.49818148609522
 89.23806108312404
 81.85894282221228
 84.37231956850181
  ⋮
 86.58256983836812
 81.95455475483641
 81.9751324898719
 82.1796160712076
 77.58724245805077
 78.9266037183557
 79.530006257086
 95.41480861614937
 89.98257332957085
 82.86447942309196
 87.72695513617735
 82.06992896076437
In [4]:
## Let's make a little plot of this. 
function get_data(ns, ps)
  Random.seed!(0)
  results_sparse = zeros(length(ns), length(ps))
  results_dense = zeros(length(ns), length(ps))
  for (i, n) in pairs(ns) # pairs is a handy way of getting the index and value... 
    for (j, p) in pairs(ps)
      A = sprand(n, n, p) # Generate a random sparse matrix
      x = rand(n) # Generate a random vector
      y = zeros(n) # Allocate space for the result
      t = @elapsed(mul!(y, A, x)) # this does an in-place multiplication
      A = Matrix(A) # Convert to dense
      t_dense = @elapsed(mul!(y, A, x)) # this does an in-place multiplication
      results_sparse[i,j] = t
      results_dense[i,j] = t_dense
    end
  end
  return results_sparse, results_dense
end 
ns = [100, 200, 400, 800, 1600]
ps = [0.01, 0.05, 0.1, 0.2, 0.33, 0.5]
results_sparse, results_dense = get_data(ns, ps)
Out[4]:
([1.375e-6 1.167e-6 … 2.2e-5 2.9875e-5; 1.875e-6 3.833e-6 … 7.375e-6 1.0333e-5; … ; 1.325e-5 1.9625e-5 … 0.000111291 0.000462833; 2.2458e-5 6.7375e-5 … 0.000411 0.000944375], [4.292e-6 2.25e-6 … 6.625e-6 4.2041e-5; 0.0001615 2.75e-5 … 7.834e-6 7.75e-6; … ; 0.00046775 0.000151667 … 0.000385959 0.001171584; 0.000681542 0.001329709 … 0.002080084 0.001563125])
In [5]:
## Now let's plot
using Plots
plot!(size=(800,600))
for (j, ps) in pairs(ps)
  plot!(ns, results_sparse[:,j], label="Sparse, p=$ps", marker=:circle)
  plot!(ns, results_dense[:,j], label="Dense, p=$ps", marker=:circle, linestyle=:dash)
end
xlabel!("Matrix size")
ylabel!("Time (s)")
Out[5]:
In [6]:
## Do this with lots of little plots, one for each p
plts = [] 
for (j,p) in pairs(ps)
  plt = plot(ns, results_sparse[:,j], label="Sparse", marker=:circle)
  plot!(plt, ns, results_dense[:,j], label="Dense", marker=:circle, linestyle=:dash)
  plot!(plt, yscale=:log10)
  title!(plt, "p = $p")
  push!(plts, plt) # add pl
end 
plot(plts..., layout=(3,2), size=(800,600)) # plot all the plots
Out[6]:
In [7]:
## Scopes in Julia
classvar1 = 5
function f1()
  i = 20 
  classvar1 = 10
  @show classvar1 
  classvar3 = 20 
  @show classvar3 
  for classvar2 in 1:10
    classvar3 = 30
    # println("classvar3 = ", classvar3)
    @show classvar3, classvar2
  end
  @show classvar1 # This will fail because classvar3 is not defined in this scope
  @show classvar3
end
f1()
println(classvar1)
classvar1 = 10
classvar3 = 20
(classvar3, classvar2) = (30, 1)
(classvar3, classvar2) = (30, 2)
(classvar3, classvar2) = (30, 3)
(classvar3, classvar2) = (30, 4)
(classvar3, classvar2) = (30, 5)
(classvar3, classvar2) = (30, 6)
(classvar3, classvar2) = (30, 7)
(classvar3, classvar2) = (30, 8)
(classvar3, classvar2) = (30, 9)
(classvar3, classvar2) = (30, 10)
classvar1 = 10
classvar3 = 30
5
In [8]:
##
function testfunc(xs)
  for i in xs
    @show i 
  end 
end 
testfunc([1,5,10])
i = 1
i = 5
i = 10