欢迎光临殡葬网
详情描述

NumPy 通用函数(ufunc)是 NumPy 的核心特性之一,我将从设计思想、实现机制和创建自定义 ufunc 几个方面来详细解析:

1. 什么是 ufunc

ufunc(universal function)是一种对数组进行逐元素操作的函数,支持:

  • 广播机制
  • 类型自动转换(type coercion)
  • 向量化操作(在 C 级别循环,避免 Python 开销)
import numpy as np

# 内置的 ufunc 示例
arr = np.array([1, 2, 3, 4])
print(np.sqrt(arr))      # 逐元素平方根
print(np.add(arr, 5))    # 逐元素加法
print(np.multiply(arr, arr))  # 逐元素乘法

2. ufunc 的实现架构

2.1 核心数据结构

// C 层的关键结构(简化示意)
typedef struct {
    PyObject_HEAD

    // ufunc 元信息
    int nin;            // 输入参数个数
    int nout;           // 输出参数个数
    int nargs;          // nin + nout

    // 核心函数指针
    PyUFuncGenericFunction *functions;      // 类型特定的核心计算函数
    void **data;                           // 每个类型特定函数的附加数据
    int ntypes;                            // 支持的数据类型数量

    // 类型处理
    char *types;                          // 类型签名数组
    char *name;                           // ufunc 名称

    // 文档和元数据
    char *doc;

} PyUFuncObject;

2.2 执行流程

Python 调用 np.add(arr1, arr2)
    ↓
1. 参数解析和验证
    ↓
2. 广播检查(broadcasting)
    ↓
3. 类型解析(type resolution)
    ↓
4. 内存分配(输出数组)
    ↓
5. 调用 C 层循环分发器
    ↓
6. 执行类型特定的内核函数
    ↓
7. 返回结果

3. 内核函数(Kernels)的实现

3.1 简单的逐元素加法内核(C 示例)

// 双精度浮点数的加法内核
static void double_add(char **args, npy_intp *dimensions,
                      npy_intp *steps, void *extra) {
    npy_intp i, n = dimensions[0];
    char *in1 = args[0], *in2 = args[1], *out = args[2];
    npy_intp in1_step = steps[0], in2_step = steps[1], out_step = steps[2];

    for (i = 0; i < n; i++) {
        // 类型安全的指针操作
        double x = *(double *)in1;
        double y = *(double *)in2;
        *(double *)out = x + y;

        // 移动到下一个元素
        in1 += in1_step;
        in2 += in2_step;
        out += out_step;
    }
}

// 单精度浮点数版本
static void float_add(char **args, npy_intp *dimensions,
                     npy_intp *steps, void *extra) {
    // 类似实现,使用 float 类型
}

3.2 步幅(strides)处理

  • steps 数组存储每个数组的步幅(字节)
  • 支持非连续内存(转置、切片等)
  • 支持广播(某些维度的步幅为 0)

4. 创建自定义 ufunc

4.1 使用 np.frompyfunc

import numpy as np

# 创建标量函数
def my_func(x, y):
    """自定义的逐元素函数"""
    return x ** 2 + y ** 2

# 转换为 ufunc(返回 Python 对象,效率较低)
my_ufunc = np.frompyfunc(my_func, 2, 1)

arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
result = my_ufunc(arr1, arr2)  # [17, 29, 45]

4.2 使用 np.vectorize

# 提供更好的类型控制和性能优化
@np.vectorize
def my_vectorized_func(x, y):
    return np.sin(x) * np.cos(y)

# 指定签名以获得更好的性能
@np.vectorize('float64(float64, float64)', signature='()->()')
def optimized_func(x):
    return x * np.exp(-x)

4.3 使用 Numba 创建高性能 ufunc

import numba
import numpy as np

@numba.vectorize(['float64(float64, float64)',
                  'float32(float32, float32)'])
def numba_add(x, y):
    return x + y

# 编译成机器码,性能接近原生 NumPy
arr1 = np.random.randn(10000)
arr2 = np.random.randn(10000)
result = numba_add(arr1, arr2)

5. 完整示例:创建自定义 ufunc

5.1 简单的 C 扩展实现

# setup.py
from distutils.core import setup, Extension
import numpy as np

