Func 的性能和继承

2024-03-22

我一直无法理解使用的性能特征Func<...>在我的代码中,当使用继承和泛型时,我发现自己一直在使用继承和泛型的组合。

让我从一个最小的测试用例开始,这样我们都知道我们在说什么,然后我将发布结果,然后我将解释我的期望和原因......

最小测试用例

public class GenericsTest2 : GenericsTest<int> 
{
    static void Main(string[] args)
    {
        GenericsTest2 at = new GenericsTest2();

        at.test(at.func);
        at.test(at.Check);
        at.test(at.func2);
        at.test(at.Check2);
        at.test((a) => a.Equals(default(int)));
        Console.ReadLine();
    }

    public GenericsTest2()
    {
        func = func2 = (a) => Check(a);
    }

    protected Func<int, bool> func2;

    public bool Check2(int value)
    {
        return value.Equals(default(int));
    }

    public void test(Func<int, bool> func)
    {
        using (Stopwatch sw = new Stopwatch((ts) => { Console.WriteLine("Took {0:0.00}s", ts.TotalSeconds); }))
        {
            for (int i = 0; i < 100000000; ++i)
            {
                func(i);
            }
        }
    }
}

public class GenericsTest<T>
{
    public bool Check(T value)
    {
        return value.Equals(default(T));
    }

    protected Func<T, bool> func;
}

public class Stopwatch : IDisposable
{
    public Stopwatch(Action<TimeSpan> act)
    {
        this.act = act;
        this.start = DateTime.UtcNow;
    }

    private Action<TimeSpan> act;
    private DateTime start;

    public void Dispose()
    {
        act(DateTime.UtcNow.Subtract(start));
    }
}

结果

Took 2.50s  -> at.test(at.func);
Took 1.97s  -> at.test(at.Check);
Took 2.48s  -> at.test(at.func2);
Took 0.72s  -> at.test(at.Check2);
Took 0.81s  -> at.test((a) => a.Equals(default(int)));

我的期望是什么以及为什么

我希望这段代码对于所有 5 种方法都能以完全相同的速度运行,更准确地说,甚至比其中任何一个方法都更快,即与以下一样快:

using (Stopwatch sw = new Stopwatch((ts) => { Console.WriteLine("Took {0:0.00}s", ts.TotalSeconds); }))
{
    for (int i = 0; i < 100000000; ++i)
    {
        bool b = i.Equals(default(int));
    }
}
// this takes 0.32s ?!?

我预计它需要 0.32 秒,因为我认为 JIT 编译器没有任何理由在这种特殊情况下不内联代码。

仔细检查后,我根本不理解这些性能数字:

  • at.func传递给函数并且在执行期间不能更改。为什么这个不内联?
  • at.Check显然比at.Check2,而两者都不能被覆盖,并且 at.Check 的 IL 在类 GenericsTest2 的情况下是固定不变的
  • 我认为没有理由Func<int, bool>通过内联时要慢一些Func而不是转换为的方法Func
  • 为什么测试用例 2 和 3 之间的差异高达 0.5 秒,而测试用例 4 和 5 之间的差异为 0.1 秒 - 难道它们不应该是相同的吗?

Question

我真的很想了解这一点...这里发生了什么,使用通用基类比内联整个类慢 10 倍?

所以,基本上问题是:为什么会发生这种情况以及如何解决它?

UPDATE

根据到目前为止的所有评论(谢谢!)我做了一些更多的挖掘。

首先,重复测试并将循环放大 5 倍并执行 4 次时得到一组新结果。我使用了诊断秒表并添加了更多测试(还添加了描述)。

(Baseline implementation took 2.61s)

