C# 泛型编译特性对性能的影响

C#作为一种强类型语言,具有丰富的泛型支持,允许开发者编写可以应对不同数据类型的通用代码。然而,在泛型编译时,针对结构和类作为泛型参数时,会对性能产生不同的影响。

泛型编译行为

在C#中,泛型编译行为取决于泛型参数的类型。具体而言,当泛型参数是结构(Struct)时,编译器会针对每个具体的结构类型生成特定的实现。而当泛型参数是类(Class)时,编译器则可能生成更通用的实现。

结构 vs 类

结构(Struct)

结构是值类型,它们存储在栈上,具有较小的内存开销。当泛型参数是结构时,编译器会针对每个具体的结构类型生成专门的实现,这可能导致更高的性能。因为每个结构类型都有自己的实现,避免了装箱和拆箱的开销,同时优化了内存分配和访问。

类(Class)

类是引用类型,存储在堆上,需要通过引用进行访问。当泛型参数是类时,编译器可能生成更通用的实现。这可能导致较低的性能,因为通用实现需要进行动态调度和引用类型的操作,增加了一些开销。

测试性能差异

针对不同的泛型参数进行性能测试是一种有效的方法,以观察结构和类对泛型编译特性的影响。在测试中,可能会发现对结构类型的泛型参数,其性能可能更高,而对类类型的泛型参数,其性能可能略低。

using System.Diagnostics;namespace ConsoleApp1 {internal interface IValueGetter {int GetValue(int index);}internal class MyTestClass<T> where T : IValueGetter {private readonly T _valueGetter;public MyTestClass(T valueGetter) {_valueGetter = valueGetter;}public void Run() {long r = 0L;for (int i = 0; i < int.MaxValue; i++) {r += _valueGetter.GetValue(i);}}}internal struct StructValueGetter : IValueGetter {public readonly int GetValue(int index) {return index + 3;}}internal struct StructValueGetter2(int someField) : IValueGetter {public readonly int GetValue(int index) {return index + 5;}}internal class ClassValueGetter1 : IValueGetter {public int GetValue(int index) {return index + 5;}}internal class ClassValueGetter2 : IValueGetter {public int GetValue(int index) {return index + 7;}}internal static class Demo2 {public static  void Run() {var t1 = new MyTestClass<StructValueGetter>(new StructValueGetter());RunDemo("StructValueGetter ", t1.Run);var t2 = new MyTestClass<ClassValueGetter1>(new ClassValueGetter1());RunDemo("ClassValueGetter1 ", t2.Run);var t3 = new MyTestClass<ClassValueGetter2>(new ClassValueGetter2());RunDemo("ClassValueGetter2 ", t3.Run);var t4 = new MyTestClass<IValueGetter>(new ClassValueGetter1());RunDemo("IValueGetter-1    ", t4.Run);var t5 = new MyTestClass<ClassValueGetter1>(new ClassValueGetter1());RunDemo("ClassValueGetter1 ", t5.Run);var t6 = new MyTestClass<StructValueGetter2>(new StructValueGetter2());RunDemo("StructValueGetter2", t6.Run);var t7 = new MyTestClass<IValueGetter>(new ClassValueGetter2());RunDemo("IValueGetter-2    ", t7.Run);var t8 = new MyTestClass<IValueGetter>(new StructValueGetter());RunDemo("IValueGetter-3    ", t8.Run);var t9 = Activator.CreateInstance(typeof(MyTestClass<>).MakeGenericType(typeof(StructValueGetter)), new StructValueGetter());Action action9 = (Action)Delegate.CreateDelegate(typeof(Action), t9, t9.GetType().GetMethod("Run"));RunDemo("Dynamic-Struct    ", action9);}static void RunDemo(string caption, Action action) {var stopWatch = Stopwatch.StartNew();action();stopWatch.Stop();Console.WriteLine($"{caption} time = {stopWatch.Elapsed}");}}
}Demo2.Run();

在.net 8.0 Release 编译执行的参考结果如下:

StructValueGetter  time = 00:00:00.6920186
ClassValueGetter1  time = 00:00:01.1887137
ClassValueGetter2  time = 00:00:05.2889692
IValueGetter-1     time = 00:00:01.1652195
ClassValueGetter1  time = 00:00:01.1625259
StructValueGetter2 time = 00:00:00.6488674
IValueGetter-2     time = 00:00:05.2114724
IValueGetter-3     time = 00:00:07.1394676
Dynamic-Struct     time = 00:00:00.6491220

