问题

为什么微软建议超过64字节不要使用结构?

回答
坦白说,微软官方并没有一个明确的、普遍适用的“超过64字节就不要使用结构”的硬性建议。这样的说法更像是一种在特定场景下的经验之谈,或者是对某种优化策略的片面理解。

然而,我们可以从几个方面来理解为什么在某些情况下,开发者可能会倾向于避免过大的结构体,以及这种“64字节”的说法可能源自何处。这背后涉及到的主要是性能、内存布局以及一些底层硬件的交互。

1. CPU缓存与性能

现代CPU为了提升处理速度,都会使用多级缓存(L1, L2, L3)。当CPU需要访问数据时,它会首先检查缓存。如果数据在缓存中,那么访问速度会非常快。如果不在缓存中,CPU就需要从主内存(RAM)中读取,这个过程会慢很多,被称为“缓存未命中”。

CPU缓存是以“缓存行”(Cache Line)为单位进行数据传输的。 常见的缓存行大小是64字节(也有32字节或128字节的,但64字节非常普遍)。这意味着,CPU在从主内存读取数据时,会将一块连续的64字节数据一起加载到缓存中。

现在,想象一下你的结构体。

如果结构体的大小刚好是64字节,或者小于64字节,那么很有可能在CPU访问结构体的任何一个成员时,整个结构体都会被一次性加载到CPU缓存的一个缓存行里。 这意味着后续对结构体其他成员的访问,由于它们已经在缓存中,速度会非常快。

如果结构体的大小 恰好 超过64字节,比如65字节,会发生什么? 这就可能导致问题。当CPU第一次访问这个结构体的某个成员,并发现它不在缓存中时,它会读取一个64字节的缓存行。如果结构体的其他部分(在第一个64字节之后)还需要被访问,那么CPU可能需要执行第二次的缓存行读取。换句话说,一个“跨越”了缓存行边界的结构体,访问时可能需要两次内存读取,这会引入额外的延迟。

如果结构体非常大,远超过64字节(比如几百字节甚至上千字节),情况会更糟。 每次对结构体中不同部分的访问,都可能触发新的缓存行读取。如果结构体非常“分散”地存储在内存中(即使是定义在一起,但由于其他数据的插入,实际物理地址可能不连续),这种缓存未命中的概率会急剧增加。

这里的“64字节”禁忌,很可能就是来源于这个缓存行的大小。 开发者希望将频繁访问的数据结构尽量“塞满”一个缓存行,或者以一种不会“跨越”缓存行的方式来组织数据,从而最大化缓存的利用效率,减少CPU等待内存的时间。

2. 内存对齐与填充

操作系统和编译器为了优化内存访问,会对数据进行对齐。这意味着,某些类型的数据(比如int, long, pointer)会被放置在内存地址是其大小倍数的地址上。

结构体内的成员变量,编译器为了提高访问效率,也会对其进行对齐。 例如,一个`char`(1字节)后面跟着一个`int`(4字节),编译器可能会在`char`和`int`之间插入填充字节,以确保`int`的起始地址是4的倍数。

整个结构体本身,在分配内存时,也可能根据其内部最严格的对齐要求进行对齐。

当结构体的大小加上内部的填充,使得它的大小刚好“卡”在缓存行的边界之外时,就可能导致上面提到的性能问题。例如,一个结构体内部所有成员加起来是60字节,但由于对齐要求,编译器可能在最后添加了8个字节的填充,使其总大小达到68字节。这时,这个68字节的结构体就会跨越一个64字节的缓存行。

3. 函数参数传递(栈分配)

在一些早期的C/C++ ABI(Application Binary Interface,应用程序二进制接口)中,或者在特定的编译选项下,函数传递的参数(尤其是结构体)可能会被压入栈中。

如果一个结构体非常大,将其作为函数参数传递(按值传递)意味着需要将整个结构体复制一份到栈上,或者将指向它的指针传递。 如果是按值传递,复制整个大结构体本身就是一个耗时的操作。

另外,在函数调用时,寄存器也是有限的。 较小的结构体(或者基本类型)可以直接放入CPU的寄存器中传递,这样效率最高。而非常大的结构体,无法放入单个寄存器,就只能通过内存(栈)来传递,这自然会慢一些。

