yosolaさんの指摘の通り調査用のコードにミスがありました。
このエントリの結果は間違いです。調査用のコードを修正したエントリをご覧ください。
--
メソッドの引数として構造体を渡す場合にちょっと気になったので調査用のコードを書いた。
比較したのは次の3つのケース。
- 構造体を値渡し
- 構造体を参照渡し(ref修飾子)
- 構造体が実装するインターフェースで渡す
using System;ksksts / junk / source — bitbucket.org
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 Program
{
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.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: 832 (1.00)
AddRefStruct: 31 (0.04)
AddInterface: 789 (0.95)
size: 10000
AddStruct: 1023 (1.00)
AddRefStruct: 119 (0.12)
AddInterface: 1803 (1.76)
size: 100000
AddStruct: 9916 (1.00)
AddRefStruct: 1100 (0.11)
AddInterface: 16277 (1.64)
size: 1000000
AddStruct: 102814 (1.00)
AddRefStruct: 11699 (0.11)
AddInterface: 159081 (1.55)
インターフェース経由が遅いのは疑問。型変換的な処理が入ってしまうのかな。
メソッドの引数に構造体を渡す場合に(変更しないのに)ref修飾子を使いまくるか、素直にコピーを発生させるか迷う。