Aufteilen einer großen Klasse / trennung von Funktionalität.

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.
Antworten
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Bei meiner MC6809 CPU Emulation, frage ich mich gerade, ob ich nicht die sehr große Klasse CPU() aufteilen sollte:

https://github.com/6809/MC6809/blob/mas ... cpu6809.py

Ich könnte natürlich das Aufteilen, durch erben machen. Dabei Frage ich mich allerdings, wie das mit der Performance aussieht.

Es gibt ja u.a. den Performance Tip, möglichst auf "dot-Names" zu verzichten und ehr auf lokale Variablen zurück zu greifen:

https://wiki.python.org/moin/PythonSpee ... iding_dots... (Aktuell down)

Wenn ich so einen Aufbau mache:

Code: Alles auswählen

class BaseCPU():
    #...

class MC6809AddressModes(BaseCPU):
    #...

class MC6809RegisterOperations(MC6809AddressModes):
    #...

class MC6809LogicalOperations(MC6809RegisterOperations):
    #...

class MC6809Branches(MC6809LogicalOperations):
    #...

# usw
Das kommt mir hier schon ziemlich falsch vor. Und dann frage ich mich, wie das intern aufgelöst wird? Es wird doch dann die Hirachie immer weiter abwärts nach der Methode/Attribut gesucht, oder? Das dürfte dann ziemlich bremsen...

Andere Vorschläge wie man das Monster aufteilen kann?!?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: Manche Sachen kann man halt nicht wirklich sinnvoll aufteilen. Muss man ja auch nicht unbedingt. Wenn überhaupt könntest Du die einzelnen Befehlsgruppen als Mixins lösen also:

Code: Alles auswählen

class RegisterOperations(object):
    # ...

class LogicalOperations(object):
    # ...

class CPU(RegisterOperations, LogicalOperations):
    # ...
Dann könnte man auch einfach CPU-Variationen aus bestehenden Befehlssätzen plus den Abweichungen so zusammensetzen. Das `MC6809` habe ich aus den Namen mal rausgelassen, denn das Modul heisst ja schon wie die CPU.

Edit: Über die Performance würde ich mir erst Gedanken machen wenn es zum Problem wird.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Stimmt, Mixins ist viel Sinnvoller!

Weil ursprünglich kommt die Idee davon:

z.Z. hab ich einen "Renne so schnell du kannst" Modus und ein "Nicht schneller als Echtzeit" Modus. Der letztere fügt quasi nur ein time.sleep() ein.

Also habe ich irgendwo im "mainloop" eine Art "if realtime:"... Das bremst natürlich, wenn Echtzeit nicht erwünscht ist... Oder wenn der Rechner auf dem es läuft einfach zu langsam dafür ist.
BlackJack hat geschrieben:Edit: Über die Performance würde ich mir erst Gedanken machen wenn es zum Problem wird.
Ist so eine Sache. Auf einem "Performanten" Rechner, bin ich ja schon mit CPython knapp über die Echtzeit.
z.B. Intel i7-4790K... (Wobei der ja im Singlecore-Performance fast führend ist)...

Mit PyPy ist es selbst auf meinem Uralt Laptop mit Single-Core 1,66GHz CPU fast 10x schneller als Echtzeit...

Auf der anderen Seite: Je besser der "mainloop" optimiert ist um so länger darf time.sleep() sein und um so weniger belastet ist die CPU und mehr Zeit ist für die GUI da...
Also Optimieren kann nicht schaden, in diesem Fall...


Wie setzt Python denn bei diesem Mixin-Ansatz den "Runtime-Code" zusammen?!? Landen alle Attribute/Methoden im __dict__ so das es im Prinzip keinen Performance Unterschied geben wird?


Ich denke ich werde das mal in einem Branch einfach mal testen...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: ”Python” allgemein trifft da keine Aussagen. Das kommt also auf die Implementierung an. Keine Ahnung wie CPython das macht. Da wären wir aber IMHO schon wieder bei Mikrooptimierungen bei denen man eher die Sprache wechseln sollte statt jetzt Implementierungsdetails auszumessen.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack hat geschrieben:@jens: ”Python” allgemein trifft da keine Aussagen. Das kommt also auf die Implementierung an. Keine Ahnung wie CPython das macht. Da wären wir aber IMHO schon wieder bei Mikrooptimierungen bei denen man eher die Sprache wechseln sollte statt jetzt Implementierungsdetails auszumessen.
Naja, ich werde bei Python bleiben. Auch wenn es nicht die optimale Sprache für eine Emulator ist :P

