c - 什么是在 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
为他们尊重的元数据值?
解决方案
ID:
使用“内存映射文件”来避免加载原始数据(例如
mmap()
在 POSIX 系统中)。请注意,这不是可移植的“普通 C”,但几乎每个操作系统都支持执行此操作的方法。确保文件格式规范要求这些值与文件中的 4 字节边界对齐,并且(如果您确实需要支持有符号整数)这些值以“2 的补码”格式(而不是“符号和幅度”或其他任何东西)
尽可能检查文件是否符合规范(不仅是对齐要求,还包括“数据不能在标题中间开始”、“数据开始 + 条目 * entry_size 不能超过文件大小”等内容, “版本无法识别”等)。
对于 little-endian 机器有不同的代码(例如,可以在编译时使用 选择使用哪个代码
#ifdef
),您可以将内存映射文件的数据转换为int32_t
(或uint32_t
)。请注意,您显示的代码(例如(buffer[ptr + 3] << 24) + (buffer[ptr + 2] << 16) + (buffer[ptr + 1] << 8) + buffer[ptr])
,负数被破坏(即使在“2 的恭维”机器上);因此替代代码(对于“非小端”情况)将比您的更复杂(并且更慢) . 当然,如果您不需要支持负数,则不应使用任何有符号整数类型(例如int
),坦率地说,int
无论如何您都不应该将“可能是 16 位”用于 32 位值。确定您应该使用多少线程(可能是命令行参数;可能是通过询问操作系统计算机实际有多少 CPU)。启动线程并告诉它们它们是哪个“线程号”(现有线程是 0 号,第一个产生的线程是 1 号等)。
让线程根据它们的“线程号”、全局“总线程”、全局“总条目”和全局“第一个条目的偏移量”计算它们的开始和结束偏移量(在内存映射文件中)。这主要是对舍入特别注意的除法。请注意(为避免全局变量),您可以将包含详细信息的结构传递给每个线程。因为线程只读取它,所以不需要对此数据采取任何保护措施(例如锁、临界区)。
让每个线程并行解析其数据部分;然后等待它们全部完成(例如,如果您不想保留线程供以后使用,也许“线程号 0”会执行“pthread_join()”)。
您可能还需要检查所有值(由所有线程解析)是否在允许的范围内(以符合文件格式规范);并在不处理的情况下进行某种错误处理(例如,当文件损坏或被恶意篡改时)。这可以像(全局的,原子递增的)“到目前为止发现的可疑值的数量”计数器一样简单;这可以让您在解析所有值后显示“找到 N 个狡猾的值”错误消息。
注1:如果您不想使用内存映射文件(或不能);您可以拥有一个“文件读取器线程”和多个“文件解析器线程”。这需要更多的同步(它会演变为具有流控制的 FIFO 队列 - 例如,提供者线程执行某种“队列满时 {wait}”,而消费者线程执行某种“队列空时 {wait}”)。与使用内存映射文件相比,这种额外的同步会增加开销并使其变慢(除了更复杂之外)。
注意 2:如果文件的数据没有被操作系统的文件数据缓存缓存,那么无论您做什么,您都可能会遇到文件 IO 的瓶颈,并且在这种情况下使用多线程可能无助于提高性能。
推荐阅读
- sql - 如何使用 sys.anydataset sql oracle 19c
- r - 创建一个显示当前日期和下一个日期的变量
- python - 如何使用 pandas cut 绘制直方图
- reactjs - 来自反应应用程序的发布请求不起作用
- javascript - 如何从组件将函数作为值分配给Vuex状态中的对象的属性?
- html - npm run build 和 watch 非常慢
- mysql - 按日期分组并在sql中以日期时间显示
- javascript - 单击提交按钮以进行引导模式和 parsley.js 验证后如何防止页面重新加载?
- r - 在 R 中使用 ggplot2 覆盖散点图
- wordpress - Hilight JS not working with wordpress REST API and nuxtjs