为什么 C# 编译器为每个传递的委托创建一个新的 Action 实例?

2023-12-09

考虑以下代码:

public static void M() {
    A(V);
    A(V);
    A(V);
}

public static void V() {

}

public static void A(Action x) {
    x();   
}

这在幕后编译为:

public static void M() {
    A(new Action(V));
    A(new Action(V));
    A(new Action(V));
}

但是,我们可以编写自己的简单性能改进来减少不必要的垃圾:

private static readonly Action v = new Action(V);
A(v);
A(v);
A(v);

对于这个非常简单的情况,Roslyn 有什么理由不能进行类似的优化吗?

如果答案是否定的,那么当方法不是静态而是实例成员时怎么办?当捕获了封闭变量时该怎么办?


我们可以编写自己的简单性能改进来减少不必要的垃圾

你重新发现了一个特殊情况公共子表达式消除-- 优化识别何时两个或多个表达式具有完全相同的值,计算该值一次,并将其存储在变量中以供重复使用。

在继续之前,我提醒您,所有所谓的“优化”实际上都是在以一件事换另一件事。您建议的优化会用每次调用时生成的少量收集压力来换取少量内存泄漏。静态字段中的缓存值将成为第 2 代堆的永久成员。这值得吗?您需要通过实际测量来回答这个问题。

对于这个非常简单的情况,Roslyn 有什么理由不能进行类似的优化吗?

没有原则无法执行此优化的原因如果优化没有对程序的行为产生不可接受的变化.

特别是,优化会导致之前值相等但引用不相等的两个委托变得引用相等。这可能是可以接受的。

在实践中,实现优化需要在设计、实现、测试和维护执行优化的代码方面付出大量的努力。 C# 不实现常见的子表达式消除优化。这种优化的性价比很差。很少有人编写可以从优化中受益的代码,而且优化很小,而且正如您发现的那样,如果您关心的话,很容易“手动”进行优化。

我注意到 C# 确实对 lambda 做了类似的缓存。它不会进行常见的子表达式消除,但只会生成一次某些 lambda 并缓存结果:

void M() { Action x = () => {}; ... }

生成就像你写的:

