13

所以在 c++ 中这很容易。您希望在堆上分配任何类/结构,请使用 new。如果你想要它在堆栈上,不要使用新的。

在 C# 中,我们总是使用 new 关键字,根据它是结构还是类,它被分配在堆栈或堆上(结构进入堆栈,类进入堆) - 在某些应用程序中可能有改变设计时巨大的性能差异使得只有那些对象进入真正属于那里的堆。

我想知道的是 - 有没有一种直接的方法来控制一个对象的分配位置,而不管它是声明为结构还是类?我知道可以将值类型(结构)装箱以进入堆(但装箱/拆箱是以性能为代价的)。有没有办法在堆栈上分配类?

此外,是否有任何机制来分配原始内存并使用 C++ 中的新位置之类的东西?我知道这与被管理的想法不同——但如果您可以使用自定义内存管理,它会产生很大的性能差异。

我喜欢 C# 的便利,因为它是垃圾收集器和其他东西 - 但有时,在处理应用程序的瓶颈时,可能希望对实际发生的事情有更多的控制。

欢迎任何提示/提示:)

编辑:性能示例:

struct Foo1
{
    public int i;
    public float f;
    public double d;
}

struct Foo2
{
   public Foo1[] bar;

   public void Init(){
        bar = new Foo1[100];
        for (int i = 0; i < 100; i++)
            bar[i] = new Foo1();
    }
}

class Program
{
    static void Main(string[] args)
    {
        DateTime time = DateTime.Now;
        Foo2[] arr = new Foo2[1000000];
        for (int i = 0; i < 1000000; i++)
        {
            arr[i] = new Foo2();
            arr[i].Init();
        }

        Console.WriteLine((DateTime.Now - time).TotalMilliseconds);
    }
}

这在我的机器上执行需要 1.8 秒(请注意,实际上只有分配正在进行 - 没有参数传递)

如果将 Foo1 从 struct 更改为 class,则执行需要 8.9 秒!慢了五倍

4

5 回答 5

10

虽然在一般情况下,对象总是在堆上分配,但 C# 确实允许您下降到指针级别,以实现繁重的互操作或非常高性能的关键代码。

不安全的块中,您可以使用stackalloc在堆栈上分配对象并将它们用作指针。

引用他们的例子:

// cs_keyword_stackalloc.cs
// compile with: /unsafe
using System; 

class Test
{
   public static unsafe void Main() 
   {
      int* fib = stackalloc int[100];
      int* p = fib;
      *p++ = *p++ = 1;
      for (int i=2; i<100; ++i, ++p)
         *p = p[-1] + p[-2];
      for (int i=0; i<10; ++i)
         Console.WriteLine (fib[i]);
   }
}

但是请注意,您不需要将整个方法声明为不安全的,您只需unsafe {...}为其使用一个块即可。

于 2010-01-08T22:48:44.937 回答
6

您对值类型与引用类型的去向(堆栈与堆)的解释并不完全正确。

例如,如果结构是引用类型的成员,它们也可以在堆上分配。或者,如果您在通过对象引用传递它们时将它们装箱。

您应该阅读http://www.yoda.arachsys.com/csharp/memory.html以更好地了解不同类型的实际分配位置。

另外,在 .Net 中,您真的不应该关心类型的分配位置——正如 Eric Lippert 所写:堆栈是一个实现细节。你最好理解类型如何传递的语义(按值、被引用等)。

此外,您似乎暗示在堆上分配对象比在堆栈上更昂贵。实际上,我认为复制值类型的性能成本超过了在堆栈上稍微更快的分配所带来的任何好处。堆栈和堆之间的最大区别在于,在大多数 CPU 架构上,堆栈更有可能保留在 CPU 缓存中 - 从而避免缓存未命中。

这不是最需要关心的问题。您应该决定该类型是否应该具有按值传递的语义。如果不是 - 那么也许它应该是一个引用类型。

于 2010-01-08T22:39:21.353 回答
2

不要被new关键字所迷惑,它对于结构是可选的。

在 C# 中有一个托管世界,您可以在其中享受垃圾收集器和类型安全,而不必(必须)担心许多内存细节。堆栈/堆的区别无关紧要,它与复制语义有关。

对于那些您确实需要控制的罕见情况,C# 的不安全(非托管)部分具有真正的指针和所有内容。

但是 C# 中的东西成本与 C++ 中的不同,所以不要猎杀鬼魂,非托管的、短命的对象非常便宜。并且编译器可以在堆栈上分配小数组作为优化,您将无法分辨,也不应该在意。

于 2010-01-08T23:12:22.707 回答
1

不用担心 - 你的头脑仍然在 c / c++ 世界中,事情的发展方向很重要。CLR 团队中有一群非常聪明的人,他们整天都在担心如何神奇地快速实现这一目标。

c# 中有一些陷阱,内存使用通常与意外创建许多小对象有关(在循环中执行字符串 = 字符串 + 其他字符串是经典的)

如果你真的认为你有一个由内存管理引起的性能问题,有一个 memprofiler 会告诉你发生了什么

我已经用 c# 编写了很多性能密集型代码(图形渲染客户端、网络服务器),而且从来不用担心这些

于 2010-01-08T22:48:47.713 回答
1

这是在 C# 中查看结构和类的错误方法。在 C# 中,结构和类之间的区别不在于分配的位置,而在于复制语义。结构具有值语义,类具有引用语义。C++ 程序员倾向于对此进行更多阅读,因为他们习惯于堆栈上的对象具有值语义,而堆上的对象具有引用语义。

如何分配此内存是运行时的实现细节。运行时可以使用堆栈、堆或它喜欢的任何其他混合分配方案。虽然确实通常结构将分配在堆栈之类的东西上,而类将分配在某种堆上,但这不是必需的。例如,在函数中分配的类而不是在函数范围之外传递的类可以很容易地在堆栈上分配。

于 2010-01-08T23:04:09.027 回答