Swift on AWS Lambda Performs Like Node and Python

Swift on AWS Lambda Performs Like Node and Python

  • 1145

This article will go through several performance tests to understand the behavior of an AWS Lambda written in Swift using the framework Swift-Sprinter and compares it to popular languages and different configurations and options.

Performance tuning in a serverless environment is the key to keep your back end responsive and your bill low.

This article will go through several performance tests to understand the behavior of an AWS Lambda written in Swift using the framework Swift-Sprinter and compares it to popular languages and different configurations and options.

What Is a Lambda?

Lambda is the AWS resource providing Function as a Service (FAAS) computing power. It acts as the building block of a broader category of services, well-known as serverless.

Like the other serverless blocks, it has the following features:

  • It’s managed.
  • It’s autoscaling.
  • It’s highly available.
  • It’s pay-per-use.

In addition to the common serverless features, Lambda:

  • Contains the code required to run.
  • Is event-driven.
  • Supports multiple languages running on compatible Linux.
  • It runs for a maximum amount of time.

How AWS Lambda Works

The AWS Lambda architecture, based on Firecracker on top of an EC2 VM, has been built to be auto-scalable.

Cold start

The first time the Lambda is invoked, a new worker instance is prepared by copying, initializing, and running the code.

This process is called cold start and it happens every time the Lambda environment adds a new worker instance to scale up. Once the worker starts, it receives the event, processes it and then executes the request for the next event remaining in a wait status.

Warm start

Once the Lambda worker has been started, the next execution is pure processing of the upcoming event as there is no more need to set up the worker.

Eventually, after many minutes, the worker doesn’t receive any events, it is being disposed to scale down and free up unused computing resources.

Computing performance

Lambda computing performance depends on the language/runtime used, the code, and the provisioned amount of memory which also determines the power of the underlying CPU.

Memory — The amount of memory available to the function during execution. Choose an amount between 128 MB and 3,008 MB in 64 MB increments.

Lambda allocates CPU power linearly in proportion to the amount of memory configured. At 1,792 MB, a function has the equivalent of one full vCPU (one vCPU-second of credits per second).

To perform performance evaluation under the same conditions, all the Lambdas have been set up with 256 MB of memory. This configuration gives the Lambda 1/7 of vCPU.

Provisioned concurrency

To reduce the number of cold starts, it is possible to take advantage of a new feature called provisioned concurrency which keeps some Lambdas always warm.

How to Evaluate Performance

Lambda performances are really important as the service is sold as “pay-per-use”. Keeping the execution time and the memory amount usage low helps to reduce the on-going costs of AWS Lambda.

Performance of languages on AWS Lambda is defined by:

  • The size of the code to execute.
  • The amount of memory used.
  • The execution speed.

The execution of a simple “Hello, World” gives a rough idea of how the language performs on a very basic task and it’s quite accurate in evaluating the AWS Lambda runtime for the language.

Heavy computational tasks may dramatically change the results of the “Hello, World” benchmark as, in this case, the test will advantage the language efficiency in dealing with computational complexity rather than the AWS Lambda runtime.

For simplicity, the article will consider only the “Hello, World” benchmark leaving the task of proofing the efficiency for other workloads to the reader.

Code and Size

To compare the performances to Swift, the Hello World example has been implemented in different languages.

This is image title

In the table above are the configurations used for the test.

Swift vs. other languages

The first thing to notice is that AWS provides a runtime for Ruby, Node.js, Python, Java, Go, and .Net Core 2.1. This gives these languages an advantage as they don’t have the overhead of a custom runtime with additional code to load.

The Hello World example is pretty basic, the code size can be totally different as the code adds more dependencies.

The code used for Node.js, Python, Go, Ruby, Java, C#, and Rust is the same provided by the article Benchmarking AWS Lambda runtimes 2019 with the addition of the Swift examples.

Swift builds and runtimes

Many versions of the Hello World code in Swift have been added to compare the performances of:

