在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++进行交互,这比直接使用PythonC API要容易得多。

Cython是已编译版本的Python。它最初是基于PYREX的,但根据Sage的开发人员的需要进行了更改;Cython是与Sage协同开发的。然而,它现在是一个独立的项目,它的使用超出了Sage的范围。因此,它是一种年轻但正在发展的语言,具有年轻但正在发展的文档。有关最新信息,请参阅其网页http://www.cython.org/,,或查看 Language Basics 立即开始。

在Sage中编写Cython代码

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

  1. 在Sage Notebook中,任何单元格都以 %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库中。那就跑吧 sage -b 来重建赛奇。

附加或加载.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皓龙的定时测试:

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代码pickling

对Python类和扩展类(如Cython)的pickling是不同的。中讨论了这一点 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),)