Single URLSession for all requests versus per request, performance

In a legacy project which has both ObjC and Swift Code I noticed that for each request a new session was created:

-(void)sendRequest {
// Some configuration

// New session is created for every request
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
[session dataTaskWithRequest:self.request completionHandler:^(NSData * _Nullable data, NSURLResponse *_Nullable response, NSError * _Nullable error) {
// Doing something with response
}
}

As per the information shared in past WWDC videos and answer over here: New NSURLSession for every DataTask overkill?, I noticed that this is an anti-pattern and should be avoided. In it's place we should use a single URLSession for all requests:

-(void)sendRequest:(NSURLReqest *)request {
// Some configuration

// 
[self.session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse *_Nullable response, NSError * _Nullable error) {
// Doing something with response
}
}

Please note that at server HTTP 1.1 is configured for the rest APIs.

As per my understanding whenever we create a new session it is equivalent to creating a new TCP connection, since there is a handshake involved at the beginning for each new TCP connection that will add latency so ideally we should use same URLSession for each request.

In order to validate it I started profiling both the implementations using Instruments for a scenario where after login we are invoking 50 APIs most of which are returning json in response, 10 returning image data.

Here is my observation:

  1. There is slight improvement in time taken by each request
  2. Whenever we use one session per request, internally OS creates parallel connections so it achieves identical results when compared to the single session used for all requests
  3. If we are directly serializing image using URLSessionDataTask it takes less time versus using URLSessionDownloadTask

Based on 2nd and 3rd observation it appears to be a bad choice in my case. Am I missing anything over here?

In your example you are using the standard (default) session configuration and not specifying delegate. Wouldn't "shared" session work for you? IIRC it doesn't complete on the main queue, but if you need that it's easy to hop to that with DispatchQueue.main.async { ... }.

I wouldn't put it that way, as creating a new TCP connection is really just a byproduct of the fact that URLSession persists quite of bit of internal state, which includes your connections, and you recreate that state every time you create a new URLSession. Fundamentally, separate URLSessions for every request is an anti-pattern because URLSession is expensive to create and tear down. By creating a separate URLSession for each request, especially if your requests occur in parallel, you will see:

  • Higher memory usage, as each URLSession maintains its own state.
  • Higher CPU usage, as each URLSession must set up and tear down that state.
  • Higher latency, as the time taken to create URLSessions will increase the overall time for network requests, both from creating the instance yourself as well as reestablishing connections to the same server.
  • Increased server load, as creating a new URLSession for each request prevents connection reuse, a fundamental HTTP optimization (especially once you move to HTTP/2 or HTTP/3). This includes not only TCP setup and teardown but TLS work as well.

Apple's OSes have various internal optimizations around maximum connections per host. There's no real way to predict how separate URLSessions connecting to the same host will interact. Not only do URLSessions establish a limited number of connections themselves, but there seems to be some sort of internal maximum per-host across all URLSessions which we can't influence. However, those connections are better reused from within one URLSession rather than many, especially with HTTP/2 and HTTP/3, since they multiplex over a single connection.

Of course, as downloads always go to disk first, so serializing from them needs to pay that cost as well. This doesn't seem to have anything to do with the question at hand though.

(Also, stop hard coding HTTP 1.1 on the server side, there are more optimizations to be had.)

So no, there's never a case where you want to use one URLSession per request.

2 Likes