欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python代碼一鍵轉(zhuǎn)Jar包及Java調(diào)用Python新姿勢

 更新時間:2020年03月10日 10:06:54   作者:軒轅之風(fēng)  
這篇文章主要介紹了Python一鍵轉(zhuǎn)Jar包,Java調(diào)用Python新姿勢,本文通過截圖實例給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下

需求背景

進擊的Python

隨著人工智能的興起,Python這門曾經(jīng)小眾的編程語言可謂是煥發(fā)了第二春。


以tensorflow、pytorch等為主的機器學(xué)習(xí)/深度學(xué)習(xí)的開發(fā)框架大行其道,助推了python這門曾經(jīng)以爬蟲見長(python粉別生氣)的編程語言在TIOBE編程語言排行榜上一路披荊斬棘,坐上前三甲的寶座,僅次于Java和C,將C++、JavaScript、PHP、C#等一眾勁敵斬落馬下。



當(dāng)然,軒轅君向來是不提倡編程語言之間的競爭對比,每一門語言都有自己的優(yōu)勢和劣勢,有自己應(yīng)用的領(lǐng)域。
另一方面,TIOBE統(tǒng)計的數(shù)據(jù)也不能代表國內(nèi)的實際情況,上面的例子只是側(cè)面反映了Python這門語言如今的流行程度。

Java 還是 Python

說回咱們的需求上來,如今在不少的企業(yè)中,同時存在Python研發(fā)團隊和Java研發(fā)團隊,Python團隊負責(zé)人工智能算法開發(fā),而Java團隊負責(zé)算法工程化,將算法能力通過工程化包裝提供接口給更上層的應(yīng)用使用。

可能大家要問了,為什么不直接用Java做AI開發(fā)呢?要弄兩個團隊。其實,現(xiàn)在包括TensorFlow在內(nèi)的框架都逐漸開始支持Java平臺,用Java做AI開發(fā)也不是不行(軒轅君的前同事就已經(jīng)在這樣做了),但限于歷史原因,做AI開發(fā)的人本就不多,而這一些人絕大部分都是Python技術(shù)棧入坑,Python的AI開發(fā)生態(tài)已經(jīng)建設(shè)的相對完善,所以造成了在很多公司中算法團隊和工程化團隊使用不同的語言。

現(xiàn)在該拋出本文的重要問題:Java工程化團隊如何調(diào)用Python的算法能力?

答案基本上只有一個:Python通過Django/Flask等框架啟動一個Web服務(wù),Java中通過Restful API與之進行交互

上面的方式的確可以解決問題,但隨之而來的就是性能問題。尤其是在用戶量上升后,大量并發(fā)接口訪問下,通過網(wǎng)絡(luò)訪問和Python的代碼執(zhí)行速度將成為拖累整個項目的瓶頸。

當(dāng)然,不差錢的公司可以用硬件堆出性能,一個不行,那就多部署幾個Python Web服務(wù)。

那除此之外,有沒有更實惠的解決方案呢?這就是這篇文章要討論的問題。

給Python加速

尋找方向

上面的性能瓶頸中,拖累執(zhí)行速度的原因主要有兩個:

  • 通過網(wǎng)絡(luò)訪問,不如直接調(diào)用內(nèi)部模塊快
  • Python是解釋執(zhí)行,快不起來

眾所周知,Python是一門解釋型腳本語言,一般來說,在執(zhí)行速度上:

解釋型語言 < 中間字節(jié)碼語言 < 本地編譯型語言

自然而然,我們要努力的方向也就有兩個:

  • 能否不通過網(wǎng)絡(luò)訪問,直接本地調(diào)用
  • Python不要解釋執(zhí)行

結(jié)合上面的兩個點,我們的目標(biāo)也清晰起來:

將Python代碼轉(zhuǎn)換成Java可以直接本地調(diào)用的模塊

對于Java來說,能夠本地調(diào)用的有兩種:

  • Java代碼包
  • Native代碼模塊

其實我們通常所說的Python指的是CPython,也就是由C語言開發(fā)的解釋器來解釋執(zhí)行。而除此之外,除了C語言,不少其他編程語言也能夠按照Python的語言規(guī)范開發(fā)出虛擬機來解釋執(zhí)行Python腳本:

  • CPython: C語言編寫的解釋器
  • Jython: Java編寫的解釋器
  • IronPython: .NET平臺的解釋器
  • PyPy: Python自己編寫的解釋器(雞生蛋,蛋生雞)

Jython?

