The Surprising Cold Start Penalty in the AWS SDK for .NET
Tags: aws, dotnet, serverless
Published: 2022-09-09 21:07:17

This post is about raising awareness of a performance penalty when initializing the AWS SDK for .NET.

This is a startup tax incurred by all AWS Lambda functions using .NET. Fortunately, it's trivial to make it happen during the INIT phase where it's free. However, there is no way of avoiding it during a cold start. I can't help but think the initialization overhead should be much lower. 

For this benchmark, our code is almost identical with the baseline function, except that we initialize an S3 client in the constructor.

```csharp
using System.IO;
using System.Threading.Tasks;
using Amazon.S3;

namespace Benchmark.AwsSdk {

    public sealed class Function {

        //--- Fields ---
        private IAmazonS3? _s3Client;

        //--- Constructors ---
        public Function() {

            // initialize S3 client
            _s3Client = new AmazonS3Client();
        }

        //--- Methods ---
        public async Task<Stream> ProcessAsync(Stream request) {
            return Stream.Null;
        }
    }
}
```

## Cold Start Durations

The following table shows the new measurements using .NET 6, _Tiered Compilation_, and _ReadyToRun_ with 3 different memory configurations: 1,024 MB, 1,769 MB, and 5,120 MB.

Initializing the AWS SDK for .NET adds a 120+ ms penalty to the cold start duration. The impact remains the same, no matter the memory configuration since the initialization is happening during the INIT phase.

| Architecture | Memory Size | Tiered | Ready2Run | PreJIT | Init    | Cold Used | Total Cold Start | Penalty |
| ------------ | ---------- | ------ | --------- | ------ | ------- | --------- | ---------------- | ------- |
| arm64        | 1024MB     | yes    | yes       | no     | 380.461 | 41.773    | 422.234          | 147.21  |
| arm64        | 1769MB     | yes    | yes       | no     | 377.036 | 29.365    | 406.401          | 141.856 |
| arm64        | 5120MB     | yes    | yes       | no     | 349.227 | 28.428    | 377.655          | 136.467 |
| x86\_64      | 1024MB     | yes    | yes       | no     | 344.643 | 29.678    | 374.321          | 128.797 |
| x86\_64      | 1769MB     | yes    | yes       | no     | 339.3   | 23.058    | 362.358          | 122.975 |
| x86\_64      | 5120MB     | yes    | yes       | no     | 325.178 | 22.468    | 347.646          | 124.422 |

## Potential Cause

Looking at the code for the [AWS SDK for .NET](https://github.com/aws/aws-sdk-net), I suspect the culprit of the slow initialization is the [endpoints.js](https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/endpoints.json) file. This 700+ KB megalodon JSON file is parsed every time the AWS SDK is initialized. Since this happens in the AWS Core assembly, this penalty is incurred by all AWS service clients.

I hope this is something the AWS team fixes in the future. As a clueless layman, I would expect the endpoint definitions to belong to their respective packages. This would also produce a smaller Lambda deployment package as the current _AWSSDK.Core.dll_ assembly size is an eyewatering 1.5 MB!

## What's Next

In the next post, I'm benchmarking the impact of using top-level statements with Lambda functions. This new way of writing Lambda code is aesthetically pleasing, but does it have a hidden cost?