Jest to swego rodzaju dziwne porównanie wydajności, ponieważ zwykle mierzy się czas potrzebny na obliczenie czegoś istotnego, zamiast sprawdzać, ile trywialnych iteracji można wykonać w określonym czasie. Miałem problem z uruchomieniem kodów Python i Julia, więc zmodyfikowałem kod Julia do pracy i po prostu nie uruchomiłem kodu Python. Jak zauważył @chepner w komentarzu, korzystanie now()i porównywanie czasu z DateTimeobiektami jest dość drogie. Funkcja Python time.time()po prostu zwraca wartość zmiennoprzecinkową. Jak się okazuje, istnieje funkcja Julia, time()która robi dokładnie to samo:
julia> time()
1.587648091474481e9
Oto czas oryginalnej f()funkcji (zmodyfikowanej do pracy) w moim systemie:
julia> using Dates
julia> function f()
i = 0
t1 = now()
while true
i += 1
if now() - t1 >= Millisecond(1000)
break
end
end
return i
end
f (generic function with 1 method)
julia> f()
4943739
Wykonał prawie 5 milionów iteracji, zanim upłynął czas. Tak jak powiedziałem, nie byłem w stanie uruchomić twojego kodu Python w moim systemie bez znaczącego majsterkowania (czego nie zawracałem sobie głowy). Ale oto wersja, f()której używa time()zamiast tego, którą wyobraźni nazywam g():
julia> function g()
i = 0
t1 = time()
while true
i += 1
if time() - t1 >= 1
break
end
end
return i
end
g (generic function with 1 method)
julia> g()
36087637
Ta wersja wykonała 36 milionów iteracji. Więc myślę, że Julia jest szybsza w zapętlaniu? Tak! Cóż, właściwie główną pracą w tej pętli są wywołania, time()więc ... Julia szybciej generuje wiele time()połączeń!
Dlaczego czas jest taki dziwny? Jak już powiedziałem, większość faktycznej pracy tutaj to dzwonienie time(). Reszta pętli tak naprawdę nic nie robi. W optymalizowanym języku kompilatora, jeśli kompilator zobaczy pętlę, która nic nie robi, całkowicie ją wyeliminuje. Na przykład:
julia> function h()
t = 0
for i = 1:100_000_000
t += i
end
return t
end
h (generic function with 1 method)
julia> h()
5000000050000000
julia> @time h()
0.000000 seconds
5000000050000000
Woah, zero sekund! Jak to możliwe? Spójrzmy na kod LLVM (coś w rodzaju kodu maszynowego, ale dla wyobrażonej maszyny używanej jako reprezentacja pośrednia), która obniża się do:
julia> @code_llvm h()
; @ REPL[16]:1 within `h'
define i64 @julia_h_293() {
top:
; @ REPL[16]:6 within `h'
ret i64 5000000050000000
}
Kompilator widzi pętlę, stwierdza, że wynik jest za każdym razem taki sam, i po prostu zwraca tę stałą wartość zamiast faktycznego wykonania pętli. Co oczywiście zajmuje zero czasu.