Pythonコンパイラ「Codon」をRaspberry Piで利用したい。
ただ、install.shではインストールに失敗するため、環境構築方法を教えて欲しい。
こんなお悩みを解決します。
先日、Pythonのコードをマシンコードにコンパイルするコンパイラ「Codon」が発表[論文]されました。
CodonによりPythonスクリプトをコンパイルして実行することで、通常のPythonよりも10倍から100倍速く実行でき、これはCやC++に匹敵します。
Pythonのコーディングのしやすさを保ちつつ、処理時間を短縮できるため、今後、注目すべきツールとなります。
今回は、Raspberry Pi上にDockerを用いたCodonの実行環境を構築する方法について解説します。
手元の環境ですぐに再現できるように、一通りファイルを用意しているので、興味がある方はぜひご覧ください。
Codonとは?
Codonは、公式サイトで以下のように説明されています。
Codon is a high-performance Python compiler that compiles Python code to native machine code without any runtime overhead. Typical speedups over Python are on the order of 100x or more, on a single thread. Codon supports native multithreading which can lead to speedups many times higher still.
https://docs.exaloop.io/codon/
ざっと訳すと以下のようになります。
Codonとは
Codonは、高パフォーマンスPythonコンパイラーで、実行時のオーバーヘッドなしにPythonコードをネイティブマシンコードにコンパイルする。
Pythonに対する一般的なスピードアップは、シングルスレッドで100倍以上のオーダーになる。
Codonは、ネイティブのマルチスレッドをサポートしており、さらに何倍ものスピードアップが可能となる。
実行時のオーバーヘッドがない点と通常でも100倍程度の高速化が期待できることが魅力的ですね。
一方、注意点として、以下の事が挙げられます。
- すべてのモジュールに対してサポートできている訳ではない。
- 一部の動的機能(動的型操作や実行時リフレクションなど)はサポートされていない。
※実行時リフレクション:プログラムの実行過程で、自身の構造を読み取ったり、書き換えたりすること。 - 2025年11月1日にApache Licence 2.0に移行する。それまでは、非商用で利用することを条件に、コピー、配布、改変を許可している。
実装方法によっては、上記の1.と2.に該当する可能性があるため、Pythonのまま実行するなど、対策が必要になります。
リポジトリ(GitHub)
これまでと同様に、今回も該当するコード一式をGitHubで管理しています。
先に全体像を確認したい方は、以下のリンクにアクセスしてください。
https://github.com/yuruto-free/codon-docker
環境構築
以降では、環境構築の方法について解説していきます。
ディレクトリ構成
今回、環境構築を行う際のディレクトリ構成は、以下のようになります。
./
|-- Dockerfile
|-- README.md
|-- docker-compose.yml
|-- start.sh
`-- src/
`-- estimate_pi.py
また、テストコードとして、ライプニッツの公式を用いて円周率を推定するスクリプトを追加しています。
$$
\begin{align*}
& & 1 - \frac{1}{3} + \frac{1}{5} - \frac{1}{7} + \frac{1}{9} - \cdots = \frac{\pi}{4} \\
& \Leftrightarrow & \sum_{n = 0}^{\infty} \frac{(-1)^{n}}{2n + 1} = \frac{\pi}{4}
\end{align*}
$$
docker-compose.ymlの内容
ベースとなるdocker-compose.yml
の内容を以下に示します。
version: '3.7'
x-logging:
&json-logging
driver: json-file
options:
max-size: "1m"
max-file: "3"
services:
codon:
build:
context: .
dockerfile: Dockerfile
args:
TZ: 'Asia/Tokyo'
image: codon
container_name: codon
environment:
- TZ=Asia/Tokyo
volumes:
- ./src:/code
restart: "no"
logging: *json-logging
Dockerfileの内容
環境構築の核となる、Dockerfileの内容を以下に示します。
FROM alpine:3.17
ARG TZ='Asia/Tokyo'
ENV PYTHONUNBUFFERED=1 \
PYTHONIOENCODING=utf-8 \
CODON_PYTHON=/usr/lib/libpython3.so \
CODON_VERSION=0.15.5
LABEL maintainer="yuruto"
RUN apk update \
\
# Install basic libraries and setup timezone
\
&& apk add --no-cache bash tzdata gcc musl-dev zlib-dev zstd-libs \
&& cp /usr/share/zoneinfo/${TZ} /etc/localtime \
&& ln -s ${TZ} /etc/timezone \
\
# install temporary libraries
\
&& apk add --no-cache --virtual .build-deps \
libffi-dev g++ libgcc libstdc++ libxslt-dev python3-dev libc-dev linux-headers \
openssl-dev curl cmake automake make llvm-dev llvm-static alpine-sdk \
\
# install python3 and pip3
\
&& apk add --no-cache python3 \
&& python3 -m ensurepip \
&& rm -r /usr/lib/python*/ensurepip \
&& pip3 install --upgrade pip setuptools \
\
# create symbolic link
\
&& ln -sf /usr/bin/python3 /usr/bin/python \
&& ln -sf /usr/bin/pip3 /usr/bin/pip \
\
# install codon
\
&& curl -fsSL https://github.com/exaloop/codon/archive/refs/tags/v${CODON_VERSION}.tar.gz --output /tmp/v${CODON_VERSION}.tar.gz \
&& cd /tmp \
&& tar zxvf v${CODON_VERSION}.tar.gz \
&& cd codon-${CODON_VERSION} \
&& mkdir build \
&& cd build \
&& cmake .. \
&& make -j 4 \
&& make install \
&& cd / \
&& apk --purge del .build-deps \
&& mkdir /code \
&& rm -rf /root/.cache /var/cache/apk/* /tmp/*
# add shell script
COPY ./start.sh /start.sh
RUN chmod 777 /start.sh
WORKDIR /code
CMD ["/start.sh"]
今回、Raspberry PiでCodonの実行環境を作成するにあたり、バイナリファイルが見つからなかったため、ソースコードからビルドしています。
また、ソースコードからビルドする際にLLVMを利用しており、LLVMのバージョンの制約(Version 14以上)により、Alpineのバージョン3.17を利用しています。
start.shの内容
コンテナを維持しておくためのshell scriptを以下に示します。
#!/bin/bash
function handler() {
echo "["$(date "+%Y/%m/%d-%H:%M:%S")"]" SIGTERM ACCEPTED
exit 0
}
trap 'handler' TERM
while :; do
sleep 3
done
動作確認用のテストコード
今回は、円周率の近似値を計算するPythonスクリプトを用意しました。
import time
from datetime import timedelta
from python import math
@python
def printf(data, _format):
print(_format.format(data))
def Leibniz(max_iter : int):
arr = reversed(range(max_iter))
_sum = 0.0
for k in arr:
_sum += ((-1) ** (k % 2) + 0.0) / (2 * k + 1)
pi = _sum * 4
return pi
if __name__ == '__main__':
max_iter = 1_000_000_000
start_time = time.time()
# estimate pi
hat_pi = Leibniz(max_iter)
elapsed_time = time.time() - start_time
err = math.fabs(hat_pi - math.pi) / math.pi
output = str(timedelta(seconds=elapsed_time))
printf(hat_pi, 'Estimated Pi: {:.15f}')
printf(err, 'Relative Err: {:.15e}')
print(output)
環境構築方法
環境構築を行う際は、以下のコマンドを実行します。
# docker-compose.ymlとDockerfileがあるディレクトリで以下を実行する
docker-compose build --no-cache
ソースコードからビルドするため、イメージ作成まで時間を要することに注意してください。
使い方・動作確認
最後に、テストコードを用いて、Codonの使い方について解説します。
使い方としては、以下の2パターンがあります。
- codon run(JITモード)
- codon build(事前コンパイル)
上記は、事前コンパイルをするか、JITコンパイラを用いた実行時コンパイルを行うかという点が違いとして挙げられます。
実際に運用する際は、事前コンパイルしておくことになると思うので、codon buildの方を覚えておけば良いです。
それぞれの実行方法を以下に示します。
# ホスト環境での実行内容
docker exec -it codon bash
# Docker環境での実行内容
# JITモードでの実行
codon run estimate_pi.py
Estimated Pi: 3.141592652589793
Relative Err: 3.183097711628829e-10
0:00:27.889480
# 事前コンパイルによる実行
codon build --release estimate_pi.py
./estimate_pi
Estimated Pi: 3.141592652589793
Relative Err: 3.183097711628829e-10
0:00:08.967100
JITモードによる実行では、約28秒、事前コンパイル後は、約9秒となりました。
また、比較用にPythonコードをそのまま実行した結果も記載しておきます。
# スクリプトの一部を改変
cat <(echo "import math") <(grep -v "python" estimate_pi.py) > original.py
python original.py
Estimated Pi: 3.141592652589793
Relative Err: 3.183097711628829e-10
0:11:20.987000
結果は、約11分21秒(≒681秒)となりました。
これらをまとめると以下のようになります。
実行形式 | 処理時間[sec] | 高速化率 |
---|---|---|
pythonコード(オリジナル) | 680.987000 | 1倍 |
JITモード | 27.889480 | 約24倍 |
事前コンパイル | 8.967100 | 約76倍 |
高速化率から分かるように、今回のケースでは最大76倍程度の高速化が期待できることが分かりました。
まとめ
今回は、Codonの環境構築方法とテストコードを用いた高速化の効果について解説しました。
通常のPythonコードが10倍程度~100倍程度、高速化できるため、積極的に利用していきたいツールであることが実感できたと思います。
興味がある方はぜひ利用してみてください。