Unity向けにBurst Compilerを用いた爆速LINQ実装を提供するライブラリ「BurstLinq」を公開しました!
LINQの高速化とは言っても全てのオペレータをサポートするわけではなく、Sum、Average、Min、Max、Contains、SequenceEqualsの6つのみ(要は集計系のオペレータのみ)を対象とします。ただし効果は絶大で、Burstによって最適化された圧倒的なパフォーマンスを発揮します。
元々は単にポインタのForループをBurst Compilerに投げるだけの簡易的な実装でしたが、@Akeit0さんにBurstが自動ベクトル化できない部分を手動で最適化していただいたおかげでv1.0時点と比較してさらに数倍高速化されています。本当にありがとうございます…!
あと今回の記事はUnity Advent Calendar シリーズ2の13日目の記事です。そして今日は16日…すみません…
使い方
まずは使い方ですが、特にこれといって説明することもありません。Burstが適用可能なコレクション型に対するオーバーロードが全て用意されているため、usingすれば勝手に適用されて勝手に爆速化してくれます。
using System.Collections.Generic;
using System.Linq;
using BurstLinq;
var enumerable = Enumerable.Range(0, 100);
var array = enumerable.ToArray();
// Enumerable.Sum()
var linqSum = enumerable.Sum();
// BurstLinqExtensions.Sum()
var burstLinqSum = array.Sum();
READMEにも書いてある通り、このBurstLinqはCysharpさんのSimdLinqというライブラリのコンセプトを元にLINQの高速化バージョンをUnity + Burstで実装したものになります。
SimdLinqの良いところは‘Drop-in replacement’であることで、global usingを追加するだけでそのまま導入/高速化できるんですね。いちいちLINQの高速化のために専用メソッド使うのも…というのはやはりあるので、コードに手を加えず導入できる点は魅力です。(そしてBurstLinqもそれに倣っています。)
よしじゃあBurstLinqでもglobal using使おう、と言いたいところですが、global usingはC#10以降の機能です。そしてUnityのC#バージョンはというと、9.0です。ダメじゃん。
ただ、実はUnity 2022.2あたりから内部のRoslynのバージョンが上がっているので、-langVersion:latestを指定すれば普通にC#10.0を動作させることが可能です。IDE側でも動作させたい場合は.csprojを書き換えればOKですが、Unityは.csprojを自動で生成するのでこれを編集するにはCsprojModifierなどのライブラリが必要になります。
パフォーマンス
で、結局パフォーマンスはどうなの?というと、爆速です。Sum/AverageはBurstが自動でベクトル化してくれるので高速だし、Min/MaxやContains/SequenceEqualもv1.2でベクトル化していただいたので、やはり圧倒的に高速です。これだけ差があるのであれば「じゃあ試しに入れてみようかな」という気にはなるんじゃないでしょうか…!
対応するコレクションについてもT[]、List<T>、Span<T>、ReadOnlySpan<T>、NativeArray<T>、NativeSlice<T>、NativeList<T>とBurstが適用可能な型はほとんど網羅しています。また、Unity独自の型(Vector3、float3等)に対するSum、Averageのサポートも追加しています。
LINQとの互換性
BurstLinqはSIMDなどの最適化が適用されるため、LINQと比較して互換性のない(また安全でない)部分がいくつかあります。やってることがほとんど同じなのでこの辺もSimdLinqと変わらないんですが…
まずSum/Averageについてですが、LINQはcheckedですがBurstLinqはuncheckedです。内部の処理をcheckedにすればいけるんじゃ…?と思ったんですが、どうやらBurstは現状checked構文に対応してないみたいなので…
Does Burst support the unchecked keyword?
https://forum.unity.com/threads/does-burst-support-the-unchecked-keyword.761495/
このスレッド自体は2019年のもので結構古いんですが、今も実際に試してみるとやっぱり対応してない(Burstつけた時だけOverflowExceptionが吐かれないみたいなことが起こる)ためuncheckedで統一しました。そのため使用する際には注意してください。
また、float/doubleに関してはSum/Averageに[BurstCompile(FloatMode = FloatMode.Fast)]を適用しているため並列に加算を行います。そのため、通常のLINQのSum/Averageと結果が僅かに異なる場合があります。まあとは言っても浮動小数点型である以上どうしても誤差は出るので、多少違ったところで問題になることはないでしょう。
また、BurstLinqはfloat/doubleに対してNaNチェックを行いません。そもそもNaNが入ってくること自体が稀なので問題になることはほとんどないと思いますが、こちらも注意しておいてください。
まとめ
単にちょっとした思いつきで作っただけのライブラリだったはずが、既にAlchemyのStarsを超えてて何とも…
ただちゃんとパフォーマンスも出るし、十分使えるライブラリだと思います。というかとにかくBurstが強い。ホントに最高なので、まだBurst使ってない方はどんどん活用していきましょう。HPC#の枠組みに沿ってコードを書かないといけないので何でもかんでも速くなるわけではないですが、その分パフォーマンスの向上は圧倒的です。使い方も[BurstCompile]付けるだけなので手軽だし。
というわけでUnityでLINQを使う方には是非こちらのBurstLinqも使ってみてください!ついでに他のライブラリも使ってみてもらえると嬉しいです…!