【问题标题】:C# Memory optimization for large arrays大型数组的 C# 内存优化
【发布时间】:2011-08-30 10:01:04
【问题描述】:

以下是 c++ 和 c# 中的两个代码部分,它们做的事情完全相同:

C++
http://ideone.com/UfL5R

#include <stdio.h>
int main(int argc, char *argv[]) {
  char p[1000000];
  unsigned int i,j;
  unsigned long long s=0;
  for(i=2;i<1000000;i++) p[i]=1;
  for(i=2;i<500000;) {
    for(j=2*i;j<1000000;j+=i) p[j]=0;
    for(i++;!p[i];i++);
  }
  for(i=3,s=2;i<1000000;i+=2) if(p[i]) s+=i;
  printf ("%lld\n",s);
  return 0;
}

时间:0.01s 内存:2576 kB

C#
http://ideone.com/baXYm

using System;

namespace ConsoleApplication4
{
    internal class Program
    {
        private  static void Main(string[] args)
        {
            var p = new byte[1000000];
            ulong i, j;
            double s = 0;
            for(i=2;i<1000000;i++) 
                p[i]=1;

            for(i=2;i<500000;) 
            {
                for(j=2*i;j<1000000;j+=i) 
                    p[j]=0;
                for(i++;p[i]==0;i++);
            }

            for(i=3,s=2;i<1000000;i+=2) 
                if(p[i]!=0) s+=i;

            Console.WriteLine(s);
        }
    }
}

时间:0.05s 内存:38288 kB

如何改进 C# 代码以向我的同事证明 C# 可以和 C++ 一样快?

如您所见,C# 执行时间增加了 5 倍,内存消耗增加了 15 倍。

【问题讨论】:

  • 在深入研究之前可能需要注意一些事情:您的数组是不同的。 C/C++ 示例中的数组位于堆栈上。在 C# 中,它在堆上。 C# 中的 ij 变量占用空间更大,如果处理器是 32 位处理器,则可能需要更多的努力才能使用。使用uint,假设(相当考虑迭代量)unsigned int 在 C/C++ 示例中是 4 个字节。 p[0]p[1] 在 C/C++ 示例中将未初始化,因此存在问题,但在 C# 示例中 0
  • Plus s 在 C# 示例中是 double,在 C/C++ 示例中是 ulong(有效)。整数运算实际上总是比浮点运算快。
  • 你的“C++ 代码示例”是用纯 C 编写的。所以在证明任何东西之前,先学习一些东西可能会有所帮助。
  • 您没有为打印计时,是吗?那还真不算。 :P
  • @pickypg:更不用说不易出错。 @Dimitry:如您所见,两者并没有做“绝对相同的事情”,只是相似的事情。

标签: c# c++ optimization performance


【解决方案1】:

在发布模式下编译和运行。在发布模式下构建和运行时,我从 C# 版本中获得了 0.01 秒。就内存消耗而言,您将苹果与橙子进行比较。托管环境将消耗更多内存,因为它托管 CLR 和垃圾收集器,而这并非没有成本。

【讨论】:

  • 测量由 Ideone 服务器完成。如果它测量包括运行时使用的内存,那么额外的 32 Mb 内存来自哪里就变得很清楚了。
  • 我明白你的意思,但我仍然认为它是苹果对苹果。在@both 情况下,您正在比较整个进程的内存消耗。这还算公平。 .NET 用户更多的内存,内存使用的比较应该反映这一点。
【解决方案2】:

内存使用可能与垃圾回收有关。在 Java 中,内存使用率故意很高——垃圾收集仅在您需要更多内存时发生。这是出于速度原因,所以 C# 做同样的事情是有道理的。您不应该在发布代码中执行此操作,但要显示您实际使用的内存量,您可以在测量内存使用情况之前调用 GC.Collect()。你真的关心它使用了多少内存吗?似乎速度更重要。如果您有内存限制,您可能可以设置程序在垃圾收集之前将使用的内存量。

