CPython vs. Numba vs. Rust
Verfasst: Freitag 11. Oktober 2024, 20:21
Hallo,
in den letzten Tage habe ich mich etwas damit beschäftigt, was denn so passiert, wenn ich einen Code ausführe. Ursprung war ein Python-Code den ich "gefunden" hatte und dem man auch schon ansieht, dass man das so nicht programmieren würde. Ich habe ihn wie er ist einfach mal in eine Funktion gesteckt und die Zeit gemessen.
Als Ergebnis kam auf meinem Laptop:
Ich schreibe mal meine Gedanken, wie ich mir das vorstelle und bitte um Berichtigung. Wenn ich den Code ausführe, dann geht der Interpreter darüber und checkt die Syntax und ob alle Namen definiert sind. Wenn das passt dann wird je nach Code, da wo eben der erste ausführbare Code steht, Zeile für Zeile in Maschinencode übersetzt und ausgeführt. Und zwar immer nach einander (?) Also übersetzen, ausführen, übersetzen, ausführen. Da in Python alles ein Objekt ist und da die Objekte verschiedene Datentypen besitzen können, muss das alles zur Laufzeit auch überprüft werden.
Wir eine Funktion bei jedem Aufruf erneut übersetzt? Ja oder?
Es gibt `Numba` mit dem Just-in-time-Dekorator. Das wollte ich testen:
Ergebnis:
Was macht Numba genau? Ich weis nur, dass die Funktion einmal übersetzt wird und dass der Code irgendwie noch optimiert wird. Aber wie oder was wird da optimiert, damit das fast 29x so schnell ist?
Wenn ich davon ausgehe, dass das dynamische an Python der Punkt ist, der viel Zeit braucht, dann war es für mich naheliegend, dass wenn ich eine statische Sprache verwende und direkt den kompilierten Code ausführe, dann müsste ich am schnellsten sein.
Also habe ich den Code stumpf in `Rust` übersetzt:
Ergebnis:
Habe ich nicht erwartet. Liegt das an der Optimierung von `Numba` oder wieso ist das langsamer?
Wird der Code eigentlich direkt, egal von welcher Sprache, in Maschinencode übersetzt? Weil ich kann mit `dis` in Python den Assembler Code anschauen, in den der Python Code übersetzt wird. Wird der dann vor der Ausführung noch einmal von `Assembler` übersetzt? So wie ich das verstanden habe, ist `Assembler` die hardwarenähste Sprache, die noch von Menschen lesbar ist.(?)
Ich weis das sind wieder viele Fragen, aber ich finde es einfach so interessant was denn im Detail passiert, wenn ich einen Code ausführe.
Freue mich wenn ihr etwas Zeit für Erklärungen habt.
Danke und Grüße
Dennis
in den letzten Tage habe ich mich etwas damit beschäftigt, was denn so passiert, wenn ich einen Code ausführe. Ursprung war ein Python-Code den ich "gefunden" hatte und dem man auch schon ansieht, dass man das so nicht programmieren würde. Ich habe ihn wie er ist einfach mal in eine Funktion gesteckt und die Zeit gemessen.
Code: Alles auswählen
from time import monotonic
def get_prime():
prim_counter = 0
prim_pruefzahl = 0
zahl = 1
prime = []
while prim_counter < 10001:
for teiler in range(1, zahl):
if zahl % teiler == 0:
prim_pruefzahl += 1
if prim_pruefzahl == 1:
prime.append(zahl)
prim_counter += 1
else:
prim_pruefzahl = 0
zahl += 1
return prime
def main():
start = monotonic()
print(get_prime()[-1])
print(f"Dauer {monotonic() - start:.2f} s")
if __name__ == '__main__':
main()
Code: Alles auswählen
[dennis@dennis ~]$ ~/PycharmProjects/Forum/.venv/bin/python ~/PycharmProjects/Forum/cpython.py
104759
Dauer 511.75 s
Wir eine Funktion bei jedem Aufruf erneut übersetzt? Ja oder?
Es gibt `Numba` mit dem Just-in-time-Dekorator. Das wollte ich testen:
Code: Alles auswählen
from time import monotonic
from numba import njit
@njit
def get_prime():
prim_counter = 0
prim_pruefzahl = 0
zahl = 1
prime = []
while prim_counter < 10001:
for teiler in range(1, zahl):
if zahl % teiler == 0:
prim_pruefzahl += 1
if prim_pruefzahl == 1:
prime.append(zahl)
prim_counter += 1
else:
prim_pruefzahl = 0
zahl += 1
return prime
def main():
start = monotonic()
print(get_prime()[-1])
print(f"Dauer {monotonic() - start:.2f} s")
if __name__ == '__main__':
main()
Code: Alles auswählen
[dennis@dennis ~]$ ~/PycharmProjects/Forum/.venv/bin/python ~/PycharmProjects/Forum/njit_test.py
104759
Dauer 17.73 s
Wenn ich davon ausgehe, dass das dynamische an Python der Punkt ist, der viel Zeit braucht, dann war es für mich naheliegend, dass wenn ich eine statische Sprache verwende und direkt den kompilierten Code ausführe, dann müsste ich am schnellsten sein.
Also habe ich den Code stumpf in `Rust` übersetzt:
Code: Alles auswählen
use std::time::Instant;
fn get_prime() -> Vec<i32> {
let mut prim_counter: i32 = 0;
let mut prim_pruefzahl: i32 = 0;
let mut zahl: i32 = 1;
let mut prime: Vec<i32> = Vec::with_capacity(1000);
while prim_counter < 10001 {
for teiler in 1..zahl {
if zahl % teiler == 0 {
prim_pruefzahl += 1;
};
};
if prim_pruefzahl == 1 {
prime.push(zahl);
prim_counter += 1;
} else {
prim_pruefzahl = 0;
};
zahl += 1;
}
prime
}
fn main() {
let start = Instant::now();
let prime = get_prime();
println!("{:?}", prime.last());
println!("Dauer: {:.2?}", start.elapsed());
}
Ergebnis:
Code: Alles auswählen
[dennis@dennis ~]$ ./rust_test/main
Some(104759)
Dauer: 61.26s
Wird der Code eigentlich direkt, egal von welcher Sprache, in Maschinencode übersetzt? Weil ich kann mit `dis` in Python den Assembler Code anschauen, in den der Python Code übersetzt wird. Wird der dann vor der Ausführung noch einmal von `Assembler` übersetzt? So wie ich das verstanden habe, ist `Assembler` die hardwarenähste Sprache, die noch von Menschen lesbar ist.(?)
Ich weis das sind wieder viele Fragen, aber ich finde es einfach so interessant was denn im Detail passiert, wenn ich einen Code ausführe.
Freue mich wenn ihr etwas Zeit für Erklärungen habt.
Danke und Grüße
Dennis