Amazon has recently released ARM64 support for Lambda based on Graviton2 processors. They claim that the new architecture provides better price performance. Let’s take a look how it stands up against the classic x64 CPU in some real-world scenarios.

Test Environment

We’re going to use Node.js 14 inside Docker as the runtime for test functions of various sizes: from 128 MB to 10240 MB, each one twice as big as the previous. The infrastructure will be managed with Pulumi. The source code is available on GitHub.

lambda.ts
  • typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (const arch of ['x86_64', 'arm64']) {
for (let i = 7; i <= 14; i++) {
const size = Math.min(2 ** i, 10240);
const name = `${project}-${stack}-${arch}-${size}`;

new aws.lambda.Function(name, {
name: name,
architectures: arch,
memorySize: size,
timeout: 60,
role: role.arn,
description: `${project}-${stack} - Worker (${arch} ${size} MB).`,
imageUri: pulumi.interpolate`${account}.dkr.ecr.${region}.amazonaws.com/${repository.name}:latest`,
packageType: 'Image',
});
}
}

All function will run benchmarks that last 100 seconds and executed twice: the first run is intended to warm up the environment, the second provides the final results.

Express.js Benchmark

The first benchmark uses a simple Express.js server that handles requests. The server is invoked from within the same function.

function.ts
  • typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const app = express();

app.post('/', (req, res) => {
res.json(req.body);
});

const server = app.listen();

benny.suite('Lambda benchmark',
benny.add('express', () => {
const runner = async () => {
await axios.post(`http://localhost:${server.address()!['port']}/`, {});
};

return runner;
}, {
maxTime: 10,
}),
benny.cycle(),
);

The results are quite interesting:

As you can see, both x64 and ARM64 have a similar performance in functions up to 512 MB. As you might know, Lambda’s CPU performance and core count scales up alongside the allocated RAM. Both architectures peak at around 2048 MB, with x64 leading the race. The performance starts to decline after 4096 MB, which could be an indication that high-memory functions have more CPU cores in exchange for a lower performance per core. Here’s the distribution of the number of CPU cores:

Because this benchmark does not rely on asynchronous code or clustering, there is no gain from extra CPU cores.

Since ARM64 has a lower price than x64, let’s compare the two in terms of price performance:

Just as expected, ARM64 has up to a 20% lower cost per operation than x64. The largest gap is within 256 and 512 MB range, which is often used for APIs and other Express.js scenarios, so it’s worth checking out if your Lambda web services can be migrated to ARM64 to get a cost reduce.

Native Code Benchmark

In the second benchmark we’re going to use Sharp.js - a Node.js binding to image processing library libvips, which is available for both x64 and ARM64. The benchmark scales down a 1280x800 image to 640x400 and saves it as a JPEG file.

function.ts
  • typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sharp.cache(false);
sharp.concurrency(os.cpus().length);

benny.suite('Lambda benchmark',
benny.add('sharp', () => {
const runner = async () => {
await sharp('./image.jpeg')
.resize(640, 400)
.jpeg({ quality: 80 })
.toBuffer();
};

return runner;
}, {
maxTime: 10,
}),
benny.cycle(),
);

The results of this benchmark are unexpected:

Both CPUs perform on on par with each other up until 2048 MB, after which ARM64 breaks ahead. This could be just a result of a better performance of libvips on ARM64. Let’s see what is the price of the two:

The difference in performance price is more profound in this benchmark, with ARM64 being up to 27% less expensive than x64. This makes ARM64 an attractive alternative for computationally intensive functions.

Conclusion

Migrating your Lambda functions from x64 to ARM64 can give you a noticeable improvement in price performance. Of course, your mileage may vary, so you should consider your own research before making any assumptions.