続続) 気づいたら、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 要らなくね?っていう議論にまでなりそう。

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

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

先日のこの記事ですが、

espresso3389.hatenablog.com

メモリ確保を関数の中でやってるのが悪いんじゃね?疑惑がありまして、そうなのであれば・・・ということで書き直しました。モダンな感じで。

C#

// Compile: csc /o /unsafe speedtest.cs
using System;
using System.Diagnostics;

class SpeedTest
{
  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 void time(Action action, int count = 100)
  {
    var tw = new Stopwatch();
    tw.Start();
    for (int i = 0; i < count; i++)
      action();
    tw.Stop();
    Console.WriteLine(tw.ElapsedMilliseconds);
  }

  static void Main(string[] args)
  {
    int w = 4321;
    int h = 6789;
    int stride = (w + 3) & ~3;
    var a = new byte[stride * h];

    time(() => test1(a, w, h, stride));
    time(() => test2(a, w, h, stride));
  }
}

C++

// Compile: cl /MD /Ox /EHsc speedtest.cpp
#include <stdio.h>
#include <windows.h>
#include <functional>
typedef unsigned char byte;

static void test2(byte* a, int w, int h, int stride)
{
  auto p0 = a;
  for (int y = 0; y < h; y++)
  {
    auto p = p0 + y * stride;
    for (int x = 0; x < w; x++)
    {
      p[x] = (byte)(x ^ y);
    }
  }
}

void time(std::function<void()> action, int count = 100)
{
  auto start = GetTickCount();
  for (int i = 0; i < count; i++)
    action();
  printf("%u\n", GetTickCount() - start);
}

int main()
{
  int w = 4321;
  int stride = (w + 3) & ~3;
  int h = 6789;
  auto a = new byte[stride * h];

  time([=]() { test2(a, w, h, stride); });

  delete[] a;
}

計測

さて、C# 版。

2533
1675

次に C++ 版。

1673

おおっと、肉薄する感じに!
正直、何回か動かした感じでは、どっちもどっちです。
どちらが速いとは一概に言えない感じで、誤差の範囲でしょうか。

メモリ確保の影響を除外すると、スピードは同じだと。

感想

正直、やはり、素晴らしい結果ですね。
スピードがほぼ同じ。

まぁ、もはや、 C++C# でコード書く上で何が違うの?って聞かれると、上のコードのように、
結構、1対1対応したコードをそのまま書けるので、意外と何にもハードルがないとも言えるんですが、
ライブラリの充実度や、GC、なんだかんだ言ってもラムダが使いにくい C++ とか考えると、
C# を使いたいです。

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

5年半程前に書いた、この記事。

espresso3389.hatenablog.com

C#というか、.NET Framework 4.6 は、 RyuJIT という新しい JIT の導入によって、64-bit 環境での実行が高速化されています。なので、さーて、少しは面白い結果が出るんじゃないかなぁーと。

blogs.msdn.microsoft.com

で、環境は、

Windows 10 Pro Insider Preview Build 14332
.NET Framework 4.6.1
Visual Studio 2015 Update 2
CPU: Intel Core i7-3770S 3.1GHz
Memory: 32GB

という環境です。ここで、実行してみました。

まずは、C#版。

3091
2282

上が配列。下が unsafe コード。unsafe が速くなってます。74%ぐらいの時間で終わるようになってる。
unsafe コードを書くモチベーションが上がります。better C として考えると、モチベーション上がりますねー!

さて、C++版。

2344

んん?あれっ?実行するプログラム間違った?
いや、間違ってない。

遅いじゃないですかー。C#に負けてるじゃないですかー。これ、64-bit 版です。
32-bit だったら?

2343

変わんねー。

C++ 敗北。
もう、C++使う理由がなくなる感じなのでは・・・。

仕事によっては、C++指定、あるいは、そうせざるを得ないことってありますけど、この結果は予想外です。個人的には、嬉しいです。

mac mini (Late 2014) の HDD を SSD に換装した

f:id:espresso3389:20160401230552j:plain

mac mini で作業をしているんですが、アプリの起動に10秒以上かかったり、brew install XXX とかすると数十分戻ってこなかったり、ウンザリしていました。まぁ、買うときにサブマシンという理由でお金をケチって HDD モデルにしちゃったのが問題なのですが、 SSD にしたら速くなるんじゃね?っていう予想に基づいて、 SSD への換装を行うことにしました。

最初にやっておくこと

クリーンインストールする気なら、この作業をする前に、OSのインストール USB を作っておきましょう。

  • AppStore で El Capitan とかをダウンロードしておきます。結構デカいので、時間がかかるかも知れません。

