如何在C语言中高效读取大型实部/复数文本文件?
优化GB级实/复数文本文件的C语言读取性能
问题描述
现有代码通过fscanf逐元素读取实部或复数格式的大型文本文件,小文件场景下逻辑正常,但处理GB级文件时,因fscanf的频繁函数调用和通用格式解析开销,读取速度远低于二进制文件,需优化读取性能。
原代码
int io_read_array_real(char ordering, DTYPE *array, int m, int n, FILE *ifile) { int i, j, match; DTYPE elem; for (i = 0; i < m; i++) { for (j = 0; j < n; j++) { match = fscanf(ifile, "%e", &elem); if (match == 0) { printf("An error occurred while parsing the file!\n"); return (-1); } if (ordering == 'C') *(array + RTC(i, j, m)) = elem; else if (ordering == 'R') *(array + i * n + j) = elem; else return (-1); } } return (0); } int io_read_array_complex(char ordering, CDTYPE *array, int m, int n, FILE *ifile) { int i, j, info; DTYPE zreal, zimag; for (i = 0; i < m; i++) { for (j = 0; j < n; j++) { info = fscanf(ifile, " (%e%ej) ", &zreal, &zimag); if (info != 2) { fprintf(stderr, "Input file in wrong format at (%d,%d) info = %d!\n" "strerror: %s\n", i, j, info, strerror(errno)); return (-1); } if (ordering == 'C') *(array + RTC(i, j, m)) = zreal + I * zimag; else if (ordering == 'R') *(array + i * n + j) = zreal + I * zimag; else return (-1); } } return (0); }
文件格式
实数格式:
1.233e-3 2.231e-1 ... 2.335e-4 8.241e-2 ... ...复数格式:
(1.233e-3+3.239e-4j) (1.233e-3+3.239e-4j) ... (7.684e-2+8.269e-5j) (1.233e-3+3.239e-4j) ... ...
优化方案
1. 增大文件缓冲区
fscanf默认缓冲区较小,频繁磁盘IO是核心瓶颈之一。通过setvbuf设置大尺寸全缓冲区,减少IO交互次数:
// 打开文件后立即设置,缓冲区建议设为1MB(可根据内存调整) char *buffer = malloc(1024 * 1024); if (!buffer) { perror("malloc failed"); return -1; } setvbuf(ifile, buffer, _IOFBF, 1024 * 1024); // 注意:文件关闭前不要释放缓冲区
2. 批量读取+手动解析
逐元素调用fscanf的函数调用和通用格式解析开销极大,改为一次性读取大块数据到内存,再针对固定格式手动解析,能大幅降低开销。
实数版本优化代码
#include <string.h> #include <ctype.h> #include <stdlib.h> int io_read_array_real_fast(char ordering, DTYPE *array, int m, int n, FILE *ifile) { const size_t buf_size = 1024 * 1024; char *buffer = malloc(buf_size); if (!buffer) { perror("malloc failed"); return -1; } setvbuf(ifile, buffer, _IOFBF, buf_size); size_t bytes_read; char *ptr, *endptr; int count = 0; const int total_elems = m * n; while ((bytes_read = fread(buffer, 1, buf_size, ifile)) > 0) { ptr = buffer; while (ptr < buffer + bytes_read && count < total_elems) { // 跳过空白字符 while (ptr < buffer + bytes_read && isspace((unsigned char)*ptr)) ptr++; if (ptr >= buffer + bytes_read) break; // 解析浮点数 DTYPE elem = strtod(ptr, &endptr); if (endptr == ptr) { fprintf(stderr, "Failed to parse real element at position %d\n", count); free(buffer); return -1; } ptr = endptr; // 写入数组 if (ordering == 'C') { int i = count / n; int j = count % n; array[RTC(i, j, m)] = elem; } else if (ordering == 'R') { array[count] = elem; } else { free(buffer); return -1; } count++; } } free(buffer); if (count != total_elems) { fprintf(stderr, "Expected %d elements, only read %d\n", total_elems, count); return -1; } return 0; }
复数版本优化代码
针对固定的(x+yj)格式,直接定位实部、虚部的位置解析:
#include <string.h> #include <ctype.h> #include <stdlib.h> int io_read_array_complex_fast(char ordering, CDTYPE *array, int m, int n, FILE *ifile) { const size_t buf_size = 1024 * 1024; char *buffer = malloc(buf_size); if (!buffer) { perror("malloc failed"); return -1; } setvbuf(ifile, buffer, _IOFBF, buf_size); size_t bytes_read; char *ptr, *endptr; int count = 0; const int total_elems = m * n; while ((bytes_read = fread(buffer, 1, buf_size, ifile)) > 0) { ptr = buffer; while (ptr < buffer + bytes_read && count < total_elems) { // 跳过空白和左括号 while (ptr < buffer + bytes_read && (isspace((unsigned char)*ptr) || *ptr == '(')) ptr++; if (ptr >= buffer + bytes_read) break; // 解析实部 DTYPE zreal = strtod(ptr, &endptr); if (endptr == ptr) { fprintf(stderr, "Failed to parse real part of element %d\n", count); free(buffer); return -1; } ptr = endptr; // 跳过正负号 while (ptr < buffer + bytes_read && (*ptr == '+' || *ptr == '-')) ptr++; if (ptr >= buffer + bytes_read) break; // 解析虚部 DTYPE zimag = strtod(ptr, &endptr); if (endptr == ptr || (endptr < buffer + bytes_read && *endptr != 'j')) { fprintf(stderr, "Failed to parse imaginary part of element %d\n", count); free(buffer); return -1; } ptr = endptr + 1; // 跳过'j' // 跳过右括号 while (ptr < buffer + bytes_read && *ptr == ')') ptr++; // 写入数组 CDTYPE val = zreal + I * zimag; if (ordering == 'C') { int i = count / n; int j = count % n; array[RTC(i, j, m)] = val; } else if (ordering == 'R') { array[count] = val; } else { free(buffer); return -1; } count++; } } free(buffer); if (count != total_elems) { fprintf(stderr, "Expected %d elements, only read %d\n", total_elems, count); return -1; } return 0; }
3. 额外优化建议
- 内存对齐:使用
posix_memalign或aligned_alloc分配目标数组,确保内存对齐,提升CPU写入效率。 - 并行解析:对于超大型文件,可将文件分块读取后用多线程并行解析,注意保证数据写入顺序正确。
- 定制化解析:如果文件格式完全固定,可编写更精简的浮点数解析函数替代
strtod,进一步降低解析开销(比如直接处理指数、小数点等固定格式)。
内容的提问来源于stack exchange,提问作者adch99
相关产品推荐
相关产品推荐

