在 C# 7.2 提供了一系列的方法用于方法参数传输的时候减少对结构体的复制从而可以高效使用内存同时提高性能

在开始阅读之前,希望读者对 C# 的值类型、引用类型有比较深刻的认知。

在 C# 中,如果对内存有严格的要求,同时需要减少 GC 的情况,推荐此时使用结构体。但是结构体有一个缺点在于,结构体在每次调用方法作为参数传递的时候都会新建一个副本,这对于性能要求特别高的情况是不适合的。

定义一个值类型

    struct Int256
{
public Int256(long bits0, long bits1, long bits2, long bits3)
{
Bits0 = bits0;
Bits1 = bits1;
Bits2 = bits2;
Bits3 = bits3;
} public long Bits0 { set; get; }
public long Bits1 { get; }
public long Bits2 { get; }
public long Bits3 { get; }
}

此时通过一个简单的赋值就可以获取复制

Int256 f1 = new Int256(0, 1, 2, 1);
var f2 = f1;
f2.Bits0 = 2;
Console.WriteLine($"f1.bits0={f1.Bits0} f2.bits0={f2.Bits0}");
//f1.bits0=0 f2.bits0=2

在调用方法的时候也一样,传入参数就是复制一个新的值

        static void Main(string[] args)
{
Int256 f1 = new Int256(0, 1, 2, 1);
Foo(f1);
Console.WriteLine($"{f1.Bits0}"); // 0
} private static void Foo(Int256 foo)
{
foo.Bits0 = 2;
}

对于很小的值类型,如果小于 IntPtr.Size 的传输,会比引用传递的复制速度快,但是对比比较大的值类型,如上面定义的,复制一次需要的时间会比较长

特别是存在很多次的值传递的时候,如下面的代码,会调用 1000 次的值传递。除了性能的问题,还存在堆栈的内存的问题

定义一个很大的值类型,里面包含 10000 个 double 看起来就很大

    struct Double10000
{
public double Double0 { get; }
public double Double1 { get; }
public double Double2 { get; }
……
public double Double9999 { get; }
}

用递归的方式进行调用,运行的时候很快就可以看到堆栈都被申请的值传递使用,同时 CPU 的使用很高

        static void Main(string[] args)
{
Double10000 foo = new Double10000();
Foo(foo);
} private static void Foo(Double10000 foo, int n = 100)
{
if (n == foo.Double0)
{
return;
} Foo(foo, n - 1);
}

如果可以让值类型和引用一样传递,是不是就可以减少值类型的复制同时减少堆栈的使用,请注意不要纠结值类型是分配在堆中还是栈中的问题,上面的代码更多的是方法的递归

对比内存的使用,更多的时候关心的是运行的速度。添加一些代码用来测试性能,同时减少调用

            var st = new Stopwatch();
st.Start();
Foo(foo);
st.Stop();
Console.WriteLine(st.ElapsedTicks);
        private static void Foo(Double10000 foo, int n = 10)
{
if (n == foo.Double0)
{
return;
} Foo(foo, n - 1);
}

这里输出的 ElapsedTicks 的单位是 100ns 需要知道 1ms=1000000ns 也就是 1w 的 tick 就是 1 毫秒,下面我运行 3 次代码,收集到的值

10991
14950
16183

在 C# 7.2 可以使用 in 关键字告诉 VisualStudio 当前的方法不会对传进来的结构体进行修改,当前这样写只是语法层面。如果有一些厉害的黑客,可能还继续这样写入,于是为了防止真的进行修改,在底层还是复制了一份。

也就是只是在参数里面使用了 in 是不够的,具体请看这个拖后腿的“in” - Bean.Hsiang - 博客园

如果想要更好的使用内存同时提高性能,只有在可以被标记为只读的结构体的时候使用 in 才可以

先将 Double100 标记为 readonly 如果一个值类型标记为 readonly 也就无法对里面的字段或属性进行设置了

在 Foo 传入的方法参数标记 in 这样就完成了,因为 in 表示对参数不进行修改,而传入的是 readonly struct 本来就不能被修改,于是就传入 struct 的引用

readonly struct Double10000

        private static void Foo(in Double10000 foo, int n = 10)
{
if (n == foo.Double0)
{
return;
} Foo(foo, n - 1);
}

同样运行 3 次,可以看到速度是原来的 10 倍

2052
1837
1683

同时占用的堆栈更小,可以使用更多的递归,修改 Foo 函数调用次数为 1000 可以看到还能运行,但是如果去掉了参数 in 最多只能调用 20 次

没有加 in 的参数,运行了 17 次

添加了 in 之后因为不需要复制值,减少内存的时候,此时运行了 1000 次递归都可以,在使用in之后速度和使用内存都比较好

在很多次方法调用使用参数的时候,如果传入的值是值类型,如果此时的 struct 里面的属性都是只读属性,推荐将 struct 标记为 readonly 同时在方法参数标记 in 这样可以让 struct 作为引用传递,也就是复制的只是指针,只要 struct 的长度比指针小就推荐这个方法

