c 'str' - 將Python編譯為機器碼是否可行?




plt.title text (11)

py2c( http://code.google.com/p/py2c )可以將python代碼轉換為c / c ++我是py2c的獨立開發者。

將Python(可能通過中間C代表)編譯成機器代碼有多可行?

據推測,它需要鏈接到一個Python運行時庫,Python標準庫中的任何部分都需要編譯(並鏈接)。

另外,如果您想對錶達式進行動態評估,則需要捆綁Python解釋器,但也許有一部分Python不允許這樣做仍然有用。

它會提供任何速度和/或內存使用優勢嗎? 據推測,Python解釋器的啟動時間將被消除(儘管共享庫在啟動時仍然需要加載)。



正如@Greg Hewgill所說的那樣,為什麼這不總是可能的,這有很好的理由。 但是,某些類型的代碼(如非常算法代碼)可以轉化為“真實”機器代碼。

有幾種選擇:

  • 使用Psyco ,它動態地發出機器碼。 不過,您應該仔細選擇要轉換的方法/函數。
  • 使用Cython ,這是一種Python編譯成Python C擴展的語言
  • 使用PyPy ,它有一個來自RPython(Python的一個受限制的子集 ,不支持Python的某些最“動態”功能)的轉換器到C或LLVM。
    • PyPy仍然是高度實驗性的
    • 不是所有的擴展都會出現

之後,您可以使用其中一個現有軟件包(freeze,Py2exe,PyInstaller)將所有內容放入一個二進製文件中。

總而言之:你的問題沒有一般的答案。 如果您的Python代碼對性能至關重要,請盡量使用盡可能多的內置功能(或者詢問“如何更快地創建我的Python代碼”問題)。 如果這沒有幫助,請嘗試識別代碼並將其移植到C(或Cython)並使用擴展名。


Jython有一個針對JVM字節碼的編譯器。 字節碼是完全動態的,就像Python語言本身一樣! 很酷。 (是的,正如Greg Hewgill的回答所暗示的,字節碼確實使用了Jython運行時,所以Jython jar文件必須隨應用程序一起分發。)


這乍看起來似乎是合理的,但是Python中有很多普通的東西,它們不能直接映射到C表示,而沒有承載大量的Python運行時支持。 例如,鴨子打字想到。 Python中許多讀取輸入的函數只要支持某些操作,就可以採用文件或文件類對象,例如。 read()或readline()。 如果您考慮將這種類型的支持映射到C需要花費多少時間,您就開始想像Python運行時系統已經完成的各種事情。

有一些實用工具,例如py2exe ,可以將Python程序和運行時綁定到單個可執行文件中(盡可能)。


答案是“是的,這是可能的”。 您可以使用Python代碼並嘗試使用CPython API將其編譯為等效的C代碼。 事實上,過去有一個Python2C項目就是這麼做的,但是我多年以來都沒有聽說過它(回到Python 1.5天是我上次看到它的時候)。

您可以嘗試盡可能將Python代碼翻譯為本機C,並在需要實際的Python功能時回退到CPython API。 上個月我一直在玩這個想法。 然而,這是一項非常多的工作,並且大量的Python特性很難轉化為C:嵌套函數,生成器,除了簡單類的簡單類之外的任何東西,涉及從模塊外部修改模塊全局變量的任何內容等等。等等


嘗試ShedSkin Python-to-C ++編譯器,但它遠非完美。 如果只需要加速,還有Psyco - Python JIT。 但恕我直言,這是不值得的努力。 對於速度至關重要的代碼部分,最好的解決方案是將它們編寫為C / C ++擴展。


Nuitka是一個Python到C ++編譯器,可以鏈接到libpython。 這似乎是一個相對較新的項目。 作者聲稱在pystone基准上比CPython 提高速度


這不會將Python編譯為機器碼。 但是允許創建一個共享庫來調用Python代碼。

如果你正在尋找的是一個簡單的方法來從C運行Python代碼而不依賴於execp的東西。 您可以從Python代碼生成一個共享庫,並通過對Python嵌入API的幾次調用進行包裝。 那麼這個應用程序就是一個共享庫,你可以在許多其他的庫/應用程序中使用它。

這裡有一個簡單的例子,它可以創建一個共享庫,您可以鏈接到一個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 astroid for Python ast分析器。


PyPy是一個在Python中重新實現Python的項目,使用編譯為本地代碼作為實現策略之一(其他人是使用JIT的VM,使用JVM等)。 他們編譯的C版本平均運行速度比CPython慢​​,但對於某些程序要快得多。

Shedskin是一個實驗性的Python-to-C ++編譯器。

Pyrex是專門為編寫Python擴展模塊而設計的語言。 它旨在彌補Python的漂亮,高級,易用的世界與凌亂的低級C世界之間的差距。


Erlang的實現有一些問題。 作為以下的基準,我未測量的Erlang程序執行時間為47.6秒,而C代碼為12.7秒。

如果你想運行計算密集的Erlang代碼,你應該做的第一件事就是使用本地代碼。 用erlc +native euler12編譯時間縮短到41.3秒。 然而,這種代碼的本機編譯速度比預期的要低得多(只有15%),問題在於你使用了-compile(export_all) 。 這對實驗很有用,但所有函數都可以從外部訪問的事實導致本機編譯器非常保守。 (正常的BEAM模擬器沒有太大的影響。)用-export([solve/0]).替換這個聲明-export([solve/0]). 提供了更好的加速:31.5秒(距基線近35%)。

但是代碼本身存在一個問題:對於factorCount循環中的每次迭代 ,執行此測試:

factorCount (_, Sqrt, Candidate, Count) when Candidate == Sqrt -> Count + 1;

C代碼不會這樣做。 一般來說,在相同代碼的不同實現之間進行公平比較是非常棘手的,特別是如果算法是數值計算的,因為您需要確定它們實際上是在做同樣的事情。 在一個實現中由於某種類型的某種類型轉換而導致的輕微舍入錯誤可能會導致它執行比其他更多的迭代,即使兩者最終都達到相同的結果。

為了消除這個可能的錯誤源(並且在每次迭代中擺脫額外的測試),我重寫了factorCount函數,如下所示,精確地模擬C代碼:

factorCount (N) ->
    Sqrt = math:sqrt (N),
    ISqrt = trunc(Sqrt),
    if ISqrt == Sqrt -> factorCount (N, ISqrt, 1, -1);
       true          -> factorCount (N, ISqrt, 1, 0)
    end.

factorCount (_N, ISqrt, Candidate, Count) when Candidate > ISqrt -> Count;
factorCount ( N, ISqrt, Candidate, Count) ->
    case N rem Candidate of
        0 -> factorCount (N, ISqrt, Candidate + 1, Count + 2);
        _ -> factorCount (N, ISqrt, Candidate + 1, Count)
    end.

這個重寫,沒有export_all和本地編譯,給了我下面的運行時間:

$ erlc +native euler12.erl
$ time erl -noshell -s euler12 solve
842161320

real    0m19.468s
user    0m19.450s
sys 0m0.010s

這與C代碼相比並不算太壞:

$ time ./a.out 
842161320

real    0m12.755s
user    0m12.730s
sys 0m0.020s

考慮到Erlang完全不適合編寫數字代碼,在這樣的程序中只比C慢50%,這是相當不錯的。

最後,關於你的問題:

問題1:由於使用了任意長度的整數,Erlang,python和haskell的速度是否會減慢,或者只要值小於MAXINT,它們是否會變慢?

是的,有點。 在Erlang中,沒有辦法說“使用32/64位算術進行換行”,所以除非編譯器能夠證明整數(通常不能),否則它必須檢查所有計算才能看到如果它們可以放在單個標記的單詞中,或者它們必須將它們變成堆分配的bignums。 即使在運行時實際上沒有使用過bignums,這些檢查也必須執行。 另一方面,這意味著你知道如果突然給它比以前更大的輸入,算法將永遠不會失敗,因為意外的整數迴繞。

問題4:我的功能實現是否允許LCO,從而避免在調用堆棧中添加不必要的幀?

是的,您的Erlang代碼在上次呼叫優化方面是正確的。





python c linker compilation