Kostenfrage (Resourcenverbrauch)

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
_Mala_Fide_
User
Beiträge: 53
Registriert: Dienstag 22. Dezember 2015, 19:17

Hallo,

habe gerade überlegt, ob ein "if" günstiger im Resourcenverbrauch ist, als ein "if not", oder sich beides nichts nimmt.
Da ich mir nicht ganz sicher bin, wollte ich mal fragen, ob das jemand von euch genau weiß?

Nochmal mit Codebeispiel:

Code: Alles auswählen

if bedingung:
	mache a
else:
	mache b
oder

Code: Alles auswählen

if not bedingung:
	mache b
else:
	mache a
Meine Überlegung dazu ist, dass "if not" mehr Resourcen verbraucht, als ein "if".
Weil "if" ja nur einen Zustand kennen muss und "if not" müsste rein theoretisch zwei Zustände kennen um zu testen, ob etwas nicht wahr ist.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

if not wird minimal mehr brauchen, weil es ein Operator mehr ist. Deine Begründung ist aber falsch. Die Bedingung ist immer noch nur eine. Es wird die eben nur negiert. Das sich das Ganze merklich verlängert ist aber außerhalb von Microbenchmarks unmerklich.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@_Mala_Fide_: Es ist ein Operator mehr der ausgeführt werden muss, verbraucht also (ganz wenig) mehr Speicher und etwas mehr Laufzeit. Beides wird in den allermeisten Fällen nicht ins Gewicht fallen. Es sollte also vorrangig die Verständlichkeit im Vordergrund stehen, also ist die Bedingung leichter mit oder ohne ``not`` verständlich und welchen Fall möchte man direkt beim ``if`` stehen haben und was ist der „sonst“-Fall, also in der Regel der nicht so häufige/prominente/wichtigere Fall.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
_Mala_Fide_
User
Beiträge: 53
Registriert: Dienstag 22. Dezember 2015, 19:17

Danke für eure schnellen Antworten.
Mir ist die Frage in den Sinn gekommen, weil ich in meinen Programmen häufig mit "if not" abfrage, da dies in den Abfragen den Hauptfall abdeckt, der meist verwendet wird. Fand es so auch sinnvoller, da die "if not" Bedingung sozusagen die Hauptaufgabe einiger Funktionen ist.
Dachte nur beim drüberlesen, ob es nicht doch sinnvoller, bzw. resourcensparender wäre mit "if" den Sonderfall zu prüfen und die Hauptaufgabe dann in "else" zu behandeln.
Also werde ich alles so lassen, da es auch verständlicher und leichter zu lesen ist.
narpfel
User
Beiträge: 643
Registriert: Freitag 20. Oktober 2017, 16:10

Tatsächlich führt ein `not` in einem `if` nicht dazu, dass wirklich der `not`-Operator ausgeführt wird, sondern es wird statt einem `POP_JUMP_IF_FALSE`-Opcode ein `POP_JUMP_IF_TRUE` generiert:

Code: Alles auswählen

In [18]: def g():
    ...:     if a:
    ...:         return 42
    ...:     else:
    ...:         return 27
    ...:

In [19]: def h():
    ...:     if not b:
    ...:         return 42
    ...:     else:
    ...:         return 27
    ...:

In [20]: dis.dis(g)
  2           0 LOAD_GLOBAL              0 (a)
              2 POP_JUMP_IF_FALSE        4 (to 8)

  3           4 LOAD_CONST               1 (42)
              6 RETURN_VALUE

  5     >>    8 LOAD_CONST               2 (27)
             10 RETURN_VALUE

In [21]: dis.dis(h)
  2           0 LOAD_GLOBAL              0 (b)
              2 POP_JUMP_IF_TRUE         4 (to 8)

  3           4 LOAD_CONST               1 (42)
              6 RETURN_VALUE

  5     >>    8 LOAD_CONST               2 (27)
             10 RETURN_VALUE