结论

泛型编译特性对性能有所影响,我们发现:

  • 泛型参数是 Struct 比 class 的性能要好,大约有两倍的差异;

  • 泛型参数如果存在多个 Struct 可能时,性能没有影响,但如果泛型参数存在多个 class 可能时,性能急剧下降5倍之多;

  • 泛型参数如果是接口形式,无论实际填充的结构还是类,其最终的执行性能一定是很慢的;

  • 使用反射(例如:MakeGenericType)构建出的泛型实例,其实际运行性能并不受影响,非常适合高度定制的运行时类型构建,这一点非常重要,例如你可以在运行时检测实际情况,构建出不同的比较器对象,虽然构建的工厂方法返回的是接口,但你可以使用反射的方式动态传入字典的比较器参数(实际上c#的 Dictionary<TKey, TValue> 这点设计是失败的,他的comparer不是一个泛型参数,而是接口);

综上所述,了解C#泛型编译特性对性能的影响是编写高性能代码的重要一部分,合理使用对于关键性代码性能至关重要。

文章转载自:编写人生

原文链接:https://www.cnblogs.com/tansm/p/CSharp-Generic-Performance.html

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

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

相关文章

.Net 字符集与编解码

0 .NET 字符集编解码 .Net 内部使用的字符集是Unicode&#xff0c;如果需要编码为其他诸如GBK、UTF8编码&#xff0c;可以通过Encoding 类来实现。 using System.Text;void PrintBytes(byte[] bytes) {foreach (var b in bytes){Console.Write("{0:X} ", b);}Conso…

Linux 命令pwd

命令作用 pwd是Linux中一个非常有用而又十分简单的命令&#xff0c;pwd是词组print working directory的首字母缩写&#xff0c;即打印工作目录&#xff1b;工作目录就是你当前所处于的那个目录。 pwd始终以绝对路径的方式打印工作目录&#xff0c;即从根目录&#xff08;/&am…

LeetCode(45)最长连续序列【哈希表】【中等】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 最长连续序列 1.题目 给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1&a…

7.23 SpringBoot项目实战【评论】

文章目录 前言一、编写控制器二、编写服务层三、Postman测试前言 我们在 7.4 和 7.20 都曾实现过 评论列表,本文我们继续SpringBoot项目实战 评论 功能。逻辑实际相当Easy:一个学生 对 任意书 都可以 多次评论,但需要经过审核! 回顾一下 4.2 的数据库设计,学生图书评论表…

【Java 基础】11 内部类

有时候&#xff0c;我们定义一个类的时候&#xff0c;它是需要依附于其他类而存在的&#xff0c;这种就是内部类。 内部类有访问外部类的成员和方法的权限&#xff0c;可以访问外部类的私有成员。 内部类的主要作用是封装和组织代码&#xff0c;有助于实现更清晰、更模块化的…

vue3+element-plus+vue-cropper实现裁剪图片上传

1.vue3element-plusvue-cropper实现裁剪图片 element-UI官网element-plus官网vue-croppervue3使用vue-cropper安装&#xff1a;npm install vue-croppernext 2.vue-cropper插件&#xff1a; <vue-cropper :img"option.img" /><script setup>import {reac…

LeetCode //C - 5. Longest Palindromic Substring

5. Longest Palindromic Substring Given a string s, return the longest palindromicsubstring in s. Example 1: Input: s “babad” Output: “bab” Explanation: “aba” is also a valid answer. Example 2: Input: s “cbbd” Output: “bb” Constraints: 1 &l…

STM32F407-14.3.7-01PWM输入模式

PWM 输入模式 此模式是输入捕获模式的一个特例。其实现步骤与输入捕获模式基本相同&#xff0c;仅存在以下不同之处&#xff1a; 例如&#xff0c;可通过以下步骤对应用于 TI1① 的 PWM 的周期&#xff08;位于 TIMx_CCR1⑨ 寄存器中&#xff09;和占空 比&#xff08;位于 …

认识JVM 一个Java文件的JVM之旅

准备 我是一个java文件&#xff0c;如何实现我的功能呢&#xff1f;需要去JVM(Java Virtual Machine)这个地方旅行。 变身 我高高兴兴的来到JVM&#xff0c;想要开始JVM之旅&#xff0c;它确说&#xff1a;“现在的我还不能进去&#xff0c;需要做一次转换&#xff0c;生成c…

.net 面试题

1.请解释一下C#中的委托&#xff08;Delegate&#xff09;。 委托是一种引用类型&#xff0c;用于封装方法的引用&#xff0c;并允许将方法作为参数传递、存储和调用。C#中的委托类似于C/C中的函数指针&#xff0c;但比函数指针更安全和灵活。通过使用委托&#xff0c;可以将方…

thinkphp6出现 htmlentities() expects parameter 1 to be string, array given

为避免出现 XSS 安全问题&#xff0c; thinkphp6默认变量输出都会使用 htmlentities 方法进行转义 输出。 如果不想被转义输出&#xff0c;模板渲染时&#xff0c;需要在变量后面加上 raw方法&#xff0c;如&#xff1a;{$data|raw} 1、出现问题前的代码 PHP代码$this->assi…

TCP Socket API 讲解,以及回显服务器客户端的实现

文章目录 TCPServerSocket APISocket API TCP 客户端服务器的实现 TCP ServerSocket API ServerSocket 是创建TCP服务端 Socket 的 API。 serverSocket构造方法&#xff1a; 方法签名方法说明ServerSocket(int port)创建一个服务端流套接字Socket&#xff0c;并绑定到指定端…

VUE2+THREE.JS 模型上方显示信息框/标签(CSS3DSprite精灵模型)

THREE.JS 模型上方显示信息框/标签---CSS3DSprite精灵模型 1.CSS2DRenderer/CSS3DRenderer/Sprite的优劣2.实现模型上方显示信息框2.1 引入2.2 初始化加载的时候就执行此方法2.3 animate循环执行2.4 获取设备状态并在每个设备上显示设备状态2.5 样式 CSS3DSprite精灵模型面向摄…

python中的函数定义

默认参数 注&#xff1a; 在Python中&#xff0c;print(x, and y both correct)是一条打印语句&#xff08;print statement&#xff09;&#xff0c;用于将一条消息输出到控制台或终端。它的作用是将变量x的值和字符串and y both correct同时输出到屏幕上。 在这个语句中&…

【复杂网络建模】——基于代理的社会网络建模(Agent-Based Modeling,ABM)[Python实现]

目录 一、复杂网络建模方法 二、基于代理的社会网络建模实现及Python实现代码 一、复杂网络建模方法 复杂网络是一种由大量相互连接的元素(节点或顶点)组成的网络结构,这些连接通常是非常复杂和动态的。这些网络可以在各种领域中发现,包括社交网络、生物学系统、信息技术…

docker中的网络不通问题

前言 有时候在使用docker时&#xff0c;会莫名其妙docker内部与外网网络不通 docker与防火墙 docker内部的网络与宿主机的防火墙有千丝万缕的联系&#xff0c;docker启动的那一刻如果防火墙是启动的&#xff0c;docker内部与外部就会走防火墙转发策略&#xff0c;这个时候&a…

MySQL表连接详解:解析内连接与外连接的使用方法

在MySQL中&#xff0c;表连接是一种将两个或多个表中的行相关联的操作。这是通过在这些表之间共享一个或多个列的值来实现的。表连接通常用于从多个表中检索相关的数据。 有几种不同类型的表连接&#xff0c;其中两个常见的是内连接&#xff08;INNER JOIN&#xff09;和外连接…

Windows11如何让桌面图标的箭头消失(去掉快捷键箭头)

在Windows 11中&#xff0c;桌面图标的箭头是快捷方式图标的一个标志&#xff0c;用来表示该图标是一个指向文件、文件夹或程序的快捷方式。如果要隐藏这些箭头&#xff0c;你需要修改Windows注册表或使用第三方软件。 在此之前&#xff0c;我需要提醒你&#xff0c;修改注册表…

Android 滑动按钮(开关) SwitchCompat 自定义风格

原生的SwitchCompat控件如下图&#xff0c;不说不堪入目&#xff0c;也算是不敢恭维了。开个玩笑... 所以我们就需要对SwitchCompat进行自定义风格&#xff0c;效果如下图 代码如下 <androidx.appcompat.widget.SwitchCompatandroid:id"id/switch_compat"android:…

【Linux】第二十二站:文件(二)深入理解重定向

文章目录 一、重定向1.文件描述符对应的分配规则2.重定向的接口 二、再次实现myshell1.实现细节2.盘点文件与进程替换的一个细节3.代码 三、1号文件和2号文件的区别四、如何理解“一切皆文件&#xff1f;” 一、重定向 1.文件描述符对应的分配规则 我们先看如下代码 #includ…