Baseline Performance for AWS Lambda .NET using Top-Level Statements
Steve Bjorg
Posted on October 12, 2022
.NET 6 introduced top-level statements, which simplify the entry point of the application code. Unlike the previous style of Lambda definitions, this project creates an executable instead of an assembly. That means we have to provide our own Lambda host implementation. Fortunately, AWS already provides one in the Amazon.Lambda.RuntimeSupport package.
Minimal Top-Level Lambda Function
The MinimalTopLevel project defined a Lambda function that takes a stream and returns an empty response. It has no business logic and only includes required libraries. There is also no deserialization of a payload. This is the Lambda function using top-level statements with the least amount of overhead.
using Amazon.Lambda.Core;
using Amazon.Lambda.RuntimeSupport;
using Amazon.Lambda.Serialization.SystemTextJson;
await LambdaBootstrapBuilder.Create(Handler, new DefaultLambdaJsonSerializer())
.Build()
.RunAsync();
Task Handler(Stream request, ILambdaContext context)
=> Task.CompletedTask;
Benchmark Data for .NET 6 on x86-64
Again, we see that the duration of the INIT phase is not impacted until we exceed the 3,008 MB threshold, which also drives up cost.
However, compared to the Minimal baseline project, this Lambda function has 20% to 100% longer cold start durations.
Memory Size | Init | Cold Used | Total Cold Start | Total Warm Used (100) | Cost (µ$) |
---|---|---|---|---|---|
128MB | 235.420 | 1,484.898 | 1,720.318 | 367.174 | 24.05849 |
256MB | 236.617 | 744.375 | 980.992 | 151.328 | 23.93210 |
512MB | 236.002 | 353.587 | 589.589 | 120.821 | 24.15341 |
1024MB | 238.403 | 163.425 | 401.828 | 115.392 | 24.84696 |
1769MB | 234.304 | 96.894 | 331.198 | 115.843 | 26.32520 |
5120MB | 216.870 | 92.632 | 309.502 | 117.367 | 37.69996 |
Minimum Cold Start Duration
Fortunately, the situation improves quite a bit once we look at the optimal configuration for minimum cold start duration. Using top-level statements shift some of the overhead from the INIT phase to the first INVOKE phase, but otherwise the total duration is very close to what was measured for the Minimal baseline project.
Architecture | Memory Size | Tiered | Ready2Run | PreJIT | Init | Cold Used | Total Cold Start |
---|---|---|---|---|---|---|---|
arm64 | 5120MB | yes | yes | no | 178.743 | 70.947 | 249.690 |
x86_64 | 5120MB | yes | no | no | 190.382 | 67.500 | 257.882 |
x86_64 | 1769MB | yes | yes | no | 189.152 | 58.625 | 247.777 |
x86_64 | 5120MB | yes | yes | no | 176.368 | 57.039 | 233.407 |
Minimum Execution Cost
Again, the ARM64 architecture is the most cost-effective approach. ReadyToRun, Tiered Compilation, and the PreJIT settings all contribute to reduce cost a bit further for the minimal top-level project. That said, the minimum execution cost is ~8.5% higher when using top-level statements. This increased cost is most likely due to the higher memory configuration, which is required to compensate for the increased overhead of the INIT and first INVOKE phases.
Architecture | Memory Size | Tiered | Ready2Run | PreJIT | Init | Cold Used | Total Warm Used (100) | Cost (µ$) |
---|---|---|---|---|---|---|---|---|
arm64 | 1024MB | no | yes | no | 234.381 | 168.534 | 122.581 | 24.08155 |
arm64 | 1024MB | no | yes | yes | 252.593 | 167.558 | 124.272 | 24.09109 |
arm64 | 1024MB | yes | yes | no | 202.952 | 124.406 | 152.314 | 23.88962 |
arm64 | 1024MB | yes | yes | yes | 218.905 | 118.831 | 152.727 | 23.82079 |
What's Next
It's been fun diving into the fundamentals of AWS Lambda for .NET functions. The post is summarizing my findings and thoughts on future projects that might be interesting to explore.
Posted on October 12, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.