Monthly Python - DATEC Datentechnik GmbH

Werbung
DATEC Datentechnik GmbH
Python Programming
Monthly Python
Lösungen zu ausgewählten technischen Problemstellungen
3. Februar 2013
Autor: Dr. Franz Geiger
Adresse: DATEC Datentechnik GmbH, Schmiedgasse 7, A-6890
Lustenau
E-Mail: [email protected]
Organisation: DATEC Datentechnik GmbH
Druckdatum: 3. Februar 2013
Copyright (C) 1998 - 2013, DATEC Datentechnik GmbH
Dieses Dokument ist urheberrechtlich geschützt.
Alle Rechte, auch die der Übersetzung, des Nachdrucks und Vervielfältigung durch Kopieren oder Scannen sowie der Speicherung in
Retrieval-Systemen des gesamten Dokumentes oder Teilen daraus, sind
DATEC Datentechnik GmbH vorbehalten.
Kein Teil des Dokumentes darf ohne schriftliche Genehmigung von DATEC Datentechnik GmbH in irgendeiner Form (Fotokopie, Mikrofilm
oder ein anderes Verfahren), auch nicht für Zwecke der Unterrichtsgestaltung, reproduziert oder unter Verwendung elektronischer Systeme
gespeichert, verarbeitet, vervielfältigt oder verbreitet werden.
Die Weitergabe an Dritte ist nur mit ausdrücklicher Erlaubnis von
DATEC Datentechnik GmbH gestattet.
Alle Marken und Produktnamen sind Warenzeichen oder eingetragene
Warenzeichen der jeweiligen Titelhalter.
2
Inhaltsverzeichnis
1 Februar 2013: Python Performance
1.1 Python Performance: Cython . . . . . . . . . . . .
1.1.1 Cython . . . . . . . . . . . . . . . . . . . .
1.1.1.1 Scaler . . . . . . . . . . . . . . . .
1.1.1.1.1 Python-Source-Code . . .
1.1.1.1.2 Cython-Source-Code . . .
1.1.1.1.3 Messergebnisse . . . . . .
1.1.1.2 Filter . . . . . . . . . . . . . . . .
1.1.2 File- bzw. Directory-Struktur für wahlfreien
Python-/Cython-Code . . . . . . . . . . . .
2 Januar 2013
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
Zugriff auf
. . . . . . .
4
4
4
5
5
5
6
7
9
11
3
1 Februar 2013: Python Performance
Sie haben sich mit Python beschäftigt und sind auch auf kritische Auseinandersetzungen mit dem Thema Python und Performance gestoßen. Aus der
Sicht eines C- oder gar Assembler-Programmierers ist Python langsam. Aber
aus wirtschaftlicher Sicht sind C- oder gar Assembler-Programmierer langsam
und damit teuer: Teuer ist nicht die Laufzeit, teuer ist die Entwicklungszeit.
Aus Sicht des Entwicklers sagt sich das so leicht. Es kann also nicht schaden, wenn man um Möglichkeiten weiß, Python-Programme performanter zu
machen. Eine vergleichsweise wenig aufwändige Möglichkeit ist die der Verwendung von Cython-Code (s. http://cython.org/).
1.1 Python Performance: Cython
1.1.1 Cython
Die Zeit eines Ingenieurs kostet mehr als CPU-Zeit, keine Frage. Es gibt aber
Situationen, in denen Python-Code zu langsam ist. Es gibt eine Reihe von
Möglichkeiten, Python-Programme schneller zu machen. Die, die am einfachsten ist, ist die Verwendung von Cython (s. http://cython.org/).
Dabei geht man vor wie folgt:
∙ .py-File in ein gleichnamiges .pyx-File kopieren.
∙ Im .pyx-File Typdefinitionen vornehmen.
∙ Per
import pyximport; pyximport.install()
import scaling_cy as cy
importieren - die Compilation erfolgt automatisch.
Was möglich ist, wollen wir an zwei Beipielen ausprobieren.
4
1 Februar 2013: Python Performance
1.1.1.1 Scaler
Das erste Beispiel ist ein Scaler, d.i. eine Klasse, die aus einem Signalwert einen
Messwert macht. Im Grunde ist lediglich eine Multiplikation durchzuführen,
mehr nicht. Keine Affäre also. Allerdings ist der Aufruf von Cython-Methoden
effizienter als der von Python-Methoden. Das sollte entsprechend zu Buche
schlagen. Aber der Reihe nach.
1.1.1.1.1 Python-Source-Code
scaling_py.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Das File, das den Python-Code enthält ist
class _Scaler2 :
""" Rechnet Signale in Messwerte um und umgekehrt .
Basisklasse ,
Verwendung vorzugsweise ü ber Ableitungen .
"""
def __init__ ( self , p_signal , p_reading , factorS2R ):
self . __p_signal = p_signal
self . __p_reading = p_reading
self . __factorS2R = factorS2R
return
def factorS2R ( self ):
return self . __factorS2R
def value4reading ( self , value4signal ):
value4reading = value4signal * self . __factorS2R
self . __p_reading . value_set ( value4reading )
return value4reading
def value4signal ( self , value4reading ):
value4signal = value4reading / self . __factorS2R
self . __p_signal . value ( value4signal )
return value4signal
class Scaler4Floats ( _Scaler2 ):
"""
"""
def __init__ ( self , minS , maxS , dimS , minR , maxR , dimR , factorS2R ):
minS = sys . float_info . min if minS is None else float ( minS )
maxS = sys . float_info . max if maxS is None else float ( maxS )
_Scaler2 . __init__ (
self ,
varbls . FVariableFloat ( 0. , minS , maxS , dimS ) ,
varbls . FVariableFloat ( 0. , minR , maxR , dimR ) ,
factorS2R
)
return
varbls.FVariableFloat sind Instanzen einer Klasse, die einen Wert, Minimalund Maximalwert sowie Dimensions-Strings (wie V“, mm“ usw.) hält.
”
”
1.1.1.1.2 Cython-Source-Code Der Cython-Code ist durch Kopieren und
Einfügen von Typdefinitionen entstanden. Das File, das den Code enthält ist
scaling_cy.pyx.
5
1 Februar 2013: Python Performance
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
cdef class _Scaler2 :
""" Rechnet Signale in Messwerte um und umgekehrt .
Basisklasse ,
Verwendung vorzugsweise ü ber Ableitungen .
"""
cdef :
object __p_signal
object __p_reading
double __factorS2R
def __init__ ( self , object p_signal , object p_reading , double factorS2R ):
self . __p_signal = p_signal
self . __p_reading = p_reading
self . __factorS2R = factorS2R
return
cpdef double
factorS2R ( self ):
return self . __factorS2R
cpdef double
value4reading ( self , double value4signal ):
value4reading = value4signal * self . __factorS2R
self . __p_reading . value_set ( value4reading )
# Wir m ü ssen hier value_set () verwenden ,
#
weil es auch eine Cython - Version der
#
hostenden Instanz gibt .
return value4reading
cpdef double
value4signal ( self , double value4reading ):
cdef double value4signal
value4signal = value4reading / self . __factorS2R
self . __p_signal . value ( value4signal )
return value4signal
cdef class Scaler4Floats ( _Scaler2 ):
"""
"""
def __init__ ( self , double minS , double maxS , unicode dimS , double minR , doubl
minS = sys . float_info . min if minS is None else float ( minS )
maxS = sys . float_info . max if maxS is None else float ( maxS )
_Scaler2 . __init__ (
self ,
varbls . cy . FVariableFloat ( 0. , minS , maxS , dimS ) ,
varbls . cy . FVariableFloat ( 0. , minR , maxR , dimR ) ,
factorS2R
)
return
Der Unterschied zum Python-Code erschöpft sich in den cdef-s und den cpdefs. Die Definition der Typen erfolgt im Block
cdef:
object
object
double
__p_signal
__p_reading
__factorS2R
1.1.1.1.3 Messergebnisse Um an Messergebnisse zu kommen, wurde folgender Code verwendet. Auch hier wird einiges verwendet, das nicht bekannt ist,
wie z.B. Timer2 (dient der Zeitmessung). Das ist an dieser Stelle aber nicht
6
1 Februar 2013: Python Performance
wichtig.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
n = 10000
scalerC = scaling . cy . Scaler4Floats ( 0 , 42 , u " V " , 0 , 4242 , u " bar " , 13)
scalerP = scaling . py . Scaler4Floats ( 0 , 42 , u " V " , 0 , 4242 , u " bar " , 13)
signal_values = [ random . randint ( - sys . maxint , sys . maxint ) for _ in range ( n )]
# Werte vorab erzeugen , damit ihre
#
Erzeugung das Messergebnis nicht
#
beeinflusst .
with Timer2 ( " Scaling by scaling . py " ) as tmr :
for valueS in signal_values :
reading_value = scalerP . value4reading ( valueS )
print tmr . results ( n )
with Timer2 ( " Scaling by scaling . cy " ) as tmr :
for valueS in signal_values :
reading_value = scalerC . value4reading ( valueS )
print tmr . results ( n )
Obiger Code führt endlich zu folgender Ausgabe
test__basic_use (__main__._TESTCASE__Scaler2) ...
Timer(): ’Scaling by scaling.py’ took 0.000 s = 0.004 ms = 3.775 us.
Timer(): ’Scaling by scaling.cy’ took 0.000 s = 0.001 ms = 1.200 us.
Wir haben also mit sehr wenig Aufwand eine dreimal kleinere Laufzeit erreicht.
1.1.1.2 Filter
Bei Filtern sind ein paar Rechenoperationen mehr durchzuführen - zwar auch
nicht wild, aber doch so, dass sich die statische Typisierung lohnen müsste.
Als Filter wird ein Filter 1. Ordnung verwendet. Die Methode, die die effektive
Berechnung durchführt, heißt execute() und präsentiert sich so:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def execute ( self ):
""" Filter - Algorithmus .
::
y = (1 - alpha ) y
+ alpha u , alpha = 1/(1 + T1 / Ts )
k
k -1
k
"""
#
Abbrevs
#
alpha = self . __alpha_value
y_ = self . __y_
#
Read input signal
#
u = self . input_value ()
#
Cal . algorithm
#
y_ [0] = (1 - alpha )* y_ [ -1] + alpha * u
#
#
Write output signal
7
1 Februar 2013: Python Performance
25
26
27
28
29
30
31
32
self . output_value ( y_ [0])
#
Store values for next call
#
y_ [ -1] = y_ [0]
return
# We could write self . output_signal ( self . __y_
#
here as well .
Der Cython-Code sieht kaum anders aus:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
cpdef int
execute ( self ):
""" Filter - Algorithmus .
::
y = (1 - alpha ) y
+ alpha u , alpha = 1/(1 + T1 / Ts )
k
k -1
k
"""
#
Abbrevs
#
cdef :
double alpha
double u
double * y_
# Pointer to an array .
alpha = self . __alpha_value
y_ = self . __y_
#
Read input signal
#
u = self . input_value ()
#
Cal . algorithm
#
y_ [ 0] = (1 - alpha )* y_ [ 1] + alpha * u
#
Write output signal
#
self . output_value ( y_ [ 0])
#
Store values for next call
#
y_ [ 1] = y_ [ 0]
return
# We could write self . output_signal ( self . __y_
#
here as well .
Und die Messergebnisse? Sehen so aus:
test (__main__._TESTCASE__MovingAverageFilterExponentiallyWeighted) ...
Timer(): ’PYFilter’ took 0.000 s = 0.018 ms = 17.607 us.
Timer(): ’CY-Filter’ took 0.000 s = 0.002 ms = 1.560 us.
Das ist Faktor 10 - nicht schlecht für das bisschen Aufwand. Nicht zu unterschätzen ist auch der Aufwand, der hier für die laufende Wartung eingespart
werden kann. Sie brauchen nicht in eine C/C++-Umgebung wechseln, Sie können Ihre Python- und Cython-Sourcen gemeinsam verwalten.
8
1 Februar 2013: Python Performance
1.1.2 File- bzw. Directory-Struktur für wahlfreien Zugriff auf
Python-/Cython-Code
Wie kann man Cython-Code am besten in bestehende Apps einbinden und
zwar transparent und umschaltbar?
Alles was Sie dazu brauchen, ist eine entsprechende Directory-Struktur und
eine konsistente Namensgebung für die involvierten Files.
Hier also ein Vorschlag, skizziert anhand eines Packages, das teilweise in Cython realisiert werden soll. Der Cython-Code, um den es geht, befindet sich im
einem File _common_cy.pyx, während die reine Python-Version des Codes in
_common_py.py definiert ist.1
Code im Package, der auf Code in _common_py.py bzw. _common_cy.pyx zugriefen will, tut das über _common.py, das definiert ist wie folgt:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
-* - coding : utf8 -* - #
Copyright ( C ) by DATEC Datentechnik GmbH , A -6890 LUSTENAU , 1998 - 2013
This file is part of tau4 .
tau4 is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
tau4 is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with tau4 . If not , see < http :// www . gnu . org / licenses / >.
from __future__ import division
import sys
import tau4
from tau4 import tau4logging
import
import
import
try :
if
33
34
35
36
37
38
39
40
41
42
43
_common_py as py
pyximport ; pyximport . install ()
_common_cy as cy
not tau4 . _settings . _TAU4 _USE_C Y_FOR _TAU4C OM :
text = " Use of py code is forced in % s ! " % __file__
tau4logging . SysEventLog (). log_warning ( text , __file__ )
raise ImportError ( text )
text = " Cython code version is used . "
from
from
from
from
from
tau4 . tau4com . tbus . _common_cy
tau4 . tau4com . tbus . _common_cy
tau4 . tau4com . tbus . _common_cy
tau4 . tau4com . tbus . _common_cy
tau4 . tau4com . tbus . _common_cy
1 Der
import
import
import
import
import
_Message
MessageAsync
MessageAsync4NewValue
MessageSynch
MessageSynch4NewValue
Unterstrich ’ ’ in _common deutet an, dass der zugehörige Code nur package-intern
verwendet wird.
9
1 Februar 2013: Python Performance
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
from tau4 . tau4com . tbus . _common_cy import _SendMessage_
tau4logging . SysEventLog (). log_info ( text , __file__ )
except ImportError , e :
text = " Pure Python code version is used . "
print >> sys . stderr , " E R R O R : % s . % s " % (e , text )
from
from
from
from
from
from
tau4 . tau4com . tbus . _common_py
tau4 . tau4com . tbus . _common_py
tau4 . tau4com . tbus . _common_py
tau4 . tau4com . tbus . _common_py
tau4 . tau4com . tbus . _common_py
tau4 . tau4com . tbus . _common_py
import
import
import
import
import
import
_Message
MessageAsync
MessageAsync4NewValue
MessageSynch
MessageSynch4NewValue
_SendMessage_
tau4logging . SysEventLog (). log_warning ( text , __file__ )
_ D y n a m i c l y C r e a t e d M e s s a g e C l a s s e s = {}
Die File-Struktur ist also
FILE.py
FILE_PY.PY
FILE_CY.PYX
10
2 Januar 2013
Hier finden Sie Problemstellungen aus der Praxis und die mittels PythonProgrammen realisierten Lösungen dazu.
Eine Einführung in Python oder gar ein Tutorial fehlt, weil es keinen Sinn
hat, die weiß Gott wievielte Einführung in die Programmierung mit Python
zu schreiben.
Im Laufe der Zeit wird sich im Anhang eine Link-Liste zu ausgewählten Themen entwickeln. Ansonsten bemühen Sie bitte Ihre Lieblingssuchmaschine.
Richtig losgehen wird’s im Februar. Da werden wir uns mit Möglichkeiten
beschäftigen, wie Programmteile intern miteinander kommunizieren können
und dabei nur lose gekoppelt sind.
Also, bis dann!
11
Herunterladen