DEV Community

Cover image for Battle of the Titans (Part 1): The Ultimate Go Lambda on AWS Graviton
Aleh Karachun
Aleh Karachun

Posted on

Battle of the Titans (Part 1): The Ultimate Go Lambda on AWS Graviton

Hi everyone! Welcome to the first part of my series exploring AWS Lambda performance. My goal is to compare Go and .NET Native AOT in a realistic serverless environment.

To make this a fair benchmark, we aren't just deploying a "Hello World" function. Our Lambda simulates a standard combat task: it deserializes a JSON payload of financial transactions, filters them, calculates the total amount, and computes a SHA-256 hash of the IDs to generate a signature (simulating CPU load).

Today, we are focusing on setting up and optimizing the Go contender on ARM64 (Graviton).


1. The Infrastructure (AWS SAM)

We use AWS SAM (Serverless Application Model) to define our infrastructure. It allows us to describe resources declaratively and generates the underlying CloudFormation template.

Here is the core of our template.yaml:

CodeUri: bin/
Handler: bootstrap
Runtime: provided.al2023
Architectures:
  - arm64
Enter fullscreen mode Exit fullscreen mode

Key takeaways

  • Runtime: provided.al2023: Amazon Linux 2023 is currently the recommended minimalist OS for compiled languages in AWS. It boots significantly faster than the legacy go1.x runtime.

  • Architectures: arm64: Targeting AWS Graviton processors. They use a RISC architecture that typically provides around 20% better price/performance for serverless workloads compared to x86_64.

  • Handler: bootstrap: When using custom runtimes, AWS Lambda expects the executable binary inside the deployment package to be named exactly bootstrap.


2. Compiling for Lambda

A standard go build works, but we can optimize it further for the Lambda environment. Here is the command we use:

GOOS=linux GOARCH=arm64 go build \
  -tags lambda.norpc \
  -ldflags="-s -w" \
  -o bin/bootstrap main.go
Enter fullscreen mode Exit fullscreen mode

Key takeaways

  • GOOS=linux GOARCH=arm64: This enables cross-compilation, allowing us to build a Linux ARM64 binary directly from our local machine (even if it's x86).

  • -tags lambda.norpc: The al2023 runtime communicates with the Lambda service via an internal HTTP API. This tag tells the compiler to drop the legacy RPC compatibility code from the aws-lambda-go library, reducing the binary size and initialization time.

  • -ldflags="-s -w": These linker flags strip the symbol table and debug information, resulting in a leaner binary that loads into memory faster.


3. Local Testing and the "Error 255"

If you develop on an x86 (Intel/AMD) machine and try to test this locally using sam local invoke, you will likely hit a Fatal Error 255.

This happens because the Docker container spins up an ARM64 environment, but your host CPU cannot natively execute ARM instructions.

The fix: We need a translator. Running the multiarch/qemu-user-static Docker image solves this. QEMU intercepts the ARM commands and translates them into x86 instructions for your host CPU on the fly, allowing you to seamlessly test the production binary locally.


4. Anatomy of a Cold Start

When you run sam deploy --guided, AWS packages the binary, uploads it to S3, and updates the CloudFormation stack. But the most interesting part happens on the first invocation.

When we triggered the Lambda, CloudWatch reported an Init Duration of ~60 ms.

During these 54 milliseconds, AWS performed the following:

  1. Allocated a Graviton-based server.
  2. Provisioned an isolated Firecracker microVM.
  3. Downloaded the deployment zip from S3 and extracted it.
  4. Booted the provided.al2023 OS.
  5. Loaded our bootstrap binary into memory.

Once the environment was warm, subsequent invocations (Warm Starts) took roughly 2 ms of compute time with a memory footprint of about 19 MB.


Conclusion

Go on ARM64 with the AL2023 runtime provides an excellent baseline. With extremely low memory consumption and cold starts consistently under 60ms, it is a highly efficient choice for serverless APIs.

What’s Next?

In Part 2, we will set up our challenger: .NET 10 Native AOT. We will explore how to configure the C# project with Zero-Allocation techniques and Source Generators to see if it can match or beat Go's numbers.

The full source code for this setup is available in my GitHub repository:
πŸ‘‰ https://github.com/olegKarachun/aws-lambda-go-graviton

Top comments (0)