続続) 気づいたら、C# が C++ の速度を凌駕している!

先日の記事、

espresso3389.hatenablog.com

.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 した結果だけです。

f:id:espresso3389:20160505103943p:plain

OFF の時。

f:id:espresso3389:20160505104053p:plain

ON の時。

f:id:espresso3389:20160505104043p:plain

面白いですねー。

.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++ との速度差は、プライオリティの問題かもしれませんし、フレームワーク自体の問題かもしれませんし、その他のノイズかもしれません。調べるのが面倒なのでそこまでは追求しません。

結論

UWP 環境だと、フルの .NET Framework/C++ ほど速くありません。ただ、その差は、2~3%程度で、別に気にする程のパフォーマンス差は感じられません。

.NET Native は、配列でコードを書いている人に対しては絶大。unsafe とほぼ同じ程度のパフォーマンスが出る。逆に、 unsafe では、 .NET Native の前後での速度差はあんまり見られない。

これだと、C++ を棄てましょうどころか、 unsafe 要らなくね?っていう議論にまでなりそう。

個人的には、レガシーテクノロジー、レガシー言語、棄てようぜ路線に変更なし。