Auf der anderen Seite, greift der JIT von PyPy genau in diesem Szenario ja super...

Ich denke richtig "optimiert" könnte man eine CPU-Emulation per Code-Generator bauen. Aber das will ich wiederum nicht...

Also zu viel Mikrooptimierungen machen, dafür aber unschönen Code haben, will ich auch nicht. Halt so einen vernünftigen Zwischenweg...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

jens hat geschrieben:Wie setzt Python denn bei diesem Mixin-Ansatz den "Runtime-Code" zusammen?!? Landen alle Attribute/Methoden im __dict__ so das es im Prinzip keinen Performance Unterschied geben wird?
Das ist ein MRO-Problem, dafür gibts ein paar Artikel, wie Python das umsetzt. Welche Laufzeitimplikationen das für verschiedene Python-Inkarnationen hat, kann ich Dir nicht sagen. Jenseits der Implementationsdetails sollte MRO-Verhalten allerdings bei allen gleich sein, da man keine Überraschungen bei Mehrfachvererbung zwischen CPython, Jython, PyPy etc. sehen möchte.

Generell zu Optimierungen:

- Kurzer Quelltext ist meist auch schneller, weil Quelltextlänge oft mit Anzahl der auszuwertenden Instruktionen korreliert (vorausgesetzt man produziert nicht seitenweise dead code oder Monsterbranches).

- Komplexe Typen vs. simple Typen - einfache Typen sind idR schneller. Das kommt aber mit einem tradeoff - idR ist die Funktionalität nicht 1:1 abbildbar und man braucht Zusatzcode (Bloatware), um die Funktionalität zu erhalten. Das geht dann schnell zu Lasten der Lesbarkeit bzw. kann der Zusatzcode sehr viel schlechter sein als der Librarycode, was dann eher alles schlimmer macht. Bei CPython kommt noch der Unterschied zwischen internen Typen (in C geschrieben) vs. selbst definierten hinzu. Eine clever gestrickte Liste mit Integern könnte evtl. das Gleiche leisten, wie eine selbst definierte Klasse usw. Ob man dagegen optimieren will - wohl eher nicht, da dass dann doch zu implementationsspezifisch sein dürfte.

- lokal vs. objektweit - Hmm, in C naher Programmierung macht das definitv was aus allein schon aufgrund der Heap/Stack-Unterschiede. Je nach Umsetzung/Durchreichung können in C/C++ geschriebene Interpreter auch in der interpretierten Sprache davon profitieren (z.B. zeigen alle JS-Interpreter/Jits dieses Verhalten). Zusätzlich brauchen objektweite Attribute einen weiteren Dereferenzierungsschritt beim Auflösen des Namens - mehr Arbeit für den Rechner. Ob Du hier etwas gewinnen kannst - schwer zu sagen, dafür bräuchtest Du ein genaues Profiling.

Generell würde ich mittels profiling schauen, ob es Flaschenhälse gibt, welche mit Mitteln der Sprache behebbar sind. Wenn dann die Performance noch nicht reichen sollte - Plan B für Funktionalität XY....
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

So, ich hab mal, auf die schnelle, den mixins Ansatz gestartet: https://github.com/6809/MC6809/compare/split_cpu

Erster Tests (mit dem eingebauten Benchmark, siehe: https://github.com/6809/MC6809#readme ) zeigen das es kaum was ausmacht, von der Performance her...

Im Gegenteil: Es scheint sogar ein Tick schneller zu sein :shock: :K


Werde noch weitere Funktionale Trennung vornehmen.
z.B. den "Speed Limit" (Für Echtzeit Lauffähigkeit) Code separieren...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@jens:
Wie viele Durchläufe/s schafft denn Dein Emulator-Loop in nicht optimiertem CPython?
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ein Schleifendurchlauf macht ja kaum was. Deswegen sind es hunder tausende, aber bei pypy ein paar Millionen...

Wie gesagt, auf einem schnellen Rechner, kommt man auch mit cpython an Echtzeit heran.

Siehe auch: https://github.com/jedie/DragonPy#performance

Allerdings stimmen die cpython Werte nicht mehr. Sind halt um die 800.000cycles/s... Jeder eigentlicher Schleifendurchlauf ist ein op code Abarbeitung die zwischen 2-4cycles sind...

Kommt also auch auf das Assembler Programm bzw. den Maschine code...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

So, ich hab nochmal mit mehreren Durchläufen vom benchmark mit CPython3 nachgemessen:

Die alte Variante (also eine mega Klasse):
450750 CPU cycles/sec
467793 CPU cycles/sec
448952 CPU cycles/sec
419205 CPU cycles/sec
457892 CPU cycles/sec
Also im Schnitt: 448918,4 CPU cycles/sec

Die mixins Variante (git commit c26c124a3816b9c391340bb6a72572fdd6926ddd ):

428119 CPU cycles/sec
460061 CPU cycles/sec
459328 CPU cycles/sec
422722 CPU cycles/sec
453091 CPU cycles/sec
Schnitt: 444664,2 CPU cycles/sec

Da die Schwankungen der einzelnen Durchläufe (die alle rund ~20Sek. dauern) so groß sind, fällt das wohl kaum ins Gewicht.

Zumal diese Modularität, ganz neue Perspektiven eröffnet ;) z.B. ist ein Trace-CPU wieder ohne weiteres Möglich. Außerdem bremst der "Speed-Limit" code nicht aus, wenn er gar nicht aktiv ist usw...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Code: Alles auswählen

