何番煎じか。古い記事を引っ張り出してごめんなさい。
でも、わたし、気になります。
概要
気づいたら、C# が C++ の速度を凌駕している!
続) 気づいたら、C# が C++ の速度を凌駕している!
だそうです。
あちこちで検証されているようですが、半年遅れて私もやってみる。
「C# が C++ の速度を凌駕している」らしいので、C++側を高速化してみた
環境
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++ の速度を凌駕している!
に掲載されているコードを変えずに実行してみる(10回ループだけいれる)。
10回試行。
最遅 | 最速 | 平均 | |
---|---|---|---|
C#(safe) | 2449 | 2403 | 2418.5 |
C#(unsafe) | 1652 | 1620 | 1637.4 |
C++ | 1407 | 1359 | 1371.9 |
DDR4のせいなのかな。どうやっても肉薄しないんだよなぁ。
わたし、気になります
System.Diagnostics.Stopwatchには、
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++側を高速化してみた
のコメントにあるようにとんでも高速化ができてしまうので、もう少し画像処理ぽくしてみる。
近傍ピクセルが関係しないピクセル独立の処理は、画像処理にほとんどないです。
固定敷居値の2値化が、恐らく一番単純な処理かなと考えました。
コードは以下、できるだけ元から変わらないようにね。
C#
// 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++
// 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#で乱数初期化忘れてる、もういいや。