This is image title

benchmark-swift-NIO-hello — SwiftSprinter with NIO AsyncHTTPClient

This is image title

benchmark-swift-curl-hello — SwiftSprinter with libCURL

This is image title

benchmark-al-ffett-hello — LambdaSwiftRuntime with NIO

Workload

The workload has been prepared as in the articles referenced at the end.

The tool Artillery has been used to test the Lambdas. An API gateway has been configured to publish the Lambdas as HTTPS endpoints.

This is image title

After the invocation, all the metrics are stored in AWS CloudWatch.

Cold Start Performances

The cold start performances have been gathered using the lumigo-CLI.

lumigo-cli analyze-lambda-cold-starts

This is image title

Cold Start performances

  • The best performance on cold start is given by Rust (~30ms) and Go (~75ms) followed by Ruby (124ms), Python (~127ms), and Node.js (~128).
  • A second group contains Swift built with Amazon Linux 2 (~190ms/~219ms) and C# .Net Core (~224ms).
  • The last group contains Swift built with Ubuntu (~329ms/~338ms) and Java (~358ms).
  • The difference between Swift built with Amazon Linux 2 and Ubuntu is about 20 MB of package size which pays around ~140 ms on cold start.

Warm Start Performances

To show the warm start performances, we used a custom dashboard on CloudWatch:

This is image title
Java/Go/Ruby

This is image title
Python/Rust/C#

This is image title
Swift Built with Amazon Linux 2

This is image title
C#/NodeJS/Swift built with Ubuntu

The best performance on average is given by C# .Net (~0.26 ms) and Go (~0.44 ms).

With performances of around 1 ms, we have Node.js (~1.06ms), Java (~1.16 ms), Rust (~1.17 ms), Swift with NIO and connection pooling built with Ubuntu (~1.19ms), Python (~1.34), Swift with NIO (~1.56 ms).

Swift build comparison

Why Should I Use Swift on AWS Lambda?

Swift was open-sourced in December 2015 by Apple and has grown from mobile development on iOS and Mac to Linux server platforms such as Ubuntu.

Anyone coming from iOS knows that the language is safe, fast, and expressive. Compared to scripting languages such as Node.js and Python, Swift enhances security and performance, avoiding malicious side-effects due to language undefined behavior and the lack of type checking.

Compared to relatively new languages such as Go or Rust, Swift adds the advantage of a large community of mobile developers and enthusiasts.

The investments of Apple on Swift NIO and the availability of the Swift Package Manager have fuelled the development of many server-side projects and libraries including HTTP Client, Postgres Client, Redis Client, AWS SDK Swift, MongoDB Swift Driver, gRPC Swift, … and more.

The Swift-Sprinter repository provides an AWS Lambda custom runtime for Swift and is based on Ubuntu to guarantee the build process and a well-defined pipeline. The repository contains examples of usage and continuous integration.

For anyone wanting to experiment, the repository Swift-Lambda-Runtime provides a version of Swift built on top of Amazon Linux 2.

The availability of Swift on serverless frameworks like AWS, OpenWhisk, and Azure, as well as traditional API/web frameworks such Vapor, Kitura, and Perfect on VMs, Docker, Kubernetes, and on-premise HW has opened the road to full-stack Swift development.

This article doesn’t cover the choice of a specific cloud provider as this would depend on different business reasons rather than technical considerations.

The advantage of using Swift on AWS Lambda rather than containers is effective when the application built with it doesn’t have a constant workload and requires to be scalable while keeping costs low. Serverless and containers are not silver bullets and the choice needs to be evaluated per use case.

Considerations

The AWS Lambda runtime choice doesn’t depend only on performance considerations.

It’s always important to consider the total cost of ownership of a solution, taking into account not only the bare cost of the serverless but also reliability, maintainability, resilience, reusability, security, platform support, availability of third-party frameworks, developer community maturity, development tools, licenses, availability of development skills, and learning curve regarding the specific use case.