# 计算后端 (/zh/features/performance/compute-backends)

VBT 为什么快，又如何控制计算在哪里执行？VBT PRO 将繁重的回测和分析操作放入 Numba 编译函数。受支持的函数还可以并行运行、使用可选 Rust 扩展，或使用您注册的 NumPy
实现。您可以逐次调用或全局选择后端，同时保留相同的 Python 研究工作流。

## 从 pandas 开始加速计算 \[#faster-calculations-from-pandas]

从 pandas Series 或 DataFrame 出发，即可调用 VBT 的滚动统计、指标和投资组合模拟器，无须自行编写编译函数。VBT
将数值数组传给计算，再用原有标签封装结果。日期、交易品种和参数组合都保留下来供分析使用。

对于自定义逻辑，Numba 让您用 Python 编写数值循环并编译。这适用于依赖未平仓头寸的信号，或逐根 K 线更新状态的指标。编译函数处理 NumPy 数组，数据准备和报告仍可保留在 pandas
中。示例见[自定义指标](/zh/features/indicators/indicator-development/)和[事件驱动回测](/zh/features/backtesting/event-driven-backtesting/)。

## 比较 NumPy、Numba 和 Rust \[#compare-numpy-numba-and-rust]

```python title="Time one rolling mean on 50 million values across backends and memory layouts"
>>> import timeit

>>> @vbt.register_jitted(task_id_or_func=vbt.nb.rolling_mean_nb)  # (1)
... def rolling_mean_np(arr, window, minp=None):
...     csum = np.cumsum(arr, axis=0)
...     out = np.full(arr.shape, np.nan)
...     out[window - 1] = csum[window - 1]
...     out[window:] = csum[window:] - csum[:-window]
...     return out / window

>>> np.random.seed(42)
>>> values = 100 + np.random.normal(0, 0.01, size=(100_000, 500)).cumsum(axis=0)
>>> layouts = {
...     "row-major": pd.DataFrame(np.ascontiguousarray(values)),  # (2)
...     "column-major": pd.DataFrame(np.asfortranarray(values)),
... }
>>> backends = {
...     "pandas": None,
...     "NumPy": "np",
...     "Numba": "nb",
...     "Numba parallel": dict(jitter="nb", parallel=True),
...     "Rust": "rs",
...     "Rust parallel": dict(jitter="rs", parallel=True),
... }
>>> def ms(func):
...     return min(timeit.repeat(func, number=3, repeat=5)) / 3 * 1000

>>> timings = {}
>>> for layout, prices in layouts.items():
...     for backend, jitted in backends.items():
...         if jitted is None:
...             func = lambda: prices.rolling(50).mean()
...         else:
...             func = lambda: prices.vbt.rolling_mean(50, jitted=jitted)
...         timings[(backend, layout)] = ms(func)
>>> pd.Series(timings).unstack().loc[list(backends), list(layouts)].round(0).astype(int)
                row-major  column-major
pandas                693           360
NumPy                 489           204
Numba                 561           163
Numba parallel        379            32
Rust                   87           145
Rust parallel          71            67
```

1.  将内置滚动均值的 NumPy 实现注册到 `np` jitter 下，使 `jitted="np"` 能选择它。
2.  行优先数组将每行的值连续存储，列优先数组将每列的值连续存储。由独立列构建的 DataFrame 通常为列优先。

表中显示 Apple M3 上每次调用的毫秒数，每个后端返回的数值均一致。最快运行耗时 32 毫秒，pandas 在相同数组上耗时 360 毫秒，但领先者随布局而变化：列优先数据以并行 Numba
最快，行优先数据则以并行 Rust 最快。Rust 的滚动均值逐行遍历数组，会先将列优先输入复制为行优先；Numba 的实现逐列遍历，列连续时受益最大。因此，VBT
允许逐次选择后端、全局设置后端，或让实测耗时决定。

## 哪个后端适合您的任务？ \[#which-backend-fits-your-workload]

