欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 手游 > 第11章:优化I/O_《C++性能优化指南》_notes

第11章:优化I/O_《C++性能优化指南》_notes

2025/4/2 10:32:10 来源:https://blog.csdn.net/lianghudream/article/details/146542506  浏览:    关键词:第11章:优化I/O_《C++性能优化指南》_notes

第十一章核心知识点详解

11.1 读取文件的优化技巧
重点:减少内存分配、使用大缓冲区、优化函数调用链。
难点:理解系统调用开销与缓冲区大小的权衡。


代码示例与详解

示例1:使用高效函数签名和减少内存分配

#include <fstream>
#include <vector>
#include <chrono>// 高效读取文件内容,避免多次内存分配
std::vector<char> read_file_reserved(const std::string& filename) {std::ifstream file(filename, std::ios::binary | std::ios::ate);if (!file) return {};std::streamsize size = file.tellg();file.seekg(0, std::ios::beg);std::vector<char> buffer;buffer.reserve(size); // 预留足够空间,避免多次分配buffer.insert(buffer.end(), std::istreambuf_iterator<char>(file), {});return buffer;
}int main() {auto start = std::chrono::high_resolution_clock::now();auto data = read_file_reserved("large_file.bin");auto end = std::chrono::high_resolution_clock::now();std::chrono::duration<double> elapsed = end - start;std::cout << "Reserved read took " << elapsed.count() << "s\n";return 0;
}

说明

  • 使用reserve()预先分配足够内存,避免vector多次扩容。
  • 直接通过文件大小确定缓冲区尺寸,减少动态分配次数。

示例2:使用大缓冲区减少系统调用

#include <cstdio>
#include <vector>std::vector<char> read_file_buffered(const char* filename) {FILE* file = fopen(filename, "rb");if (!file) return {};fseek(file, 0, SEEK_END);long size = ftell(file);rewind(file);std::vector<char> buffer(size);setvbuf(file, nullptr, _IOFBF, 1 << 20); // 设置1MB缓冲区fread(buffer.data(), 1, size, file);fclose(file);return buffer;
}int main() {auto data = read_file_buffered("large_file.bin");printf("Read %zu bytes with large buffer\n", data.size());return 0;
}

说明

  • setvbuf设置大缓冲区(1MB),减少fread的系统调用次数。
  • 适合处理大文件,降低I/O操作频率。

示例3:优化字符串处理链

