AleaGPU C#中的高阶函数



我正在尝试对F#reduce函数的C#版本(以C#风格)进行编码,可以在这里找到:

https://github.com/quantalea/AleaGPUTutorial/tree/master/src/fsharp/examples/generic_reduce

更具体地说,以这个函数为例:

let multiReduce (opExpr:Expr<'T -> 'T -> 'T>) numWarps =
    let warpStride = WARP_SIZE + WARP_SIZE / 2 + 1
    let sharedSize = numwarps * warpStride
    <@ fun tid (x:'T) ->
        // stuff
    @>

我主要是一个F#的人,我不太确定我应该如何在C#中编码这样的函数。对于C#版本,multiReduce函数将是类成员。所以,如果我想对F#代码进行更直接的翻译,我会从我的MultiReduce成员返回一个Func。

另一种选择是"压平"multiReduce函数,这样我的C#成员版本就会有两个额外的参数。所以…

public T MultiReduce(Func<T,T,T> op, int numWarps, int tid, T x)
{
    // stuff
}

但我不认为这在所有情况下都适用于AleaGPU编码,因为F#版本中引用的表达式是一个设备函数。您需要嵌套的函数结构,以便能够将某些变量的赋值与函数的实际调用分开。

我看到的另一种方法是创建一个MultiReduce类,并将opExpr和numWarps作为字段,然后将引号中的函数作为类成员。

那么,像这样的高阶函数通常是如何在AleaGPU-C#中实现的呢?我认为返回Func<..>不好到处都是,因为我在C#编码中看不到这一点。AleaGPU是一种特殊情况吗?

AleaGPU C#的基本实现如下所示:

internal class TransformModule<T> : ILGPUModule
{
    private readonly Func<T, T> op;
    public TransformModule(GPUModuleTarget target, Func<T, T> opFunc)
        : base(target)
    {
        op = opFunc;
    }
    [Kernel]
    public void Kernel(int n, deviceptr<T> x, deviceptr<T> y)
    {
        var start = blockIdx.x * blockDim.x + threadIdx.x;
        var stride = gridDim.x * blockDim.x;
        for (var i = start; i < n; i += stride)
            y[i] = op(x[i]);
    }
    public void Apply(int n, deviceptr<T> x, deviceptr<T> y)
    {
        const int blockSize = 256;
        var numSm = this.GPUWorker.Device.Attributes.MULTIPROCESSOR_COUNT;
        var gridSize = Math.Min(16 * numSm, Common.divup(n, blockSize));
        var lp = new LaunchParam(gridSize, blockSize);
        GPULaunch(Kernel, lp, n, x, y);
    }
    public T[] Apply(T[] x)
    {
        using (var dx = GPUWorker.Malloc(x))
        using (var dy = GPUWorker.Malloc<T>(x.Length))
        {
            Apply(x.Length, dx.Ptr, dy.Ptr);
            return dy.Gather();
        }
    }
}

高阶函数在C#中并不像在F#中那样普遍存在。虽然有很多接受函数作为参数的例子,但C#代码很少返回功能作为结果。我想这部分是因为代码非常难看(到处都是Func<T,U>),部分是因为C#程序员通常不习惯函数式风格,更倾向于OO方式。

特别是,C#中没有自动货币化/部分应用程序。您可以将其视为所有F#函数总是具有元组参数。事实上,如果从F#调用多参数C#方法,那么它就是这样的。

我还必须注意,代码中的函数是而不是,实际上是"高阶"。它既不接受也不返回任何函数。相反,它接受并返回引号,这根本不是一回事。大致来说,函数是对一段代码的引用,但引号是一种数据结构。它们看起来很相似,但却是完全不同的动物。

C#也有自己的引号,由类型System.Linq.Expressions.Expression<T>表示(其中T必须是委托类型)。然而,它们是而不是与F#引号是一回事。从F#端,您可以(排序)使用C#引号,但不能使用其他方式
F#和C#报价都有其长处和短处。特别是,C#支持编译,F#不支持。F#支持拼接,C#不支持。

这就引出了下一点:您可能需要拼接。因为您在返回的报价单正文中使用了opExpr,不是吗
C#没有开箱即用的支持。是的,理论上可以将剪接作为库函数来实现,但由于某种原因,没有事实上的标准、定期维护的实现。就我们而言,我们不得不自己打滚。它也是开源的,而且非常简单,所以可以随意使用

现在,在说了以上所有内容之后,我想表达一个疑问,即您是否能够使用C#来实现这一点。我真的不知道AleaGPU是如何工作的,但看起来它希望你返回一个F#引号,然后它可能会将其编译成GPU代码。如果是这样的话,因为C#和F#引号是两种不同的东西,你可能无法向AleaGPU返回C#引号来代替F#引号。当然,除非它有单独的支持。

最新更新