如果能夠在JVM中直接執(zhí)行Python腳本,與Java業(yè)務(wù)代碼的交互自然是最簡單不過。但隨后的調(diào)研發(fā)現(xiàn),這條路很快就被堵死了:

  • 不支持Python3.0以上的語法
  • python源碼中若引用的第三方庫包含C語言擴展,將無法提供支持,如numpy等

這條路行不通,那還有一條:把Python代碼轉(zhuǎn)換成Native代碼塊,Java通過JNI的接口形式調(diào)用。

Python -> Native代碼

整體思路

先將Python源代碼轉(zhuǎn)換成C代碼,之后用GCC編譯C代碼為二進制模塊so/dll,接著進行一次Java Native接口封裝,使用Jar打包命令轉(zhuǎn)換成Jar包,然后Java便可以直接調(diào)用。


流程并不復(fù)雜,但要完整實現(xiàn)這個目標(biāo),有兩個關(guān)鍵問題需要解決:

1.Python代碼如何轉(zhuǎn)換成C代碼?

終于要輪到本文的主角登場了,將要用到的一個核心工具叫:Cython

請注意,這里的Cython和前面提到的CPython不是一回事。CPython狹義上是指C語言編寫的Python解釋器,是Windows、Linux下我們默認(rèn)的Python腳本解釋器。

而Cython是Python的一個第三方庫,你可以通過pip install Cython進行安裝。

官方介紹Cython是一個Python語言規(guī)范的超集,它可以將Python+C混合編碼的.pyx腳本轉(zhuǎn)換為C代碼,主要用于優(yōu)化Python腳本性能或Python調(diào)用C函數(shù)庫。

聽上去有點復(fù)雜,也有點繞,不過沒關(guān)系,get一個核心點即可:Cython能夠把Python腳本轉(zhuǎn)換成C代碼

來看一個實驗:

# FileName: test.py
def test_function():
 print("this is print from python script")

將上述代碼通過Cython轉(zhuǎn)化,生成test.c,長這個樣子:

另外添加一個main.c,在其中實現(xiàn)C語言的main函數(shù),并調(diào)用原python中的函數(shù):

extern void test_function();
int main() {
 test_function();
 return 0;
}

輸出結(jié)果:

可以正常工作!

2.轉(zhuǎn)換后的C代碼如何包裝成JNI接口使用

實際動手

1.Python源代碼

def logic(param):
 print('this is a logic function')

# 接口函數(shù),導(dǎo)出給Java Native的接口
def JNI_API_TestFunction(param):
 print("enter JNI_API_test_function")
 logic(param)
 print("leave JNI_API_test_function")

2.使用Cython工具轉(zhuǎn)換成C代碼

3.編譯生成動態(tài)庫

4.封裝為Jar包

準(zhǔn)備一個JNI調(diào)用的Interface:JNITest.java

public class JNITest {
 native boolean Java_PkgName_module_initModule( );
 native void Java_PkgName_module_uninitModule( );
 native String Java_PkgName_module_TestFunction(String param);
}

這里有3個native方法:

  • initModule: 對應(yīng)C代碼中Java_JNITest_initModule(),主要完成Python初始化
  • uninitModule: 對應(yīng)C代碼中Java_JNITest_uninitModule(),主要完成Python反初始化
  • TestFunction: 對應(yīng)C代碼中的Java_JNITest_TestFunction(),為核心業(yè)務(wù)接口

接口聲明文件+二進制動態(tài)庫文件準(zhǔn)備就緒,開始打包:

jar -cvf JNITest.jar ./JNITest

5.Java調(diào)用

關(guān)鍵問題

1.import問題

上面演示的案例只是一個單獨的py文件,而實際工作中,我們的項目通常是具有多個py文件,并且這些文件通常是構(gòu)成了復(fù)雜的目錄層級,互相之間各種import關(guān)系,錯綜復(fù)雜。

Cython這個工具有一個最大的坑在于:經(jīng)過其處理的文件代碼中會丟失代碼文件的目錄層級信息,如下圖所示,C.py轉(zhuǎn)換后的代碼和m/C.py生成的代碼沒有任何區(qū)別。


這就帶來一個非常大的問題:A.py或B.py代碼中如果有引用m目錄下的C.py模塊,目錄信息的丟失將導(dǎo)致二者在執(zhí)行import m.C時報錯,找不到對應(yīng)的模塊!

幸運的是,經(jīng)過實驗表明,在上面的圖中,如果A、B、C三個模塊處于同一級目錄下時,import能夠正確執(zhí)行。

