[Python-de] "Typsicherheit" / Geschwindigkeitsoptimierung

Stefan Schwarzer sschwarzer at sschwarzer.net
Son Nov 23 15:10:33 CET 2003


Hallo Olaf,

On Sun, 2003-11-23 09:08:12 +0100, Olaf 'Ruebezahl' Radicke wrote:
> Am Sam, 2003-11-22 um 16.35 schrieb Stefan Schwarzer:
> [..]
> > Wenn du "langen verworrenen" Code hast, _schreit_ das nach
> > Vereinfachung/Refaktorierung des Codes, unabhängig davon, ob die
> > Verknüpfung eines Namens mit einem bestimmten Typ möglich ist oder
> > nicht. :-) Letzteres flickt das eigentliche Problem nur sehr
> > notdürftig.
> 
> OK. Um mal wieder vom "Allgemeinen" zum "Speziellen" zu kommen:
> Ich habe eine Klasse "Config" die eine XML-Datei parst und die
> Werte den anderen Klassen mit get-Methoden zur Verfügung stellt.
> 
> Jetzt habe ich ja meine Socket-Klasse und die braucht einen
> Rechnernamen und ein Port. Der Rechnername ist ein str und 
> der Port ein unsigned int. Wo auf dem Weg von der XML-Datei
> zur Socket-Instanz soll ich den str in ein int umwandeln, so
> das später noch klar ist ab wo ich es mit was zu tun habe.

ich würde mir überlegen, was der "natürlichste" Typ (also der, der die
abstrakte Sicht am besten wiedergibt) für eine Größe ist, und diesen
so früh wie möglich (direkt beim Parsen der XML-Datei) einstellen und
dabei bleiben.

Manchmal lässt sich die Frage nach dem "natürlichsten" Typ nicht so
leicht beantworten, aber in den meisten Fällen wird eine Größe nur mit
zwei Typen verwendet:

- Ausgabe

- alles andere

Dann würde ich den für "alles andere" als "natürlichen" Typ ansehen,
und für die Ausgabe etwas wie

warning = "Port %d is a superuser port" % port   # `port` is an int

verwenden.

Wenn dir das nicht reicht, wäre eine andere - aber deutlich
aufwändigere! - Möglichkeit, eine Klasse `Port` zu definieren:

class Port:
    def __init__(self, port):
        self._port = int(port)

    def as_string(self):
        return str(self._port)

    def as_int(self):
        return self._port

Du solltest dann eine Instanz der Klasse so früh wie möglich erzeugen,
also wieder beim Parsen der Konfiguration.

> Wenn mir jetzt später ein fällt, ich will wissen ob eine 
> privilegierte Port-Nummer benutzt wird, muss ich den ganzen
> Code durchgehen, wo immer der Wert mal durch gereicht wurde,
> um sicher zu stellen, das diese Code auch das tut was ich
> von ihm erwarte:

Generell solltest du die Möglichkeit, dass es mal das eine oder das
andere sein kann, vermeiden, indem du den Typ so früh wie möglich und
konsistent festlegst.

Wenn du den String oft brauchen solltest, kannst du die Häufigkeit von
Missverständnissen verringern, indem du den Typ explizit im Namen
unterbringst:

port_string = str(port)

(Wenn du so einen Namen verwendest und der zugewiesene Wert
tatsächlich ein int ist, hast du zugegebenermaßen ein Problem. ;-) )

> SUPERUSERPORTS = 1024
> 
> if conf.get_port_numder() <= SUPERUSERPORTS:
>    print "bist du wahnsinnig? Du benutzt Port:", conf.get_port_numder()
>  
> > Du kannst in Python nicht "aus einem int ein str" machen. Ein Objekt
> > hat immer einen bestimmten Typ (außer vielleicht so pathologischen
> > Fällen wie Zuweisungen an __class__  ;-) ). Meinst du eher "eine
> > Funktion/Methode akzeptiert ein int und gibt ein str zurück"? 
> 
> Ich will nicht ein und der selben Instanz mal dies, mal das
> zuweisen dürfen und schon gar nicht Äpfel mit Birnen vergleichen.

Dann tu es einfach nicht ;-))

> > C oder C++ zu verwenden hat m. E. nichts mit der Größe des Projekts zu
> > tun, sondern mit der Art des Projekts. Ich kann mir gut vorstellen,
> > dass auch für viele "große" Projekte Python weitaus besser als C/C++
> > geeignet ist/wäre.
> 
> Ich werde in meinem Code XML in LaTeX parsen. Das ging
> in C++ rasant schnell. Ich werde sehen was Python dabei
> für eine Figur macht. Was Größe angeht, hat Zope und Mailman
> usw. gezeigt was geht.   

Empfehlung:

1. alles in Python programmieren; dabei _nicht_ "vorsorglich"
   optimieren

2. möglichst realistische Anwendungsfälle testen

3. wenn es _wirklich_ zu langsam ist, mit dem Profiler (Modul profile)
   messen, wo die tatsächlichen Geschwindigkeitsprobleme sind; man
   kann mit einer Schätzung "aus dem Bauch" manchmal (oder sogar oft)
   enorm danebenliegen, wodurch man an völlig falschen Stellen
   "optimieren" würde

4. überlegen, ob das Problem ganz anders gelöst werden kann, so dass
   der geschwindigkeitskritische Teil gar nicht ausgeführt werden
   muss; an dieser Stelle fällt einem manchmal ein viel einfacheres
   Design ein

5. wenn der Aufwand nicht zu groß ist, den Code so ändern, dass die
   geschwindigkeitskritischen Teile auf der C-Ebene von Python
   ablaufen (z. B. Verwendung von Listen, Dictionaries, re-Modul etc.)

6. wenn 4. oder 5. zu aufwändig wären oder nicht funktionieren, nach
   frei verfügbaren Modulen suchen, die geschwindigkeitsoptimiert sind
   und im wesentlichen das machen, was du willst (z. B. SAX, PyXML,
   mx.TextTools, NumericPython etc.)

7. wenn 6. nicht geht, aus dem geschwindigkeitskritischen Teil eine
   C-Erweiterung machen oder Hilfsmittel wie bspw. psyco
   (http://psyco.sourceforge.net/) verwenden

Je mehr Code in C/C++ vorliegt, desto schwieriger wird es sein, die
Punkte 4, 5 und 6 anzuwenden. Das spricht paradoxerweise für _weniger_
statt mehr C/C++, um längerfristig die beste Geschwindigkeit zu
erreichen bzw. beim "Tunen" flexibel zu bleiben.

Eine Frage an die Leser der Mailingliste: Wie sind eure Erfahrungen
bzw. was sollte in der obigen Liste ergänzt/geändert werden?

Viele Grüße
 Stefan