[譯] 深刻對比數據科學工具箱:Python 和 R 的 C/C++ 實現

概述

幾周前,我有幸在 Scipy 大會上發表了 Civis如何使用Python和R的演講。爲何要在一個Python大會上大談R呢?這是要挑起一個Python和R語言的一場戰爭嗎?不是的!討論哪一個語言比較好簡直是浪費時間。在 Civis,咱們很愉快地同時使用這兩種語言,不只僅是在咱們平常工做中解決數據科學問題,也用它們來寫一些其餘工具。下面是我在SciPy 大會上的一些討論。python

問題現狀

咱們 Civis 的同事有着十分不一樣的學術背景。我效力的研發團隊有一個物理學家、一個經濟學家、兩個統計學家以及一位土木工程師組成。在 Civis,每一個人在數據科學上都會作一些不一樣內容的工做,有的領域是R比較流行,有的領域是Python比較流行,還有的一些是Matlab。在這種場景下只支持一種語言並非一個明智的選擇。遷移到一門新語言上會花費許多時間,拋開在學院或者業界多年的技能回報,容許人們使用它們所熟悉的工具能夠確保你們有更高的生成效率。c++

另一個咱們同時使用兩種語言的緣由是已有的統計學工具與包。在解決數據科學問題上,咱們常常遇到在某些特定管道上須要某一種特定語言。咱們的調研管道是一個很好的例子。確保隨機樣本具備全集表明性是須要一個被稱爲 raking 的過程的,傳統上,Python 在社會科學上並不流行,所以咱們只會用R來完成這件事情。固然調查也包括了QA的全文檢索,在某種程度上來講R在NLP社區上並非很流行,所以這個部分將會由Python來完成。而分析調查數據只是咱們在Civis解決問題的一個小步驟。git

結合許多不一樣的語言來實現工做流是具備挑戰性的。數據科學平臺幫助咱們能夠提交一連串任務節點,而後交由基礎設施負責獨立調用每一個任務節點,且先後不依賴。可是這並非一個十分理想的狀況。緣由有二,其一,這樣作整個任務就顯得破碎化,稍有修改某個任務,每每會致使全局失敗,並且任何人在這個分佈式系統中所作的工做都只瞭解局部狀況,而不知道全局狀況。第二,這樣作並不高效。在兩個語言中切換一般須要將數據以特定格式加載到磁盤上(一般最壞的狀況是csv格式)。這樣不只僅是解析成本比較高,並且還會丟失一些類型信息。github

解決方案

我概述了在Civis遇到的種種問題,可是到底什麼是理想的狀態呢?最完美的解決方案是咱們能夠無縫切換工具和語言。許多熟悉Python的人喜歡用Python作數據分析,R也是相似的。事實證實這是徹底有可能實現,有至關數量的項目已經開始做爲跨語言工具:TensorFlow, XGBoost, 和 Stan 都在Civis被普遍運用。移植或安利一個已有的工具也是可能的,咱們已經成功地完成了glmnetR包的安利。segmentfault

對另外一些爲讀者寫數據科學工具的人來講,他們從一開始就考慮了這些跨語言。有一些方法能夠作到這一點,但我我的最喜歡的是用C語言來寫底層,而後使用各自的Python和R C api作一些綁定/封裝。Python和R其實是用C實現的,這是條阻力最小的路徑。C是一門古老語言,C語言社區已經演進出了一些強大的工具鏈。晦澀的編譯器錯誤消息已經成爲了過去時,GCC和Clang(最流行的編譯器)給友善的消息反饋(Clang網站能夠看到栗子)。如今還有各類各樣的「消毒液」來輔助捕獲內存泄漏等常見錯誤或未定義的行爲(llvm文檔)。api

案例

下面咱們經過一個小例子,用C編寫一個函數,使這可調用的Python和R。代碼以及幻燈片從個人GitHub上能夠找到。數組

Python

咱們將下面的Python函數轉換爲C:微信

def tally(s):
    total = 0
    for elm in s:
        total += elm
    return total

C

這是相同的功能用C實現:分佈式

#include <stddef.h>

double tally(double *s, size_t n) {
    double total = 0;
    for (size_t i = 0; i < n; i++) {
        total += s[i];
    }
    return total;
}

