https://www.google.co.jp/search?q=Mojo+%E3%81%A7Python%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%82%92%E4%BD%BF%E3%81%86&sca_esv=0fe395fae313f6fd&sxsrf=ADLYWIJDjvHfSjKOv2AgpsSNnAfjHwmEpA%3A1717706417055&source=hp&ei=sR5iZrCiAdSqvr0Po7gc&iflsig=AL9hbdgAAAAAZmIswb3xuXRZZsHwnwWcV7tr47ZMx0LI&ved=0ahUKEwjwvsKG68eGAxVUla8BHSMcBwAQ4dUDCA8&uact=5&oq=Mojo+%E3%81%A7Python%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%82%92%E4%BD%BF%E3%81%86&gs_lp=Egdnd3Mtd2l6IiZNb2pvIOOBp1B5dGhvbuODqeOCpOODluODqeODquOCkuS9v-OBhjIJECEYoAEYChgqSNKYAVDwCVjOlAFwCXgAkAEBmAGpAqABnjuqAQcwLjExLjI2uAEDyAEA-AEBmAIloALZK6gCAcICBhCzARiFBMICBxAAGIAEGATCAgUQABiABMICBBAAGB7CAgYQABgKGB7CAgYQABgEGB7CAggQABgEGAoYHsICCBAAGIAEGKIEwgIIEAAYogQYiQXCAgcQABiABBgNwgIHECEYoAEYCpgDA5IHBzkuMTAuMTigB_xb&sclient=gws-wiz
こんにちは。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フレームワークが出てくると楽しそうですね。
今後も引き続き開発の状況を追いたいと思います。
参考ページ
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
Pythonの35000倍速い新言語Mojo🔥 に触れてみた【基礎編】
Mojo🔥とは
PythonのシンプルさとRustのスピードとメモリの安全性を組み合わせた、新しいプログラミング言語。
Pythonの35000倍速いとされており、Pythonの弱点である「遅さ」を補うと期待されている。また、書き方もPythonと似ているので移行もしやすいです。
そんな言語が2023年10月19日より、Appleシリコン搭載Macに対応したとのことで、早速触ってみました。
本家のドキュメントはここにまとまっています。
Mojo🔥のセットアップ
ターミナルやVSCodeで使えるようにセットアップが必要です。
以前記事にまとめたので、そちらをご覧ください
Mojo🔥の基本
主にPythonとの比較をしながら書いていきます。
前提
・Mojo🔥はコンパイル言語であり、他のコンパイル言語たち同様、エントリーポイントとして main()
が必要
・Pythonのすべての構文とセマンティクスをサポート(Pythonの書き方でも実行可能)
変数 var, let
var x: Int = 1
let y: Int = 2
varは可変な変数、letは不変な変数の定義に用います。Intなどの型タイプは省略も可能です。
関数 : def → fn
Python:
def main():
x = 1
x += 1
print(x)
Mojo🔥
fn main():
var x: Int = 1
x += 1
print(x)
ただ、defのままでも実行できます。fnにすると型の安全性を強制するようにできます。
なので、fnの中でvarなどを削除するとエラーになります。
また、関数に引数や返り値を設定する場合は、型タイプを明記する必要があります。書かないとエラーが出ます
・OKな例
fn add(x: Int, y: Int = 1) -> Int:
return x + y
・ダメな例
fn add(x, y):
return x + y
引数のデフォルト値の設定方法はPythonと同じです
borrowed と inout と owned
関数の引数はimmutableな変数(変更不可)として読み取られます。
従って、以下はエラーになります
fn add(x: Int, y: Int) -> Int:
x += 1
return x + y
borrowedはそのことを明記する(保証する)役割らしいです
→関数がそのオブジェクトの所有権を持たず変更しないことを保証している
※borrowedは、特に何も書いていない上のfn add()
と同じ挙動になります。
→デフォルトでborrowedが省略されていると考えれば良い?
fn add(borrowed x: Int, borrowed y: Int) -> Int:
return x + y
さらに、inoutというものがあります。
fn add(inout x: Int, inout y: Int) -> Int:
return x + y
inoutとして設定すると、引数をmutableな変数(変更可能)とできます。そして関数内部で加えられた変更は外部にも影響します。
fn main():
var a = 1
var b = 2
let c = add_inout(a, b)
print(a)
print(b)
print(c)
fn add_inout(inout x: Int, inout y: Int) -> Int:
x += 1
y += 1
return x + y
これを実行すると、
2
3
5
となります。aは元々1, bは2でしたが、fn add_inout()
の中で1ずつ足されたので、それぞれ2, 3になっています。
ownedは、引数をmutable(変更可能)とするものの、関数の外には影響を与えないとするものです。
先ほどのコードでinoutをownedに変更すると、
fn main():
var a = 1
var b = 2
let c = add_inout(a, b)
print(a)
print(b)
print(c)
fn add_inout(owned x: Int, owned y: Int) -> Int:
x += 1
y += 1
return x + y
結果は
1
2
5
となって、aもbも変更されていないことがわかります。
クラス class → struct
完全に静的なclassのようなもので、メソッド、フィールド、オーバーロード、デコレータなどを定義できます。
struct MyPair:
var first: Int
var second: Int
fn __init__(inout self, first: Int, second: Int):
self.first = first
self.second = second
fn dump(self):
print(self.first, self.second)
fn main():
let mine = MyPair(2, 4)
mine.dump()
第一引数にself
を渡すところや__init__()
を定義するところはPythonのclassと同じです
出力は
2 4
となります。Pythonのclassと違うところとして、__init__()
で定義するself.first
やself.second
は事前にvar first: Int
と定義しておく必要があることが挙げられます。これがないとエラーになってしまいます。
error: 'MyPair' value has no attribute 'first'
self.first = first + 1
また、__init__()
の引数はinout
として渡します。
これは、イニシャライザはオブジェクトの初期化を担当する部分であり、このメソッド内でself
オブジェクトを変更するので引数はmutableでなければなりません。したがってborrowed
は適しません。
さらに、self
オブジェクトを変更してこのメソッド外においてもその変更を適用させたいのでowned
も適しません。したがって、inout
とする必要があります。
まあ、一言で言えば、「selfはインスタンスを示すので、__init__で行われるインスタンスの初期化をクラス内の他のメソッドでも適用させたいため、inoutとする」ということです。
Pythonライブラリを使う Python integration
ドキュメント通りのコードを実行するとエラーが出る。。。
numpyをうまく読み込めないっぽい。色々調べたけど治らず。
まだ情報が少ないからこういう問題が発生した時の対処が大変です。。
最後に
まだまだMojo🔥は発展途中の言語で、ドキュメントにもそのことは繰り返し書かれていました。Pythonに似ていて書きやすいので今後の発展が楽しみです。
次はPythonとの速さの比較の記事を書こうと思っているので、読んでもらえたら嬉しいです!
また、X(Twitter)では日々データサイエンスやAIに関する勉強記録・情報発信をしています。フォロワーはもうすぐ5500人に到達します。
少しでも刺激になるようなことを発信できたらいいなと思っているので、気になった方は見てみてください🔥
では👋
0 コメント:
コメントを投稿