Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

AWS Lambdaの処理性能を言語毎に測ってみた

こんにちは、@です。

この間AWS Summit Tokyoに参加してきたのですが、皆一様に「AWS Lambda」を、
これからのサーバレスアーキテクチャを支える技術として紹介していましたね。

資料でも言葉でも多分に見聞きしており、軽いゲシュタルト崩壊を起こしている今日この頃、
皆さんはいかがお過ごしでしょうか。

さて、今回はAWS Lambdaです。

AWS Lambdaの処理はJavaやNode.js、Pythonなどの言語で記述することができますが、その性能差がどの程度あるのか?測ってみました。

構成

今回の構成は次の様なシンプルなものにしています。

[計測対象(言語)]

  1. Python
  2. Node.js
  3. Java

[計測対象(カテゴリ)]

  1. 処理速度
  2. 使用メモリ

[Lambdaでの処理内容]

  1. API Gatewayでリクエストを受け付け
  2. Lambda内でDynamoDBから1件データを取り出す
  3. 取り出したデータをログに出力する

図で示すとこんな感じです。

f:id:acro-engineer:20160714104306p:plain

[計測条件]

  1. 次に示すサンプルコードをそれぞれLambdaで定義して実行する。
  2. クライアントにはApache JMeterを利用。
  3. 各言語とも200回計測し、それぞれ100、90、80、50パーセンタイル値、最小値(もっとも速い/軽い)を取る。
  4. メモリは192MB指定。(Javaが192MBでないと動かないので、他も合わせてスペックアップ)

[サンプルコード]

各言語のソースコードは次の通りです。

[Python]

#!/usr/bin/python2.7
# -*- coding:utf-8 -*-
# Python
import boto3
import logging
from boto3.dynamodb.conditions import Key

logger = logging.getLogger()
logger.setLevel(logging.INFO)

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('mytest')

def handle_request(event, context):
    response = table.query(
        KeyConditionExpression=Key('id').eq('test')
    )
    logger.info(response)
    logger.info("SUCCESS")

[Node.js]

// Node.js
var AWS = require('aws-sdk');
var dynamodb = new AWS.DynamoDB();

exports.handler = function(event, context) {
    var params = {
        Key: {id: {"S": "test"}},
        TableName: "mytest"
    };

    dynamodb.getItem(params, function (err, data) {
        if (err) {
            console.log(err);
            context.fail('Failed');
        } else {
            console.log(data);
            context.succeed('Succeeded');
        }
    });
};

[Java]

package jp.co.acroquest.aws.bench;

import com.amazonaws.auth.EnvironmentVariableCredentialsProvider;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;

public class DynamoDBBench implements RequestHandler<Object, Object> {
	AmazonDynamoDBClient client;
	Table table;
	DynamoDB dynamoDB;

	public DynamoDBBench() {
		client = new AmazonDynamoDBClient(
				new EnvironmentVariableCredentialsProvider());
		client.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1));
		dynamoDB = new DynamoDB(client);
		table = dynamoDB.getTable("mytest");
	}

	public String handleRequest(Object input, Context context) {
		LambdaLogger logger = context.getLogger();
		Item item = table.getItem("id", "test");
		if (item == null) {
			logger.log("Item is null.");
			return "Failure";
		}
		logger.log("Item obtained: " + item);
		return "Success";
	}
}

結果

200回計測した結果の値を取ってみたところ、このようになりました。

[100パーセンタイル(最大)]
f:id:acro-engineer:20160718084625p:plain

[90パーセンタイル]
f:id:acro-engineer:20160718084624p:plain

[80パーセンタイル]
f:id:acro-engineer:20160718084623p:plain

[50パーセンタイル]
f:id:acro-engineer:20160718084622p:plain

[最小]
f:id:acro-engineer:20160718084626p:plain

  • Javaは初回アクセスからしばらくの間、オーバーヘッドが大きいことがわかります。
  • 使用メモリは予想通りJavaが一番大きく、PythonとNode.jsはほぼ同程度の使用率であることがわかります。
  • 実行回数を重ねるごとに、Javaのパフォーマンスが上がっていることがわかります。
  • 処理時間が最小の結果では、Javaが他の言語に追いついていることがわかります。

今回の結果の生データは次のリンクに置いておきました。

taste-of-tech-topics/measure_result.xlsx at master · kojiisd/taste-of-tech-topics · GitHub

Java性能のパフォーマンス向上における確認実験

ちなみに回数を重ねるごとにJavaの性能が大きく上がりはするものの、実行開始時の処理時間がかかっている部分をもう少し詳しく知るために、
AWS Lambda上でフィボナッチ数列を求める計算を簡易的にですが各言語毎に調べてみました。今回は40番目の数値を求める計算をしました。

たった5回の計測でしたが、次の様な平均値が取れました。Javaが他言語に比べてオーバーヘッドなど関係なく圧倒的に早いことがわかります。
Pythonは2分のタイムアウト設定を設けてもタイムアウトしてしまったので、今回はそれ以上は計測していません。

Java Node.js Python
平均処理時間[ms] 4308.3 12387.0 2分以上