【讨论】:

  • "你可能可以设置你的程序在垃圾收集之前将使用的内存量"我们应该如何实现呢?
  • @user492238 - 可能是it's not possible
【解决方案3】:

如何改进 C# 代码以向我的同事证明 C# 可以和 C++ 一样快?

你不能。在某些合法领域,C++ 从根本上比 C# 快。但也有一些领域 C# 代码会比等效的 C++ 代码执行得更好。它们是不同的语言,具有不同的优势和劣势。

但作为一名程序员,你真的应该根据逻辑做出决定。

逻辑要求您应该首先收集信息,然后根据该信息做出决定。

相反,你首先做出了决定,然后寻找支持它的信息。 如果您是政治家,这可能会奏效,但这不是编写软件的好方法。

不要去寻找证明 C# 比 C++ 快的证据。相反,请检查哪个选项在您的情况下更快。

在任何情况下,如果你想证明 X 可以和 Y 一样快,你必须按照通常的方式来做:让 X 和 Y 一样快。和往常一样,在进行性能调整时,分析器是你的最好的朋友。找出额外的时间花在了哪里,然后弄清楚如何消除它。

内存使用是一个失败的原因。 .NET 只是使用更多内存,原因如下:

  • 它有一个更大的运行时库,必须存在于进程的地址空间中
  • .NET 对象具有 C++ 类中不存在的其他成员,因此它们使用更多内存
  • 垃圾收集器意味着您通常会有一些“不再使用但尚未回收”的内存。在 C++ 中,通常会立即释放内存。在 .NET 中不是。 .NET 基于内存便宜的假设(这通常是正确的)

【讨论】:

    【解决方案4】:

    如何极大地提高 C# 代码的性能

    为此去“不安全”(非托管)......每次你在做someSortOfArray[i]时,.NET 框架都会做各种需要时间的整洁的事情(比如越界检查) .

    这就是非托管的真正意义所在(然后使用指针并执行 myPointer++)。

    澄清一下,如果您不进行管理,然后仍然执行 for-loop 并执行 someArray[i],那么您什么也没有保存。

    另一个 S.O.可能对您有帮助的问题:True Unsafe Code Performance

    免责声明

    顺便说一句,我并不是说要一直这样做,而是仅作为 THIS 特定问题的答案。

    【讨论】:

    • JIT 可以跳过越界检查,因为它永远不会越界。
    • 我可以在堆栈上分配 1 mb 数组吗?并从那里获取数据?
    • @Dmitry:有stackalloc(可能是 C# 规范中唯一提到人们一直在谈论的“堆栈”的地方:)。如果堆栈上有一个 meg 可用,则可以。
    • @Martinho - 32 位抖动仅在某些情况下消除了边界检查。例如,如果您将数组的长度存储在局部变量中,然后对照该变量检查循环变量,您将进行边界检查。 64 位抖动在这方面表现不同(优化较少)。 (这来自我几个月前读过的一篇文章,很遗憾我现在找不到。)
    【解决方案5】:

    请注意您的时间安排。它没有显示,你是如何测量执行时间的。可以预期 .NET 应用程序在启动时会产生合理的开销。因此,如果您只关心循环的执行时间,则应该多次(多次)运行内部循环,跳过 1..2 次第一次迭代,测量其他迭代并计算平均值。

    我希望结果更相似。然而,与往常一样,在瞄准“峰值性能”时 - 有关内存管理的预防措施很重要。在这里,防止测量功能内部出现“新”可能就足够了。在每次迭代中重用 p[]。

    【讨论】:

      猜你喜欢
      • 2017-02-06
      • 1970-01-01
      • 2016-09-19
      • 1970-01-01
      • 2011-09-14
      • 1970-01-01
      • 2018-02-21
      • 2014-05-05
      • 1970-01-01
      相关资源
      最近更新 更多