続続) 気づいたら、C# が C++ の速度を凌駕している!
先日の記事、
.NET Native だとどうよ?っていう話があったので、試してみました。
コードは趣旨を変更しない範囲で弄りました。
スレッドプールのプライオリティとかどうなってんの?っていう疑問はあるんですが、
実行した感じ、それらの影響はなさそうなので、結構適当です。
概要だけ提示できれば良いので、XAMLは提示しませんが、普通にバインドして表示しているだけです。
using System; using System.Collections.ObjectModel; using System.Diagnostics; using System.Threading.Tasks; using Windows.UI.Xaml.Controls; namespace App1 { public sealed partial class MainPage : Page { public ObservableCollection<double> Times { get; } = new ObservableCollection<double>(); public MainPage() { this.DataContext = this; this.InitializeComponent(); Loaded += (s, e) => workOnBackground(); } async Task workOnBackground() { double t1 = 0, t2 = 0; await Task.Factory.StartNew(() => { int w = 4321; int h = 6789; int stride = (w + 3) & ~3; var a = new byte[stride * h]; t1 = time(() => test1(a, w, h, stride)); t2 = time(() => test2(a, w, h, stride)); }); Times.Add(t1); Times.Add(t2); } static void test1(byte[] a, int w, int h, int stride) { for (int y = 0; y < h; y++) { int offset = y * stride; for (int x = 0; x < w; x++) { a[x + offset] = (byte)(x ^ y); } } } static unsafe void test2(byte[] a, int w, int h, int stride) { fixed (byte* p0 = a) { for (int y = 0; y < h; y++) { byte* p = p0 + y * stride; for (int x = 0; x < w; x++) { p[x] = (byte)(x ^ y); } } } } static long time(Action action, int count = 100) { var tw = new Stopwatch(); tw.Start(); for (int i = 0; i < count; i++) action(); tw.Stop(); return tw.ElapsedMilliseconds; } } }
結果
32-bit/64-bit で特に何の差も見れませんでしたので、そこに関しては割愛。
結局、ここにある、 "Compile with .NET Native tool chain" を ON/OFF した結果だけです。
OFF の時。
ON の時。
面白いですねー。
.NET Native が OFF の場合の速度の傾向はフルの .NET Framework 4.6 のコードと同じですね。配列は遅く、 unsafe は速い。ただ、前回調べた、 .NET Framework 4.6 や C++ と比べると、微妙に遅いですね。
ところが、 .NET Native を ON にすると、配列と unsafe の速度差がグッと縮む。速度的に、2~3%程度の差しかありません。この差だと、 unsafe コードを無理して使わないでも良いような気がする速度です。
この差を見せられると、時間をかけてコンパイルできる AOT コンパイラは馬鹿に出来ないなぁと思わざるを得ません。
.NET Framework 4.6 や C++ との速度差は、プライオリティの問題かもしれませんし、フレームワーク自体の問題かもしれませんし、その他のノイズかもしれません。調べるのが面倒なのでそこまでは追求しません。