草庐IT

C# 性能好奇心

coder 2024-05-19 原文

对下面的程序真的很好奇(是的,在没有附加调试器的情况下以 Release模式运行),第一个循环为数组的每个元素分配一个新对象,运行大约需要一秒钟。

所以我想知道哪个部分花费的时间最多——对象创建或分配。所以我创建了第二个循环来测试创建对象所需的时间,第三个循环来测试分配时间,两者都在几毫秒内运行。怎么回事?

static class Program
{
    const int Count = 10000000;

    static void Main()
    {
        var objects = new object[Count];
        var sw = new Stopwatch();
        sw.Restart();
        for (var i = 0; i < Count; i++)
        {
            objects[i] = new object();
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds); // ~800 ms
        sw.Restart();
        object o = null;
        for (var i = 0; i < Count; i++)
        {
            o = new object();
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds); // ~ 40 ms
        sw.Restart();
        for (var i = 0; i < Count; i++)
        {
            objects[i] = o;
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds); // ~ 50 ms
    }
}

最佳答案

当创建一个占用少于 85,000 字节 RAM 且不是 double 数组的对象时,它被放置在称为零代堆的内存区域中。每当 Gen0 堆增长到一定大小时,Gen0 堆中系统可以找到事件引用的每个对象都会被复制到 Gen1 堆;然后 Gen0 堆被批量删除,因此它有空间容纳更多新对象。如果 Gen1 堆达到一定大小,那里存在引用的所有内容都将被复制到 Gen2 堆,于是 Gen0 堆可以被批量删除。

如果创建了许多对象并立即放弃,Gen0 堆将反复填满,但只有极少数的对象需要从 Gen0 堆复制到 Gen1 堆。因此,Gen1 堆将被非常缓慢地填充,如果有的话。相比之下,如果 Gen0 堆中的大部分对象在 Gen0 堆变满时仍被引用,则系统将不得不将这些对象复制到 Gen1 堆中。这将迫使系统花时间复制这些对象,也可能 Gen1 堆填满,以至于必须扫描事件对象,并且必须将那里的所有事件对象再次复制到 Gen2 堆.所有这些都需要更多时间。

另一个在您的第一个测试中减慢速度的问题是,当尝试识别所有事件的 Gen0 对象时,系统可以忽略任何 Gen1 或 Gen2 对象,前提是它们自上次 Gen0 收集后未被触及。在第一个循环中,objects 数组会不断被触及;因此,每个 Gen0 集合都必须花时间处理它。在第二个循环中,它根本没有被触及,所以即使会有同样多的 Gen0 收集,它们也不会花费那么长的时间来执行。在第三个循环中,数组将不断被触及,但不会创建新的堆对象,因此不需要垃圾收集周期,也不需要花费多长时间。

如果你要添加第四个循环,它在每次通过时创建和放弃一个对象,但它还在一个数组槽中存储一个对预先存在的对象的引用,我预计它会花费比合并时间更长的时间第二个和第三个循环,即使它会执行相同的操作。也许没有第一次循环那么多时间,因为很少有新创建的对象需要从 Gen0 堆中复制出来,但比第二次循环时间长,因为需要额外的工作来确定哪些对象仍然存在。如果您想进一步探索,使用嵌套循环进行第五次测试可能会很有趣:

for (int ii=0; ii<1024; ii++)
  for (int i=ii; i<Count; i+=1024)
     ..

我不知道确切的细节,但 .NET 试图通过将它们分割为 block 来避免必须扫描整个大数组,其中只有一小部分被触及。如果大数组的一个 block 被触及,则必须扫描该 block 内的所有引用,但存储在自上次 Gen0 收集以来未被触及的 block 中的引用可能会被忽略。如上所示打破循环可能会导致 .NET 最终触及 Gen0 集合之间数组中的大部分块,很可能产生比第一个循环更慢的时间。

关于C# 性能好奇心,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18165019/