個人的には(シンプルな実装なので最適とは言えないが)V8のJavaScriptエンジンを搭載しているNode.jsの3倍もの性能をJavaが出せたのは意外でした。
以上の結果から、今回のDynamoDBにアクセスして値を取得してくる処理に関しては、Javaライブラリの読み込みに時間がかかってしまっており、実行を繰り返す中で最適化がされていったため、初動は遅いが繰り返すたびにJavaの性能が上がってきた、と推測することはできそうです。

今回のフィボナッチ数列計算を試した際のコードは以下になります。

[Java]

package jp.co.acroquest.aws.bench;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;

public class Fibonacci {
	private static final int INDEX = 40;
	LambdaLogger logger;

	public String handleRequest(Object input, Context context) {
		logger = context.getLogger();
		long calcResult;
		calcResult = calcFib(INDEX);
		logger.log("result: " + calcResult + "\n");
		return "Success";
	}

	public long calcFib(int number) {
		if (number == 1 || number == 2)
			return 1;
		return calcFib(number - 2) + calcFib(number - 1);
	}

}

[Node.js]

exports.handler = (event, context, callback) => {
    var result = calcFib(40);
    context.succeed('result: ' + result)
    context.succeed('Success');
};

function calcFib(num) {
  if(num == 1 || num == 2) {
    return 1;
  }
  return calcFib(num - 1) + calcFib(num - 2);
}

[Python]

import logging
def lambda_handler(event, context):
    result = calcFib(40)
    logging.info('result: ' + result)
    return 'Success'

def calcFib(num):
  if num <= 2:
    return 1
  else :
    return calcFib(num - 1) + calcFib(num - 2)

言語毎の実行ログは以下になります。

[Java]

REPORT RequestId: b0afcdb2-5715-11e6-8d3c-537f664ac4cd	Duration: 4626.36 ms	Billed Duration: 4700 ms Memory Size: 128 MB	Max Memory Used: 34 MB	 
REPORT RequestId: b77533b9-5715-11e6-8d3c-537f664ac4cd	Duration: 4211.39 ms	Billed Duration: 4300 ms Memory Size: 128 MB	Max Memory Used: 34 MB	 
REPORT RequestId: bc0c9611-5715-11e6-8d14-ed04d7d6b375	Duration: 4239.18 ms	Billed Duration: 4300 ms Memory Size: 128 MB	Max Memory Used: 34 MB	 
REPORT RequestId: c2ecb0b3-5715-11e6-ac28-fd403fb9707b	Duration: 4229.15 ms	Billed Duration: 4300 ms Memory Size: 128 MB	Max Memory Used: 34 MB	 
REPORT RequestId: c8944a4a-5715-11e6-b5a7-bb8b3c4b93fe	Duration: 4235.18 ms	Billed Duration: 4300 ms Memory Size: 128 MB	Max Memory Used: 34 MB	

[Node.js]

REPORT RequestId: e3125256-5714-11e6-a4f7-85ff9fd7c6c7	Duration: 12458.36 ms	Billed Duration: 12500 ms Memory Size: 128 MB	Max Memory Used: 16 MB	 
REPORT RequestId: 04db011d-5715-11e6-b7c7-15170a43b8ce	Duration: 12367.85 ms	Billed Duration: 12400 ms Memory Size: 128 MB	Max Memory Used: 16 MB	 
REPORT RequestId: 72e8fccb-5715-11e6-9523-3f458c891180	Duration: 12367.47 ms	Billed Duration: 12400 ms Memory Size: 128 MB	Max Memory Used: 16 MB	 
REPORT RequestId: 7c31b1ac-5715-11e6-8ff0-2d80e2cd4b43	Duration: 12374.82 ms	Billed Duration: 12400 ms Memory Size: 128 MB	Max Memory Used: 16 MB	 
REPORT RequestId: 842b529d-5715-11e6-ad82-7103149e1efd	Duration: 12366.68 ms	Billed Duration: 12400 ms Memory Size: 128 MB	Max Memory Used: 16 MB	

[Python]

REPORT RequestId: 474c2f4b-571a-11e6-8a89-4bca1cc5593f	Duration: 120000.15 ms	Billed Duration: 120000 ms Memory Size: 128 MB	Max Memory Used: 15 MB	 
2016-07-31T12:30:25.971Z 474c2f4b-571a-11e6-8a89-4bca1cc5593f Task timed out after 120.00 seconds

考察

今までの実験を踏まえると、AWS Lambdaで言語を選択する際には、次のようなことを考えるとよいのかと思いました。

  • 継続的にアクセスされず、かつCPUもあまり使わない処理であれば、Pythonが速い。
  • 継続的にアクセスされ、かつCPUをあまり使わない処理であれば、どの言語も大差ない。
  • CPUを大量に使うのであれば、Javaが速い。

実際には上記に加えて書き慣れている言語であるかなど開発者側のスキルなども多分に関係すると思いますが、一旦結果のみから判断しようとすると、こうなるのかな、という感じです。


今回はシーケンシャルかつシングルスレッドで処理されるプログラムで実験をしましたが、複数同時リクエストの場合や、内部で複数スレッドを使って処理をするような場合での性能比較も、どこかで実験してみたいと思います。

それでは!

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、AWSHadoop、Storm、NoSQL、Elasticsearch、SpringBoot、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 
データ分析で国内に新規市場を生み出す新サービス開発者WANTED! - Acroquest Technology株式会社の新卒・インターンシップ - Wantedlywww.wantedly.com