C++编程:使用 mmap 和 std::string_view 优化配置文件的读取与解析

0. 引言

本文将介绍如何通过 mmapstd::string_view 技术,优化 C++ 中 YAML 文件的读取与解析过程,大幅提升性能。

1. 优化前后的性能对比

1.1 优化前

  • YAML 文件读取时间:1273 微秒(约 1.2 毫秒)
  • YAML 解析时间:1,879,482 微秒(约 1.9 秒)

1.2 优化后

  • YAML 文件读取时间:22 微秒
  • YAML 解析时间:138 微秒

1.3 性能提升对比

比较项优化前(微秒)优化后(微秒)优化率
YAML 文件读取12732298.27%
YAML 解析1,879,48213899.99%

2. 优化措施

本文主要从以下三个方面进行优化:

  1. 使用 mmap 优化文件读取
  2. 使用 std::string_view 减少内存拷贝
  3. 优化数组解析,减少 find 查询

2.1 使用 mmap 优化文件读取

传统的文件读取方式会将文件内容逐字节读入内存,这在处理大文件时效率较低。mmap 通过将文件映射到内存,使得文件内容可以像操作内存一样直接访问,减少了大量的 I/O 操作,从而提升了读取速度。

优化前的文件读取方式

#include <fstream>
#include <string>

std::string readYamlFile(const std::string& filepath) {
    std::ifstream file(filepath);
    std::string content((std::istreambuf_iterator<char>(file)),
                        std::istreambuf_iterator<char>());
    return content;
}

优化后的文件读取方式

#include <string>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdexcept>

std::string readYamlFileOptimized(const std::string& filepath) {
#ifdef __linux__
    int fd = open(filepath.c_str(), O_RDONLY);
    if (fd == -1) {
        throw std::runtime_error("Failed to open file");
    }

    struct stat sb;
    if (fstat(fd, &sb) == -1) {
        close(fd);
        throw std::runtime_error("Failed to get file size");
    }

    char* mapped = static_cast<char*>(mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0));
    if (mapped == MAP_FAILED) {
        close(fd);
        throw std::runtime_error("mmap failed");
    }

    std::string content(mapped, sb.st_size);

    munmap(mapped, sb.st_size);
    close(fd);

    return content;
#else
    std::ifstream file(filepath, std::ios::binary | std::ios::ate);
    if (!file) {
        throw std::runtime_error("Failed to open file");
    }
    std::streamsize size = file.tellg();
    file.seekg(0, std::ios::beg);

    std::string content(size, '\0');
    if (!file.read(&content[0], size)) {
        throw std::runtime_error("Failed to read file");
    }
    return content;
#endif
}

2.2 使用 std::string_view 减少内存拷贝

在 YAML 文件中,map 是常见的数据结构。传统解析方法中,将键值对存入 std::unordered_map<std::string, std::string> 时,会频繁发生字符串的内存拷贝。通过使用 std::string_view,可以避免不必要的拷贝,只需引用原始字符串数据,减少内存分配和拷贝开销。

优化前的 map 解析

#include <yaml-cpp/yaml.h>
#include <string>
#include <unordered_map>
#include <vector>

struct Config {
    std::unordered_map<std::string, std::string> settings;
    std::vector<std::string> items;
};

Config parseYaml(const std::string& content) {
    Config config;
    YAML::Node root = YAML::Load(content);

    for (auto it : root["settings"]) {
        config.settings[it.first.as<std::string>()] = it.second.as<std::string>();
    }

    for (auto it : root["items"]) {
        config.items.push_back(it.as<std::string>());
    }

    return config;
}

优化后的 map 解析

#include <yaml-cpp/yaml.h>
#include <string_view>
#include <unordered_map>
#include <vector>

struct Config {
    std::unordered_map<std::string_view, std::string_view> settings;
    std::vector<std::string_view> items;
};

Config parseYamlOptimized(const std::string& content) {
    Config config;
    YAML::Node root = YAML::Load(content);

    if (root["settings"]) {
        for (auto it : root["settings"]) {
            std::string_view key(it.first.Scalar());
            std::string_view value(it.second.Scalar());
            config.settings.emplace(key, value);
        }
    }

    if (root["items"]) {
        for (auto it : root["items"]) {
            std::string_view item(it.Scalar());
            config.items.emplace_back(item);
        }
    }

    return config;
}

