【问题标题】:Defining more complicated static arrays定义更复杂的静态数组
【发布时间】:2016-02-05 10:37:12
【问题描述】:

在数值方法中,通常会有很多静态系数,因为它们对于特定方法是固定的。我想知道在 Cython / C 中设置此类数组或变量的最佳方法是什么。

在我的例子中,龙格-库塔积分方法大多相同,除了系数和阶段数。现在我正在做类似(简化)的事情

# Define some struct such that it can be used for all different Runge-Kutta methods
ctypedef struct RKHelper:
    int numStages
    double* coeffs

cdef:
    RKHelper firstRKMethod
    # Later secondRKMethod, thirdRKMethod, etc.

firstRKMethod.numStages = 3
firstRKMethod.coeffs = <double*> malloc(firstRKMethod.numStages*sizeof(double))

# Arrays can be large and most entries are zero
for ii in range(firstRKMethod.numStages):
    firstRKMethod.coeffs[ii] = 0.

# Set non-zero elements
firstRKMethod.coeffs[2] = 1.3

几点:

  • 我知道 malloc 不适用于静态数组,但我不知道如何在 Cython 中将“numStages”或“RKHelper”声明为静态,所以我不能使用静态数组...或者我可以类似于 RKHelper 中的“double[4]”,它不允许对所有 RK 方法使用相同的结构定义。
  • 我想知道是否有比执行循环更好的方法。我不想手动设置整个数组(例如,array = [0., 0., 0., 0., 1.3, ... 很多数字大多为零])。
  • 据我所知,Cython 中没有“真正的”静态变量,是吗?

有没有更好的方法来做我想做的事?

干杯

【问题讨论】:

    标签: python c cython


    【解决方案1】:

    实现您想要实现的一种方法是将 Runge Kutta 方案的系数设置为全局变量,这样您就可以使用静态数组。这会很快,但绝对丑陋

    丑陋的解决方案

    cdef int numStages = 3
    # Using the pointer notation you can set a static array
    # as well as its elements in one go
    cdef double* coeffs = [0.,0.,1.3]
    # You can always change the coefficients further as you wish
    
    def RungeKutta_StaticArrayGlobal():
        # Do stuff
    
        # Just to check
        return numStages
    

    更好的解决方案是定义一个cython 类,其中Runge Kutta 系数作为其成员

    优雅的解决方案

    cdef class RungeKutta_StaticArrayClass:
        cdef double* coeffs
        cdef int numStages
        def __cinit__(self):
            # Note that due to the static nature of self.coeffs, its elements
            # expire beyond the scope of this function    
            self.coeffs = [0.,0.,1.3]
            self.numStages = 3
    
        def GetnumStages(self):
            return self.numStages
    
        def Integrate(self):
            # Reset self.coeffs
            self.coeffs = [0.,0.,0.,0.,0.8,2.1]
            # Perform integration
    

    关于设置元素的问题,让我们使用calloc 而不是malloc 使用动态分配的数组修改您自己的代码

    动态分配的版本

    from libc.stdlib cimport calloc, free
    
    ctypedef struct RKHelper:
        int numStages
        double* coeffs
    
    def RungeKutta_DynamicArray():
        cdef:
            RKHelper firstRKMethod
    
        firstRKMethod.numStages = 3
        # Use calloc instead, it zero initialises the buffer, so you don't 
        # need to set the elements to zero within a loop
        firstRKMethod.coeffs = <double*> calloc(firstRKMethod.numStages,sizeof(double))
    
        # Set non-zero elements
        firstRKMethod.coeffs[2] = 1.3
    
        free(firstRKMethod.coeffs)
    
        # Just to check
        return firstRKMethod.numStages
    

    让我们做一个有点荒谬的基准测试,以验证前两个示例中的数组是否真正是静态的(即没有运行时成本)

    In[1]: print(RungeKutta_DynamicArray())
    3
    In[2]: print(RungeKutta_StaticArray())
    3
    In[3]: obj = RungeKutta_StaticArrayClass()
    In[4]: print(obj.GetnumStages())
    3
    
    In[5]: %timeit RungeKutta_DynamicArray()
    10000000 loops, best of 3: 65.2 ns per loop
    
    In[6]: %timeit RungeKutta_StaticArray()
    10000000 loops, best of 3: 25.2 ns per loop 
    
    In[6]: %timeit RungeKutta_StaticArrayClass()
    10000000 loops, best of 3: 49.6 ns per loop 
    

    RungeKutta_StaticArray 本质上接近于no-op 成本,这意味着数组分配没有运行时损失。您可以选择在此函数中声明coeffs,时间仍然相同。 RungeKutta_StaticArrayClass 尽管使用其成员和构造函数设置类的开销仍然比动态分配的版本快。

    【讨论】:

    • 感谢您的回答。关于你的建议的一些事情:如果很多条目是相同的(例如零),在代码中编写 StaticArray() 有点烦人。我不想在我的代码中有数千行带零的行。基本上我希望有一种方法可以告诉 Cython 或编译器在编译时编写一个静态数组,即“Cython:在编译时使用这个循环创建一个静态数组”。集成器类是我的第一个实现,但考虑到使用 GIL 的 cdef 类的局限性,我试图在非常基本的块(如集成器)中避开它。
    【解决方案2】:

    为什么不直接使用 numpy 数组呢?实际上,它实际上并不是静态的(见最后的注释),但您可以在全局范围内分配它,以便在模块启动时创建它。您还可以访问下面的原始 C 数组,因此没有真正的效率成本。

    import numpy as np
    
    # at module global scope
    cdef double[::1] rk_coeffs = np.zeros((50,)) # avoid having to manually fill with 0s
    # illustratively fill the non-zero elements
    rk_coeffs[1] = 2.0
    rk_coeffs[3] = 5.0
    
    # if you need to convert to a C array
    cdef double* rk_coeffs_ptr = &rk_coeffs[0]
    

    注意我对这个问题的解读是,您使用“静态”来表示“编译到模块中”,而不是任何 numerous C-related definitions 或与 Python 静态方法有关的任何内容/类变量。

    【讨论】:

      猜你喜欢
      • 2011-06-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-28
      相关资源
      最近更新 更多