You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言下高效保存百万级边图至TXT文件的最优方案咨询

优化百万级图数据写入TXT的高效方案

针对你处理百万级边图写入TXT的性能优化需求,我有几个实用的方向可以帮你进一步提速,这些都是工业级场景常用的高效技巧:

1. 优化数字转字符串的核心逻辑

你当前手动实现的数字转字符串和位数计算逻辑还有优化空间,尤其是循环和分支判断的开销在百万次迭代下会被放大。这里有两个更高效的思路:

用编译器内置函数快速计算位数

替换你的getNumberOfDigitsBig函数,利用GCC/Clang的内置函数__builtin_clz(计算无符号整数的前导零个数)来快速推导数字位数,避免嵌套if的分支开销:

static inline int get_digit_count(int num) {
    if (num == 0) return 1;
    // 处理负数,转成无符号避免溢出
    unsigned int n = num < 0 ? (unsigned int)-num : (unsigned int)num;
    // 利用前导零个数计算位数,再补全高位判断
    return 32 - __builtin_clz(n) + 
           (n >= 10) + (n >= 100) + (n >= 1000) + (n >= 10000) + 
           (n >= 100000) + (n >= 1000000) + (n >= 10000000) + 
           (n >= 100000000) + (n >= 1000000000);
}

更高效的数字转字符串实现

你之前“先算位数再从高位写入”的思路是对的,可以进一步优化为一次性完成转换,同时处理INT_MIN的边界情况(避免取绝对值时溢出):

// 将int转换为字符串,返回写入的字符数
static inline int int_to_str(int num, char *buf) {
    char *start = buf;
    unsigned int n;

    if (num < 0) {
        *buf++ = '-';
        // 处理INT_MIN:-2147483648转成无符号避免溢出
        n = (unsigned int)(-(num + 1)) + 1;
    } else {
        n = (unsigned int)num;
    }

    // 先计算位数
    int digits = get_digit_count(num < 0 ? (int)n : num);
    // 从高位开始写入
    char *pos = buf + digits - (num < 0 ? 0 : 1);
    do {
        *pos-- = '0' + (n % 10);
        n /= 10;
    } while (n != 0);

    return buf + digits - start;
}

2. 优化缓冲区与文件IO策略

你的大缓冲区一次性写入思路没问题,但可以调整为分块缓冲区+预设置文件缓存,进一步减少IO开销:

匹配磁盘IO块的缓冲区大小

不要一次性分配GB级的内存,而是用64KB或128KB的固定大小缓冲区(和磁盘IO块大小匹配),边填充边写入,避免内存压力同时利用操作系统的缓存优化:

#define CHUNK_SIZE (128 * 1024) // 128KB,适配多数磁盘的IO块大小

禁用stdio默认缓存,手动设置大缓存

默认的stdio缓冲区很小,用setvbuf设置更大的文件缓存,减少用户态到内核态的切换次数:

FILE *f = fopen(filename, "wb"); // 用二进制模式,避免Windows下换行符转换开销
if (!f) { /* 错误处理 */ }

char file_cache[CHUNK_SIZE];
setvbuf(f, file_cache, _IOFBF, CHUNK_SIZE); // 设置全缓冲模式

分块写入,避免单次IO过大

在循环中填充缓冲区,当缓冲区快满时立即写入文件,最后再写入剩余数据:

char *buf = malloc(CHUNK_SIZE);
if (!buf) { /* 错误处理 */ }
size_t buf_pos = 0;
const size_t reserve = 100; // 预留空间避免溢出

const node_t *nodes = dij->nodes;
const int num_nodes = dij->num_nodes;

// 写入第一条记录
buf_pos += int_to_str(0, buf + buf_pos);
buf[buf_pos++] = ' ';
buf_pos += int_to_str(0, buf + buf_pos);
buf[buf_pos++] = ' ';
buf[buf_pos++] = '-';
buf[buf_pos++] = '1';
buf[buf_pos++] = '\n';

for (int i = 1; i < num_nodes; ++i) {
    const node_t *node = &nodes[i];

    // 写入i
    buf_pos += int_to_str(i, buf + buf_pos);
    buf[buf_pos++] = ' ';

    // 写入cost
    if (node->cost != -1) {
        buf_pos += int_to_str(node->cost, buf + buf_pos);
    } else {
        buf[buf_pos++] = '-';
        buf[buf_pos++] = '1';
    }
    buf[buf_pos++] = ' ';

    // 写入parent
    if (node->parent != -1) {
        buf_pos += int_to_str(node->parent, buf + buf_pos);
    } else {
        buf[buf_pos++] = '-';
        buf[buf_pos++] = '1';
    }
    buf[buf_pos++] = '\n';

    // 缓冲区快满时写入文件
    if (buf_pos >= CHUNK_SIZE - reserve) {
        fwrite(buf, 1, buf_pos, f);
        buf_pos = 0;
    }
}

// 写入剩余数据
if (buf_pos > 0) {
    fwrite(buf, 1, buf_pos, f);
}

fclose(f);
free(buf);

3. 进阶优化:内存映射文件(mmap)

如果你的系统支持mmap,可以把文件直接映射到内存空间,直接往内存地址写入数据,相当于把IO调度交给操作系统,性能可能比fwrite更优:

// 示例:用mmap写入
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) { /* 错误处理 */ }

// 预计算文件总大小(可以提前估算或遍历一次计算)
off_t total_size = num_nodes * (11 + 1 + 11 + 1 + 11 + 1); // 每个字段最多11字符+分隔符+换行
ftruncate(fd, total_size);

char *mmap_buf = mmap(NULL, total_size, PROT_WRITE, MAP_SHARED, fd, 0);
if (mmap_buf == MAP_FAILED) { /* 错误处理 */ }

// 直接往mmap_buf写入数据,逻辑和之前的缓冲区写入一致
// ...

munmap(mmap_buf, total_size);
close(fd);

4. 编译器优化不可少

别忘了开启编译器的最高级优化(比如-O3),编译器会自动帮你做循环展开、函数内联、指针优化等,这对性能提升非常显著,甚至可能比手动优化的效果还大。


内容的提问来源于stack exchange,提问作者Anton Kretov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:02:00