# Experiments with LoopVectorization and convolutions

**URL:** https://discourse.julialang.org/t/experiments-with-loopvectorization-and-convolutions/123188
**Category:** Performance
**Tags:** simd, loopvectorization, for-loop, convolution
**Created:** [November 27, 2024, 8:50pm UTC](https://discourse.julialang.org/t/experiments-with-loopvectorization-and-convolutions/123188 "2024-11-27T20:50:58Z")
**Posts on this page:** 5
**Page:** 2

<div class="post-metadata">

### Author: ![roflmaostc](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/roflmaostc/32/30123_2.png) [@roflmaostc](https://discourse.julialang.org/u/roflmaostc)
#### Post date: [December 3, 2024, 5:47am UTC](https://discourse.julialang.org/t/experiments-with-loopvectorization-and-convolutions/123188/21 "2024-12-03T05:47:51Z")

</div>

Yes, hence also should work with 1.11

Tullio.jl would use LV if it was loaded, otherwise no.

---

<div class="post-metadata">

### Author: ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)
#### Post date: [December 3, 2024, 6:28am UTC](https://discourse.julialang.org/t/experiments-with-loopvectorization-and-convolutions/123188/22 "2024-12-03T06:28:05Z")

</div>

> [@roflmaostc](#):
>
> Yes, hence also should work with 1.11

But Loopvectorization.jl _does_ work with 1.11. Or, at least I had that impression.

---

<div class="post-metadata">

### Author: ![roflmaostc](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/roflmaostc/32/30123_2.png) [@roflmaostc](https://discourse.julialang.org/u/roflmaostc)
#### Post date: [December 3, 2024, 6:36am UTC](https://discourse.julialang.org/t/experiments-with-loopvectorization-and-convolutions/123188/23 "2024-12-03T06:36:28Z")

</div>

Oh yeah, seems like the 1.11 issues have been solved:

It’s future is still unclear but at least there is some hope!

> **[GitHub - JuliaSIMD/LoopVectorization.jl: Macro(s) for vectorizing loops.](https://github.com/JuliaSIMD/LoopVectorization.jl?tab=readme-ov-file#maintanence)**
>
> Macro(s) for vectorizing loops.

> **[SciML: Open Source Software for Scientific Machine Learning](https://sciml.ai/small_grants/#update_loopvectorization_to_support_changes_in_julia_v112_200)**
>
> Open Source Software for Scientific Machine Learning

---

<div class="post-metadata">

### Author: ![Marco\_Lombardi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/marco_lombardi/32/14608_2.png) [@Marco\_Lombardi](https://discourse.julialang.org/u/Marco_Lombardi)
#### Post date: [December 3, 2024, 7:40pm UTC](https://discourse.julialang.org/t/experiments-with-loopvectorization-and-convolutions/123188/24 "2024-12-03T19:40:41Z")

</div>

Yes, but the benchmarks for Tullio are not great without LV. If I look at my example, I see that

```julia
@bs (@tullio threads=false $out[i, j] = $A[i-l, j-k] * $kernel[l, k]) seconds=1

```

gives essentially the same results of `f0!`, because Tullio is using LV to do the work. However, if I disable LV

```julia
@bs (@tullio threads=false avx=false $out[i, j] = $A[i-l, j-k] * $kernel[l, k]) seconds=1

```

then Tullio is almost ten times slower (therefore, worse than my naive implementation). Therefore, in my very specific use case, Tullio does not seem to provide any advantage. I will check, however, if it helps with GPU (although the GPU support in Tullio is experimental).

---

<div class="post-metadata">

### Author: ![Marco\_Lombardi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/marco_lombardi/32/14608_2.png) [@Marco\_Lombardi](https://discourse.julialang.org/u/Marco_Lombardi)
#### Post date: [December 3, 2024, 8:35pm UTC](https://discourse.julialang.org/t/experiments-with-loopvectorization-and-convolutions/123188/25 "2024-12-03T20:35:27Z")

</div>

For my own reference, and with the hope that it can help other people, I am writing here what I just found from an unrelated question replied to by @Elrod.

With the help of `@turbo_debug` one can know what LV is doing. Redefine the function as

```julia
function f0_debug!(out::AbstractArray{T,N}, A::AbstractArray{S,N}, kernel::AbstractArray{K,N}) where {T,S,K,N}
    LoopVectorization.@turbo_debug for J ∈ CartesianIndices(out)
        tmp = zero(eltype(out))
        for I ∈ CartesianIndices(kernel)
            tmp += A[J-I] * kernel[I]
        end
        out[J] = tmp
    end
end

```

and then call

```julia-repl
julia> ls = f0_debug!(out, A, kernel);

julia> LoopVectorization.choose_order(ls)
([Symbol("J#2#"), Symbol("J#1#"), Symbol("I#2#"), Symbol("I#1#")], Symbol("I#1#"), Symbol("J#2#"), Symbol("J#1#"), 1, 6)

```

This means that

- LV is using the following loop orders, from the outermost to the innermost: `J[2], J[1], I[2], I[1]`
- The next two symbols, `I[1]` and `J[2]`, are the unrolled loops. The first is unrolled by 1, the second by 6 (the two integers values at the end of the list).
- `J[1]` is the vectorized loop

There is also a related function `LoopVectorization.choose_order_cost`, which also provides some “cost” for the loop and a boolean indicating if LV will inline.

[Previous page](https://discourse.julialang.org/t/experiments-with-loopvectorization-and-convolutions/123188.md?page=1)
