如何把结构体转换成对应的byte数组

  最近看到一个帖子,问的是怎么把自己定义的结构体转换成对应的byte数组,一般来说,都会想到用Marshal类来完成这个功能,其实还有一个方法也可以,那就是利用unsafe代码。

  先定义假想的一个值类型:

ContractedBlock.gif ExpandedBlockStart.gif Code
 1         [StructLayout(LayoutKind.Explicit, Size = 6)]
 2         public struct MyStruct
 3         {
 4             [FieldOffset(0)]
 5             public int Value;
 6             [FieldOffset(0)]
 7             public short Low;
 8             [FieldOffset(2)]
 9             public short Hi;
10             [FieldOffset(4)]
11             public short X;
12         }

  然后,定义一个公用方法签名:Action<MyStruct, Stream>,这个是为了方便之后的几种不同方式做性能测试。

  先来看看Marshal类是怎么做到的:

ContractedBlock.gif ExpandedBlockStart.gif Code
 1         public static void ByMarshal(object p_StructObj, Stream stream)
 2         {
 3             int stuctSize = Marshal.SizeOf(p_StructObj);
 4             byte[] stuctBytes = new byte[stuctSize];
 5             IntPtr structPtr = Marshal.AllocHGlobal(stuctSize);
 6             Marshal.StructureToPtr(p_StructObj, structPtr, false);
 7             Marshal.Copy(structPtr, stuctBytes, 0, stuctSize);
 8             Marshal.FreeHGlobal(structPtr);
 9             stream.Write(stuctBytes, 0, stuctSize);
10         }

  然后看看unsafe代码是如何做到的:

ContractedBlock.gif ExpandedBlockStart.gif Code
        public unsafe static void ByUnsafe(MyStruct s, Stream stream)
        {
            MyStruct
* ptr = &s;
            
for (int i = 0; i < sizeof(MyStruct); i++)
                stream.WriteByte(
*((byte*)ptr + i));
        }

  比较两者,可以发现,使用Marshal类出现了装箱,新建数组(因为签名定成了输出到流),分配堆空间,和释放对空间等,当然,实战中可以避免掉新建数组的代价,但是总体代价依然高于unsafe方法,当然,用Marshal可以很轻松的做到把任意值类型copy到数组里面,这一点上unsafe方法就吃亏了,因为unsafe方法必须要非常明确的支出结构体的具体类型。

  先来看一个性能对比:

ContractedBlock.gif ExpandedBlockStart.gif Code
 1         static void Main()
 2         {
 3             MyStruct s = new MyStruct();
 4             s.Low = 5;
 5             s.Hi = 10;
 6             s.X = 1;
 7             const int count = 1000000;
 8             MemoryStream ms = new MemoryStream();
 9             Stopwatch sw = new Stopwatch();
10             sw.Start();
11             for (int i = 0; i < count; i++)
12                 ByUnsafe(s, ms);
13             sw.Stop();
14             Console.WriteLine(sw.ElapsedMilliseconds);
15             ms = new MemoryStream();
16             GC.Collect(2);
17             GC.WaitForPendingFinalizers();
18             sw.Reset();
19             sw.Start();
20             for (int i = 0; i < count; i++)
21                 ByMarshal(s, ms);
22             sw.Stop();
23             Console.WriteLine(sw.ElapsedMilliseconds);
24         }

  在我机器(型号比较老。。。)上的运行结果是:135,718

  可以发现unsafe代码的性能优势非常明显,但是,如果不解决类型问题,unsafe的方式显然实用意义仍然小与Marshal方式,可能有人会想到用泛型,但是不幸的是泛型无法作用于unsafe代码,也就是说,T*是无法通过编译的,难道真的走投无路了吗?

  别忘了,.net还有一个杀手锏,LCG——轻量级代码生成,就是凭借LCG,IronPython的性能才能如此出众。我们无法在运用泛型指针,但是我们可以用泛型方法来生成对应类型的非泛型unsafe代码,来实现一个:

