「C# が C++ の速度を凌駕している」らしいので、私も試してみる

何番煎じか。古い記事を引っ張り出してごめんなさい。
でも、わたし、気になります。
# 概要
[気づいたら、C# が C++ の速度を凌駕している！](http://espresso3389.hatenablog.com/entry/2016/05/02/081308)
[続) 気づいたら、C# が C++ の速度を凌駕している！](http://espresso3389.hatenablog.com/entry/2016/05/04/122936)
だそうです。
あちこちで検証されているようですが、半年遅れて私もやってみる。
[「C# が C++ の速度を凌駕している」らしいので、C++側を高速化してみた](http://qiita.com/YSRKEN/items/8b53b1a783191503560c)

# 環境
>Windows 10 Pro バージョン 1607(OSビルド 14393.321) 64bit版
>.NET Framework 4.6.1
>Visual Studio 2015 Update 3
>CPU: Intel Core i7-6700T
>Memory: 16GB (DDR4)
>csc.exe バージョン 4.6.1586.0
>cl.exe バージョン 19.00.24215 for x64

# とりあえずそのまま
[続) 気づいたら、C# が C++ の速度を凌駕している！](http://espresso3389.hatenablog.com/entry/2016/05/04/122936)
に掲載されているコードを変えずに実行してみる(10回ループだけいれる)。

10回試行。

| |最遅 |最速 |平均 |
|:-------:|:---:|:--:|:---:|
|C#(safe) |2449 |2403 |2418.5 |
|C#(unsafe) |1652 |1620 |1637.4 |
|C++ |1407 |1359 |1371.9 |

DDR4のせいなのかな。どうやっても肉薄しないんだよなぁ。

# わたし、気になります
[System.Diagnostics.Stopwatch](https://msdn.microsoft.com/ja-jp/library/system.diagnostics.stopwatch(v=vs.110).aspx)には、
>The Stopwatch measures elapsed time by counting timer ticks in the underlying timer mechanism.If the installed hardware and operating system support a high-resolution performance counter, then the Stopwatch class uses that counter to measure elapsed time.

高精度タイマーがあれば使うとあります。

WindowsAPIのプログラム経験がある方は、C++のコードを見てぴんとくると思います。
というわけで、**GetTickCount**をstd::chrono::high_resolution_clockに置き換えましょう。QueryPerformanceCounterも高精度タイマがあれば使いますから。

| |最遅 |最速 |平均 |
|:-:|:-:|:-:|:-:|
|C++ |1375 |1354 |1364.7 |

上振れしない分だけさらに差が開きましたね。

おそらく追記します。
# 追記
## もう少し画像処理
[「C# が C++ の速度を凌駕している」らしいので、C++側を高速化してみた](http://qiita.com/YSRKEN/items/8b53b1a783191503560c)
のコメントにあるようにとんでも高速化ができてしまうので、もう少し画像処理ぽくしてみる。
近傍ピクセルが関係しないピクセル独立の処理は、画像処理にほとんどないです。
固定敷居値の2値化が、恐らく一番単純な処理かなと考えました。
コードは以下、できるだけ元から変わらないようにね。

C#

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

class SpeedTest
{
    private const byte threshold = 127;
    private const byte Black = (byte)0x00U;
    private const byte White = (byte)0xFFU;

    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] = (threshold<a[x+offset])? White : Black;
            }
        }
    }

    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] = (threshold<p[x])? White : Black;
                }
            }
        }
    }

    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);
    }

    void fill(byte[] bytes)
    {
        Random random = new Random(DateTime.UtcNow.Millisecond);
        for(int i = 0; i<bytes.Length; ++i) {
            bytes[i] = (byte)random.Next();
        }
    }

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

        for(int i = 0; i<10; ++i) {
            time(() => test1(a, w, h, stride));
        }
        Console.WriteLine();
        for(int i = 0; i<10; ++i) {
            time(() => test2(a, w, h, stride));
        }
    }
}
```

C++

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

static void test(byte* a, int w, int h, int stride)
{
    static const byte threshold = 127;
    auto p0 = a;
    for(int y = 0; y < h; y++){
        auto p = p0 + y * stride;
        for(int x = 0; x < w; x++){
            p[x] = (threshold<p[x])? 0xFFU : 0x00U;
        }
    }
}

void time(std::function<void()> action, int count = 100)
{
    std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
    //auto start = GetTickCount();
    for(int i = 0; i < count; i++)
        action();

    std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
    std::chrono::milliseconds msec = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    printf("%lld\n", msec.count());
    //printf("%u\n", GetTickCount() - start);
}

void fill(int size, byte* bytes)
{
    std::random_device devrand;
    std::mt19937 random(devrand());
    for(int i=0; i<size; ++i){
        bytes[i] = static_cast<byte>(random());
    }
}

int main()
{
    int w = 4321;
    int stride = (w + 3) & ~3;
    int h = 6789;
    auto a = new byte[stride * h];
    for(int i=0; i<10; ++i){
        fill(w*stride, a);
        time([=]() { test(a, w, h, stride); });
    }
    delete[] a;

    return 0;
}
```

### 結果
| |最遅 |最速 |平均 |
|:-------:|:---:|:--:|:---:|
|C#(safe) |3767 |3724 |3746.8 |
|C#(unsafe) |2821 |2806 |2815.1 |
|C++ |1882 |1857 |1866.5 |

メモリアクセスがもっと遅ければ肉薄する気がしてきた。
あ、C#で乱数初期化忘れてる、もういいや。