2-е ОБНОВЛЕНИЕ: Проверьте hckr ответ. Намного лучше, чем у меня.
ОБНОВЛЕНИЕ: Это не исчерпывающий ответ. Столько, сколько мне удалось разгадать, и мне пришлось сейчас сдаться из-за нехватки времени.
Я, вероятно, не лучший человек, чтобы ответить на этот вопрос, поскольку, что касается оптимизации компилятора, я знаю достаточно, чтобы быть опасным. Надеюсь, кто-то, кто понимает компилятор Джулии, немного лучше наткнется на этот вопрос и сможет дать более исчерпывающий ответ, потому что, насколько я вижу, ваша c2
функция выполняет огромную работу, которая ей не нужна.
Итак, здесь есть как минимум две проблемы. Во-первых, как c1
, так и c2
будут всегда возвращать nothing
. По какой-то причине я не понимаю, компилятор может решить это в случае c1
, но не в случае c2
. Следовательно, после компиляции c1
выполняется почти мгновенно, потому что цикл в алгоритме фактически никогда не выполняется. Действительно:
julia> @btime c1()
1.535 ns (0 allocations: 0 bytes)
Вы также можете увидеть это, используя @code_native c1()
и @code_native c2()
. Первая содержит всего пару строк, а вторая содержит гораздо больше инструкций. Также стоит отметить, что первый не содержит никаких ссылок на функцию <=
, указывающую, что условие в цикле while
было полностью оптимизировано.
Мы можем решить эту первую проблему, добавив оператор return x
в нижней части обеих ваших функций, что заставит компилятор фактически заняться вопросом, каким будет окончательное значение x
.
Однако, если вы сделаете это, вы заметите, что c1
все еще примерно в 10 раз быстрее, чем c2
, что является второй загадкой в вашем примере.
Мне кажется, что даже с return x
достаточно умный компилятор обладает всей информацией, необходимой для полного пропуска цикла. То есть во время компиляции ему известно начальное значение x
, точное значение преобразования внутри цикла и точное значение завершающего условия. Удивительно, но если вы запустите @code_native c1()
(после добавления return x
внизу), вы заметите, что оно действительно отработало возвращаемое значение функции прямо в собственном коде (cmpq $1000000001
):
julia> @code_native c1()
.text
; Function c1 {
; Location: REPL[2]:2
movq $-1, %rax
nopw (%rax,%rax)
; Location: REPL[2]:3
; Function <=; {
; Location: int.jl:436
; Function <=; {
; Location: int.jl:429
L16:
addq $1, %rax
cmpq $1000000001, %rax # imm = 0x3B9ACA01
;}}
jb L16
; Location: REPL[2]:6
retq
nopl (%rax)
;}
так что я не совсем уверен, почему он все еще выполняет какую-либо работу!
Для справки вот вывод @code_native c2()
(после добавления return x
):
julia> @code_native c2()
.text
; Function c2 {
; Location: REPL[3]:2
pushq %r14
pushq %rbx
pushq %rax
movq $-1, %rbx
movabsq $power_by_squaring, %r14
nopw %cs:(%rax,%rax)
; Location: REPL[3]:3
; Function literal_pow; {
; Location: none
; Function macro expansion; {
; Location: none
; Function ^; {
; Location: intfuncs.jl:220
L32:
addq $1, %rbx
movl $10, %edi
movl $9, %esi
callq *%r14
;}}}
; Function <=; {
; Location: int.jl:436
; Function >=; {
; Location: operators.jl:333
; Function <=; {
; Location: int.jl:428
testq %rax, %rax
;}}}
js L59
cmpq %rax, %rbx
jbe L32
; Location: REPL[3]:6
L59:
movq %rbx, %rax
addq $8, %rsp
popq %rbx
popq %r14
retq
nopw %cs:(%rax,%rax)
;}
Здесь явно много дополнительной работы для c2
, которая не имеет для меня особого смысла. Надеюсь, кто-то, более знакомый с внутренностями Джулии, сможет пролить свет на это.