一、量化回测的痛点:为什么传统回测慢?
做量化策略的朋友,应该都有过这种体验:
写了一个看起来不错的策略,回测一跑,等半天。数据量稍微大一点,比如回测个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 的三个优势一一对应。你仔细看,每个痛点都有对应的解决方案。
我个人习惯,在开始优化前,先画这样一张图。理清思路,比直接写代码重要得多。
五、一个简单的加速示例
光说不练假把式。我们看一个最简单的例子:计算 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 加速。
嗯,今天就先聊到这。记住一句话:回测慢,不是策略的问题,是工具的问题。