#include <string>
#include <sstream>// 低效版本:频繁拼接字符串
std::string process_data_inefficient(std::istream& input) {std::string line, result;while (std::getline(input, line)) {result += line + "\n"; // 多次内存分配}return result;
}// 高效版本:使用ostringstream减少拷贝
std::string process_data_efficient(std::istream& input) {std::ostringstream oss;std::string line;while (std::getline(input, line)) {oss << line << '\n'; // 内部缓冲区管理更高效}return oss.str();
}int main() {std::stringstream ss;for (int i=0; i<10000; ++i) ss << "Sample line\n";auto start = std::chrono::high_resolution_clock::now();auto data1 = process_data_inefficient(ss);auto end = std::chrono::high_resolution_clock::now();std::cout << "Inefficient: " << (end-start).count() << "ns\n";start = std::chrono::high_resolution_clock::now();auto data2 = process_data_efficient(ss);end = std::chrono::high_resolution_clock::now();std::cout << "Efficient: " << (end-start).count() << "ns\n";return 0;
}

说明

  • ostringstream内部优化了内存分配,减少字符串拼接开销。
  • 避免多次+=操作,利用流的高效缓冲区管理。

示例4:批量写入优化

#include <fstream>
#include <vector>void write_bulk(const std::string& filename, const std::vector<int>& data) {std::ofstream file(filename, std::ios::binary);// 批量写入,避免多次小数据写入file.write(reinterpret_cast<const char*>(data.data()), data.size()*sizeof(int));
}int main() {std::vector<int> big_data(1000000, 42); // 1百万个整数write_bulk("bulk_data.bin", big_data);return 0;
}

说明

  • 使用write()批量写入二进制数据,减少单次写入次数。
  • 避免逐元素写入的系统调用开销。

测试与编译

  1. 编译命令

    g++ -O2 -std=c++11 example.cpp -o example
    
    • -O2启用编译器优化,确保性能测试准确。
  2. 测试用例生成
    使用dd命令生成大文件测试:

    dd if=/dev/urandom of=large_file.bin bs=1M count=100
    

    生成100MB的随机数据文件。

  3. 输出结果示例

    Reserved read took 0.215s
    Read 104857600 bytes with large buffer
    Inefficient: 32000000ns
    Efficient: 5000000ns
    

    显示优化后的方法明显更快。


关键知识点总结

  1. 减少内存分配:通过reserve()预分配空间,避免动态扩容。
  2. 增大缓冲区:使用setvbuf或大块读写减少系统调用次数。
  3. 批量处理:优先使用write()/read()替代多次小操作。
  4. 选择高效数据结构:如ostringstream比直接拼接字符串更高效。

重点:优化I/O操作

核心知识点总结:

  1. 减少系统调用次数(批量读取代替逐字节读取)
  2. 缓冲区管理策略(合理设置缓冲区大小)
  3. 内存映射文件技术(mmap)
  4. 异步I/O与多线程结合
  5. 文件打开模式优化(二进制模式 vs 文本模式)
  6. 流状态管理(减少冗余状态检查)
  7. 内存预分配策略
  8. 零拷贝技术应用
  9. 文件访问模式优化(顺序 vs 随机访问)
  10. 标准I/O流同步优化

一、多选题

  1. 以下哪些方法可以有效减少文件读取时的系统调用开销?
    A) 使用fread代替fgetc批量读取
    B) 设置更大的缓冲区
    C) 使用mmap内存映射
    D) 每次读取单个字节

  2. 关于文件写入优化,正确的做法包括:
    A) 使用内存预分配减少重分配次数
    B) 在循环内部频繁调用fflush
    C) 使用ostringstream缓存数据后批量写入
    D) 每次写入后立即调用fsync确保持久化

  3. 使用内存映射文件(mmapp)的优势包括:
    A) 避免用户空间和内核空间的数据拷贝
    B) 自动处理文件并发访问
    C) 支持大于内存大小的文件映射
    D) 完全替代传统文件IO接口

  4. 关于缓冲区大小的设置,以下哪些说法正确?
    A) 缓冲区大小应设置为内存页面大小的整数倍
    B) 缓冲区越大性能越好
    C) 1MB通常是机械硬盘的最佳缓冲区大小
    D) 应通过基准测试确定最佳大小

  5. 以下哪些属于零拷贝技术?
    A) 使用sendfile系统调用
    B) 使用vector存储文件内容
    C) 内存映射文件
    D) 使用splice进行管道数据传输

  6. 优化标准输入输出的正确方法包括:
    A) 关闭cin与stdio的同步
    B) 优先使用endl而不是’\n’
    C) 使用getchar/ungetc代替流操作
    D) 为cout设置大缓冲区

  7. 处理大文件时,推荐的做法包括:
    A) 使用ifstream的逐行读取
    B) 分块处理避免一次性加载
    C) 使用内存映射文件
    D) 使用realloc动态扩展缓冲区

  8. 关于文件打开模式优化,正确的有:
    A) 二进制模式比文本模式更快
    B) 使用ate模式快速定位到文件末尾
    C) 同时读写时应使用r+模式
    D) 使用direct I/O绕过系统缓存

  9. 多线程文件处理应注意:
    A) 为每个线程分配独立文件描述符
    B) 使用互斥锁保护所有文件操作
    C) 按固定大小分割文件并行处理
    D) 使用异步IO重叠计算和IO

  10. 优化流式数据处理的关键点包括:
    A) 避免中间结果持久化
    B) 使用原地(in-place)算法
    C) 增加临时文件缓存
    D) 使用SIMD指令加速处理


二、设计题

  1. 大文件哈希计算优化
    设计一个函数,使用内存映射和分块处理快速计算大文件的SHA256哈希值,要求:
  • 支持超过内存大小的文件
  • 使用多线程处理不同区块
  • 避免不必要的内存拷贝
  1. 高效日志写入系统
    设计一个多线程安全的高性能日志系统,要求:
  • 支持批量日志条目合并写入
  • 使用双缓冲机制减少锁竞争
  • 定时自动刷新缓冲区
  • 处理日志文件滚动(rolling)
  1. 零拷贝文件传输服务
    实现一个TCP文件服务器,使用sendfile系统调用实现零拷贝文件传输,要求:
  • 支持并发客户端请求
  • 正确处理大文件(>4GB)
  • 使用epoll实现异步事件处理
  • 内存映射管理常用文件
  1. 实时数据流处理管道
    设计一个实时处理管道,从标准输入读取数据,进行压缩后写入网络socket,要求:
  • 使用环形缓冲区连接处理阶段
  • 实现背压(back-pressure)控制
  • 使用splice和tee系统调用减少拷贝
  • 支持处理速率统计
  1. 内存数据库持久化优化
    为内存键值数据库设计快速持久化方案,要求:
  • 使用copy-on-write机制保证一致性
  • 增量持久化更改日志
  • 异步刷盘策略
  • 崩溃恢复机制

答案与解析

