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

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

一:背景

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.1IL_0012: callvirt instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()IL_0017: stloc.2IL_0018: nopIL_0019: nopIL_001a: ldloc.1IL_001b: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()IL_0020: brtrue.s IL_0011// end loopIL_0022: leave.s IL_002f} // end .tryfinally{IL_0024: ldloc.1IL_0025: brfalse.s IL_002eIL_0027: ldloc.1IL_0028: callvirt instance void [mscorlib]System.IDisposable::Dispose()IL_002d: nopIL_002e: endfinally} // end handlerIL_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中发现的第二个问题我特别好奇,????????,我们到托管堆上去看下到底是哪一个具体类调用了GetEnumerator()方法。

!clrstack -l > !do xx 到线程栈上抓list变量


0:000> !clrstack -l
000000229e3feda0 00007ff889e40951 *** WARNING: Unable to verify checksum for ConsoleApp2.exe
ConsoleApp2.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp2\Program.cs @ 32]LOCALS:0x000000229e3fede8 = 0x0000019bf33b9a880x000000229e3fede0 = 0x0000019be33b2d900x000000229e3fedfc = 0x00000000004c4b400:000> !do 0x0000019be33b2d90
Name:        System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]]
MethodTable: 00007ff8e8d36d18
EEClass:     00007ff8e7cf5640
Size:        32(0x20) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:MT    Field   Offset                 Type VT     Attr            Value Name
00007ff8e7a98538  4002ffe        8       System.Int32[]  0 instance 0000019bf33b9a88 _array
00007ff8e7a985a0  4002fff       10         System.Int32  1 instance          5000000 _index
00007ff8e7a985a0  4003000       14         System.Int32  1 instance          5000000 _endIndex
00007ff8e8d36d18  4003001        0 ...Int32, mscorlib]]  0   shared           static Empty>> Domain:Value dynamic statics NYI 0000019be1893a80:NotInit  <<

居然有这么一个类型 Name: System.SZArrayHelper+SZGenericArrayEnumerator,然来是JIT捣的鬼,生成了这么一个SZGenericArrayEnumerator类型,接下来把它的方法表打出来看看里面都有啥方法。


0:000> !dumpmt -md 00007ff8e8d36d18
EEClass:         00007ff8e7cf5640
Module:          00007ff8e7a71000
Name:            System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]]
mdToken:         0000000002000a98
File:            C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
BaseSize:        0x20
ComponentSize:   0x0
Slots in VTable: 11
Number of IFaces in IFaceMap: 3
--------------------------------------
MethodDesc TableEntry       MethodDesc    JIT Name
00007ff8e7ff2450 00007ff8e7a78de8 PreJIT System.Object.ToString()
00007ff8e800cc60 00007ff8e7c3b9b0 PreJIT System.Object.Equals(System.Object)
00007ff8e7ff2090 00007ff8e7c3b9d8 PreJIT System.Object.GetHashCode()
00007ff8e7fef420 00007ff8e7c3b9e0 PreJIT System.Object.Finalize()
00007ff8e8b99fd0 00007ff8e7ebf388 PreJIT System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]].MoveNext()
00007ff8e8b99f90 00007ff8e7ebf390 PreJIT System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]].get_Current()
00007ff8e8b99f60 00007ff8e7ebf398 PreJIT System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]].System.Collections.IEnumerator.get_Current()
00007ff8e8b99f50 00007ff8e7ebf3a0 PreJIT System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]].System.Collections.IEnumerator.Reset()
00007ff8e8b99f40 00007ff8e7ebf3a8 PreJIT System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]].Dispose()
00007ff8e8b99ef0 00007ff8e7ebf3b0 PreJIT System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]]..cctor()
00007ff8e8b99ff0 00007ff8e7ebf380 PreJIT System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Int32, mscorlib]]..ctor(Int32[], Int32)

可以看到这是一个标准的迭代类,这性能又被拖累了。。。

二:优化性能

综合上面分析,貌似问题出在了 foreach 和 IEnumerable<int>这两个方面。

1. IEnumerable 替换 int[], foreach改成for

知道了这两点,接下来把代码修改如下:

        public static void Main(string[] args){var list = GetHasEmailCustomerIDList();for (int i = 0; i < list.Length; i++) { }Console.ReadLine();}public static int[] GetHasEmailCustomerIDList(){return Enumerable.Range(1, 5000000).ToArray();}.method public hidebysig static void Main (string[] args) cil managed
{// (no C# code)IL_0000: nop// int[] hasEmailCustomerIDList = GetHasEmailCustomerIDList();IL_0001: call int32[] ConsoleApp2.Program::GetHasEmailCustomerIDList()IL_0006: stloc.0// for (int i = 0; i < hasEmailCustomerIDList.Length; i++)IL_0007: ldc.i4.0IL_0008: stloc.1// (no C# code)IL_0009: br.s IL_0011// loop start (head: IL_0011)IL_000b: nopIL_000c: nop// for (int i = 0; i < hasEmailCustomerIDList.Length; i++)IL_000d: ldloc.1IL_000e: ldc.i4.1IL_000f: addIL_0010: stloc.1// for (int i = 0; i < hasEmailCustomerIDList.Length; i++)IL_0011: ldloc.1IL_0012: ldloc.0IL_0013: ldlenIL_0014: conv.i4IL_0015: cltIL_0017: stloc.2IL_0018: ldloc.2// (no C# code)IL_0019: brtrue.s IL_000b// end loop// Console.ReadLine();IL_001b: call string [mscorlib]System.Console::ReadLine()// (no C# code)IL_0020: pop// }IL_0021: ret
} // end of method Program::Main

