型変換による実行時コスト

.NETで外部ベンダー提供の DLL を使っているのだけれど、その中のあるメソッドの返値が符号付バイトの配列 sbyte で返ってくる。これを BinaryWriter.Writeを使って書き込みたいのだけれど、あいにくこのメソッドには sbyte を受け入れるオーバーロードがない。Write(sbyte)で 1 バイトずつ書き込んでもいいんだけど、毎秒3000回近く呼び出される箇所なのでメソッド呼び出しにかかるコストが気になる。

そこで BinaryWriter.Write(byte) で書き込むことにし、sbyte を符号無バイトの配列 byte[] に変換する方法を探る。しかし System.Convert や System.BitConverter のリファレンスを見ても適当なメソッドが無い。そこで http://www.ujihara.jp/CodeTips/VJSharp/ja/jslanguage.html を見ると、特にメソッドなど使わず、型変換をかませば目的を達成できるようだ。

この処理にかかる時間が配列の長さに比例するかどうかが気になる。リンク先には

JIT後のコードでは、変換の実行時コストはない

とあるので、中間言語のレベルで見てみよう。次のような C# のプログラムを用意した。


// ByteTest.cs
using System;

class ByteTest
{
public static void Main(string[] argv)
{
byte[] bb = { 0, 50, 100, 150, 200, 250};
sbyte[] ss = (sbyte[])((object)bb);
}
}

object 型までアップキャストしてから打って返してダウンキャストする。このプログラムを実行可能形式にコンパイル後、中間言語に逆アセンブルしてみる。

>csc ByteTest.cs
>ildasm ByteTest.exe /out=ByteTest.il
できあがった ByteTest.il はテキストエディタで読める。ごちゃごちゃといろいろなおまじないがまとわりついているが、本体はここだろう。

.method public hidebysig static void Main(string[] argv) cil managed
{
.entrypoint
// Code size 27 (0x1b)
.maxstack 3
.locals init (uint8[] V_0,
int8[] V_1)
IL_0000: nop
IL_0001: ldc.i4.6
IL_0002: newarr [mscorlib]System.Byte
IL_0007: dup
IL_0008: ldtoken field valuetype '{934617AD-5A3F-4DA5-B4AC-E2247CF20B6B}'/'__StaticArrayInitTypeSize=6' '{934617AD-5A3F-4DA5-B4AC-E2247CF20B6B}'::'$$method0x6000001-1'
IL_000d: call void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [mscorlib]System.Array,
valuetype [mscorlib]System.RuntimeFieldHandle)
IL_0012: stloc.0
IL_0013: ldloc.0
IL_0014: castclass int8[]
IL_0019: stloc.1
IL_001a: ret
} // end of method ByteTest::Main
IL_0014 を見ると、キャストが一発で行なわれていることが分かる。ここまでやって思ったけど、castclass が配列の長さに依存しない定数時間で終わる保証はないので、問題の解決になってない気が・・・。 IL の仕様を調べねば。