C#大型数组的内存优化

5
这里有两个代码片段,一个是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.01秒 内存: 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.05秒 内存:38288 kB

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

正如你所看到的,C#的执行时间是C++的5倍,内存消耗是C++的15倍。


7
在深入讨论之前,值得注意一些事情:你的数组不同。C/C++示例中的数组在栈上,而在C#中则在堆上。在32位处理器上,C#中的ij变量占用更大的内存空间,可能需要更多的处理器资源。假设(考虑到迭代次数)C/C++示例中的“unsigned int”为4字节,则应使用uint。在C/C++示例中,p[0]p[1]将未初始化,因此可能存在疑问,但在C#示例中为0。 - pickypg
1
在C#示例中,加号s是一个double,而在C/C++示例中则是一个ulong(实际上)。整数运算几乎总是比浮点运算快。 - pickypg
1
你的“C++代码示例”是用纯C编写的。因此,在证明任何东西之前,先学习一些知识可能会有所帮助。 - Öö Tiib
1
你没有计时打印,是吗?那不算数。:P - user541686
3
@pickypg: 不要忘记更少的错误。@Dimitry: 正如你所看到的,这两个并不是在做“完全相同的事情”,只是类似的事情。 - R. Martinho Fernandes
5个回答

10

在“发布模式”下编译和运行。当以“发布模式”构建并运行C#版本时,时间精度可以达到0.01秒。就内存消耗而言,你正在比较“苹果”和“橙子”。托管环境会消耗更多的内存,因为它托管CLR(公共语言运行时)和垃圾回收器,这两者都不是没有代价的。


测量是由Ideone服务器完成的。如果它测量包括运行时使用的内存,那么额外的32 Mb内存来自哪里就变得清楚了。 - v00d00
1
我理解你的意思,但我仍然认为这是苹果和苹果之间的比较。在两种情况下,您都在比较整个进程的内存消耗。这是公平的。.NET使用更多的内存,内存使用的比较应该反映出这一点。 - jalf

6
我该如何改进C#代码来向同事证明C#可以像C++一样快?
你做不到。在某些方面,C++确实比C#更快。但是,在某些情况下,C#代码将比等效的C++代码执行得更好。它们是不同的语言,具有不同的优缺点。
但作为程序员,你应该基于逻辑进行决策。
逻辑决定你应该先收集信息,然后再根据信息做出决定。
相反,你首先做出了决定,然后寻找支持它的信息。这可能对政客有效,但不是编写软件的好方法。
不要去寻找C#比C++更快的证据。相反,检查哪个选项在你的情况下更快。
无论如何,如果你想证明X可以像Y一样快,你必须按照通常的方式做:使X和Y一样快。在性能调整时,分析器是你最好的朋友。找出额外时间花费在哪里,然后找出如何消除它。
虽然内存使用是一个失落的原因。由于几个原因,.NET使用更多内存:
- 它有一个更大的运行时库,必须存在于进程的地址空间中 - .NET对象具有C++类中不存在的其他成员,因此它们使用更多内存 - 垃圾回收器意味着你通常会有一些“不再使用但尚未回收”的内存。在C++中,内存通常会立即释放。在.NET中不是这样的。.NET基于内存便宜的假设(通常是正确的)

6

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

使用"unsafe"(非托管)方式...每次你在做someSortOfArray[i]操作时,.NET框架会执行各种花哨的操作(如越界检查),这需要消耗时间。

这正是使用非托管方式的关键之处(然后使用指针并执行myPointer++)。

请注意,如果你使用非托管方式,并且仍然使用for-loop并执行someArray[i]操作,则没有节省任何时间。

以下S.O.问题也许可以帮助你:True Unsafe Code Performance

免责声明

顺便说一下,我并不是建议总是这样做,而是作为回答特定问题的方案。


3
JIT 可以跳过越界检查,因为它永远不会越界。 - R. Martinho Fernandes
我可以在堆栈上分配1MB的数组吗?并且从那里获取数据? - v00d00
@Dmitry:有stackalloc(可能是C#规范中唯一提到这个“堆栈”东西的地方:)。如果堆栈上有1兆可用空间,你就可以使用它。 - R. Martinho Fernandes
1
@Martinho - 32位Jitter只在某些情况下消除边界检查。例如,如果您将数组的长度存储在本地变量中,然后检查循环变量是否与其相等,那么您将进行边界检查。在这方面,64位Jitter的行为不同(优化较少)。 (这来自我几个月前读过的一篇文章,但我目前不幸找不到它。) - phoog

3

提醒一下关于时间问题,您的代码没有显示如何测量执行时间。.NET应用程序在启动时可能会有合理的开销,因此如果您只关注循环的执行时间,建议多次运行内部循环(跳过第一到第二次迭代),并测量其他迭代的平均值。

我预计结果会更相似。然而,像通常使用'peak performance'时,内存管理方面的预防措施很重要。在这里,可能只需要避免在测量函数中使用'new'。每个迭代都重复使用p[]


1

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


你可以在垃圾回收之前设置程序使用的内存量,我们应该如何实现这一点? - user492238
@user492238 - 也许这是不可能的 - Brendan Long

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接