module = Extension('myufunc',
                   sources=['myufunc.c'],
                   include_dirs=[np.get_include()])

setup(ext_modules=[module])
// myufunc.c
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <Python.h>
#include <numpy/arrayobject.h>
#include <numpy/ufuncobject.h>

// 内核函数:计算 hypot = sqrt(x² + y²)
static void double_hypot(char **args, npy_intp *dimensions,
                        npy_intp *steps, void *data) {
    npy_intp i, n = dimensions[0];
    char *in1 = args[0], *in2 = args[1], *out = args[2];
    npy_intp is1 = steps[0], is2 = steps[1], os = steps[2];

    for (i = 0; i < n; i++) {
        double x = *(double *)in1;
        double y = *(double *)in2;
        *(double *)out = sqrt(x*x + y*y);
        in1 += is1; in2 += is2; out += os;
    }
}

static PyMethodDef module_methods[] = {
    {NULL, NULL, 0, NULL}
};

// 模块初始化
static struct PyModuleDef moduledef = {
    PyModuleDef_HEAD_INIT,
    "myufunc",
    NULL,
    -1,
    module_methods,
    NULL,
    NULL,
    NULL,
    NULL
};

PyMODINIT_FUNC PyInit_myufunc(void) {
    PyObject *m, *hypot_ufunc, *dtypes[2];
    PyUFuncGenericFunction hypot_funcs[1];
    void *hypot_data[1];
    char hypot_signature[3] = {NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE};

    import_array();
    import_umath();

    m = PyModule_Create(&moduledef);
    if (!m) return NULL;

    // 注册 hypot ufunc
    hypot_funcs[0] = double_hypot;
    hypot_data[0] = NULL;

    // 创建 ufunc 对象
    hypot_ufunc = PyUFunc_FromFuncAndData(
        hypot_funcs, hypot_data,
        hypot_signature, 1,  // 1个类型循环
        2, 1,                // 2个输入,1个输出
        PyUFunc_None,        // 标识
        "hypot",             // 名称
        "custom hypot function (sqrt(x² + y²))",  // 文档
        0                    // 未使用
    );

    // 添加到模块
    PyModule_AddObject(m, "hypot", hypot_ufunc);
    return m;
}

6. 性能优化技巧

6.1 避免类型检查开销

# 使用 numba 编译 ufunc
@numba.guvectorize(['void(float64[:], float64[:], float64[:])'], 
                   '(n),(n)->(n)', nopython=True)
def fast_operation(x, y, out):
    for i in range(x.shape[0]):
        out[i] = x[i] * y[i] + np.sin(x[i])

6.2 使用 SIMD 指令

// 使用 SIMD 内在函数的示例(AVX2)
#include <immintrin.h>

void avx2_add(const double *a, const double *b, double *c, int n) {
    int i;
    for (i = 0; i <= n - 4; i += 4) {
        __m256d va = _mm256_loadu_pd(a + i);
        __m256d vb = _mm256_loadu_pd(b + i);
        __m256d vc = _mm256_add_pd(va, vb);
        _mm256_storeu_pd(c + i, vc);
    }
    // 处理剩余元素
}

7. 特殊 ufunc 特性

7.1 约简(Reduction)操作

arr = np.array([[1, 2, 3], [4, 5, 6]])

# ufunc 的 reduce 方法
print(np.add.reduce(arr, axis=0))     # [5, 7, 9]
print(np.multiply.reduce(arr, axis=1)) # [6, 120]

# accumulate(累积)
print(np.add.accumulate(arr, axis=0))
# [[1, 2, 3],
#  [5, 7, 9]]

7.2 外积(Outer)操作

a = np.array([1, 2, 3])
b = np.array([4, 5, 6, 7])

result = np.multiply.outer(a, b)
# 4x3 矩阵:a[i] * b[j]

8. 调试和性能分析

import numpy as np

# 检查 ufunc 信息
ufunc = np.add
print(f"名称: {ufunc.__name__}")
print(f"文档: {ufunc.__doc__[:100]}...")
print(f"输入数: {ufunc.nin}")
print(f"输出数: {ufunc.nout}")

# 性能对比
import timeit

# Python 循环 vs ufunc
def python_sum(arr):
    result = 0
    for x in arr:
        result += x
    return result

