You instantiate an empty vector and do nothing with it. I guess the 1.10 compiler optimizes the code better and thus doesn’t create the unused vector at all. Seems like a useful performance optimization to me.
I think you should give even more context. You can of course do something like
julia> function test()
a = Bool[]
GC.@preserve a ccall(:memchr, Ptr{Nothing}, (Ptr{Nothing}, Cint, Csize_t), pointer_from_objref(a), 0, 0)
return nothing
end
But that involves the full function call overhead of maybe ~15 cycles, and makes the compiler forget a lot of info (i.e. it doesn’t play well with surrounding LICM because the compiler must assume that the foreign call clobbers memory). So does this really reflect your real workload? We cannot help you with that without more context on what your real workloads are.
If your goal is just to count calls, you could return the count of Fib as a second argument. The count of a particular call would be the sum of the counts of each of the two recursive calls (or 1 in the base case). This could count the calls using only local variables.
The setup code is executed once per sample, but a sample may contain many evaluations. You have to add evals = 1 to @btime to get a single evaluation per sample. In my experience, this leads to quite inaccurate results for short runtimes.
Keep in mind that the language doesn’t provide any guarantees about allocations. They can be optimized away, as you observed, but in other cases, there can be more of them than you expect. Even with @VinceNeede’s solution, this is hardly a reliable way of counting the number of function calls.
Other people have mentioned setup, but the easiest solution here is just to reset the counter as part of the benchmark:
julia> const counter = Ref(0);
julia> function fib(n)
counter[] += 1
if (n == 0) || (n == 1)
return 1
else
return fib(n - 1) + fib(n - 2)
end
end
fib (generic function with 1 method)
julia> @btime begin
$counter[] = 0
f = fib(5)
(f, $counter[])
end
11.261 ns (0 allocations: 0 bytes)
(8, 15)