MojoでProject Euler 1

MojoPythonのスーパーセットを目指している高速に動作する言語らしいです。
ここを見ると、インストールできます。

https://dev.to/jjokah/getting-started-with-mojo-4985

WSL2ならスムーズにインストールできます。

インストールできたら、Project Euler 1を解いてみましょう。

https://projecteuler.net/problem=1

Pythonならこんな風に書くでしょうか。

import sys

def f(N: int) -> int:
    return sum(n for n in range(1, N) if n % 3 == 0 or n % 5 == 0)

N = int(sys.argv[1])
print(f(N))

しかし、MojoではGenerator式とか内包表記とかはまだ書けません。なので、手続型で書くと、

# e001.py
import sys

def f(N: int) -> int:
    s = 0
    for n in range(1, N):
        if n % 3 == 0 or n % 5 == 0:
            s += n
    return s

N = int(sys.argv[1])
print(f(N))

これをMojoにすると、

# e001.mojo
import sys

fn f(N: Int) -> Int:
    var s = 0
    for n in range(1, N):
        if n % 3 == 0 or n % 5 == 0:
            s += n
    return s

fn main():
    let args = sys.argv()
    try:
        let N = atol(args[1])
        print(f(N))
    except:
        pass

まず、明確にmainがあって、関数はdefも使えるらしいですが、fnで宣言します。
変数もvarかletで宣言して、letならimmutableです。
型はintでなくてIntで、これは64ビット符号付き整数のようです。
早速、速度を測ってみましょう。
Pythonは3.8.8、PyPyは3.1.17、Mojoは0.4.0です。

$ time python e001.py 1000000000
233333333166666668

real    1m3.780s
user    1m3.770s
sys     0m0.000s

$ time pypy3 e001.py 1000000000
233333333166666668

real    0m2.679s
user    0m2.650s
sys     0m0.020s

$ time mojo e001.mojo 1000000000
233333333166666668

real    0m1.122s
user    0m1.124s
sys     0m0.010s

確かに速いのですが、こういう単純なコードだとPyPyも速いんですよね。
Mojoは実行ファイルも作ることができるのでそちらも試してみましょう。

mojo build e001.mojo -o e001_mojo

time ./e001_mojo 1000000000
233333333166666668

real    0m1.067s
user    0m1.053s
sys     0m0.010s

変わらないですね。
C++とRustでも試してみましょう。
g++は9.4.0、rustcは1.72.0です。

g++ -O2 e001.cpp -o e001_cpp

time ./e001_cpp 1000000000
233333333166666668

real    0m0.839s
user    0m0.836s
sys     0m0.000s

rustc -C opt-level=3 e001.rs -o e001_rust
time ./e001_rust 1000000000
233333333166666668

real    0m2.207s
user    0m2.185s
sys     0m0.010s

C++はさすがの速さなのですが、Rustはなぜ遅いんでしょうね。

// e001.cpp
#include <iostream>

typedef long long   ll;

ll f(ll N) {
    ll  s = 0;
    for(ll n = 1; n < N; ++n) {
        if(n % 3 == 0 || n % 5 == 0)
            s += n;
    }
    return s;
}

int main(int argc, char **argv) {
    const ll    N = atoll(argv[1]);
    std::cout << f(N) << std::endl;
}
// e001.rs
#![allow(non_snake_case)]

use std::env;

fn f(N: u64) -> u64 {
    let mut s: u64 = 0;
    for n in 1..N {
        if n % 3 == 0 || n % 5 == 0 {
            s += n
        }
    }
    s
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let N: u64 = args[1].parse().unwrap();
    println!("{}", f(N))
}