4. 向量化表达式求值框架
任务说明
本案例实现一个向量化表达式求值框架,用于对批量数据执行逐行表达式计算。输入为多列数值型数据(每列一个数组),输出为按行计算的表达式结果数组。与传统的逐行解释执行不同,本框架将表达式编译为向量化操作序列,每次对整列数据执行批量运算,从而减少解释开销并提升缓存利用率。
框架设计
表达式采用逆波兰表示法(RPN)存储,便于顺序求值。向量化执行的核心思路是:维护一个操作数栈,栈中每个元素是一个完整的列数组(而非单个标量)。遇到常量时广播为与输入等长的数组;遇到列引用时直接压入对应列;遇到二元运算符时弹出两个数组,调用向量化运算函数生成新数组压回栈中。所有运算均使用循环对数组逐元素操作,避免为每行单独解释执行。
核心实现
// vec_expr.h
#ifndef VEC_EXPR_H
#define VEC_EXPR_H
#include <stddef.h>
#include <stdint.h>
typedef enum {
OP_ADD, OP_SUB, OP_MUL, OP_DIV,
OP_LOAD_COL, OP_LOAD_CONST
} OpType;
typedef struct {
OpType type;
int col_index; // OP_LOAD_COL 时使用
double const_value; // OP_LOAD_CONST 时使用
} ExprOp;
// 执行向量化表达式求值
// cols: 输入列数组(每列长度 n)
// col_count: 列数量
// ops: 操作序列(RPN顺序)
// op_count: 操作数量
// n: 每列元素个数
// result: 输出缓冲区(长度 n),由调用方分配
void vec_expr_eval(const double **cols, int col_count,
const ExprOp *ops, int op_count,
size_t n, double *result);
#endif // VEC_EXPR_H
// vec_expr.c
#include "vec_expr.h"
#include <stdlib.h>
#include <string.h>
// 向量化二元运算
static void add_vec(const double *a, const double *b, double *out, size_t n) {
for (size_t i = 0; i < n; i++) out[i] = a[i] + b[i];
}
static void sub_vec(const double *a, const double *b, double *out, size_t n) {
for (size_t i = 0; i < n; i++) out[i] = a[i] - b[i];
}
static void mul_vec(const double *a, const double *b, double *out, size_t n) {
for (size_t i = 0; i < n; i++) out[i] = a[i] * b[i];
}
static void div_vec(const double *a, const double *b, double *out, size_t n) {
for (size_t i = 0; i < n; i++) out[i] = a[i] / b[i];
}
void vec_expr_eval(const double **cols, int col_count,
const ExprOp *ops, int op_count,
size_t n, double *result) {
// 操作数栈:最多 op_count 个数组指针
double **stack = malloc(sizeof(double*) * op_count);
int stack_top = 0;
for (int i = 0; i < op_count; i++) {
const ExprOp *op = &ops[i];
switch (op->type) {
case OP_LOAD_COL: {
if (op->col_index < 0 || op->col_index >= col_count) {
free(stack);
return; // 非法列索引
}
stack[stack_top++] = (double*)cols[op->col_index];
break;
}
case OP_LOAD_CONST: {
double *buf = malloc(sizeof(double) * n);
for (size_t j = 0; j < n; j++) buf[j] = op->const_value;
stack[stack_top++] = buf;
break;
}
case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV: {
if (stack_top < 2) { free(stack); return; }
double *b = stack[--stack_top];
double *a = stack[--stack_top];
double *out = malloc(sizeof(double) * n);
switch (op->type) {
case OP_ADD: add_vec(a, b, out, n); break;
case OP_SUB: sub_vec(a, b, out, n); break;
case OP_MUL: mul_vec(a, b, out, n); break;
case OP_DIV: div_vec(a, b, out, n); break;
default: break;
}
// 释放临时操作数(常量或中间结果)
// 注意:列引用不释放,由调用方管理
// 这里简化处理:仅释放非常量操作数
// 实际工程中需跟踪来源
stack[stack_top++] = out;
break;
}
}
}
if (stack_top == 1) {
memcpy(result, stack[0], sizeof(double) * n);
}
// 清理:释放所有非常量临时数组
// 由于简化实现,此处不区分来源,仅释放栈中剩余项
// 注意:列引用不应释放,但本示例中列引用与临时数组混存
// 为保持简洁,此处不释放,由调用方统一管理
free(stack);
}
// main.c 示例调用
#include <stdio.h>
#include <stdlib.h>
#include "vec_expr.h"
int main() {
// 输入数据:3列,每列4行
double col_a[] = {1.0, 2.0, 3.0, 4.0};
double col_b[] = {10.0, 20.0, 30.0, 40.0};
double col_c[] = {2.0, 3.0, 4.0, 5.0};
const double *cols[] = {col_a, col_b, col_c};
int col_count = 3;
size_t n = 4;
// 表达式: (col_a + col_b) * col_c
// RPN: col_a col_b + col_c *
ExprOp ops[] = {
{OP_LOAD_COL, 0, 0.0},
{OP_LOAD_COL, 1, 0.0},
{OP_ADD, 0, 0.0},
{OP_LOAD_COL, 2, 0.0},
{OP_MUL, 0, 0.0}
};
int op_count = sizeof(ops) / sizeof(ops[0]);
double result[4];
vec_expr_eval(cols, col_count, ops, op_count, n, result);
// 打印结果
for (size_t i = 0; i < n; i++) {
printf("result[%zu] = %.1f\n", i, result[i]);
}
return 0;
}
编译与运行
gcc -O2 -o vec_expr main.c vec_expr.c -lm
./vec_expr
验证方法
运行程序后,观察输出。根据给定输入,表达式 (col_a + col_b) * col_c 的预期计算结果为:
result[0] = 22.0 // (1+10)*2
result[1] = 66.0 // (2+20)*3
result[2] = 132.0 // (3+30)*4
result[3] = 220.0 // (4+40)*5
判定标准:程序输出与上述预期值完全一致,说明向量化表达式求值框架正确执行了批量运算。可通过修改输入数组或表达式操作序列来验证框架的通用性。