Skip to main content

AWS Lambda vs Windmill

This benchmark compares the performance of AWS Lambda and Windmill for executing a simple but computationally intensive task - calculating the 33rd Fibonacci number using a recursive algorithm.

We chose this task because:

  1. It's CPU-bound rather than I/O-bound, making it a good test of raw compute performance
  2. The recursive implementation creates significant computational overhead
  3. It's simple enough to implement identically in both platforms

The goal is to measure and compare:

  • Cold start latency
  • Warm execution time
  • Consistency of response times
  • Cost implications at scale

Both services were configured similarly, using Python 3.11 as the runtime. The same Fibonacci calculation code was deployed to each platform to ensure a fair comparison.

Setup

Windmill

The setup is the exact same as for the other benchmarks in Airflow vs Prefect vs Temporal vs Kestra vs Windmill. We used the same EC2 m4-large instance and deployed Windmill on docker using the docker-compose.yml in Windmill's official GitHub repo (with the same adjustment, i.e. 1 worker only, even though for this use case it would not make a difference).

We created a script in Windmill computing a Fibonacci number in Python:


N_FIBO = 33

# WIMDMILL script: `u/benchmarkuser/fibo_script`
def fibo(n: int):
if n <= 1:
return n
else:
return fibo(n - 1) + fibo(n - 2)

def main():
return fibo(N_FIBO)

which we called multiple times using its webhook. We used siege benchmark tool to trigger the jobs multiple times using its webhook:

siege -r500 -c1 -v -H "Cookie: token=$WM_TOKEN" "http://$WM_HOST/api/w/benchmarks/jobs/run_wait_result/p/u%2Fbenchmarksuser%2Ffibo_script"

AWS Lambda

We set up a Lambda running Python 3.11 with the following simple script:

import json

N_FIBO = 33

def fibo(n: int):
if n <= 1:
return n
else:
return fibo(n - 1) + fibo(n - 2)

def lambda_handler(event, context):
result = fibo(N_FIBO)
return {
'statusCode': 200,
'body': json.dumps(result)
}

We gave the Lambda 2048MB of memory, but according to the logs the memory never exceeded 50MB. On AWS, vCPU is proportional to the memory so we can assume it got a decent vCPU.

We exposed a trigger through AWS API Gateway and from our EC2 instance, we called it using the same siege benchmark tool:

siege -r500 -c1 -v -H "x-api-key: $AWS_API_KEY" "https://$AWS_LAMBDA_HOST/default/fibo_lambda"

Results

In the same vein as the other benchmarks, we ran fibonacci(10) 500 times (--reps=500 as siege argument) and fibonacci(33) 100 times.

The results were the following:

(in seconds)# repsAWS Lambda (sec)Windmill
Normal (sec)
Windmill
Dedicated Worker (sec)
fibo(10)50036.5655.3626.81
fibo(33)10093.95109.06104.5

Which gives an average duration per job in milliseconds:

(in milliseconds)AWS Lambda (sec)Windmill
Normal (sec)
Windmill
Dedicated Worker (sec)
fibo(10)7311154
fibo(33)94010911045

Visually, we have the following graphs:

Conclusion

For running a high number of lightweight tasks (fibonacci(10)), we can see that Windmill in dedicated worker mode is the fastest. Windmill in normal mode is slower because it runs a cold start for each task. For long running tasks (fibonacci(33)), Windmill in normal mode and dedicated worker mode is almost equivalent because the warm-up time needed in normal mode is negligible compared to the duration of tasks. AWS Lambda has slightly better performance for those kind of tasks, likely because it is able to run the core of the Python logic faster than Windmill.