可以看到上面的IL指令都是非常基础的指令,大多都有CPU指令直接提供支持,非常简洁,大爱~~~

这里有一点要注意:我后来观察foreach不需要改成for,vs编辑器在底层帮我们转换了,看的出来foreach在迭代数组类型的时候还是非常智能的,知道怎么帮助我们优化。。。修改代码如下:

public static void Main(string[] args){var list = GetHasEmailCustomerIDList();//for (int i = 0; i < list.Length; i++) { }foreach (var item in list) { }Console.ReadLine();}.method public hidebysig static void Main (string[] args) cil managed
{// (no C# code)IL_0000: nop// int[] hasEmailCustomerIDList = GetHasEmailCustomerIDList();IL_0001: call int32[] ConsoleApp2.Program::GetHasEmailCustomerIDList()IL_0006: stloc.0// (no C# code)IL_0007: nop// int[] array = hasEmailCustomerIDList;IL_0008: ldloc.0IL_0009: stloc.1// for (int i = 0; i < array.Length; i++)IL_000a: ldc.i4.0IL_000b: stloc.2// (no C# code)IL_000c: br.s IL_0018// loop start (head: IL_0018)// int num = array[i];IL_000e: ldloc.1IL_000f: ldloc.2IL_0010: ldelem.i4// (no C# code)IL_0011: stloc.3IL_0012: nopIL_0013: nop// for (int i = 0; i < array.Length; i++)IL_0014: ldloc.2IL_0015: ldc.i4.1IL_0016: addIL_0017: stloc.2// for (int i = 0; i < array.Length; i++)IL_0018: ldloc.2IL_0019: ldloc.1IL_001a: ldlenIL_001b: conv.i4IL_001c: blt.s IL_000e// end loop// Console.ReadLine();IL_001e: call string [mscorlib]System.Console::ReadLine()// (no C# code)IL_0023: pop// }IL_0024: ret
} // end of method Program::Main

2. 代码测试

微观方面已经带大家分析过了,接下来宏观测试两种方式的性能到底相差多少,每一个方法我都做10次性能对比。

        public static void Main(string[] args){var arr = GetHasEmailCustomerIDArray();for (int i = 0; i < 10; i++){var watch = Stopwatch.StartNew();foreach (var item in arr) { }watch.Stop();Console.WriteLine($"i={i},时间:{watch.ElapsedMilliseconds}");}Console.WriteLine("---------------");var list = arr as IEnumerable<int>;for (int i = 0; i < 10; i++){var watch = Stopwatch.StartNew();foreach (var item in list) { }watch.Stop();Console.WriteLine($"i={i},时间:{watch.ElapsedMilliseconds}");}Console.ReadLine();}public static int[] GetHasEmailCustomerIDArray(){return Enumerable.Range(1, 5000000).ToArray();}i=0,时间:10
i=1,时间:10
i=2,时间:10
i=3,时间:9
i=4,时间:9
i=5,时间:9
i=6,时间:10
i=7,时间:10
i=8,时间:12
i=9,时间:12
---------------
i=0,时间:45
i=1,时间:37
i=2,时间:35
i=3,时间:35
i=4,时间:37
i=5,时间:35
i=6,时间:36
i=7,时间:37
i=8,时间:35
i=9,时间:36

难以置信的是居然有3-4倍的差距。。。这就是用灵活性换取性能的代价????????????

好了,本篇就说到这里,希望对你有帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310211.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[Java网络编程]UDP通信程序练习

代码如下: package UdpPracticePack;import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException;pub…

打造更好用的 EF 自动审计

打造更好用的 EF 自动审计Intro上次基于 EF Core 实现了一个自动审计的功能&#xff0c;详细可以参考 EF Core 数据变更自动审计设计&#xff0c;虽然说多数情况下可以适用&#xff0c;但是因为要显式继承于一个 AuditDbContextBase 或 AuditDbContext&#xff0c;所以对代码的…

[Java网络编程基础]UDP发送和接收数据

代码如下: package InetAddressPack;import java.io.IOException; import java.net.*; import java.nio.charset.StandardCharsets;public class SendDemo {public static void main(String[] args) throws IOException {DatagramSocket ds new DatagramSocket();byte[] bys …

从Copyright到Copyleft,聊聊版权与开源协议

4月26日是世界知识产权日&#xff0c;很多人或许会觉得这和软件开发没什么关系&#xff0c;但事实上&#xff0c;开源软件大多受到知识产权法中著作权法&#xff08;Copyright&#xff0c;也称版权&#xff09;的保护。开源软件虽说开放了源代码&#xff0c;但是用户在使用、修…

[Java网络编程基础]TCP发送和接收数据

代码如下: package ClientPack;import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException;public class ClientDemo {public static void main(String[] args) throws IOExcept…

手机端适应_不轻易透露的超强技巧!详解iVX中怎样做设备自适应

"自适应网页设计"的概念2010年&#xff0c;Ethan Marcotte提出了"自适应网页设计"&#xff08;Responsive Web Design&#xff09;这个名词&#xff0c;指可以自动识别屏幕宽度、并做出相应调整的网页设计&#xff0c;自适应是为了解决如何才能在不同大小的…

五分钟了解dotnetcore配置框架

一、前言配置的本质就是字符串的键值对&#xff0c;微软的一系列接口其实就是对这些键值对字符串的抽象。二、基本类型2.1、Nuget包Microsoft.Extensions.Configuration.AbstractionsMicrosoft.Extensions.Configuration2.2、抽象接口IConfiguration提供了查询、设置配置项、监…

彩光和灰光模块_5G承载网主要光模块图谱

&#xff08;一&#xff09;5G承载光模块总览在光通信中&#xff0c;业务信息的传送与接收都是靠光模块来实现的。在发送端&#xff0c;光模块完成电/光转换&#xff0c;光在光纤中传输&#xff0c;然后在接收端实现光/电转换。可以说&#xff0c;没有光模块就没有光通信。在我…

Mobius 一个运行在 .NET Core 上的 .NET 运行时

导语一个 .NET 应用仅仅只是一块在 .NET 运行时上面运行的二进制代码。而 .NET 运行时只是一个能执行这项任务的程序。当前的 .NET Framework 和 .NET Core 运行时采用 C 编写&#xff0c;而 Mobius 是一个使用 C# 重写的 .NET 运行时&#xff0c;重写包括 JIT 编译和 GC 等&am…

Autofac的切面编程实现

面向切面编程&#xff1a;Autofac.Annotation扩展组件是我开源的一款利用打标签完成autofac容器的注入组件。https://github.com/yuzd/Autofac.Annotation我们之前介绍了利用Aspect标签来完成拦截器功能Aspect是一对一的方式&#xff0c;我想要某个class开启拦截器功能我需要针…

[Java基础]体验Lambda表达式

普通写法: 代码如下: package LambdaPack01;public class MyRunnable implements Runnable{Overridepublic void run() {System.out.println("多线程启动了");} }package LambdaPack01;public class LambdaDemo01 {public static void main(String[] args) {MyRunna…

linux启动mqtt_linux下安装MQTT服务器 - EMQTT

1. 下载从官网下载https://www.emqx.io/downloads#broker&#xff0c; 本文所用版本为broker/v3.2.1/emqx-centos7-v3.2.1.ziplinux下 下载:wget https://www.emqx.io/downloads/broker/v3.2.1/emqx-centos7-v3.2.1.zip解压&#xff1b;unzip emqx-centos7-v3.2.1.zip所在目录&…

.net core 基于Dapper 的分库分表开源框架(core-data)

一、前言感觉很久没写文章了&#xff0c;最近也比较忙&#xff0c;写的相对比较少&#xff0c;抽空分享基于Dapper 的分库分表开源框架core-data的强大功能&#xff0c;更好的提高开发过程中的效率&#xff1b;在数据库的数据日积月累的积累下&#xff0c;业务数据库中的单表数…

[Java基础]Lambda表达式练习

代码如下: package LambdaPracticePack;public interface Eatable {void eat(); }package LambdaPracticePack;public class EatableImpl implements Eatable{Overridepublic void eat() {System.out.println("一天一苹果&#xff0c;医生远离我");} }package Lambd…

​你可能不知道的7个HTML小技巧

五一期间&#xff0c;知道大家都比较懒&#xff0c;我也是。所以写篇简单且基础的技术小文&#xff0c;不需要动脑子&#xff0c;扫一眼就能掌握的那种。DETAILS 标签<details> 标签将额外的详情信息隐藏起来&#xff0c;用户在需要的时候点击即可展开查看详情。<deta…

青年节寄语和新课程免费上架

大家好&#xff0c;确实好久没有写东西了&#xff0c;希望这篇推送不是用来提醒你取消关注哈。2020年这突如其来的疫情&#xff0c;打乱了我们的生活&#xff0c;也让很多人更加明白了生活的无常以及可贵吧&#xff0c;在此也真诚希望大家都要好好哒&#xff0c;不负韶华&#…

从堆里找回“丢失”的代码相关命令简介

前言 在上一篇文章中&#xff0c;我们主要使用了三个命令 !address&#xff0c;s&#xff0c;.writemem 把丢失的代码成功的保存到了文件中。本文简单介绍一下上文用到的这三个命令。windbg 中的地址范围语法 很多命令都会用到 地址范围。比如 s 命令&#xff0c;.writemem 命令…