【教程】cpp转python Nanobind 实践 加速轻量版 pythonbind11

主要是尝试一下把c++这边的函数封装打包给python用,选择nanobind的原因是:1. 优化速度快,2. 生成二进制包小,不过pythonbind11是更为广泛知道的,nanobind也是pythonbind11作者后续做的,可以查看作者写的 why another binding libaray?

总结一下就是:nanobind 同样是一个用于创建 C++ 和 Python 之间绑定的工具,它的目标是简化和加速绑定生成过程。与 pybind11 相比,nanobind 的不同之处在于它专注于 较小的 C++ 子集,提供更高效的内部数据结构和性能优化,并引入了一些便利性和质量改进的新功能。

参考资料:

  1. official code: https://github.com/wjakob/nanobind
  2. official docs: https://nanobind.readthedocs.io/en/latest/
  3. 非常简单的示例:https://github.com/wjakob/nanobind_example/tree/master
  4. 本篇博文的示例代码:dztimer (which 耗时 小张同学 3小时找bug)

1. 安装 Install & 查询 Find

注意不同的安装方式在 cmakelist.txt 的写法会不一样,下面会分别举例:

# 1. pip install
python -m pip install nanobind
# 2. conda install
conda install -c conda-forge nanobind
# 3. from source
git submodule add https://github.com/wjakob/nanobind ext/nanobind
git submodule update --init --recursive

那么对应 如果是 1/2 方案则需要走到Python executable去寻找

# Detect the installed nanobind package and import it into CMake
execute_process(
  COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
  OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE NB_DIR)
list(APPEND CMAKE_PREFIX_PATH "${NB_DIR}")
find_package(nanobind CONFIG REQUIRED)

第三方案则是直接定位到那个clone下来的repo

add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/ext/nanobind)

2. 初步尝试

此次直接copy的官方文档的方案进行快速尝试