注意到它看起來並非都不一樣的Python函數。固然,除了有一些類型註解和額外的語法噪音大括號外,咱們還必須跟蹤數組的長度,但總體邏輯是同樣的。函數

接下來,咱們須要實現一個Python綁定,容許用戶調用這個函數就像任何其餘Python函數。

Cython

#include <stdio.h>
#include "Python.h"
#include "tally.h"

static PyObject *tally_(PyObject *self, PyObject *args) {
    PyObject *buf;
    if (!PyArg_ParseTuple(args, "O", &buf)) {
        return NULL;
    }

    Py_buffer view;
    int buf_flags = PyBUF_ANY_CONTIGUOUS | PyBUF_FORMAT;
    if (PyObject_GetBuffer(buf, &view, buf_flags) == -1) {
        return NULL;
    }
    
    if (strcmp(view.format,"d") != 0) {
        PyErr_SetString(PyExc_TypeError, "we only take floats :(");
        PyBuffer_Release(&view);
        return NULL;
    }

    double result = tally(view.buf, view.shape[0]);
    PyBuffer_Release(&view);
    return Py_BuildValue("d", result);
}

static PyMethodDef MethodTable[] = {
    {"tally", &tally_, METH_VARARGS, "Compute the sum of an array"}, 
    { NULL, NULL, 0, NULL}
};

static struct PyModuleDef tally_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "tally_",
    .m_size = -1,
    .m_methods = MethodTable
};


PyMODINIT_FUNC PyInit_tally_(void) {
    return PyModule_Create(&tally_module);
}

這裏有不少實現的方法,但大多數只是Python模塊的一部分樣板代碼。
在最上面,咱們定義了一個函數,它接受一個Python對象,而且檢查這是一個適當類型的數組,再調用咱們的計數功能,而後返回結果。其他的代碼模塊定義,告訴Python解釋器咱們計數功能的名稱和它的參數類型。

R

R的過程很是類似,可是更加簡潔:

#include <R.h>
#include <Rinternals.h>
#include <R_ext/Rdynload.h>
#include "tally.h"

SEXP tally_(SEXP x_) {
  double *x = REAL(x_);
  int n = length(x_);
  
  SEXP out = PROTECT(allocVector(REALSXP, 1));
  REAL(out)[0] = tally(x, n);
  UNPROTECT(1);
  
  return out;
}

static R_CallMethodDef callMethods[] = {
  {"tally_", (DL_FUNC)&tally_, 1},
  {NULL, NULL, 0}
};

void R_init_tallyR(DllInfo *info) {
  R_registerRoutines(info, NULL, callMethods, NULL, NULL);
}

這裏須要的代碼量顯著減小,由於R和Python類型系統有所不一樣,沒有真正標量類型,因此咱們不須要作相同級別的檢驗/驗證用戶輸入咱們在上面的Python示例。剩下的代碼大體相同,咱們定義一個組函數可在R編譯。

總結

一個真實世界的例子必定會更加複雜,但整個過程並非那麼困難。在編寫跨語言工具時有幾件事要記住:

  • 若是你打算在二者之間共享函數就不要依賴宿主語言的api(R或Python)代碼。

  • 使用錯誤碼來傳遞異常提示,不要直接調用退出或者在宿主語言裏面才處理異常。

  • 最好用宿主語言負責內存分配和重分配,這意味着你的C/C++代碼應該要略過預先分配的內存和輸出過程。

  • 相信編譯器,你也應該重視編譯器的錯誤和警告。若是代碼在編譯時有警告那代碼就不算寫完。

不管是哪一個「贏得」這場語言戰爭,Python和R都將保持在數據科學屆的地位。這意味着爲工具開發者不能忽視的另一門語言,構建有用的工具就得保證這兩種語言均可以使用。一個簡單的方式是用C或c++編寫大量代碼,而後用 C 的 API的爲兩種語言提供封裝。

參考資料

原做者:Bill Lattner 翻譯:Harry Zhu
英文原文地址:Forget Python vs. R: how they can work together

做爲分享主義者(sharism),本人全部互聯網發佈的圖文均聽從CC版權,轉載請保留做者信息並註明做者 Harry Zhu 的 FinanceR專欄:https://segmentfault.com/blog...,若是涉及源代碼請註明GitHub地址:https://github.com/harryprince。微信號: harryzhustudio商業使用請聯繫做者。

相關文章
相關標籤/搜索