<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    qileilove

    blog已經轉移至github,大家請訪問 http://qaseven.github.io/

    高性能的Python擴展:第一部分

     簡介
      通常來說,Python不是一種高性能的語言,在某種意義上,這種說法是真的。但是,隨著以Numpy為中心的數學和科學軟件包的生態圈的發展,達到合理的性能不會太困難。
      當性能成為問題時,運行時間通常由幾個函數決定。用C重寫這些函數,通常能極大的提升性能。
      在本系列的第一部分中,我們來看看如何使用NumPy的C API來編寫C語言的Python擴展,以改善模型的性能。在以后的文章中,我們將在這里提出我們的解決方案,以進一步提升其性能。
      文件
      這篇文章中所涉及的文件可以在Github上獲得。
      模擬
      作為這個練習的起點,我們將在像重力的力的作用下為N體來考慮二維N體的模擬。
      以下是將用于存儲我們世界的狀態,以及一些臨時變量的類。
    # lib/sim.py
    class World(object):
    """World is a structure that holds the state of N bodies and
    additional variables.
    threads : (int) The number of threads to use for multithreaded
    implementations.
    STATE OF THE WORLD:
    N : (int) The number of bodies in the simulation.
    m : (1D ndarray) The mass of each body.
    r : (2D ndarray) The position of each body.
    v : (2D ndarray) The velocity of each body.
    F : (2D ndarray) The force on each body.
    TEMPORARY VARIABLES:
    Ft : (3D ndarray) A 2D force array for each thread's local storage.
    s  : (2D ndarray) The vectors from one body to all others.
    s3 : (1D ndarray) The norm of each s vector.
    NOTE: Ft is used by parallel algorithms for thread-local
    storage. s and s3 are only used by the Python
    implementation.
    """
    def __init__(self, N, threads=1,
    m_min=1, m_max=30.0, r_max=50.0, v_max=4.0, dt=1e-3):
    self.threads = threads
    self.N  = N
    self.m  = np.random.uniform(m_min, m_max, N)
    self.r  = np.random.uniform(-r_max, r_max, (N, 2))
    self.v  = np.random.uniform(-v_max, v_max, (N, 2))
    self.F  = np.zeros_like(self.r)
    self.Ft = np.zeros((threads, N, 2))
    self.s  = np.zeros_like(self.r)
    self.s3 = np.zeros_like(self.m)
    self.dt = dt
     在開始模擬時,N體被隨機分配質量m,位置r和速度v。對于每個時間步長,接下來的計算有:
      合力F,每個體上的合力根據所有其他體的計算。
      速度v,由于力的作用每個體的速度被改變。
      位置R,由于速度每個體的位置被改變。
      第一步是計算合力F,這將是我們的瓶頸。由于世界上存在的其他物體,單一物體上的力是所有作用力的總和。這導致復雜度為O(N^2)。速度v和位置r更新的復雜度都是O(N)。
      如果你有興趣,這篇維基百科的文章介紹了一些可以加快力的計算的近似方法。
      純Python
      在純Python中,使用NumPy數組是時間演變函數的一種實現方式,它為優化提供了一個起點,并涉及測試其他實現方式。
    # lib/sim.py
    def compute_F(w):
    """Compute the force on each body in the world, w."""
    for i in xrange(w.N):
    w.s[:] = w.r - w.r[i]
    w.s3[:] = (w.s[:,0]**2 + w.s[:,1]**2)**1.5
    w.s3[i] = 1.0 # This makes the self-force zero.
    w.F[i] = (w.m[i] * w.m[:,None] * w.s / w.s3[:,None]).sum(0)
    def evolve(w, steps):
    """Evolve the world, w, through the given number of steps."""
    for _ in xrange(steps):
    compute_F(w)
    w.v += w.F * w.dt / w.m[:,None]
    w.r += w.v * w.dt
      合力計算的復雜度為O(N^2)的現象被NumPy的數組符號所掩蓋。每個數組操作遍歷數組元素。
      可視化
      這里是7個物體從隨機初始狀態開始演化的路徑圖:
      性能
      為了實現這個基準,我們在項目目錄下創建了一個腳本,包含如下內容:
      import lib
      w = lib.World(101)
      lib.evolve(w, 4096)
      我們使用cProfile模塊來測試衡量這個腳本。
      python -m cProfile -scum bench.py
      前幾行告訴我們,compute_F確實是我們的瓶頸,它占了超過99%的運行時間。
    428710 function calls (428521 primitive calls) in 16.836 seconds
    Ordered by: cumulative time
    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    0.000    0.000   16.837   16.837 bench.py:2(<module>)
    1    0.062    0.062   16.756   16.756 sim.py:60(evolve)
    4096   15.551    0.004   16.693    0.004 sim.py:51(compute_F)
    413696    1.142    0.000    1.142    0.000 {method 'sum' ...
    3    0.002    0.001    0.115    0.038 __init__.py:1(<module>)
    ...
      在Intel i5臺式機上有101體,這種實現能夠通過每秒257個時間步長演化世界。
      簡單的C擴展 1
      在本節中,我們將看到一個C擴展模塊實現演化的功能。當看完這一節時,這可能幫助我們獲得一個C文件的副本。文件src/simple1.c,可以在GitHub上獲得。
      關于NumPy的C API的其他文檔,請參閱NumPy的參考。Python的C API的詳細文檔在這里。
      樣板
      文件中的第一件事情是先聲明演化函數。這將直接用于下面的方法列表。
      static PyObject *evolve(PyObject *self, PyObject *args);
      接下來是方法列表。
      static PyMethodDef methods[] = {
      { "evolve", evolve, METH_VARARGS, "Doc string."},
      { NULL, NULL, 0, NULL } /* Sentinel */
      };
      這是為擴展模塊的一個導出方法列表。這只有一個名為evolve方法。
      樣板的最后一部分是模塊的初始化。
      PyMODINIT_FUNC initsimple1(void) {
      (void) Py_InitModule("simple1", methods);
      import_array();
      }
      另外,正如這里顯示,initsimple1中的名稱必須與Py_InitModule中的第一個參數匹配。對每個使用NumPy API的擴展而言,調用import_array是有必要的。
      數組訪問宏
      數組訪問的宏可以在數組中被用來正確地索引,無論數組被如何重塑或分片。這些宏也使用如下的代碼使它們有更高的可讀性。
      #define m(x0) (*(npy_float64*)((PyArray_DATA(py_m) + \
      (x0) * PyArray_STRIDES(py_m)[0])))
      #define m_shape(i) (py_m->dimensions[(i)])
      #define r(x0, x1) (*(npy_float64*)((PyArray_DATA(py_r) + \
      (x0) * PyArray_STRIDES(py_r)[0] + \
      (x1) * PyArray_STRIDES(py_r)[1])))
      #define r_shape(i) (py_r->dimensions[(i)])
      在這里,我們看到訪問宏的一維和二維數組。具有更高維度的數組可以以類似的方式被訪問。
      在這些宏的幫助下,我們可以使用下面的代碼循環r:
      for(i = 0; i < r_shape(0); ++i) {
      for(j = 0; j < r_shape(1); ++j) {
      r(i, j) = 0; // Zero all elements.
      }
      }
     命名標記
      上面定義的宏,只在匹配NumPy的數組對象定義了正確的名稱時才有效。在上面的代碼中,數組被命名為py_m和py_r。為了在不同的方法中使用相同的宏,NumPy數組的名稱需要保持一致。
      計算力
      特別是與上面五行的Python代碼相比,計算力數組的方法顯得頗為繁瑣。
    static inline void compute_F(npy_int64 N,
    PyArrayObject *py_m,
    PyArrayObject *py_r,
    PyArrayObject *py_F) {
    npy_int64 i, j;
    npy_float64 sx, sy, Fx, Fy, s3, tmp;
    // Set all forces to zero.
    for(i = 0; i < N; ++i) {
    F(i, 0) = F(i, 1) = 0;
    }
    // Compute forces between pairs of bodies.
    for(i = 0; i < N; ++i) {
    for(j = i + 1; j < N; ++j) {
    sx = r(j, 0) - r(i, 0);
    sy = r(j, 1) - r(i, 1);
    s3 = sqrt(sx*sx + sy*sy);
    s3 *= s3 * s3;
    tmp = m(i) * m(j) / s3;
    Fx = tmp * sx;
    Fy = tmp * sy;
    F(i, 0) += Fx;
    F(i, 1) += Fy;
    F(j, 0) -= Fx;
    F(j, 1) -= Fy;
    }
    }
    }
      請注意,我們使用牛頓第三定律(成對出現的力大小相等且方向相反)來降低內環范圍。不幸的是,它的復雜度仍然為O(N^2)。
      演化函數
      該文件中的最后一個函數是導出的演化方法。
    static PyObject *evolve(PyObject *self, PyObject *args) {
    // Declare variables.
    npy_int64 N, threads, steps, step, i;
    npy_float64 dt;
    PyArrayObject *py_m, *py_r, *py_v, *py_F;
    // Parse arguments.
    if (!PyArg_ParseTuple(args, "ldllO!O!O!O!",
    &threads,
    &dt,
    &steps,
    &N,
    &PyArray_Type, &py_m,
    &PyArray_Type, &py_r,
    &PyArray_Type, &py_v,
    &PyArray_Type, &py_F)) {
    return NULL;
    }
    // Evolve the world.
    for(step = 0;  step< steps; ++step) {
    compute_F(N, py_m, py_r, py_F);
    for(i = 0; i < N; ++i) {
    v(i, 0) += F(i, 0) * dt / m(i);
    v(i, 1) += F(i, 1) * dt / m(i);
    r(i, 0) += v(i, 0) * dt;
    r(i, 1) += v(i, 1) * dt;
    }
    }
    Py_RETURN_NONE;
    }
      在這里,我們看到了Python參數如何被解析。在該函數底部的時間步長循環中,我們看到的速度和位置向量的x和y分量的顯式計算。
      性能
      C版本的演化方法比Python版本更快,這應該不足為奇。在上面提到的相同的i5臺式機中,C實現的演化方法能夠實現每秒17972個時間步長。相比Python實現,這方面有70倍的提升。
      觀察
      注意,C代碼一直保持盡可能的簡單。輸入參數和輸出矩陣可以進行類型檢查,并分配一個Python裝飾器函數。刪除分配,不僅能加快處理,而且消除了由Python對象不正確的引用計數造成的內存泄露(或更糟)。
      下一部分
      在本系列文章的下一部分,我們將通過發揮C-相鄰NumPy矩陣的優勢來提升這種實現的性能。之后,我們來看看使用英特爾的SIMD指令和OpenMP來進一步推進。

    posted on 2014-12-03 13:49 順其自然EVO 閱讀(270) 評論(0)  編輯  收藏 所屬分類: 測試學習專欄

    <2014年12月>
    30123456
    78910111213
    14151617181920
    21222324252627
    28293031123
    45678910

    導航

    統計

    常用鏈接

    留言簿(55)

    隨筆分類

    隨筆檔案

    文章分類

    文章檔案

    搜索

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 一边摸一边爽一边叫床免费视频| 亚洲女女女同性video| 国产黄在线观看免费观看不卡| 免费大学生国产在线观看p| 亚洲av无码专区在线观看亚| 日韩免费无砖专区2020狼| 亚洲男同gay片| 亚洲av午夜成人片精品电影| 免费一级毛suv好看的国产网站 | 久久不见久久见免费影院www日本| 午夜国产羞羞视频免费网站| 农村寡妇一级毛片免费看视频| 亚洲国产精品日韩| 中文字幕乱码系列免费| 亚洲国产精品久久久久久| 最近中文字幕完整版免费高清| 亚洲欧洲精品一区二区三区| 日本免费网站视频www区| 中文字幕在线观看亚洲日韩| 国产小视频在线观看免费| 中文字幕免费播放| 亚洲五月六月丁香激情| 国产精品成人免费一区二区| 美女的胸又黄又www网站免费| 伊人婷婷综合缴情亚洲五月| 一区二区在线免费观看| 亚洲中文字幕久久精品无码A| 又大又黄又粗又爽的免费视频| 久久性生大片免费观看性| 亚洲美女一区二区三区| 国产一级高清视频免费看| 9i9精品国产免费久久| 亚洲午夜电影在线观看| 亚洲成AⅤ人影院在线观看| 免费国产黄网站在线观看| 亚洲国产成人久久精品大牛影视| 中文字幕亚洲电影| 又黄又爽又成人免费视频| 一个人看的免费视频www在线高清动漫| 亚洲成人午夜在线| 日韩高清免费观看|