修正後のコードは次の通り。
修正したのはクラス名とPoint.AddRefStructのケースでの処理時間が加算ではなく代入になっていた点(104行目の"refStructTicks += sw.ElapsedTicks;")
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Text; | |
namespace StructInterface | |
{ | |
interface ITuple | |
{ | |
int X { get; set; } | |
int Y { get; set; } | |
int Z { get; set; } | |
} | |
struct Point : ITuple | |
{ | |
public int X { get; set; } | |
public int Y { get; set; } | |
public int Z { get; set; } | |
public Point(int x, int y, int z) : this() { X = x; Y = y; Z = z; } | |
public void AddStruct(Point point) | |
{ | |
X += point.X; | |
Y += point.Y; | |
Z += point.Z; | |
} | |
public void AddRefStruct(ref Point point) | |
{ | |
X += point.X; | |
Y += point.Y; | |
Z += point.Z; | |
} | |
public void AddInterface(ITuple tuple) | |
{ | |
X += tuple.X; | |
Y += tuple.Y; | |
Z += tuple.Z; | |
} | |
public override bool Equals(object obj) | |
{ | |
if (!(obj is Point)) | |
return false; | |
var p = (Point)obj; | |
var result = X == p.X && Y == p.Y && Z == p.Z; | |
return result; | |
} | |
public override int GetHashCode() | |
{ | |
var result = X ^ Y ^ Z; | |
return result; | |
} | |
public override string ToString() | |
{ | |
var str = string.Format("{0}, {1}, {2}, ", X, Y, Z); | |
return str; | |
} | |
} | |
class StructInterface | |
{ | |
static void Main(string[] args) | |
{ | |
for (var size = 1000; size <= 1000000; size *= 10) | |
Run(size); | |
} | |
static void Run(int size) | |
{ | |
Console.WriteLine("size: {0}", size); | |
var count = 10; | |
var sw = new Stopwatch(); | |
var structTicks = 0L; | |
var refStructTicks = 0L; | |
var interfaceTicks = 0L; | |
for (var c = 0; c <= count; c++) | |
{ | |
var random = new Random(count); | |
var points = Enumerable.Range(0, size) | |
.Select(_ => new Point(random.Next(), random.Next(), random.Next())) | |
.ToList<Point>(); | |
// Point.AddStruct | |
sw.Reset(); | |
sw.Start(); | |
var sp = new Point(); | |
foreach (var point in points) | |
sp.AddStruct(point); | |
sw.Stop(); | |
structTicks += sw.ElapsedTicks; | |
// Point.AddRefStruct | |
sw.Reset(); | |
sw.Start(); | |
var rsp = new Point(); | |
foreach (var point in points) | |
{ | |
var p = new Point(point.X, point.Y, point.Z); | |
rsp.AddRefStruct(ref p); | |
} | |
sw.Stop(); | |
refStructTicks += sw.ElapsedTicks; | |
// Point.AddInterface | |
sw.Reset(); | |
sw.Start(); | |
var ip = new Point(); | |
foreach (var point in points) | |
ip.AddInterface(point); | |
sw.Stop(); | |
interfaceTicks += sw.ElapsedTicks; | |
// correctness | |
if (!sp.Equals(rsp)) | |
Console.WriteLine("!sp.Equals(rsp)"); | |
if (!sp.Equals(ip)) | |
Console.WriteLine("!sp.Equals(ip)"); | |
} | |
Console.WriteLine("AddStruct: {0} ({1:F})", structTicks / count, structTicks / (double)structTicks); | |
Console.WriteLine("AddRefStruct: {0} ({1:F})", refStructTicks / count, refStructTicks / (double)structTicks); | |
Console.WriteLine("AddInterface: {0} ({1:F})", interfaceTicks / count, interfaceTicks / (double)structTicks); | |
} | |
} | |
} |
size: 1000読み取れること:
AddStruct: 104 (1.00)
AddRefStruct: 23 (0.22)
AddInterface: 148 (1.42)
size: 10000
AddStruct: 250 (1.00)
AddRefStruct: 220 (0.88)
AddInterface: 914 (3.65)
size: 100000
AddStruct: 2332 (1.00)
AddRefStruct: 2184 (0.94)
AddInterface: 7715 (3.31)
size: 1000000
AddStruct: 21937 (1.00)
AddRefStruct: 21208 (0.97)
AddInterface: 73993 (3.37)
- 繰り返し回数が少ない場合はインターフェースを経由しない参照渡しが速い
- ただし繰り返し回数を増やすとインターフェースを経由しない参照渡しと値渡しとの差は数パーセントまで小さくなる
- インターフェースを経由させると経由させない場合と比較して3倍以上遅くなる
この結果からメソッドに構造体を渡す場合は値渡しでいいと思う。ref修飾子を書いて参照渡しにしても数パーセントしか違いがないならわざわざそんなことをする意味はない。どうせほかの部分の処理時間で関係なくなる。
またインターフェース経由で渡した場合は遅いので実行速度のパフォーマンスが気になる個所ではそれは避けるようにしたほうがいい。
最後に、前回の計測と今回の計測では環境が違う。
前回はたぶんWindows 7, Visual Studio 2010、今回はWindows 10, Visual Studio 2015。それに加えてPCも違う。
前回の計測ではインターフェース経由でない値渡しの場合とインターフェース経由の場合での差が1.0倍から1.7倍くらいだったが、今回の計測ではそれが3倍以上になっているのは、Visual Studio(Visual C#)のコード生成処理が更新されていることが原因だと思う。