nostr: avoid a String per generic tag key in Filter - #1427
Merged
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Contributor
Author
|
Codecov: The three lines are the visitor's |
yukibtc
requested changes
Aug 5, 2026
Serializing a filter built the map key with `format!("#{tag}")`, allocating
a `String` for every generic tag query. The key is always `#` followed by a
single ASCII letter, so it fits in a two-byte buffer on the stack.
Deserializing had the mirror problem: every key in the map was read as an
owned `String` just to look at its first two characters. Reading it through
a small key type that inspects the borrowed `&str` and yields the tag
directly avoids that.
Measured on an Apple M4, filter with three generic tag queries (178 bytes):
| operation | before | after |
|-------------|-------------------|-------------------|
| `as_json` | 302ns, 5 allocs | 188ns, 2 allocs |
| `from_json` | 724ns, 15 allocs | 584ns, 12 allocs |
A filter with no generic tags is unchanged, since neither path allocated
for keys in that case.
Output is unchanged: ten filter shapes, their round-trips, and fifteen
well-formed and malformed inputs all produce byte-identical results,
including which keys are ignored and which are rejected.
The key shapes had partial coverage, so the rules are now pinned: `#`
followed by exactly one letter is a tag query, `#` followed by exactly one
non-letter is rejected, and anything else is ignored.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Third of the follow-ups mentioned in #1425, after #1426.
Serializing a filter built each map key with
format!("#{tag}"), allocating aStringper generic tag query. The key is always#followed by a single ASCII letter, so it fits in a two-byte buffer on the stack.Deserializing had the mirror problem: every key in the map was read into an owned
Stringjust to inspect its first two characters. Reading it through a small key type that looks at the borrowed&strand yields the tag directly avoids that.Measured on an Apple M4, filter with three generic tag queries (178 bytes):
Filter::as_jsonFilter::from_jsonA filter with no generic tags is unchanged (55 -> 52 ns, 119 -> 116 ns, one allocation either way), since neither path allocated for keys in that case. The win is proportional to how many tag queries a filter carries.
Notes to the reviewers
Output is unchanged. Ten filter shapes, their round-trips, and fifteen well-formed and malformed inputs all produce byte-identical results, including which keys are ignored and which are rejected.
The key-shape rules had only partial coverage, and I got two of them wrong while writing the test, so they are now pinned explicitly:
#followed by exactly one letter is a tag query.#followed by exactly one non-letter is rejected. This includes"##", which matches the#Xshape and then fails the letter check."#ab","#","t","".Verified with the full
just precommitset pluscargo build --workspace --all-targetsandcargo clippy --all-targets -- -D warnings, including theno_stdandwasm32combinations.That is the last of the near-term follow-ups from #1425. The remaining one is larger and I would rather discuss it before writing anything:
Event::from_jsonspends 43 allocations on a twelve-tag event, 37 of them insideTags, becauseTagholds aVec<String>andTagsaVec<Tag>. That is a representation change to a public type, so I will open an issue with the numbers rather than a PR.Checklist
CHANGELOG.md(if applicable)