2. 火山模型与向量化模型性能对比

任务说明

本案例目标是在同一执行引擎框架内分别实现火山模型(Volcano Model)与向量化模型(Vectorized Model)的整数加法运算,通过可重复的基准测试对比两种模型的吞吐量差异。你需要完成:两种模型的算子实现、数据生成、基准测试驱动以及结果判定标准。

模型实现

火山模型采用经典的next()迭代协议,每次调用返回一行数据;向量化模型采用nextBatch()协议,每次返回一批(例如1024行)连续内存数据。两者执行相同的计算逻辑:对两列输入执行a + b,输出一列结果。

// vector_vs_volcano.cpp
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <chrono>
#include <vector>
#include <memory>

// ---------- 火山模型 ----------
class VolcanoOp {
public:
    virtual bool next(int64_t* out) = 0;
    virtual ~VolcanoOp() {}
};

class VolcanoScan : public VolcanoOp {
    const std::vector<int64_t>& data;
    size_t pos = 0;
public:
    explicit VolcanoScan(const std::vector<int64_t>& d) : data(d) {}
    bool next(int64_t* out) override {
        if (pos >= data.size()) return false;
        *out = data[pos++];
        return true;
    }
};

class VolcanoAdd : public VolcanoOp {
    VolcanoOp* left;
    VolcanoOp* right;
public:
    VolcanoAdd(VolcanoOp* l, VolcanoOp* r) : left(l), right(r) {}
    bool next(int64_t* out) override {
        int64_t a, b;
        if (!left->next(&a)) return false;
        if (!right->next(&b)) return false;
        *out = a + b;
        return true;
    }
};

// ---------- 向量化模型 ----------
class VecOp {
public:
    virtual size_t nextBatch(const int64_t** out) = 0;
    virtual ~VecOp() {}
};

class VecScan : public VecOp {
    const std::vector<int64_t>& data;
    size_t pos = 0;
    static constexpr size_t BATCH = 1024;
public:
    explicit VecScan(const std::vector<int64_t>& d) : data(d) {}
    size_t nextBatch(const int64_t** out) override {
        if (pos >= data.size()) return 0;
        size_t remaining = data.size() - pos;
        size_t count = remaining < BATCH ? remaining : BATCH;
        *out = &data[pos];
        pos += count;
        return count;
    }
};

class VecAdd : public VecOp {
    VecOp* left;
    VecOp* right;
    std::vector<int64_t> result;
public:
    VecAdd(VecOp* l, VecOp* r) : left(l), right(r) {
        result.resize(1024);
    }
    size_t nextBatch(const int64_t** out) override {
        const int64_t* a;
        const int64_t* b;
        size_t cnt = left->nextBatch(&a);
        if (cnt == 0) return 0;
        size_t cnt2 = right->nextBatch(&b);
        if (cnt2 != cnt) {
            fprintf(stderr, "左右输入批次大小不一致\n");
            exit(1);
        }
        for (size_t i = 0; i < cnt; ++i) {
            result[i] = a[i] + b[i];
        }
        *out = result.data();
        return cnt;
    }
};

// ---------- 基准测试 ----------
template <typename F>
double timeIt(F&& f, int64_t totalRows) {
    auto start = std::chrono::high_resolution_clock::now();
    f();
    auto end = std::chrono::high_resolution_clock::now();
    double sec = std::chrono::duration<double>(end - start).count();
    return totalRows / sec; // rows per second
}

int main() {
    const int64_t N = 10 * 1000 * 1000; // 1000万行
    std::vector<int64_t> colA(N), colB(N);
    for (int64_t i = 0; i < N; ++i) {
        colA[i] = i % 1000;
        colB[i] = (i * 7) % 1000;
    }

    // 火山模型执行
    VolcanoScan scanA(colA), scanB(colB);
    VolcanoAdd vadd(&scanA, &scanB);
    int64_t sumCheck = 0;
    int64_t val;
    auto volcanoRun = [&]() {
        while (vadd.next(&val)) {
            sumCheck += val;
        }
    };
    double volcanoRate = timeIt(volcanoRun, N);

    // 向量化执行
    VecScan vscanA(colA), vscanB(colB);
    VecAdd vaddOp(&vscanA, &vscanB);
    int64_t vecSumCheck = 0;
    auto vecRun = [&]() {
        const int64_t* batch;
        size_t cnt;
        while ((cnt = vaddOp.nextBatch(&batch)) > 0) {
            for (size_t i = 0; i < cnt; ++i) {
                vecSumCheck += batch[i];
            }
        }
    };
    double vecRate = timeIt(vecRun, N);

    // 输出结果
    printf("总行数: %lld\n", (long long)N);
    printf("火山模型吞吐: %.2f 行/秒\n", volcanoRate);
    printf("向量化吞吐: %.2f 行/秒\n", vecRate);
    printf("加速比: %.2fx\n", vecRate / volcanoRate);
    printf("校验和(火山): %lld\n", (long long)sumCheck);
    printf("校验和(向量): %lld\n", (long long)vecSumCheck);
    return 0;
}

编译与运行

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

验证方法

运行程序后观察以下判定标准:

  1. 正确性验证:程序输出的两个校验和必须完全相等,且等于sum_{i=0}^{N-1} ((i%1000) + ((i*7)%1000))的数学期望值。若不等,说明某个模型实现有误。
  2. 性能对比验证:观察输出的加速比。在-O2编译且数据规模为1000万行时,向量化模型通常应快于火山模型(加速比>1)。若加速比≤1,请检查是否未开启编译优化,或尝试增大N至5000万行以降低缓存与调用开销的干扰。
  3. 可复现性:多次运行程序,两次吞吐量数值波动应在±10%以内(受系统调度影响)。

预期输出示例

总行数: 10000000
火山模型吞吐: 85000000.00 行/秒
向量化吞吐: 420000000.00 行/秒
加速比: 4.94x
校验和(火山): 49995000000
校验和(向量): 49995000000

注意:以上数值仅为逻辑一致的示例,实际数值取决于CPU型号、内存带宽与编译器版本。判定通过的唯一标准是校验和相等且加速比>1。

火山模型 vs 向量化模型 执行流程对比 火山模型(行迭代) Scan A Scan B Add (a+b) next() → 1行 每行一次 next() 调用,虚函数开销大 向量化模型(批量迭代) Scan A Scan B Add (a+b) nextBatch() → 1024行 每批一次调用,循环内无虚函数,缓存友好