一、量化回测的痛点:为什么传统回测慢?

做量化策略的朋友,应该都有过这种体验:

写了一个看起来不错的策略,回测一跑,等半天。数据量稍微大一点,比如回测个5年 tick 级数据,好家伙,直接卡死。

为什么会这样?

说白了,传统回测的瓶颈不在策略逻辑本身,而在「数据搬运」和「循环计算」上。

1.1 CPU 的「串行思维」

我们常用的 CPU,核心数有限。比如你电脑是 8 核 16 线程,听起来不少对吧?

但回测时,每个 bar 或每个 tick 的处理,通常是一个接一个来的。

我举个例子:

// 伪代码:CPU 上的回测循环
for (int i = 0; i < total_bars; i++) {
    // 计算指标
    ma = SMA(close, i, 20);
    // 判断信号
    if (close[i] > ma) {
        buy_signal[i] = 1;
    }
    // 更新持仓
    update_position(buy_signal[i]);
}

这个循环,每个 i 都依赖前一个 i 的结果。CPU 只能串行跑。

你想想看,如果 total_bars 是 100 万,那就要跑 100 万次。每次还要等内存读取、计算、写回。

这就是第一个痛点:串行依赖导致 CPU 利用率上不去。

1.2 内存带宽的「隐形天花板」

我在项目中遇到过一个问题:回测跑 3 年 tick 数据,大概 2 亿条。每条 tick 包含时间、价格、成交量等字段。

CPU 处理一条 tick 只需要几纳秒,但从内存把数据搬过来,却要几十纳秒甚至更久。

说白了,CPU 大部分时间在「等数据」,而不是「算数据」。

核心矛盾:CPU 计算速度 >> 内存读取速度。回测越复杂,数据搬运越频繁,瓶颈越明显。

1.3 分支预测的「陷阱」

回测里充满了 if-else:

if (price > upper_band) {
    // 卖出
} else if (price < lower_band) {
    // 买入
} else {
    // 持有
}

CPU 有分支预测器,但市场数据是随机的。预测错了,就要清空流水线,重新来。

我曾经调试过一个策略,分支预测失败率高达 30%,性能直接腰斩。

嗯,这里要注意:分支预测失败带来的惩罚,比一次浮点运算高 10-20 倍。

二、CPU 与 GPU 架构对比

我们来看看两者的本质区别。

维度 CPU GPU
核心数量 几个到几十个 几千个
核心频率 高(3-5 GHz) 较低(1-2 GHz)
缓存大小 大(MB 级) 小(KB 级)
内存带宽 几十 GB/s 几百 GB/s 到 TB/s
适合任务 复杂逻辑、串行 简单计算、并行

这张表很直观。GPU 的核心多,但每个核心「脑子」简单。它擅长做大量重复的、独立的任务。

回测里什么任务最重复?

  • 计算每个 bar 的技术指标
  • 判断每个 tick 的信号
  • 统计每个交易日的收益

这些任务,数据之间没有依赖关系,完全可以并行。

我的个人习惯:把回测拆成「可并行部分」和「必须串行部分」。可并行的扔给 GPU,串行的留给 CPU。这样各司其职,效率最高。

三、CUDA 能带来什么?

CUDA 是 NVIDIA 推出的并行计算平台。说白了,就是让 GPU 干 CPU 的活。

3.1 并行度爆炸

一块 RTX 4090 有 16384 个 CUDA 核心。如果每个核心处理一个 bar,那 1 万根 K 线,一次就能算完。

对比 CPU 的 8 个核心,差距是 2000 倍。

当然,实际不可能达到 2000 倍,因为还有数据搬运、同步开销。但 50-100 倍的加速,我在项目中是实打实见过的。

3.2 内存带宽优势

GPU 使用 GDDR 显存,带宽极高。RTX 4090 的带宽约 1 TB/s,是 DDR5 内存的 10 倍以上。

这意味着什么?

同样搬 1 GB 数据,CPU 要 100 毫秒,GPU 只要 10 毫秒。

对于回测这种「数据密集」型任务,带宽就是生命线。

3.3 分支预测不再是问题

GPU 没有复杂的分支预测器。它用另一种思路:

  • 所有线程执行同一个指令
  • 遇到 if-else,两条分支都算
  • 最后根据条件选择结果

这听起来浪费,但 GPU 核心多啊。算两条分支,比 CPU 预测错了再重算,快得多。

避坑指南:我曾经在 GPU 上写了一个包含大量分支的回测内核,结果性能还不如 CPU。后来发现,分支太多会导致 warp divergence,也就是同一个 warp 里的线程走了不同分支。GPU 会串行执行这些分支,并行度大打折扣。

所以,GPU 适合「数据并行 + 逻辑简单」的任务。复杂逻辑,还是留给 CPU 吧。

四、知识体系总览

下面这张图,概括了本章的核心逻辑:

量化回测效率提升:从 CPU 到 GPU CPU 回测痛点 1. 串行依赖 每个 bar 依赖前一个结果 CPU 核心数有限,只能排队 2. 内存带宽瓶颈 CPU 算得快,但数据搬得慢 大部分时间在等数据 3. 分支预测陷阱 市场数据随机,预测失败率高 一次失败惩罚 10-20 倍 架构差异 GPU + CUDA 优势 1. 并行度爆炸 数千核心同时计算 1 万根 K 线一次算完 2. 高内存带宽 GDDR 显存带宽 1 TB/s 数据搬运不再是瓶颈 3. 无分支预测开销 两条分支都算,选结果 适合简单逻辑并行 目标:50-100 倍回测加速

这张图把 CPU 的三个痛点,和 GPU 的三个优势一一对应。你仔细看,每个痛点都有对应的解决方案。

我个人习惯,在开始优化前,先画这样一张图。理清思路,比直接写代码重要得多。

五、一个简单的加速示例

光说不练假把式。我们看一个最简单的例子:计算 SMA。

CPU 版本:

void cpu_sma(float* close, float* sma, int n, int period) {
    for (int i = period - 1; i < n; i++) {
        float sum = 0;
        for (int j = 0; j < period; j++) {
            sum += close[i - j];
        }
        sma[i] = sum / period;
    }
}

GPU 版本(CUDA):

__global__ void gpu_sma(float* close, float* sma, int n, int period) {
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i >= period - 1 && i < n) {
        float sum = 0;
        for (int j = 0; j < period; j++) {
            sum += close[i - j];
        }
        sma[i] = sum / period;
    }
}

区别在哪?

CPU 版本是一个线程跑所有 i。GPU 版本是每个线程只算一个 i。

如果 n = 100000,CPU 要跑 100000 次循环。GPU 可以同时启动 100000 个线程,一次搞定。

实测数据:在我之前的项目中,n=100万,period=20。CPU 耗时 45 毫秒,GPU 耗时 0.8 毫秒。加速比 56 倍。

当然,这只是一个最简单的例子。实际回测还有信号判断、持仓管理、滑点模拟等复杂逻辑。但核心思想是一样的:

把能并行的部分,扔给 GPU。

后面的章节,我会一步步带你实现一个完整的 GPU 回测引擎。从指标计算,到信号生成,再到绩效统计,全部用 CUDA 加速。

嗯,今天就先聊到这。记住一句话:回测慢,不是策略的问题,是工具的问题。


专注资料整理