Skip to content
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

Reduce slightly allocations when reading/writing HTTP messages #950

Merged
merged 12 commits into from
Dec 21, 2022

Conversation

nickrobinson251
Copy link
Collaborator

@nickrobinson251 nickrobinson251 commented Oct 31, 2022

  1. write direct to io rather than first creating strings
    • we need to be careful to string any numbers before doing this, but doing this selectively still reduces allocations
  2. don't create a VersionNumber every time we parse a message (in parse_status_line!/parse_request_line!), as the VersionNumber constructor relies on regex matching which allocates, instead we can define our own much simpler type with allocation-free parsing

Together this takes a simple GET request from 170 -> 155 allocations

@@ -205,7 +205,7 @@ function parse_status_line!(bytes::AbstractString, response)::SubString{String}
if !exec(re, bytes)
throw(ParseError(:INVALID_STATUS_LINE, bytes))
end
response.version = VersionNumber(group(1, re, bytes))
response.version = HTTPVersion(group(1, re, bytes))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here, and a few lines above, is where we were allocating as part of construction VersionNumber

src/Strings.jl Outdated Show resolved Hide resolved
@@ -1,9 +1,98 @@
module Strings

export escapehtml, tocameldash, iso8859_1_to_utf8, ascii_lc_isequal
export HTTPVersion, escapehtml, tocameldash, iso8859_1_to_utf8, ascii_lc_isequal
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is in src/Strings.jl because it was the submodule that was imported in exactly the same places we need HTTPVersion

happy to move it somewhere else

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's fine

Base.:(==)(va::VersionNumber, vb::HTTPVersion) = va == VersionNumber(vb)
Base.:(==)(va::HTTPVersion, vb::VersionNumber) = VersionNumber(va) == vb

Base.isless(va::VersionNumber, vb::HTTPVersion) = isless(va, VersionNumber(vb))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we either need to define these methods, or else overload getproperty such that r.version returns a VersionNumber and we do all comparisons with VersionNumbers. Either seems fine to me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not that much code, so this seems fine

@codecov-commenter
Copy link

codecov-commenter commented Oct 31, 2022

Codecov Report

Merging #950 (2fe988b) into master (7a54ffc) will decrease coverage by 0.62%.
The diff coverage is 85.93%.

❗ Current head 2fe988b differs from pull request most recent head d59f8ae. Consider uploading reports for the commit d59f8ae to get more accurate results

@@            Coverage Diff             @@
##           master     #950      +/-   ##
==========================================
- Coverage   79.85%   79.23%   -0.63%     
==========================================
  Files          36       36              
  Lines        2969     3010      +41     
==========================================
+ Hits         2371     2385      +14     
- Misses        598      625      +27     
Impacted Files Coverage Δ
src/ConnectionPool.jl 85.40% <ø> (-0.86%) ⬇️
src/Servers.jl 79.47% <0.00%> (ø)
src/Strings.jl 90.24% <79.48%> (-9.76%) ⬇️
src/Messages.jl 86.04% <100.00%> (+0.08%) ⬆️
src/Parsers.jl 97.29% <100.00%> (ø)
src/Streams.jl 93.75% <100.00%> (-1.14%) ⬇️
src/clientlayers/MessageRequest.jl 92.30% <100.00%> (ø)
src/access_log.jl 80.00% <0.00%> (-13.34%) ⬇️
src/Exceptions.jl 91.89% <0.00%> (-2.71%) ⬇️
src/connectionpools.jl 70.70% <0.00%> (-2.03%) ⬇️
... and 6 more

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

src/Messages.jl Outdated Show resolved Hide resolved
src/Messages.jl Outdated Show resolved Hide resolved
src/Messages.jl Outdated

"""
writestartline(::IO, ::Message)

e.g. `"GET /path HTTP/1.1\\r\\n"` or `"HTTP/1.1 200 OK\\r\\n"`
"""
function writestartline(io::IO, r::Request)
write(io, "$(r.method) $(r.target) $(httpversion(r))\r\n")
write(io, r.method, " ", r.target, " ", r.version, "\r\n")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So instead of creating a big string and writing that, we iterate over the args and each is written to io?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, exactly, we save on allocating that big string before writing it out
https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-string-interpolation-for-I/O

and we overload Base.write for version so it also writes what we want straight to IO

so each saving 1 string allocation

Copy link
Member

@quinnj quinnj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM; thanks @nickrobinson251!

@nickrobinson251
Copy link
Collaborator Author

tests pass locally. not sure if we want to resolve #951 and get a clean CI run before merging here?

src/Messages.jl Outdated Show resolved Hide resolved
nickrobinson251 and others added 4 commits December 20, 2022 15:20
- ...before flushing to socket, to avoid task switching
  mid writing to the socket itself.
- unnecessary and was leading to occasional DNS Errors
@nickrobinson251 nickrobinson251 merged commit a2da061 into master Dec 21, 2022
@nickrobinson251 nickrobinson251 deleted the npr/allocs branch December 21, 2022 16:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants