在Python中运行C扩展比纯C更快

3
我已经在C语言中实现了Python扩展,并发现在Python内部执行C函数比仅在C中执行C代码要快2倍。
但是为什么会更快?我本以为通过Python调用的纯C代码与直接从C中调用时性能应该完全相同。
这是我的实验:
- 纯C计算代码(简单的3for矩阵乘法) - 调用mmult()函数的纯C主函数 - 调用mmult()函数的Python扩展包装器 - 所有时间测量都在C代码内部进行
这里是我的结果:
纯C - 85us
Python扩展 - 36us
代码如下:
#include "mmult.h"

void mmult(int32_t a[1024],int32_t b[1024],int32_t c[1024]) {

  struct timeval t1, t2;
  gettimeofday(&t1, NULL);

  for(int i=0; i<32; i=i+1) {
    for(int j=0; j<32; j=j+1) {
        int32_t result=0;
         for(int k=0; k<32; k=k+1) {
           result+=a[i*32+k]*b[k*32+j];
         }
         c[i*32+j] = result;
      }
  }

  gettimeofday(&t2, NULL);

  double elapsedTime = (t2.tv_usec - t1.tv_usec) + (t2.tv_sec - t1.tv_sec)*1000000;
  printf("elapsed time: %fus\n",elapsedTime);

}

--mmult.h-------

#include <stdint.h>

void mmult(int32_t a[1024],int32_t b[1024],int32_t c[1024]);

--main.cpp------

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include "mmult.h"

int main() {
  int* a = (int*)malloc(sizeof(int)*1024);
  int* b = (int*)malloc(sizeof(int)*1024);
  int* c = (int*)malloc(sizeof(int)*1024);

  for(int i=0; i<1024; i++) {
    a[i]=i+1;
    b[i]=i+1;
    c[i]=0;
  }

  struct timeval t1, t2;
  gettimeofday(&t1, NULL);
  mmult(a,b,c);
  gettimeofday(&t2, NULL);

  double elapsedTime = (t2.tv_usec - t1.tv_usec) + (t2.tv_sec - t1.tv_sec)*1000000;
  printf("elapsed time: %fus\n",elapsedTime);
  free(a);
  free(b);
  free(c);

  return 0;
}

这里是我如何编译主要代码的过程:
gcc -o main main.cpp mmult.cpp -O3

--wrapper.cpp-----

#include <Python.h>
#include <numpy/arrayobject.h>
#include "mmult.h"

static PyObject* mmult_wrapper(PyObject* self, PyObject* args) {
   int32_t* a;
   PyArrayObject* a_obj = NULL;
   int32_t* b;
   PyArrayObject* b_obj = NULL;
   int32_t* c;
   PyArrayObject* c_obj = NULL;

   int res = PyArg_ParseTuple(args, "OOO", &a_obj, &b_obj, &c_obj);

   if (!res)
      return NULL;

   a = (int32_t*) PyArray_DATA(a_obj);
   b = (int32_t*) PyArray_DATA(b_obj);
   c = (int32_t*) PyArray_DATA(c_obj);

   /* call function */
   mmult(a,b,c);

   Py_RETURN_NONE;
}

/*  define functions in module */
static PyMethodDef TheMethods[] = {
   {"mmult_wrapper", mmult_wrapper, METH_VARARGS, "your c function"},
   {NULL, NULL, 0, NULL}
};

static struct PyModuleDef cModPyDem = {
   PyModuleDef_HEAD_INIT,
   "mmult", "Some documentation",
   -1,
   TheMethods
};

PyMODINIT_FUNC
PyInit_c_module(void) {
   PyObject* retval = PyModule_Create(&cModPyDem);
   import_array();
   return retval;
}

--setup.py-----

import os
import numpy
from distutils.core import setup, Extension
cur = os.path.dirname(os.path.realpath(__file__))
c_module = Extension("c_module", sources=["wrapper.cpp","mmult.cpp"],include_dirs=[cur,numpy.get_include()])
setup(ext_modules=[c_module])

--code.py-----

import c_module
import time
import numpy as np
if __name__ == "__main__":
    a = np.ndarray((32,32),dtype='int32',buffer=np.linspace(1,1024,1024,dtype='int32').reshape(32,32))
    b = np.ndarray((32,32),dtype='int32',buffer=np.linspace(1,1024,1024,dtype='int32').reshape(32,32))
    c = np.ndarray((32,32),dtype='int32',buffer=np.zeros((32,32),dtype='int32'))

    c_module.mmult_wrapper(a,b,c)

这是我编译Python扩展的方法:
python3.6 setup_sw.py build_ext --inplace

更新

我已经更新了mmult.cpp代码,在内部运行3for循环1,000,000次。这导致非常相似的时间:

纯C - 27微秒

Python扩展 - 27微秒


1
你正在使用两个不同的编译器,对吗?所以我猜想其中一个在这个实例中更擅长创建高效的可执行文件。 - bendl
在Python中执行C函数比仅在C主函数中执行C代码快2倍。 我们如何对速度进行基准测试,我很好奇您是如何得出结论的,认为在Python中执行C函数更快? - danglingpointer
6
在大多数情况下,答案是错误的基准测试。 - Lundin
1
int* a = (int*)malloc(sizeof(int)*1024); -- 这个条目数对于有意义的基准测试来说是一个微不足道的数量。 - PaulMcKenzie
1个回答

7

85微秒的延迟太小了,无法可靠地进行重复测量。例如,CPU缓存效应(或上下文切换,或分页)可能会支配计算时间(并将其改变以使计时无意义)。

(我猜你在Linux / x86-64上)

作为经验法则,请尝试至少持续半秒钟的运行,并多次重复基准测试。您还可以使用time(1)进行测量。

另请参见time(7)。时间有多个概念(经过的“真实”时间、单调时间、进程CPU时间、线程CPU时间等)。您可以考虑使用clock(3)clock_gettime(2)来测量时间。

顺便提一下,您可以使用更新的GCC版本进行编译(在2017年11月,GCC7发布了,几周后会发布GCC8),并且您想使用gcc -march=native -O3进行基准测试编译。尝试其他优化选项和调整。您也可以尝试另一个编译器,例如Clang/LLVM

还要看看这个答案(关于并行化)相关问题。可能numpy包(内部)使用类似技术(超出Python GIL范围),因此比C中的顺序矩阵乘法代码更快。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接