# Performance of filling an array

**URL:** <https://discourse.julialang.org/t/performance-of-filling-an-array/22788>\
**Category:** General Usage\
**Tags:** performance\
**Created:** [April 5, 2019, 9:38am UTC](https://discourse.julialang.org/t/performance-of-filling-an-array/22788 "2019-04-05T09:38:55Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![gdkrmr](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gdkrmr/32/2791_2.png) [@gdkrmr](https://discourse.julialang.org/u/gdkrmr)\
**Post date:** [April 5, 2019, 9:38am UTC](https://discourse.julialang.org/t/performance-of-filling-an-array/22788/1 "2019-04-05T09:38:56Z")

</div>

Ways to fill an array and performance. Peculiar to me that `f6!` does less allocations than the others and that `f4!` – `f6!` are faster than `f1!`.

- Is there an “official” way to fill an array?
- Does the performance difference come down to bounds checking?

All benchmarks are done with self compiled Julia 1.1 from current release-1.1 branch on Ubuntu 16.04.

```julia
using BenchmarkTools

x = Array{Float64}(undef, 1000, 1000)
f1!(x) = fill!(x, 0.0)
f2!(x) = x .= 0.0;
f3!(x) = x .= 0;
f4!(x) = x[:] .= 0.0;
f5!(x) = x[:] .= 0;
f6!(x) = x[:,:] .= 0;
function f7!(x)
    @inbounds for i in eachindex(x)
        x[i] = 0
    end
end
function f8!(x)
    for i in eachindex(x)
        x[i] = 0
    end
end

julia> @btime f1!($x);
  655.608 μs (0 allocations: 0 bytes)

julia> @btime f2!($x);
  659.358 μs (0 allocations: 0 bytes)

julia> @btime f3!($x);
  662.742 μs (0 allocations: 0 bytes)

julia> @btime f4!($x);
  386.123 μs (3 allocations: 128 bytes)

julia> @btime f5!($x);
  378.328 μs (3 allocations: 128 bytes)

julia> @btime f6!($x);
  377.334 μs (1 allocation: 48 bytes)

julia> @btime f7!($x);
  377.607 μs (0 allocations: 0 bytes)

julia> @btime f8!($x);
  664.502 μs (0 allocations: 0 bytes)
```

---

<div class="post-metadata">

**Author:** ![zgornel](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zgornel/32/217487_2.png) [@zgornel](https://discourse.julialang.org/u/zgornel)\
**Post date:** [April 5, 2019, 11:01am UTC](https://discourse.julialang.org/t/performance-of-filling-an-array/22788/2 "2019-04-05T11:01:03Z")

</div>

I do not get the same performance differences …  
`f1!` to `f3!` take `~350 μs`, `f4!` to `f7!` take `~250 μs`, `f8!` takes `~650 μs`.

I think `fill!` is intended for the ‘official’ way (if there can be one).

Differences are indeed related to bounds checking for `f8!` as running julia with `--check-bounds=no` speeds it up (but not `f1`-`f3`)

---

<div class="post-metadata">

**Author:** ![baggepinnen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/baggepinnen/32/693_2.png) [@baggepinnen](https://discourse.julialang.org/u/baggepinnen)\
**Post date:** [April 5, 2019, 2:00pm UTC](https://discourse.julialang.org/t/performance-of-filling-an-array/22788/3 "2019-04-05T14:00:59Z")

</div>

Looks kind of similar to

> [@Possible inefficiency in randn!](https://discourse.julialang.org/t/possible-inefficiency-in-randn/22732/5):
>
> I was confused in the previous post, sorry. That has nothing to do with simd: using Random broadcast\_randn(x) = (x .= randn.()) inplace\_randn(x) = randn!(x) function myrandn!(x) @inbounds @simd for i in eachindex(x) x[i] = randn() end x end function myrandn\_nosimd!(x) @inbounds for i in eachindex(x) x[i] = randn() end x end x = zeros(10\_000) @btime broadcast\_randn($x); @btime inplace\_randn($x); @btime myrandn!($x); @btime myrandn\_nosimd!($x); is only…

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [April 5, 2019, 2:38pm UTC](https://discourse.julialang.org/t/performance-of-filling-an-array/22788/4 "2019-04-05T14:38:48Z")

</div>

Hah, that’s funny. Try it with any value other than 0:

```julia
julia> x = Array{Float64}(undef, 1000, 1000)
       f1!(x) = fill!(x, 1.0)
       f2!(x) = x .= 1.0;
       f3!(x) = x .= 1;
       f4!(x) = x[:] .= 1.0;
       f5!(x) = x[:] .= 1;
       f6!(x) = x[:,:] .= 1;
       function f7!(x)
           @inbounds for i in eachindex(x)
               x[i] = 1
           end
       end
       function f8!(x)
           for i in eachindex(x)
               x[i] = 1
           end
       end
f8! (generic function with 1 method)

julia> @btime f1!($x);
  430.771 μs (0 allocations: 0 bytes)

julia> @btime f2!($x);
  429.985 μs (0 allocations: 0 bytes)

julia> @btime f3!($x);
  431.457 μs (0 allocations: 0 bytes)

julia> @btime f4!($x);
  430.093 μs (3 allocations: 128 bytes)

julia> @btime f5!($x);
  430.087 μs (3 allocations: 128 bytes)

julia> @btime f6!($x);
  432.691 μs (1 allocation: 48 bytes)

julia> @btime f7!($x);
  431.716 μs (0 allocations: 0 bytes)

julia> @btime f8!($x);
  465.570 μs (0 allocations: 0 bytes)

```

So what’s going on here? It’s that some of these cases allow for constant propagation of the 0 the “whole way down” to the inner loop — and if that 0 is available to LLVM at compile time, then LLVM can use special instructions to zero the entire chunk of memory.

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [April 5, 2019, 3:29pm UTC](https://discourse.julialang.org/t/performance-of-filling-an-array/22788/5 "2019-04-05T15:29:35Z")

</div>

As far as why some of these forms allow for constant propagation and some don’t, it appears as though there was a [strange edge case in the compiler](https://discourse.julialang.org/t/performance-degradation-of-fill-in-latest-julia-0-7-dev/9648/3) back in the 0.7 timeframe that prompted a [simple workaround](https://github.com/JuliaLang/julia/pull/26418). That’s no longer necessary.

> <https://github.com/JuliaLang/julia/pull/31626>
>
> This used to be necessary to avoid a strange edge case in the compiler, but it i…s no longer necessary -- and can now in fact cause other performance snags.
> 
> Using the test case from \[the original discourse post that prompted #26418\](https://discourse.julialang.org/t/performance-degradation-of-fill-in-latest-julia-0-7-dev/9648):
> 
> \`\`\`julia
> \# BEFORE
> julia\> @btime fill(1.0,5,5);
> 49.335 ns (1 allocation: 288 bytes)
> 
> julia\> @btime fill(0.0,5,5);
> 52.773 ns (1 allocation: 288 bytes)
> 
> \# AFTER
> julia\> @btime fill(0.0,5,5);
> 46.724 ns (1 allocation: 288 bytes)
> 
> julia\> @btime fill(1.0,5,5);
> 42.202 ns (1 allocation: 288 bytes)
> \`\`\`
> 
> Even more compelling is the case for a larger array where LLVM can exploit some sort of wider/simdier implementation for zeros when this gets inlined thanks to constant propagation:
> 
> \`\`\`julia
> \# AFTER
> julia\> A = Array{Float64}(undef, 1000, 1000);
> 
> julia\> @btime fill!($A,0.0);
> 345.103 μs (0 allocations: 0 bytes)
> 
> julia\> @btime fill!($A,1.0);
> 458.976 μs (0 allocations: 0 bytes)
> \`\`\`
> 
> Ref https://discourse.julialang.org/t/performance-of-filling-an-array/22788

---

<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:** [April 5, 2019, 3:48pm UTC](https://discourse.julialang.org/t/performance-of-filling-an-array/22788/6 "2019-04-05T15:48:15Z")

</div>

That’s great!

But do you know why `x[:] .= 0` is faster than `x .= 0`?

```julia
foo!(x, val) = (x .= val)
bar!(x, val) = (x[:] .= val)

julia> @btime foo!($x, 0);
379.111 μs (0 allocations: 0 bytes)

julia> @btime bar!($x, 0);
280.690 μs (1 allocation: 48 bytes)

```

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [April 5, 2019, 3:51pm UTC](https://discourse.julialang.org/t/performance-of-filling-an-array/22788/7 "2019-04-05T15:51:07Z")

</div>

Yes, it’s because we have a peephole “performance optimization” in broadcast to use `fill!` for simple cases — because that should be the fastest way to do it. But it backfired here…

> <https://github.com/JuliaLang/julia/blob/d43c23b1d19616fabf7e593497299d4cc59acf82/base/broadcast.jl#L844-L850>

---

<div class="post-metadata">

**Author:** ![under-Peter](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/under-peter/32/3626_2.png) [@under-Peter](https://discourse.julialang.org/u/under-Peter)\
**Post date:** [April 5, 2019, 3:52pm UTC](https://discourse.julialang.org/t/performance-of-filling-an-array/22788/8 "2019-04-05T15:52:05Z")

</div>

I can’t reproduce these differences on my system, I get ` 325.541 μs` and `327.219 μs` respectively.

---

<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:** [April 5, 2019, 3:52pm UTC](https://discourse.julialang.org/t/performance-of-filling-an-array/22788/9 "2019-04-05T15:52:12Z")

</div>

OK, so it should be fixed once `fill!` is fixed, then?

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [April 5, 2019, 3:53pm UTC](https://discourse.julialang.org/t/performance-of-filling-an-array/22788/10 "2019-04-05T15:53:18Z")

</div>

> [@under-Peter](#):
>
> I can’t reproduce these differences on my system, I get `325.541 μs` and `327.219 μs` respectively.

It’s likely you have an older processor that doesn’t have the AVX-512 (wide SIMD) instruction set.

> [@DNF](#):
>
> OK, so it should be fixed once `fill!` is fixed, then?

Yup!
