在 C 语言中解析二进制文件的高性能和干净方法是什么?



我正在解析我知道格式的自定义二进制文件结构。

一般的想法是将每个文件分解为连续字节块,我想并行分离和解码。

我正在寻找一种可读的、高性能的替代方案来替代decode_block()

这是我目前正在使用的内容:

#include <stdio.h>
int decode_block(uint8_t buffer[]);
int main(){
FILE *ptr;
ptr = fopen("example_file.bin", "rb");
if (!ptr){
printf("can't open.n");
return 1;
}
int block1_size = 2404;
uint8_t block1_buffer[block1_size];
fread(block1_buffer, sizeof(char), block1_size, ptr);
int block2_size = 3422;
uint8_t block2_buffer[block2_size];
fread(block2_buffer, sizeof(char), block2_size, ptr);
fclose(ptr);
//Do these in parallel
decode_block(block1_buffer);
decode_block(block2_buffer);
return 0;
}
int decode_block(uint8_t buffer[]){
unsigned int size_of_block = (buffer[3] << 24) + (buffer[2] << 16) + (buffer[1] << 8) + buffer[0];
unsigned int format_version = buffer[4];
unsigned int number_of_values = (buffer[8] << 24) + (buffer[7] << 16) + (buffer[6] << 8) + buffer[5];
unsigned int first_value = (buffer[10] << 8) + buffer[9];
// On and on and on
int ptr = first_value
int values[number_of_values];
for(int i = 0; i < number_of_values; i++){
values[i] = (buffer[ptr + 3] << 24) + (buffer[ptr + 2] << 16) + (buffer[ptr + 1] << 8) + buffer[ptr];
ptr += 4
}
// On and on and on
return 0
}

将整个文件读取到一个字节数组中,然后逐个字节地解释数组,感觉有点多余。此外,它使代码非常庞大。

但是由于我需要并行操作文件的多个部分,因此我想不出另一种方法来执行此操作。此外,是否有更简单或更快的方法将buffer中的早期字节转换为其受重视的元数据值?

我会:

  • 使用"内存映射文件"以避免加载原始数据(例如mmap()在 POSIX 系统中(。请注意,这不是可移植的"普通C",但几乎每个操作系统都支持一种方法来做到这一点。

  • 确保文件格式规范要求值与文件中的 4 字节边界对齐,并且(如果您确实需要支持有符号整数(值以"2's compliment"格式存储(而不是"符号和幅度"或其他任何内容(

  • 检查文件是否尽可能符合规范(不仅仅是对齐要求,还包括"数据不能从标头中间开始"、"数据开始 + 条目 * entry_size不能超过文件大小"、"版本无法识别"等内容(。

  • 为小端计算机提供不同的代码(例如,在编译时可以使用#ifdef选择使用哪个代码(,您可以在其中将内存映射文件的数据转换为int32_t(或uint32_t(。请注意,您显示的代码(例如 负数的(buffer[ptr + 3] << 24) + (buffer[ptr + 2] << 16) + (buffer[ptr + 1] << 8) + buffer[ptr])被打破(即使在"2的赞美"机器上(;因此,替代代码(对于"非小端序"情况(将比您的更复杂(且更慢(。当然,如果你不需要支持负数,你不应该使用任何有符号整数类型(例如int(,坦率地说,无论如何,您都不应该对 32 位值使用"可能 16 位"int

  • 确定您应该使用多少个线程(可能是命令行参数;可能是询问操作系统计算机实际有多少个 CPU(。启动线程并告诉它们它们是哪个"线程编号"(其中现有线程是编号 0,第一个生成的线程是编号 1,等等(。

  • 让线程根据其"线程数"、全局"总
  • 线程数"、全局"总条目数"和全局"第一个条目的偏移量"计算其起始和结束偏移量(在内存映射文件中(。这主要只是除法,特别注意四舍五入。请注意,(为了避免全局变量(您可以将包含详细信息的结构传递给每个线程。此数据不需要任何保护措施(例如锁、关键部分(,因为线程只读取它。

  • 让每个线程并行解析其数据部分;然后等待它们全部完成(例如,如果您不想保留线程以供以后使用,也许"线程编号 0"会执行"pthread_join(("(。

您可能还需要检查所有值(由所有线程解析(是否在允许的范围内(以符合文件格式规范(;并且在它们没有(例如,当文件损坏或被恶意篡改时(进行某种错误处理。这可以像(全局,原子递增的("到目前为止找到的狡猾值数"计数器一样简单;这可能允许您在解析所有值后显示"找到 N 个狡猾的值"错误消息。

注意 1:如果您不想使用内存映射文件(或不能(;您可以有一个"文件读取器线程"和多个"文件解析器线程"。这需要更多的同步(它演变成具有流量控制的FIFO队列 - 例如,提供者线程执行某种"而队列已满{等待}",而使用者线程执行某种"而队列空{等待}"(。与使用内存映射文件相比,这种额外的同步会增加开销并使其变慢(除了更复杂(。

注意 2:如果操作系统的文件数据缓存未缓存文件的数据,则无论您做什么,都可能会受到文件 IO 的瓶颈,并且使用多个线程可能无助于在这种情况下的性能。

相关内容

最新更新