| 您的任务                 | 可以尝试                                 |
| -------------------- | ------------------------------------ |
| 使用自定义 Python 交易规则的回测 | Numba 编译数值回调与模拟循环。                   |
| 跨大量交易品种或参数组合的滚动计算    | 并行 Numba 或 Rust 可将受支持计算分配到多个 CPU 核心。 |
| 反复调用受支持的内置函数         | Rust 直接运行原生代码，无须等待 Numba 编译该函数。      |
| 复用已有 NumPy 实现        | 将它与内置实现一起注册，再通过 `jitted` 选择。         |
| 计算形状各异的研究流程          | 测量关键调用，逐次选择后端或使用 AutoBench。          |

列既可代表交易品种，也可代表策略配置。模拟会并行处理独立列或组，同时按顺序处理各策略的 K
线。若要将完整回测分配给多个工作进程，或将大数组分块，参见[并行执行与缓存](/zh/features/performance/parallel-execution-and-caching/)。

## Numba 编译 \[#numba-compilation]

VBT 的大多数数值函数都由 Numba 编译，从滚动统计、信号生成器，到 `vbt.PF.from_signals` 背后的模拟器。模拟器是遍历 K
线的编译循环，而非整数组操作，因此止损、根据当前余额调整仓位等路径依赖规则也能以编译速度运行。编译函数释放 GIL，使线程能够并行执行；启用缓存的内置函数会将编译代码存入磁盘，以便后续 Python
会话复用。只要参数类型不变，仅改变数据或参数值本身不会触发重新编译，因此很适合反复研究。

