3.5K Views
February 01, 24
スライド概要
ベクトルプログラミング入 門を読んでunsafeの沼に 足を踏み入れかけたが unsafeはUnsafeでセーフ 松井 敏(binnmti)
よーわからん 今回勉強会にあたって何を話すか悩ん だが、ちょうど直近読書会で読み始め た本の内容が自分が知らない分野で理 解度も低かったため、敢えてこの内容 で発表することに。あんま分かってな いけれどこれを機に学んだので是非 ツッコミを 2
ベクトルプログラミング入門はどんな 本? ベクトルプログラミング入門は SIMD(Single Instruction/Multiple Data)をC#で使う ための方法が書いてある本 System.Runtime.Intrinsics(イントリンシック)の命 令、Avxなどを使って1回の命令で256ビットを処理 するプログラムが書ける float(32ビット)型の配列なら256/32で8個まとめて 扱える。流石に常に8倍速くなる…わけではないが、 2倍から状況によっては8倍以上速くなることもあ る 3
unsafe System.Runtime.Intrinsicsの命令を扱う際 にunsafe、fixed、stackallocなどが出てく る 自分はC#歴はそこそこあるが、書いたこ とのないキーワードは未だにある。その 一つがunsafe 正直業務でパフォーマンスが理由で unsafeを使ったことはない 4
そもそも そもそもunsafeを使いたい理由っ て何だっけ? unsafe=ポインタを使いたいからみ たいな印象 Learnにもアンセーフコードとポイ ンタという内容がある 5
ポインタは速い? でも考えてみるとポインタだから速い というわけではない ただの受け方の問題なのでコピーが発 生しない参照で受けても速度劣化はな いはず。。 余談:最近C++でもポインタはめっき り書かなくなった 6
fixed そう考えるとポインタはunsafeを 使いたい理由とはならないはず。 ではfixedは?ポインタを使う時は fixedで受けている byte[] bytes = [1, 2, 3]; fixed (byte* pointerToFirst = bytes) 7
fixedステートメント fixed ステートメントを使うと、ガベージ コレク ターによる移動可能変数の再配置を防ぎ、その変 数へのポインターを宣言することができます。 固 定 (またはピン留め) された変数のアドレスは、そ のステートメントの実行中に変わりません。 宣言 されたポインターは、対応する fixed ステートメ ント内でのみ使用できます。 宣言されたポイン ターは読み取り専用であり、変更できません。 8
ガベージコレクション 色々書いてあるけれど、ポイント はGCが避けられること。++C++に もGCを避けることが速度最適化の コツと書いてある 9
GCを避ける つまりunsafeを使いたい理由が速 度向上ならGCを避けることが基本 GCを避ける方法の1つがfixedを 使ってピン止めすること もう1つGCを避ける方法があって stackallocを使うこと 10
stackalloc stackallocはスタックにメモリを割り 当てられる。スタックに割り当てられ たメモリは、そのメソッドが戻るとき に自動的に破棄される つまり参照型であってもヒープではな く、スタックからメモリを確保すれば GCは避けられる 11
Span<T> で、stackalloc。これはC#7.2から Span<T>構造体と併用すればunsafeを 使わなくても書けるようになった。 因みに本ではstackallocだけを使う所 もポインタで受けているのでunsafeは 使っている 12
Span<T> Span<T>を使うべき5つの理由がとても詳しい。 Span<T>は低レベルプログラミングのために存在す る パフォーマンスが良くアンマネージドヒープにも使 える unsafeがいらず、読み取り専用のReadOnlySpan<T> がある。 List<T>でいいと思ったら使う必要はない。 13
C++ 因みにC++でも20から同じ用途の spanが追加されていた alloca(標準ではない)。。でス タックからメモリ確保も可能 ただGCがないケースでどれぐらい パフォーマンス有利があるのか。。 14
ref struct Span<T>構造体の宣言を見るとref struct がある。これがミソ。 ref structの特徴はインスタンスをスタッ ク上にしか置けない ref構造体をフィールドとして持てるのは ref構造体だけ readonly ref structと書くことで読み取り 専用にできる 15
Quiz ちょっとしたクイズ!var の型はど うなる? var i = stackalloc int[4]; var i = (type == a) ? stackalloc int[4] : new int[4]; var i = (stackalloc int[4]); 16
計測 さらにC#はunsafeの方が速いという幻想にあるが、 unsafeにしなくても、低レベルの操作をする方法 がある。実際に計測してみたが、メモリアクセス にMarshalを使ったものが今も最速 この例はnewが20MB以上だったのでstackallocにし たらオーバーフローだったが1MBぐらいにしたら newよりも確実に速かった。[SkipLocalsInit]を使っ たらさらに少し速くなった 17
unsafeを使わずSIMD 本にこそunsafeを書かないコードは乗ってないが、.NET 7 こそがC# SIMDプログラミングを始めるのに最適である理 由が特に詳しい。 unsafeを書かずに以下を駆使して書いている ReadOnlySpan<byte>,MemoryMarshal,Unsafe(AddByteOffse t),Vector256(IsHardwareAccelerated,LoadUnsafe, StoreUn safe) Learnにも「NET 7 ではクロスプラットフォーム ハードウ ェアの組み込みが追加されました」と.NET 8のところに書 いてあるが、公式にこの辺のことが書いてある文章は見つ けられてない。。 18
C++ .NET8から512ビット扱えるVector512<T> も使えるようになった。同じ書き方でさ らに倍に出来るかも? 半精度浮動小数点数(Half)で512ビットな ら32回動作出来る ベクトルプログラミングの作者が書いた C#ではなさそうだが、512ビット専用の 本もある。 19
まとめ C#でunsafeを使いたい理由はパフォーマンス が大きな理由 GCを避けるにはstackallocを使ってspan<T>構 造体で受ければunsafeは必要ない。実際 stackalloc速い。 unsafeを使わなくてもMarshalクラスや Unsafeクラスは使えば速く出来る という感じでunsafeは今日も書かず 20
参考:引用 書籍 ベクトルプログラミング入門 512ビット・ベクトルプログラミング入門 Learn アンセーフ コード、ポインター fixed ステートメント ++C++ unsafe Span構造体 blog Span<T>を使うべき5つの理由 .NET 7こそがC# SIMDプログラミングを始めるのに最適である理由 C#はunsafeの方が速いという幻想 21