Skip to content

Optimize code paths #23

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

Merged
merged 9 commits into from
Aug 6, 2024
Merged
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

**Unreleased**
- Support Python 3.12 and 3.13.
- Speed up encoding by ~85% by optimizing blocklist checks.
This improvement requires more calculation when the `Sqids` class is instantiated,
so users are encouraged to instantiate `Sqids` once and always reuse the instance.

**v0.4.1**
- Compatibility with Python 3.6 (not officially supported)
Expand Down
99 changes: 99 additions & 0 deletions assets/filter_blocklist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import pathlib
import sys
from typing import Set, Tuple


repo_root = pathlib.Path(__file__).parent.parent
this_file = pathlib.Path(__file__).relative_to(repo_root)
constants_path = repo_root / "sqids/constants.py"
import sqids.constants # noqa


DIGITS = set("0123456789")


def filter_blocklist() -> Tuple[Set[str], Set[str], Set[str]]:
"""Pre-filter the blocklist and update the constants file."""

exact_match = set()
match_at_ends = set()
match_anywhere = set()

for word in sqids.constants.DEFAULT_BLOCKLIST:
if len(word) == 3:
exact_match.add(word)
elif set(word) & DIGITS:
match_at_ends.add(word)
else:
match_anywhere.add(word)

return exact_match, match_at_ends, match_anywhere


def generate_new_constants_file(
exact_match: Set[str],
match_at_ends: Set[str],
match_anywhere: Set[str],
) -> str:
"""Generate the text of a new constants file."""

lines = [
f'DEFAULT_ALPHABET = "{sqids.constants.DEFAULT_ALPHABET}"',
f"DEFAULT_MIN_LENGTH = {sqids.constants.DEFAULT_MIN_LENGTH}",
"",
"# =======",
"# NOTE",
"# =======",
"#",
f"# When updating the blocklist, run {this_file} to pre-filter constants.",
"# This is critical for performance.",
"#",
"",
"DEFAULT_BLOCKLIST = [",
]
# Output a sorted blocklist.
for word in sorted(sqids.constants.DEFAULT_BLOCKLIST):
lines.append(f' "{word}",')
lines.append("]")

# Output exact-match blocklist words.
lines.append("")
lines.append("_exact_match = {")
for word in sorted(exact_match):
lines.append(f' "{word}",')
lines.append("}")

# Output match-at-ends blocklist words.
lines.append("")
lines.append("_match_at_ends = (")
for word in sorted(match_at_ends):
lines.append(f' "{word}",')
lines.append(")")

# Output match-anywhere blocklist words.
lines.append("")
lines.append("_match_anywhere = {")
for word in sorted(match_anywhere):
lines.append(f' "{word}",')
lines.append("}")

return "\n".join(lines).rstrip() + "\n" # Include a trailing newline.


def main() -> int:
text = constants_path.read_text()

exact_match, match_at_ends, match_anywhere = filter_blocklist()
new_text = generate_new_constants_file(exact_match, match_at_ends, match_anywhere)

if text == new_text:
print("No changes necessary")
return 0

print(f"Updating {constants_path.relative_to(repo_root)}")
constants_path.write_text(new_text, newline="\n", encoding="utf-8")
return 1


if __name__ == "__main__":
sys.exit(main())
61 changes: 61 additions & 0 deletions assets/performance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import sqids
import timeit

number = 100_000

print(f"Iterations: {number:,d}")

print(
"{0:<20s} {1:7.3f}".format(
"Instantiate:",
timeit.timeit(
stmt="sqids.Sqids()",
globals={"sqids": sqids},
number=number,
)
)
)

print(
"{0:<20s} {1:7.3f}".format(
"Encode [0]:", # [0] -> 'bM'
timeit.timeit(
stmt="squid.encode([0])",
globals={"squid": sqids.Sqids()},
number=number,
)
)
)

print(
"{0:<20s} {1:7.3f}".format(
"Encode [0, 1, 2]:", # [0, 1, 2] -> 'rSCtlB'
timeit.timeit(
stmt="squid.encode([0, 1, 2])",
globals={"squid": sqids.Sqids()},
number=number,
)
)
)

print(
"{0:<20s} {1:7.3f}".format(
"Decode 'bM':", # 'bM' -> [0]
timeit.timeit(
stmt="squid.decode('bM')",
globals={"squid": sqids.Sqids()},
number=number,
)
)
)

print(
"{0:<20s} {1:7.3f}".format(
"Decode 'rSCtlB':", # 'rSCtlB' -> [0, 1, 2]
timeit.timeit(
stmt="squid.decode('rSCtlB')",
globals={"squid": sqids.Sqids()},
number=number,
)
),
)
Loading