Skip to content
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

Performance optimizations #53

Merged
merged 3 commits into from
Jun 11, 2020
Merged

Conversation

kolomenkin
Copy link
Contributor

@kolomenkin kolomenkin commented May 26, 2020

This is a draft PR.

I would like to discuss following questions:

  1. technical solution to cache character mappings for decoding. Is it good enough?
  2. should I keep file for algorithm speed measurement? Should I change something there?

The results of speed measurements are stored in separate files inside of PR:

perf.encode.orig.txt - before optimization
perf.encode.from_bytes.txt -after optimization

perf.decode.orig.txt - before optimization
perf.decode.mapping.txt -after optimization

It was surprize for me that the biggest impact was done for short strings (25 bytes of enthropy, like in bitcoin wallet address). And longer strings got less optimization in percents.

P.S. I'm going to remove redundant files in final PR: editorconfog, gitognore, my naive additional unit tests. And I will clean up the code. And I will create informative commit messages.

@keis
Copy link
Owner

keis commented May 27, 2020

Thanks!

At a quick glance the numbers look promising, I'll go through and add some review comments in the code. The benchmark code already served it's purpose so no need to include that but just an aside do you know of the timeit module from the standard lib? IIRC it does pretty much what your measure function is doing.

The short vs long string is interesting, I'm guessing this is because for long strings the map lookup is a smaller part compared to the number crunching that follows.

# map = array.array('b', [alphabet.find(x) for x in range(256)])
# map = bytes(fix_position(alphabet.find(x)) for x in range(256))

map = GLOBAL_MAP.get(alphabet, None)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than dealing with caching in decode lets use functools.lru_cache to abstract this bit away

@lru_cache
def get_map(alphabet: string):
    return bytes(...)

Copy link
Contributor Author

@kolomenkin kolomenkin May 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool! Did not know about lru_cache. This also solves potential problem with too big cache in case somebody will try using numerous alphabets

@@ -23,6 +24,16 @@
alphabet = BITCOIN_ALPHABET


def fix_position(position: int) -> int:
return position if position >= 0 else 255
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be written as position % 256 and be inlined which I think makes as it's about fitting the number into a byte.


map = GLOBAL_MAP.get(alphabet, None)
if not map:
# map = array.array('b', [alphabet.find(x) for x in range(256)])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the consideration between array and bytes? is there a difference?

Copy link
Contributor Author

@kolomenkin kolomenkin May 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From algorithmic point of view they are very similar for this task.
I have found array was a bit slower in my case. So I used bytes.

@kolomenkin
Copy link
Contributor Author

kolomenkin commented May 28, 2020

I have looked at timeit before writing performance test. But I felt like it is needs equal efforts to adopt for it and to write the same manually. And it looked not very flexible.

I agree about the long string optimization results. Big number arithmetics takes most of the time for those cases. And everything else is not so important any more.

@keis keis mentioned this pull request Jun 5, 2020
@keis
Copy link
Owner

keis commented Jun 6, 2020

Hey @kolomenkin did you have a chance to have another look at this? It would be really good to have this landed in master and there's no real blocker

@kolomenkin
Copy link
Contributor Author

Hey @kolomenkin did you have a chance to have another look at this? It would be really good to have this landed in master and there's no real blocker

Sorry, I was busy.
Now I have updated and cleaned up the pull request.

base58/__init__.py Outdated Show resolved Hide resolved
base58/__init__.py Outdated Show resolved Hide resolved
@kolomenkin kolomenkin changed the title Performance optimizations - draft PR Performance optimizations Jun 11, 2020
@keis keis merged commit 13080e2 into keis:master Jun 11, 2020
@keis
Copy link
Owner

keis commented Jun 11, 2020

Everything looks good to me, merged 🥳

Thanks again for all the work you put into this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants