日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

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

發布時間:2023/11/23 python 44 coder
生活随笔 收集整理的這篇文章主要介紹了 【教程】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

總結

以上是生活随笔為你收集整理的【教程】cpp转python Nanobind 实践 加速轻量版 pythonbind11的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。