arr = np.random.randn(1000000)

t1 = timeit.timeit(lambda: python_sum(arr), number=10)
t2 = timeit.timeit(lambda: np.sum(arr), number=10)

print(f"Python 循环: {t1:.3f}s")
print(f"NumPy ufunc: {t2:.3f}s")

总结

NumPy ufunc 的实现要点:

C 层循环:避免 Python 解释器开销 类型分发:为每种数据类型提供优化版本 内存布局抽象:通过步幅处理连续/非连续内存 广播机制:自动扩展不同形状的数组 可扩展性:支持自定义 ufunc 的添加

这种设计使得 NumPy 在科学计算中能够提供接近 C/Fortran 的性能,同时保持 Python 的易用性。对于需要极致性能的场景,可以考虑使用 Numba、Cython 或直接编写 C 扩展来创建自定义 ufunc。

相关帖子
经营贷放款规则详解,了解资金发放、受托支付与监管使用要求
经营贷放款规则详解,了解资金发放、受托支付与监管使用要求
2026年,关于试用期纠纷的仲裁与诉讼流程是否有新的变化?
2026年,关于试用期纠纷的仲裁与诉讼流程是否有新的变化?
2026年,当个人消费评价被商家恶意删除或屏蔽时,如何保障自身的评价监督权?
2026年,当个人消费评价被商家恶意删除或屏蔽时,如何保障自身的评价监督权?
孝感市殡仪服务-白事花蓝,价格透明,1小时上门
孝感市殡仪服务-白事花蓝,价格透明,1小时上门
孝感市一条龙殡葬服务-白事仪式,收费合理,快速上门
孝感市一条龙殡葬服务-白事仪式,收费合理,快速上门
在食用当季蔬果时,有哪些有效的家庭处理方法可以减少农残疑虑?
在食用当季蔬果时,有哪些有效的家庭处理方法可以减少农残疑虑?
因手机信号或系统问题导致无法按时核销,会被误判为爽约吗?
因手机信号或系统问题导致无法按时核销,会被误判为爽约吗?
2026年中考综合素质评价越来越重要,小学阶段该怎么帮孩子攒素材?
2026年中考综合素质评价越来越重要,小学阶段该怎么帮孩子攒素材?
当有意外收入时,将其转为储蓄而非消费的心理技巧有哪些?
当有意外收入时,将其转为储蓄而非消费的心理技巧有哪些?
对于刚参加工作不久的年轻人,租房提取住房公积金有哪些便利条件与渠道?
对于刚参加工作不久的年轻人,租房提取住房公积金有哪些便利条件与渠道?
近年有哪些新兴的行业或赛道获得了更多的创业政策倾斜?
近年有哪些新兴的行业或赛道获得了更多的创业政策倾斜?
过桥垫资费用如何核算?结合计费规则看懂单日收费与总成本计算
过桥垫资费用如何核算?结合计费规则看懂单日收费与总成本计算
房产抵押贷款相关法规解读,学法懂法保障抵押借贷行为合法合规
房产抵押贷款相关法规解读,学法懂法保障抵押借贷行为合法合规
台风来临前窗外突然变得安静,真的是风暴已经过去了吗?
台风来临前窗外突然变得安静,真的是风暴已经过去了吗?
押车贷款办理流程详解,签订借据时借款用途怎么填写?
押车贷款办理流程详解,签订借据时借款用途怎么填写?
大暑三候
大暑三候"腐草为萤、土润溽暑、大雨时行"该怎么理解?
解读相关法律法规对“最终解释权归本店所有”这类单方声明的具体规定
解读相关法律法规对“最终解释权归本店所有”这类单方声明的具体规定
2026年有没有更智能的个人收款码账单管理与分析工具?
2026年有没有更智能的个人收款码账单管理与分析工具?
房产证红本抵押贷款办理流程全解析,从申请到放款只需七步
房产证红本抵押贷款办理流程全解析,从申请到放款只需七步
头伏饺子二伏面三伏烙饼摊鸡蛋,这套吃法背后是哪里的民俗习惯?
头伏饺子二伏面三伏烙饼摊鸡蛋,这套吃法背后是哪里的民俗习惯?