5

开始微优化不是我的目标,所以如果这就是结果,我很乐意放弃这个问题。但我即将开始做出一些设计决策,并希望了解更多信息。

我正在阅读和处理一种文件格式,其中包含许多以明确定义的格式记录的数据结构。我已经在代码中将它们表示为结构。

现在,如果我将结构与 1 字节对齐打包#pragma pack(1),我可以将 IO 流中的结构直接读取到结构指针上。这很方便。如果我不打包结构,我可以fread将字段一个接一个或一个fread块一个接一个地打包,reinterpret_cast结构字段一个接一个,这可能会很快变老。

作为参考,这些结构将被(可能)数千人读取,并且可能会对它们进行一些数字运算。它们主要由无符号 16 位整数(约 60%)、无符号 32 位整数(约 30%)和一些 64 位整数组成。

所以手头的问题是,我...

  • 进行数以万计的微小呼叫fread
  • 读取块并复制相关字节?
  • 打包结构并直接阅读它们?
4

3 回答 3

5

最终,解决方案 A 和解决方案 B 之间的性能差异只能通过基准测试来确定。在互联网上询问会给你不同的结果,这些结果可能反映也可能不反映你的情况。

当您“未对齐”数据时会发生什么情况是处理器需要对一条数据进行多次读取[并且同样适用于写入]。确切需要多少额外时间取决于处理器 - 有些处理器不会自动执行,因此运行时系统将捕获“错误读取”并在某些仿真层执行读取 [或者,在某些处理器中,简单地杀死“未对齐内存访问”的过程]。显然,采取陷阱并执行几个读取操作然后返回到调用代码对性能的影响非常显着——它很容易比对齐的读取操作花费数百个周期。

在 x86 的情况下,它“就像你期望的那样工作”,但通常会额外增加 1 个时钟周期[假设数据已经在 L1 缓存中]。在现代处理器中,一个时钟周期并不是很多,但如果循环的迭代次数为 10000000000000 次并且读取未对齐的数据 n 次,那么您现在已经在执行时间中添加了 n * 10000000000000 个时钟周期,这可能很重要。

其他替代方案也会对性能产生影响。做很多小读可能比做一个大读要慢很多。从性能的角度来看,转换函数可能更好。

同样,请不要将其视为“给定的”,您确实需要比较不同的解决方案(或选择一个,如果性能不差,并且代码看起来不可怕,那就这样吧) . 我相当相信您可以为您建议的“最佳”三种解决方案中的每一种找到案例。

还要记住 #pragma pack是特定于编译器的,例如,要实现允许您在“Microsoft”和“gcc”解决方案之间进行选择的宏并不容易。编辑:似乎更新的 gcc 版本确实支持此选项 - 但并非所有编译器都支持。

于 2013-04-19T16:21:53.147 回答
2

根据您对另一个答案的评论,您的代码旨在与平台无关,并且明确指定了文件格式的字节序。在这种情况下,直接读入打包struct文件会失去很多清晰度,因为它需要一个读取后的字节序清理步骤,否则会导致在字节序与文件格式不同的架构上出现不正确的数据。

假设您总是知道字节数(可能来自文件中的结构类型指示符),我建议使用工厂模式,其中创建的对象的构造函数知道如何按属性从内存缓冲区属性中提取字节(如果文件是足够小,您可以将整个内容读入缓冲区,然后执行循环/工厂创建/反序列化到对象通过构造函数。这样您可以控制字节序并允许编译器所需的结构对齐。

于 2013-04-19T16:21:45.400 回答
1

如果您只使用打包并直接读入结构,代码将是最清晰的。这也可能是最快的阅读速度。不幸的是,它也可能成为错误的来源,特别是如果将来结构的布局发生变化。

元素的对齐可能是一个问题,也可能不是,这取决于许多因素。如果元素按大小排序,最大的在前,对齐不太可能成为问题。如果源通过直接写入整个结构来生成字节流,那么它也可能已针对该系统正确对齐,并且可能在您的端完美地工作。x86 架构可以很好地处理错位,最坏的情况只有轻微的减速;即使是通过一次加载整个高速缓存行的高速缓存结构来最小化,保证大部分字节已经在高速缓存中。其他架构可能根本无法处理错位,但如果发生这种情况,您很快就会知道。

如果您需要与源不同的字节顺序,您可以在结构的每个元素上调用一个函数来单独修复它们。到那时,直接读取的简单性和清晰度就会降低,而使用另一种方法可能会更好。

于 2013-04-19T16:13:12.937 回答