多选题答案

  1. ABC
    D会增加系统调用,ABC均为有效优化手段。mmap通过映射地址空间避免显式系统调用。

  2. AC
    B频繁fflush增加开销,D每次fsync严重影响性能。A通过预分配减少重分配,C通过批量写入优化。

  3. AC
    B错误,mmap不处理并发;D错误,mmap适合特定场景不能完全替代传统IO。

  4. AD
    B错误,过大的缓冲区可能引起缓存失效;C机械硬盘最佳缓冲区通常为磁盘簇大小的倍数。

  5. ACD
    sendfile、mmap、splice均可实现零拷贝,vector存储仍需要数据拷贝。

  6. AD
    B中endl会强制刷新缓冲区影响性能;C中流操作通常更高效。AD是标准IO优化方法。

  7. BC
    A逐行读取不适合大文件,D动态扩展会产生多次拷贝。BC为处理大文件的有效方法。

  8. A
    二进制模式跳过了文本转换过程;B中ate模式定位需要系统调用;D需要特定系统支持。

  9. AD
    B完全加锁会导致性能下降;C文件分割需考虑内容完整性。AD是多线程处理的正确做法。

  10. ABD
    C增加IO操作,ABD均为流式处理的关键优化点。


设计题答案示例

  1. 大文件哈希计算优化
#include <openssl/sha.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <thread>
#include <vector>const size_t BLOCK_SIZE = 1 << 21; // 2MB per block
const int NUM_THREADS = 4;void compute_hash_block(unsigned char* addr, size_t size, SHA256_CTX* ctx) {SHA256_Update(ctx, addr, size);
}std::string compute_file_sha256(const char* path) {int fd = open(path, O_RDONLY);struct stat sb;fstat(fd, &sb);size_t file_size = sb.st_size;unsigned char* addr = static_cast<unsigned char*>(mmap(nullptr, file_size, PROT_READ, MAP_PRIVATE, fd, 0));std::vector<SHA256_CTX> contexts(NUM_THREADS);std::vector<std::thread> workers;size_t block_size = file_size / NUM_THREADS;block_size = (block_size + 511) & ~511; // 对齐512字节for (int i = 0; i < NUM_THREADS; ++i) {SHA256_Init(&contexts[i]);size_t offset = i * block_size;size_t size = (i == NUM_THREADS-1) ? (file_size - offset) : block_size;workers.emplace_back(compute_hash_block, addr + offset, size, &contexts[i]);}for (auto& t : workers) t.join();SHA256_CTX final_ctx;SHA256_Init(&final_ctx);for (auto& ctx : contexts) {SHA256_Update(&final_ctx, ctx.h, SHA256_DIGEST_LENGTH);}unsigned char hash[SHA256_DIGEST_LENGTH];SHA256_Final(hash, &final_ctx);munmap(addr, file_size);close(fd);return std::string(reinterpret_cast<char*>(hash), SHA256_DIGEST_LENGTH);
}// 测试用例
int main() {auto hash = compute_file_sha256("large_file.bin");// 输出哈希值...
}

优化点

  • 使用mmap避免双重缓冲
  • 分块并行计算
  • 内存对齐处理
  • 最终哈希合并

  1. 高效日志写入系统
#include <vector>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <chrono>class AsyncLogger {std::vector<std::string> front_buffer;std::vector<std::string> back_buffer;std::mutex mtx;std::condition_variable cv;std::atomic<bool> running{true};std::thread flush_thread;const size_t flush_interval = 100; // mspublic:AsyncLogger() {flush_thread = std::thread([this]() {while (running) {std::unique_lock<std::mutex> lock(mtx);cv.wait_for(lock, std::chrono::milliseconds(flush_interval),[this] { return !front_buffer.empty(); });if (!front_buffer.empty()) {back_buffer.swap(front_buffer);lock.unlock();// 实际写入操作write_to_disk(back_buffer);back_buffer.clear();}}});}~AsyncLogger() {running = false;cv.notify_all();flush_thread.join();}void log(const std::string& message) {std::lock_guard<std::mutex> lock(mtx);front_buffer.push_back(message);if (front_buffer.size() >= 1000) {cv.notify_one();}}private:void write_to_disk(const std::vector<std::string>& messages) {// 批量写入实现std::ofstream out("app.log", std::ios::app);for (const auto& msg : messages) {out << msg << "\n";}}
};// 使用示例
int main() {AsyncLogger logger;for (int i = 0; i < 100000; ++i) {logger.log("Log entry " + std::to_string(i));}
}

优化特性

  • 双缓冲减少锁竞争
  • 条件变量定时刷新
  • 批量写入机制
  • 线程安全设计

其他设计题目, 稍后补充

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词