4. 向量化表达式求值框架

任务说明

本案例实现一个向量化表达式求值框架,用于对批量数据执行逐行表达式计算。输入为多列数值型数据(每列一个数组),输出为按行计算的表达式结果数组。与传统的逐行解释执行不同,本框架将表达式编译为向量化操作序列,每次对整列数据执行批量运算,从而减少解释开销并提升缓存利用率。

框架设计

表达式采用逆波兰表示法(RPN)存储,便于顺序求值。向量化执行的核心思路是:维护一个操作数栈,栈中每个元素是一个完整的列数组(而非单个标量)。遇到常量时广播为与输入等长的数组;遇到列引用时直接压入对应列;遇到二元运算符时弹出两个数组,调用向量化运算函数生成新数组压回栈中。所有运算均使用循环对数组逐元素操作,避免为每行单独解释执行。

col_a col_b col_c 表达式编译 RPN: col_a col_b + col_c * → 操作序列 1. load col_a 2. load col_b 3. add_vec 4. load col_c 5. mul_vec 向量化执行 栈操作(整列) push col_a push col_b add → tmp1 push col_c mul → result 结果列 result[i] 每步操作处理整个数组,而非单行标量;中间结果 tmp1 也是完整数组。

核心实现

// 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

判定标准:程序输出与上述预期值完全一致,说明向量化表达式求值框架正确执行了批量运算。可通过修改输入数组或表达式操作序列来验证框架的通用性。