Die Implementierung von beiden Opcodes ist quasi gleich, also würde ich erwarten, dass man damit nichtmal in einem Mikrobenchmark einen signifikanten Unterschied wird messen können.

An der Empfehlung von __blackjack__ ändert das natürlich nichts: Der Code sollte so geschrieben werden, dass er möglichst verständlich ist und nicht mit fragwürdigen Mikrooptimierungen im Hinterkopf, die (wie hier) oft nichts bringen.

Falls der Code tatsächlich zu langsam ist: Messen und sich speziell die Hotspots angucken. (Oder einfach PyPy benutzen.)
Benutzeravatar
DeaD_EyE
User
Beiträge: 1012
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Code: Alles auswählen

[andre@andre-Fujitsu-i5 ~]$ ipython
Python 3.11.0 (main, Oct 25 2022, 10:22:43) [GCC 12.2.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.6.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: def test1(bedingung=True):
   ...:     if not bedingung:
   ...:         pass
   ...:     else:
   ...:         pass

In [2]: def test2(bedingung=True):
   ...:     if bedingung:
   ...:         pass
   ...:     else:
   ...:         pass

In [3]: %timeit test1()
47.7 ns ± 0.475 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

In [4]: %timeit test2()
48 ns ± 1.05 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

Code: Alles auswählen

Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz
Über 48 ns mache ich mir keine Gedanken.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

_Mala_Fide_ hat geschrieben: Montag 21. November 2022, 22:23 habe gerade überlegt, ob ein "if" günstiger im Resourcenverbrauch ist, als ein "if not", oder sich beides nichts nimmt.
Da tut sich nicht viel. Stell es dir so vor, dass deine ``if`` bzw ``if not`` Blöcke beim Kompilieren quasi aufgelöst werden. Der Code wird dabei also linear abgearbeitet, aber es werden Sprünge benutzt (Goto kennt man evtl noch vom C64). Das musst du dir jetzt umgekehrt vorstellen: Bei einem ``if`` Block überspringt er den Code im Block nur, wenn die Bedingung *nicht* zutrifft, ansonsten "darf" er den Block ja ausführen. Bei einem ``if not`` ist es natürlich anders herum. Die Logik ist also im Endeffekt die selbe. Daran haben die Python-Entwickler gedacht und entsprechende Opcodes (JUMP_IF_FALSE bzw JUMP_IF_TRUE) eingebaut. Die sind von der Laufzeit her gleich.
Benutzeravatar
Kebap
User
Beiträge: 686
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Ich habe gelernt, dass man zuerst den Block schreiben soll, der wahrscheinlicher auftreten wird.
Ob das wirklich ins Gewicht fällt, keine Ahnung, habe ich nie geprüft, und wenn dann vermutlich nur gering.
Grundsätzlich schreibe ich meine Fälle so auf, dass sie möglichst verständlich sind, wenn ich (oder wer anders) sie später nochmal liest.
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Kebap: von solchen Microoptimierungen sollte man sich nicht treiben lassen. Was der Compiler letztlich daraus macht, ist wiederum seine Sache, wobei der CPython-Compiler relativ wenige, einfache Optimierungen macht. Auf Maschinenebene kann ein falsch vorhergesagter Sprung (oder Nicht-Sprung) einige CPU-Zyklen kosten, in der Virtuellen Maschine von Python ist es letztlich nur die Entscheidung ob der Instruktionszähler um 1 oder um eine variable Anzahl hochgezählt wird.

Der Grund, warum man zuerst den wahrscheinlicheren Fall im if-Block schreiben sollte, liegt hier auch in der Lesbarkeit, weil damit der Leser sofort beim "wichtigen" Code weiterlesen kann.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wo es einen Unterschied in der Laufzeit machen kann sind mehrere ``if``/``elif``/…-Blöcke, weil dort dann weniger oft unwahrscheinliches erfolglos geprüft wird bis man bei dem Zweig angelangt ist, der zutrifft.

Wie viel das dann tatsächlich bringt die Zweige umzuordnen, hängt davon ab wie teuer die Auswertungen der Bedingungen sind, und da sind wir dann auch schon wieder dabei, dass man Laufzeiten am besten erst einmal messen sollte um die Punkte zu finden wo es sich überhaupt lohnt zu optimieren. Und in der Regel muss man zusätzlich zur Laufzeitmessung der einzelnen Bedingungen auch noch die Häufigkeit zählen, in der die bei üblichen/realistischen Daten zutreffen oder nicht. Die Entscheidung was der Haupt- oder Normalfall ist, fällt oft leicht, aber die Einschätzung wie oft eigentlich welcher Randfall auftreten kann, um die optimal zu ordnen, ist nicht immer so offensichtlich. Ohne Messungen kann einen das reine Bauchgefühl auch in die Irre führen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Im Endeffekt kommt es halt darauf an, wie teuer der eigentliche Test ist. Ein ``if x > 0`` verbraucht in der Regel weniger Ressourcen als ein ``is_valid(x)``, wenn innerhalb der Funktion noch dies und das geprüft wird. Das reine Vorhandensein von if-Blöcken ist erstmal nicht so wild. Bei mehreren Bedingungen kann man aber durch geschickte Anordnung optimieren (wurde ja schon geschrieben). Die Frage ist dann, ob der schnellste Test vorne stehen soll oder lieber derjenige, der in den meisten Fällen möglichst früh zum Ausstieg führt. Denn bekanntlich ist Python, wie auch viele andere Sprachen, "lazy" bei der Auswertung, d.h. ein ``test1 and test2 and test3`` spart sich die anderen beiden Tests, wenn bereits test1 negativ ist. Man würde also bei der Formulierung der Prüflogik ansetzen und nicht so sehr auf Bytecode-Ebene denken.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Und ganz generell würde ich inbesondere mit Validierungen sparsam umgehen. Python prüft intern ja schon viel und wirft bei Fehlschlägen entsprechende Ausnahmen. Das ist auf jeden Fall performanter als im eigenen Code sozusagen um die Ausnahme herum zu validieren. Und es macht den Code insgesamt schlanker und wartbarer.

Ganz wichtig ist halt nur, dass nicht mit False Positives gearbeitet wird, z.B. dass ein Wert plötzlich negativ wird, wo es eigentlich niemand erwartet und ähnlich "Lustiges". Da muss es dann auf jeden Fall krachen, egal ob selbst veranlasst oder durch die Standardbibliothek.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1012
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Die Validierung von gültigen Floats ist z.B. ein wenig komplex.

Wenn man versucht einen str in einen float umzuwandeln, könnte man vorher alle möglichen Tests machen, um Fehler zu vermeiden. Die Wahrscheinlichkeit, dass man etwas vergisst, ist ziemlich groß. Anstatt das Rad jedes Mal neu zu erfinden, verlässt man sich auf die hoffentlich richtige Implementierung von Float in Python. Wenn Python einen Fehler auslöst, so kann man diesen einfach abfangen. Ganz nach dem Prinzip: Don't ask for permission, ask for forgiveness. Es gibt aber auch Entwickler, die anderer Meinung sind: https://leowid.com/dont-ask-for-forgive ... ermission/
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Der Artikel bezieht sich aber gar nicht auf's Programmieren, sondern auf zwischenmenschliche Interaktion. Das ist ja ein bisschen was anderes ob ich irgendeiner Person ”auf die Füsse trete” und hinterher um Verzeihung bitte, oder eine Zahl einfach umwandle und auf Ausnahmen reagiere. Und ist der überhaupt Softwareentwickler? Da steht was von Start-Up-Gründer, längeren Aufenthalten in Asien im Kloster, und „Freedom Coaching“.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

_Mala_Fide_ hat geschrieben: Montag 21. November 2022, 22:23 Meine Überlegung dazu ist, dass "if not" mehr Resourcen verbraucht, als ein "if".
Weil "if" ja nur einen Zustand kennen muss und "if not" müsste rein theoretisch zwei Zustände kennen um zu testen, ob etwas nicht wahr ist.
Was meinst du eigentlich mit den Zuständen? ``if`` ist einfach ein Schlüsselwort, das die Abarbeitung des nachfolgenden Codes beeinflusst, basierend auf einem Wahrheitswert. Für das ``if`` selbst ist es völlig egal, wie komplex die übergebene Auswertung ist. Es "sieht" halt wirklich nur True oder False und leitet dann einen Sprung ein oder lässt es. Dafür braucht man ja keinen Zustand. Wo sollte der auch zum Tragen kommen?
_Mala_Fide_
User
Beiträge: 53
Registriert: Dienstag 22. Dezember 2015, 19:17

@snafu

Mit Zustand meine ich True oder False.

Ich hatte so gedacht, dass "if x" ja nur wissen muss, dass x wahr ist und "if not y" wissen muss, das x wahr ist, um dann entscheiden zu können, ob y nicht wahr ist.
Daher ja auch meine Überlegung mit den Kosten, da "if" nur einen "Zustand" und "if not" zwei "Zustände" kennen muss um zu entscheiden, da kam mir "if" "günstiger" vor als "if not".
Glaube jetzt aber, dass man an die Sache so nicht ran gehen kann...?
Benutzeravatar
sparrow
User
Beiträge: 4165
Registriert: Freitag 17. April 2009, 10:28

Du verstrickt dich da in ein viel zu kompliziertes Gedankenkonstrukt. Und in den Thread wurde ja schon erklärt warum.

Da sind keine zwei Zustände.
_Mala_Fide_
User
Beiträge: 53
Registriert: Dienstag 22. Dezember 2015, 19:17

Hallo,

habe noch eine andere Frage zum Resourcenverbrauch.

Code: Alles auswählen

test = False
if irgendwas:
	for _ in range(800):
		test = True
		mache_irgenwas
if test:
	mache_irgendwas
	test = False

Code: Alles auswählen

test = False
if irgendwas:
	for _ in range(800):
		if not test:
			test = True
		mache_irgenwas
if test:
	mache_irgendwas
	test = False
Welcher von beiden Codes ist sinnvoller im Beszug auf Resourcenverbrauch? Oder nimmt sich beides nichts?
Benutzeravatar
sparrow
User
Beiträge: 4165
Registriert: Freitag 17. April 2009, 10:28

Die Frage stellt sich eigentlich nicht.
Entweder du musstg prüfen, ob test False ist oder nicht. Das aus Spaß zu testen verschwendet natürlich Ressourcen,
_Mala_Fide_
User
Beiträge: 53
Registriert: Dienstag 22. Dezember 2015, 19:17

Vllt habe ich die Frage etwas ungeschickt vormuliert...

Ich habe eine Methode für einen Computergegner im Spiel Tetris. Da die Figuren ja nach einer gewissen Zeit eins nach unten fallen, habe ich zwischen dem Fallen mit sleep() gearbeitet. Am Anfang der Methode wird eine Variable figure_moved mit False initialisiert. Wird die Figur bewegt, wird figure_moved auf True gesetzt, da das Programm nach dem Bewegen der Figur eine gewisse Zeit warten soll, bevor die Figur eins nach unten fällt. Wird die Figur nicht bewegt, fällt die Figur eins nach unten und dann wird erst gewartet. Jetzt ist es aber so, dass nach fast jeder Bewegung figure_moved auf True gesetzt wird und daher meine Frage, ob es dann nicht sinnvoller wäre das mit einer if-Abfrage abzufangen?

edit: Die 800 in der for-Schleife in meiner Frage ist auch etwas übertrieben gewählt, so oft wird die Figur ja nie bewegt. Es sind maximal drei Drehungen und fünf Bewegungen zu einer Seite, also max acht Bewegungen.
Antworten