2

我正在尝试编写此处找到的 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();
        }
    }
}
4

1 回答 1

1

高阶函数在 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# 引用是两个不同的东西,您可能无法将 C# 引用返回到 AleaGPU 来代替 F# 引用。当然,除非它有单独的支持。

于 2015-03-24T22:00:57.680 回答