如何使用 C++20 协程编写数据生成器和文件编写器?

问题描述 投票:0回答:0

几天来我一直在观看 C++ 协程视频,现在正试图围绕它们(不是我们所有人),并且根据我目前所知道的,它似乎非常适合一些旧的我拥有的代码。这是该代码的完全工作简化,可以相对快速地同步工作。 在我的机器上它在大约 1.7 秒内生成 2GB 的数据.

我想我在基本层面上了解协程。但是 API 似乎为实现者留下了很大的空间来犯细微的错误。我还看到了完全不同的教程,很难评估从协同程序的角度考虑我的生成器的“正确方法”是什么

要自己运行它,只需更改两个 const 路径以使其对您有意义,而 lut 实际上只是一个包含随机二进制数据的二进制文件。

#include <filesystem>
#include <fstream>
#include <iostream>
#include <numeric>
#include <random>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>


static std::random_device s_rd;
static std::mt19937_64 s_mt(s_rd());
static std::discrete_distribution<int32_t> dist = { 26, 28, 23, 18, 5 };

// Pretend these are in a sane place, owned by some manager class.
static std::vector<uint8_t> m_rngDataLut;
static std::vector<uint8_t> writeBuffer;

// Path to some binary file with binary data in it.
static const std::string s_lutPath = "e:\\testing\\lut.dat";
static const std::string s_destinationRootPath = "e:\\testing\\build\\";
enum class BucketType : int32_t
{
    // 1B - 1KB
    ExtraSmall = 0,

    // 1KB - 4KB
    Small,

    // 4KB - 16KB
    Medium,

    // 16KB - 128KB
    Large,

    // 128KB - 128MB
    ExtraLarge,
    End,
};

struct Range
{
    int64_t minimum = std::numeric_limits<uint64_t>::min();
    int64_t maximum = std::numeric_limits<uint64_t>::max();
};

// Hard coded file size ranges aimed at simulating game file sizes.
static const std::unordered_map<BucketType, Range> s_fileSizeBuckets =
{
    {
        // 1B to 1KB
        BucketType::ExtraSmall, {1, 1024}
    },
    {
        // > 1KB to 4KB
        BucketType::Small, {1025, 4096}
    },
    {
        // > 4KB to 16KB
        BucketType::Medium, {4097, 16384}
    },
    {
        // > 16KB to 128KB
        BucketType::Large, {16385, 131072}
    },
    {
        // > 128KB to 128MB
        BucketType::ExtraLarge, {131073, 134217728}
    },
};

const Range& GetFileSizeRangeForBucketType(BucketType type)
{
    return s_fileSizeBuckets.at(type);
}

static int64_t GetRandomNumber(int64_t lowerBound, int64_t upperBound)
{
    std::uniform_int_distribution<int64_t> dist(lowerBound, upperBound);
    return dist(s_mt);
}
int64_t GetRandomFileSizeBytes()
{
    // First randomly select bucket based on
    int32_t bucket = dist(s_mt);

    // Get the file size based on the bucket-specific size range.
    const Range& fileSizeRange = GetFileSizeRangeForBucketType(static_cast<BucketType>(bucket));
    return GetRandomNumber(fileSizeRange.minimum, fileSizeRange.maximum);
}

void InitLut()
{
    namespace fs = std::filesystem;
    int64_t fileSize = fs::file_size(s_lutPath);
    m_rngDataLut.resize(fileSize);

    std::ifstream rngLutFile;
    rngLutFile.open(s_lutPath, std::ios::in | std::ios::binary);
    rngLutFile.seekg(0);
    rngLutFile.read(reinterpret_cast<char*>(m_rngDataLut.data()), fileSize);
    rngLutFile.close();
}

void GenerateAndWriteFile(const std::string& filePath, int64_t fileSize)
{
    namespace fs = std::filesystem;
    fs::path path = filePath;
    fs::path parentPath = path.parent_path();
    if (!fs::exists(parentPath))
    {
        fs::create_directory(parentPath);
    }

    std::ofstream outFile;
    outFile.open(filePath, std::ios::out | std::ios::binary);
    int64_t chunkSizeBytes = writeBuffer.size();

    int64_t bytesRemaining = fileSize;
    while (bytesRemaining > 0)
    {
        // Generate data. Maybe this is its own coroutine?
        int64_t startIndex = GetRandomNumber(0, m_rngDataLut.size() - chunkSizeBytes);
        std::ranges::copy_n(m_rngDataLut.begin() + startIndex, chunkSizeBytes, writeBuffer.begin());

        int64_t bytesToWrite = std::min(bytesRemaining, chunkSizeBytes);
        
        // Write the data. Maybe this is its own coroutine?
        outFile.write(reinterpret_cast<char*>(writeBuffer.data()), bytesToWrite);
        bytesRemaining -= bytesToWrite;
    }

    outFile.close();
}

int main()
{
    // 64KB
    int64_t chunkBufferSize = 65536;
    writeBuffer.resize(chunkBufferSize);

    // 2GB
    int64_t bytesRemaining = 2147483648;

    namespace fs = std::filesystem;
    InitLut();

    int fileCount = 0;

    fs::path root = s_destinationRootPath;
    while (bytesRemaining > 0)
    {
        // Picks an appropriate file size based on the size ratios.
        int64_t fileSizeBytes = GetRandomFileSizeBytes();
        int64_t bytesToWrite = std::min(bytesRemaining, fileSizeBytes);

        std::stringstream ss;
        ss << "data_" << fileCount++ << ".dat";

        fs::path targetFilePath = root / ss.str();
        GenerateAndWriteFile(targetFilePath.string(), bytesToWrite);
        bytesRemaining -= bytesToWrite;

    }
}

问题

我认为对于我的情况,有一个生成随机数据的协程和一个将数据写入文件的协程是有意义的。哪个,我想我可以在真空中写。不过,我不确定他们应该如何互动。

他们应该用

Transfer{}
在彼此之间来回弹跳吗?意思是,
DataGeneratorCoroutine
只是在
FileWriterCoroutine
完成写入最后一个数据集并准备好新数据集时将数据传递给
FileWriterCoroutine

或者他们应该将控制权交还给他们自己的班级(比如经理班级之类的)?

  1. 我想任何人都可以以一种演示使用最佳实践使用多个协程异步生成/写入数据的方式翻译这段代码吗? 甚至只是类似于我正在做的事情,我可以尝试插入。

  2. 目前我的代码一次生成 64KB 并将其传递给写入 64KB 的编写器。即使这些是并行完成的,我想知道我是否真的获得了很多。 有没有办法使用协程同时生成多个文件?或者我会为此结合使用线程吗?意味着生成/写入数据的协程将在一个线程上运行,而我可以生成多个?

感觉好像没有很多实用的好例子。

c++ asynchronous random file-io c++20
© www.soinside.com 2019 - 2024. All rights reserved.