有关C# 性能好奇心的更多相关文章

  1. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  2. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  3. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  4. Ruby 的数字方法性能 - 2

    我正在使用Ruby解决一些ProjectEuler问题,特别是这里我要讨论的问题25(Fibonacci数列中包含1000位数字的第一项的索引是多少?)。起初,我使用的是Ruby2.2.3,我将问题编码为:number=3a=1b=2whileb.to_s.length但后来我发现2.4.2版本有一个名为digits的方法,这正是我需要的。我转换为代码:whileb.digits.length当我比较这两种方法时,digits慢得多。时间./025/problem025.rb0.13s用户0.02s系统80%cpu0.190总计./025/problem025.rb2.19s用户0.0

  5. ruby - Ruby 性能中的计时器 - 2

    我正在寻找一个用ruby​​演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent

  6. ruby-on-rails - 如果条件与 &&,是否有任何性能提升 - 2

    如果用户是所有者,我有一个条件来检查说删除和文章。delete_articleifuser.owner?另一种方式是user.owner?&&delete_article选择它有什么好处还是它只是一种写作风格 最佳答案 性能不太可能成为该声明的问题。第一个要好得多-它更容易阅读。您future的自己和其他将开始编写代码的人会为此感谢您。 关于ruby-on-rails-如果条件与&&,是否有任何性能提升,我们在StackOverflow上找到一个类似的问题:

  7. c# - C# 中的 Flatten Ruby 方法 - 2

    我如何做Ruby方法"Flatten"RubyMethod在C#中。此方法将锯齿状数组展平为一维数组。例如:s=[1,2,3]#=>[1,2,3]t=[4,5,6,[7,8]]#=>[4,5,6,[7,8]]a=[s,t,9,10]#=>[[1,2,3],[4,5,6,[7,8]],9,10]a.flatten#=>[1,2,3,4,5,6,7,8,9,10 最佳答案 递归解决方案:IEnumerableFlatten(IEnumerablearray){foreach(variteminarray){if(itemisIEnume

  8. ruby - 如何找到我的 Ruby 应用程序中的性能瓶颈? - 2

    我编写了一个Ruby应用程序,它可以解析来自不同格式html、xml和csv文件的源中的大量数据。我如何找出代码的哪些区域花费的时间最长?有没有关于如何提高Ruby应用程序性能的好资源?或者您是否有任何始终遵循的性能编码标准?例如,你总是用加入你的字符串吗?output=String.newoutput或者你会使用output="#{part_one}#{part_two}\n" 最佳答案 好吧,有一些众所周知的做法,例如字符串连接比“#{value}”慢得多,但是为了找出您的脚本在哪里消耗了大部分时间或比所需时间更多,您需要进行分

  9. ruby - 可以像在 C# 中使用#region 一样在 Ruby 中使用 begin/end 吗? - 2

    我最近从C#转向了Ruby,我发现自己无法制作可折叠的标记代码区域。我只是想到做这种事情应该没问题:classExamplebegin#agroupofmethodsdefmethod1..enddefmethod2..endenddefmethod3..endend...但是这样做真的可以吗?method1和method2最终与method3是同一种东西吗?还是有一些我还没有见过的用于执行此操作的Ruby惯用语? 最佳答案 正如其他人所说,这不会改变方法定义。但是,如果要标记方法组,为什么不使用Ruby语义来标记它们呢?您可以使用

  10. c# - Ruby 等效于 C# Linq 聚合方法 - 2

    什么是Linq聚合方法的ruby​​等价物。它的工作原理是这样的varfactorial=new[]{1,2,3,4,5}.Aggregate((acc,i)=>acc*i);每次将数组序列中的值传递给lambda时,变量acc都会累积。 最佳答案 这在数学以及几乎所有编程语言中通常称为折叠。它是更普遍的变形概念的一个实例。Ruby从Smalltalk中继承了这个特性的名称,它被称为inject:into:(像aCollectioninject:aStartValueinto:aBlock一样使用。)所以,在Ruby中,它称为inj

随机推荐