我搭建了自己的博客 https://lindexi.gitee.io/ 欢迎大家访问,里面有很多新的博客。只有在我看到博客写成熟之后才会放在csdn或博客园,但是一旦发布了就不再更新

如果在博客看到有任何不懂的,欢迎交流,我搭建了 dotnet 职业技术学院 欢迎大家加入


本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接:http://blog.csdn.net/lindexi_gd ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系

C# 7.2 通过 in 和 readonly struct 减少方法值复制提高性能的更多相关文章

  1. C# 中的只读结构体(readonly struct)

    翻译自 John Demetriou 2018年4月8日 的文章 <C# 7.2 – Let's Talk About Readonly Structs>[1] 在本文中,我们来聊一聊从 ...

  2. Swift开发第五篇——四个知识点(Struct Mutable方法&Tuple&autoclosure&Optional Chain)

    本篇分三部分: 一.Struct Mutable方法 二.多元组(Tuple) 的使用 三.autoclosure 的使用 四.Optional Chain 的使用 一.Struct Mutable方 ...

  3. go chapter 10 函数 方法 struct的方法

    1. struct的方法 // 定义struct type MyStruct struct{} // 定义方法 (那个对象可以回调)方法名(参数) 返回值 {} (s *MyStruct) FillS ...

  4. matlab中struct创建方法

    MATLAB中struct创建方法可分为:直接创建法和struct()函数创建法 (1)直接创建: 直接定义字段,像使用一般matlab变量一样,不需要事先声明,支持动态扩充.下面创建一个Studen ...

  5. 解决Hibernate Write operations are not allowed in read-only mode的方法

    错误信息: org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed i ...

  6. .net TxetBox控件设置ReadOnly=True后台取值问题

    1.为TxetBox添加onfocus=this.blur()进行模拟 2.通过 Request.From["TextBox"].Trim()取值; 3.后台CS文件设置TextB ...

  7. GO入门——6. struct与方法

    1 struct Go 中的struct与C中的struct非常相似,并且Go没有class 使用 type struct{} 定义结构,名称遵循可见性规则 支持指向自身的指针类型成员 支持匿名结构, ...

  8. golang 对struct进行Serialize的方法,即将存取二进制文件到struct的方法

    方法一: serialize 的标准方法: 使用gob 和 base64 或 base58. 方法二: 下面是自己实现的 serialize 方法,不推荐自己实现,应该用标准方法. 代码如下: pac ...

  9. Android源码中中一种常见的struct使用方法

    直接看例子: #include<iostream> #include<stdlib.h> using namespace std; struct Base{ int ba; i ...

随机推荐

  1. vue移动端项目

    用vue mint-ui  jquery-weui写了一个移动端demo 技术栈 vue2.0 vue-router axios mint-ui jquery-weui webpack 页面截图 最后 ...

  2. BZOJ 4817数点涂色题解

    题目链接 考试考了一道类似的题目,然后不争气的挂掉了,于是跑过来学习这道题的解法... 我还是太菜了.... 我们可以发现任意时刻,原树中颜色相同的点的集合一定是一条链, 即上面这种状态,而这种结构是 ...

  3. jquery 浏览器缓存网页

    头部:<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate&q ...

  4. spring - constructor-arg的常规用法

     1.使用构造子注入时,则使用constructor-arg子标签,来指定构造函数的参数.    <bean id="provider" class="com.ap ...

  5. 【JZOJ4929】【NOIP2017提高组模拟12.18】B

    题目描述 在两个n*m的网格上染色,每个网格中被染色的格子必须是一个四联通块(没有任何格子被染色也可以),四联通块是指所有染了色的格子可以通过网格的边联通,现在给出哪些格子在两个网格上都被染色了,保证 ...

  6. Java 和 DynamoDB

    https://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/GettingStarted.Java.html 官方

  7. docker 与host互传文件

    docker 的cp命令可以从容器往外复制,也可以从本机复制的容器. docker cp 文件路径 容器id:/容器目录 docker help cp Usage:    docker cp [OPT ...

  8. CSS面试题总结2(转)

    1.你最喜欢的图片替换方法是什么,你如何选择使用. 图像替代,就是像我们在平时将文本添加到文本中,然后通过css隐藏文本并在它的位置上显示一个背景图片,这样,搜索引擎仍然可以搜到HTML文本,即使我们 ...

  9. 2019-4-7-VisualStudio-解决方案筛选器-slnf-文件

    title author date CreateTime categories VisualStudio 解决方案筛选器 slnf 文件 lindexi 2019-04-07 11:34:59 +08 ...

  10. 60.0.1(64位)windows版 uploadify使用有问题

    http://mozilla.com.cn/thread-403024-1-1.html 60.0.1(64位)版,在使用uploadify插件,上传附件时,上传没有问题,可以成功,但执行上传成功后的 ...