3. 批量数据块(Batch)设计与实现

批量数据块(Batch)设计与实现 学习卡片

任务说明

在火山模型(Volcano Model)中,每次调用next()只返回一行数据,导致函数调用开销巨大。本案例的目标是设计并实现一个批量数据块(Batch),将多行数据打包传递,为后续向量化执行奠定基础。你需要完成:定义Batch的数据结构、实现行与列的存取操作、提供批量迭代器接口,并通过一个最小示例验证Batch能正确存储和读取多行多列数据。

Batch数据结构设计

Batch采用列式存储(Columnar Layout),即同一列的数据在内存中连续存放。这种布局对向量化计算友好,能够充分利用CPU缓存。Batch包含以下核心组件:

  • 列集合:使用std::vector<Column>存储所有列,每列是一个std::vector<int32_t>(为简化,本案例仅支持INT32类型)。
  • 行数:记录当前Batch中有效数据的行数,所有列的长度必须一致。
  • 容量:Batch预分配的最大行数,避免频繁内存分配。

代码实现


#include <iostream>
#include <vector>
#include <stdexcept>
#include <cstdint>

// 批量数据块:列式存储,仅支持INT32类型
class Batch {
private:
    std::vector<std::vector<int32_t>> columns_;  // 每列数据
    size_t num_rows_ = 0;                        // 当前有效行数
    size_t capacity_ = 0;                        // 最大容量

public:
    // 构造:指定列数和容量
    Batch(size_t num_cols, size_t capacity)
        : columns_(num_cols), capacity_(capacity) {
        for (auto& col : columns_) {
            col.reserve(capacity);
        }
    }

    // 向指定列追加一个值
    void append(size_t col_idx, int32_t value) {
        if (col_idx >= columns_.size()) {
            throw std::out_of_range("列索引越界");
        }
        if (num_rows_ >= capacity_) {
            throw std::overflow_error("Batch容量已满");
        }
        columns_[col_idx].push_back(value);
    }

    // 完成一行的写入:所有列必须已追加,然后增加行数
    void end_row() {
        if (num_rows_ >= capacity_) {
            throw std::overflow_error("Batch容量已满");
        }
        // 检查所有列长度一致
        for (const auto& col : columns_) {
            if (col.size() != num_rows_ + 1) {
                throw std::runtime_error("列长度不一致,无法结束当前行");
            }
        }
        num_rows_++;
    }

    // 获取某行某列的值
    int32_t get(size_t row_idx, size_t col_idx) const {
        if (row_idx >= num_rows_ || col_idx >= columns_.size()) {
            throw std::out_of_range("行或列索引越界");
        }
        return columns_[col_idx][row_idx];
    }

    // 获取列数
    size_t num_cols() const { return columns_.size(); }

    // 获取当前有效行数
    size_t num_rows() const { return num_rows_; }

    // 获取指定列的原始数据引用(供向量化操作使用)
    const std::vector<int32_t>& column(size_t col_idx) const {
        return columns_.at(col_idx);
    }

    // 重置Batch以便复用
    void reset() {
        for (auto& col : columns_) {
            col.clear();
        }
        num_rows_ = 0;
    }
};

// 批量迭代器:按行遍历Batch
class BatchIterator {
private:
    const Batch& batch_;
    size_t row_idx_ = 0;

public:
    explicit BatchIterator(const Batch& batch) : batch_(batch) {}

    bool has_next() const { return row_idx_ < batch_.num_rows(); }

    // 返回当前行的所有列值
    std::vector<int32_t> next() {
        if (!has_next()) {
            throw std::out_of_range("迭代器越界");
        }
        std::vector<int32_t> row(batch_.num_cols());
        for (size_t c = 0; c < batch_.num_cols(); ++c) {
            row[c] = batch_.get(row_idx_, c);
        }
        row_idx_++;
        return row;
    }
};

// 使用示例:写入3行2列数据并遍历
int main() {
    Batch batch(2, 10);  // 2列,容量10行

    // 写入第1行: (10, 20)
    batch.append(0, 10);
    batch.append(1, 20);
    batch.end_row();

    // 写入第2行: (30, 40)
    batch.append(0, 30);
    batch.append(1, 40);
    batch.end_row();

    // 写入第3行: (50, 60)
    batch.append(0, 50);
    batch.append(1, 60);
    batch.end_row();

    std::cout << "Batch行数: " << batch.num_rows() << std::endl;
    std::cout << "Batch列数: " << batch.num_cols() << std::endl;

    // 使用迭代器遍历
    BatchIterator it(batch);
    int row_num = 0;
    while (it.has_next()) {
        auto row = it.next();
        std::cout << "第" << row_num++ << "行: ";
        for (int32_t val : row) {
            std::cout << val << " ";
        }
        std::cout << std::endl;
    }

    // 验证随机访问
    std::cout << "get(2,1) = " << batch.get(2, 1) << std::endl;  // 应为60

    return 0;
}

关键设计说明

本实现采用追加-结束行两阶段写入模式:先向各列追加值,再调用end_row()确认该行完成。这种设计保证了列间长度一致性——end_row()会校验所有列长度是否都等于num_rows_+1,防止写入不完整行。列式存储使得column()方法能直接暴露连续内存,供后续SIMD向量化操作直接访问,无需行转列转换。

验证方法

将上述代码保存为batch_demo.cpp,使用以下命令编译并运行:


g++ -std=c++11 -O2 -o batch_demo batch_demo.cpp
./batch_demo

预期输出(由代码逻辑直接推出):


Batch行数: 3
Batch列数: 2
第0行: 10 20
第1行: 30 40
第2行: 50 60
get(2,1) = 60

判定标准:程序正常退出且输出与上述预期一致,说明Batch的写入、行结束校验、随机读取和迭代遍历均工作正常。若end_row()前各列追加次数不一致,程序应抛出std::runtime_error异常,可自行添加错误路径测试验证。

Batch列式存储结构 列0 (vector<int32>) 索引0: 10 索引1: 30 索引2: 50 列1 (vector<int32>) 索引0: 20 索引1: 40 索引2: 60 行0 行1 行2 Batch元数据 num_rows_ = 3 num_cols_ = 2 capacity_ = 10 append(col0, val) append(col1, val) end_row() 校验列长度一致 num_rows_++ BatchIterator has_next() → 按行遍历 next() → 返回整行vector 列式存储:同列数据连续内存,便于SIMD向量化加载