提速30倍!这个加速包让Python代码飞起来

点击“机器学习算法与Python实战”,“置顶”公众号

重磅干货,第一时间送达

大数据文摘出品 编译:啤酒泡泡、宁静

Python是社区里最受喜爱的编程语言!它是目前为止最易使用的语言,因为它的代码短小精悍,符合人们的思维方式,也符合人们的阅读习惯。

但是你会经常听到有人吐槽Python,尤其是有些C语言的大牛吐槽Python速度慢。

他们说得没错,相比其他高级程序设计语言,如C语言来说,Python确实速度很慢,这主要是因为C语言更面向计算机底层,像一些单片机、电路板的设计都使用C语言,C语言和汇编语言之间的转换也更快,但是“every coin has two sides”,没有完美无缺的编程语言,C语言也有着其诸如代码量大、面向过程的一些缺点,如何让Python兼顾C语言速度方面的优点呢?

一个很形象的例子,就像上图展示的一样,赛车手需要兼顾汽车的发动引擎的内耗磨损,也要兼顾赛车的运行速度,那么,如何在两者之间取得平衡呢?

其实有很多可以提高运行速度的办法,比如:

  • 利用多进程库来使用所有的CPU内核;
  • 如果你正在使用NumPy,Pandas,或是Scikit-Learn库,那么可以使用Rapids来提高GPU的处理速度。

但是这只针对你的任务可以并行的情况,例如数据预处理、矩阵操作等,上述办法都很棒,可是如果你只使用纯Python语言,那该怎么办呢?再比如,你必须使用一个很大的for循环,而且因为数据必须被顺序处理导致你无法使用矩阵,在这种情况下,有没有办法提高Python本身的速度呢?

Cython就是用来加速纯Python代码的。

什么是Cython?

从本质上讲,Cython是Python和C/C++的桥梁。它允许你对Python代码稍作修改,然后把Python代码直接翻译成C语言代码。

你唯一需要修改Python代码的地方就是在每一个变量前面加上它的类型,通常,我们在Python里会这样声明变量:

x = 0.5

如果使用Cython,我们会给变量加上它的类型:

cdef float x = 0.5

这会告诉Cython,我们的变量是浮点型变量,这和我们在C语言中的操作一样。使用纯Python语言,变量的数据类型在赋值后被自动定义。Cython这种显式的变量声明方法使得Python代码转换成C代码成为可能,因为C语言要求变量的数据类型必须在声明变量时写出来。

使用pip安装Cython只需一行代码:

pip install cython

Cython的数据类型

使用Cython时,Cython提供两类类型,一类用于变量,一类用于函数。

对于变量,我们可以这样写(请注意,这些类型都来自C/C++):

  • cdef int a, b, c
  • cdef char *s
  • cdef float x = 0.5 (single precision)
  • cdef double x = 63.4 (double precision)
  • cdef list names
  • cdef dict goals_for_each_play
  • cdef object card_deck

对于函数,我们可以这样写:

  • def – 普通的Python函数,只用Python解释器
  • cdef – Cython专用函数,不能通过纯Python代码使用该函数,必须在Cython内使用
  • cpdef – C语言和Python共用。可以通过C语言或者Python代码使用该函数

有了对Cython的了解,我们可以更进一步,开始加速我们的代码了!

使用Cython提高代码的运行速度

首先,我们建立一个Python代码的基准——使用一个for循环来计算某个数的阶乘。以下是用纯Python语言写的代码:

def test(x):
    y = 1
    for i in range(1, x+1):
        y *= i
    return y

使用Cython写出的函数和纯Python代码写出的函数很类似,首先,我们要确保Cython代码文件的扩展名是.pyx。然后,我们唯一修改的地方就是在我们已声明的每个变量和函数前加上它们的类型,run_cython.pyx代码如下:

cpdef int test(int x):
    cdef int y = 1
    cdef int i
    for i in range(1, x+1):
        y *= i
    return y

请注意,函数前有cpdef,以确保我们可以通过Python调用该函数。同时请注意,for循环里的变量i也有类型信息,你需要设定函数中所有变量的类型,这样C语言编译器才能知道需要使用什么类型!

接下来,创建一个setup.py文件,把Cython代码翻译成C代码:

from distutils.core import setup
from Cython.Build import cythonize
setup(ext_modules = cythonize('run_cython.pyx'))

然后执行汇编过程,在命令行输入如下命令:

python setup.py build_ext --inplace

我们的C代码已经被编译了,可以使用了!你可以在文件夹里看见你的Cython代码,你有了所有运行C代码所需要的文件,包括run_cython.c文件,文摘菌在win10系统尝试后文件夹结构如下:

打开run_cython.c后的部分代码(代码有点长,此处只展示开头变量声明和结尾的函数实现)

/***the beginning part of the code***/ 
/* Generated by Cython 0.29.13 */
/* BEGIN: Cython Metadata
{
    "distutils": {
        "name": "run_cython",
        "sources": [
            "run_cython.pyx"
        ]
    },
    "module_name": "run_cython"
}
END: Cython Metadata */

#define PY_SSIZE_T_CLEAN
#include "Python.h"
#ifndef Py_PYTHON_H
    #error Python headers needed to compile C extensions, please install development version of Python.
#elif PY_VERSION_HEX < 0x02060000 || (0x03000000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x03030000)
    #error Cython requires Python 2.6+ or Python 3.3+.
#else
#define CYTHON_ABI "0_29_13"
#define CYTHON_HEX_VERSION 0x001D0DF0
#define CYTHON_FUTURE_DIVISION 0
#include <stddef.h>
#ifndef offsetof
  #define offsetof(type, member) ( (size_t) & ((type*)0) -> member )
#endif
#if !defined(WIN32) && !defined(MS_WINDOWS)
  #ifndef __stdcall
    #define __stdcall
  #endif
  #ifndef __cdecl
    #define __cdecl
  #endif
  #ifndef __fastcall
    #define __fastcall
  #endif
#endif
#ifndef DL_IMP

/****the end part of the code***/
static CYTHON_INLINE Py_ssize_t __Pyx_PyIndex_AsSsize_t(PyObject* b) {
  Py_ssize_t ival;
  PyObject *x;
#if PY_MAJOR_VERSION < 3
  if (likely(PyInt_CheckExact(b))) {
    if (sizeof(Py_ssize_t) >= sizeof(long))
        return PyInt_AS_LONG(b);
    else
        return PyInt_AsSsize_t(b);
  }
#endif
  if (likely(PyLong_CheckExact(b))) {
    #if CYTHON_USE_PYLONG_INTERNALS
    const digit* digits = ((PyLongObject*)b)->ob_digit;
    const Py_ssize_t size = Py_SIZE(b);
    if (likely(__Pyx_sst_abs(size) <= 1)) {
        ival = likely(size) ? digits[0] : 0;
        if (size == -1) ival = -ival;
        return ival;
    } else {
      switch (size) {
         case 2:
           if (8 * sizeof(Py_ssize_t) > 2 * PyLong_SHIFT) {
             return (Py_ssize_t) (((((size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]));
           }
           break;
         case -2:
           if (8 * sizeof(Py_ssize_t) > 2 * PyLong_SHIFT) {
             return -(Py_ssize_t) (((((size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]));
           }
           break;
         case 3:
           if (8 * sizeof(Py_ssize_t) > 3 * PyLong_SHIFT) {
             return (Py_ssize_t) (((((((size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]));
           }
           break;
         case -3:
           if (8 * sizeof(Py_ssize_t) > 3 * PyLong_SHIFT) {
             return -(Py_ssize_t) (((((((size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]));
           }
           break;
         case 4:
           if (8 * sizeof(Py_ssize_t) > 4 * PyLong_SHIFT) {
             return (Py_ssize_t) (((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]));
           }
           break;
         case -4:
           if (8 * sizeof(Py_ssize_t) > 4 * PyLong_SHIFT) {
             return -(Py_ssize_t) (((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]));
           }
           break;
      }
    }
    #endif
    return PyLong_AsSsize_t(b);
  }
  x = PyNumber_Index(b);
  if (!x) return -1;
  ival = PyInt_AsSsize_t(x);
  Py_DECREF(x);
  return ival;
}
static CYTHON_INLINE PyObject * __Pyx_PyBool_FromLong(long b) {
  return b ? __Pyx_NewRef(Py_True) : __Pyx_NewRef(Py_False);
}
static CYTHON_INLINE PyObject * __Pyx_PyInt_FromSize_t(size_t ival) {
    return PyInt_FromSize_t(ival);
}

#endif /* Py_PYTHON_H

现在我们可以测试我们全新的、速度超快的C代码了!看看下面的代码,它是比较Python代码和Cython代码速度的测试:

import run_python
import run_cython
import time

number = 10

start = time.time()
run_python.test(number)
end =  time.time()

py_time = end - start
print("Python time = {}".format(py_time))

start = time.time()
run_cython.test(number)
end =  time.time()

cy_time = end - start
print("Cython time = {}".format(cy_time))

print("Speedup = {}".format(py_time /

代码很容易理解,我们用Python的方式导入我们的文件,然后也像Python那样运行我们的函数!

只需稍作修改,Cython就可以帮你加速几乎所有的纯Python代码。值得注意的是,你使用的循环越多、需要筛选处理的数据越多,Cython就越能发挥加速的作用。

请看下面的表格,它记录了Cython在计算不同数阶乘的运行速度,number的值从10到10000000,使用Cython,我们的速度提高了36倍!

以上对Cython的介绍,希望可以给习惯使用Python进行编程的读者带来帮助。文摘菌也强势安利这款Cython加速器,带你在Python的道路上驰骋!

相关报道:

https://towardsdatascience.com/use-cython-to-get-more-than-30x-speedup-on-your-python-code-f6cb337919b6

原文发布于微信公众号 - 机器学习与统计学(tjxj666)

原文发表时间:2019-08-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券