ContractedBlock.gif ExpandedBlockStart.gif Code
 1         public static Action<T, Stream> ByLcg<T>()
 2             where T : struct
 3         {
 4             DynamicMethod dm = new DynamicMethod(string.Empty, typeof(void),
 5                 new Type[] { typeof(T), typeof(Stream) }, typeof(T));
 6             var il = dm.GetILGenerator();
 7             var ptr = il.DeclareLocal(typeof(T).MakePointerType());
 8             var i = il.DeclareLocal(typeof(int));
 9 
10             var loopCondition = il.DefineLabel();
11             var loopBegin = il.DefineLabel();
12 
13             il.Emit(OpCodes.Ldarga_S, 0);
14             il.Emit(OpCodes.Conv_U);
15             il.Emit(OpCodes.Stloc_0);
16             il.Emit(OpCodes.Ldc_I4_0);
17             il.Emit(OpCodes.Stloc_1);
18             il.Emit(OpCodes.Br_S, loopCondition);
19             il.MarkLabel(loopBegin);
20             il.Emit(OpCodes.Ldarg_1);
21             il.Emit(OpCodes.Ldloc_0);
22             il.Emit(OpCodes.Ldloc_1);
23             il.Emit(OpCodes.Add);
24             il.Emit(OpCodes.Ldind_U1);
25             il.Emit(OpCodes.Callvirt, typeof(Stream).GetMethod("WriteByte"));
26             il.Emit(OpCodes.Ldloc_1);
27             il.Emit(OpCodes.Ldc_I4_1);
28             il.Emit(OpCodes.Add);
29             il.Emit(OpCodes.Stloc_1);
30             il.MarkLabel(loopCondition);
31             il.Emit(OpCodes.Ldloc_1);
32             il.Emit(OpCodes.Sizeof, typeof(T));
33             il.Emit(OpCodes.Clt);
34             il.Emit(OpCodes.Brtrue_S, loopBegin);
35             il.Emit(OpCodes.Ret);
36 
37             return (Action<T, Stream>)dm.CreateDelegate(typeof(Action<T, Stream>));
38         }

  有点长,但是性能如何哪?来改造一下测试代码:

ContractedBlock.gif ExpandedBlockStart.gif Code
        static void Main()
        {
            MyStruct s 
= new MyStruct();
            s.Low 
= 5;
            s.Hi 
= 10;
            s.X 
= 1;
            
const int count = 1000000;
            MemoryStream ms 
= new MemoryStream();
            Stopwatch sw 
= new Stopwatch();
            sw.Start();
            
for (int i = 0; i < count; i++)
                ByUnsafe(s, ms);
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            ms 
= new MemoryStream();
            GC.Collect(
2);
            GC.WaitForPendingFinalizers();
            sw.Reset();
            sw.Start();
            
for (int i = 0; i < count; i++)
                ByMarshal(s, ms);
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            ms 
= new MemoryStream();
            GC.Collect(
2);
            GC.WaitForPendingFinalizers();
            sw.Reset();
            sw.Start();
            var d 
= ByLcg<MyStruct>();
            
for (int i = 0; i < count; i++)
                d(s, ms);
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
        }

 1         public static Action<T, Stream> ByLcg<T>()
 2             where T : struct
 3         {
 4             DynamicMethod dm = new DynamicMethod(string.Empty, typeof(void),
 5                 new Type[] { typeof(T), typeof(Stream) }, typeof(T));
 6             var il = dm.GetILGenerator();
 7             var ptr = il.DeclareLocal(typeof(T).MakePointerType());
 8             var i = il.DeclareLocal(typeof(int));
 9 
10             var loopCondition = il.DefineLabel();
11             var loopBegin = il.DefineLabel();
12 
13             il.Emit(OpCodes.Ldarga_S, 0);
14             il.Emit(OpCodes.Conv_U);
15             il.Emit(OpCodes.Stloc_0);
16             il.Emit(OpCodes.Ldc_I4_0);
17             il.Emit(OpCodes.Stloc_1);
18             il.Emit(OpCodes.Br_S, loopCondition);
19             il.MarkLabel(loopBegin);
20             il.Emit(OpCodes.Ldarg_1);
21             il.Emit(OpCodes.Ldloc_0);
22             il.Emit(OpCodes.Ldloc_1);
23             il.Emit(OpCodes.Add);
24             il.Emit(OpCodes.Ldind_U1);
25             il.Emit(OpCodes.Callvirt, typeof(Stream).GetMethod("WriteByte"));
26             il.Emit(OpCodes.Ldloc_1);
27             il.Emit(OpCodes.Ldc_I4_1);
28             il.Emit(OpCodes.Add);
29             il.Emit(OpCodes.Stloc_1);
30             il.MarkLabel(loopCondition);
31             il.Emit(OpCodes.Ldloc_1);
32             il.Emit(OpCodes.Sizeof, typeof(T));
33             il.Emit(OpCodes.Clt);
34             il.Emit(OpCodes.Brtrue_S, loopBegin);
35             il.Emit(OpCodes.Ret);
36 
37             return (Action<T, Stream>)dm.CreateDelegate(typeof(Action<T, Stream>));
38         }

  看看结果如何:136,713,142

  真的这么强大吗?LCG本身的代价哪?可以把count修改成1,再次运行,得到的结果是:0,0,14

  也可以看到在循环次数太少的情况下,获得的性能优势不足以弥补LCG本身的消耗,那么多少是临界点哪?

  我的机器上,经过多次试验,发现21000时,Marshal方法和LCG方法的时间消耗相等,也就是说,当次数小于21000次是,Marshal占了上风,单是如果,超过21000次时,LCG带来的性能优势,就足以弥补代码生成的损失了。(当然,前提是生成的代码,以委托的方式被缓存下来)

  如果,无论次数多少,都要求最高性能的话,那就只有用unsafe的方法,把每一个需要用到的结构体都写一遍了,就是用人力换速度。。。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值