当前位置: 首页 > news >正文

?a品定制网站开发设计素材网排名

?a品定制网站开发,设计素材网排名,建设局和住建局区别,sem工作内容粉丝朋友们#xff0c;不知道大家看故事看腻了没#xff08;要是没腻可一定留言告诉我^_^#xff09;#xff0c;今天这篇文章换换口味#xff0c;正经的来写写技术文。言归正传#xff0c;咱们开始吧#xff01;今天的这篇文章#xff0c;聊一个轩辕君之前工作中遇到的…粉丝朋友们不知道大家看故事看腻了没要是没腻可一定留言告诉我^_^今天这篇文章换换口味正经的来写写技术文。言归正传咱们开始吧今天的这篇文章聊一个轩辕君之前工作中遇到的需求如何在Java中调用Python代码要不要先Mark一下说不定将来哪天就用上了呢本文结构- 需求背景- 进击的 Python- Java 和 Python - 给 Python 加速- 寻找方向- Jython? - Python-Native 代码- 整体思路- 实际动手- 自动化 - 关键问题- import 的问题- Python GIL 问题 - 测试效果 - 总结需求背景进击的 Python随着人工智能的兴起Python 这门曾经小众的编程语言可谓是焕发了第二春。以 tensorflow、pytorch 等为主的机器学习深度学习的开发框架大行其道助推了 python 这门曾经以爬虫见长python 粉别生气的编程语言在 TIOBE 编程语言排行榜上一路披荆斩棘坐上前三甲的宝座仅次于 Java 和 C将 C、JavaScript、PHP、C#等一众劲敌斩落马下。当然轩辕君向来是不提倡编程语言之间的竞争对比每一门语言都有自己的优势和劣势有自己应用的领域。另一方面TIOBE 统计的数据也不能代表国内的实际情况上面的例子只是侧面反映了 Python 这门语言如今的流行程度。Java 还是 Python说回咱们的需求上来如今在不少的企业中同时存在 Python 研发团队和 Java 研发团队Python 团队负责人工智能算法开发而 Java 团队负责算法工程化将算法能力通过工程化包装提供接口给更上层的应用使用。可能大家要问了为什么不直接用 Java 做 AI 开发呢要弄两个团队。其实现在包括 TensorFlow 在内的框架都逐渐开始支持 Java 平台用 Java 做 AI 开发也不是不行其实已经有不少团队在这样做了但限于历史原因做 AI 开发的人本就不多而这一些人绝大部分都是 Python 技术栈入坑Python 的 AI 开发生态已经建设的相对完善所以造成了在很多公司中算法团队和工程化团队不得不使用不同的语言。现在该抛出本文的重要问题Java 工程化团队如何调用 Python 的算法能力答案基本上只有一个Python 通过 Django/Flask 等框架启动一个 Web 服务Java 中通过 Restful API 与之进行交互上面的方式的确可以解决问题但随之而来的就是性能问题。尤其是在用户量上升后大量并发接口访问下通过网络访问和 Python 的代码执行速度将成为拖累整个项目的瓶颈。当然不差钱的公司可以用硬件堆出性能一个不行那就多部署几个 Python Web 服务。那除此之外有没有更实惠的解决方案呢这就是这篇文章要讨论的问题。给 Python 加速寻找方向上面的性能瓶颈中拖累执行速度的原因主要有两个通过网络访问不如直接调用内部模块快Python 是解释执行快不起来众所周知Python 是一门解释型脚本语言一般来说在执行速度上解释型语言 中间字节码语言 本地编译型语言自然而然我们要努力的方向也就有两个能否不通过网络访问直接本地调用Python 不要解释执行结合上面的两个点我们的目标也清晰起来将 Python 代码转换成 Java 可以直接本地调用的模块对于 Java 来说能够本地调用的有两种Java 代码包Native 代码模块其实我们通常所说的 Python 指的是 CPython也就是由 C 语言开发的解释器来解释执行。而除此之外除了 C 语言不少其他编程语言也能够按照 Python 的语言规范开发出虚拟机来解释执行 Python 脚本CPython: C 语言编写的解释器Jython: Java 编写的解释器IronPython: .NET 平台的解释器PyPy: Python 自己编写的解释器鸡生蛋蛋生鸡)Jython?如果能够在 JVM 中直接执行 Python 脚本与 Java 业务代码的交互自然是最简单不过。但随后的调研发现这条路很快就被堵死了不支持 Python3.0 以上的语法python 源码中若引用的第三方库包含 C 语言扩展将无法提供支持如 numpy 等这条路行不通那还有一条把 Python 代码转换成 Native 代码块Java 通过 JNI 的接口形式调用。Python - Native 代码整体思路先将 Python 源代码转换成 C 代码之后用 GCC 编译 C 代码为二进制模块 so/dll接着进行一次 Java Native 接口封装使用 Jar 打包命令转换成 Jar 包然后 Java 便可以直接调用。流程并不复杂但要完整实现这个目标有一个关键问题需要解决Python 代码如何转换成 C 代码终于要轮到本文的主角登场了将要用到的一个核心工具叫Cython请注意这里的Cython和前面提到的CPython不是一回事。CPython 狭义上是指 C 语言编写的 Python 解释器是 Windows、Linux 下我们默认的 Python 脚本解释器。而 Cython 是 Python 的一个第三方库你可以通过pip install Cython进行安装。官方介绍 Cython 是一个 Python 语言规范的超集它可以将 PythonC 混合编码的.pyx 脚本转换为 C 代码主要用于优化 Python 脚本性能或 Python 调用 C 函数库。听上去有点复杂也有点绕不过没关系get 一个核心点即可Cython 能够把 Python 脚本转换成 C 代码来看一个实验# FileName: test.py def TestFunction():print(this is print from python script) 将上述代码通过 Cython 转化生成 test.c长这个样子代码非常长而且不易读这里仅截图示意。实际动手1.准备 Python 源代码# FileName: Test.py # 示例代码将输入的字符串转变为大写 def logic(param):print(this is a logic function)print(param is [%s] % param)return param.upper()# 接口函数导出给Java Native的接口 def JNI_API_TestFunction(param):print(enter JNI_API_test_function)result logic(param)print(leave JNI_API_test_function)return result 注意1这里在 python 源码中使用一种约定以JNI_API_为前缀开头的函数表示为Python代码模块要导出对外调用的接口函数这样做的目的是为了让我们的 Python 一键转 Jar 包系统能自动化识别提取哪些接口作为导出函数。注意2这一类接口函数的输入是一个 python 的 str 类型字符串输出亦然如此可便于移植以往通过JSON形式作为参数的 RESTful 接口。使用JSON的好处是可以对参数进行封装支持多种复杂的参数形式而不用重载出不同的接口函数对外调用。注意3还有一点需要说明的是在接口函数前缀JNI_API_的后面函数命名不能以 python 惯有的下划线命名法而要使用驼峰命名法注意这不是建议而是要求原因后续会提到。2.准备一个 main.c 文件这个文件的作用是对 Cython 转换生成的代码进行一次封装封装成 Java JNI 接口形式的风格以备下一步 Java 的使用。/* DO NOT EDIT THIS FILE - it is machine generated */ #include jni.h #include Python.h #include stdio.h#ifndef _Included_main #define _Included_main #ifdef __cplusplus extern C { #endif#if PY_MAJOR_VERSION 3 # define MODINIT(name) init ## name #else # define MODINIT(name) PyInit_ ## name #endif PyMODINIT_FUNC MODINIT(Test)(void);JNIEXPORT void JNICALL Java_Test_initModule (JNIEnv *env, jobject obj) {PyImport_AppendInittab(Test, MODINIT(Test));Py_Initialize();PyRun_SimpleString(import os);PyRun_SimpleString(__name__ __main__);PyRun_SimpleString(import sys);PyRun_SimpleString(sys.path.append(./));PyObject* m PyInit_Test_Test();if (!PyModule_Check(m)) {PyModuleDef *mdef (PyModuleDef *) m;PyObject *modname PyUnicode_FromString(__main__);m NULL;if (modname) {m PyModule_NewObject(modname);Py_DECREF(modname);if (m) PyModule_ExecDef(m, mdef);}}PyEval_InitThreads(); }JNIEXPORT void JNICALL Java_Test_uninitModule (JNIEnv *env, jobject obj) {Py_Finalize(); }JNIEXPORT jstring JNICALL Java_Test_testFunction (JNIEnv *env, jobject obj, jstring string) {const char* param (char*)(*env)-GetStringUTFChars(env, string, NULL);static PyObject *s_pmodule NULL;static PyObject *s_pfunc NULL;if (!s_pmodule || !s_pfunc) {s_pmodule PyImport_ImportModule(Test);s_pfunc PyObject_GetAttrString(s_pmodule, JNI_API_testFunction);}PyObject *pyRet PyObject_CallFunction(s_pfunc, s, param);(*env)-ReleaseStringUTFChars(env, string, param);if (pyRet) {jstring retJstring (*env)-NewStringUTF(env, PyUnicode_AsUTF8(pyRet));Py_DECREF(pyRet);return retJstring;} else {PyErr_Print();return (*env)-NewStringUTF(env, error);} } #ifdef __cplusplus } #endif #endif 这个文件中一共有3个函数Java_Test_initModule: python初始化工作Java_Test_uninitModule: python反初始化工作Java_Test_testFunction:真正的业务接口封装了对原来Python中定义对JNI_API_testFuncion函数的调用同时要负责JNI层面的参数jstring类型的转换。根据 JNI 接口规范native 层面的 C 函数命名需要符合如下的形式// QualifiedClassName: 全类名 // MethodName: JNI接口函数名 void JNICALL Java_QualifiedClassName_MethodName(JNIEnv*, jobject); 所以在main.c文件中对定义需要向上面这样命名,这也是为什么前面强调python接口函数命名不能用下划线这会导致JNI接口找不到对应的native函数。3.使用 Cython 工具编译生成动态库补充做一个小小的准备工作把Python源码文件的后缀从.py改成.pyxpython源代码Test.pyx和main.c文件都准备就绪接下来便是Cython登场的时候了它将会将所有pyx的文件自动转换成.c文件并结合我们自己的main.c文件内部调用gcc生成一个动态二进制库文件。Cython 的工作需要准备一个 setup.py 文件配置好转换的编译信息包括输入文件、输出文件、编译参数、包含目录、链接目录如下所示from distutils.core import setup from Cython.Build import cythonize from distutils.extension import Extensionsourcefiles [Test.pyx, main.c]extensions [Extension(libTest, sourcefiles,include_dirs[/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/include,/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/include/darwin/,/Library/Frameworks/Python.framework/Versions/3.6/include/python3.6m],library_dirs[/Library/Frameworks/Python.framework/Versions/3.6/lib/],libraries[python3.6m])]setup(ext_modulescythonize(extensions, language_level 3)) 注意这里涉及Python二进制代码的编译需要链接Python的库注意这里涉及JNI相关数据结构定义需要包含Java JNI目录setup.py文件准备就绪后便执行如下命令启动转换编译工作python3.6 setup.py build_ext --inplace 生成我们需要的动态库文件libTest.so4.准备Java JNI调用的接口文件Java业务代码使用需要定义一个接口如下所示// FileName: Test.java public class Test {public native void initModule();public native void uninitModule();public native String testFunction(String param);到这一步其实已经实现了在Java中调用的目的了注意调用业务接口之前需要先调用initModule进行native层面的Python初始化工作。import Test; public class Demo {public void main(String[] args) {System.load(libTest.so);Test tester new Test();tester.initModule();String result tester.testFunction(this is called from java);tester.uninitModule();System.out.println(result);} } 输出enter JNI_API_test_function this is a logic function param is [this is called from java] leave JNI_API_test_function THIS IS CALLED FROM JAVA! 成功实现了在Java中调用Python代码5.封装为 Jar 包做到上面这样还不能满足为了更好的使用体验我们再往前一步封装成为Jar包。首先原来的JNI接口文件需要再扩充一下加入一个静态方法loadLibrary自动实现so文件的释放和加载。// FileName: Test.java public class Test {public native void initModule();public native void uninitModule();public native String testFunction(String param);public synchronized static void loadLibrary() throws IOException {// 实现略... } 接着将上面的接口文件转换成java class文件:javac Test.java 最后准备将class文件和so文件放置于Test目录下打包jar -cvf Test.jar ./Test自动化上面5个步骤如果每次都要手动来做着实是麻烦好在我们可以编写Python脚本将这个过程完全的自动化真正做到Python一键转换Jar包限于篇幅原因这里仅仅提一下自动化过程的关键自动扫描提取python源代码中需要导出的接口函数main.c、setup.py和JNI接口java文件都需要自动化生成可以定义模板参数形式快速构建需要处理好各模块名、函数名对应关系关键问题1.import 问题上面演示的案例只是一个单独的 py 文件而实际工作中我们的项目通常是具有多个 py 文件并且这些文件通常是构成了复杂的目录层级互相之间各种 import 关系错综复杂。Cython 这个工具有一个最大的坑在于经过其处理的文件代码中会丢失代码文件的目录层级信息如下图所示C.py 转换后的代码和 m/C.py 生成的代码没有任何区别。这就带来一个非常大的问题A.py 或 B.py 代码中如果有引用 m 目录下的 C.py 模块目录信息的丢失将导致二者在执行 import m.C 时报错找不到对应的模块幸运的是经过实验表明在上面的图中如果 A、B、C 三个模块处于同一级目录下时import 能够正确执行。轩辕君曾经尝试阅读 Cython 的源代码并进行修改将目录信息进行保留使得生成后的 C 代码仍然能够正常 import但限于时间仓促对 Python 解释器机理了解不足在一番尝试之后选择了放弃。在这个问题上卡了很久最终选择了一个笨办法将树形的代码层级目录展开成为平坦的目录结构就上图中的例子而言展开后的目录结构变成了A.py B.py m_C.py 单是这样还不够还需要对 A、B 中引用到 C 的地方全部进行修正为对 m_C 的引用。这看起来很简单但实际情况远比这复杂在 Python 中import 可不只有 import 这么简单有各种各样复杂的形式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 ... 除此之外在代码中还可能存在直接通过模块进行引用的写法。展开成为平坦结构的代价就是要处理上面所有的情况轩辕君无奈之下只有出此下策如果各位大佬有更好的解决方案还望不吝赐教。2.Python GIL 问题Python 转换后的 jar 包开始用于实际生产中了但随后发现了一个问题每当 Java 并发数一上去之后JVM 总是不定时出现 Crash随后分析崩溃信息发现崩溃的地方正是在 Native 代码中的 Python 转换后的代码中。难道是 Cython 的 bug转换后的代码有坑还是说上面的 import 修正工作有问题崩溃的乌云笼罩在头上许久冷静下来思考为什么测试的时候正常没有发现问题上线之后才会崩溃再次翻看崩溃日志发现在 native 代码中发生异常的地方总是在 malloc 分配内存的地方难不成内存被破坏了又发现测试的时候只是完成了功能性测试并没有进行并发压力测试而发生崩溃的场景总是在多并发环境中。多线程访问 JNI 接口那 Native 代码将在多个线程上下文中执行。猛地一个警觉99%跟 Python 的 GIL 锁有关系众所周知限于历史原因Python 诞生于上世纪九十年代彼时多线程的概念还远远没有像今天这样深入人心过Python 作为这个时代的产物一诞生就是一个单线程的产品。虽然 Python 也有多线程库允许创建多个线程但由于 C 语言版本的解释器在内存管理上并非线程安全所以在解释器内部有一个非常重要的锁在制约着 Python 的多线程所以所谓多线程实际上也只是大家轮流来占坑。原来 GIL 是由解释器在进行调度管理如今被转成了 C 代码后谁来负责管理多线程的安全呢由于 Python 提供了一套供 C 语言调用的接口允许在 C 程序中执行 Python 脚本于是翻看这套 API 的文档看看能否找到答案。幸运的是还真被我找到了获取 GIL 锁释放 GIL 锁在 JNI 调用入口需要获得 GIL 锁接口退出时需要释放 GIL 锁。加入 GIL 锁的控制后烦人的 Crash 问题终于得以解决测试效果准备两份一模一样的 py 文件同样的一个算法函数一个通过 Flask Web 接口访问Web 服务部署于本地 127.0.0.1尽可能减少网络延时另一个通过上述过程转换成 Jar 包。在 Java 服务中分别调用两个接口 100 次整个测试工作进行 10 次统计执行耗时上述测试中为进一步区分网络带来的延迟和代码执行本身的延迟在算法函数的入口和出口做了计时在 Java 执行接口调用前和获得结果的地方也做了计时这样可以计算出算法执行本身的时间在整个接口调用过程中的占比。从结果可以看出通过 Web API 执行的接口访问算法本身执行的时间只占到了 30%大部分的时间用在了网络开销数据包的收发、Flask 框架的调度处理等等。而通过 JNI 接口本地调用算法的执行时间占到了整个接口执行时间的 80%以上而 Java JNI 的接口转换过程只占用 10%的时间有效提升了效率减少额外时间的浪费。除此之外单看算法本身的执行部分同一份代码转换成 Native 代码后的执行时间在 300~500μs而 CPython 解释执行的时间则在 2000~4000μs同样也是相差悬殊。总结本文提供了一种 Java 调用 Python 代码的新思路仅供参考其成熟度和稳定性还有待商榷通过 HTTP Restful 接口访问仍然是跨语言对接的首选。至于文中的方法感兴趣的朋友欢迎留言交流。
http://www.yutouwan.com/news/264855/