from __future__ import absolute_import, division, print_function

import sys
print(sys.version)


INTERNAL_LOOPS = 1000


class CPU(object):
    def __init__(self):
        self.memory = [0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02]
        self.program_counter = 0x00
        self.register_A = 0x00

    def run(self):
        for __ in range(INTERNAL_LOOPS):
            self.program_counter = 0x00
            self.register_A = 0x00
            for __ in range(len(self.memory)):
                self.next_opcode()

    def next_opcode(self):
        opcode = self.memory[self.program_counter]
        self.program_counter += 1

        if opcode == 0x01:
            self.op_inc_a()
        elif opcode == 0x02:
            self.op_dec_a()

    def op_inc_a(self):
        self.register_A += 1

    def op_dec_a(self):
        self.register_A -= 1

#-----------------------------------------------------------------------------

class BaseCPU(object):
    def __init__(self):
        self.memory = [0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02]
        self.program_counter = 0x00
        self.register_A = 0x00

class RunMixin(object):
    def run(self):
        for __ in range(INTERNAL_LOOPS):
            self.program_counter = 0x00
            self.register_A = 0x00
            for __ in range(len(self.memory)):
                self.next_opcode()

    def next_opcode(self):
        opcode = self.memory[self.program_counter]
        self.program_counter += 1

        if opcode == 0x01:
            self.op_inc_a()
        elif opcode == 0x02:
            self.op_dec_a()

class OpsMixin(object):
    def op_inc_a(self):
        self.register_A += 1

    def op_dec_a(self):
        self.register_A -= 1

class MixinCPU(BaseCPU, RunMixin, OpsMixin):
    pass

#-----------------------------------------------------------------------------


def test_single_class():
    single_class = CPU()
    single_class.run()
    assert single_class.register_A == -3

def test_mixin_class():
    mixin_class = MixinCPU()
    mixin_class.run()
    assert mixin_class.register_A == -3


if __name__ == "__main__":
    from timeit import Timer

    test_single_class()
    test_mixin_class()

    def timeit(func, number):
        name = func.__name__
        sys.stdout.write("%20s: " % name)
        sys.stdout.flush()
        t = Timer("%s()" % name, setup="from __main__ import %s" % name)
        print("%.3fsec" % t.timeit(number))

    number = 1000
    timeit(test_single_class, number)
    timeit(test_mixin_class, number)
Ausgabe:
3.4.3 (v3.4.3:9b73f1c3e601, Feb 24 2015, 22:43:06) [MSC v.1600 32 bit (Intel)]
test_single_class: 4.319sec
test_mixin_class: 4.282sec
Ist wirklich ein Tick schneller?!? Warum?

EDIT: und mit Py2:

Code: Alles auswählen

2.7.10 (default, May 23 2015, 09:40:32) [MSC v.1500 32 bit (Intel)]
   test_single_class: 3.656sec
    test_mixin_class: 3.641sec

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Nochmal...

Hab die Klasse noch weiter aufgeteilt (Auch wenn da so keinen Sinn macht) und noch cProfile() lauf dazu gepackt:

Code: Alles auswählen

from __future__ import absolute_import, division, print_function

from timeit import Timer
import cProfile

import sys
print(sys.version)


INTERNAL_LOOPS = 20000


