欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

面向接口编程,你考虑过性能吗?

程序员文章站 2022-12-20 17:35:22
大家在平时开发中大多都会遵循接口编程,这样就可以方便实现依赖注入也方便实现多态等各种小技巧,但这种是以牺牲性能为代价换取代码的灵活性,万物皆有阴阳,看你的应用场景进行取舍。 一:背景 1. 缘由 在项目的性能改造中,发现很多方法签名的返回值都是采用IEnumerable接口,比如下面这段代码: 2. ......

大家在平时开发中大多都会遵循接口编程,这样就可以方便实现依赖注入也方便实现多态等各种小技巧,但这种是以牺牲性能为代价换取代码的灵活性,万物皆有阴阳,看你的应用场景进行取舍。

一:背景

1. 缘由

在项目的性能改造中,发现很多方法签名的返回值都是采用ienumerable接口,比如下面这段代码:

        public static void main(string[] args)
        {
            var list = gethasemailcustomeridlist();

            foreach (var item in list){}

             console.readline();
        }

        public static ienumerable<int> gethasemailcustomeridlist()
        {
            return enumerable.range(1, 5000000).toarray();
        }

2. 有什么问题

这段代码乍一看也没啥什么性能问题,foreach迭代天经地义,这个还能怎么优化???

<1> 从msil中寻找问题

首先我们尽可能把原貌还原出来,简化后的msil如下。

.method public hidebysig static 
	void main (
		string[] args
	) cil managed 
{
	il_0009: callvirt instance class [mscorlib]system.collections.generic.ienumerator`1<!0> class [mscorlib]system.collections.generic.ienumerable`1<int32>::getenumerator()
	il_000e: stloc.1
	.try
	{
		il_000f: br.s il_001a
		// loop start (head: il_001a)
			il_0011: ldloc.1
			il_0012: callvirt instance !0 class [mscorlib]system.collections.generic.ienumerator`1<int32>::get_current()
			il_0017: stloc.2
			il_0018: nop
			il_0019: nop

			il_001a: ldloc.1
			il_001b: callvirt instance bool [mscorlib]system.collections.ienumerator::movenext()
			il_0020: brtrue.s il_0011
		// end loop

		il_0022: leave.s il_002f
	} // end .try
	finally
	{
		il_0024: ldloc.1
		il_0025: brfalse.s il_002e

		il_0027: ldloc.1
		il_0028: callvirt instance void [mscorlib]system.idisposable::dispose()
		il_002d: nop

		il_002e: endfinally
	} // end handler

	il_002f: ret
} // end of method program::main

从il中看到了标准的get_current,movenext,dispose 还有一个try,finally,一下子多了这么多方法和关键词,不就是一个简单的foreach迭代数组嘛? 至于搞的这么复杂嘛?这样在大数据下怎么快的起来?

还有一个奇葩的事,如果你仔细观察il代码,比如这句:[mscorlib]system.collections.generic.ienumerable``1<int32>::getenumerator(), 这个getenumerator前面是接口ienumerable,正常情况下应该是具体迭代类吧,按理说应该会调用array的getenumerator方法,如下所示。

[serializable]
[comvisible(true)]
[__dynamicallyinvokable]
public abstract class array : icloneable, ilist, icollection, ienumerable, istructuralcomparable, istructuralequatable 
{
    [__dynamicallyinvokable]
	public ienumerator getenumerator()
	{
		int lowerbound = getlowerbound(0);
		if (rank == 1 && lowerbound == 0)
		{
			return new szarrayenumerator(this);
		}
		return new arrayenumerator(this, lowerbound, length);
	}
}

<2> 从windbg中寻找问题

il中发现的第二个问题我特别好奇,