Skip to content

Look up str.strip chars in a set instead of rescanning them - #259

Closed
Dilshad-Tanvi wants to merge 1 commit into
meta:mainfrom
Dilshad-Tanvi:strip-chars-set
Closed

Dilshad-Tanvi wants to merge 1 commit into
meta:mainfrom
Dilshad-Tanvi:strip-chars-set

Conversation

@Dilshad-Tanvi

Copy link
Copy Markdown
Contributor

str.strip, lstrip and rstrip with a chars argument test each character with chars.contains(c), which scans all of chars every time, so stripping is O(len(S) * len(chars)). A string made of characters that only appear at the end of a long chars makes every test scan the whole argument. Starlark has no unbounded loops or recursion, so builtins are expected to cost about linear in their input, but this script ties up an evaluator for seconds:

n = 320000
("é" * n).strip("ĩ" * n + "é")

Release starlark binary:

n before after
80000 0.34s <0.01s
160000 1.36s <0.01s
320000 5.42s 0.01s
5000000 ~22 min (extrapolated) 0.04s

The ASCII form ("a" * n).strip("b" * n + "a") takes 2.80s at n=320000 before and under 0.01s after.

The set is now built once per call: a 128-bit mask for ASCII and a sorted, deduplicated Vec<char> searched with binary_search for the rest, so the cost is O(len(S) log len(chars) + len(chars)) and an all-ASCII chars allocates nothing. starlark-go goes through Go's strings.Trim, which is linear for an ASCII cutset; CPython is quadratic here too, so this is faster than Python rather than matching it. These three methods are the only per-character membership scans over a user string in the workspace. The new test covers ASCII at both ends of the range, non-ASCII, duplicates and empty arguments, and passes both before and after the change since results are unchanged. cargo test and cargo clippy pass.

strip, lstrip and rstrip tested each character with `chars.contains(c)`,
which scans the whole `chars` argument every time, so stripping cost
O(len(S) * len(chars)). Build the set once per call instead: a bitmap for
ASCII and a sorted Vec for the rest. Results are unchanged.
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 29, 2026
@meta-codesync

meta-codesync Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

This pull request has been imported. If you are a Meta employee, you can view this in D128326955. (Because this pull request was imported automatically, there will not be any future comments.)

@ndmitchell

Copy link
Copy Markdown
Contributor

Super cool! I'll get this merged. Very nice work.

meta-codesync Bot pushed a commit to meta/buck2 that referenced this pull request Oct 30, 2026
Summary:
`str.strip`, `lstrip` and `rstrip` with a `chars` argument test each character with `chars.contains(c)`, which scans all of `chars` every time, so stripping is O(len(S) * len(chars)). A string made of characters that only appear at the end of a long `chars` makes every test scan the whole argument. Starlark has no unbounded loops or recursion, so builtins are expected to cost about linear in their input, but this script ties up an evaluator for seconds:

```python
n = 320000
("é" * n).strip("ĩ" * n + "é")
```

Release `starlark` binary:

| n | before | after |
|---|---|---|
| 80000 | 0.34s | <0.01s |
| 160000 | 1.36s | <0.01s |
| 320000 | 5.42s | 0.01s |
| 5000000 | ~22 min (extrapolated) | 0.04s |

The ASCII form `("a" * n).strip("b" * n + "a")` takes 2.80s at n=320000 before and under 0.01s after.

The set is now built once per call: a 128-bit mask for ASCII and a sorted, deduplicated `Vec<char>` searched with `binary_search` for the rest, so the cost is O(len(S) log len(chars) + len(chars)) and an all-ASCII `chars` allocates nothing. starlark-go goes through Go's `strings.Trim`, which is linear for an ASCII cutset; CPython is quadratic here too, so this is faster than Python rather than matching it. These three methods are the only per-character membership scans over a user string in the workspace. The new test covers ASCII at both ends of the range, non-ASCII, duplicates and empty arguments, and passes both before and after the change since results are unchanged. `cargo test` and `cargo clippy` pass.

X-link: meta/starlark-rust#259

Reviewed By: ndmitchell

Differential Revision: D128326955

fbshipit-source-id: c57b1322064bcad9ad32a7ecd568ae433549043f
@meta-codesync meta-codesync Bot closed this in 6f222f5 Oct 30, 2026
@meta-codesync meta-codesync Bot added the Merged label Oct 30, 2026
@meta-codesync

meta-codesync Bot commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

This pull request has been merged in 6f222f5.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants