Inclusion of a function in a local namespace to speed up the entry of access descriptors

I am using the numpy.random.normal function in a hard loop in a class.

 class MyClass(MyBaseClass): def run(self): while True: ... self.L.append(numpy.random.normal(0,1)) 

I know that in Python it's pretty slow to use multiple searches. There are 3 searches in numpy.random.normal : numpy looked at first, then random , and then normal .

So I decided to solve this problem by assigning numpy.random.normal local variable _normal .

Here we go:

 class MyClass(MyBaseClass): _normal = numpy.random.normal def run(self): while True: ... self.L.append(MyClass._normal(0,1)) 

The descriptors really bother me. When a variable in a class is being accessed, all base classes look for a data descriptor with the same name. He described here :

Mark objectname.__class__.__dict__ for attrname. If it exists and is a data descriptor, return the result of the descriptor. Search all objectname.__class__ for the same case.

So, I suppose if I put _normal in local space, as I said above, it will consider all the database classes for the data descriptor. And I fear that this will become a source of slowdown.

Are my concerns justified?

Do I have to worry about the time it takes to search for descriptors in base classes?

And is there a better way to speed up access to a function deep in a module when it is used in a class?


There was a discussion in the comments to the answers.

I decided to give some additional implementation details that turned out to be important (for my specific case).

Actually, the code is closer to this (it is very simplified):

 class MyClass(MyBaseClass): def __iter__(self): return self def next(self): self.L.append(numpy.random.normal(0,1)) def run(self): while True: self.next() 
+4
source share
2 answers

If you have to do something like this (is function search really the dominant cost? Generating random numbers is not cheap) you should understand that one global + one attr-search ( MyClass._normal ) is not much cheaper than one global + three attr search ( numpy.random.normal ). You really want to get zero global or attr requests inside the loop, which you can only do by defining _normal inside the function. If you are really desperate for shaving cycles, you should also precede the list pre-call:

 class MyClass(MyBaseClass): def run(self): _normal = numpy.random.normal _Lappend = self.L.append while True: ... _Lappend(_normal(0,1)) 

Contrast parsing output ( append statement only):

  LOAD_FAST 0 (self) LOAD_ATTR 1 (L) LOAD_ATTR 2 (append) LOAD_GLOBAL 3 (numpy) LOAD_ATTR 4 (random) LOAD_ATTR 5 (normal) LOAD_CONST 1 (0) LOAD_CONST 2 (1) CALL_FUNCTION 2 CALL_FUNCTION 1 POP_TOP 

vs

  LOAD_FAST 2 (_Lappend) LOAD_FAST 1 (_normal) LOAD_CONST 1 (0) LOAD_CONST 2 (1) CALL_FUNCTION 2 CALL_FUNCTION 1 

What's even better - vectorization - generate a lot of random normal values ​​and add them to the list at a time - you can do this with the argument size to numpy.random.normal .

+5
source

And I fear that this will become a source of slowdown.

Are my concerns justified?

It depends. Is it fast enough for the application you have in mind? If so, don’t worry. Changes to the laws of CPython, PyPy, NumPy, and moore are likely to reduce the amount of “deceleration” before it becomes a braking point.

+2
source

Source: https://habr.com/ru/post/1384598/


All Articles