軒轅君曾經(jīng)嘗試閱讀Cython的源代碼,并進行修改,將目錄信息進行保留,使得生成后的C代碼仍然能夠正常import,但限于時間倉促,對Python解釋器機理了解不足,在一番嘗試之后選擇了放棄。

在這個問題上卡了很久,最終選擇了一種笨辦法:將樹形的代碼層級目錄展開成為平坦的目錄結(jié)構(gòu),就上圖中的例子而言,展開后的目錄結(jié)構(gòu)變成了

A.py
B.py
m_C.py

單是這樣還不夠,還需要對A、B中引用到C的地方全部進行修正為對m_C的引用。

這看起來很簡單,但實際情況遠比這復(fù)雜,在Python中,import可不只有import這么簡單,有各種各樣復(fù)雜的形式:

import package
import module
import package.module
import module.class / function
import package.module.class / function
import package.*
import module.*
from module import *
from module import module
from package import *
from package import module
from package.module import class / function
...

除此之外,在代碼中還可能存在直接通過模塊進行引用的寫法。

展開成為平坦結(jié)構(gòu)的代價就是要處理上面所有的情況!軒轅君無奈之下只有出此下策,如果各位大佬有更好的解決方案還望不吝賜教。

2.Python GIL問題

Python轉(zhuǎn)換后的jar包開始用于實際生產(chǎn)中了,但隨后發(fā)現(xiàn)了一個問題:

每當(dāng)Java并發(fā)數(shù)一上去之后,JVM總是不定時出現(xiàn)Crash

隨后分析崩潰信息發(fā)現(xiàn),崩潰的地方正是在Native代碼中的Python轉(zhuǎn)換后的代碼中。

  • 難道是Cython的bug?
  • 轉(zhuǎn)換后的代碼有坑?
  • 還是說上面的import修正工作有問題?

崩潰的烏云籠罩在頭上許久,冷靜下來思考:
為什么測試的時候正常沒有發(fā)現(xiàn)問題,上線之后才會崩潰?

再次翻看崩潰日志,發(fā)現(xiàn)在native代碼中,發(fā)生異常的地方總是在malloc分配內(nèi)存的地方,難不成內(nèi)存被破壞了?
又敏銳的發(fā)現(xiàn)測試的時候只是完成了功能性測試,并沒有進行并發(fā)壓力測試,而發(fā)生崩潰的場景總是在多并發(fā)環(huán)境中。多線程訪問JNI接口,那Native代碼將在多個線程上下文中執(zhí)行。

猛地一個警覺:99%跟Python的GIL鎖有關(guān)系!

眾所周知,限于歷史原因,Python誕生于上世紀(jì)九十年代,彼時多線程的概念還遠遠沒有像今天這樣深入人心過,Python作為這個時代的產(chǎn)物一誕生就是一個單線程的產(chǎn)品。

雖然Python也有多線程庫,允許創(chuàng)建多個線程,但由于C語言版本的解釋器在內(nèi)存管理上并非線程安全,所以在解釋器內(nèi)部有一個非常重要的鎖在制約著Python的多線程,所以所謂多線程實際上也只是大家輪流來占坑。

原來GIL是由解釋器在進行調(diào)度管理,如今被轉(zhuǎn)成了C代碼后,誰來負責(zé)管理多線程的安全呢?

由于Python提供了一套供C語言調(diào)用的接口,允許在C程序中執(zhí)行Python腳本,于是翻看這套API的文檔,看看能否找到答案。

幸運的是,還真被我找到了:

獲取GIL鎖:


釋放GIL鎖:

在JNI調(diào)用入口需要獲得GIL鎖,接口退出時需要釋放GIL鎖。

加入GIL鎖的控制后,煩人的Crash問題終于得以解決!

測試效果

準(zhǔn)備兩份一模一樣的py文件,同樣的一個算法函數(shù),一個通過Flask Web接口訪問,(Web服務(wù)部署于本地127.0.0.1,盡可能減少網(wǎng)絡(luò)延時),另一個通過上述過程轉(zhuǎn)換成Jar包。

在Java服務(wù)中,分別調(diào)用兩個接口100次,整個測試工作進行10次,統(tǒng)計執(zhí)行耗時:


