为什么16字节是C#中struct的推荐大小?

时间:2010-03-09 08:56:10

标签: c# struct memory-management class-design

我阅读了Cwalina一书(关于.NET应用程序开发和设计的建议)。

他说一个好的设计结构必须小于16个字节(出于性能目的)。

为什么会这样?

而且(更重要的是)如果我在Windows 7 x64下运行我的.NET 3.5(即将成为.NET 4.0){strong> 64位应用程序Core i7 (这是基于CPU / OS的限制)?

再次强调 - 我需要尽可能高效的结构。我试着一直把它放在堆栈上。该应用程序是多线程的,并且以亚毫秒为间隔运行,结构的当前大小为64字节。

7 个答案:

答案 0 :(得分:21)

你错误地引用了这本书(至少是第2版)。 Jeffrey Richter声明,如果符合以下条件,则值类型可以超过16个字节:

  

您不打算将它们传递给其他人   方法或将它们复制到一个或从一个   集合类。

此外,Eric Gunnerson补充说(关于16字节限制)

  

使用此指南作为触发器   更多的调查。

结构“必须小于16个字节”是不正确的。这一切都取决于使用情况。

如果您正在创建结构并使用它并且担心性能,那么使用分析器比较结构与类,以查看最适合您的结构。

答案 1 :(得分:10)

JIT编译器生成特殊情况代码以复制小于某个阈值的结构,并为较大的结构生成稍微慢一些的通用构造。我猜想当写出这个建议时,阈值是16个字节,但在今天的32位框架中它似乎是24个字节;对于64位代码,它可能更大。

话说回来,创建任何大小类对象的成本远远大于复制保存相同数据的结构的成本。如果代码创建一个具有32字节字段的类对象,然后传递或以其他方式复制对该对象的引用1,000次,则复制1,000个对象引用而不必复制1,000个32字节结构所节省的时间可能会超过成本创建类对象。但是,如果在仅仅复制引用两次之后对象实例将被放弃,则创建对象的成本可能会大大超过复制32字节结构两次的成本。

另请注意,在许多情况下,如果有人愿意,可以避免按值传递结构或以其他方式冗余地复制它们。例如,将任何大小的结构作为ref参数传递给方法只需要传递单机字(4或8字节)地址。因为.net缺少任何类型的const ref概念,只有可写字段或变量可以通过这种方式传递,而.net中构建的集合 - 除System.Array之外 - 无法通过以下方式访问成员: ref。但是,如果有人愿意使用数组或自定义集合,则可以非常有效地处理大型(100字节或更多)结构。在许多情况下,使用结构而不是不可变类的性能优势可能会随着封装数据的大小而增长。

答案 2 :(得分:9)

只有您知道您的结构在程序中的使用方式。但如果没有别的,你可以随时测试它。例如,如果它经常传递给其他功能,以下内容可能会照亮您:

class MainClass
{
    static void Main()
    {
        Struct64 s1 = new Struct64();
        Class64 c1 = new Class64();
        DoStuff(s1);
        DoStuff(c1);
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < 10000; i++)
        {
            s1 = DoStuff(s1);
        }
        sw.Stop();
        Console.WriteLine("Struct {0}", sw.ElapsedTicks);
        sw.Reset();

        sw.Start();
        for (int i = 0; i < 10000; i++)
        {
            c1 = DoStuff(c1);
        }
        sw.Stop();
        Console.WriteLine("Class {0}", sw.ElapsedTicks);
        sw.Reset();

        Console.ReadLine();
    }
}

使用:

public class Class64
{
    public long l1;
    public long l2;
    public long l3;
    public long l4;
    public long l5;
    public long l6;
    public long l7;
    public long l8;
}
public struct Struct64
{
    public long l1;
    public long l2;
    public long l3;
    public long l4;
    public long l5;
    public long l6;
    public long l7;
    public long l8;
}

尝试使用代表性结构/类的这种东西,看看你得到了什么结果。 (在我的机器上,上面的测试,这个类似乎快了〜3倍)

答案 3 :(得分:4)

  • 较大的结构不是那么有效,但是......如果你有更多的数据,你就拥有它。没有必要谈论那里的效率。

  • 64个字节应该没问题。

  • 主要原因可能是复制操作....如果结构较大,会使IIRC变慢。它必须被复制很多。

我通常建议在这里使用类;)但是如果不知道结构的内容,那就有点棘手了。

答案 4 :(得分:3)

我在进行汇编语言编程时学到的一件事是你将结构(和其他数据)对齐在8或16字节边界上(在MASM中实际上有一个预处理器命令)。这样做的原因是在内存中存储或移动内容时,内存访问速度要快得多。保持结构大小不超过16个字节将确保成功完成内存对齐,因为没有任何内容可以跨越对齐边界。

但是,在使用.NET框架时,我希望在很大程度上隐藏这种事情。如果你的结构大于16个字节,那么我希望框架(或JIT编译器)在32字节边界上对齐你的数据,依此类推。看到这些书中评论的一些验证会很有趣,看看速度的差异是什么。

答案 5 :(得分:2)

实际上,在64位机器上,32字节是用于结构数据的最大大小。从我的测试中,32字节结构(四个长)与对象引用一样执行。

您可以在此找到测试代码并自行运行测试: https://github.com/agileharbor/structBenchmark

最后几个测试将大量的结构/对象从一个数组复制到另一个数组,并且你能够看到类的结果优于具有8个long的结构,但它与具有4个long的结构大致相同。 / p>

答案 6 :(得分:1)

我认为另一个关键点是,如果你有一个包含一百万个结构的列表,那么堆上只有一个内存分配,而一个包含一百万个类的列表将有大约一百万个单独的堆对象。垃圾收集负载在两种情况下都是完全不同的。从这个角度来看,结构可能会提前出现。