--- Run 0 ---
Took 3.00s for (a) => at.Check2(a)
Took 12.04s for Check3<int>
Took 12.51s for (a) => GenericsTest2.Check(a)
Took 13.74s for at.func
Took 16.07s for GenericsTest2.Check
Took 12.99s for at.func2
Took 1.47s for at.Check2
Took 2.31s for (a) => a.Equals(default(int))
--- Run 1 ---
Took 3.18s for (a) => at.Check2(a)
Took 13.29s for Check3<int>
Took 14.10s for (a) => GenericsTest2.Check(a)
Took 13.54s for at.func
Took 13.48s for GenericsTest2.Check
Took 13.89s for at.func2
Took 1.94s for at.Check2
Took 2.61s for (a) => a.Equals(default(int))
--- Run 2 ---
Took 3.18s for (a) => at.Check2(a)
Took 12.91s for Check3<int>
Took 15.20s for (a) => GenericsTest2.Check(a)
Took 12.90s for at.func
Took 13.79s for GenericsTest2.Check
Took 14.52s for at.func2
Took 2.02s for at.Check2
Took 2.67s for (a) => a.Equals(default(int))
--- Run 3 ---
Took 3.17s for (a) => at.Check2(a)
Took 12.69s for Check3<int>
Took 13.58s for (a) => GenericsTest2.Check(a)
Took 14.27s for at.func
Took 12.82s for GenericsTest2.Check
Took 14.03s for at.func2
Took 1.32s for at.Check2
Took 1.70s for (a) => a.Equals(default(int))

我从这些结果中注意到,当你开始使用泛型时,它会变得慢得多。深入研究一下我发现的非通用实现的 IL:

L_0000: ldarga.s 'value'
L_0002: ldc.i4.0 
L_0003: call instance bool [mscorlib]System.Int32::Equals(int32)
L_0008: ret 

对于所有通用实现:

L_0000: ldarga.s 'value'
L_0002: ldloca.s CS$0$0000
L_0004: initobj !T
L_000a: ldloc.0 
L_000b: box !T
L_0010: constrained. !T
L_0016: callvirt instance bool [mscorlib]System.Object::Equals(object)
L_001b: ret 

虽然其中大部分都可以优化,但我认为callvirt这里可能有问题。

为了使其更快,我在方法的定义中添加了“T : IEquatable”约束。结果是:

L_0011: callvirt instance bool [mscorlib]System.IEquatable`1<!T>::Equals(!0)

虽然我现在对性能有了更多的了解(它可能无法内联,因为它创建了 vtable 查找),但我仍然很困惑:为什么它不简单地调用 T::Equals?毕竟我do指定它将在那里...


始终运行微基准测试 3 次。第一个将触发 JIT 并排除这种情况。检查第二次和第三次运行是否相等。这给出:

... run ...
Took 0.79s
Took 0.63s
Took 0.74s
Took 0.24s
Took 0.32s
... run ...
Took 0.73s
Took 0.63s
Took 0.73s
Took 0.24s
Took 0.33s
... run ...
Took 0.74s
Took 0.63s
Took 0.74s
Took 0.25s
Took 0.33s

The line

func = func2 = (a) => Check(a);

添加额外的函数调用。删除它通过

func = func2 = this.Check;

gives:

... 1. run ...
Took 0.64s
Took 0.63s
Took 0.63s
Took 0.24s
Took 0.32s
... 2. run ...
Took 0.63s
Took 0.63s
Took 0.63s
Took 0.24s
Took 0.32s
... 3. run ...
Took 0.63s
Took 0.63s
Took 0.63s
Took 0.24s
Took 0.32s

这表明1.和2.run之间的(JIT?)效果由于删除了函数调用而消失了。前 3 个测试现在相等.

在测试 4 和 5 中,编译器可以将函数参数内联到 void test(Func),而在测试 1 到 3 中,编译器要弄清楚它们是常量需要很长的路要走。有时,从我们编码员的角度来看,编译器存在一些不容易看到的约束,例如与由 C++ 生成的二进制文件相比,来自 .Net 程序的动态特性的 .Net 和 Jit 约束。无论如何,正是函数 arg 的内联造成了这里的差异。

4和5的区别? 嗯,test5 看起来编译器也可以很容易地内联该函数。也许他为闭包构建了一个上下文,并解决了比需要的更复杂的问题。没有深入研究 MSIL 来弄清楚。

以上测试使用 .Net 4.5。这里使用 3.5,证明编译器通过内联变得更好:

... 1. run ...
Took 1.06s
Took 1.06s
Took 1.06s
Took 0.24s
Took 0.27s
... 2. run ...
Took 1.06s
Took 1.08s
Took 1.06s
Took 0.25s
Took 0.27s
... 3. run ...
Took 1.05s
Took 1.06s
Took 1.05s
Took 0.24s
Took 0.27s

和.Net 4:

... 1. run ...
Took 0.97s
Took 0.97s
Took 0.96s
Took 0.22s
Took 0.30s
... 2. run ...
Took 0.96s
Took 0.96s
Took 0.96s
Took 0.22s
Took 0.30s
... 3. run ...
Took 0.97s
Took 0.96s
Took 0.96s
Took 0.22s
Took 0.30s

现在将 GenericTest 更改为 GenericTest !

... 1. run ...
Took 0.28s
Took 0.24s
Took 0.24s
Took 0.24s
Took 0.27s
... 2. run ...
Took 0.24s
Took 0.24s
Took 0.24s
Took 0.24s
Took 0.27s
... 3. run ...
Took 0.25s
Took 0.25s
Took 0.25s
Took 0.24s
Took 0.27s

这是 C# 编译器带来的惊喜,类似于我在密封类以避免虚函数调用时遇到的情况。也许埃里克·利珀特对此有话要说?

删除聚合的继承可以恢复性能。我学会了永远不要使用继承,好吧,很少,并且强烈建议您至少在这种情况下避免使用继承。 (这是我对这个问题的务实解决方案,无意引发口水战)。我一直严格使用界面,而且它们不会带来性能损失。

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

Func 的性能和继承 的相关文章

  • 秒表有最长运行时间吗?

    多久可以Stopwatch在 NET 中运行 如果达到该限制 它会回绕到负数还是从 0 重新开始 Stopwatch Elapsed返回一个TimeSpan From MSDN https learn microsoft com en us
  • .NET 中是否有内置函数可以对密码进行哈希处理?

    我看到这个问题加密 散列数据库中的纯文本密码 https stackoverflow com questions 287517 encrypting hashing plain text passwords in database 我知道我
  • 类模板参数推导 - clang 和 gcc 不同

    下面的代码使用 gcc 编译 但不使用 clang 编译 https godbolt org z ttqGuL template
  • HTTPWebResponse 响应字符串被截断

    应用程序正在与 REST 服务通信 Fiddler 显示作为 Apps 响应传入的完整良好 XML 响应 该应用程序位于法属波利尼西亚 在新西兰也有一个相同的副本 因此主要嫌疑人似乎在编码 但我们已经检查过 但空手而归 查看流读取器的输出字
  • C#中如何移动PictureBox?

    我已经使用此代码来移动图片框pictureBox MouseMove event pictureBox Location new System Drawing Point e Location 但是当我尝试执行时 图片框闪烁并且无法识别确切
  • 创建链表而不将节点声明为指针

    我已经在谷歌和一些教科书上搜索了很长一段时间 我似乎无法理解为什么在构建链表时 节点需要是指针 例如 如果我有一个节点定义为 typedef struct Node int value struct Node next Node 为什么为了
  • 将多个表映射到实体框架中的单个实体类

    我正在开发一个旧数据库 该数据库有 2 个具有 1 1 关系的表 目前 我为每个定义的表定义了一种类型 1Test 1Result 我想将这些特定的表合并到一个类中 当前的类型如下所示 public class Result public
  • 显示UnityWebRequest的进度

    我正在尝试使用下载 assetbundle统一网络请求 https docs unity3d com ScriptReference Networking UnityWebRequest GetAssetBundle html并显示进度 根
  • 使用 Bearer Token 访问 IdentityServer4 上受保护的 API

    我试图寻找此问题的解决方案 但尚未找到正确的搜索文本 我的问题是 如何配置我的 IdentityServer 以便它也可以接受 授权带有 BearerTokens 的 Api 请求 我已经配置并运行了 IdentityServer4 我还在
  • SolrNet连接说明

    为什么 SolrNet 连接的容器保持静态 这是一个非常大的错误 因为当我们在应用程序中向应用程序发送异步请求时 SolrNet 会表现异常 在 SolrNet 中如何避免这个问题 class P static void M string
  • Google App Engine 如何预编译 Java?

    App Engine 对应用程序的 Java 字节码使用 预编译 过程 以增强应用程序在 Java 运行时环境中的性能 预编译代码的功能与原始字节码相同 有没有详细的信息这是做什么的 我在一个中找到了这个谷歌群组消息 http groups
  • 如何序列化/反序列化自定义数据集

    我有一个 winforms 应用程序 它使用强类型的自定义数据集来保存数据进行处理 它由数据库中的数据填充 我有一个用户控件 它接受任何自定义数据集并在数据网格中显示内容 这用于测试和调试 为了使控件可重用 我将自定义数据集视为普通的 Sy
  • 使用 x509 证书签署 json 文档或字符串

    如何使用 x509 证书签署 json 文档或字符串 public static void fund string filePath C Users VIKAS Desktop Data xml Read the file XmlDocum
  • 链接器错误:已定义

    我尝试在 Microsoft Visual Studio 2012 中编译我的 Visual C 项目 使用 MFC 但出现以下错误 error LNK2005 void cdecl operator new unsigned int 2
  • C# 模拟VolumeMute按下

    我得到以下代码来模拟音量静音按键 DllImport coredll dll SetLastError true static extern void keybd event byte bVk byte bScan int dwFlags
  • C# - OutOfMemoryException 在 JSON 文件上保存列表

    我正在尝试保存压力图的流数据 基本上我有一个压力矩阵定义为 double pressureMatrix new double e Data GetLength 0 e Data GetLength 1 基本上 我得到了其中之一pressur
  • Windows 和 Linux 上的线程

    我在互联网上看到过在 Windows 上使用 C 制作多线程应用程序的教程 以及在 Linux 上执行相同操作的其他教程 但不能同时用于两者 是否存在即使在 Linux 或 Windows 上编译也能工作的函数 您需要使用一个包含两者的实现
  • 如何在文本框中插入图像

    有没有办法在文本框中插入图像 我正在开发一个聊天应用程序 我想用图标图像更改值 等 但我找不到如何在文本框中插入图像 Thanks 如果您使用 RichTextBox 进行聊天 请查看Paste http msdn microsoft co
  • C++ 中类级 new 删除运算符的线程安全

    我在我的一门课程中重新实现了新 删除运算符 现在我正在使我的代码成为多线程 并想了解这些运算符是否也需要线程安全 我在某处读到 Visual Studio 中默认的 new delete 运算符是线程安全的 但这对于我的类的自定义 new
  • 如何防止用户控件表单在 C# 中处理键盘输入(箭头键)

    我的用户控件包含其他可以选择的控件 我想实现使用箭头键导航子控件的方法 问题是家长控制拦截箭头键并使用它来滚动其视图什么是我想避免的事情 我想自己解决控制内容的导航问题 我如何控制由箭头键引起的标准行为 提前致谢 MTH 这通常是通过重写

随机推荐

  • 使用导航抽屉更改片段(?)布局或活动

    我正在尝试使用新的抽屉式导航 我遵循了指南 并设法让它与所选项目上的部分名称标题更改等一起使用 现在我陷入了困境 因为我不知道如何归档我下一步想做的事情 当我单击抽屉中的某个项目时 home 例如 我希望它能将我带到一个屏幕 其中包含我在特
  • Swift:通过 NSNotificationCenter 的键盘观察器不起作用

    我试图在我的 iOS 8 Swift 应用程序中实现一个简单的键盘观察器 但它确实不起作用 这是我当前使用的代码 override func viewDidAppear animated Bool NSNotificationCenter
  • 如何为Google的MLKIT使用图像格式YUV_420_888

    ImageReader 从相机预览中获取每一帧是具有格式的图像YUV 420 888 我想用它作为 MLKIT 的输入 在谷歌的文档中 我可以运行检测器 输入是 Bitmap 媒体图像 字节缓冲区 字节数组 A File 我尝试转换YUV
  • ExpressJS:承诺和错误处理中间件

    我定义了一些错误处理中间件和返回承诺的路由 但是当这个承诺出现错误时 我必须手动附加 catch err gt next err 在每一个承诺之后 虽然这不是问题 但对于 ExpressJs 来说 查看路由是否返回 Promise 如果返回
  • 快速使函数中的计时器无效

    我正在尝试创建一个带有主比赛时钟和开始 停止按钮的曲棍球比赛时钟应用程序 但我的 stopGameclock 函数遇到了问题 计时器不会失效 通过在这里搜索其他问题 我认为这与我有关 var gameclockTimer NSTimer 接
  • NSArray 和 NSMutableArray 的区别

    黑白有什么区别NSArray and NSMutableArray NSMutableArray 以及所有其他类Mutable名称中 可以修改 所以 如果你创建一个普通的NSArray 您以后无法更改其内容 无需重新创建它 但如果你创建一个
  • IdentityServer4 PKCE 错误:“转换后的代码验证程序与代码质询不匹配”

    我无法获得使用 Postman 工作的 IdentityServer4 PKCE 授权 使用在线工具我创建了必要的部分 选择一个随机字符串 1234567890 获取其 SHA 256 哈希值 c775e7b757ede630cd0aa11
  • Safari 中存在块作用域变量的 bug?

    我正在测试是否可以使用块作用域来替换 IIFE 以通过闭包创建 私有 变量 在 Safari 11 0 3 11604 5 6 1 1 中进行测试之前一切进展顺利 该版本支持块作用域 但存在块和闭包的错误 例如 let i 0 functi
  • ConcurrentDictionary 陷阱 - GetOrAdd 和 AddOrUpdate 的委托工厂是否同步?

    的文档ConcurrentDictionary没有明确说明 所以我想我们不能指望代表valueFactory and updateValueFactory让它们的执行同步 分别来自 GetOrAdd 和 AddOrUpdate 操作 所以
  • 在文件中每行的开头和结尾添加字符

    在每行的开头和结尾添加一些字符的最佳方法是什么 可以使用 Vim 或其他方式完成吗 在vim中 你可以这样做 s 1 s regex replace 是用于搜索和替换的 vim 命令 使其适用于整个文件 and 分别表示行的开始和结束 捕捉
  • 在摩卡测试之间重新导入模块

    在我的节点 打字稿快递应用程序中 我将配置设置存储在settings json作为对象加载和导出的文件config ts 每个使用配置设置的模块都会像这样导入模块 import Config from config config ts看起来
  • 如何使用 C# 验证文件是否是受密码保护的 ZIP 文件

    给定文件路径 如何验证该文件是否是受密码保护的 zip 文件 即 我将如何实现这个功能 bool IsPasswordProtectedZipFile string pathToFile 我不需要解压缩该文件 我只需要验证它是否是 ZIP
  • 可拖动的CALayer

    有什么方法可以让用户拖动 CALayer 吗 如果是这样 怎么办 在可可 Mac 中 图层本身无法接收鼠标事件 您必须在包含该层的视图或视图控制器中进行事件处理 If a mouseDragged 事件起源于某个层 请参见 CALayer
  • 在C++中使用const成员变量有什么优点

    我写代码像 template
  • python-re:如何匹配字母字符

    如何将字母字符与正则表达式匹配 我想要一个角色 w但不在 d 我希望它与 unicode 兼容 这就是为什么我不能使用 a zA Z 你的前两句话互相矛盾 在 w但不在 d 包括下划线 我从你的第三句话假设你不需要下划线 在信封背面使用维恩
  • iOS:用于登录屏幕的表格样式文本字段?

    我想制作一个像 Facebook 应用程序那样的登录屏幕 我想要复制的部分是两个文本字段 它们堆叠起来看起来像一个表格组 但我不明白他们是怎么做到的 谁知道其中的窍门 我无法发布图片 因为我是 stackoverflow 的新手 这是一种效
  • PHP 图片大小小于 1mb

    目前我正在使用以下内容来计算文件大小是否小于 1MB 但是由于以下代码来自 9lession 示例站点 它说要检查 1mb 的大小 但如果我乘以 1024 2 这就是他们在这里所做的不等于 1mb 而是 2048kb 说它上传的大小不是以
  • 如何在 macOS 上的 SwiftUI 中检测键盘事件?

    如何在 macOS 上的 SwiftUI 视图中检测键盘事件 我希望能够使用击键来控制特定屏幕上的项目 但不清楚如何检测键盘事件 这通常是通过覆盖keyDown event NSEvent in NSView 与 Xcode 12 捆绑在一
  • 将正则表达式匹配到第一个空格

    我必须填充商店的图像 并提供以下格式的图像文件夹 例如BRL0368 Side jpg 5510 Dura Guard Carpet jpg 现在我想做的就是把所有这些都砍掉 这样我就可以尝试在 Excel 中匹配零件编号 例如 BRL03
  • Func 的性能和继承

    我一直无法理解使用的性能特征Func lt gt 在我的代码中 当使用继承和泛型时 我发现自己一直在使用继承和泛型的组合 让我从一个最小的测试用例开始 这样我们都知道我们在说什么 然后我将发布结果 然后我将解释我的期望和原因 最小测试用例