上述測試中,為進一步區(qū)分網(wǎng)絡(luò)帶來的延遲和代碼執(zhí)行本身的延遲,在算法函數(shù)的入口和出口做了計時,在Java執(zhí)行接口調(diào)用前和獲得結(jié)果的地方也做了計時,這樣可以計算出算法執(zhí)行本身的時間在整個接口調(diào)用過程中的占比。

  • 從結(jié)果可以看出,通過Web API執(zhí)行的接口訪問,算法本身執(zhí)行的時間只占到了30%+,大部分的時間用在了網(wǎng)絡(luò)開銷(數(shù)據(jù)包的收發(fā)、Flask框架的調(diào)度處理等等)。
  • 而通過JNI接口本地調(diào)用,算法的執(zhí)行時間占到了整個接口執(zhí)行時間的80%以上,而Java JNI的接口轉(zhuǎn)換過程只占用10%+的時間,有效提升了效率,減少額外時間的浪費。
  • 除此之外,單看算法本身的執(zhí)行部分,同一份代碼,轉(zhuǎn)換成Native代碼后的執(zhí)行時間在300~500μs,而CPython解釋執(zhí)行的時間則在2000~4000μs,同樣也是相差懸殊。

總結(jié)

本文提供了一種Java調(diào)用Python功能的新思路,僅供參考,其成熟度和穩(wěn)定性還有待商榷,通過HTTP Restful接口訪問仍然是跨語言對接的首選。

到此這篇關(guān)于Python代碼一鍵轉(zhuǎn)Jar包及Java調(diào)用Python新姿勢的文章就介紹到這了,更多相關(guān)Python轉(zhuǎn)Jar包內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • python 實現(xiàn)兩個變量值進行交換的n種操作

    python 實現(xiàn)兩個變量值進行交換的n種操作

    這篇文章主要介紹了python 實現(xiàn)兩個變量值進行交換的n種操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • Python進程池Pool應(yīng)用實例分析

    Python進程池Pool應(yīng)用實例分析

    這篇文章主要介紹了Python進程池Pool應(yīng)用,結(jié)合實例形式分析了Python進程池Pool功能、使用方法及相關(guān)操作注意事項,需要的朋友可以參考下
    2019-11-11
  • Python從入門到精通之多線程使用詳解

    Python從入門到精通之多線程使用詳解

    這篇文章主要介紹了Python中的多線程使用,包括創(chuàng)建線程、線程同步、線程間通信以及線程池等基本概念和技巧,文中的示例代碼講解詳細,需要的可以參考一下
    2023-07-07
  • Python爬取成語接龍類網(wǎng)站

    Python爬取成語接龍類網(wǎng)站

    在本篇文章里我們給大家分享了關(guān)于Python爬取成語接龍類網(wǎng)站的相關(guān)知識點,有需要的朋友們學(xué)習(xí)下。
    2018-10-10
  • Python迭代器與生成器及作用示例詳解

    Python迭代器與生成器及作用示例詳解

    Python生成器在內(nèi)存管理、延遲計算、生成無限序列以及提高代碼簡潔性和可讀性方面都具有重要作用,這篇文章主要介紹了Python迭代器與生成器示例詳解,需要的朋友可以參考下
    2024-02-02
  • Python matplotlib實現(xiàn)多重圖的繪制

    Python matplotlib實現(xiàn)多重圖的繪制

    Matplotlib作為Python的2D繪圖庫,它以各種硬拷貝格式和跨平臺的交互式環(huán)境生成出版質(zhì)量級別的圖形。本文將利用Matplotlib庫繪制多重圖,感興趣的可以了解一下
    2022-03-03
  • python利用線程實現(xiàn)多任務(wù)

    python利用線程實現(xiàn)多任務(wù)

    這篇文章主要介紹了python利用線程實現(xiàn)多任務(wù),幫助大家更好的理解和學(xué)習(xí)python,感興趣的朋友可以了解下
    2020-09-09
  • python配置grpc環(huán)境

    python配置grpc環(huán)境

    gRPC 是一款高性能、開源的 RPC 框架,產(chǎn)自 Google,基于 ProtoBuf 序列化協(xié)議進行開發(fā),支持多種語言(Golang、Python、Java等),本篇只介紹 Python 的 gRPC 安裝使用
    2019-01-01
  • Python?Pyperclip模塊安裝和使用詳解

    Python?Pyperclip模塊安裝和使用詳解

    Pyperclip模塊兼容python2和python3,能跨平臺使用,這篇文章主要介紹了Pyperclip模塊安裝和使用詳解,需要的朋友可以參考下
    2023-03-03
  • python中關(guān)于range()函數(shù)反向遍歷的幾種表達

    python中關(guān)于range()函數(shù)反向遍歷的幾種表達

    這篇文章主要介紹了python中關(guān)于range()函數(shù)反向遍歷的幾種表達,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-05-05

最新評論