python 代碼使用ctypes調用C接口實現性能優化的解決方案

由於python相較於C++運行較慢,例如在DL時代,大規模的數據處理有的時候即便是多進程python也顯得捉襟見肘,所以性能優化非常重要,下面是基於ctypes的性能優化流程:

一、性能分析

第一步首先要分析代碼中哪些模塊耗時,各模塊的耗時比要有所掌握,這裏使用line-profiler工具進行分析;

安裝: pip install line-profiler

使用:

(1)不需要import;

(2)在需要分析性能的函數前面加上修飾器 "@profile",如下圖所示:

 

 

 (3)使用命令:kernprof -l xxxxx.py

-l 表示逐行分析時間,最後結果會自動寫到同目錄級下的.lprof文件中,若是要是直接可視化出來,則加上 -v 參數,效果如下:

 

 

 (上表中會有運行時間、運行次數、時間佔比等)

(4)使用命令:python -m line_profiler xxxxx.py.lprof   查看lprof文件;

二、基於ctypes的性能優化

1、作用:ctypes用來爲python提供C的接口,可以將C++實現的模塊編譯爲.dll或.so,然後在python中調用對應dll中的模塊名從而進行加速;

2、例程(目的是將cv2.imread()讀取過程放在C++實現):

(1)C++中的代碼:

#include<opencv2/opencv.hpp>
#include<stdlib.h>

#define DLLEXPORT extern "C" __declspec(dllexport)  #DLLEXPORT用於聲明將要生成到DLL中的函數

typedef struct Ret {
    int* buffer_args;
    uchar* buffer_img;
}Ret, *Ret_p;

DLLEXPORT Ret_p imread(char* img_path) {
    Ret_p ret_p = (Ret_p)malloc(sizeof(Ret));
    cv::Mat img = cv::imread(img_path);
    int img_h = img.rows;
    int img_w = img.cols;
    int img_c = img.channels();

    uchar* buffer_img = (uchar*)malloc(sizeof(uchar) * (img_h * img_w * img_c));
    ret_p->buffer_img = buffer_img;

    int* buffer_args = (int*)malloc(sizeof(int) * 3);
    memcpy(buffer_img, img.data, img_h * img_w * img_c);
    int args[3] = { img_h, img_w, img_c };
    memcpy(buffer_args, args, 3*sizeof(int));
    ret_p->buffer_args = buffer_args;
    return ret_p;
}

DLLEXPORT void release(uchar* data) {
    free(data);
}

 

由上面代碼可知:C++中實現模塊功能獲得輸出後,將輸出存儲到內存中,然後python調用該內存即可。

 

設置爲生成.dll即可。

(2)python中代碼:

import os
import cv2
import ctypes
import numpy as np

c_uint8_p = ctypes.POINTER(ctypes.c_ubyte)
c_int_p = ctypes.POINTER(ctypes.c_int) #ctypes沒有定義c_int_p,因此需要自己構造
class Ret_p(ctypes.Structure):
    _fields_ = [("args", c_int_p),
                ("img_p", c_uint8_p*1)]

def main():
    template_h, template_w, template_c = 750, 840, 3
    src_img_path = "./template.jpg"
    dll = ctypes.WinDLL(r"./match.dll") #指定dll文件,後面將會調用這個dll中的函數
    src_img_path = ctypes.c_char_p(bytes(src_img_path, "utf-8")) #輸入端:將python變量轉換爲ctypes類型變量從而適配C++端的輸入
    dll.imread.restype = ctypes.POINTER(Ret_p) #輸出端:設置ctypes類型變量
    pointer = dll.imread(src_img_path)  #調用dll中的imread函數,返回的是ctypes中的指針,指向自定義的結構體
    
    ret_args = np.asarray(np.fromiter(pointer.contents.args, dtype=np.int, count=3))  #np.fromiter從迭代器中獲取數據,這裏即可以從指針指向的內存區域中迭代去值
    
    print(ret_args)
    img = np.asarray(np.fromiter(pointer.contents.img_p, dtype=np.uint8, count=template_h*template_w*template_c))
    img = img.reshape((template_h, template_w, template_c))
    cv2.imwrite("./1.jpg", img)
   dll.release(img) #內存釋放
if __name__ == "__main__": main()

 

 要注意:在使用ctypes時會涉及到 C++、ctypes 和 python 三方的數據轉換,對應關係參考:https://blog.csdn.net/wchoclate/article/details/11684905

3、其他:

(1)做了一個實驗,在實際測試中發現,原生cv2.imread()要由於這種ctypes的調用方式,因爲C++實現了讀取操作後(將圖片數據保存爲一維數組到內存中),但是還要在python中使用np.fromiter從內存中讀取並reshape,這步非常耗時(涉及到多次迭代的內存讀取開銷)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章