2017-12-09 9 views
2

私はオーバーヘッドに関心がありました。だから、多かれ少なかれ機能する2つの関数nopstarnopをエクスポートする最小限のC拡張を書きました。彼らは彼らの入力を通過する(2つの関連機能は、残りはちょうど退屈なボイラープレートコードである上部に右である):関数呼び出しのオーバーヘッド - Pythonの組み込み関数が組み込み関数より速いのはなぜですか?

amanmodule.c:

#include <Python.h> 

static PyObject* aman_nop(PyObject *self, PyObject *args) 
{ 
    PyObject *obj; 

    if (!PyArg_UnpackTuple(args, "arg", 1, 1, &obj)) 
    return NULL; 
    Py_INCREF(obj); 
    return obj; 
} 

static PyObject* aman_starnop(PyObject *self, PyObject *args) 
{ 
    Py_INCREF(args); 
    return args; 
} 

static PyMethodDef AmanMethods[] = { 
    {"nop", (PyCFunction)aman_nop, METH_VARARGS, 
    PyDoc_STR("nop(arg) -> arg\n\nReturn arg unchanged.")}, 
    {"starnop", (PyCFunction)aman_starnop, METH_VARARGS, 
    PyDoc_STR("starnop(*args) -> args\n\nReturn tuple of args unchanged")}, 
    {NULL, NULL} 
}; 

static struct PyModuleDef amanmodule = { 
    PyModuleDef_HEAD_INIT, 
    "aman", 
    "aman - a module about nothing.\n\n" 
    "Provides functions 'nop' and 'starnop' which do nothing:\n" 
    "nop(arg) -> arg; starnop(*args) -> args\n", 
    -1, 
    AmanMethods 
}; 

PyMODINIT_FUNC 
PyInit_aman(void) 
{ 
    return PyModule_Create(&amanmodule); 
} 

setup.py:

from setuptools import setup, extension 

setup(name='aman', version='1.0', 
     ext_modules=[extension.Extension('aman', ['amanmodule.c'])], 
     author='n.n.', 
     description="""aman - a module about nothing 

     Provides functions 'nop' and 'starnop' which do nothing: 
     nop(arg) -> arg; starnop(*args) -> args 
     """, 
     license='public domain', 
     keywords='nop pass-through identity') 

import numpy as np 
from aman import nop, starnop 
from timeit import timeit 

def mnsd(x): return '{:8.6f} \u00b1 {:8.6f} \u00b5s'.format(np.mean(x), np.std(x)) 

def pnp(x): x 

globals={} 
for globals['nop'] in (int, bool, (0).__add__, hash, starnop, nop, pnp, lambda x: x): 
    print('{:60s}'.format(repr(globals['nop'])), 
      mnsd([timeit('nop(1)', globals=globals) for i in range(10)]), 
      ' ', 
      mnsd([timeit('nop(True)',globals=globals) for i in range(10)])) 

次に、私は純粋なPythonの実装とも何も次に何組み込みコマンドのカップルに対してそれらを時間を計ります3210

最初の質問私は方法論的に遅れた何かをやっていないのですか? 1,000,000通話ごとの10個のブロックの

結果:

<class 'int'>            0.099754 ± 0.003917 µs 0.103933 ± 0.000585 µs 
<class 'bool'>            0.097711 ± 0.000661 µs 0.094412 ± 0.000612 µs 
<method-wrapper '__add__' of int object at 0x8c7000>   0.065146 ± 0.000728 µs 0.064976 ± 0.000605 µs 
<built-in function hash>          0.039546 ± 0.000671 µs 0.039566 ± 0.000452 µs 
<built-in function starnop>         0.056490 ± 0.000873 µs 0.056234 ± 0.000181 µs 
<built-in function nop>          0.060094 ± 0.000799 µs 0.059959 ± 0.000170 µs 
<function pnp at 0x7fa31c0512f0>        0.090452 ± 0.001077 µs 0.098479 ± 0.003314 µs 
<function <lambda> at 0x7fa31c051378>      0.086387 ± 0.000817 µs 0.086536 ± 0.000714 µs 

今私の実際の質問:私のnopはCで書かれており、何もしない場合であっても(starnopもその引数を解析しません)組み込みhash機能一貫して高速です。私はintがPythonで独自のハッシュ値であることを知っていますので、hashもここではノップですが、それは私のノップ以上のものではないので、なぜスピードの違いですか?

更新:完全に忘れてしまった:私はかなり標準的なx86_64マシン、linux gcc4.8.5です。拡張子はpython3 setup.py install --userを使用してインストールします。

+0

どのようにあなたのCコードをコンパイルしていますか?あなたはこの重要な情報を私たちに語っていません。また、Pythonはどのようにコンパイルされていますか?あなたのコードにない最適化がPythonで有効になっていますか? – Sebivor

+0

@Sebivorはセットアップスクリプトを呼び出すだけです: 'python3 setup.py install --user'。私は、あなたが明示的に指定しない限り、Python自体がコンパイルされたのと同じコンパイラ設定を使用すると常に考えていました。私は質問を更新します。 –

+0

...と私が尋ねた他の質問への答え?それらも持っていますか?そうでなければ、あなたはこの質問をするのに十分な研究をしていません。 – Sebivor

答えて

1

Python関数呼び出しのオーバーヘッドの大部分は、argsタプルの作成です。引数の解析によってもオーバーヘッドが追加されます。

関数は、すべての引数を格納するためにタプルを作成する必要があるので、METH_VARARGS呼び出し規約を使用して定義します。単一の引数が必要な場合は、METH_O呼び出し規約を使用できます。 METH_Oでは、タプルは作成されません。単一の引数が直接渡されます。あなたの例にnop1を追加しました。これはMETH_Oを使用しています。

