2016/02/20

C#の構造体の引き渡し方によるパフォーマンスの違い(修正版)

C#の構造体の引き渡し方によるパフォーマンスの違いの調査用コードにミスがあるとyosolaさんからコメントで指摘をもらっていたのでその修正版のエントリ。

修正後のコードは次の通り。
修正したのはクラス名とPoint.AddRefStructのケースでの処理時間が加算ではなく代入になっていた点(104行目の"refStructTicks += sw.ElapsedTicks;")
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#)のコード生成処理が更新されていることが原因だと思う。

2016/02/19

「公的個人認証クライアントソフト(JPKI利用者ソフト) Ver3.0」で個人番号カードの証明書確認をすると「予期せぬエラーが発生しました。(CryptAcquireContext:0x80090006)」となる場合の対処法

来年も同じことでつまづきそうなので書いておく。
Windows Updateで配信される更新を適用している環境ではこれは起こらないと思う。
自分の場合は年一回の確定申告のために常用環境にソフトをインストールするのがイヤで、手元にあったプレーンなWindows 7 SP1の仮想環境で確定申告をしようとしたところ引っかかった。

まずは現象の再現。
  1. 「公的個人認証サービス」-「JPKI利用者ソフト」を起動する。
  2. 表示されたダイアログの「証明書をみる」の「自分の証明書」をクリックする。
  3. 表示されたダイアログの「署名用電子証明書」を選択して「OK」をクリックする。
  4. ここで「エラーが発生しました。予期せぬエラーが発生しました。(CryptAcquireContext:0x80090006)」というダイアログが表示される。

もちろんこれではe-Taxで申告書等を提出できない。
試行錯誤してしまったのはスルーして、「公的個人認証クライアントソフト Ver3.0」の動作環境を確認すると更新プログラムの適用が必要とのこと。
前提となるWindows更新プログラム
 
利用者クライアントソフト(Windows版)は、SHA256に対応する為、 下記のWindows更新プログラムの適用が必須です。
Windows8.1の場合
  
Windowsの更新プログラム(KB2919355)
Windows7/VISTAの場合
  
Windowsの更新プログラム(KB3035131)
  
Windowsの更新プログラム(KB3033929)
Windows 8.1の場合とWindows 7/Vistaの場合とx86/x64に注意して上記のWindows更新プログラムを適用することで、「公的個人認証クライアントソフト Ver3.0」が正常に動作してe-Taxで申請書等を提出できた。