Skip to content

Simplified and improved generate_otp() performance in Steam class #179

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Genarito
Copy link

Hi! I hope you are doing well! This is a minimal change but using lists instead of string concatenations (to avoid the generation of new strings in each iteration) and avoiding the unnecessary casting of the int_code % total_chars to int can simplify and improve the performance (~18.5% faster) of the generate_otp method in the Steam class.

Here is a benchmark in case you are interested in measuring the time difference:

import timeit

# Constants for the OTP generation
STEAM_CHARS = "23456789BCDFGHJKMNPQRTVWXY"  # steam's custom alphabet
STEAM_DEFAULT_DIGITS = 5  # Steam TOTP code length


# A base class with a dummy implementation of generate_otp,
# which simply returns a fixed string.
class BaseOTP:
    def generate_otp(self, input: int) -> str:
        # For benchmarking purposes, return a constant numeric string.
        return "12345678"


# Original implementation of generate_otp
class OriginalOTP(BaseOTP):
    def generate_otp(self, input: int) -> str:
        """
        Generate an OTP using string concatenation.
        """
        str_code = super().generate_otp(input)
        int_code = int(str_code)

        steam_code = ""
        total_chars = len(STEAM_CHARS)

        for _ in range(STEAM_DEFAULT_DIGITS):
            pos = int_code % total_chars
            char = STEAM_CHARS[int(pos)]
            steam_code += char
            int_code //= total_chars

        return steam_code


# Optimized implementation of generate_otp using list accumulation
class OptimizedOTP(BaseOTP):
    def generate_otp(self, input: int) -> str:
        """
        Generate an OTP using list accumulation for improved performance.
        """
        int_code = int(super().generate_otp(input))
        total_chars = len(STEAM_CHARS)

        digits = []
        for _ in range(STEAM_DEFAULT_DIGITS):
            pos = int_code % total_chars
            digits.append(STEAM_CHARS[pos])
            int_code //= total_chars

        return "".join(digits)


# Create instances of each implementation
original = OriginalOTP()
optimized = OptimizedOTP()

# Number of iterations for the benchmark
iterations = 100_000_000

# Benchmark the original implementation
original_time = timeit.timeit(lambda: original.generate_otp(42), number=iterations)

# Benchmark the optimized implementation
optimized_time = timeit.timeit(lambda: optimized.generate_otp(42), number=iterations)

print(f"Original version time for {iterations} iterations: {original_time:.6f} seconds")
print(f"Optimized version time for {iterations} iterations: {optimized_time:.6f} seconds")

If you want, I can refactor the code to store int_code % total_chars in a variable called pos and make it more readable, but well, it would be slightly slower given its allocation.

Greetings!

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.

1 participant