相关文章:

  • 沈阳城市建设管理学校网站网站建设属于什么税目
  • 艺术网站欣赏网站开发排期表模板
  • 做网站用asp div代码网站开发需要解决的问题
  • 网站建设排名优化公司网站主题页
  • 老域名新网站推广企业融资需要准备哪些资料
  • 做设计找图有哪些网站有哪些做摄影网站的公司
  • 模块化网站开发电商运营教程
  • 做移动网站优化排名wordpress安装服务器
  • 有没有做家居服设计师看的网站如何注册网站名称
  • 如何用oss做视频网站广东建设信息网行业服务版
  • 河南网站开发优化百度分析工具
  • qq直接登录网站无需下载无锡网站制作联系电话
  • 建设工程敎育网网站星子网易云
  • 书籍网站开发多少钱档案馆建设网站
  • 网站制作合同范本网站做直播
  • 南宁做网站服务商做网站主页上主要放哪些内容
  • 江苏省品牌专业建设网站成都市小程序推广
  • 规划排版网站爱站长尾词
  • 网站建设岗位说明重庆seo网络推广平台
  • 十大免费自助建站做试试彩网站人员
  • 网站设计风西宁做网站好的公司
  • 365建站网建设网络道德教育网站不包括
  • 贵阳网站建设价格山东建设管理局网站
  • 包小盒设计网站济南做网站最好的公司
  • 商城网站模块有记事本做简易网站
  • 奉贤做网站的wordpress iis8.5
  • 轻云服务器菁英版 多个网站深圳百度关键词
  • 哪些网站推广公司大型门户网站核心技术
  • 湖南省住建云公共服务平台郑州网站优化网络建设有限公司
  • 顺德做网站的公司汉中360网站建设