<p dir="auto">Consider the following equivalent definitions of a function and the benchmarking</p>
<div class="highlight highlight-source-julia notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="mutable struct Agent
	R::Float64
	V::Float64
end

initi(n) = Agent.(1., zeros(n))
f1(arr) = mean((a-&gt;cis(getproperty(a, :R))).(arr))
f2(arr) = mean(cis.(getproperty.(arr,:R)))
agents = initi(10000)
fcompose = @benchmark f1($agents)
fchain = @benchmark f2($agents)
judge(median(fcompose), median(fchain))"><pre class="notranslate"><span class="pl-k">mutable struct</span> Agent
	R<span class="pl-k">::</span><span class="pl-c1">Float64</span>
	V<span class="pl-k">::</span><span class="pl-c1">Float64</span>
<span class="pl-k">end</span>

<span class="pl-en">initi</span>(n) <span class="pl-k">=</span> <span class="pl-c1">Agent</span>.(<span class="pl-c1">1.</span>, <span class="pl-c1">zeros</span>(n))
<span class="pl-en">f1</span>(arr) <span class="pl-k">=</span> <span class="pl-c1">mean</span>((a<span class="pl-k">-&gt;</span><span class="pl-c1">cis</span>(<span class="pl-c1">getproperty</span>(a, <span class="pl-c1">:R</span>)))<span class="pl-k">.</span>(arr))
<span class="pl-en">f2</span>(arr) <span class="pl-k">=</span> <span class="pl-c1">mean</span>(<span class="pl-c1">cis</span>.(<span class="pl-c1">getproperty</span>.(arr,<span class="pl-c1">:R</span>)))
agents <span class="pl-k">=</span> <span class="pl-c1">initi</span>(<span class="pl-c1">10000</span>)
fcompose <span class="pl-k">=</span> <span class="pl-c1">@benchmark</span> <span class="pl-c1">f1</span>(<span class="pl-k">$</span>agents)
fchain <span class="pl-k">=</span> <span class="pl-c1">@benchmark</span> <span class="pl-c1">f2</span>(<span class="pl-k">$</span>agents)
<span class="pl-c1">judge</span>(<span class="pl-c1">median</span>(fcompose), <span class="pl-c1">median</span>(fchain))</pre></div>
<p dir="auto">The output of the judge is this-</p>
<div class="highlight highlight-source-julia notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="BenchmarkTools.TrialJudgement: 
  time:   -71.13% =&gt; improvement (5.00% tolerance)
  memory: -49.99% =&gt; improvement (1.00% tolerance)"><pre class="notranslate">BenchmarkTools<span class="pl-k">.</span>TrialJudgement<span class="pl-k">:</span> 
  time<span class="pl-k">:</span>   <span class="pl-k">-</span><span class="pl-c1">71.13</span><span class="pl-k">%</span> <span class="pl-k">=&gt;</span> improvement (<span class="pl-c1">5.00</span><span class="pl-k">%</span> tolerance)
  memory<span class="pl-k">:</span> <span class="pl-k">-</span><span class="pl-c1">49.99</span><span class="pl-k">%</span> <span class="pl-k">=&gt;</span> improvement (<span class="pl-c1">1.00</span><span class="pl-k">%</span> tolerance)</pre></div>
<p dir="auto">Shouldn't both have equivalent performance? From what I understand, the chained version iterates through the array multiple times, whereas the composed version doesn't. Is this an expected thing? If so, then won't everyone just use for loops or maps instead?</p>
<h2 dir="auto">Version Info</h2>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Julia Version 1.5.3
Commit 788b2c77c1 (2020-11-09 13:37 UTC)
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-9.0.1 (ORCJIT, skylake)
Environment:
  JULIA_REVISE_WORKER_ONLY = 1"><pre class="notranslate"><code class="notranslate">Julia Version 1.5.3
Commit 788b2c77c1 (2020-11-09 13:37 UTC)
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-9.0.1 (ORCJIT, skylake)
Environment:
  JULIA_REVISE_WORKER_ONLY = 1
</code></pre></div>
<p dir="auto">Consider two implementations of the same function</p>
<div class="highlight highlight-source-julia notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="julia&gt; function foo2(T, c, f)
       d = T.(c)
       return sum(f .* d)
       end
foo2 (generic function with 2 methods)

