C++笔记之大块顺序写

C++笔记之大块顺序写

code review!

文章目录

  • C++笔记之大块顺序写
    • [1 顺序写](#1 顺序写)
      • [1.1 含义](#1.1 含义)
      • [1.2 举例](#1.2 举例)
      • [1.3 示例代码](#1.3 示例代码)
      • [1.4 要点](#1.4 要点)
    • [2 大块顺序写](#2 大块顺序写)
      • [2.1 含义](#2.1 含义)
      • [2.2 目的](#2.2 目的)
      • [2.3 举例](#2.3 举例)
      • [2.4 实践建议](#2.4 实践建议)
    • [3 小块写与大块写对比](#3 小块写与大块写对比)
      • [3.1 示例代码](#3.1 示例代码)
      • [3.2 说明](#3.2 说明)
    • [4 刷盘](#4 刷盘)
      • [4.1 含义](#4.1 含义)
      • [4.2 特点](#4.2 特点)
      • [4.3 flush 与 fsync 的区别](#4.3 flush 与 fsync 的区别)
      • [4.4 示例](#4.4 示例)
      • [4.5 刷盘策略](#4.5 刷盘策略)
    • [5 总结](#5 总结)

1 顺序写

1.1 含义

含义:将原本按随机地址(乱序、跳跃位置)写入文件的操作,改为按文件地址从小到大依次顺序写入,避免磁盘/文件指针频繁跳转,从而提高写入性能(尤其对机械磁盘效果明显,因为顺序写减少了寻道时间)。

1.2 举例

  • 随机写:先写第 100 字节,再写第 10 字节,再写第 500 字节。
  • 顺序写:先写第 0~99 字节,再写第 100~199 字节,依次递增。

1.3 示例代码

cpp 复制代码
#include <fstream>

int main() {
    // 随机写:每次都要 seekp 跳到不同位置,乱序,效率低
    {
        std::ofstream f("random.bin", std::ios::binary);
        f.seekp(300); f.write("C", 1);
        f.seekp(0);   f.write("A", 1);
        f.seekp(150); f.write("B", 1);
    }

    // 顺序写:定位一次(或不定位),写指针自动后移,效率高
    {
        std::ofstream f("sequential.bin", std::ios::binary);
        f.write("A", 1);
        f.write("B", 1);
        f.write("C", 1);
    }

    return 0;
}

1.4 要点

  • 顺序写只需一次定位,甚至不需要定位,后续写指针自动递增。
  • 随机写每次都要 seekp 跳转到不同位置,增加磁盘寻道开销。

2 大块顺序写

2.1 含义

含义:将数据攒够一定大小(大块,如几十KB~几MB)后一次性顺序写入,而不是频繁地写小块数据。

2.2 目的

  • 减少写入次数,包括系统调用次数和 IO 请求次数。
  • 充分利用磁盘的顺序写带宽,提高吞吐量。
  • 降低每次调用的固定开销,例如用户态与内核态切换、加锁和元数据更新。

2.3 举例

cpp 复制代码
// 小块写(低效):多次调用,每次只写一点
for (int i = 0; i < 10000; i++) {
    f.write(&data[i], 1);   // 每次写 1 字节
}

// 大块写(高效):攒够数据后一次性写入
f.write(buffer, buffer_size);  // 一次写几 MB

2.4 实践建议

  • 在内存中维护一个写缓冲区,大小可取 64KB 到数 MB,写满后再一次性写出。
  • 程序结束或需要保证数据可见时,把缓冲区中剩余的数据写出。
  • 可以用 rdbuf()->pubsetbuf() 为 ofstream 设置更大的缓冲区。这个调用要在打开文件前完成,行为依赖具体实现。

3 小块写与大块写对比

3.1 示例代码

cpp 复制代码
#include <fstream>
#include <chrono>
#include <iostream>
#include <vector>

int main() {
    const int N = 1000000; // 1M次
    char c = 'A';

    // 小块写:每次写1字节,共写N次
    {
        std::ofstream f("small.bin", std::ios::binary);
        auto start = std::chrono::steady_clock::now();
        for (int i = 0; i < N; i++) {
            f.write(&c, 1);   // 频繁小块写
        }
        auto end = std::chrono::steady_clock::now();
        std::cout << "小块写耗时: "
                  << std::chrono::duration<double>(end - start).count()
                  << " 秒\n";
    }

    // 大块写:攒满缓冲区后一次性写入
    {
        std::ofstream f("large.bin", std::ios::binary);
        std::vector<char> buffer(N, c); // 一次准备好所有数据
        auto start = std::chrono::steady_clock::now();
        f.write(buffer.data(), buffer.size()); // 一次性大块写
        auto end = std::chrono::steady_clock::now();
        std::cout << "大块写耗时: "
                  << std::chrono::duration<double>(end - start).count()
                  << " 秒\n";
    }

    return 0;
}

3.2 说明

  • 两者写入的数据量相同,都是 N 字节。小块写调用 N 次 write,大块写只调用 1 次。
  • 大块写通常明显更快。
  • std::ofstream 自带用户态缓冲区,所以小块写的耗时主要是函数调用开销。如果直接调用系统调用 write(),或者每次写完都 flush(),两者的差距会大得多。

4 刷盘

4.1 含义

刷盘是把数据从内存缓冲区真正写入物理磁盘的过程。数据从程序到磁盘要经过两层缓存:

  1. 用户态缓冲区,例如 ofstream 和 FILE* 的缓冲区。
  2. 操作系统页缓存(page cache)。

4.2 特点

  • 平时调用 write() 时,数据通常只到达内存缓存,没有真正落盘。这样速度快,但断电会丢数据。
  • 刷盘通过 flush()、fsync() 等操作强制写入,保证数据持久化,但会增加 IO 等待,降低性能。

4.3 flush 与 fsync 的区别

  • flush()(或 fflush)只把用户态缓冲区的数据交给操作系统,不保证数据已经落到磁盘。
  • fsync(fd)(Linux/POSIX)会要求操作系统把该文件的页缓存写到存储设备,这才是真正的持久化。Windows 对应的是 FlushFileBuffers。

4.4 示例

cpp 复制代码
#include <cstdio>
#include <unistd.h>   // fsync, fileno

int main() {
    FILE* fp = std::fopen("data.bin", "wb");
    const char data[] = "hello";

    std::fwrite(data, 1, sizeof(data), fp); // 写入用户态缓冲区
    std::fflush(fp);                        // 刷到操作系统页缓存
    fsync(fileno(fp));                      // 强制页缓存落盘

    std::fclose(fp);
    return 0;
}

使用 ofstream 时,f.flush() 只完成第一步。需要真正落盘时,要改用文件描述符或 FILE*,再调用 fsync。

4.5 刷盘策略

  • 不要每写一小块就刷盘,这会抵消大块顺序写带来的收益。
  • 常见做法是攒够一个大块后写入,再按数据重要性决定刷盘时机。可以每写若干 MB 刷一次,也可以按时间间隔刷,关键数据则写完立即刷。

5 总结

  • 顺序写减少磁盘寻道和随机 IO。
  • 大块写减少系统调用和 IO 次数,提升吞吐量。
  • 刷盘保证数据持久化,但代价是性能,要在可靠性和效率之间取舍。
  • 高性能写入的常用组合是:内存中攒数据,大块顺序写入,再按策略批量刷盘。
相关推荐
打工仔折腾 AI1 小时前
从 Demo 到生产级 Agent:8 个关键设计机制与 Python 实现拆解
java·jvm·人工智能·后端·python·langchain·ai agent 实战
无名猿2 小时前
对象构造与析构顺序全解:声明顺序、继承链与逆序析构
c++·内存管理·调试技巧·现代c++
殷色玫瑰2 小时前
C/C++ 内存管理详解:从内存分布到 new/delete 底层原理
java·linux·c语言·c++
C++ 老炮儿的技术栈2 小时前
不一样的数值交换
数据结构·c++·人工智能·算法·c·csdn开发云
C++ 老炮儿的技术栈2 小时前
main函数之后的调用
c语言·c++·人工智能·qt·mfc·c
山岚的运维笔记2 小时前
ComfyUI NVIDIA安装教程:官方便携版下载+run_nvidia_gpu.bat启动,8G显存Windows实操
运维·服务器·windows·笔记·prompt·aigc·comfyui
知识分享小能手3 小时前
C++ 学习教程,从入门到精通,C++ 友元、异常和其他特性 — 详细知识点(15)
开发语言·c++·学习
ebiobiz3 小时前
极海 APM32 使用 Nimmake 编译指南
c++·python·单片机·嵌入式硬件·mcu
青山木3 小时前
Hot 100 --- 编辑距离
java·数据结构·算法·leetcode·动态规划