class CPU(object):
    def __init__(self):
        self.memory = [0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02]
        self.program_counter = 0x00
        self.register_A = 0x00

    def run(self):
        for __ in range(INTERNAL_LOOPS):
            self.program_counter = 0x00
            self.register_A = 0x00
            for __ in range(len(self.memory)):
                self.next_opcode()

    def next_opcode(self):
        opcode = self.memory[self.program_counter]
        self.program_counter += 1

        if opcode == 0x01:
            self.op_inc_a()
        elif opcode == 0x02:
            self.op_dec_a()

    def op_inc_a(self):
        self.register_A += 1

    def op_dec_a(self):
        self.register_A -= 1

#-----------------------------------------------------------------------------

class BaseCPU(object):
    def __init__(self):
        self.memory = [0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02]
        self.program_counter = 0x00
        self.register_A = 0x00

class Run1Mixin(object):
    def run(self):
        for __ in range(INTERNAL_LOOPS):
            self.program_counter = 0x00
            self.register_A = 0x00
            for __ in range(len(self.memory)):
                self.next_opcode()

class Run2Mixin(object):
    def next_opcode(self):
        opcode = self.memory[self.program_counter]
        self.program_counter += 1

        if opcode == 0x01:
            self.op_inc_a()
        elif opcode == 0x02:
            self.op_dec_a()

class Ops1Mixin(object):
    def op_inc_a(self):
        self.register_A += 1

class Ops2Mixin(object):
    def op_dec_a(self):
        self.register_A -= 1

class MixinCPU(BaseCPU, Run1Mixin, Run2Mixin, Ops1Mixin, Ops2Mixin):
    pass

#-----------------------------------------------------------------------------


def test_single_class():
    single_class = CPU()
    single_class.run()
    assert single_class.register_A == -3

def test_mixin_class():
    mixin_class = MixinCPU()
    mixin_class.run()
    assert mixin_class.register_A == -3


test_single_class()
test_mixin_class()



if __name__ == "__main__":
    def timeit(func, number):
        name = func.__name__
        sys.stdout.write("%20s: " % name)
        sys.stdout.flush()
        t = Timer("%s()" % name, setup="from __main__ import %s" % name)
        print("%.3fsec" % t.timeit(number))

    def profile_it(func, number):
        name = func.__name__
        sys.stdout.write("%20s:" % name)
        sys.stdout.flush()
        cProfile.run(
            'for __ in range(%(number)i):%(name)s()' % {
                "name":name,
                "number": number,
            }
        )

    number = 100
    timeit(test_single_class, number)
    timeit(test_mixin_class, number)

    profile_it(test_single_class, number)
    profile_it(test_mixin_class, number)
Py2:

Code: Alles auswählen