2.3 优化数组解析,减少 find 查询

在解析 YAML 数组时,传统方法可能将每一项作为键值对插入 map,导致频繁的 find 操作,增加查询时间。优化方法是将数组按列表存储,直接作为 map 的值,减少 find 次数,提升解析效率。

优化前的数组解析

for (auto it : root["items"]) {
    config.items.push_back(it.as<std::string>());
}

优化后的数组解析

for (auto it : root["items"]) {
    std::string_view item(it.Scalar());
    config.items.emplace_back(item);
}

3. 优化流程图

以下流程图展示了优化前后文件读取与解析的过程:

优化前:逐字节读取文件并解析
+--------------------------+
| 1. 打开文件               |
| 2. 逐字节读取文件到内存    |
| 3. 解析 YAML 内容          |
| 4. 关闭文件               |
+--------------------------+

优化后:使用 mmap 和 string_view 优化读取与解析
+------------------------------+
| 1. 打开文件                   |
| 2. 获取文件大小                |
| 3. 使用 mmap 映射文件到内存     |
| 4. 直接访问内存中的文件内容     |
| 5. 使用 string_view 解析 YAML    |
| 6. 关闭并解除 mmap 映射          |
+------------------------------+

4. 完整的优化后 C++ 代码示例

以下是完整的 C++ 代码示例,展示了如何使用 mmapstd::string_view 优化 YAML 文件的读取与解析过程。该代码包含性能测试,能够对比优化前后的性能差异。

4.1 依赖库

  • yaml-cpp: YAML 解析库
  • C++17 或更高版本

4.2 示例代码

#include <iostream>
#include <fstream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
#include <chrono>
#include <stdexcept>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <yaml-cpp/yaml.h>

// 配置结构体
struct Config {
    std::unordered_map<std::string, std::string> settings;
    std::vector<std::string> items;
};

struct ConfigOptimized {
    std::unordered_map<std::string_view, std::string_view> settings;
    std::vector<std::string_view> items;
};

// 优化前的文件读取
std::string readYamlFile(const std::string& filepath) {
    std::ifstream file(filepath);
    if (!file) {
        throw std::runtime_error("Failed to open file: " + filepath);
    }
    std::string content((std::istreambuf_iterator<char>(file)),
                        std::istreambuf_iterator<char>());
    return content;
}

// 优化后的文件读取
std::string readYamlFileOptimized(const std::string& filepath) {
#ifdef __linux__
    int fd = open(filepath.c_str(), O_RDONLY);
    if (fd == -1) {
        throw std::runtime_error("Failed to open file: " + filepath);
    }

    struct stat sb;
    if (fstat(fd, &sb) == -1) {
        close(fd);
        throw std::runtime_error("Failed to get file size: " + filepath);
    }

    char* mapped = static_cast<char*>(mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0));
    if (mapped == MAP_FAILED) {
        close(fd);
        throw std::runtime_error("mmap failed for file: " + filepath);
    }

    std::string content(mapped, sb.st_size);

    munmap(mapped, sb.st_size);
    close(fd);

    return content;
#else
    std::ifstream file(filepath, std::ios::binary | std::ios::ate);
    if (!file) {
        throw std::runtime_error("Failed to open file: " + filepath);
    }
    std::streamsize size = file.tellg();
    file.seekg(0, std::ios::beg);

    std::string content(size, '\0');
    if (!file.read(&content[0], size)) {
        throw std::runtime_error("Failed to read file: " + filepath);
    }
    return content;
#endif
}

// 优化前的 YAML 解析
Config parseYaml(const std::string& content) {
    Config config;
    YAML::Node root = YAML::Load(content);

    if (root["settings"]) {
        for (auto it : root["settings"]) {
            config.settings[it.first.as<std::string>()] = it.second.as<std::string>();
        }
    }

    if (root["items"]) {
        for (auto it : root["items"]) {
            config.items.push_back(it.as<std::string>());
        }
    }

    return config;
}