METH_NOARGSを使用して引数を必要としない関数を定義することは可能です。最小のオーバーヘッドについては、nop2を参照してください。

METH_VARARGSを使用して、直接argsタプルを解析する代わりに、PyArg_UnpackTupleまたは関連PyArg_関数を呼び出すことにより、わずかにオーバーヘッドを低減することができます。少し速いです。 nop3を参照してください。

組み込みのhash()関数は、METH_O呼び出し規約を使用しました。

#include <Python.h> 

static PyObject* aman_nop(PyObject *self, PyObject *args) 
{ 
    PyObject *obj; 

    if (!PyArg_UnpackTuple(args, "arg", 1, 1, &obj)) 
    return NULL; 
    Py_INCREF(obj); 
    return obj; 
} 

static PyObject* aman_nop1(PyObject *self, PyObject *other) 
{ 
    Py_INCREF(other); 
    return other; 
} 

static PyObject* aman_nop2(PyObject *self) 
{ 
    Py_RETURN_NONE; 
} 

static PyObject* aman_nop3(PyObject *self, PyObject *args) 
{ 
    PyObject *obj; 

    if (PyTuple_GET_SIZE(args) == 1) { 
    obj = PyTuple_GET_ITEM(args, 0); 
    Py_INCREF(obj); 
    return obj; 
    } 
    else { 
    PyErr_SetString(PyExc_TypeError, "nop3 requires 1 argument"); 
    return NULL; 
    } 
} 

static PyObject* aman_starnop(PyObject *self, PyObject *args) 
{ 
    Py_INCREF(args); 
    return args; 
} 

static PyMethodDef AmanMethods[] = { 
    {"nop", (PyCFunction)aman_nop, METH_VARARGS, 
    PyDoc_STR("nop(arg) -> arg\n\nReturn arg unchanged.")}, 
    {"nop1", (PyCFunction)aman_nop1, METH_O, 
    PyDoc_STR("nop(arg) -> arg\n\nReturn arg unchanged.")}, 
    {"nop2", (PyCFunction)aman_nop2, METH_NOARGS, 
    PyDoc_STR("nop(arg) -> arg\n\nReturn arg unchanged.")}, 
    {"nop3", (PyCFunction)aman_nop3, METH_VARARGS, 
    PyDoc_STR("nop(arg) -> arg\n\nReturn arg unchanged.")}, 
    {"starnop", (PyCFunction)aman_starnop, METH_VARARGS, 
    PyDoc_STR("starnop(*args) -> args\n\nReturn tuple of args unchanged")}, 
    {NULL, NULL} 
}; 

static struct PyModuleDef amanmodule = { 
    PyModuleDef_HEAD_INIT, 
    "aman", 
    "aman - a module about nothing.\n\n" 
    "Provides functions 'nop' and 'starnop' which do nothing:\n" 
    "nop(arg) -> arg; starnop(*args) -> args\n", 
    -1, 
    AmanMethods 
}; 

PyMODINIT_FUNC 
PyInit_aman(void) 
{ 
    return PyModule_Create(&amanmodule); 
} 

修正テストamanmodule.c変更

。PY

import numpy as np 
from aman import nop, nop1, nop2, nop3, starnop 
from timeit import timeit 

def mnsd(x): return '{:8.6f} \u00b1 {:8.6f} \u00b5s'.format(np.mean(x), np.std(x)) 

def pnp(x): x 

globals={} 
for globals['nop'] in (int, bool, (0).__add__, hash, starnop, nop, nop1, nop3, pnp, lambda x: x): 
    print('{:60s}'.format(repr(globals['nop'])), 
      mnsd([timeit('nop(1)', globals=globals) for i in range(10)]), 
      ' ', 
      mnsd([timeit('nop(True)',globals=globals) for i in range(10)])) 

# To test with no arguments 
for globals['nop'] in (nop2,): 
    print('{:60s}'.format(repr(globals['nop'])), 
      mnsd([timeit('nop()', globals=globals) for i in range(10)]), 
      ' ', 
      mnsd([timeit('nop()',globals=globals) for i in range(10)])) 

結果

$ python3 test.py 
<class 'int'>            0.080414 ± 0.004360 µs 0.086166 ± 0.003216 µs 
<class 'bool'>            0.080501 ± 0.008929 µs 0.075601 ± 0.000598 µs 
<method-wrapper '__add__' of int object at 0xa6dca0>   0.045652 ± 0.004229 µs 0.044146 ± 0.000114 µs 
<built-in function hash>          0.035122 ± 0.003317 µs 0.033419 ± 0.000136 µs 
<built-in function starnop>         0.044056 ± 0.001300 µs 0.044280 ± 0.001629 µs 
<built-in function nop>          0.047297 ± 0.000777 µs 0.049536 ± 0.007577 µs 
<built-in function nop1>          0.030402 ± 0.001423 µs 0.031249 ± 0.002352 µs 
<built-in function nop3>          0.044673 ± 0.004041 µs 0.042936 ± 0.000177 µs 
<function pnp at 0x7f946342d840>        0.071846 ± 0.005377 µs 0.071085 ± 0.003314 µs 
<function <lambda> at 0x7f946342d8c8>      0.066621 ± 0.001499 µs 0.067163 ± 0.002962 µs 
<built-in function nop2>          0.027736 ± 0.001487 µs 0.027035 ± 0.000397 µs 
+0

うわー、ありがとう!私はもう少し待つだろうが、私は何とか良い答えが出てくるのか疑問に思う。 –

+0

もう一度、完全な情報と楽しい答えをありがとう。そして、私の「ノップ」のスピードもかなりいいですね。 –

関連する問題