问题之源

C# 7.2推出了全新的参数修饰符in,据说是能提升一定的性能,官方MSDN文档描述是:

Add the in modifier to pass an argument by reference and declare your design intent to pass arguments by reference to avoid unnecessary copying.

然而,想当然地使用它却导致更多的副本出现,影响代码运行速度。

MSDN中还有一段隐含的副作用的描述:

You can call any instance method that uses by value parameters. In those instances, a copy of the in parameter is created.

同时文档也提到了readonly ref的目的:

After adding support for in parameters and ref redonly [sic] returns the problem of defensive copying will get worse since readonly variables will become more common.

来看一下MSDN里的这个例子::

private static double CalculateDistance(in Point3D point1, in Point3D point2)
{
double xDifference = point1.X - point2.X;
double yDifference = point1.Y - point2.Y;
double zDifference = point1.Z - point2.Z; return Math.Sqrt(xDifference * xDifference + yDifference * yDifference + zDifference * zDifference);
}

假设Point3D类型是这样定义的:

public struct Point3D
{
public Point3D(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
} public double X { get; }
public double Y { get; }
public double Z { get; }
}

结果C#的几个本不相关的特性以一种闹心的方式结合起来:

  1. 标记了in的结构体参数是readonly只读的
  2. 调用标记为readonly的结构体的实例化方法将产生一个副本
    • 因为这个方法要通过改变this指针来达到确保标记了readonly的原值不会被修改
  3. 属性访问器也是实例方法,受this影响

每次给CalculateDistance方法传递标记了in的结构体参数时,编译器会在访问时自动为这个参数的每个属性创建一个副本,本以为不会创建副本,结果反而每个传进来的参数在方法内部弄出来3个!

这个问题存在已久,看一下Jon Skeet的博客:The Surprising Inefficiency of Readonly Fields。只不过使用in让这个尴尬的场面更频繁易现了。

解决方案

解决办法同样来自C# 7.2:readonly struct.

如果将public struct Point3D改成public readonly struct Point3D,因为所有字段也已经是readonly了,所以整个结构体都无需改变,编译器此时也会省掉副本的操作,只有这样才会出现结构体参数比按值传递获得更快的运行速度。

不过注意在C# 7.1中结构体是可以通过标记ref来传参达到同样避免副本开销的。尽管如此,结构体参数的字段想要改变值仍是可变的,甚至这个结构体都可以指向另一个新的。在函数的参数列表中使用in的声明主要意图还是为了告诉调用者本函数不会去修改传进来的参数,当然编译器也会配合强制保证。

示例

这里有一个关于inrefstruct和readonly struct各种组合的性能测评(结构体size太小看不出差别,因此这个示例把结构体增加到56 bytes以便跑出更明显的对比效果)。结果如下:

总结

  • 当使用in代替ref表示设计意图时,要明白在传递较大且较多的结构体时会有微小的性能损失
  • 当使用in又要避免产生副本或提高性能,在声明结构体时要使用readonly struct

(原文:‘in’ will make your code slower)

