Skip to content

Commit 70388b8

Browse files
authored
Merge pull request #23 from kurtmckee/optimize-code-paths
Optimize code paths
2 parents 4dbf371 + 7c4ef18 commit 70388b8

File tree

7 files changed

+851
-34
lines changed

7 files changed

+851
-34
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

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

69
**v0.4.1**
710
- Compatibility with Python 3.6 (not officially supported)

assets/filter_blocklist.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import pathlib
2+
import sys
3+
from typing import Set, Tuple
4+
5+
6+
repo_root = pathlib.Path(__file__).parent.parent
7+
this_file = pathlib.Path(__file__).relative_to(repo_root)
8+
constants_path = repo_root / "sqids/constants.py"
9+
import sqids.constants # noqa
10+
11+
12+
DIGITS = set("0123456789")
13+
14+
15+
def filter_blocklist() -> Tuple[Set[str], Set[str], Set[str]]:
16+
"""Pre-filter the blocklist and update the constants file."""
17+
18+
exact_match = set()
19+
match_at_ends = set()
20+
match_anywhere = set()
21+
22+
for word in sqids.constants.DEFAULT_BLOCKLIST:
23+
if len(word) == 3:
24+
exact_match.add(word)
25+
elif set(word) & DIGITS:
26+
match_at_ends.add(word)
27+
else:
28+
match_anywhere.add(word)
29+
30+
return exact_match, match_at_ends, match_anywhere
31+
32+
33+
def generate_new_constants_file(
34+
exact_match: Set[str],
35+
match_at_ends: Set[str],
36+
match_anywhere: Set[str],
37+
) -> str:
38+
"""Generate the text of a new constants file."""
39+
40+
lines = [
41+
f'DEFAULT_ALPHABET = "{sqids.constants.DEFAULT_ALPHABET}"',
42+
f"DEFAULT_MIN_LENGTH = {sqids.constants.DEFAULT_MIN_LENGTH}",
43+
"",
44+
"# =======",
45+
"# NOTE",
46+
"# =======",
47+
"#",
48+
f"# When updating the blocklist, run {this_file} to pre-filter constants.",
49+
"# This is critical for performance.",
50+
"#",
51+
"",
52+
"DEFAULT_BLOCKLIST = [",
53+
]
54+
# Output a sorted blocklist.
55+
for word in sorted(sqids.constants.DEFAULT_BLOCKLIST):
56+
lines.append(f' "{word}",')
57+
lines.append("]")
58+
59+
# Output exact-match blocklist words.
60+
lines.append("")
61+
lines.append("_exact_match = {")
62+
for word in sorted(exact_match):
63+
lines.append(f' "{word}",')
64+
lines.append("}")
65+
66+
# Output match-at-ends blocklist words.
67+
lines.append("")
68+
lines.append("_match_at_ends = (")
69+
for word in sorted(match_at_ends):
70+
lines.append(f' "{word}",')
71+
lines.append(")")
72+
73+
# Output match-anywhere blocklist words.
74+
lines.append("")
75+
lines.append("_match_anywhere = {")
76+
for word in sorted(match_anywhere):
77+
lines.append(f' "{word}",')
78+
lines.append("}")
79+
80+
return "\n".join(lines).rstrip() + "\n" # Include a trailing newline.
81+
82+
83+
def main() -> int:
84+
text = constants_path.read_text()
85+
86+
exact_match, match_at_ends, match_anywhere = filter_blocklist()
87+
new_text = generate_new_constants_file(exact_match, match_at_ends, match_anywhere)
88+
89+
if text == new_text:
90+
print("No changes necessary")
91+
return 0
92+
93+
print(f"Updating {constants_path.relative_to(repo_root)}")
94+
constants_path.write_text(new_text, newline="\n", encoding="utf-8")
95+
return 1
96+
97+
98+
if __name__ == "__main__":
99+
sys.exit(main())

assets/performance.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import sqids
2+
import timeit
3+
4+
number = 100_000
5+
6+
print(f"Iterations: {number:,d}")
7+
8+
print(
9+
"{0:<20s} {1:7.3f}".format(
10+
"Instantiate:",
11+
timeit.timeit(
12+
stmt="sqids.Sqids()",
13+
globals={"sqids": sqids},
14+
number=number,
15+
)
16+
)
17+
)
18+
19+
print(
20+
"{0:<20s} {1:7.3f}".format(
21+
"Encode [0]:", # [0] -> 'bM'
22+
timeit.timeit(
23+
stmt="squid.encode([0])",
24+
globals={"squid": sqids.Sqids()},
25+
number=number,
26+
)
27+
)
28+
)
29+
30+
print(
31+
"{0:<20s} {1:7.3f}".format(
32+
"Encode [0, 1, 2]:", # [0, 1, 2] -> 'rSCtlB'
33+
timeit.timeit(
34+
stmt="squid.encode([0, 1, 2])",
35+
globals={"squid": sqids.Sqids()},
36+
number=number,
37+
)
38+
)
39+
)
40+
41+
print(
42+
"{0:<20s} {1:7.3f}".format(
43+
"Decode 'bM':", # 'bM' -> [0]
44+
timeit.timeit(
45+
stmt="squid.decode('bM')",
46+
globals={"squid": sqids.Sqids()},
47+
number=number,
48+
)
49+
)
50+
)
51+
52+
print(
53+
"{0:<20s} {1:7.3f}".format(
54+
"Decode 'rSCtlB':", # 'rSCtlB' -> [0, 1, 2]
55+
timeit.timeit(
56+
stmt="squid.decode('rSCtlB')",
57+
globals={"squid": sqids.Sqids()},
58+
number=number,
59+
)
60+
),
61+
)

0 commit comments

Comments
 (0)