Paul BradleySolutions Architect & Software Developer


Published:

Go - Recompiling Lambda Functions for the ARM/Graviton Architecture

How to switch your Go Lambda Functions from x86 to the ARM Graviton2 processor and benefit from a 34% price reduction

midjourney ai - ubuntu, high resolution, 8k, unreal engine, cinematic, golden hour

Table of Contents
  1. Introduction
  2. Go Code Compilation for the ARM64 Architecture
  3. Terraform Provisioning of the Lambda Functions

Introduction

In September 2021 AWS announced the availabity of running Lambda functions with the ARM/Graviton2 processor. In their announcement blog post it states that Lambda functions powered by Graviton2 are designed to deliver up to 19 percent better performance at 20 percent lower cost.

In addition to the price reduction, functions using the Arm architecture benefit from the performance and security built into the Graviton2 processor. On the whole, you can get better performance for web and mobile backends, microservices, and data processing systems.

At work I design, develop and run a large web application using Lambda functions. The application gets around two million invocations a month. This article covers how I migrated the application from using the x86 architecture to using the ARM/Graviton2 processor.

Go Code Compilation for the ARM64 Architecture

I first had to re-compile each of my functions so that they targeted the ARM64 architecture. This was done by changing the GOARCH environment variable.

AWS is in the process of shifting newer Lambda runtimes to use an Amazon Linux 2 (AL2) execution environment. All languages with a native runtime have an AL2-based runtime with the exception of Go. Instead of using the Go1.x execution environment, I’ve switched to using the AL2 variant of the custom runtime called provided.al2.

By using the custom AL2 runtime, we can also remove the RPC logic from the finished binary. The aws-lambda-go library provides a build tag called lambda.norpc to remove all RPC logic, reducing the compiled binary file size. The size of the binary file size contributes to Lambda cold-start time, so any reduction we make will help improve this metric.

I also use the ldflags to turn off DWARF debugging information -w and disable the generation of the Go symbol table -s. Disabling these options shouldn’t affect the execution of your program. They only affect whether you can debug or analyze the program with other tools. Removing the debugging information in this way also helps to further reduce the file size.

GOARCH=arm64 GOOS=linux \
    go build -tags="lambda.norpc" \
             -ldflags="-w -s" \
             -o ./bootstrap *.go

upx -9 bootstrap
zip -9 main.zip bootstrap

Once the Go compiler has produced the binary file bootstrap, then I run the compression program upx which typically reduces the binary by a further 40+%. Finally, the binary is packaged into a ZIP file called main.zip for deployment to AWS.

Terraform Provisioning of the Lambda Functions

The Terraform code snippet below, highlights the three values that I had to change within my Lambda function definition. These are the handler, runtime and the architectures values.

resource "aws_lambda_function" "homepage" {

    function_name = "${var.name_prefix}homepage"
    description   = "responds to the homepage endpoint /"
    package_type  = "Zip"
    filename      = "/home/lpres-viewer-homepage/main.zip"

    // the values required to invoke the binary
    // in an AL2 execution environment using the
    // ARM64 Graviton2 processor.
    handler       = "bootstrap"
    runtime       = "provided.al2"
    architectures = ["arm64"]

    role          = var.lambda_role_arn
    ...
}