一些编译器的优化策略,比如“寄存器分配”,会优先将寄存器分配给那些频繁使用且大小合适的数据。一个过大的结构体,即便是作为局部变量,其在寄存器中的生命周期也可能受限,更容易被“赶出”寄存器,回到内存中。

4. 考虑“值传递”的成本

当你在代码中写 `MyStruct data = anotherStruct;` 或者 `void process(MyStruct s);` 时,你是在进行“值传递”或“值复制”。

对于小结构体,这种复制开销很小。 比如一个包含两个int的结构体,复制起来很快。

对于一个包含100个int的结构体(400字节),如果按值传递,意味着每次调用函数或赋值,都需要复制400字节的数据。 这对性能的影响是累积的。

因此,对于大的结构体,通常更推荐传递指针或引用(`MyStruct s` 或 `MyStruct& s`),这样只传递一个地址,而不是复制整个数据。但即便如此,如果结构体非常大,我们还是会从整体设计上考虑,是否应该将其拆分。

总结一下,那个“64字节”的说法,绝不是一个硬性规定,而更像是一种基于CPU缓存机制和内存访问效率的“经验法则”或“性能优化技巧”。

核心思想是: 尽量让频繁操作的、相互关联的数据,能够“友好地”映射到CPU缓存的缓存行中,减少缓存未命中的次数。

它提醒我们: 当我们定义一个结构体时,要考虑它的成员组合,以及它的大小。如果一个结构体非常庞大,它可能:
一次性加载到缓存的开销太大。
容易跨越缓存行边界,导致多次内存读取。
按值传递的成本过高。
可能暗示了设计上的问题,即这个“大块头”是否应该被拆分成更小、更内聚的部分。

所以,与其说“不要使用超过64字节的结构”,不如说“要警惕非常大的结构体,并审慎考虑其设计和使用方式,尤其是在性能敏感的场景下。” 如果你的结构体正好在64字节附近,并且你的代码大量地创建、复制或频繁访问它,那么你可能需要关注一下它对性能的影响,考虑是否可以通过优化(比如拆分、使用指针/引用)来提升效率。但如果一个结构体是用来存储一组紧密相关的、稍大的数据(比如一个复杂的点云数据块),并且你每次操作都是对整个数据块,那么其大小也就不那么重要了。关键在于“使用场景”和“访问模式”。

网友意见

user avatar

针对这个“64字节”的细节更新一下答案。

如果这个建议是针对2016年以后的.NET Framework(64-bit) 和.NET Core,那么原因是因为RyuJIT 编译器对struct promotion 优化(其他编译器叫做scalar replacement)有64-byte 的struct 尺寸限制。即64-byte 以上的未逃逸struct 不会被拆包并直接计算它的每个field。struct promotion 是.NET 中非常重要的一项优化,因为RyuJIT 的SSA 只针对local variables,struct/class objects 上的冗余操作无法被直接优化掉,比如临时struct object 经常会产生大量memory copy。然而如果一个struct 被promoted 了,那编译器会打开这个struct 并将每一个field 视为local variable 进行优化。

上个月我已经把这个限制放宽到了128-byte Improve struct promotion for 256-bit SIMD fields by fiigii · Pull Request #19663 · dotnet/coreclr

=====================

因为C# 的struct 是value type。

微软设计struct 的一个原因就是要赋予程序员精确控制内存的能力,比如让有能力的开发者把value types 保持在stack 上来避免一些GC 开销。然而由于value type 的特性(比如call-by-value,捕获变量要copy到GC heap 上去,等等),很多时候总是没办法避免copy 整个struct的,比如虽然在函数间传递大尺寸struct 时并不是“直接”copy 整个struct,而是传递一个hidden return buffer 的指针,但你一旦不小心改变了struct 的一部分值,编译器就必须帮你copy 整个struct 来保证call-by-value 的语义。所以用struct 来提升性能时必须是struct 的copy 开销要小于对应class 的GC 开销才行,越大的struct 当然copy 开销也就越大。

性能分析这种事对于一般的开发者要求还是太高了,所以微软直接给出给出一个建议让你记住就行了。我觉得靠谱。(对于不满足于这个level的人,可配合profiling参考 @赵劼 姐夫的答案)