全新环境中的首次调用仍需编译，可能耗时数秒。自定义回调，例如 `from_signals` 的信号函数，也会编译，而且每个新回调都会使模拟器重新编译，在构建本页的机器上约耗时 20
秒。重新定义回调，例如重运行定义它的 notebook
单元格，也被视为新回调，因此应将回调放在独立单元格或模块中。[`staticized` 选项](/zh/features/backtesting/backtesting-engine/#staticization)可将调用回调的模拟转化为也能在磁盘缓存的函数，使其跨重启和新工作进程复用。缓存的是编译后的模拟器，因此仍可用于新的市场数据和参数。

调试时，`jitted=False` 会运行底层 Python 函数，如下方累加和示例所示。您也可以在导入 VBT 前，通过 `numba.disable` 配置关闭 Numba。这控制 Numba
编译，而后端选择决定调用是否使用 Rust。

## 后端与 jitter \[#backends-and-jitters]

VBT 中的 jitted 函数是一个注册表条目，拥有一个或多个实现：`nb` 表示 Numba，`rs` 表示可选 Rust 扩展，`np` 表示向量化 NumPy，也可以是您注册的任意
jitter。`jitted` 参数为每次调用选择实现，并传递 `parallel=True`
等选项，详见下方[即时编译](#jitting)与[并行 Numba](#parallel-numba)功能介绍。并行版本在多个核心上处理列，宽数组受益明显，窄数组则有少量额外开销。滚动夏普和
Sortino 比率等收益滚动度量通过单次遍历更新累计和，无须重新计算每个窗口，详见下方[超高速滚动度量](#hyperfast-rolling-metrics)。

## 后端选择 \[#backend-selection]

安装 Rust 扩展后，兼容调用默认交给 Rust；Rust 无法处理输入时回退到 Numba。上例默认调用在行优先数据上耗时 86 毫秒，在列优先数据上耗时 147 毫秒，与 Rust
的耗时一致。以下三种设置可改变选择：

*   **逐次调用：** 使用 `jitted="nb"`、`jitted="rs"`，或包含选项的字典。
*   **按模块：** 解析覆盖规则可为路径匹配的全部函数设置后端，例如 `vectorbtpro.returns` 下的所有函数。
*   **按实测：** AutoBench 读取在本机生成的基准缓存，根据测量结果，以及必要时的输入形状，将每次调用分配给符合条件的后端。

Rust 引擎页的
[Rust 后端](/zh/features/performance/native-rust/#rust-backend)功能介绍展示各个选项。后端选择发生在计算内部，因此切换受支持实现时，可以保留相同的
pandas 输入和结果处理方式。

!!! tip "找到最快的后端"
    使用 VBT 基准工具，在自己的数组上比较后端，再依据耗时为各项计算选择后端。Python 输入和带标签的结果保持不变。

## 并行 Numba \[#parallel-numba]

新增于 1.0.0

✅ 大多数 Numba 编译函数已重写，使用
[`@jit` 自动并行化](https://numba.readthedocs.io/en/stable/user/parallel.html)并行处理各列。只需一条命令即可启用。此方法最适合应用于宽数组的轻量函数。

```python title="Benchmark the rolling mean without and with parallelization"
>>> np.random.seed(42)
>>> df = pd.DataFrame(np.random.uniform(size=(1000, 1000)))

>>> %timeit df.rolling(10).mean()  # (1)
45.6 ms ± 138 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

>>> %timeit df.vbt.rolling_mean(10)  # (2)
5.33 ms ± 302 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

>>> %timeit df.vbt.rolling_mean(10, jitted=dict(parallel=True))  # (3)
1.82 ms ± 5.21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
```

1.  使用 Pandas。
2.  使用未并行化的 Numba。
3.  使用并行化的 Numba。

## 即时编译 \[#jitting]

新增于 1.0.0

✅ Jitting 原指即时编译，但在 VBT 中，它泛指加速。Numba 仍是主要 jitter，VBT 现在也允许实现自定义 jitter 类，例如向量化 NumPy，甚至支持 GPU 的
[JAX](https://github.com/google/jax)。所有 jitted 函数都全局注册，因此只需一条命令，就能切换不同实现，甚至完全关闭加速。

```python title="Run different implementations of the cumulative sum"
>>> data = vbt.YFData.pull("BTC-USD", start="7 days ago")
>>> log_returns = np.log1p(data.close.pct_change())
>>> log_returns.vbt.cumsum()  # (1)
Date
2023-01-31 00:00:00+00:00    0.000000
2023-02-01 00:00:00+00:00    0.024946
2023-02-02 00:00:00+00:00    0.014271
2023-02-03 00:00:00+00:00    0.013310
2023-02-04 00:00:00+00:00    0.008288
2023-02-05 00:00:00+00:00   -0.007967
2023-02-06 00:00:00+00:00   -0.010087
Freq: D, Name: Close, dtype: float64

>>> log_returns.vbt.cumsum(jitted=False)  # (2)
Date
2023-01-31 00:00:00+00:00    0.000000
2023-02-01 00:00:00+00:00    0.024946
2023-02-02 00:00:00+00:00    0.014271
2023-02-03 00:00:00+00:00    0.013310
2023-02-04 00:00:00+00:00    0.008288
2023-02-05 00:00:00+00:00   -0.007967
2023-02-06 00:00:00+00:00   -0.010087
Freq: D, Name: Close, dtype: float64

>>> @vbt.register_jitted(task_id_or_func=vbt.nb.nancumsum_nb)  # (3)
... def nancumsum_np(arr):
...     return np.nancumsum(arr, axis=0)

>>> log_returns.vbt.cumsum(jitted="np")  # (4)
Date
2023-01-31 00:00:00+00:00    0.000000
2023-02-01 00:00:00+00:00    0.024946
2023-02-02 00:00:00+00:00    0.014271
2023-02-03 00:00:00+00:00    0.013310
2023-02-04 00:00:00+00:00    0.008288
2023-02-05 00:00:00+00:00   -0.007967
2023-02-06 00:00:00+00:00   -0.010087
Freq: D, Name: Close, dtype: float64
```

1.  使用内置 Numba 编译函数。
2.  使用内置函数，但关闭 Numba → 普通 Python → 较慢！
3.  为内置 Numba 函数注册 NumPy 版本。
4.  使用 NumPy 版本。

## 超高速滚动度量 \[#hyperfast-rolling-metrics]

新增于 1.0.0

✅ 基于收益率的滚动度量已进行极致性能优化，最高可加速 1000 倍！

```python title="Benchmark the rolling Sortino ratio"
>>> import quantstats as qs

>>> index = vbt.date_range("2020", periods=100000, freq="1min")
>>> np.random.seed(42)
>>> returns = pd.Series(np.random.normal(0, 0.001, size=len(index)), index=index)

>>> %timeit qs.stats.rolling_sortino(returns, rolling_period=10)  # (1)
2.79 s ± 24.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

>>> %timeit returns.vbt.returns.rolling_sortino_ratio(window=10)  # (2)
8.12 ms ± 199 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
```

1.  使用 QuantStats。
2.  使用 VBT。