julia&gt; bar2(T, c, f) = sum(f .* T.(c))
bar2 (generic function with 3 methods)"><pre class="notranslate">julia<span class="pl-k">&gt;</span> <span class="pl-k">function</span> <span class="pl-en">foo2</span>(T, c, f)
       d <span class="pl-k">=</span> <span class="pl-c1">T</span>.(c)
       <span class="pl-k">return</span> <span class="pl-c1">sum</span>(f <span class="pl-k">.*</span> d)
       <span class="pl-k">end</span>
foo2 (generic <span class="pl-k">function</span> with <span class="pl-c1">2</span> methods)

julia<span class="pl-k">&gt;</span> <span class="pl-en">bar2</span>(T, c, f) <span class="pl-k">=</span> <span class="pl-c1">sum</span>(f <span class="pl-k">.*</span> <span class="pl-c1">T</span>.(c))
bar2 (generic <span class="pl-k">function</span> with <span class="pl-c1">3</span> methods)</pre></div>
<p dir="auto"><code class="notranslate">bar2</code> seems like it is just better,<br>
it avoids creating the intermidairy variable, so allows the broadcase to fuse,<br>
and indeed with a plain vector it is:</p>
<div class="highlight highlight-source-julia notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="julia&gt; @btime foo2(Float32, [0.1,0.2], [1.0,2.0]);
  473.802 ns (9 allocations: 512 bytes)

julia&gt; @btime bar2(Float32, [0.1,0.2], [1.0,2.0]);
  369.920 ns (8 allocations: 416 bytes)"><pre class="notranslate">julia<span class="pl-k">&gt;</span> <span class="pl-c1">@btime</span> <span class="pl-c1">foo2</span>(Float32, [<span class="pl-c1">0.1</span>,<span class="pl-c1">0.2</span>], [<span class="pl-c1">1.0</span>,<span class="pl-c1">2.0</span>]);
  <span class="pl-c1">473.802</span> ns (<span class="pl-c1">9</span> allocations<span class="pl-k">:</span> <span class="pl-c1">512</span> bytes)

julia<span class="pl-k">&gt;</span> <span class="pl-c1">@btime</span> <span class="pl-c1">bar2</span>(Float32, [<span class="pl-c1">0.1</span>,<span class="pl-c1">0.2</span>], [<span class="pl-c1">1.0</span>,<span class="pl-c1">2.0</span>]);
  <span class="pl-c1">369.920</span> ns (<span class="pl-c1">8</span> allocations<span class="pl-k">:</span> <span class="pl-c1">416</span> bytes)</pre></div>
<p dir="auto">But with a StaticArray <code class="notranslate">foo2</code> is much better.</p>
<div class="highlight highlight-source-julia notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="julia&gt; @btime foo2(Float32, @SVector[0.1,0.2], @SVector[1.0,2.0]);
  0.031 ns (0 allocations: 0 bytes)

julia&gt; @btime bar2(Float32, @SVector[0.1,0.2], @SVector[1.0,2.0]);
  946.536 ns (22 allocations: 528 bytes)"><pre class="notranslate">julia<span class="pl-k">&gt;</span> <span class="pl-c1">@btime</span> <span class="pl-c1">foo2</span>(Float32, <span class="pl-c1">@SVector</span>[<span class="pl-c1">0.1</span>,<span class="pl-c1">0.2</span>], <span class="pl-c1">@SVector</span>[<span class="pl-c1">1.0</span>,<span class="pl-c1">2.0</span>]);
  <span class="pl-c1">0.031</span> ns (<span class="pl-c1">0</span> allocations<span class="pl-k">:</span> <span class="pl-c1">0</span> bytes)

julia<span class="pl-k">&gt;</span> <span class="pl-c1">@btime</span> <span class="pl-c1">bar2</span>(Float32, <span class="pl-c1">@SVector</span>[<span class="pl-c1">0.1</span>,<span class="pl-c1">0.2</span>], <span class="pl-c1">@SVector</span>[<span class="pl-c1">1.0</span>,<span class="pl-c1">2.0</span>]);
  <span class="pl-c1">946.536</span> ns (<span class="pl-c1">22</span> allocations<span class="pl-k">:</span> <span class="pl-c1">528</span> bytes)</pre></div>
<p dir="auto">I think what is happening is that something about <code class="notranslate">foo2</code> is constant fold-able for <code class="notranslate">StaticArray</code>s.<br>
and that <code class="notranslate">bar2</code> isn't.</p>
<p dir="auto">Those number were taken on 1.5., but i saw similar on master</p>