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
验证方法
运行程序后观察以下判定标准:
- 正确性验证:程序输出的两个校验和必须完全相等,且等于
sum_{i=0}^{N-1} ((i%1000) + ((i*7)%1000))的数学期望值。若不等,说明某个模型实现有误。 - 性能对比验证:观察输出的加速比。在
-O2编译且数据规模为1000万行时,向量化模型通常应快于火山模型(加速比>1)。若加速比≤1,请检查是否未开启编译优化,或尝试增大N至5000万行以降低缓存与调用开销的干扰。 - 可复现性:多次运行程序,两次吞吐量数值波动应在±10%以内(受系统调度影响)。
预期输出示例
总行数: 10000000
火山模型吞吐: 85000000.00 行/秒
向量化吞吐: 420000000.00 行/秒
加速比: 4.94x
校验和(火山): 49995000000
校验和(向量): 49995000000
注意:以上数值仅为逻辑一致的示例,实际数值取决于CPU型号、内存带宽与编译器版本。判定通过的唯一标准是校验和相等且加速比>1。