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 DateTime
obiektami 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.