C++读取1.5GB二进制文件转文本过慢,求优化方案
优化1.5GB二进制文件解码写入速度的方案
1. 消除重复打开/关闭输出文件的开销
这是当前代码最大的性能瓶颈——每次循环都打开并关闭输出文件,文件IO的打开关闭操作开销极大。应该只在初始化时打开一次输出文件,全程保持打开状态直到所有数据处理完成。
修改示例:
// 循环外初始化输出文件,仅打开一次 std::ofstream fout("test2", std::ios::out | std::ios::trunc); if (!fout.is_open()) { throw std::runtime_error("无法打开输出文件"); } // 输出格式只需设置一次,无需循环重复配置 fout << std::fixed << std::left; while (/* 循环条件 */) { // ... 数据处理逻辑 ... // 直接写入,无需重复打开关闭 fout << std::setprecision(9) << std::setw(19) << timestamp << std::setprecision(10) << std::setw(15) << x << std::setprecision(10) << std::setw(15) << y << std::setw(16) << z << std::setw(10) << inten << '\n'; // 用'\n'替代endl,避免强制刷新缓冲区 } fout.close(); // 所有数据处理完成后统一关闭
注意:std::endl会强制刷新输出缓冲区,频繁刷新会大幅拖慢IO速度,改用'\n'可以让系统自动管理缓冲区刷新时机。
2. 简化读取逻辑,消除冗余内存拷贝
原代码的readBytes函数存在多余的内存分配和拷贝操作,且通过临时数组做类型转换的方式效率极低。可以直接读取数据到目标类型变量中,彻底消除中间载体的开销。
替换readBytes函数为直接读取:
template<class T> bool readValue(std::ifstream& input, T& value) { return input.read(reinterpret_cast<char*>(&value), sizeof(T)); } // 直接定义目标类型变量,跳过vector和临时数组 double timestamp; float x, y, z; uint16_t inten; uint8_t clss, Retn_scn; // 读取时直接写入变量 if (!readValue(file, timestamp) || !readValue(file, x) || !readValue(file, y) || !readValue(file, z) || !readValue(file, inten) || !readValue(file, clss) || !readValue(file, Retn_scn)) { // 读取失败(如文件结束),退出循环 break; }
3. 增大IO缓冲区大小
默认的ifstream/ofstream缓冲区很小,会导致频繁的磁盘IO交互。手动设置更大的缓冲区可以减少IO次数,显著提升速度。
示例:
constexpr size_t BUFFER_SIZE = 1024 * 1024; // 1MB缓冲区,可根据内存调整为2MB/4MB // 给输入文件设置大缓冲区 std::ifstream file("lidar_Mission.dat", std::ios::binary); char* in_buf = new char[BUFFER_SIZE]; file.rdbuf()->pubsetbuf(in_buf, BUFFER_SIZE); // 给输出文件设置大缓冲区 char* out_buf = new char[BUFFER_SIZE]; fout.rdbuf()->pubsetbuf(out_buf, BUFFER_SIZE); // 最后记得释放缓冲区 delete[] in_buf; delete[] out_buf;
注意:缓冲区设置必须在文件打开后、读写操作前完成。
4. 修复循环条件,避免无效执行
原代码的while (file)结合file.good()的判断逻辑存在问题,可能在文件结束后多执行一次无效循环。正确的做法是在读取数据后判断是否成功:
修改后的循环结构:
file.seekg(12, std::ios::beg); // 跳过文件头部 double timestamp; float x, y, z; uint16_t inten; uint8_t clss, Retn_scn; while (true) { // 尝试读取一组完整数据 if (!readValue(file, timestamp) || !readValue(file, x) || !readValue(file, y) || !readValue(file, z) || !readValue(file, inten) || !readValue(file, clss) || !readValue(file, Retn_scn)) { break; } // 写入处理后的数据 fout << std::setprecision(9) << std::setw(19) << timestamp << std::setprecision(10) << std::setw(15) << x << std::setprecision(10) << std::setw(15) << y << std::setw(16) << z << std::setw(10) << inten << '\n'; }
5. 批量处理数据(进阶优化)
如果内存允许,可以一次性读取多组数据到内存缓冲区,批量解码后再批量写入,进一步减少IO交互次数:
示例思路:
// 定义数据结构体,匹配二进制文件的格式 struct LidarData { double timestamp; float x, y, z; uint16_t inten; uint8_t clss; uint8_t Retn_scn; }; constexpr size_t BATCH_SIZE = 1000; // 每次读取1000组数据,可根据内存调整 std::vector<LidarData> batch(BATCH_SIZE); while (true) { // 批量读取数据到缓冲区 file.read(reinterpret_cast<char*>(batch.data()), sizeof(LidarData) * BATCH_SIZE); std::streamsize read_count = file.gcount() / sizeof(LidarData); if (read_count == 0) break; // 批量写入处理后的数据 for (size_t i = 0; i < read_count; ++i) { const auto& data = batch[i]; fout << std::setprecision(9) << std::setw(19) << data.timestamp << std::setprecision(10) << std::setw(15) << data.x << std::setprecision(10) << std::setw(15) << data.y << std::setw(16) << data.z << std::setw(10) << data.inten << '\n'; } }
6. 注意字节序问题(避免潜在错误)
原代码的类型转换未考虑字节序差异,如果二进制文件的字节序与当前系统不一致,会导致数据错误。可以用C++20的std::endian或手动转换字节序来保证正确性,这一步不会影响性能。
内容的提问来源于stack exchange,提问作者Guled
相关产品推荐
相关产品推荐