再者为什么是“64 byte”这个数值,我只能猜了,一个很有可能的原因是在目前的CPU 体系结构下很难对64 byte (512 bit)以上的struct 做类似HVA/HFA 的first-class struct 优化,这种优化就是把一个普通struct 当作SIMD type 在SIMD register (xmm, ymm, 或zmm)中传递/拷贝/计算,从而尽量避免内存访问。

user avatar

因为值类型传递时是要复制一份的,复制太多性能差。

当然,规则不要硬记,用对以后使用大值类型提高性能也很正常,例如我的项目里就有两百多个字段的值类型,然后开一个大数组,反复复用,于是内存里只有一个大对象,不会回收不回移动,对GC毫无压力。

你问大结构传参怎么办,ref了解一下。事实上因为这种模式对于性能优化太常见,C# 7还提供了readonly ref和ref return和ref local更进一步鼓励人们使用这种模式。

类似的话题

  • 回答
    坦白说,微软官方并没有一个明确的、普遍适用的“超过64字节就不要使用结构”的硬性建议。这样的说法更像是一种在特定场景下的经验之谈,或者是对某种优化策略的片面理解。然而,我们可以从几个方面来理解为什么在某些情况下,开发者可能会倾向于避免过大的结构体,以及这种“64字节”的说法可能源自何处。这背后涉及到.............
  • 回答
    这事儿,说起来也挺有意思的,也挺普遍的。你想啊,QQ和微信在中国那可是国民级的社交软件,几乎人人都在用,而且功能又多又方便。所以,当一些外国朋友想在国内开展业务,或者和中国的朋友、同事建立联系的时候,自然就会想到用QQ和微信。可问题就出在这儿了,QQ和微信的注册流程,对外国人来说,那可真是够折腾的。.............
  • 回答
    你好!看到你对微电、光电、通信和电子这几个方向的选择感到困惑,这是很正常的。这些专业都属于大电子信息类,既有联系又有各自侧重的领域,选对方向对未来的学习和职业发展至关重要。别急,我来帮你梳理一下,咱们一块儿好好分析分析。首先,我们要明确一点:没有绝对“最好”的选择,只有最适合你的选择。 你的兴趣、你.............
  • 回答
    作为学生党,想做微商,选择一个靠谱的上家确实是成功的第一步。这不像在实体店购物,可以直接看到产品和商家,微商这个模式很大程度上依赖于信任和沟通。下面我给大家分享一些我自己的经验和建议,希望能帮助大家少走弯路。首先,我们要明白,微商不是“一夜暴富”的捷径,而是一个需要投入时间和精力去经营的事业。 找上.............
  • 回答
    .......
  • 回答
    微软删除世界上最大的公开人脸识别数据库,这一行为可以从技术、道德、法律和社会责任等多个维度来理解。虽然微软官方的具体解释可能侧重于某些方面,但背后往往是多重因素交织的结果。核心原因概览:微软删除其公开人脸识别数据库,最主要的原因可以归结为:担忧其被滥用,以及对潜在的隐私侵犯和歧视性应用的风险管理。详.............
  • 回答
    微软拥有丰富的开发资源,但 Windows Phone(现已更名为 Windows 10 Mobile)的更新缓慢是一个复杂的问题,涉及技术、市场、战略、生态系统等多个层面。下面将详细分析其原因: 一、技术层面的挑战1. 平台架构的碎片化与历史包袱: Windows Phone 7/8.............
  • 回答
    微软的Modern UI(如Windows 8及后续版本)与苹果和Google的扁平化界面在设计哲学、用户习惯、文化背景和技术实现上存在显著差异,导致前者在部分用户群体中接受度较低,而后者则广受好评。以下是详细分析: 1. 设计哲学与历史背景 微软的Modern UI:从“触摸优先”到“功能导向” .............
  • 回答
    微软在Windows和Office产品中没有像某些免费软件那样植入明显广告的原因,可以从多个层面来理解,这并非一个简单的“做还是不做”的决定,而是基于其商业模式、品牌定位、用户体验以及市场策略的深思熟虑。1. 商业模式的核心差异:收费与免费最根本的原因在于Windows和Office本身就是付费产品.............
  • 回答
    微软市值能够长时间保持世界第一,并且持续稳定在万亿美元以上,这绝非偶然,而是其多年来深耕技术、精准战略布局、不断创新和适应市场变化的综合结果。下面我将从多个维度为您详细阐述:一、 核心业务的强大基石与多元化发展:1. Windows 和 Office:不可动摇的生态系统霸主 操作系统垄.............
  • 回答
    微软 OneDrive 和百度网盘在文件上传和同步机制上存在一些本质的区别,这些区别直接导致了 OneDrive 不支持我们通常理解的“秒传”功能,而百度网盘则将其作为一项核心卖点。要详细解释这一点,我们需要从以下几个方面入手:1. “秒传”的本质:文件校验和去重首先,我们需要明确百度网盘的“秒传”.............
  • 回答
    微软的软件为何普遍体型庞大,这并非偶然,而是由多种复杂因素共同作用的结果,其中包含着技术演进、市场策略以及历史包袱等方方面面的考量。要深入理解这一点,我们需要一层层剥开它背后的逻辑。首先,得从微软的核心产品和其市场定位说起。微软最广为人知的两大产品线,Windows操作系统和Office办公套件,都.............
  • 回答
    微软 WP 的“Metro 风格”的确是它与苹果 iOS 和谷歌 Android 最显著的区别之一,这也是很多人对它产生好奇甚至争议的原因。要深入理解为什么微软要选择这条不同的交互道路,我们需要从几个层面来分析:一、 历史渊源与设计哲学:从Windows到Windows Phone微软的Metro设.............
  • 回答
    过去几年,.NET 和 C 在国内的“没落”论调确实甚嚣尘上,而与此形成鲜明对比的是,在欧美等发达国家,.NET 的地位依旧稳固,甚至可以说是如日中天。这背后的原因错综复杂,涉及到技术生态、市场需求、人才培养以及国内互联网行业发展路径的特殊性等多个维度。咱们就掰开了揉碎了好好聊聊。首先,我们得承认,.............
  • 回答
    要理解微软为什么不像其他一些软件那样提供一个简单粗暴的“关闭自动更新”开关,我们需要深入剖析其背后的多重考量。这并非仅仅是企业想“管着”用户,而是关乎到整个操作系统生态的稳定、安全以及用户体验的整体性。一、 安全是压倒一切的首要任务这是最关键也是最无可辩驳的原因。Windows作为全球最普及的操作系.............
  • 回答
    微软在 Windows 10 自动更新这件事情上,可以说是踩过很多坑,也收到了海量的用户反馈,但至今为止,那套“一刀切”式的强制自动更新机制,依然是许多用户心中的痛点。为什么微软就是不肯彻底改呢?这背后其实牵扯到很多复杂的考量,远不止“用户体验差”这么简单。要拆解这个问题,咱们得从几个层面去理解:1.............
  • 回答
    直到 Windows 10 之前,命令行(或者说 cmd.exe,以及后来的 PowerShell 在其基础上运行时)在粘贴文本这件事情上,确实让不少习惯了现代图形界面的用户感到不适应。很多用户可能会疑惑,为什么一个如此基础且普遍的操作,在命令行里却迟迟没有得到原生支持,非要等到 Windows 1.............
  • 回答
    微软日渐萎靡,这个说法本身就有些微妙。要说“日渐萎靡”,可能得先定义清楚这个“萎靡”指的是什么。如果以市值和整体营收来衡量,微软无疑还在全球科技巨头的行列,甚至在某些领域(如云计算)表现强势。但如果从更深层次的创新活力、对新兴趋势的引领能力,或者说曾经那种颠覆性的影响力来看,确实会有人觉得它不如从前.............
  • 回答
    微软不将Windows的旧版本系统开源,这背后有着多方面的原因,而且这些原因相互交织,形成了一个复杂的局面。简单来说,开源一个庞大、复杂的商业操作系统,就像是将自己最核心的商业秘密拱手让人,对微软的生存和发展来说,这几乎是不可想象的。首先,我们得明白,Windows从来都不是一个“自由软件”或者“开.............
  • 回答
    微软开发 Windows 10,这可不是个一时兴起的决定,背后有一系列深思熟虑的战略考量和对市场趋势的敏锐洞察。简单来说,微软希望通过 Windows 10 来解决过去几代 Windows 系统的一些痛点,同时为未来的计算环境打下坚实的基础。咱们一点点掰开了聊聊这个过程。首先,得说说 Windows.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有