Stop the query path doing work it does not need to - #658
Merged
Merged
Conversation
Three things a query paid for on the way through, none of which it used. A log line that is dropped still built its attributes. Passing them to With appends to a slice before anything checks the level, so a suppressed Debug cost an allocation and 185 ns rather than 70 and none; on the package logger, where With clones the handler, it cost six allocations and 575 ns rather than 11 and none. Thirty of those sat on the query path, one or more per element, so a chain of five paid for five records nobody was going to read. Where the logger With returns is used once, the attributes go to the call instead. The six places that keep the logger and log through it again are unchanged. A route ran four regular expressions that were never configured. An unset criterion compiled to the empty expression, which matches everything and still costs as much to run as one that does something: 159 ns each, 561 ns for a route with no criteria at all, on every route a query is offered to until one matches. A criterion that was not given is not compiled, and a route with none now decides in 13 ns. Five smaller ones, all of them per query or per record: the rate limiter built a mask and formatted the masked address into a string for every query, to look up a counter. The masks are built once and the key is an array, which a map takes without allocating. the DoH client expanded its URL template for every POST. A POST carries the query in its body, so the template takes no values and expands to the same URL every time. It is expanded once. the response name blocklist built a message per record in a response, to hand a name to a matcher that takes a query. One message carries each name in turn. the IP blocklist allocated a match to describe a hit for queries that missed, once per address in a response. It returns nothing on a miss, as the name databases already did. filtering a response built a new record list even when it dropped nothing, which is almost every response. The list is built once a record is dropped. Measured per call on the paths that changed: route with no criteria 319 -> 13 ns rate limiter Resolve 389 -> 253 ns, 3 -> 0 allocations IP filter, nothing dropped 491 -> 244 ns, 5 -> 0 allocations response name check 368 -> 341 ns, 3 -> 2 allocations The rate limiter and the response IP blocklist had no tests. Both have them now, the first over who shares an allowance with whom under either prefix and over a query with no source address at all, the second over a record dropped first, last, in the middle, all of them, and none of them, since that is where building the list lazily could go wrong. isDefault and String also read the name expression and had to stop assuming there is one.
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.
Three things a query paid for on the way through, none of which it used.
A log line that is dropped still built its attributes
Passing them to
Withappends to a slice before anything checks the level, so a suppressedDebugcost an allocation and 185 ns rather than 70 and none. On the package logger, whereWithclones the handler, it cost six allocations and 575 ns rather than 11 and none.Thirty of those sat on the query path, one or more per element, so a chain of five paid for five records nobody was going to read. Where the logger
Withreturns is used once, the attributes go to the call instead. The six places that keep the logger and log through it again are unchanged.A route ran four regular expressions that were never configured
An unset criterion compiled to the empty expression, which matches everything and still costs as much to run as one that does something: 159 ns each, 561 ns for a route with no criteria at all, on every route a query is offered to until one matches. A criterion that was not given is not compiled, and a route with none now decides in 13 ns.
Five smaller ones, all per query or per record
Measured
ResolveTests
The rate limiter and the response IP blocklist had none. Both have them now: the first over who shares an allowance with whom under either prefix and over a query with no source address at all, the second over a record dropped first, last, in the middle, all of them, and none of them, since that is where building the list lazily could go wrong.
isDefaultandStringalso read the name expression and had to stop assuming there is one, which the existing router tests caught.