前面的文章主要介绍了如何在 Python 中调用简单的 C++ 函数,而本篇文章将进一步探讨如何在 Python 中使用 C++ 定义的类及其成员函数。本文将分别在 C++ 和 Python 中实现一个矩阵类,并为其编写矩阵乘法的成员函数。最后,我们将把两种实现与 NumPy 的矩阵乘法进行性能对比,以观察各自的效率差异。
矩阵类实现思路
定义一个矩阵需要三个基本要素:行数、列数以及存储矩阵元素的数据。对于矩阵操作,还需要一些基础方法,例如获取行数或列数,以及设置或读取指定位置的元素。因此,一个矩阵类可以定义如下:
# 矩阵类的伪代码
class Matrix:int rowsint colsvector<float> dataint get_row_num()int get_col_num()void set(int row, int col, float value)float get(int row, int col)
在这个矩阵实现中,我们使用一维数组来存储矩阵的数据,并通过行列索引计算对应的一维下标,从而定位到具体的元素。
矩阵乘法实现思路
这里不使用任何矩阵乘法优化算法,就使用矩阵乘法定义的算法:
设矩阵 \(A\) 为 \(m \times n\),矩阵 \(B\) 为 \(n \times p\),则矩阵 \(C = A B\)为 \(m \times p\),其中元素 \(c_{ij}\) 的计算方式为:
# 计算 C = A × B 的伪代码
# A: m × n
# B: n × p
# C: m × pfor i = 0 to m-1:for j = 0 to p-1:C[i][j] = 0for k = 0 to n-1:C[i][j] += A[i][k] * B[k][j]
C++ 矩阵类具体实现
在明确了矩阵类的设计思路后,我们首先给出矩阵类在 C++ 中的完整实现。该实现包含数据存储、元素读写、随机填充、矩阵乘法等核心功能。
#include <cstddef>
#include <cstdlib>
#include <ctime>#include <pybind11/pybind11.h>class Matrix {private:int rows, cols;std::vector<float> data;public:Matrix(int rows, int cols): rows(rows), cols(cols), data(rows * cols, 0.0f) {srand((unsigned)time(NULL));};void set(int row, int col, float value);float get(int row, int col) const;int rows_num() const;int cols_num() const;// 使用随机数填充矩阵void fill_random();Matrix dot(const Matrix &other) const;void print() const;
};
#include "matrix.h"
#include <cstdio>
#include <iostream>
#include <stdexcept>void Matrix::set(int row, int col, float value) {if ((row >= 0 && row < rows) && (col >= 0 && col < cols)) {data[row * cols + col] = value;}return;
}float Matrix::get(int row, int col) const {float value;if ((row >= 0 && row < rows) && (col >= 0 && col < cols)) {value = data[row * cols + col];}return value;
}int Matrix::rows_num() const { return rows; }int Matrix::cols_num() const { return cols; }void Matrix::fill_random() {for (float &i : data) {i = rand() / float(RAND_MAX);}
}Matrix Matrix::dot(const Matrix &other) const {if (other.rows != cols) {std::invalid_argument("Matrix dimisions not match for dot product!");}Matrix result(rows, other.cols);float sum = 0;for (int i = 0; i < rows; i++) {for (int j = 0; j < other.cols; j++) {sum = 0;for (int k = 0; k < cols; k++) {sum += this->get(i, k) * other.get(k, j);}result.set(i, j, sum);}}return result;
}void Matrix::print() const{std::cout << '[' << ' ';for (int i = 0; i < rows_num(); i++) {for (int j = 0; j < cols_num(); j++) {std::cout << get(i, j) << " ";}if (i < rows_num() - 1)std::cout << '\n' << " ";}std::cout << "]" << '\n';return;
}
使用 pybind11 将 C++ 类导出
为了使 Python 能直接调用 C++ 实现的矩阵类,我们使用 pybind11 将其封装为 Python 扩展模块。通过绑定类的方法、构造函数以及成员函数,Python 端便可像使用普通对象一样创建和操作 C++ 的 Matrix 类实例。以下代码展示如何通过 pybind11 暴露该类。
/*** 简要说明:* 该文件通过 pybind11 暴露了一个名为 "my_matrix" 的 Python 扩展模块。* 模块中注册了一个 Matrix 类(C++ 实现),用于在 Python 中创建和操作矩阵对象。** 导出的主要功能(Python 侧):* - 类名: my_matrix.Matrix* - 构造器:* Matrix(rows: int, cols: int)* 创建一个指定行列数的矩阵(初始内容由底层实现决定)。* - 实例方法(代表性说明):* set(i: int, j: int, value)* 在位置 (i, j) 设置元素值(下标约定以实现为准,通常为 0 基或 1 基,参见实现文档)。* get(i: int, j: int) -> value* 返回位置 (i, j) 的元素值。* rows_num() -> int* 返回矩阵的行数。* cols_num() -> int* 返回矩阵的列数。* fill_random()* 用随机值填充矩阵(随机数分布与范围由实现决定)。* dot(other)* 矩阵乘法(点乘/矩阵积)。若维度不匹配,应抛出异常或以实现约定处理。* print()* 将矩阵内容以可读格式输出(用于调试/展示)。**/
#include <pybind11/detail/common.h>
#include <pybind11/pybind11.h>#include "matrix.h"namespace py = pybind11;PYBIND11_MODULE(my_matrix, m){py::class_<Matrix>(m, "Matrix").def(py::init<int, int>()).def("set", &Matrix::set).def("get", &Matrix::get).def("rows_num", &Matrix::rows_num).def("cols_num", &Matrix::cols_num).def("fill_random", &Matrix::fill_random).def("dot", &Matrix::dot).def("print", &Matrix::print);
}
编写 CMakeLists.txt
要成功构建 Python 扩展模块,需要使用 CMake 对项目进行配置。CMakeLists.txt 文件主要负责指定编译选项、寻找依赖库(如 Python、pybind11),并将 C++ 源文件编译为可供 Python 导入的模块。本节给出完整的 CMake 构建脚本,确保项目可以顺利编译运行。
# 最小 CMake 版本要求
cmake_minimum_required(VERSION 3.14)# 定义项目名称为 'matrix',并指定项目使用 C++ 语言。
project(matrix LANGUAGES CXX)# 查找 Python 3 环境。
# COMPONENTS Interpreter Development: 确保找到 Python 解释器和开发头文件/库。
find_package(Python3 REQUIRED COMPONENTS Interpreter Development)# 查找 pybind11 库。
# REQUIRED: 如果找不到 pybind11,则停止配置并报错。
find_package(pybind11 REQUIRED)# 设置 C++ 语言标准为 C++11。pybind11 通常至少需要 C++11。
set(CMAKE_CXX_STANDARD 11)
# 强制要求编译器必须支持设置的 C++ 标准
set(CMAKE_CXX_STANDARD_REQUIRED True)
# 设置默认构建类型为 Release。这会启用优化选项。
set(CMAKE_BUILD_TYPE Release)
# 为 Release 构建类型设置高级优化标志 (-O3)
set(CMAKE_CXX_FLAGS_RELEASE "-O3")# 使用 pybind11 提供的便捷函数来创建 Python 扩展模块。
# 目标名: my_matrix (对应于 Python 中 import my_matrix)
# 源文件: matrix.cpp matrix_bind.cpp
# pybind11 会自动处理链接 Python 库和添加必要的包含目录。pybind11_add_module(my_matrix matrix.cpp matrix_bind.cpp)
编写 python 脚本
在 Python 脚本中,我们同样定义了一个 Matrix 类,其接口设计与 C++ 版本基本一致,用于保持对比的一致性。在主程序中,我们依次使用下列三种方式执行矩阵乘法:
- 使用 pybind11 导出的 C++ 版本 Matrix 类
- 使用 Python 纯接口实现的 Matrix 类
- 使用 NumPy 的矩阵乘法(高度优化)
通过测量三者的运行时间,便可以直观地比较不同语言、不同实现方式的执行效率。
import my_matrix
import numpy as np
import time
import randomclass Matrix:def __init__(self, rows, cols, data):self.rows = rowsself.cols = colsif data is None:self.data = [0.0] * (rows * cols)else:self.data = list(data)def index(self, row, col):return row * self.cols + coldef set(self, row, col, value):if ((row >=0 and row < self.rows) and (col >= 0 and col < self.cols)):self.data[self.index(row, col)] = valuedef get(self, row, col):if ((row >=0 and row < self.rows) and (col >= 0 and col < self.cols)):return self.data[self.index(row, col)]def fill_random(self):for i in range(self.rows * self.cols):self.data[i] = random.random()def dot(self, other):if self.cols != other.rows:raise ValueError("Matrix shapes do not match for multiplication")result = Matrix(self.rows, other.cols, None)for i in range(self.rows):for j in range(other.cols):sum = 0.0for k in range(self.cols):sum += self.get(i, k) * other.get(k, j)result.set(i, j, sum)return resultif __name__ == "__main__":a_rows = 100a_cols = b_rows = 100b_cols = 400a = my_matrix.Matrix(a_rows, a_cols)b = my_matrix.Matrix(b_rows, b_cols)a.fill_random()b.fill_random()start = time.perf_counter()c = a.dot(b)elapsed = time.perf_counter() - startprint(f"my matrix compute time: c computed in {elapsed:.6f} s")a = np.random.rand(a_rows, a_cols).astype(np.float32)b = np.random.rand(b_rows, b_cols).astype(np.float32)start = time.perf_counter()c = np.dot(a, b)elapsed = time.perf_counter() - startprint(f"numpy compute time: c computed in {elapsed:.6f} s")a = Matrix(a_rows, a_cols, None)b = Matrix(b_rows, b_cols, None)a.fill_random()b.fill_random()start = time.perf_counter()c = a.dot(b)elapsed = time.perf_counter() - startprint(f"python matrix compute time: c computed in {elapsed:.6f} s")
实验结果

在小规模矩阵乘法中,C++ 实现表现最为出色,NumPy 次之,而纯 Python 的性能最弱。随着矩阵规模增大,NumPy 的优势逐渐显现,在大规模矩阵乘法中性能最优;C++ 紧随其后;纯 Python 与二者相比则存在巨大差距。
文件结构如下:
.
├── build
│ ├── CMakeCache.txt
│ ├── CMakeFiles
│ ├── cmake_install.cmake
│ ├── Makefile
│ ├── matrix.py
│ └── my_matrix.cpython-313-aarch64-linux-gnu.so
├── CMakeLists.txt
├── extern
│ └── pybind11
├── matrix_bind.cpp
├── matrix.cpp
├── matrix.h
├── matrix.py
└── __pycache__