这个拖后腿的“in”的更多相关文章

  1. 2020年9月程序员工资统计,平均14459元!你给程序员拖后腿了吗?https://jq.qq.com/?_wv=1027&k=JMPndqoM

    2020年9月全国招收程序员362409人.2020年9月全国程序员平均工资14459元,工资中位数12500元,其中95%的人的工资介于5250元到35000元. 工资与上个月持平,但是岗位有所增加 ...

  2. 设计爬虫Hawk背后的故事

    本文写于圣诞节北京下午慵懒的午后.本文偏技术向,不过应该大部分人能看懂. 五年之痒 2016年,能记入个人年终总结的事情没几件,其中一个便是开源了Hawk.我花不少时间优化和推广它,得到的评价还算比较 ...

  3. ios培训机构排名

    移动互联网的时代,智能手机的作用已经无所不在,APP在人们的生活中也起到了非常重要的作用,iOS开发行业同样受到越来越多人的关注,更多的人选择参加iOS培训机构来加入这个行列,而如何选择一个真正可以学 ...

  4. 事后分析报告(Postmortem Report)

    小组讨论照片 设想和目标 1.我们的团队项目为英语单词学习助手,名为“我爱记单词”.主要提供服务包括:单词查询,单词测试,单词记忆和中英互译.目前开发的是单机版本,用户可以根据自己的需求灵活的使用相应 ...

  5. 《奥威Power-BI案例应用:带着漫画看报告》腾讯课程开课啦

    元旦小假期过去了,不管是每天只给自己两次下床机会的你,还是唱K看电影逛街样样都嗨的你,是时候重振旗鼓,重新上路了!毕竟为了不给国家的平均工资水平拖后腿,还是要努力工作的.话说2016年已经过去了,什么 ...

  6. 分享自己的超轻量级高性能ORM数据访问框架Deft

    Deft 简介 Deft是一个超轻量级高性能O/R mapping数据访问框架,简单易用,几分钟即可上手. Deft包含如下但不限于此的特点: 1.按照Transact-SQL的语法语义风格来设计,只 ...

  7. 一些对数学领域及数学研究的个人看法(转载自博士论坛wcboy)

    转自:http://www.math.org.cn/forum.php?mod=viewthread&tid=14819&extra=&page=1 原作者: wcboy 现在 ...

  8. 8.31 js基础总结1

    JavaScript是一种脚本语言,由web浏览器进行解释和执行.它给予页面灵魂,让页面可以动起来,包括动态的数据,动态的标签,动态的样式等等. 将JavaScript应用到网页中常用的方法有两种,第 ...

  9. 2016 ICPC China-Final 现场赛总结

    距离比赛结束快有一个礼拜了才抽出时间来写这篇总结.今年比赛打了也有5场了(4场区域赛+1场省赛),也取得了不错的成绩(区域赛两银),总的来说第一年就取得这成绩还是挺高兴的.我们队,我自己都渐渐的趋于成 ...

随机推荐

  1. 安卓修改开机logo和开机动画的方法

    第一种和第二种方法亲测可用,安卓版本是4.2和安卓5.1均可.第二种方法待验证 以下三种方法 Android 开机其实总共会出现3个画面: 1.第一个就是 linux 系统启动,出现Linux小企鹅画 ...

  2. HDMI ip中的时钟 vid_clk与ls_clk

    由TMDS_Bit_clock_Ratio.TMDS_clk和色彩深度,就可以确定出tmds_clk,cdr_clk,vid_clk和ls_clk之间的关系. 1.Tmds_clk时钟频率的确定: 原 ...

  3. 几种修改Linux主机名的方法

    在安装一些系统时,需要修改hostname,比如安装Hadoop时需要修改主机名,而且主机名不能包含下划线. 实际上,主机名分三种(命令hostnamectl或hostnamectl status可查 ...

  4. 20171126-handler消息机制理解

    1.handler消息机制的理解 http://www.jianshu.com/p/8343a39b8a2c?s_q_s_h_a_r_e_1MTAzNTIwODAxNTExNTg5NTkwMzE0Nz ...

  5. Libre Office冻结操作-MAC

    冻结行/列 选中所要冻结的行/列,选择window-Freeze 冻结成功 冻结单元格 操作方法与“冻结行/列”一致

  6. noip第4课作业

    1.    计算邮资 [问题描述] 根据邮件的重量和用户是否选择加急计算邮费.计算规则:重量在1000克以内 (包含1000克),基本费8元.超过1000克的部分,每500克加收超重费4元,不足500 ...

  7. python特殊方法定制类

    #coding:utf-8class RoundFloat(object): def __init__(self,val): assert isinstance(val, float),"v ...

  8. delphi 分享三个随机字符串

    uses math; function GenID:String; var b, x: byte; begin Result := '{'; Randomize; do begin ) > ,) ...

  9. Android-WebView加载网页(new WebView(this)方式)

    之前的博客,都是 findViewById(R.id.webview);,来得到WebView, 此博客使用 new WebView(this)方式; AndroidManifest.xml中配置网络 ...

  10. XAMPP配置基于虚拟目录、多域名的环境

      打开Apache 2.x 配置文件 http.conf 搜索Include etc/extra/httpd-vhosts.conf,然后去掉前面的#号 再编辑extra/httpd-vhosts. ...