AWS Lambda and .NET 8: Enhancing Serverless Performance with Native AOT

Let's quickly review what's new and what you need to know when building your Lambda Functions on .NET 8.

Rahul Pulikkot Nath
Rahul Pulikkot Nath

Table of Contents

It's been a while since AWS Lambda supports .NET 8.

Let's quickly review what's new and what you need to keep in mind when building your Lambda Functions on .NET 8.

If you are new to AWS Lambda, I highly recommend getting started here 👇

AWS Lambda For The .NET Developer
This blog post is a collection of other posts that covers various aspects of AWS Lambda and other services you can integrate with when building serverless applications on Lambda.

What's New in AWS Lambda and .NET 8?

Some of the key updates with AWS Lambda on .NET 8 are listed below.

You can find more details on them in the official documentation here.

  • Upgraded OS
    .NET 8 runtime is built on the minimal container image of Amazon Linux 2023 (AL2023). This makes it a smaller deployment footprint and also updated versions of common libraries such as glibc 2.34 and OpenSSL 3.
  • Performance
    Lambda has increased the default memory size in the blueprints and templates from 256 MB to 512 MB for improved performance with .NET 8.
  • Native AOT
    Allows .NET applications to be pre-compiled to a single binary removing the need for just-in-time (JIT) compilation, enabling native AOT enabled apps to start up faster.
With Native AOT, Benchmarks show up to 86% improvement in cold start times by eliminating the JIT compilation in the blueprints and templates from 256 MB to 512 MB.

Building Your First .NET 8 AWS Lambda Function

The AWS Toolkit has been updated to create .NET 8 Lambda functions, which makes it easy to create new projects.

It supports different blueprints that you can choose to create your Lambda function. Below is the sample code for an Empty Lambda Function, which generates a simple function that converts a string input to upper case.

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace hello_dotnet8_lambda;

public class Function
{
    public string FunctionHandler(string input, ILambdaContext context)
    {
        return input.ToUpper();
    }
}

You can also use the Mock Lambda test tool to run and test your functions locally.

AWS Lambda and Native AOT with .NET

The Toolkit now lists the Native AOT (.NET 8) blueprint type, to create a AOT supported Lambda Function.

AWS Lambda Function Native AOT project template in Visual Studio for .NET 8.

This sets up the Lambda Function to be compiled as Native AOT.

In this case, the Function code is slightly different, using the LambdaBootstrapBuilder class to bootstrap the lambda runtime.

The FunctionHandler is passed on to the bootstrap code and is executed at runtime.

public class Function
{
    private static async Task Main()
    {
        Func<string, ILambdaContext, string> handler = FunctionHandler;
        await LambdaBootstrapBuilder.Create(
                handler, 
                new SourceGeneratorLambdaJsonSerializer<LambdaFunctionJsonSerializerContext>())
            .Build()
            .RunAsync();
    }

    public static string FunctionHandler(string input, ILambdaContext context)
    {
        return input.ToUpper();
    }
}

The csproj file also has some additional flags set up for Native AOT support. This sets up the Native AOT compile settings for the project file.

<AWSProjectType>Lambda</AWSProjectType>
<!-- Set build directory like publish to help Lambda tool find dependencies -->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<!-- Enable Native AOT for better cold start time -->
<PublishAot>true</PublishAot>
<!-- Strip debug symbols to reduce executable size -->
<StripSymbols>true</StripSymbols>
<!-- Trim only trimmable assemblies to reduce size -->
<TrimMode>partial</TrimMode>

To compile Lambda function using the Native AOT feature, you need to have docker setup.

To use native AOT, your function code must be compiled in an environment with the same AL2023 operating system as the .NET 8 runtime. The .NET CLI commands in the following sections use Docker to develop and build Lambda functions in an AL2023 environment.

Publish Lambda Function using .NET 8 native AOT uses docker to generate the deployable packages.

When publishing from Visual Studio, you can see it using docker to compile the Lambda function.

Performance Improvements with .NET 8 Lambda Functions

In AWS Lambda, the Init Duration represents the time taken to initialize the function's runtime environment, which occurs only during a cold start—when a new instance of the Lambda function is created

REPORT RequestId: 5c7c1727-b828-414f-909f-0b6a7c3b1daa Duration: 34.35 ms Billed Duration: 35 ms Memory Size: 512 MB Max Memory Used: 32 MB Init Duration: 109.89 ms

In the example log, an Init Duration of 109.89 ms, which is significantly lower than a Lambda Function that uses JIT compilation.

You can find the Lambda Init duration in CloudWatch logs, every time a new instance of the function is created. If that entry is missing, it means you are reusing a Lambda instance.

Why Should You Care About Lambda Lifecycle As A .NET Developer?
The Lambda Lifecycle affects the way we write out Function code. Learn some of the dos and don’ts when building Lambda Functions in .NET because of how Lambda initializes the Function classes.
AWS