You are not benchmarking the F correctly. Here is a way to do it:
julia> @btime $F(1)
0.026 ns (0 allocations: 0 bytes)
2
The problem is not that F is a global non-constant variable while G is a constant, so when accessing F its value is not stable within @btime.
Adding a $ in front of the call makes F a local variable during a benchmark and you can then see that it is equally fast.
Also it is probably better to benchmark some larger functions in such cases. Here is a quick example:
julia> function test(x)
s = 0
for i in 1:10^6
s += x(1)
end
s
end
test (generic function with 1 method)
julia> @btime test($F)
35.830 μs (0 allocations: 0 bytes)
2000000
julia> @btime test($G)
35.839 μs (0 allocations: 0 bytes)
2000000
Also you can check with @code_native that F and G end up with identical native code:
julia> @code_native F(1)
.text
; ┌ @ REPL[10]:2 within `foo'
cmpb $0, (%rsi)
je L17
; │ @ REPL[10]:3 within `foo'
; │┌ @ int.jl:53 within `+'
addq $1, %rdx
; │└
movq %rdx, (%rdi)
movb $2, %dl
xorl %eax, %eax
retq
L17:
movb $1, %dl
xorl %eax, %eax
; │ @ REPL[10]:3 within `foo'
retq
nopw %cs:(%rax,%rax)
; └
julia> @code_native G(1)
.text
; ┌ @ REPL[10]:2 within `foo'
cmpb $0, (%rsi)
je L17
; │ @ REPL[10]:3 within `foo'
; │┌ @ int.jl:53 within `+'
addq $1, %rdx
; │└
movq %rdx, (%rdi)
movb $2, %dl
xorl %eax, %eax
retq
L17:
movb $1, %dl
xorl %eax, %eax
; │ @ REPL[10]:3 within `foo'
retq
nopw %cs:(%rax,%rax)
; └