# Request guidance in optimising the performance of my implementation (running time and allocation)

**URL:** https://discourse.julialang.org/t/request-guidance-in-optimising-the-performance-of-my-implementation-running-time-and-allocation/10528
**Category:** General Usage
**Tags:** question
**Created:** [April 25, 2018, 11:05am UTC](https://discourse.julialang.org/t/request-guidance-in-optimising-the-performance-of-my-implementation-running-time-and-allocation/10528 "2018-04-25T11:05:31Z")
**Posts on this page:** 10
**Page:** 1

<div class="post-metadata">

### Author: ![zygmuntszpak](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zygmuntszpak/32/2591_2.png) [@zygmuntszpak](https://discourse.julialang.org/u/zygmuntszpak)
#### Post date: [April 25, 2018, 11:05am UTC](https://discourse.julialang.org/t/request-guidance-in-optimising-the-performance-of-my-implementation-running-time-and-allocation/10528/1 "2018-04-25T11:05:31Z")

</div>

I suspect that it is possible for further improve upon my implementation of a particular _cost function_, but I’m not sure how to proceed further. The cost function involves summing the result of a series of matrix and vector multiplications. I’m hoping someone with more experience could suggest ways to speed up the evaluation of the cost function. My current implementation allocates a lot of memory and I suspect that this could somehow be reduced.

Many thanks for any advice.

#### The Implementation

```julia
using StaticArrays
using BenchmarkTools

const ⊗ = kron

mutable struct Point2DH <: FieldVector{3,Float64}
    x::Float64
    y::Float64
    h::Float64
end

function cost(𝛉::AbstractArray, 𝒞::Tuple{AbstractArray, Vararg{AbstractArray}}, 𝒟::Tuple{AbstractArray, Vararg{AbstractArray}})
    ℳ, ℳʹ = collect(𝒟)
    Λ₁, Λ₂ = collect(𝒞)
    Jₐₘₗ = fill(0.0,(1,1))
    N = length(𝒟[1])
    𝚲ₙ = @MMatrix zeros(4,4)
    𝐞₁ = @SVector [1.0, 0.0, 0.0]
    𝐞₂ = @SVector [0.0, 1.0, 0.0]
    for n = 1:N
        𝚲ₙ[1:2,1:2] .= @view Λ₁[n][1:2,1:2]
        𝚲ₙ[3:4,3:4] .= @view Λ₂[n][1:2,1:2]
        𝐦 = ℳ[n]
        𝐦ʹ= ℳʹ[n]
        𝐔ₙ = (𝐦 ⊗ 𝐦ʹ)
        ∂ₓ𝐮ₙ = [(𝐞₁ ⊗ 𝐦ʹ) (𝐞₂ ⊗ 𝐦ʹ) (𝐦 ⊗ 𝐞₁) (𝐦 ⊗ 𝐞₂)]
        𝐁ₙ = ∂ₓ𝐮ₙ * 𝚲ₙ * ∂ₓ𝐮ₙ'
        𝚺ₙ = 𝛉' * 𝐁ₙ * 𝛉
        𝚺ₙ⁻¹ = inv(𝚺ₙ)
        Jₐₘₗ .= Jₐₘₗ + 𝛉' * 𝐔ₙ * 𝚺ₙ⁻¹ * 𝐔ₙ' * 𝛉
    end
    Jₐₘₗ[1]
end

# Some sample data
N = 3376821
ℳ = [Point2DH(rand(3)) for i = 1:N]
ℳʹ = [Point2DH(rand(3)) for i = 1:N]
Λ₁ = [SMatrix{3,3}(diagm([1.0,1.0,0.0])) for i = 1:length(ℳ)]
Λ₂ = [SMatrix{3,3}(diagm([1.0,1.0,0.0])) for i = 1:length(ℳ)]
t = @MVector rand(9)
𝒞 = (Λ₁,Λ₂)
𝒟 = (ℳ, ℳʹ)

cost(t,𝒞 , 𝒟)
@time cost(t,𝒞 , 𝒟)
@btime cost($t,$𝒞 , $𝒟)

```

#### Benchmark Results

```julia
10.695948 seconds (135.07 M allocations: 13.636 GiB, 15.68% gc time)
10.509 s (135072844 allocations: 13.64 GiB)

```

#### My Julia Version

```julia
               _
   _ _ _(_)_ | A fresh approach to technical computing
  (_) | (_) (_) | Documentation: https://docs.julialang.org
   _ _ _| |_ __ _ | Type "?help" for help.
  | | | | | | |/ _` | |
  | | |_| | | | (_| | | Version 0.6.2 (2017-12-13 18:08 UTC)
 _/ |\ __'_|_|_|\__'_| | Official http://julialang.org/ release
|__/ | x86_64-pc-linux-gnu

```

---

<div class="post-metadata">

### Author: ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)
#### Post date: [April 25, 2018, 1:04pm UTC](https://discourse.julialang.org/t/request-guidance-in-optimising-the-performance-of-my-implementation-running-time-and-allocation/10528/2 "2018-04-25T13:04:47Z")

</div>

Looks like StaticArrays is missing `kron` for two `SVector`s. The result is a normal `Array` and then we get really sad.

---

<div class="post-metadata">

### Author: ![zygmuntszpak](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zygmuntszpak/32/2591_2.png) [@zygmuntszpak](https://discourse.julialang.org/u/zygmuntszpak)
#### Post date: [April 26, 2018, 12:17am UTC](https://discourse.julialang.org/t/request-guidance-in-optimising-the-performance-of-my-implementation-running-time-and-allocation/10528/3 "2018-04-26T00:17:52Z")

</div>

Thank you. To test the potential speed improvement I’ve changed the representation of static vectors to static single column matrices.

#### The Implementation

```julia
using StaticArrays
using BenchmarkTools

const ⊗ = kron

function cost(𝛉::AbstractArray, 𝒞::Tuple{AbstractArray, Vararg{AbstractArray}}, 𝒟::Tuple{AbstractArray, Vararg{AbstractArray}})
    ℳ, ℳʹ = collect(𝒟)
    Λ₁, Λ₂ = collect(𝒞)
    Jₐₘₗ = fill(0.0,(1,1))
    N = length(𝒟[1])
    𝚲ₙ = @MMatrix zeros(4,4)
    # 𝐞₁ = @SVector [1.0, 0.0, 0.0]
    # 𝐞₂ = @SVector [0.0, 1.0, 0.0]
    𝐞₁ = @SMatrix [1.0; 0.0; 0.0]
    𝐞₂ = @SMatrix [0.0; 1.0; 0.0]
    for n = 1:N
        𝚲ₙ[1:2,1:2] .= @view Λ₁[n][1:2,1:2]
        𝚲ₙ[3:4,3:4] .= @view Λ₂[n][1:2,1:2]
        𝐦 = ℳ[n]
        𝐦ʹ= ℳʹ[n]
        𝐔ₙ = (𝐦 ⊗ 𝐦ʹ)
        ∂ₓ𝐮ₙ = [(𝐞₁ ⊗ 𝐦ʹ) (𝐞₂ ⊗ 𝐦ʹ) (𝐦 ⊗ 𝐞₁) (𝐦 ⊗ 𝐞₂)]
        𝐁ₙ = ∂ₓ𝐮ₙ * 𝚲ₙ * ∂ₓ𝐮ₙ'
        𝚺ₙ = 𝛉' * 𝐁ₙ * 𝛉
        𝚺ₙ⁻¹ = inv(𝚺ₙ)
        Jₐₘₗ .= Jₐₘₗ + 𝛉' * 𝐔ₙ * 𝚺ₙ⁻¹ * 𝐔ₙ' * 𝛉
    end
    Jₐₘₗ[1]
end

# Some sample data
N = 3376821
ℳ = [@MMatrix(rand(3,1)) for i = 1:N]
ℳʹ = [@MMatrix(rand(3,1)) for i = 1:N]
Λ₁ = [SMatrix{3,3}(diagm([1.0,1.0,0.0])) for i = 1:length(ℳ)]
Λ₂ = [SMatrix{3,3}(diagm([1.0,1.0,0.0])) for i = 1:length(ℳ)]
t = @MVector rand(9)
𝒞 = (Λ₁,Λ₂)
𝒟 = (ℳ, ℳʹ)

cost(t,𝒞 , 𝒟)
@time cost(t,𝒞 , 𝒟)
@btime cost($t,$𝒞 , $𝒟)

```

#### Benchmark Results

```julia
  1.133429 seconds (13.51 M allocations: 669.848 MiB, 9.67% gc time)
  1.104 s (13507288 allocations: 669.84 MiB)

```

This almost amounts to a 10 fold improvement. Would it be fair to say that without parallelism, there is not much more to do here?

---

<div class="post-metadata">

### Author: ![tkoolen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tkoolen/32/1603_2.png) [@tkoolen](https://discourse.julialang.org/u/tkoolen)
#### Post date: [April 26, 2018, 1:01am UTC](https://discourse.julialang.org/t/request-guidance-in-optimising-the-performance-of-my-implementation-running-time-and-allocation/10528/4 "2018-04-26T01:01:25Z")

</div>

Some more improvements:

```julia
using StaticArrays
using BenchmarkTools

const ⊗ = kron

function cost(𝛉::AbstractArray, 𝒞::Tuple{AbstractArray, Vararg{AbstractArray}}, 𝒟::Tuple{AbstractArray, Vararg{AbstractArray}})
    ℳ, ℳʹ = collect(𝒟)
    Λ₁, Λ₂ = collect(𝒞)
    Jₐₘₗ = 0.0
    N = length(𝒟[1])
    𝚲ₙ = @MMatrix zeros(4,4)
    # 𝐞₁ = @SVector [1.0, 0.0, 0.0]
    # 𝐞₂ = @SVector [0.0, 1.0, 0.0]
    𝐞₁ = @SMatrix [1.0; 0.0; 0.0]
    𝐞₂ = @SMatrix [0.0; 1.0; 0.0]
    @inbounds for n = 1:N
        index = SVector(1, 2)
        𝚲ₙ[1:2,1:2] .= Λ₁[n][index, index]
        𝚲ₙ[3:4,3:4] .= Λ₂[n][index, index]
        𝐦 = ℳ[n]
        𝐦ʹ= ℳʹ[n]
        𝐔ₙ = (𝐦 ⊗ 𝐦ʹ)
        ∂ₓ𝐮ₙ = [(𝐞₁ ⊗ 𝐦ʹ) (𝐞₂ ⊗ 𝐦ʹ) (𝐦 ⊗ 𝐞₁) (𝐦 ⊗ 𝐞₂)]
        𝐁ₙ = ∂ₓ𝐮ₙ * 𝚲ₙ * ∂ₓ𝐮ₙ'
        𝚺ₙ = 𝛉' * 𝐁ₙ * 𝛉
        Jₐₘₗ += 𝛉' * 𝐔ₙ * (𝚺ₙ \ 𝐔ₙ') * 𝛉
    end
    Jₐₘₗ
end

```

Before:

```julia
cost(t, 𝒞, 𝒟) = 1.4694106525431348e6
  1.375 s (13507288 allocations: 669.84 MiB)

```

After:

```julia
cost(t, 𝒞, 𝒟) = 1.4694106525431348e6
  972.911 ms (3 allocations: 336 bytes)

```

Changes:

- Use `\` instead of `inv` (pretty big difference, typically numerically better as well)
- Replace `@view` with indexing with `SVector(1, 2)` so that size is known at compile time
- Add `@inbounds` annotation (minor improvement; also note that I neglected adding size checks before the loop, so this is unsafe)
- Change `Jₐₘₗ` from a 1x1 matrix to a `Float64` (big difference)

---

<div class="post-metadata">

### Author: ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)
#### Post date: [April 26, 2018, 9:16am UTC](https://discourse.julialang.org/t/request-guidance-in-optimising-the-performance-of-my-implementation-running-time-and-allocation/10528/5 "2018-04-26T09:16:44Z")

</div>

> [@zygmuntszpak](#):
>
> Would it be fair to say that without parallelism, there is not much more to do here?

It depends on how general you want the code to be. Right now, `𝚲ₙ` is block diagonal and the `𝐞₁ ⊗ 𝐦ʹ` are quite sparse so exploiting those things would likely help.

---

<div class="post-metadata">

### Author: ![cortner](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cortner/32/204_2.png) [@cortner](https://discourse.julialang.org/u/cortner)
#### Post date: [April 26, 2018, 3:55pm UTC](https://discourse.julialang.org/t/request-guidance-in-optimising-the-performance-of-my-implementation-running-time-and-allocation/10528/6 "2018-04-26T15:55:18Z")

</div>

Also ask yourself whether you _care_ about further improvements.

---

<div class="post-metadata">

### Author: ![zygmuntszpak](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zygmuntszpak/32/2591_2.png) [@zygmuntszpak](https://discourse.julialang.org/u/zygmuntszpak)
#### Post date: [April 29, 2018, 11:02am UTC](https://discourse.julialang.org/t/request-guidance-in-optimising-the-performance-of-my-implementation-running-time-and-allocation/10528/7 "2018-04-29T11:02:01Z")

</div>

Kristoffer and Twan, thank you very much for your excellent suggestions. I am astounded that it was possible to reduce everything down to just 3 allocations. I learned a lot from your posts. Thanks also to everyone else who took the time to read the code snippet and think about the problem.

---

<div class="post-metadata">

### Author: ![goedman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goedman/32/217_2.png) [@goedman](https://discourse.julialang.org/u/goedman)
#### Post date: [April 29, 2018, 2:07pm UTC](https://discourse.julialang.org/t/request-guidance-in-optimising-the-performance-of-my-implementation-running-time-and-allocation/10528/8 "2018-04-29T14:07:54Z")

</div>

Hi, thanks for this thread, quite a good example! Can I ask a related but off-topic question? What are those last 2 characters in Jₐₘₗ ? On my system (MacOS) they show up as pr [?] (closed box in both cases. I can certainly store and run the scripts with similar timing results. Thanks!

---

<div class="post-metadata">

### Author: ![Ralph\_Smith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ralph_smith/32/10344_2.png) [@Ralph\_Smith](https://discourse.julialang.org/u/Ralph_Smith)
#### Post date: [April 30, 2018, 4:03am UTC](https://discourse.julialang.org/t/request-guidance-in-optimising-the-performance-of-my-implementation-running-time-and-allocation/10528/9 "2018-04-30T04:03:16Z")

</div>

> [@goedman](#):
>
> What are those last 2 characters in Jₐₘₗ ?

These are LATIN SUBSCRIPT SMALL LETTER (M,L).  
A brilliant [tool](http://babelstone.co.uk/Unicode/whatisit.html) for questions like this is at [Babelstone](http://babelstone.co.uk).

---

<div class="post-metadata">

### Author: ![goedman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goedman/32/217_2.png) [@goedman](https://discourse.julialang.org/u/goedman)
#### Post date: [April 30, 2018, 11:57am UTC](https://discourse.julialang.org/t/request-guidance-in-optimising-the-performance-of-my-implementation-running-time-and-allocation/10528/10 "2018-04-30T11:57:29Z")

</div>

Thank you, indeed a nice tool!
