Nimを知ってほしい2022
Nimを知ってほしいという記事があり、Nimを知らなかった人々向けに最初の紹介として大変な貢献をしてくださりました。
しかしまだNimを使ったプロダクトというのも少なく、競プロではチラホラ見かけるものの、人々の中にある意識としては「気になっています」という域を越えられていないのも事実です。
そこで今回は企業での意思決定をする人や、5年以上の経歴があるエンジニア向けに、Nimを書いてみようと感じてもらうことを目的に、先日私が登壇したみんなのPython勉強会#79 『Pythonistaに伝えたいNimの魅力』に加筆して投稿してみたいと思います。
Nimって何?
2008年から開発が始まった新しいプログラミング言語です。
「Pythonに型が付いて、Goみたいに高速に、バイナリになってOSの実行環境に依存しないで動いてくれる言語ないかな〜」という全プログラマーの夢を叶えてくれる言語です。
書きやすさと読みやすさ
そのソースコードは型がついただけのPythonと言ってもいいほど、書きやすく読みやすいです。
mypy型付きPythonとフィボナッチ数列を出力する関数で比較してみましょう。
# Python
def fib(n: int) -> int:
if n < 2:
return n
else:
return fib(n - 1) + fib(n - 2)
print(fib(30))
# Nim
proc fib(n: int): int =
if n < 2:
return n
else:
return fib(n - 1) + fib(n - 2)
echo(fib(30))
フィボナッチ数列だから簡単なんでしょ、こんなのTypeScriptで書いてもGoで書いてもあんまり変わんないよ、という声もあるかもしれません。
では次は「ファイルを開いて中にある文字列をゲノム解析をして、DNA配列中のGとCの割合を表示するコード」で比較してみましょう
引用:なぜ私はデータ処理においてNimをPythonの代わりに使うのか(翻訳)
# Python
gc = 0
total = 0
for line in open("orthocoronavirinae.fasta"):
if line[0] == '>': # ignore comment lines
continue
for letter in line.rstrip():
if letter == 'C' or letter == 'G':
gc += 1
total += 1
print(gc / total)
# Nim
var gc = 0
var total = 0
for line in lines("orthocoronavirinae.fasta"):
if line[0] == '>': # ignore comment lines
continue
for letter in line:
if letter == 'C' or letter == 'G':
gc += 1
total += 1
echo(gc / total)
ね、ほとんど同じですね。特にPython経験者は非常に低い学習コストでNimを使うことができます。
実行速度の速さ
上のソースコードですが、PythonからNimにほぼ同じコードの書きやすさで移植するだけで、大幅なスピードアップをすることができます。
言語 | 実行時間 | Nimとの比較 |
---|---|---|
Python3.9 | 23.43秒 | 30.6x |
PyPy 7.3 | 2.54秒 | 3.3x |
Nim 1.4 (-d:danger --gc:orcをコンパイルオプションに付けて) | 0.765秒 | 1.0x |
またこちらの様々な言語のベンチマークを測定しているページでは、Nimの実行速度はCやGo、Rustと十分に戦えるレベルであることがわかります。
引用:Completely Unscientific Benchmarks
Language | Real Time, seconds | Slowdown Time | Memory, MB | Binary Size, MB | Compiler Version |
---|---|---|---|---|---|
C++ "raw pointers with pool" (gcc & static) | 0.167 | x1 | 0.25 | 1.7 (static) | GCC 9.3.0 |
Rust "idiomatic" | 0.23*** | x1.4 | 0.4 | 0.207 | Rustc 1.42.0 |
Go "with pointers" | 0.37 | x2.2 | 6.8 | 1.9 (static) | Go 1.14.1 |
Nim "--gc:refc " |
0.48 | x2.9 | 0.5 | 0.059 | Nim 1.2.0 / GCC 9.3.0 |
JavaScript | 1.03 | x6.2 | 49 | N/A | Node.js 13.12.0 |
PHP | 3.60 | x21 | 5 | N/A | PHP 7.4.4 |
Ruby | 6.05 | x36 | 9 | N/A | Ruby 2.7.1 |
Python (CPython) | 9.10 | x54 | 3.5 | N/A | CPython 3.8.2 |
更なるメモリ最適化を目指して
Nim作者のAraqはここ数年、Nimのメモリ管理の最適化に注力しています。プログラミング言語がその実効速度を速くしようとすれば、最後の課題になるのがメモリ解放です。
Java、Python、JSなどで一般的に用いられるGCを使えばプログラムが自動でメモリ解放をしてくれますが、GCが動くコストがあるために実行速度が遅くなります。
C言語や初期のC++では開発者がmalloc
やnew
で確保したメモリを、変数の使用後に開発者自身がfree
やdelete
でメモリ解放するという手動でのメモリ管理を行います。これはGCが動かず、またメモリ上に不要な変数も持たないために最速かつ最小メモリ使用を実現できますが、開発者への深い知識と開発コストが求められます。
Rustではスタックだけでなくヒープから確保した変数についてもスコープを抜けると変数の即時開放を行い、また所有権の概念を用いることで、所有権を失った変数についても即時開放され、この作法に従っていないコードはコンパイルエラーを起こすという、非常に厳格なメモリ管理を採用しています。
参考:Rustのメモリ管理って面白い
NimではARC(Automatic Reference Counting with destructors and move semantics)という手法を採用しています。
NimではC言語にトランスパイルする時に、トランスパイラが自動でソースコードを変換します。
proc main =
let mystr = stdin.readLine()
case mystr
of "hello":
echo "Nice to meet you!"
of "bye":
echo "Goodbye!"
quit()
else:
discard
main()
↓ 自動で変換
var mystr
try:
mystr = readLine(stdin)
case mystr
of "hello":
echo ["Nice to meet you too!"]
of "bye":
echo ["Goodbye!"]
quit(0)
else:
discard
finally:
`=destroy`(mystr)
引用:Nimのメモリ管理を理解する① ― Nimの新しいGC、ARCについて
スコープ後に=destroy()
を自動挿入しています。=destroy()
の内部ではdealloc
が呼ばれており、変数のメモリが開放されています。これによりNimでもRustと同じく、ヒープの変数についてもスコープベースでのメモリ管理と即時開放を実現しています。またこれをトランスパイラが自動で行ってくれるため、Rustのように開発者が所有権について考える必要がなく、低い学習コストで素晴らしいメモリ管理の恩恵を受けることができます。
そしてこの新しいメモリ管理手法により、既存の参照カウントで16秒かかった処理を6.75秒で終えることができるようになりました。
2020年10月16日にリリースされたv1.4.0からこの仕組みを使うことが出来ます。
参考:Nimを調べてみる
参考:公式ブログ記事 Introduction to ARC/ORC in Nim
参考:Nimのメモリ管理を理解する② ― Nimのムーブセマンティクス
開発環境の充実について
環境構築
pyenvやnodenvのような、複数のバージョンを管理するchoosenim
というツールがあり、たった1行のコマンドを実行するだけで開発環境を作ることができます。
curl https://nim-lang.org/choosenim/init.sh -sSf | sh
参考:choosenim Github
参考:choosenimでnim環境構築
参考:Windowsでもchoosenimがしたい
またDockerhubにはUbuntuとAlpineをベースとしたDockerイメージが用意されています。
Nim Dockerhub
標準ライブラリ
標準ライブラリの全ての関数とサンプル、型一覧や関数の引数の型は何かが公式ドキュメントにまとめられています。
PythonのSphinxに似た機能が言語自体に備わっていて(nimble docコマンド)、ドキュメントがソースコードから自動生成されています。
Pythonの「battery included」の思想を受け継いでおり、標準ライブラリがとても豊富です。
Nim Standard Library
外部ライブラリ
nimble install
コマンドを使って、外部ライブラリをインストールすることができます。
インストールできる外部ライブラリはまとめられており、検索することができます。
Nim package directory
エディタサポート
VSCodeの拡張機能とIntelliJ IDEAのプラグインがあります。
どちらもシンタックスハイライト、補完、マウスホバーで関数定義の表示、定義元ジャンプなどが使えます。
どこでも動く
Nimで作ったアプリケーションはLinux以外にも上記の環境で動かすことができます。
またC、C++、Objective-C、JavaScriptをターゲットにしてコンパイルすることができます。
Nim Backend Integration
プロダクト開発実績
こちらにNimを使っている企業の一覧がまとめられています。
Webサービス
Nim製WebフレームワークのJester、Nim製フロントエンドフレームワークのKaraxを使ってNim forumは作られています。
デスクトップアプリ
C言語でデスクトップアプリを作るライブラリのQt5をNimから扱うライブラリであるnimqmlを使って、SlackやDiscordのようなStatus社のクライアントソフトウェアが作られています。
2Dゲーム
クロスプラットホームな2Dゲームを作るライブラリのSDL2を使って、2Dゲームが作られています。
またゲームボーイアドバンス20周年記念としてクラウドファンディングで企画されたゲーム、Goodboy GalaxyはNimで作られています。
Nim forum―Goodboy Galaxy - Kickstarter and demo now live!
3Dゲーム
C言語で3Dゲームを作るプラットフォーム兼ゲームエンジンのGodotとそのNimラッパーであるgodot-nimを使って、Nimで3Dゲームを作ることができます
ゲームを作りたい(Godot + Nim)
回路図シミュレーションゲームであるTuring CompleteはNimとGodotで作られています。
コンテナエンジン
Go言語で作られているDockerですが、そのNim版を作った人がいました。なんと日本人です。作者によるYouTube講演
ターミナルエディタ
VimのようなターミナルエディタのNim製のものがあります。作者は上のコンテナエンジンを作った方と同じです。
Twitterクライアント
軽量なTwitterクライアントであるNitterはNimで作られています。
Ethereumクライアント
EthereumクライアントにはgethというGo製のツールが使われることが多いですが、
Ethereum2.0のBeacon Chainに対応したノードを立てるアプリケーション、NimbusがNimで作られています。
OS
NimでOSを作ることもできます。
おわりに
今までのプログラミング言語では、開発難易度と実行速度はトレードオフでした。つまり、速く動く言語では習得が難しい、簡単に開発できる言語は遅い、ということでした。しかしNimの登場によって、言語自体の学習コストの低さと実行速度の速さを同時に満たすことができるようになりました。
更に様々な用途で、マルチプラットフォームで動くのです。JS向けにコンパイルしてフロントエンドの開発からOSの開発まで1つの言語が担えるようになりました。
少しでも多くの人がNimで開発して、Nimを書いてくれることを願っています。
ありがとうございました。
Discussion