高效计算大规模信号序列的滑动中位数:基于 Cython 的极致优化方案

梦涛大大_9888

梦涛大大_9888

2026-11-06

790人浏览

原创

高效计算大规模信号序列的滑动中位数:基于 Cython 的极致优化方案

本文介绍一种比 NumPy 原生 sliding_window_view + nanmedian 快 13 倍、比 SciPy 滑动中位数快 10 倍的高性能实现——通过手写 Cython 扩展,利用 C++ std::multiset 维护有序窗口,在 O(n log k) 时间内完成单次滑动更新,显著加速 10,000 条长度为 750 的信号处理任务。

本文介绍一种比 numpy 原生 `sliding_window_view` + `nanmedian` 快 13 倍、比 scipy 滑动中位数快 10 倍的高性能实现——通过手写 cython 扩展,利用 c++ `std::multiset` 维护有序窗口,在 o(n log k) 时间内完成单次滑动更新,显著加速 10,000 条长度为 750 的信号处理任务。

在时间序列分析、生物信号去噪或工业传感器数据预处理中,对数千条中等长度(如 750 点)信号批量计算滑动中位数是常见需求。但标准方法(如 np.lib.stride_tricks.sliding_window_view 配合 np.nanmedian)会产生大量中间数组,内存占用高、缓存不友好,导致性能瓶颈。以原文场景为例:处理 2000 条信号,每条 750 点、窗口大小 150,原方法耗时达 6.11 秒。

✅ 更优解:Cython + std::multiset 实现增量式滑动中位数

核心思想是避免重复排序——用 C++ 标准库的 multiset(底层为红黑树)动态维护当前窗口内所有元素,并始终持有一个指向中位数位置的迭代器。每次滑窗仅需:

  • 删除最左侧旧值(O(log k)),
  • 插入最右侧新值(O(log k)),
  • 调整中位数迭代器(均摊 O(1)),
  • 直接读取中位数值(O(1))。

该策略将单次窗口计算从 O(k log k) 降至 O(log k),整体复杂度由 O(n·k log k) 优化至 O(n log k),实测 median.sliding_median 函数仅需 0.20 秒(纯计算部分),较 SciPy 加速 10×,较原始 NumPy 方法加速 30×。

以下是关键 Cython 实现(median.pyx)精简说明:

# cython: language_level=3, boundscheck=False, wraparound=False
import numpy as np
from numpy cimport float64_t
from libcpp.set cimport multiset
from cython.operator cimport dereference as deref, preincrement as inc, predecrement as dec

def sliding_median(float64_t[:] arr, int64_t n):
    assert n >= 3 and arr.size >= n
    cdef int64_t i, arr_size = arr.size
    cdef float64_t[:] out = np.empty(arr_size - n + 1, dtype=np.float64)

    # 初始化 multiset 并填充首窗口
    cdef multiset[float64_t] sorted_items
    for i in range(n):
        sorted_items.insert(arr[i])

    # 定位中位数迭代器(支持偶数长度双中位平均)
    cdef int64_t mid1 = (n - 1) // 2
    cdef multiset[float64_t].iterator it = sorted_items.begin()
    for i in range(mid1): inc(it)
    out[0] = _median_value(it, n)

    # 滑动更新:删除左端、插入右端、智能调整迭代器
    for i in range(n, arr_size):
        cdef float64_t old_val = arr[i - n], new_val = arr[i]
        cdef multiset[float64_t].iterator old_it = sorted_items.find(old_val)

        # 关键:根据新旧值与当前中位数关系,预判迭代器偏移
        cdef bint delayed_inc = False, delayed_dec = False
        if old_it == it:
            inc(it)  # 避免擦除后迭代器失效
        sorted_items.erase(old_it)
        sorted_items.insert(new_val)

        if delayed_inc: inc(it)
        if delayed_dec: dec(it)
        out[i - n + 1] = _median_value(it, n)

    return np.asarray(out)

? 注意:_median_value() 是一个辅助函数,处理奇偶长度逻辑(偶数时取中间两数平均)。完整代码需配合 setup.py 编译(见原文),并确保系统已安装 Cython 和 C++ 编译器(如 GCC/Clang)。

? 集成到 DataFrame 流水线

为无缝替代原 moving_median,封装加速版函数:

import median  # 编译后的 Cython 模块

def moving_median_fast(signal, n=150):
    arr = np.asarray(signal, dtype=np.float64)
    b = median.sliding_median(arr, n)  # 0.2s 核心计算
    # 高效补齐前 n-1 个值(可进一步 Cython 化以消除 concat 开销)
    b_full = np.concatenate([np.full(n-1, np.nanmedian(arr)), b])
    return signal - b_full

# 应用到 DataFrame(推荐先转为 2D 数组以规避 list 转换开销)
df['detrended'] = df['values'].apply(moving_median_fast)

⚠️ 关键优化建议与注意事项

  • 数据结构先行:避免 DataFrame 存储 Python list。若所有信号等长(如本例均为 750),请直接使用 2D NumPy 数组(shape (2000, 750)):

    signals_2d = np.array(column_data)  # shape (2000, 750)
    # 向量化调用(需修改 Cython 支持 axis=1)
  • 消除 Python 层开销:np.concatenate([...]) 和 np.nanmedian() 在循环中调用会成为瓶颈。生产环境应:

    • 预分配输出数组;
    • 在 Cython 中直接计算首段均值并写入;
    • 使用 prange + nogil 实现多线程并行(需显式释放 GIL)。
  • 浮点精度警告:当前实现假设窗口内无重复浮点值精确等于中位数(否则 find() 可能定位错误节点)。对高精度场景,建议改用 lower_bound + 计数器增强鲁棒性。

  • 编译与部署:首次编译后生成 .so(Linux/macOS)或 .pyd(Windows)文件,可像普通模块一样导入。CI/CD 中建议缓存编译产物。

? 性能对比(i5-9600KF,Numpy 2.1.3)

方法 耗时 加速比(vs 原始)
sliding_window_view + nanmedian 6.11 s 1×
SciPy ndimage.median_filter(串行) 2.02 s 3×
ndfilters(并行,冷启动 8.6 s) 0.98 s 6×
median.sliding_median(纯 Cython) 0.20 s 30×
moving_median_fast(含 Python 封装) 0.45 s 13.6×

✅ 结论:当处理万级中等长度信号时,手写 Cython 是目前最实用的性能解。它平衡了开发成本与执行效率,且易于集成到现有 NumPy/Pandas 生态中。下一步可结合 joblib.Parallel 或 numba.prange 进一步榨干多核性能。

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

2023.07.20

1871

4

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

2023.07.25

5144

7

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.07.31

1829

3

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

2023.08.03

31057

23

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

3687

5

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

3687

5

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

1243

5

python合并两个列表
python合并两个列表

Python是一种强大的编程语言,具有许多方便的功能和工具。在Python中,有多种方法可以合并两个列表。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.10

616

4

python是前端还是后端
python是前端还是后端

Python属于前端也属于后端,其灵活性和丰富的生态系统使得开发人员能够在不同的领域中灵活运用。本专题为大家提供python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

2863

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.7万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 134.1万人学习