两个文件即可:

  1. 新建一个my_ext.cpp

    #include <nanobind/nanobind.h>
    
    int add(int a, int b) { return a + b; }
    
    NB_MODULE(my_ext, m) {
        m.def("add", &add);
    }
    
  2. 新建一个CMakeLists.txt (注意因为我直接pip install的所以选用的是方案一进行的nanobind的查找)

    project(my_project) # Replace 'my_project' with the name of your project
    cmake_minimum_required(VERSION 3.15...3.27)
    find_package(Python 3.8 COMPONENTS Interpreter Development.Module REQUIRED)
    
    if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
      set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
      set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
    endif()
    
    # Detect the installed nanobind package and import it into CMake
    execute_process(
      COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
      OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE NB_DIR)
    list(APPEND CMAKE_PREFIX_PATH "${NB_DIR}")
    find_package(nanobind CONFIG REQUIRED)
    
    nanobind_add_module(my_ext my_ext.cpp)
    
  3. 搞定,就在此文件夹目录下 终端输入:

    cmake -S . -B build
    cmake --build build
    
  4. 运行

    cd build
    python3
    
    Python 3.11.1 (main, Dec 23 2022, 09:28:24) [Clang 14.0.0 (clang-1400.0.29.202)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    import my_ext
    my_ext.add(1, 2)
    3
    

至此你完成了一个cpp转python的(感觉这里好像没啥好解释的,看起来就非常易懂,但是!自己操作起来就是另一回事了 欢迎尝试:

3. 复杂尝试

如果感兴趣可以自己先开始干,这是Daniel在ufomap里写的一个timer.hpp文件 [请遵循许可证使用],内含一个巨好用的 Timer 类,一应俱全,正常C++的使用方式是

#include "timing.hpp"

Timing timing;
timing.start("Total");
timing[0].start("One Scan Cost");
// do something.
std::cout<<timing[0].lastSeconds()<<std::endl;
timing[0].stop();

timing[6].start("Write");
// do write file function
timing[6].stop();
timing.print("MyTest" /*title*/, true /*color*/, true /*bold*/);

源码 click here: timing.hpp

那么我们同样想在python里使用这个类,需要用pythonbind11或者nanobind进行搭桥,假设我们python的使用要求如下:

import dztimer
from time import sleep

if __name__ == '__main__':
    timer = dztimer.Timing()
    print(timer)
    timer.start("Total")
    timer[0].start("One Scan Cost")
    for i in range(5):
        sleep(0.05 + i * 0.01)
    timer[0].stop()
    
    for i in range(5):
        timer[1].start("Second Scan Cost")
        sleep(0.08 + i * 0.01)
        timer[1].stop()
    timer.print(title="MyTest", random_colors=True, bold=True)

—— 华丽的分割线 ——

以下为答案部分(不唯一)

#include "timer.hpp"
#include "nanobind/nanobind.h"
#include <nanobind/stl/string.h>

NB_MODULE(dztimer, m) {
    nanobind::class_<Timer>(m, "Timer")
        .def("start", static_cast<void (Timer::*)()>(&Timer::start))
        .def("stop", &Timer::stop);

    nanobind::class_<Timing, Timer>(m, "Timing")
        .def(nanobind::init<>())
        .def(nanobind::init<const std::string &>())
        .def("start", static_cast<void (Timing::*)(const std::string &)>(&Timing::start))
        .def("start", static_cast<void (Timing::*)(const std::string &, const std::string &)>(&Timing::start))
        .def("__getitem__", static_cast<Timing& (Timing::*)(std::size_t)>(&Timing::operator[]), nanobind::rv_policy::reference)
        .def("print", &Timing::print, nanobind::arg("title")="Default", nanobind::arg("random_colors")=false, nanobind::arg("bold")=false, 
             nanobind::arg("group_colors_level")=std::numeric_limits<std::size_t>::max(), nanobind::arg("precision")=4);
}

接下来开始从 小张 遇到的一个个bug开始讲起:

Class parent children

如果你要使用的对象是从父类里继承的,那么!分类也要在nanobind里申明!! 这就是为什么小张同学直接call stop的时候 说找不到,所以需要也把父类expose出来

string

#include <nanobind/stl/string.h>

这个是报错,然后一脸懵逼 直到chatgpt也无能为力 让我试试最简单的例子,也就是print hello 才发现原来是…. 头文件没加,也就是说如果你的输入参数有std::string 类型 你应该要带上这个头文件 不然会运行报错如下:

Invoked with types: nanobind_example.Timer, str

然而还是自己看文档这个部分发现不同 无意看到想着加一个试一下 然后就好了…

更多std::的其他函数可能也有这个需求 可以看官方文档的这个include 获取:https://github.com/wjakob/nanobind/tree/master/include/nanobind/stl

[] 操作符重载

其实其他的都有 唯独这个没有,后面才知道原来不需要在python里重载这个 而是用get_item去做这件事,对应的issue还是从pythonbind11里找到解答的:https://github.com/pybind/pybind11/issues/2702

所以最后才写成了

.def("__getitem__", static_cast<Timing& (Timing::*)(std::size_t)>(&Timing::operator[]), nanobind::rv_policy::reference)

Ownership

也就是getitem的时候 之前直接这样写的:

.def("__getitem__", static_cast<Timing& (Timing::*)(std::size_t)>(&Timing::operator[]))

but 不报错 但是结果是错的,也只能给出Total的结果,所以很难找到原因,只能求助chatgpt,然后给了一个不存在的方案 但是灵机一动 搜一下最后的nanobind::return_value_policy::reference refernce,发现其实差一点点 他就对了(可能是因为pythonbind11训练样本更多吧

.def("__getitem__", static_cast<Timing& (Timing::*)(std::size_t)>(&Timing::operator[]), nanobind::return_value_policy::reference)

也就是在nanobind的 **ownership章节,提到了类似的:**

Data data; // This is a global variable

m.def("get_data", []{ return &data; }, nb::rv_policy::reference)

所以一修改 哦吼就好了!

4. 本地安装 与 本地上传 pip

本地的话,建议看一下 dztimer repo 所有的代码,以便漏掉了某个环节

首先是本地可能需要venv去隔离开环境,比如提示我先安装这个py3.8环境:

sudo apt install python3.8-venv

然后在对应 dztimer 文件目录下运行:

python3 -m pip install --upgrade build
python3 -m build

打印信息如下,也就是你得到了一个dist文件夹下有库的二进制包了

接下来是了解怎么从本地push上去。管理pip install的是pypi这个组织,然后旗下可以先走testpypi

步骤是:1. 注册账号,2. 验证邮箱,3. 转到api-tokens创建API,4. 设置为整个账户,5. 保存到本机上方便上传

接下来,我们需要上传我们的release。为此,我们必须使用上传工具来上传我们的包。PyPI 官方上传工具是twine,所以让我们安装 twine 并在该dist/目录下上传我们的发行版档案。

拿到API token后 set up your $HOME/.pypirc file like this:

[testpypi]
  username = __token__
  password = pypi-AgENd???

然后文件目录下终端输入:

python3 -m pip install --upgrade twine
python3 -m twine upload --repository testpypi dist/*

然后就是提交上去啦 如下就可以看到公开的一个link

现在可以换个环境下载一下这个包进行使用:

python3 -m pip install --index-url https://test.pypi.org/simple/ --no-deps dztimer

但是需要注意的是 你可以看到编译的只有当前环境的py38和 manylinux,所以还需要走到下一步 也就是使用github action的功能来对所有系统 所有版本进行编译并上传

5. 自动提交到pip库内

这样的话 你的包就可以直接pip install 了!想想都觉得成就感(当然 是有意义的哈 别push了一堆example hahaha)【reference link】

但是通常来说我们会使用github去自动完成这个步骤,那么需要了解:

  • github repo下的 secrets 添加
  • github action的工作流程 官方文档
- name: Publish package to TestPyPI
  uses: pypa/gh-action-pypi-publish@release/v1
  with:
    password: ${{ secrets.TEST_PYPI_API_TOKEN }}
    repository-url: https://test.pypi.org/legacy/
  • example 参考文档

主要注意的是多平台的支持就需要满足很多coding的严格限制,比如 写这篇的时候 ubuntu迅速通过并push 然而多平台 window macos一直报错

  1. C++版本要约束好在CMakeLists.txt,特别是使用的库是17的新功能
  2. 头文件不要拉,比如 array 头文件在ubuntu落了不报错 正常运行 但是其他两个就不行
  3. 模板类的一系列都要指定好 不能想着让系统自己figure out

以上,更多可以看 https://github.com/KTH-RPL/dztimer 这个repo的心路历程… commit都没删

TestPyPI是一个PyPI的分支类似于大家专门先在这里测试一下 然后确认好了 再走到PyPI,两者唯一的区别就是你token给哪个平台的 其余都是一模一样的操作

finally 欢迎大家star 并尝试 python dztimer 希望是一个好用的timing方案


赠人点赞 手有余香 😆;正向回馈 才能更好开放记录 hhh

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/173765.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

多项式求和

题目描述 给定程序中 fun 函数的功能是&#xff1a;求出以下分数序列的前 n 项之和&#xff0c;并通过函数值返回 main 函数。 输入格式 输入参数。 输出格式 计算公式返回的结果。 输入输出样例 输入1 5 输出1 8.391667 python解&#xff1a; def fun(n):a1b2s0for…

【数据结构】HashMap 和 HashSet

目录 1.哈希表概念 2冲突 2.1概念 2.2 冲突-避免 2.3冲突-避免-哈希函数设计 2.4 冲突-避免-负载因子调节 ​编辑 2.5 冲突-解决-开散列/哈希桶 2.5冲突严重时的解决办法 3.实现 4.性能分析 5.与Java集合类的关系 1.哈希表概念 在顺序结构中&#xff0c;元素关键码和存…

风电场数字孪生-升压站BIM三维模型-obj格式

简介&#xff1a; 风电场中的升压站三维模型&#xff0c;obj格式&#xff0c;采用BIM技术建模&#xff0c;可应用于风电场三维数字孪生领域&#xff0c;用于对升压站进行漫游浏览&#xff1b;三维可视化场景应用&#xff1b;风电场三维设计模型。 下载地址 风电场数字孪生-升…

2019ICPC南京站

A A Hard Problem 题意&#xff1a;给定一个正整数 n &#xff0c;你需要找出最小整数 k&#xff0c;满足&#xff1a;从{1,2,⋯,n}中任意选择长度为k的子集&#xff0c;存在两个不同的整数 u,v∈T, 且 u 是 v 的因数。 思路&#xff1a;打表找规律 #include <bits/std…

微服务实战系列之加密RSA

前言 在这个时代&#xff0c;我们选择的人生目标已丰富多彩&#xff0c;秉持的人生态度也千差万别&#xff1a; 除了吃喝玩乐&#xff0c;还有科技探索&#xff1b; 除了CityWalk&#xff0c;还有“BookWalk”&#xff1b; 除了走遍中国&#xff0c;还有走遍世界&#xff1b; …

源码安装Apache

一、下载Apache,源码安装Apache #下载 [rootlocalhost opt]# wget -c https://mirrors.aliyun.com/apache/httpd/httpd-2.4.58.tar.gz [rootlocalhost opt]# ls httpd-2.4.58.tar.gz [rootlocalhost opt]# tar -xf httpd-2.4.58.tar.gz [rootlocalhost opt]# ls httpd-2.4.58…

怎么让NetCore接口支持Json参数

项目&#xff1a;NetCore Web API 接口支持Json参数需要安装Newtonsoft.Json.Linq和Microsoft.AspNetCore.Mvc.NewtonsoftJson Program代码 //支持json需要安装Microsoft.AspNetCore.Mvc.NewtonsoftJson using Newtonsoft.Json.Serialization;var builder WebApplication.Cr…

机器学习8:在病马数据集上进行算法比较(ROC曲线与AUC)

ROC曲线与AUC。使用不同的迭代次数&#xff08;基模型数量&#xff09;进行 Adaboost 模型训练&#xff0c;并记录每个模型的真阳性率和假阳性率&#xff0c;并绘制每个模型对应的 ROC 曲线&#xff0c;比较模型性能&#xff0c;输出 AUC 值最高的模型的迭代次数和 ROC 曲线。 …

力扣1038. 从二叉搜索树到更大和树(java,树的中序遍历解法)

Problem: 1038. 从二叉搜索树到更大和树 文章目录 题目描述思路解题方法复杂度Code 题目描述 给定一个二叉搜索树 root (BST)&#xff0c;请将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。 提醒一下&#xff0c; 二叉搜索树 满足下列约束条件&#xff…

ssh远程连接不了虚拟机ubuntu

直奔主题 1. 确保linux安装了ssh2.查看网络适配器是否启用3.连接成功 1. 确保linux安装了ssh sudo apt-get install openssh-server2.查看网络适配器是否启用 3.连接成功

高德地图点击搜索触发输入提示

减少调用次数&#xff0c;不用每输入一次调用一次&#xff0c;输入完后再触发搜索 效果图&#xff1a; ![Alt](https://img-home.csdnimg.cn/images/20220524100510.png dom结构 <div class"seach"><van-searchshow-actionv-model"addressVal"…

CentOS用nginx搭建文件下载服务器

Nginx 是开源、高性能、高可靠的 Web 和反向代理服务器&#xff0c;而且支持热部署&#xff0c;几乎可以做到 7 * 24 小时不间断运行&#xff0c;即使运行几个月也不需要重新启动。在工作中&#xff0c;我们经常会用到需要搭建文件服务器的情况&#xff0c;这里就以在linux下搭…

debian 12 配置

1. 修改apt源 修改apt源为http版本 # 默认注释了源码镜像以提高 apt update 速度&#xff0c;如有需要可自行取消注释 deb http://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm main contrib non-free non-free-firmware # deb-src http://mirrors.tuna.tsinghua.edu.cn/d…

【SA8295P 源码分析】132 - GMSL2 协议分析 之 GPIO/SPI/I2C/UART 等通迅控制协议带宽消耗计算

【SA8295P 源码分析】132 - GMSL2 协议分析 之 GPIO/SPI/I2C/UART 等通迅控制协议带宽消耗计算 一、GPIO 透传带宽消耗计算二、SPI 通迅带宽消耗计算三、I2C 通迅带宽消耗计算四、UART 通迅带宽消耗计算系列文章汇总见:《【SA8295P 源码分析】00 - 系列文章链接汇总》 本文链接…

【经验之谈·高频PCB电路设计常见的66个问题】

文章目录 1、如何选择PCB 板材&#xff1f;2、如何避免高频干扰&#xff1f;3、在高速设计中&#xff0c;如何解决信号的完整性问题&#xff1f;4、差分布线方式是如何实现的&#xff1f;5、对于只有一个输出端的时钟信号线&#xff0c;如何实现差分布线&#xff1f;6、接收端差…

安装向量数据库milvus及其Attu

前置条件安装docker compose 在宿主机上创建文件目录 mkdir -p /home/sunyuhua/milvus/db mkdir -p /home/sunyuhua/milvus/conf mkdir -p /home/sunyuhua/milvus/etcd下载docker-compose.yml wget https://github.com/milvus-io/milvus/releases/download/v2.2.11/milvus-s…

www.testfire.nets渗透测试报告

www.testfire.nets渗透测试报告 一、测试综述 1.1.测试⽬的 通过实施针对性的渗透测试&#xff0c;发现testfire.net⽹站的安全漏洞&#xff0c;锻炼自己的渗透水平 1.2.测试范围 域名&#xff1a;www.testfire.net IP:65.61.137.117 测试时间&#xff1a; 2023年11月…

代码随想录算法训练营第23期day52|300.最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组

目录 一、300.最长递增子序列 二、674. 最长连续递增序列 三、718. 最长重复子数组 一、300.最长递增子序列 力扣题目链接 子序列是可以在不改变原有次序的情况下删除一些元素&#xff0c;需要进行二重遍历进行判断 class Solution { public:int lengthOfLIS(vector<in…

vue3的两个提示[Vue warn]: 关于组件渲染和函数外部使用

1. [Vue warn]: inject() can only be used inside setup() or functional components. 这个消息是提示我们&#xff0c;需要将引入的方法作为一个变量使用。以vue-store为例&#xff0c;如果我们按照如下的方式使用&#xff1a; import UseUserStore from ../../store/module…

平民如何体验一把大模型知识库

背景 随着openai发布的chatgpt,各界掀起大模型热. 微软、谷歌、百度、阿里等大厂纷纷拥抱人工智能, 表示人工智能将是下一个风口.确实, chatgpt的表现确实出乎大部分的意料之外,网上也不断流传出来,chatgpt未来会替换很多白领.作为一名普通的程序员,觉得非常有必要随波逐流一下…