新しいPython風プログラミング言語Mojoを試してみた
こんにちは。CX事業本部Delivery部のakkyです。
少々旧聞となりますが、今年9月にMojo言語がローカルで実行できるようにリリースされました。
MojoはSwiftの開発者が立ち上げたModular社が開発している新しいプログラミング言語で、Pythonの文法とRustのメモリ安全性を兼ね備えたコンパイラ型プログラミング言語です。
AI開発に使用することが想定されていて、SIMDのファーストクラスサポートなども特徴的です。実際にllama2.mojoというLlama2の実行環境の実装も行われています。
現在はPythonとの完全な互換性はありませんが、Pythonインタプリタを呼び出すことでPythonコード/ライブラリを呼び出すことができ、将来的にはMojo自体がPythonのスーパーセットとなることを目指しているそうです。
10月19日にはMacのApple silicon(ARM)版もリリースされましたので、M1/M2マシンでも試せるようになりました。
Pythonとの関係
文法
関数の定義にdef
とfn
が存在するのが大きな違いです。def
はPython互換で型を付ける必要がなく、fn
はMojo独自の関数で型定義などをする必要がありますが、大幅に最適化されます。
def
で定義された関数もfn
で定義された関数も、それぞれ相互に呼び出して使うことができます。
なお、型を付ける必要があるという違いのほか、fn
では例外をきちんと処理しなければならないという違いもあります。詳しくは公式ドキュメントで説明されています。
また、fn
では変数宣言が必要で、let
(イミュータブル・変更不可能)とvar
(ミュータブル・変更可能)を明示できます。型推論がありますが、明示的に書くこともできます。
ライブラリ
ライブラリの一覧はドキュメントに記載されています。 現在のところ、Python標準ライブラリがすべて実装されているわけではなく、一部の実装に限られています。実装されているものでも引数や使い方が異なっていることがあるので、ライブラリに関しては完全に別物と思っていたほうがいいと思います。
互換性
MojoではPythonのライブラリをインポートして使うことができます。たとえば、次のようなコードを書くと、requestsでHTTPアクセスできます。
from python import Python def main(): let requests = Python.import_module("requests") let url = "https://checkip.amazonaws.com/" let responce = requests.get(url) let ip_raw = responce.content.decode() let ip_str = ip_raw.strip() let message = "your ip address is " + ip_str.to_string() print(message)
ただし、この場合requestsライブラリやその後の処理はMojoではなくCPythonのインタプリタが実行するため、高速化はされないようです。
このコードで言うとrequests
オブジェクトはもちろんのこと、ip_raw
などのオブジェクトもPythonのものになります。Mojoの文字列にはstrip()
メソッドはないのにこのコードが動くのは、ip_raw
はPythonのオブジェクトで、実際の処理はCPythonインタプリタが行っているためです。
REPLで実行すると変数の型が表示されて、url
はmojoネイティブのStringLiteral
型ですが、ip_raw
はPythonObject
型になっていることがわかります。
インストール
公式サイトの手順通りに行います。Ubuntu 23.10では、事前にpython3-venv
をインストールしておく必要がありました。venvなしでインストールするとエラーになるのですが、この状態では再びmodular install mojo
してもダメで、modular clean
してからやり直す必要がありました。
実行方法
mojo
コマンドでREPLが起動します。ファイルに保存されているスクリプトを実行するときはmojo run ファイル名
とします。
なお、ファイル名は.mojo
または.🔥
という拡張子(絵文字も可!)である必要があります。
mojo build ファイル名
とすると、コンパイルして実行ファイルを生成することも可能です。
実行速度を実験してみた
Mojoは高速化が大きな目玉とされていますが、実際はどの程度高速化されるのでしょうか?既存のソフトウェアがそのまま動く状態ではないため、簡単なコードでの実験となりますが、比較してみました。比較対象はCPythonとPypyです。
実行速度の測定にはtimeコマンドを使用し、3回実行して最も速いrealの値を記載しました。 実行環境はWSL2上のUbuntu 23.10です。
使用バージョン
- Mojo 0.4.0
- Python 3.11.6
- PyPy 7.3.12 (Python 3.9.17)
フィボナッチ数の計算
まずはフィボナッチ数の計算で比較してみます。
Python/Pypy
import sys def fibonacci(n): if n == 0 or n == 1: return n else: return fibonacci(n - 2) + fibonacci(n - 1) def main(): print(fibonacci(int(sys.argv[1]))) main()
Mojo
2バージョンで比較してみます。まずはdefで定義したものです。こちらは型を付けていません。
Pythonコードとの違いは、sys.argv()
がプロパティではなく関数である点と、文字列を数値に変換するのにatol()
を使うという点です。
import sys def fibonacci(n): if n == 0 or n == 1: return n else: return fibonacci(n - 2) + fibonacci(n - 1) def main(): print(fibonacci(atol(sys.argv()[1])))
次にfnで定義したものです。Int型を指定しました。mainもfnとしたので、例外をキャッチして握りつぶすコードを追加しています。
import sys fn fibonacci(n: Int) -> Int: if n == 0 or n == 1: return n else: return fibonacci(n - 2) + fibonacci(n - 1) fn main(): try: print(fibonacci(atol(sys.argv()[1]))) except: pass
実行速度の比較
引数を40とした場合。
実行環境 | 実行時間(秒) |
---|---|
Mojo(def) | 8.634 |
Mojo(fn) | 0.430 |
Python | 17.262 |
Pypy | 7.442 |
Mojoでもdefを使うとあまり高速化しませんが、fnを使うと別物のように高速化しました。
竹内関数の計算
もう一つ、フィボナッチ数の計算と傾向の違いはあまりないと予想できますが、プログラミング言語の処理系のベンチマークに使われる竹内関数も実験してみました。
Python/Pypy
import sys call_cnt = 0 def tarai(x, y, z): global call_cnt call_cnt += 1 if x <= y: return y else: return tarai(tarai(x - 1, y, z), tarai(y - 1, z, x), tarai(z - 1, x, y)) def main(): arg = sys.argv if len(arg) >= 4: x = int(arg[1]) y = int(arg[2]) z = int(arg[3]) res = tarai(x, y ,z) print(call_cnt) main()
Mojo
こちらはmainをdefで定義してみました。(実際の処理はtarai関数が行うので、実行速度にほとんど変化はないはずです)
import sys var call_cnt = 0 fn tarai(x: Int, y: Int, z: Int) -> Int: call_cnt += 1 if x <= y: return y else: return tarai(tarai(x - 1, y, z), tarai(y - 1, z, x), tarai(z - 1, x, y)) def main(): let arg = sys.argv() if len(arg) >= 4: let x = atol(arg[1]) let y = atol(arg[2]) let z = atol(arg[3]) let res = tarai(x, y ,z) print(call_cnt)
実行速度の比較
引数を13 6 0とした場合。
実行環境 | 実行時間(秒) |
---|---|
Mojo(fn) | 0.213 |
Python | 4.940 |
Pypy | 1.528 |
考察とまとめ
フィボナッチ数や竹内関数のような主に関数の呼び出しがメイン負荷となるベンチマークでは、おおむね20倍程度高速化する傾向があることがわかりました。
実際のワークロードではここまでの差は出ないかもしれませんが、SIMDのネイティブサポートなどによってCPUバウンドな処理ではかなり高速化されそうです。
たとえば、Modular公式ブログではPythonから68,000倍高速化したという記事がありますが、これはMojo自体の速度に加えて、SIMDを活用した高速化とマルチコアCPUを活用した最適化の結果のようです。簡単に並列処理ができるのはとても良いですね。
ただ、現在のところは互換性や機能の不足によって、PythonやPypyを置き換えられるような状態にはなっていません。 今後開発が進み、CPythonとの互換性が高まると、既存のPythonプログラムが手軽に高速化できるようになり、他の言語で開発された拡張モジュールを使う必要がなくなるかもしれません。
AWSへの適用という点を考えると、実行ファイルをコンパイル可能なので、Lambdaの実行が高速になるのに加え、デプロイが簡単になるというメリットがありそうです。Webフレームワークが出てくると楽しそうですね。
今後も引き続き開発の状況を追いたいと思います。