static Action anon = null;
void M() 
{
  if (anon == null) anon = () => {};
  Action x = anon;
  ...

如果答案是否定的,那么当方法不是静态而是实例成员时怎么办?

没有原则无法执行此优化的原因如果优化没有对程序的行为产生不可接受的变化.

我注意到,在这种情况下,当然需要进行优化来推断实例何时相同。如果不这样做,就无法维持程序行为不得改变的不变性。

同样,在实践中,C# 不执行公共子表达式消除。

当捕获了封闭变量时该怎么办?

被什么捕获了?您刚才讨论的是方法组到委托的转换,显然现在我们正在讨论 lambda 转换为委托。

C# 规范明确指出,编译器可以选择对相同的 lambda 执行公共子表达式消除,也可以不执行,只要它认为合适。

没有原则无法执行此优化的原因如果优化没有对程序的行为产生不可接受的变化。由于规范明确指出允许这种优化,因此根据定义它是可以接受的。

同样,在实践中,C# 不执行公共子表达式消除。

也许您在这里注意到了一种趋势。问题的答案“是否允许这样那样的优化?”几乎总是“是的,如果它不会对程序的行为产生不可接受的变化”。但问题的答案是“C# 在实践中是否实现了这样那样的优化?”通常是没有。

如果您想了解编译器执行的优化的一些背景知识,我在 2009 年描述过它们.

大多数情况下,Roslyn 在这些优化方面做得更好。例如,Roslyn 在将临时值和局部变量具体化为临时变量而不是持久变量方面做得更好。我完全重写了可为空算术优化器;我的八部分系列文章描述了这里的情况。还有更多的改进。但我们从未考虑过进行 CSE。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 C# 编译器为每个传递的委托创建一个新的 Action 实例? 的相关文章

  • 不同提供商的相同 EDMX 文件

    我正在开发一个项目 其中有一个本地数据库 SQL CE 在不存在与服务器的连接的情况下用作缓冲区 在服务器上我想使用相同的数据库布局 当然 我想使用服务器和客户端上可用的 Common dll 中的相同 EDMX 文件 在客户端中 我有一个
  • 使用sqlbulkcopy之前如何创建表

    我有一个 DBF 文件 我正在尝试导入该文件 然后将其写入 SQL 表 我遇到的问题是 如果我使用 SqlBulkCopy 它需要我提前创建表 但在我的场景中这是不可能的 因为 dbf 文件不断变化 到目前为止 这是我的代码 public
  • 套接字编程-listen() 和accept() 有什么区别?

    我一直在读本教程 http www cs rpi edu moorthy Courses os98 Pgms socket html了解套接字编程 看来listen and accept 系统调用都做同样的事情 即阻塞并等待客户端连接到使用
  • C++ 中的“int”默认是“signed long int”吗?

    Is int默认情况下signed long int in C 它是否依赖于平台和 或编译器 如果是这样 怎么办 EDIT 以下任何一项是否保证是重复的 signed short int signed int signed long int
  • 如何使用最小起订量模拟私有只读 IList 属性

    我试图嘲笑这个列表 private readonly IList
  • JetBrains Rider 针对 4.5 框架,无法切换到 4.7

    基本上 当尝试添加不支持旧框架的 NuGet 包时 会出现错误 但是在项目配置中只有 4 5 可用 在项目创建过程中 不存在选择目标的选项 有什么方法可以正确配置它吗 I haven t found out how to set up NE
  • 尽管浮点数相同,但它们并不相等? [复制]

    这个问题在这里已经有答案了 下面的程序输出This No is not same 当两个数字相同时为什么会这样做 void main float f 2 7 if f 2 7 printf This No is same else prin
  • 优化 Haskell 内循环

    仍在 Haskell 中进行 SHA1 实现 我现在已经有了一个有效的实现 这是内部循环 iterateBlock Int gt Word32 gt Word32 gt Word32 gt Word32 gt Word32 gt Word3
  • AcceptSocket 超时?

    是否有可能AcceptSocket on a TcpListener具有超时的对象 以便它偶尔被中断 TcpListener server new TcpListener localIP port server Start while sh
  • 从 Golang 调用 C 函数

    我想在 Golang 中编写控制器逻辑并处理 json 和数据库 同时在 C 中使用我的数学处理模型 在我看来 调用 C 函数的开销必须尽可能低 就像设置寄存器 rcx rdx rsi rdi 一样 执行一些操作fastcall 并获取 r
  • 多个线程访问一个变量

    我在正在读的一本教科书中发现了这个问题 下面也给出了解决方案 我无法理解最小值怎么可能是 2 为什么一个线程不能读取 0 而所有其他线程都执行并写入 1 而无论是1还是2 最后写入的线程仍然必须完成自己的循环 int n 0 int mai
  • 从二进制文件读取字节到 long int

    我有两个问题 我有二进制文件的数据 我想使用 read 函数读取前 8 个字节以签署 long int 但我不能 你知道我该怎么做吗 如何直接读取一块数据到字符串中 我可以像所示那样阅读吗 前任 ifstream is is open te
  • 原子的 C++ 内存屏障

    在这方面我是个新手 谁能提供以下内存屏障之间差异的简化解释 窗户MemoryBarrier 围栏 mm mfence 内联汇编asm volatile memory 内在的 ReadWriteBarrier 如果没有简单的解释 一些好文章或
  • 在 Windows 上使用 C/C++ 开发时省略 msvcr100.dll?

    是否可以在 Windows 上使用 C C 进行开发而不链接到 msvcr100 dll 我知道这是 Windows 的标准 c 库 但我想知道如果我没有安装 Visual Studio 或 Redistributable 软件包 我的计算
  • 如果项目包含多个文件夹,如何使用 Add-Migration

    我想Add Migration使用我的 DbContext 但出现错误 The term add migration is not recognized as the name of a cmdlet function script fil
  • 使用 WinAPI 连接禁用的显示设备

    我的问题是启用禁用的监视器ChangeDisplaySettingsEx 我想这不是火箭科学 但经过一番挖掘后 它看起来仍然是不可能的 我找到了一种根据找到的 Microsoft 代码示例禁用所有辅助显示器的方法here https msd
  • 按 Enter 继续

    这不起作用 string temp cout lt lt Press Enter to Continue cin gt gt temp cout lt lt Press Enter to Continue cin ignore 或更好 in
  • 如何使 WinForms UserControl 填充其容器的大小

    我正在尝试创建一个多布局主屏幕应用程序 我在顶部有一些按钮链接到应用程序的主要部分 例如模型中每个实体的管理窗口 单击这些按钮中的任何一个都会在面板中显示关联的用户控件 面板包含用户控件 而用户控件又包含用户界面 WinForms User
  • 如何获取运行或段落的高度

    我找到了Run or Paragraph in FlowDocument现在我需要知道HEIGHT of it i e while navigator CompareTo flowDocViewer Document ContentEnd
  • 有没有办法在 C# 中仅通过文件名查找文件?

    我们现在使用绝对路径或相对路径在 C 应用程序中查找文件 如果文件位于当前工作目录下或 路径 之一下 有没有办法仅通过名称查找文件 使用绝对路径不好 使用相对路径也不够好 因为我们可能通过重命名或移动项目文件夹来更改项目结构 如果我们的代码

随机推荐