Open markrwilliams opened 7 years ago
At least one Automat user has found it to cause serious slow downs: https://github.com/meejah/txtorcon/issues/224
The thing that is slow in txtorcon is the "microdescriptor parser", which is here: https://github.com/meejah/txtorcon/blob/master/txtorcon/_microdesc_parser.py#L6
This parses a pretty simple line-based file (well, actually its streamed from the network) that has groups of (usually 4) related lines. A line starting with "r " starts a new "router" and other lines (if present) add information to it. On a decent 3GHz Xeon this takes 2+ seconds to parse a ~20k-line file in cpython. From cProfile
most of the time is spent in MethodicalInput
creating new onInput
inner-functions. Commenting-out the "@preserveName" and "@wraps" decorators speeds up by about 5 times.
So, I think a benchmark that would simulate this would just track "inputs per second" or "state transitions per second" or similar.
BTW I tested against python3 and pypy as well, with similar differences (obviously, pypy was way faster overall).
Okay, here's a simple test-case. I haven't worked it into an "actual benchmark", but did confirm that this shows similar ratio: ~56000 inputs/second vs ~230000 inputs/second by commenting-out the @wraps
and @preserveName
in MethodicalInput
.
import time
import automat
class Simple(object):
"""
"""
_m = automat.MethodicalMachine()
@_m.input()
def one(self, data):
"some input data"
@_m.state(initial=True)
def waiting(self):
"patiently"
@_m.output()
def boom(self, data):
pass
waiting.upon(
one,
enter=waiting,
outputs=[boom],
)
def transitions_per_second(machine, total):
start = time.time()
for x in range(total):
machine.one(x)
diff = time.time() - start
return total / diff
print("{} transitions/s".format(transitions_per_second(Simple(), 100000)))
What do you think of following trick in MethodicalInput.__get__
:
def __get__(self, oself, type=None):
...
@preserveName(self.method)
@wraps(self.method)
def doInput(*args, **kwargs):
...
setattr(oself, self._name(), doInput)
return doInput
?
That is replace getter in object with its result. It makes given benchmark to run about 5 times faster.
On Jul 21, 2017, at 4:35 AM, daa notifications@github.com wrote:
What do you think of following trick in MethodicalInput.get:
def __get__(self, oself, type=None): ... @preserveName(self.method) @wraps(self.method) def doInput(*args, **kwargs): ... setattr(oself, self.name(), doInput) return doInput
?
That is replace getter in object with its result. It makes given benchmark to run about 5 times faster.
Hmm. I suspect it would make it slower on PyPy though. Have you tried there?
I'm not sure if it's possible, but I think the best course would be completely eliminating the "create a new function + decorators on every __get__
call". I haven't looked deeply into what that would actually take, though, so feel free to ignore me ;)
In any case, I like the trick -- if only because it gets a pretty great speed-up without any deep changes :) as long as it's also faster on PyPy
With PyPy benchmark improves even more than with CPython - I tried and got 30-50x speedup.
Recently I had an idea that MethodicalInput()
could be decorator itself, so no tricks would be required to avoid continuous creation of doInput()
, however it will make implementations of input()
, output()
and state()
methods non-uniform. Your opinions?
Recently I had an idea that MethodicalInput() could be decorator itself
I don't see what benefit that would have. Can you expound? Perhaps on a dedicated ticket, since it seems totally unrelated to benchmarking?
Let's use https://codspeed.io for this!
Automat should have a benchmark suite so we can measure performance overhead.