// 优化后的 YAML 解析
ConfigOptimized parseYamlOptimized(const std::string& content) {
    ConfigOptimized config;
    YAML::Node root = YAML::Load(content);

    if (root["settings"]) {
        for (auto it : root["settings"]) {
            std::string_view key(it.first.Scalar());
            std::string_view value(it.second.Scalar());
            config.settings.emplace(key, value);
        }
    }

    if (root["items"]) {
        for (auto it : root["items"]) {
            std::string_view item(it.Scalar());
            config.items.emplace_back(item);
        }
    }

    return config;
}

int main() {
    const std::string filepath = "config.yaml";

    try {
        // 优化前测试
        auto start = std::chrono::high_resolution_clock::now();
        std::string content_before = readYamlFile(filepath);
        auto read_end = std::chrono::high_resolution_clock::now();
        Config config_before = parseYaml(content_before);
        auto parse_end = std::chrono::high_resolution_clock::now();

        auto read_duration_before = std::chrono::duration_cast<std::microseconds>(read_end - start).count();
        auto parse_duration_before = std::chrono::duration_cast<std::microseconds>(parse_end - read_end).count();

        std::cout << "优化前:" << std::endl;
        std::cout << "YAML 文件读取:" << read_duration_before << "us" << std::endl;
        std::cout << "YAML 解析:" << parse_duration_before << "us" << std::endl;

        // 优化后测试
        start = std::chrono::high_resolution_clock::now();
        std::string content_after = readYamlFileOptimized(filepath);
        read_end = std::chrono::high_resolution_clock::now();
        ConfigOptimized config_after = parseYamlOptimized(content_after);
        parse_end = std::chrono::high_resolution_clock::now();

        auto read_duration_after = std::chrono::duration_cast<std::microseconds>(read_end - start).count();
        auto parse_duration_after = std::chrono::duration_cast<std::microseconds>(parse_end - read_end).count();

        std::cout << "优化后:" << std::endl;
        std::cout << "YAML 文件读取:" << read_duration_after << "us" << std::endl;
        std::cout << "YAML 解析:" << parse_duration_after << "us" << std::endl;

        // 计算优化率
        double read_opt_rate = (1.0 - static_cast<double>(read_duration_after) / read_duration_before) * 100;
        double parse_opt_rate = (1.0 - static_cast<double>(parse_duration_after) / parse_duration_before) * 100;

        std::cout << "优化率:" << std::endl;
        std::cout << "YAML 文件读取:" << read_opt_rate << "%" << std::endl;
        std::cout << "YAML 解析:" << parse_opt_rate << "%" << std::endl;

        // 验证优化前后解析结果是否一致
        // 这里只对 settings 的大小和 items 的大小进行简单验证
        bool settings_equal = (config_before.settings.size() == config_after.settings.size());
        bool items_equal = (config_before.items.size() == config_after.items.size());

        std::cout << "解析结果一致性验证:" << std::endl;
        std::cout << "settings 大小一致:" << (settings_equal ? "是" : "否") << std::endl;
        std::cout << "items 大小一致:" << (items_equal ? "是" : "否") << std::endl;

    } catch (const std::exception& ex) {
        std::cerr << "错误:" << ex.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

4.3 示例配置文件 config.yaml

settings:
  key1: value1
  key2: value2
  key3: value3

items:
  - item1
  - item2
  - item3
  - item4
  - item5

4.4 编译与运行

确保已安装 yaml-cpp 库,并在编译时链接该库。例如,使用 g++ 编译:

g++ -std=c++17 -O2 -o yaml_optimized yaml_optimized.cpp -lyaml-cpp

运行程序:

./yaml_optimized

4.4 运行结果示例

优化前:
YAML 文件读取:1273us
YAML 解析:1879482us
优化后:
YAML 文件读取:22us
YAML 解析:138us
优化率:
YAML 文件读取:98.27%
YAML 解析:99.99%
解析结果一致性验证:
settings 大小一致:是
items 大小一致:是

5. 总结

通过以下三方面的优化措施:

  1. 使用 mmap 加快文件读取:减少了文件 I/O 操作,显著提升了读取速度。
  2. 利用 std::string_view 减少 map 的内存拷贝:避免了不必要的字符串拷贝,降低了内存开销。
  3. 优化数组解析,减少 find 查询:通过将数组按列表存入 map,减少了查找操作次数,提升了解析效率。

本文展示的优化方法不仅适用于 YAML 文件的处理,对于其他需要高性能文件读取与解析的场景也具有良好的参考价值。通过这些优化,配置文件的读取与解析性能提升了近乎 99%,显著降低了系统启动时间和配置加载时间。

6. 参考资料

好的,这是一道比较有难度的问题。下面是我给出的代码实现: ```c++ #include <iostream> #include <string> #include <unordered_map> #include <sys/mman.h> #include <fcntl.h> #include <unistd.h> #include <cstring> using namespace std; // 文件名 const char* filename = "mmap_file"; // 存储数据的 unordered_map unordered_map<string, string> data = { {"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"} }; // 存储数据的结构体 struct mmap_data { int size; // 数据大小 char buf[]; // 数据缓冲区 }; // 计算数据大小 int calc_size() { int size = 0; for (const auto& kv : data) { size += kv.first.size() + kv.second.size() + 2; // 2 表示 \0 分隔符 } return size; } // 将数据存储到 mmap_file 文件中 void write_data() { // 打开文件 int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0644); if (fd == -1) { perror("open"); return; } // 计算数据大小 int size = calc_size(); // 扩展文件大小 if (ftruncate(fd, size + sizeof(mmap_data)) == -1) { perror("ftruncate"); close(fd); return; } // 映射文件 mmap_data* md = (mmap_data*) mmap(NULL, size + sizeof(mmap_data), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (md == MAP_FAILED) { perror("mmap"); close(fd); return; } // 存储数据 md->size = size; char* buf = md->buf; for (const auto& kv : data) { strncpy(buf, kv.first.c_str(), kv.first.size() + 1); buf += kv.first.size() + 1; strncpy(buf, kv.second.c_str(), kv.second.size() + 1); buf += kv.second.size() + 1; } // 解除映射 if (munmap(md, size + sizeof(mmap_data)) == -1) { perror("munmap"); close(fd); return; } // 关闭文件 close(fd); } // 从 mmap_file 文件中读取数据 unordered_map<string, string> read_data() { unordered_map<string, string> result; // 打开文件 int fd = open(filename, O_RDONLY); if (fd == -1) { perror("open"); return result; } // 映射文件 mmap_data* md = (mmap_data*) mmap(NULL, sizeof(mmap_data), PROT_READ, MAP_SHARED, fd, 0); if (md == MAP_FAILED) { perror("mmap"); close(fd); return result; } // 读取数据 char* buf = md->buf; for (int i = 0; i < md->size; ) { string key(buf); buf += key.size() + 1; string value(buf); buf += value.size() + 1; result[key] = value; i += key.size() + value.size() + 2; } // 解除映射 if (munmap(md, sizeof(mmap_data)) == -1) { perror("munmap"); close(fd); return result; } // 关闭文件 close(fd); return result; } int main() { // 写入数据 write_data(); // 读取数据 unordered_map<string, string> result = read_data(); // 输出结果 for (const auto& kv : result) { cout << kv.first << ": " << kv.second << endl; } return 0; } ``` 上面的代码中,我们首先定义了一个 unordered_map 存储数据,并且定义了一个结构体 mmap_data,用于在 mmap 映射文件中存储数据。然后,我们实现了一个 calc_size 函数,用于计算数据大小。接着,我们实现了一个 write_data 函数,用于将数据存储到 mmap_file 文件中。我们首先打开文件,然后计算数据大小,扩展文件大小,映射文件,存储数据,解除映射,最后关闭文件。接着,我们实现了一个 read_data 函数,用于从 mmap_file 文件中读取数据。我们首先打开文件,然后映射文件,读取数据,解除映射,最后关闭文件。最后,我们在 main 函数中,先写入数据,然后读取数据,并输出结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘色的喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值