【问题标题】:Is it feasible to compile Python to machine code?将Python编译成机器码可行吗?
【发布时间】:2010-09-13 09:41:05
【问题描述】:

将 Python(可能通过中间 C 表示)编译成机器代码有多可行?

大概它需要链接到 Python 运行时库,并且 Python 标准库的任何部分(即 Python 本身)也需要编译(和链接)。

此外,如果您想对表达式进行动态评估,则需要捆绑 Python 解释器,但也许不允许这样做的 Python 子集仍然有用。

它会提供任何速度和/或内存使用优势吗?据推测,Python 解释器的启动时间将被消除(尽管共享库仍需要在启动时加载)。

【问题讨论】:

标签: python c linker compilation


【解决方案1】:

正如@Greg Hewgill 所说,有充分的理由说明这并非总是可行的。但是,某些类型的代码(例如非常算法代码)可以变成“真正的”机器代码。

有几种选择:

  • 使用Psyco,它动态地发出机器代码。不过,您应该谨慎选择要转换的方法/函数。
  • 使用Cython,这是一种类似于 Python 的语言,被编译成 Python C 扩展
  • 使用PyPy,它具有从 RPython(Python 的一个受限子集,不支持 Python 的一些最“动态”特性)到 C 或 LLVM 的翻译器。
    • PyPy 仍处于高度实验阶段
    • 并非所有扩展都会出现

之后,您可以使用现有软件包之一(freeze、Py2exe、PyInstaller)将所有内容放入一个二进制文件中。

总而言之:您的问题没有通用答案。如果您的 Python 代码对性能至关重要,请尝试使用尽可能多的内置功能(或询问“如何使我的 Python 代码更快”的问题)。如果这没有帮助,请尝试识别代码并将其移植到 C(或 Cython)并使用扩展。

【讨论】:

  • Pypy 是 Psyco 的继承者
【解决方案2】:

尝试ShedSkin Python-to-C++ 编译器,但它远非完美。如果只需要加速,还有 Psyco - Python JIT。但恕我直言,这是不值得的努力。对于代码的速度关键部分,最好的解决方案是将它们编写为 C/C++ 扩展。

