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

PseudoHeaderFields adopting CoW to reduce size #88

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

guoye-zhang
Copy link
Contributor

HTTPRequest size 288 -> 16
HTTPResponse size 80 -> 16

rdar://144520456

@guoye-zhang guoye-zhang added 🔨 semver/patch No public API change. area/performance Improvements to performance. labels Feb 13, 2025
Copy link

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

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

Left a small note around the _modify accessor that may be worth some consideration.

I like the approach of having the two object references side-by-side, but I want to make sure we're considering the tradeoff here. The other approach would have been to move everything, including the HTTPFields, into the backing class.

If we did that, the cost of copying this object goes down further (it's just a single call to swift_retain/swift_release instead of two per copy). However, the cost of accessing the fields goes up slightly if it's done rarely, as we have a double-indirection to get to them.

I'd love to hear your thoughts about the choice between those two @guoye-zhang.

if !isKnownUniquelyReferenced(&self._storage) {
self._storage = self._storage.copy()
}
self._storage.extendedConnectProtocol = newValue
Copy link

Choose a reason for hiding this comment

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

A bit of a nit, but these accessors probably want to either replace set with _modify or to use @inlinable. Otherwise minor modifications can risk CoWing the underlying strings where it isn't necessary to do so.

This isn't a huge deal as I don't think there's a lot of minor string modifications happening this way (pretty rare to do request.path.value += "/something/else") but it'd be nice to avoid the CoW in that case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not very familiar with _modify. Does this look reasonable?

            _modify {
                if !isKnownUniquelyReferenced(&self._storage) {
                    self._storage = self._storage.copy()
                }
                yield &self._storage.path
                if let name = self._storage.path?.name {
                    precondition(name == .path, "Cannot change pseudo-header field name")
                }
            }

Copy link

Choose a reason for hiding this comment

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

I believe that will be fine, though _modify is extremely sensitive to the exact nature of its flow control. I recommend actually building this under -O and checking that there isn't a call to malloc or similar there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is a call to malloc after this change. Not sure how to get rid of it.

libHTTPTypes.dylib.zip

Screenshot 2025-02-17 at 3 40 44 PM

Copy link

Choose a reason for hiding this comment

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

Will take a look

get {
self._storage.status
}
set {
Copy link

Choose a reason for hiding this comment

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

Same note on these accessors re _modify.

@guoye-zhang
Copy link
Contributor Author

I like the approach of having the two object references side-by-side, but I want to make sure we're considering the tradeoff here. The other approach would have been to move everything, including the HTTPFields, into the backing class.

Initially I thought that we were limited by the current API shape since PseudoHeaderFields is a distinct type that can be operated on its own (e.g. request1.pseudoHeaderFields = request2.pseudoHeaderFields). But now that I think more about it, it is definitely possible to do. Also I wasn't sure about nesting 2 levels of CoW for headerFields but it should be fine, too.

I took the easier approach to implement them as separate CoW structs. I think 2-words is small enough like a Swift String.

@ekinnear
Copy link

It's interesting that we keep coming back to this pattern, but if that's the right way to do it, then 👍.
Approved pending the other discussion.

@Lukasa
Copy link

Lukasa commented Feb 17, 2025

I took the easier approach to implement them as separate CoW structs. I think 2-words is small enough like a Swift String.

Ah yeah, I'm not concerned about the size, only about the cost of copying. But agreed that the current design is fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/performance Improvements to performance. 🔨 semver/patch No public API change.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants