この記事は ダーディット株式会社の技術ブログ からのサルベージ品です。

代表の遠藤です。

弊社では(というよりは私は?)、日常業務のちょっとした作業で Python を活用しています。

Python は非常に書きやすく、機械学習の分野では非常によく使われているポピュラーな言語で、弊社でもよく使われています。 一方で、 Python は実行速度が遅い というのはよく知られた批判ですし、 個人的には PipenvPoetry 等といったパッケージマネージャーが乱立している状況 も好ましく思っておらず、乗り換え先となる言語を探していました。

安住の地を求める私の旅は長く続いておりましたが、最近 Nim という候補を見つけましたので、 実例をもとに紹介したいと思います。

What’s Nim?

NimAndreas Rumpf により最初に開発され、2008年に世に出たプログラミング言語とのことです。 公式Webサイトには “Efficient, expressive, elegant” というスローガンとともに、下記の説明があります。

Nim is a statically typed compiled systems programming language. It combines successful concepts from mature languages like Python, Ada and Modula.

日本語にするとこんな感じでしょうか。

Nim は静的型付けされたコンパイル型システムプログラミング言語です。 Python, Ada や Modula といった成熟した言語からのうまくいったコンセプトを組み合わせています。

Nim の特徴を私の浅い理解なりに簡単にまとめると、

  • Python によく似た文法: Python に慣れている人なら言語を乗り換える障壁が比較的低い
  • 内部で C/C++ に変換され、ネイティブで実行可能なバイナリが生成される: インタプリター型言語よりも高速に動く
  • 静的型付け言語: Python のような型の闇がコンパイル時に解決されるので、実行時に安心できる
  • 柔軟な文法: 演算子オーバーロードやマクロなどが用意されており、かゆい所に手が届く

などといった感じになると思います。 (他にもいいところはあると思うよ!)

試しに有名な FizzBuzz 問題 を Nim で解いてみると、 演算子オーバーロードを活用して次のようにシンプルに書けます。

他にも書き方はいろいろ考えられますが、Nim のシンプルな文法とその表現力がちょっとは伝わる例ではないかと思います。

Experiment & Result

今回は社内のとあるプロジェクトでの実験で生成された、動画像処理における各フレームごとの処理時間の内訳を記録したログデータ(CSV)の分析を例に、 Python と Nim でなるべく同じようなスクリプトを記述し、処理結果と時間を比較しました。

今回使用した CSV 形式のログファイルの例を、以下に示します。

filename,width,height,algorithm,frame_pos,x,elapsed_time_get_roi,elapsed_time_bgr_to_hsv,elapsed_time_hsv_in_range,elapsed_time_morphology_ex,elapsed_time_median_blur,elapsed_time_line_detection,elapsed_time_all
assets/1.mp4,160,120,0,1,-1,0.036,0.613,0.374,1.099,0.434,2.210,4.777
assets/1.mp4,160,120,0,2,-1,0.030,0.310,0.102,0.480,0.365,0.835,2.133
assets/1.mp4,160,120,0,3,-1,0.030,0.204,0.107,0.504,0.378,1.503,2.883
assets/1.mp4,160,120,0,4,-1,0.027,0.195,0.100,0.468,0.362,0.864,2.026

ある動画ファイルを複数の解像度にリサイズし、更に複数のアルゴリズムで処理した際の、処理時間を記録したものとなっています。 今回は、これらのログから、解像度・アルゴリズムごとの処理時間の平均等を計算します。 使用したスクリプトについては Codes をご覧ください。

まずは、オリジナルのCSVファイルを用いて、Python と Nim の計算結果を比較しました。 実際の処理結果は省略しますが、Python と Nim で一致する計算結果が得られ、プログラム上の問題はないことを確認しました。

次に、オリジナル CSV ファイルよりデータ行をコピペして、データ量 6.0GB、データ件数(行数)にすると 86,492,500 件の比較的大規模な CSV ファイルを生成しました。 その CSV ファイルを、それぞれ Python と Nim に処理させて、処理時間を比較しました。

Language Elapsed Time Speed up
Python 7m31.973s 1.00
Nim (debug build) 15m35.813s 0.48
Nim (compiled with -d:release) 1m14.869s 6.04

以上の測定結果より、今回の実験では (ちゃんとリリースビルドすれば) Nim は Python の6倍速い という結果となりました。

なお、今回の実行環境は次の通りでした。

  • CPU: AMD Ryzen 9 3900X
  • メモリー: 64GB
  • OS: ArchLinux
  • Python バージョン: 3.9.7 (pyenv にてインストール)
  • Nim バージョン: 1.4.8 (choosenim にてインストール)

Codes

今回の評価で用いたスクリプトは以下の通りです。 先に Python でスクリプトを書いたのちに、その Python スクリプトになるべく似るような Nim のスクリプトを記述しました。

双方を比較してみると、CSVパーサーのライブラリの使い方が微妙に違ったりはするものの、 おおむね似たような雰囲気のコードになっているのではないかと思います。 このことから、Nim は Python のように気軽に書ける言語であるということが、よりお分かりいただけるかと思います。

一方、Nim が静的型付け言語であるという特徴から、今回のように辞書(Nim ではテーブルといいますが)の値が複数の型を取る場合が、 どうしても少し面倒になってしまう点は否めません。

一般に、下記に示すような JSON (これは JSON Example から拝借の上改変) のように、 辞書の値は複数の型を持つことがあります。 (ここでは widget/window/title が文字列型であるのに対し、widget/window/width は整数型である)

{"widget": {
    "debug": "on",
    "window": {
        "title": "Sample Konfabulator Widget",
        "name": "main_window",
        "width": 500,
        "height": 500
    }
}}    

このようなケースでは、例えば上記 Nim スクリプトの7行目から15行目のように、型の種類を示す列挙型(kind)と それぞれの型に対応するメンバー(num, data)を持つようなオブジェクト型を定義することで解決ができます。 他にも方法はあると思いますが、この方法は Nim ManualNim JSONパーサーにおけるオブジェクト定義 でも用いられており、 Nim らしい書き方なのだと思います。

この辺の取り扱いは、静的型付け言語ゆえにどうしようもないところではありますが、 もう少し何とかならんものかと思うところです。 (とはいえ、言語処理系を作る気持ちになってみると、この辺が落としどころなんだろうなぁという気持ちにもなる)

Conclusion

今回は、巨大CSVファイルの解析を Nim と Python で行い、双方を比較してみました。

Nim は Python と同じような雰囲気で気軽にアルゴリズムを表現できる記述力の高い言語 であることがわかりました。 そしてさらに、 Nim は Python よりも6倍速い という測定結果が得られました。 これらのことから、「Python 並の手軽さで C 並のパフォーマンスが出る」と噂の Nim が持つ、 高いポテンシャルを感じることができました。

一方、Nim は Python と比べてユーザー数が圧倒的に少なく、Python の持つ豊富なライブラリー資産がないために、 メインの言語にするにはまだちょっと難しい点があることは否めません。 しかし、これは次第に Nim が普及していくにつれ、時間が解決してくれる問題でしょう。

Python 一強ともいえるこのご時世、この記事をきっかけに少しでも多くの方に Nim のことを知っていただくことで、 より良い選択肢の可能性を広げていくことができれば幸いです。

ありがとうございました!