2.7.10 (default, May 23 2015, 09:40:32) [MSC v.1500 32 bit (Intel)]
   test_single_class: 7.282sec
    test_mixin_class: 7.290sec
   test_single_class:         32000403 function calls in 10.249 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   10.249   10.249 <string>:1(<module>)
      100    0.000    0.000    0.000    0.000 test.py:17(__init__)
      100    3.007    0.030   10.249    0.102 test.py:22(run)
 14000000    5.054    0.000    6.894    0.000 test.py:29(next_opcode)
  4000000    0.499    0.000    0.499    0.000 test.py:38(op_inc_a)
 10000000    1.341    0.000    1.341    0.000 test.py:41(op_dec_a)
      100    0.000    0.000   10.249    0.102 test.py:84(test_single_class)
  2000000    0.073    0.000    0.073    0.000 {len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
  2000101    0.275    0.000    0.275    0.000 {range}


    test_mixin_class:         32000403 function calls in 10.474 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   10.474   10.474 <string>:1(<module>)
      100    0.000    0.000    0.000    0.000 test.py:47(__init__)
      100    3.085    0.031   10.474    0.105 test.py:53(run)
 14000000    5.127    0.000    7.026    0.000 test.py:61(next_opcode)
  4000000    0.530    0.000    0.530    0.000 test.py:71(op_inc_a)
 10000000    1.369    0.000    1.369    0.000 test.py:75(op_dec_a)
      100    0.000    0.000   10.474    0.105 test.py:89(test_mixin_class)
  2000000    0.076    0.000    0.076    0.000 {len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
  2000101    0.287    0.000    0.287    0.000 {range}
Py3:

Code: Alles auswählen

3.4.3 (v3.4.3:9b73f1c3e601, Feb 24 2015, 22:43:06) [MSC v.1600 32 bit (Intel)]
   test_single_class: 8.346sec
    test_mixin_class: 8.329sec
   test_single_class:         30000303 function calls in 11.274 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   11.274   11.274 <string>:1(<module>)
      100    0.000    0.000    0.000    0.000 test.py:17(__init__)
      100    3.290    0.033   11.273    0.113 test.py:22(run)
 14000000    5.873    0.000    7.909    0.000 test.py:29(next_opcode)
  4000000    0.623    0.000    0.623    0.000 test.py:38(op_inc_a)
 10000000    1.413    0.000    1.413    0.000 test.py:41(op_dec_a)
      100    0.000    0.000   11.274    0.113 test.py:84(test_single_class)
        1    0.000    0.000   11.274   11.274 {built-in method exec}
  2000000    0.074    0.000    0.074    0.000 {built-in method len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}


    test_mixin_class:         30000303 function calls in 11.183 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   11.183   11.183 <string>:1(<module>)
      100    0.000    0.000    0.000    0.000 test.py:47(__init__)
      100    3.272    0.033   11.182    0.112 test.py:53(run)
 14000000    5.810    0.000    7.836    0.000 test.py:61(next_opcode)
  4000000    0.624    0.000    0.624    0.000 test.py:71(op_inc_a)
 10000000    1.402    0.000    1.402    0.000 test.py:75(op_dec_a)
      100    0.000    0.000   11.183    0.112 test.py:89(test_mixin_class)
        1    0.000    0.000   11.183   11.183 {built-in method exec}
  2000000    0.074    0.000    0.074    0.000 {built-in method len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

Fazit: Der Unterschied zwischen mixin und nicht-mixin ist inrelevant, gerade auch im Vergleich zum Unterschied Py2 vs. Py3.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@jens:
Unter Python2.7 wirken folgende Änderungen Wunder:

Code: Alles auswählen

class CPU(object):
    def __init__(self):
        self.memory = [0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02]
        self.program_counter = 0x00
        self.register_A = 0x00

    def run(self):
        local_registers = [self.program_counter, self.register_A]
        memory = self.memory
        next = self.next_opcode
        for _ in xrange(INTERNAL_LOOPS):
            local_registers[0] = 0
            local_registers[1] = 0
            for _ in xrange(len(memory)):
                next(local_registers, memory)
        self.program_counter, self.register_A = local_registers

    def next_opcode(self, registers, memory):
        opcode = memory[registers[0]]
        registers[0] += 1

        if opcode == 0x01:
            registers[1] += 1
        elif opcode == 0x02:
            registers[1] -= 1
Idee ist, innerhalb der Schleifen unnötige Lookups zu entfernen, heisst Attribut- oder Methodenlookup (self.) im lokalen Namensraum zu halten. Achso und natürlich range vs. xrange...
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

jerch hat geschrieben:Idee ist, innerhalb der Schleifen unnötige Lookups zu entfernen, heisst Attribut- oder Methodenlookup (self.) im lokalen Namensraum zu halten.
Mache ich teilweise. z.B. hier: https://github.com/6809/MC6809/blob/028 ... #L286-L287

Natürlich kann man das extrem ausbauen. Aber das führt zu sehr unschönem code. Das will ich dann auch nicht ;)
jerch hat geschrieben:Achso und natürlich range vs. xrange...
Im eigentlich code nutzte ich auch xrange in py2 und range in py3...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@jens:
Alles innerhalb Deiner self.get_and_call_next_op Methode ist "busy loop", d.h. jeder kleine Zusatzaufwand summiert sich auf. Und von da absteigend hast Du immernoch self.-Zugriffe drin, diese sind halt teurer als lokale. Was spricht dagegen, den äusseren Zustand per Parameter an den lokalen Namensraum mitzugeben (siehe memory-Übergabe im Bsp. oben)?

Bei Deiner Registerimplementation nutzt Du u.a. getter und setter-Methoden. Die sind deutlich teurer in CPython als der direkte lesende/schreibende Attributzugriff. Da die Registermanipulation die Hauptarbeit der CPU ist, wirkt sich das massiv aus.

Ich vermute, dass Du mit Umstellung auf lokale Namen und direkten Attributzugriff die Cyclerate der CPython-Version etwa verdoppelt könntest.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

jerch hat geschrieben:Alles innerhalb Deiner self.get_and_call_next_op Methode ist "busy loop"...
Zu den generellen Optimierungen, hab ich dir bei http://www.python-forum.de/viewtopic.ph ... 62#p282162 geantwortet. IMHO passt das dort besser.

Hier geht es um Mega-class-Aufteilen. Allerdings werde ich wohl eh bei der Aufteilung per Mixin bleiben, denke ich mir.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Antworten