用Cython编码

本章讨论Cython,它是一种基于Python的编译语言。它在Python上的主要优点是代码可以更快(有时数量级),并且可以直接调用C和C++代码。由于Cython本质上是Python语言的超集,人们通常不会在Sage中区分Cython和Python代码(例如,有人谈到“Sage Python库”和“Python编码约定”)。

Python是一种解释语言,没有为变量声明数据类型。这些特性使得编写和调试变得很容易,但是Python代码有时会很慢。Cython代码看起来很像Python,但是它被翻译成C代码(通常是非常高效的C代码)然后进行编译。因此,它提供了一种Python开发人员熟悉的语言,但具有更高速度的潜力。Cython还允许SAGE开发人员与C和C++接口相比,直接使用Python C API要容易得多。

Cython是Python的编译版本。它最初是基于Pyrex的,但是根据Sage的开发人员需要而改变了;Cython是与Sage一起开发的。但是,它现在是一个独立的项目,它的使用范围超出了Sage的范围。因此,它是一种年轻的,但正在发展的语言,有着年轻的,但正在发展的文档。看看它的网页,http://www.cython.org/,以获取最新信息或查看 Language Basics 立即开始。

用Sage编写Cython代码

有几种方法可以在Sage中创建和构建Cython代码。

  1. 在Sage笔记本中,以 %cython . 当你评估那个细胞时,

    1. 它被保存到一个文件中。

    2. Cython在它上运行,所有的标准Sage库都会在必要时自动链接起来。

    3. 生成的共享库文件 (.so / .dll / .dylib )然后加载到Sage的运行实例中。

    4. 在该单元格中定义的功能现在可供您在笔记本中使用。另外,输出单元有一个链接到C程序,该程序被编译来创建 .so 文件。

    5. A cpdefdef 功能,比如说 testfunction ,定义为 %cython 可以导入工作表中的单元格,并在其他 %cython 导入同一工作表中的单元格,如下所示:

      %cython
      from __main__ import testfunction
      
  2. 创建一个 .spyx 文件并从命令行附加或加载它。这类似于创建 %cython 单元格在笔记本中,但完全从命令行工作(而不是从笔记本)。

  3. 创建一个 .pyx 归档并添加到Sage库中。

    1. 首先,将Cython扩展的列表添加到变量中 ext_modules 在文件中 SAGE_ROOT/src/module_list.py . 见 distutils.extension.Extension 类获取有关创建新Cython扩展的详细信息。

    2. sage -b 重建Sage。

    例如,为了编译 SAGE_ROOT/src/sage/graphs/chrompoly.pyx ,我们在 module_list.py

    Extension('sage.graphs.chrompoly',
              sources = ['sage/graphs/chrompoly.pyx'],
              libraries = ['gmp']),
    

附加或加载.spyx文件

尝试Cython而不必学习distutils等的最简单方法是创建一个扩展名为的文件 spyx ,代表“Sage”:

  1. 创建文件 power2.spyx .

  2. 把以下内容放进去:

    def is2pow(n):
        while n != 0 and n%2 == 0:
            n = n >> 1
        return n == 1
    
  3. 启动Sage命令行解释器并加载 spyx 文件(如果没有安装C编译器,这将失败)。

    sage: load("power2.spyx")
    Compiling power2.spyx...
    sage: is2pow(12)
    False
    

请注意,您可以更改 power2.spyx ,然后再次加载它,它将在运行时重新编译。您也可以附加 power2.spyx 因此,只要您进行更改,就会重新加载:

sage: attach("power2.spyx")

Cython用于它的速度。以下是在2.6 GHz Opteron上进行的定时测试:

sage: %time [n for n in range(10^5) if is2pow(n)]
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536]
CPU times: user 0.60 s, sys: 0.00 s, total: 0.60 s
Wall time: 0.60 s

现在,文件中的代码 power2.spyx 是有效的Python,如果我们将其复制到一个文件中 powerslow.py 加载后,我们得到以下结果:

sage: load("powerslow.py")
sage: %time [n for n in range(10^5) if is2pow(n)]
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536]
CPU times: user 1.01 s, sys: 0.04 s, total: 1.05 s
Wall time: 1.05 s

顺便说一句,我们可以用带有类型声明的Cython版本通过改变 def is2pow(n):def is2pow(unsigned int n): .

中断和信号处理

在为Sage编写Cython代码时,必须特别注意确保代码可以被中断 CTRL-C .

Sage使用 cysignals package 对此,请参见 cysignals documentation 更多信息。

解包Cython代码

Python类和扩展类(如Cython)的pickle是不同的。这在 Python pickling documentation . 对于扩展类的取消拾取,您需要编写 __reduce__() 方法,它通常返回元组 (f, args, ...) 这样的话 f(*args) 返回原始对象(的副本)。例如,下面的代码片段是 __reduce__() 方法从 sage.rings.integer.Integer

def __reduce__(self):
    '''
    This is used when pickling integers.

    EXAMPLES::

        sage: n = 5
        sage: t = n.__reduce__(); t
        (<cyfunction make_integer at ...>, ('5',))
        sage: t[0](*t[1])
        5
        sage: loads(dumps(n)) == n
        True
    '''
    # This single line below took me HOURS to figure out.
    # It is the *trick* needed to pickle Cython extension types.
    # The trick is that you must put a pure Python function
    # as the first argument, and that function must return
    # the result of unpickling with the argument in the second
    # tuple as input. All kinds of problems happen
    # if we don't do this.
    return sage.rings.integer.make_integer, (self.str(32),)