f:id:espresso3389:20160402024951p:plain

  • createinstallmedia で USB に起動ディスクを作成する

割と面倒なので、分からない人は他のサイトをググってください。
support.apple.com

必要な工具

肝心のドライバー。ネジが全部、所謂、トルクスネジ(Torx Screw)って奴になっていて、これに合うドライバーが必要なんですが、Amazonでググっても、 TR6 Torx とかはなかなか出てきません。なので、結論から書くと、
www.amazon.co.jp
f:id:espresso3389:20160401231520j:plain
これを買えば、全部OKです。Fixit にある、 T6、あるいは、TR6 は全て、このドライバーでは、 T6 が使えます。というか、 HDD を固定しているネジ以外は、全部、これだけで OK です。HDD は、T7 ですが、それも付属しているので、本当にこれだけ買えば分解に困る事はありません。
f:id:espresso3389:20160401232947j:plain

  • 柔らかいヘラ的

後は、裏蓋を開けるのに、柔らかいヘラ的なものが必要ですが、僕は、手元の接着剤に付属していたプラスチック製のヘラを使いました。

f:id:espresso3389:20160402000555j:plain

作業

まぁ、下の奴を見てくださいな。っていうのでほとんど終わっちゃうんですけど。

www.ifixit.com

ただ、ネジの数多いし、意外と面倒な部分が沢山あります。

まぁ、後は、英語が読めないとすぐには理解できなさそうなのは、ACのプラグ部分を反時計回りに90度回せっていう部分(先にその部分を固定している金属製のピンを抜く)とかぐらいかな?

そして、ロジックボードを引っ張り出す部分とかは、

f:id:espresso3389:20160402001619j:plain

みたいな感じで、適当な硬い棒があればそれでどうにかなるでしょう。

ちなみに、僕が買った SSD は、 SAMSUNG の 850 EVO 500GB。コスパはおそらく最強レベルではないかと。

f:id:espresso3389:20160402003347j:plain

そして、こちらが僕を苦しめた激遅 HDD。5400rpm ってこんなに遅かったんでしたっけ?

f:id:espresso3389:20160402032518j:plain

あと、 HDD をはずそうとしているときに、一瞬、間違って、この蓋のネジをはずそうとしてしまいました。これも、なにげに T6 トルクスネジなんですよね。

Visual Studio Code の拡張を作った

そもそものモチベーション

Visual Studio Code の拡張を作ったといっても、実験とかではありません。
僕は、今まで、というか、ここ数年の間に、

とエディタを乗り換えてきたというか、乗り換えきれずに来たのですが、いよいよ、VS.Codeと心中すべき時が来たかなぁと思って、自分が使っている拡張の類を移植しました。といっても、やりたいことはそんなに難しくなくて、基本的には、

  1. ランダムなテキストを出力する(パスワードとかね)
  2. UUID/GUIDを出力する
  3. Visual Studio にパスの通った cmd.exe を起動する

といった簡単なタスクだけなのですが、今回、このうちの 1, 2 を作成しました。
1に関しては、何故か、そういう趣旨のパッケージが見つからないのと、1,2に共通して言えるのは、マルチカーソル(Alt-クリック)対応が怪しいという問題、後は、VS.Codeの拡張一般として、何故か、キーボードだけで気持ちよく使わせてくれないっていう問題があるのです。

まぁ、この拡張は、今までも、Sublime Text用に、Python 2/3で記述し、ATOM用にはCoffee Scriptで書き直し、そして、今回は TypeScript で書き直しましたみたいな状況です。

どの言語が一番良いかと言えば、断然、TypeScriptでしょうね。node-uuid を使おうとして、 typings 関連でちょっと泣きそうになって、最後は適当に誤魔化すという手を取らざるを得なかったのは事実ですが・・・。

Visual Studio Marketplace

genrandom | Visual Studio Marketplace
genuuid | Visual Studio Marketplace

genuuid の方は、 WiX とかで大量の GUID が必要になった時などには、マルチカーソルが火を噴くぜって言うぐらいに便利です。

f:id:espresso3389:20160328030316g:plain

まぁ、いずれにしても、これらのエディタを使うためには、コマンドパレット使いこなそうぜっていうのが一番重要な事なんですが、コマンドパレットと気の利く拡張群があれば、 VS.Code は、今度こそ、真打ちとして、Sublime Text を抹殺してくれるでしょう。問題は起動が遅いことではあるんですがね・・・。