【讨论】:

  • 仅供参考,ShedSkin 放弃了 Windows 支持。
  • @sorin : 好吧,今天它支持 Windows ...code.google.com/p/shedskin/downloads/…
  • 速度方面最好的解决方案仍然是PyPy
  • shedskin 已经有大约两年时间没有​​完成任何工作了。 :(
【解决方案3】:

Nuitka 是一个链接到 libpython 的 Python 到 C++ 编译器。这似乎是一个相对较新的项目。作者在 pystone 基准测试中声称通过 CPython 获得了speed improvement

【讨论】:

    【解决方案4】:

    PyPy 是一个用 Python 重新实现 Python 的项目,使用编译为本机代码作为实现策略之一(其他是带有 JIT 的 VM,使用 JVM 等)。他们编译的 C 版本的平均运行速度比 CPython 慢,但对于某些程序来说要快得多。

    Shedskin 是一个实验性的 Python-to-C++ 编译器。

    Pyrex 是专门为编写 Python 扩展模块而设计的语言。它旨在弥合 Python 的漂亮、高级、易用的世界与杂乱、低级的 C 世界之间的鸿沟。

    【讨论】:

    • Cython 是 Pyrex 使用更广泛、开发更积极的友好分支。
    • “Python 的漂亮、高级、易于使用的世界和 C 的混乱、低级的世界”——有趣的是,我只是在想 C 和汇编程序有多“好”和简单,而 Python 生活在“杂乱”、“高级”的世界中
    【解决方案5】:

    Pyrex 是编译成 C 的 Python 语言的子集,由最初为 Python 构建 list comprehensions 的人完成。它主要是为构建包装器而开发的,但可以在更一般的环境中使用。 Cython 是 pyrex 的一个更积极维护的分支。

    【讨论】:

    • Cython 是 Pyrex 使用更广泛、开发更积极的友好分支。
    【解决方案6】:

    一些额外的参考:

    【讨论】:

      【解决方案7】:

      Jython 有一个针对 JVM 字节码的编译器。字节码是完全动态的,就像 Python 语言本身一样!很酷。 (是的,正如 Greg Hewgill 的回答所暗示的,字节码确实使用 Jython 运行时,因此 Jython jar 文件必须与您的应用程序一起分发。)

      【讨论】:

        【解决方案8】:

        Psyco 是一种即时 (JIT) 编译器:适用于 Python 的动态编译器,代码运行速度快 2-100 倍,但需要大量内存。

        简而言之:它可以更快地运行您现有的 Python 软件,而您的源代码没有任何变化,但它不会像 C 编译器那样编译为目标代码。

        【讨论】:

          【解决方案9】:

          答案是“是的,有可能”。您可以获取 Python 代码并尝试使用 CPython API 将其编译为等效的 C 代码。事实上,曾经有一个 Python2C 项目就是这样做的,但我已经很多年没有听说过它了(回到 Python 1.5 天是我最后一次看到它的时候。)

          您可以尝试将 Python 代码尽可能多地转换为原生 C,并在需要实际 Python 功能时回退到 CPython API。在过去的一两个月里,我自己一直在玩弄这个想法。然而,这是一项非常大量的工作,而且大量 Python 特性很难翻译成 C:嵌套函数、生成器、除了具有简单方法的简单类之外的任何东西,任何涉及从模块外部修改模块全局变量的东西等等等。

          【讨论】:

            【解决方案10】:

            这不会将 Python 编译为机器码。但允许创建一个共享库来调用 Python 代码。

            如果您正在寻找一种从 C 语言运行 Python 代码而不依赖 execp 的简单方法。您可以通过对Python embedding API 的几次调用包装的python 代码生成一个共享库。好吧,该应用程序是一个共享库,一个 .so 您可以在许多其他库/应用程序中使用。

            这是一个创建共享库的简单示例,您可以将其与 C 程序链接。共享库执行 Python 代码。

            要执行的python文件是pythoncalledfromc.py:

            # -*- encoding:utf-8 -*-
            # this file must be named "pythoncalledfrom.py"
            
            def main(string):  # args must a string
                print "python is called from c"
                print "string sent by «c» code is:"
                print string
                print "end of «c» code input"
                return 0xc0c4  # return something
            

            您可以使用python2 -c "import pythoncalledfromc; pythoncalledfromc.main('HELLO') 进行尝试。它将输出:

            python is called from c
            string sent by «c» code is:
            HELLO
            end of «c» code input
            

            共享库将由callpython.h定义如下:

            #ifndef CALL_PYTHON
            #define CALL_PYTHON
            
            void callpython_init(void);
            int callpython(char ** arguments);
            void callpython_finalize(void);
            
            #endif
            

            关联的callpython.c是:

            // gcc `python2.7-config --ldflags` `python2.7-config --cflags` callpython.c -lpython2.7 -shared -fPIC -o callpython.so
            
            #include <stdlib.h>
            #include <stdio.h>
            #include <string.h>
            #include <python2.7/Python.h>
            
            #include "callpython.h"
            
            #define PYTHON_EXEC_STRING_LENGTH 52
            #define PYTHON_EXEC_STRING "import pythoncalledfromc; pythoncalledfromc.main(\"%s\")"
            
            
            void callpython_init(void) {
                 Py_Initialize();
            }
            
            int callpython(char ** arguments) {
              int arguments_string_size = (int) strlen(*arguments);
              char * python_script_to_execute = malloc(arguments_string_size + PYTHON_EXEC_STRING_LENGTH);
              PyObject *__main__, *locals;
              PyObject * result = NULL;
            
              if (python_script_to_execute == NULL)
                return -1;
            
              __main__ = PyImport_AddModule("__main__");
              if (__main__ == NULL)
                return -1;
            
              locals = PyModule_GetDict(__main__);
            
              sprintf(python_script_to_execute, PYTHON_EXEC_STRING, *arguments);
              result = PyRun_String(python_script_to_execute, Py_file_input, locals, locals);
              if(result == NULL)
                return -1;
              return 0;
            }
            
            void callpython_finalize(void) {
              Py_Finalize();
            }
            

            你可以用下面的命令编译它:

            gcc `python2.7-config --ldflags` `python2.7-config --cflags` callpython.c -lpython2.7 -shared -fPIC -o callpython.so
            

            创建一个名为 callpythonfromc.c 的文件,其中包含以下内容:

            #include "callpython.h"
            
            int main(void) {
              char * example = "HELLO";
              callpython_init();
              callpython(&example);
              callpython_finalize();
              return 0;
            }
            

            编译并运行:

            gcc callpythonfromc.c callpython.so -o callpythonfromc
            PYTHONPATH=`pwd` LD_LIBRARY_PATH=`pwd` ./callpythonfromc
            

            这是一个非常基本的例子。它可以工作,但根据库的不同,将 C 数据结构序列化为 Python 以及从 Python 序列化为 C 可能仍然很困难。事情可以在某种程度上自动化......

            Nuitka 可能会有所帮助。

            还有numba,但他们都不打算完全按照您的意愿行事。从 Python 代码生成 C 标头是可能的,但前提是您指定如何将 Python 类型转换为 C 类型或可以推断该信息。有关 Python ast 分析器的信息,请参阅 python astroid

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2014-09-17
              • 2012-04-28
              • 2011-09-06
              • 2014-01-16
              • 2020-12-13
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多