Contenu connexe Similaire à C# 7.2 with .NET Core 2.1 (20) C# 7.2 with .NET Core 2.12. 今日の話
• .NET Core 2.xでのパフォーマンス改善の話
• C# 7.x
• .NET Core 2.xのパフォーマンス改善の流れに同調
• 去年リリースされたけど、.NET Core 2.1が出てからが本番
3. .NET Core 2.1
• https://blogs.msdn.microsoft.com/dotnet/2018/02/27/announci
ng-net-core-2-1-preview-1/
• Global Tools
• Build Performance Improvements
• Minor-Version Roll-forward
• Sockets Performance and HTTP Managed Handler
• Span<T>, Memory<T> and friends ←今日の話これ
• Windows Compatibility Pack
12. • Span<T> (.NET Core 2.1)
• Hardware Intrinsics (.NET Core 2.1より後)
• おまけ: .NET Core 2.0での細かい最適化 軽い方から先に
14. 例えばこんなリファクタリング(2)
• Common Path Optimization (よく通る場所だけ優先最適化)
• 効果
• よく通る場所だけはインライン化が効いて速くなる
• (長かったり、throw文があるとインライン化されない)
if (9割方true)
短い処理
else
長い処理をメソッド抽出();
if (9割方true)
短い処理
else
長い処理
元 後
15. Q & A (1)
• Q. 「この手の細かい改善で5%くらい速くなります」といわれ
て、やる?
• A. 5%でもやりたい人という人は「世の中にはいる」
• こういうものは、限られた人数では最適化されづらい
• コミュニティ貢献が多いらしい
• ほんと細かい積み重ねはオープンソース強い
16. Q & A (2)
• Q. いまさら?
• A 1. .NET Frameworkからの移行で手一杯だったから…
• A 2. ほんと、いまさら
• 前述のとおり、こういうのはコミュニティ貢献が結構大きい
• 「いまさら」というなら、オープン化のタイミングが…
• 方針転換
• 最適化はコンパイラーの仕事ではないか → ホットパスは手作業してでも高速化
17. • Span<T> (.NET Core 2.1)
• Hardware Intrinsics (.NET Core 2.1より後)
• おまけ: .NET Core 2.0での細かい最適化
2.1から外れ
ちゃったんで…
おまけ程度に
18. C# 対 C言語
• 遅いのはガベージ コレクション(GC)?
• → No
• スタックの方が速いけどC#でもスタックを主体にしたコードは書ける
• そのための機能も年々増えてる(C# 7の構造体活用)
• 根本的に(Cでも)ヒープが必要なら、むしろGCは無茶苦茶速い
• じゃあ、あと、何が遅いのか?
• → 特定CPU向け最適化(CPU専用命令)
• 一番犠牲にしてるのはポータビリティ
• 要するに、速くしたければ#ifdefだらけ
19. CPU専用命令の例
• 例: SIMD
• single instruction multiple data
• Vector命令とか言われたり
• 例えばSSE(Pentium IIIの頃に導入された原始的なやつ)ですら
• 128ビットレジスターを持ってて
• float×4の四則演算を1命令でできる
• 単純に考えて個別に4回計算する場合の4倍速い
• オーバーヘッドの分を差し引いても倍は速い
単一 命令で 複数 データ を一気に処理
20. Hardware Instrinsics
• 専用命令の使い方
• 普通のコードを書いて、コンパイラーの最適化に任せる?
• 案外、その手のコードを生成してくれない
• コンパイラーが特殊な関数を用意
• 「この関数呼び出しは、この命令に置き換える」みたいなコンパイルを行う
#include <immintrin.h>
__m128 c = _mm_mul_ps(a, b)
コンパイラーによって
提供されるライブラリ
CPU専用命令に置き換わる
21. Hardware Instrinsics
• 専用命令の使い方
• 普通のコードを書いて、コンパイラーの最適化に任せる?
• 案外、その手のコードを生成してくれない
• コンパイラーが特殊な関数を用意
• 「この関数呼び出しは、この命令に置き換える」みたいなコンパイルを行う
#include <immintrin.h>
__m128 c = _mm_mul_ps(a, b)
こういう関数のことをintrinsicsという
• Intrinsic: 内在的な
• Hardware Intrinsics: 特定のCPUが内在的に
持っている機能
22. C#でHardware Intrinsics (1)
• パッケージ参照
• .NET Core 2.1 preview 1までは、標準に組み込まれる予定だった
• 2.1リリースには間に合わず、現状は
• パッケージ参照が必要
• 2.1リリース時点では「プレビュー」なパッケージになる予定
23. C#でHardware Intrinsics (2)
• コードの書き方
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
result = Sse.Multiply(Sse.Shuffle(a, a, 0x00), Sse.LoadVector128(p
result = Sse.Add(result, Sse.Multiply(Sse.Shuffle(a, a, 0x55), Sse
result = Sse.Add(result, Sse.Multiply(Sse.Shuffle(a, a, 0xaa), Sse
result = Sse.Add(result, Sse.Multiply(Sse.Shuffle(a, a, 0xff), Sse
・・・
命令セットの
名前のクラス
命令に対応
するメソッド
26. • Span<T> (.NET Core 2.1)
• Hardware Intrinsics (.NET Core 2.1より後)
• おまけ: .NET Core 2.0での細かい最適化
本題。
というか今日の話題で2.1
の話題なのはこれだけに
29. 文字列処理の現状
• Stream + stringでの処理
… 7B 0D 0A 20 20 22 69 64 22 3A 20 31 2C 0D 0A 20 20 22 6E 61 6D 65 22 3A 20 22 F0 9F 91 A6 F0 9F 8F BD 22 0D 0A 7D …
7B 0D 0A 20 20 22 69 64 22 3A 20 31 2C 0D 0A 20 20 22 6E 61 6D 65 22 3A 20 22 F0 9F 91 A6 F0 9F 8F BD 22 0D 0A 7D
Stream.Read(byte[] buffer, int offset, int count)
Encoding.GetString(byte[] bytes)
… 22 00 69 00 64 00 22 00 3A 00 20 00 31 00 2C 00 0D 00 0A 00 20 00 20 00 22 00 6E 00 61 00 6D 00 65 00 22 00 3A 00 20 00 …
string.Substring string.Substring
31 00
1
22 00 3D D8 66 DC 3C D8 FD DF 22 00
"👦🏽"
UTF-8
UTF-16
30. 文字列処理をどうしたいか(1)
• Nativeヒープを直接参照
… 7B 0D 0A 20 20 22 69 64 22 3A 20 31 2C 0D 0A 20 20 22 6E 61 6D 65 22 3A 20 22 F0 9F 91 A6 F0 9F 8F BD 22 0D 0A 7D …
IPipeReader.TryRead(out ReadResult result)
OwnedBuffer
参照管理のための小さいクラス
31. 文字列処理をどうしたいか(2)
• UTF-8のままで処理
… 7B 0D 0A 20 20 22 69 64 22 3A 20 31 2C 0D 0A 20 20 22 6E 61 6D 65 22 3A 20 22 F0 9F 91 A6 F0 9F 8F BD 22 0D 0A 7D …
OwnedBuffer Utf8String
new Utf8String(buffer.Span)
32. 文字列処理をどうしたいか(3)
• 部分参照でコピーを作らない
… 7B 0D 0A 20 20 22 69 64 22 3A 20 31 2C 0D 0A 20 20 22 6E 61 6D 65 22 3A 20 22 F0 9F 91 A6 F0 9F 8F BD 22 0D 0A 7D …
OwnedBuffer Utf8String
new Utf8String(buffer.Span)
Substring
Utf8String
Utf8String
1 "👦🏽"
スタックしか使わない
Substringもコピーを作らない
直接intにParse
すればヒープ不要
ToString時に
初めてヒープ確保
33. 何が必要か
• メモリの一部分を参照
• Nativeヒープを直接
• stringの一部分
• 参照を前提にしたI/O
• UTF-8を直接読み書き
Span<T>型
Pipelines
Utf8String
.NET Core 2.1
その後
依存関係
C# 7.0~7.2
• ref戻り値
• ref安全ルール
Span<T>型
Pipelines
Utf8String
今ここ
34. Span<T>: 「範囲」を参照
• 論理的には、参照+長さ
… 7B 0D 0A 20 20 22 69 64 22 3A 20 31 2C 0D 0A 20 20 22 6E 61 6D 65 22 3A 20 22 F0 9F 91 A6 F0 9F 8F BD 22 0D 0A 7D …
1
struct Span<T>
{
ref T Pointer;
int Length;
}
どこから
何要素
例 ここから1バイト ここから10バイト
" "
36. C# 7.2: Span安全ルール
• スタック(ローカル変数)の参照を外に返してはいけない
ref int Ok(ref int x)
{
return ref x;
}
ref int Ng()
{
int x;
return ref x;
}
Span<int> Ok(Span<int> x)
{
return x;
}
Span<int> Ng()
{
Span<int> x = stackalloc int[1];
return x;
}
ref安全ルール(C# 7.0) Span<T>※安全ルール(C# 7.2)
ローカル変数
(メソッドを抜けると消える)
消えるものの参照
(返そうとするとエラー)
※ 正確にはSpan<T>専用ではなくて、ref構造体に対して掛かる制限
37. C# 7.2: 安全なstackalloc
• 例: 頻出数字の検索
static int MostFrequentDigit(string s)
{
var digits = new int[10];
foreach (var c in s)
{
var d = c - '0';
if ((uint)d < 10) ++digits[d];
}
var max = 0;
for (int i = 1; i < 10; i++)
if (digits[max] < digits[i]) max = i;
return max;
}
0~9の文字の頻度をカウント
するための一時バッファー
配列のせいでヒープ確保
(あんまり好ましくない)
38. C# 7.2: 安全なstackalloc
• 例: 頻出数字の検索
static int MostFrequentDigit(string s)
{
Span<int> digits = stackalloc int[10];
foreach (var c in s)
{
var d = c - '0';
if ((uint)d < 10) ++digits[d];
}
var max = 0;
for (int i = 1; i < 10; i++)
if (digits[max] < digits[i]) max = i;
return max;
}
一時バッファーをnewから
stackallocに変更
ヒープ確保がなくなる
(速い)
unsafe不要
実際、安全
• 範囲チェックあり
• 外に返せないルールあり
40. デモ
• Split → Join
• string.Splitやstring.Joinは一時的なオブジェクトを確保しまくる
• Span<T>でヒープ確保なくSplit, Join
• (ただし、使い勝手はあまり良くない)
• https://github.com/ufcpp/UfcppSample/tree/master/Demo/2018/Span
Performance/StringManipulation
Notes de l'éditeur 最近日本でも「並列処理の言語比較」で見事に炎上したやつがいたけども。まあ、言語比較とか手を出すの危険。 数値計算処理とかではほんとスクリプト言語は遅い 「おっ、2.1に入ってんじゃーん」とか思って喜んでスライド作ってたら、「間に合わない。一度外す」って話になった… 適当なアップローダーに上げたJSONファイルをダウンロードした結果をfiddlerで覗いたもの