最近看到一个帖子,问的是怎么把自己定义的结构体转换成对应的byte数组,一般来说,都会想到用Marshal类来完成这个功能,其实还有一个方法也可以,那就是利用unsafe代码。
先定义假想的一个值类型:
然后,定义一个公用方法签名:Action<MyStruct, Stream>,这个是为了方便之后的几种不同方式做性能测试。
先来看看Marshal类是怎么做到的:
然后看看unsafe代码是如何做到的:
}
比较两者,可以发现,使用Marshal类出现了装箱,新建数组(因为签名定成了输出到流),分配堆空间,和释放对空间等,当然,实战中可以避免掉新建数组的代价,但是总体代价依然高于unsafe方法,当然,用Marshal可以很轻松的做到把任意值类型copy到数组里面,这一点上unsafe方法就吃亏了,因为unsafe方法必须要非常明确的支出结构体的具体类型。
先来看一个性能对比:
在我机器(型号比较老。。。)上的运行结果是:135,718
可以发现unsafe代码的性能优势非常明显,但是,如果不解决类型问题,unsafe的方式显然实用意义仍然小与Marshal方式,可能有人会想到用泛型,但是不幸的是泛型无法作用于unsafe代码,也就是说,T*是无法通过编译的,难道真的走投无路了吗?
别忘了,.net还有一个杀手锏,LCG——轻量级代码生成,就是凭借LCG,IronPython的性能才能如此出众。我们无法在运用泛型指针,但是我们可以用泛型方法来生成对应类型的非泛型unsafe代码,来实现一个:
有点长,但是性能如何哪?来改造一下测试代码:
d(s, ms);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
}
看看结果如何:136,713,142
真的这么强大吗?LCG本身的代价哪?可以把count修改成1,再次运行,得到的结果是:0,0,14
也可以看到在循环次数太少的情况下,获得的性能优势不足以弥补LCG本身的消耗,那么多少是临界点哪?
我的机器上,经过多次试验,发现21000时,Marshal方法和LCG方法的时间消耗相等,也就是说,当次数小于21000次是,Marshal占了上风,单是如果,超过21000次时,LCG带来的性能优势,就足以弥补代码生成的损失了。(当然,前提是生成的代码,以委托的方式被缓存下来)
如果,无论次数多少,都要求最高性能的话,那就只有用unsafe的方法,把每一个需要用到的结构体都写一遍了,就是用人力换速度。。。