AWS Lambda Custom Runtimesを利用してFortranで数値計算 #reinvent

この記事はAWS Lambda Custom Runtimes芸人 Advent Calendar 2018の3日目です。

はじめに

ふと、連立一次方程式が解きたくなることってありますよね?(異論は認めない)

それ、Lambdaでできますよ!

・・・ということで、re:Invent 2018で発表されたAWS Lambda Custom Runtimesを利用して、LambdaでFortranを動かしてみます。 Custom Runtimesについては以下の記事もご参照ください。

【アップデート】 もう言語で悩まない!AWS LambdaでCustom Runtimeが利用できるようになりました! #reinvent

実行方法

ソースコードはこちらです。

検証環境

macOS (MacBook Pro)で実行しています。ビルドにDockerを利用していますので、Dockerのインストールが必要です。

  • macOS 10.13.6
  • Docker 18.09.0 (Docker Desktop 2.0.0.0-mac78)

ビルド

makeすると、Lambda関数のパッケージ linear_eq.zip ができます。

$ make
$ ls -l
total 3712
-rw-r--r--  1 yamashita.katsumi  staff      748 12  3 09:39 Dockerfile
-rw-r--r--  1 yamashita.katsumi  staff      448 12  3 09:39 Makefile
-rw-r--r--  1 yamashita.katsumi  staff       27 11 30 17:18 README.md
-rwxr-xr-x  1 yamashita.katsumi  staff      603 12  2 20:18 bootstrap
-rw-r--r--  1 yamashita.katsumi  staff     1708 12  3 01:31 json_matrix.f90
-rwxr-xr-x  1 yamashita.katsumi  staff  1329656 12  3 09:42 linear_eq
-rw-r--r--  1 yamashita.katsumi  staff      645 12  3 09:39 linear_eq.f90
-rw-r--r--  1 yamashita.katsumi  staff   544667 12  3 09:42 linear_eq.zip

実行

まずは関数を作成します。作成方法は1日目の記事と同じですが、以下だけ変える必要があります。

  • 関数コードのzipファイルに上記でビルドしたlinear_eq.zipを指定
  • ハンドラを"fortran.linear_eq"とする

関数ができたらテストイベントを作成します。連立一次方程式 Ax=b の行列AとベクトルbをJSON形式で指定します。

上記の例は以下のような連立一次方程式を意味します。

\[ \begin{equation} \left( \begin{array}{rr} 1 & 3 \\ 2 & 4 \end{array} \right) \left( \begin{array}{l} x_1 \\ x_2 \end{array} \right) = \left( \begin{array}{r} 7 \\ 10 \end{array} \right) \end{equation} \]

実行すると、以下のように答え x1 = 1, x2 = 2 が出力されました。上手く動きましたね。

コード解説

起動の仕組み

基本的な仕組みは公式ドキュメントに記載されています。 実行すると、Lambdaはまず"bootstrap"という名前のスクリプトを探し、存在すればそれを実行します。

したがって、Fortranのようにコンパイルして実行バイナリを作る言語では、bootstrapスクリプトと実行バイナリをzipに入れておいて、bootstrapから実行バイナリを叩けば簡単に実行することができます。以下は今回利用したbootstrapスクリプトです。 ほぼ公式チュートリアルのものと変わりませんが、実行バイナリの名前をハンドラ(環境変数$_HANDLER)から指定するようにしています。

#!/bin/sh
 
set -euo pipefail

# 実行バイナリ名
# ハンドラが "fortran.実行バイナリ名" という想定で、ドット以下の文字列を取得する
LM=$(echo "$_HANDLER" | cut -d. -f2)

# Processing
while true; do
  HEADERS="$(mktemp)"
  # Get an event
  EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
  REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)
 
  # Execute the handler function from the script
  RESPONSE="$(./$LM "$EVENT_DATA")"
  echo $RESPONSE
 
  # Send the response
  curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" -d "$RESPONSE"
done

共有ライブラリの扱い

ただし、Fortranのような言語では、普通にコンパイル・リンクしたのでは共有ライブラリは別になってしまうため、 何らかの方法で関数パッケージにライブラリを含める必要があります。

もっとも簡単な方法は、全てスタティックリンクしてしまう方法です。今回はこの方法でやりました。

RUN gfortran -static -o linear_eq linear_eq.o json_matrix.o /usr/local/jsonfortran-gnu-6.10.0/lib/libjsonfortran.a -llapack -lblas

スタティックリンクせずとも、共有ライブラリのファイルをパッケージに含めて実行時にそこを参照できるようにしてもOKです。 ざっと見ただけですがawslabsのC++カスタムランタイムはそのようにしているようです。

Go言語だと基本的には全てスタティックリンクされるので、このあたりを気にしなくて済むのが楽ですね。

その他Fortran特有のこと

  • 連立一次方程式を解くために、古来よりこの業界で使われているLAPACKを使用しています。このような古き良きライブラリが揃っていることがFortranを利用するメリットだ、とFortran使いは信じています *1
  • 自分が知っているFortranにはJSONなどというモダンなものは存在しないのですが、今回は簡単のため入力データをJSONにしています。 FortranでJSONをパースするため、JSON-Fortranという素敵なライブラリを使いました *2

まとめ

AWS Lambda Custom RuntimesでFortranを動かしてみました。 実際にLambdaでFortranを使う方はおそらくほとんどいないとは思いますが、他の言語にしても、共有ライブラリさえどうにかできればだいたい何でも実行できるはず、ということが伝われば幸いです。

自分はFortran歴が一番長いので、こういうネタの時はついFortranを使ってみたくなってしまうのですが、 今回やってみて改めて時代とかけ離れた言語環境であることを痛感しています。あくまでもネタにとどめておくことをお勧めします。

脚注

  1. もちろん、現代ではNumPyとかGSLとかいくらでも他に選択肢はあります。
  2. より古来のFortranチックにやるなら、バイナリデータをS3においてごにょごにょ、とかいう感じになるでしょうか・・・。