-
Notifications
You must be signed in to change notification settings - Fork 18.6k
Use lazy initialization to reuse ordered dict/list creations to save… #3891
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use lazy initialization to reuse ordered dict/list creations to save… #3891
Conversation
|
Is it possible to make this return either an immutable object, or make a copy each time? |
|
There would be no point in this code if it made a copy each time. I can look at making it immutable. But do you know of any code that relies on it always being a new copy? Because I didn't see any point in this behavior, which is why I made the change. In fact, it caused a lot of trouble because I expected it to be just accessing, when in reality, it's making new copies every time. |
|
Do you have any workloads where the time spent on these Python operations is nontrivial? In practice, you spend all your time in C++/CUDA for any real-world workload right? |
|
Yes I do. I use python for doing training and testing as well, so my only real interface with the C++/CUDA stuff is through calling forward and backward. For extremely large networks like ResNet or what I work on, calling net.blobs repeatedly to fill in values for a forward pass can take seconds per forward pass. True enough I could store the net.blobs dict as a variable on my end, but this seems like a really unobvious interface. Most property accesses you would expect to be constant time operations, which is why I was so confused when calling net.blobs seemed to give massive slow downs. The point is that the interface isn't written as net.get_blobs_dict(), it is written as net.blobs. One is a method that should return a dictionary which could/maybe should be new each time, and one is accessing an internal variable of the underlying structure. There is no reason why net.blobs should give a new copy other than bad naming conventions. |
|
I'm not alone in thinking this way. Take a look at the Google Python style guide to see what I mean: https://google.github.io/styleguide/pyguide.html?showone=Access_Control#Access_Control
So perhaps the best thing would be to change the name of these variables and break everyone's code to make sure they're all on the same page. But that doesn't sound super friendly. And for the record, Caffe ostensibly follows the Google Python style guide, so this isn't just blowing smoke: http://caffe.berkeleyvision.org/development.html |
python/caffe/pycaffe.py
Outdated
| def _Net_inputs(self): | ||
| return [list(self.blobs.keys())[i] for i in self._inputs] | ||
| if not hasattr(self, '_input_list'): | ||
| self._input_list = [list(self.blobs.keys())[i] for i in self._inputs] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While you're considering optimizations to this function, you might as well pull out list(self.blobs.keys()) as a separate line (and same for _Net_outputs).
|
Now that I think about it more, you're right, it doesn't matter whether or not this is a copy, since the only part that is being copied is the I'm not aware of any code that relies on the copy behavior of the |
|
@seanbell What do you mean pull out as a separate line? Like put that in a method and do the same lazy instantiation? Or just break it into two lines. I'm not sure I see the point in the second one, and the first one I would call overkill, as it really doesn't save that much computation time. The point of the PR is exactly the one you make in your previous comment. I'm not sure anyone cares about it being a new copy each time, and it's only a shallow copy, so the blobs will still act the same. However, it does save lots of time on repeated calls to the self.blobs property, which is a nice simple interface that really shouldn't be taking a long time to run, and users shouldn't have to worry about saving the result themselves. |
|
Regarding splitting |
|
Oh, I see. That's a valid point, and I can make that change. Do you know how to edit a PR without adding another commit? I got a "talking to" before for having multiple commits, and I was told to use git squash, but when I try to squash, I can't commit because I'm behind the previously pushed version. If I pull, I'll need to merge which will add a commit. |
|
You can make sure you're up to date with Then once you're done, force push |
5d1b704 to
7deb6fd
Compare
…ime on repeated calls.
7deb6fd to
7a81836
Compare
|
Done. Thanks! |
|
FWIW, you can DRY yourself a bit by reusing something like cached_property from https://github.com/pallets/werkzeug/blob/master/werkzeug/utils.py, and then just |
|
@ajtulloch Adding another dependency to Caffe for these few changes seems like a bad idea. This PR feels ready for a merge. @seanbell what do you think? |
|
LGTM @longjon -- thoughts? |
|
Yes, looks great, and I agree with the conclusions of this discussion. In particular:
Of course, the alternative to this is to figure out some way to actually create these objects at initialization time, which has been obstructed from the beginning by the awkward (but fun) boost-plus-python class definition. I think a workaround might be possible, but it's okay with me to keep doing it this way right now. Code looks good, history looks good, no additional tests are needed, so merging, thanks! |
…tiation-fix Use lazy initialization to reuse ordered dict/list creations to save…
… time on repeated calls.