Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Removing unnecessary lazy calls.#605

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
OliverLetterer wants to merge1 commit intoswift-server:main
base:main
Choose a base branch
Loading
fromOliverLetterer:removing-unnecessary-lazy-calls

Conversation

@OliverLetterer
Copy link

/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP1Connections.swift had unnecessary lazy calls in it. Since the results of the lazy calls are immediately consumed, using lazy here introduces an unnecessary performance overhead!

@swift-server-bot
Copy link

Can one of the admins verify this patch?

6 similar comments
@swift-server-bot
Copy link

Can one of the admins verify this patch?

@swift-server-bot
Copy link

Can one of the admins verify this patch?

@swift-server-bot
Copy link

Can one of the admins verify this patch?

@swift-server-bot
Copy link

Can one of the admins verify this patch?

@swift-server-bot
Copy link

Can one of the admins verify this patch?

@swift-server-bot
Copy link

Can one of the admins verify this patch?

Copy link
Collaborator

@LukasaLukasa left a comment

Choose a reason for hiding this comment

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

Thanks for this PR! However, it's not the case that the.lazy has no use because the call is immediately consumed without overhead. Without.lazy,.map will allocate and return anArray. That Array is a performance cost, and we don't want to materialise it.

@OliverLetterer
Copy link
Author

That is simply not correct!

Bildschirm­foto 2022-07-28 um 18 02 55

Bildschirm­foto 2022-07-28 um 18 08 45

Bildschirm­foto 2022-07-28 um 18 12 01

Bildschirm­foto 2022-07-28 um 18 09 13 1

@dnadoba
Copy link
Collaborator

dnadoba commentedJul 28, 2022
edited
Loading

You have changed lazy in two places. Lets first talk about about theDictionary.init(_:uniquingKeysWith:) at line 607:

letstartingRequiredEventLoopConnectionCount=Dictionary(
self.connections[self.overflowIndex..<self.connections.endIndex].lazy.map{
($0.eventLoop.id,1)
},
uniquingKeysWith:+
)

I have slightly modified the test as I wasn't sure how you have createdallobjects but I can't reproduce your test results. Here is my modified version:

@inline(never)func blackHole(_ value:someAny){}func lazy(){letallobjects=Array(repeating:1, count:100)varcounter=0letduration=ContinuousClock().measure{for_in0..<1_000_000{            counter+=Dictionary(allobjects[0..<40].lazy.map({($0,1)}), uniquingKeysWith:+).values.first!}}blackHole(counter)print("lazy", duration)}func eager(){letallobjects=Array(repeating:1, count:100)varcounter=0letduration=ContinuousClock().measure{for_in0..<1_000_000{            counter+=Dictionary(allobjects[0..<40].map({($0,1)}), uniquingKeysWith:+).values.first!}}blackHole(counter)print("eager", duration)}lazy()eager()

which prints in release mode with swift-5.7-DEVELOPMENT-SNAPSHOT-2022-07-17-a:

lazy 5.722392167000001 secondseager 6.738160625 seconds

This makes perfect sense as no intermediate array is allocated becauseDictionary.init(_:uniquingKeysWith:) takessome Sequence as it's input.


However, I think you are right about the second place where you have changed lazy:

varconnectionToCreate= requiredEventLoopOfPendingRequests
.flatMap{ eventLoop, requestCount->[(Connection.ID,EventLoop)]in
// We need a connection for each queued request with a required event loop.
// Therefore, we look how many request we have queued for a given `eventLoop` and
// how many connections we are already starting on the given `eventLoop`.
// If we have not enough, we will create additional connections to have at least
// on connection per request.
letconnectionsToStart= requestCount- startingRequiredEventLoopConnectionCount[eventLoop.id, default:0]
returnstride(from:0, to: connectionsToStart, by:1).lazy.map{ _in
(self.createNewOverflowConnection(on: eventLoop), eventLoop)
}
}

We have manually specified the return type as being anArray, we sadly do allocate an intermediateArray.
ButflatMap has an overload which actually acceptssome Sequence as the return type from the closure and hence eliminates the intermediate allocation too:https://developer.apple.com/documentation/swift/sequence/flatmap(_:)-jo2y
Using this isn't straight forward though because.lazy.map(_:) will now take an escaping closure and we make a mutating call to self.

The solution to this is a bit ugly as we need to usewithoutActuallyEscaping and would look something like this:

varconnectionToCreate=withoutActuallyEscaping({(eventLoop:EventLoop)->HTTPConnectionPool.Connection.IDinself.createNewOverflowConnection(on: eventLoop)}){ createNewOverflowConnectionOnEventLoopin    requiredEventLoopOfPendingRequests.flatMap{ eventLoop, requestCount->LazyMapSequence<LazySequence<StrideTo<Int>>.Elements,(HTTPConnectionPool.Connection.ID,EventLoop)>in           // We need a connection for each queued request with a required event loop.           // Therefore, we look how many request we have queued for a given `eventLoop` and           // how many connections we are already starting on the given `eventLoop`.           // If we have not enough, we will create additional connections to have at least           // on connection per request.letconnectionsToStart= requestCount- startingRequiredEventLoopConnectionCount[eventLoop.id, default:0]returnstride(from:0, to: connectionsToStart, by:1).lazy.map{ _in(createNewOverflowConnectionOnEventLoop(eventLoop), eventLoop)}}}

I haven't tested the performance but I'm quite confident that this will improve performance too as we do not allocate an intermediate array.

As this makes the code a bit harder to follow I ended up not using thewithoutActuallyEscaping trick. Thelazy is just an artifact I forgot while reverting the change. Aslazy has no effect here, we could just remove it.@Lukasa what do you think? Is it worth the complexity to use the non-allocatingwithoutActuallyEscaping trick or should we just removelazy?

@Lukasa
Copy link
Collaborator

In the second case you're right that.lazy is entirely unnecessary. Sorry about the confusion there, that's my fault. As constructed, the.lazy achieves nothing, because we're specifying that we're returning anArray. I definitely don't think we gain enough to justify usingwithoutActuallyEscaping, which is a pattern I generally dislike.

However, in the case of Dictionary construction there remains insufficient evidence to make the case that this flow optimises the intermediate Array away. That allocation is extraordinarily painful, and it's worth removingeven though.lazy is a substantially slower way to iterate.

dnadoba reacted with thumbs up emoji

@Lukasa
Copy link
Collaborator

As a sidebar, if we really wanted to make this faster the way to do it is to replace this functional code that feels like it wants to use.lazy with imperative iteration. Swift is substantially better at optimising the latter (due to the absence of escaping closures).

dnadoba reacted with thumbs up emoji

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

Reviewers

@LukasaLukasaLukasa requested changes

Requested changes must be addressed to merge this pull request.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

4 participants

@OliverLetterer@swift-server-bot@dnadoba@Lukasa

[8]ページ先頭

©2009-2025 Movatter.jp