为什么在 .NET 7 中使用带有泛型的接口实现在 WebAssembly 中如此缓慢?

问题描述 投票:0回答:1

以下方法在编译为 WebAssembly (WASM) 时在浏览器上运行得非常快:

public static int SumInt(int[] vals)
{
    int sum = default;

    for (int i = 0; i < vals.Length; i++)
        sum += vals[i];

    return sum;
}

在我的计时测试中(该方法被调用 100 次,每次 1,000,000 个数字),运行时间大约为 70 毫秒。

在尝试使此方法通用时(为了处理其他基本类型,例如

float
double
),我编写了以下内容:

private static T Sum<T>(T[] vals, IAdder<T> adder) where T : struct
{
    T sum = default;

    for (int i = 0; i < vals.Length; i++)
        sum = adder.Add(sum, vals[i]);

    return sum;
}

public interface IAdder<T> where T : struct
{
    public T Add(T x, T y);
}

public class AdderInt : IAdder<int>
{
    public int Add(int x, int y) => x + y;
}

这样我就可以为整数定义以下方法:

public static int Sum(int[] vals) => Sum(vals, new AdderInt());

但是,对于与之前相同的时序测试,这个新实现的运行时间约为 1000 毫秒。这慢了超过 14 倍。

当我不在 WebAssembly 中运行此代码而是在 .NET 应用程序中运行此代码时,它们花费的时间大约相同。

对正在发生的事情有任何想法或改进代码以便以高性能方式处理其他类型(但也避免代码重复)的建议吗?

c# .net generics webassembly
1个回答
0
投票

对于 C# 7 你想要这样的东西

static class Program
{
    static void Main(string[] args)
    {
        int[] int_array = { 7, 3, -2, 1, 8 };
        float[] float_array = { 7f, 3f, -2f, 1f, 8f };
        double[] double_array = { 7.0, 3.0, -2.0, 1.0, 8.0 };

        int int_sum = Operation<int>.Sum(int_array);
        float float_sum = Operation<float>.Sum(float_array);
        double double_sum = Operation<double>.Sum(double_array);

        Console.WriteLine($"sum({string.Join(",", int_array)}) = {int_sum}");
        Console.WriteLine($"sum({string.Join(",", float_array)}) = {float_sum}");
        Console.WriteLine($"sum({string.Join(",", double_array)}) = {double_sum}");
    }
}

您可以使用以下类

Operation<T>
来执行此操作,该类使用
Expression
树生成函数。

OperationT.cs

public enum BindTo 
{
    Left,
    Right,
}
public class Operation<T>
{

    static Operation()
    {
        var T_arg1 = Expression.Parameter(typeof(T));
        var T_arg2 = Expression.Parameter(typeof(T));

        ConvertFromInteger = ConvertFrom<int>();
        ConvertFromFloat = ConvertFrom<float>();
        ConvertFromDouble = ConvertFrom<double>();

        Zero = ConvertFromInteger(0);
        One = ConvertFromInteger(1);

        UnaryPlus = BuildUnary(Expression.UnaryPlus(T_arg1));
        Negate = BuildUnary(Expression.Negate(T_arg1));
        Add = BuildBinary(Expression.Add(T_arg1, T_arg2));
        Subtract = BuildBinary(Expression.Subtract(T_arg1, T_arg2));
        Multiply = BuildBinary(Expression.Multiply(T_arg1, T_arg2));
        Divide = BuildBinary(Expression.Divide(T_arg1, T_arg2));
        Square = Expression.Lambda<Func<T, T>>(Expression.Multiply(T_arg1, T_arg1), T_arg1).Compile() as Func<T, T>;
        SquareRoot = FromFunction<double>(Math.Sqrt);
        Power = FromFunction<double>(Math.Pow);
        Invert = BuildUnary<T>(Expression.Divide(Expression.Constant(One), T_arg1), BindTo.Right);
        IsZero = BuildUnary<bool>(Expression.Equal(T_arg1, Expression.Constant(Zero)), BindTo.Left);
        Equal = BuildBinary<bool>(Expression.Equal(T_arg1, T_arg2));
        NotEqual = BuildBinary<bool>(Expression.NotEqual(T_arg1, T_arg2));
        GreaterThan = BuildBinary<bool>(Expression.GreaterThan(T_arg1, T_arg2));
        GreaterThanOrEqual = BuildBinary<bool>(Expression.GreaterThanOrEqual(T_arg1, T_arg2));
        LessThan = BuildBinary<bool>(Expression.LessThan(T_arg1, T_arg2));
        LessThanOrEqual = BuildBinary<bool>(Expression.GreaterThanOrEqual(T_arg1, T_arg2));
    }
    static Func<T> BuildNullary(ConstantExpression constant)
        => Expression.Lambda<Func<T>>(constant).Compile() as Func<T>;
    static Func<T,T> BuildUnary(ParameterExpression parameter)
        => Expression.Lambda<Func<T,T>>(Expression.UnaryPlus(parameter), parameter).Compile() as Func<T,T>;
    static Func<T, TResult> BuildUnary<TResult>(ParameterExpression parameter)
        => Expression.Lambda<Func<T, TResult>>(Expression.Convert(parameter, typeof(TResult)), parameter).Compile() as Func<T, TResult>;
    static Func<T, T> BuildUnary(UnaryExpression unary)
        => Expression.Lambda<Func<T, T>>(unary, unary.Operand as ParameterExpression).Compile() as Func<T, T>;
    static Func<T, T, T> BuildBinary(BinaryExpression binary)
        => Expression.Lambda<Func<T, T, T>>(binary, binary.Left as ParameterExpression, binary.Right as ParameterExpression).Compile() as Func<T, T, T>;
    static Func<T, TResult> BuildUnary<TResult>(UnaryExpression unary)
        => Expression.Lambda<Func<T, TResult>>(unary, unary.Operand as ParameterExpression).Compile() as Func<T, TResult>;
    static Func<TResult> BuildNullary<TResult>(UnaryExpression unary)
        => Expression.Lambda<Func<TResult>>(unary).Compile() as Func<TResult>;
    static Func<T, T, TResult> BuildBinary<TResult>(BinaryExpression binary)
        => Expression.Lambda<Func<T, T, TResult>>(binary, binary.Left as ParameterExpression, binary.Right as ParameterExpression).Compile() as Func<T, T, TResult>;
    static Func<T, TResult> BuildUnary<TResult>(BinaryExpression binary, BindTo bind) 
    {
        switch (bind)
        {
            case BindTo.Left:
                return Expression.Lambda<Func<T, TResult>>(binary, binary.Left as ParameterExpression).Compile() as Func<T, TResult>;
            case BindTo.Right:
                return Expression.Lambda<Func<T, TResult>>(binary, binary.Right as ParameterExpression).Compile() as Func<T, TResult>;
            default:
                throw new NotSupportedException();
        }
    }
    public static readonly T Zero;
    public static readonly T One;
    public static Func<T, T> UnaryPlus { get; }
    public static Func<T, T> Negate { get; }
    public static Func<T, T, T> Add { get; }
    public static Func<T, T, T> Subtract { get; }
    public static Func<T, T, T> Multiply { get; }
    public static Func<T, T, T> Divide { get; }
    public static Func<T, T> Square { get; }
    public static Func<T, T> SquareRoot { get; }
    public static Func<T, T, T> Power { get; }
    public static Func<T, T> Raise(double exponent)
    {
        if (exponent == 0) return (x) => One;
        if (exponent == 1) return UnaryPlus;
        if (exponent == -1) return Invert;
        if (exponent == 2) return Square;
        if (exponent == -0.5) return SquareRoot;
        var T_arg = Expression.Parameter(typeof(T));
        return FromFunction<double>((x) => Math.Pow(x, exponent));
    }

    public static Func<T, T> Invert { get; }
    public static Func<T, bool> IsZero { get; }
    public static Func<T, T, bool> Equal { get; }
    public static Func<T, T, bool> NotEqual { get; }
    public static Func<T, T, bool> GreaterThan { get; }
    public static Func<T, T, bool> LessThan { get; }
    public static Func<T, T, bool> GreaterThanOrEqual { get; }
    public static Func<T, T, bool> LessThanOrEqual { get; }
    public static Func<int, T> ConvertFromInteger { get; }
    public static Func<float, T> ConvertFromFloat { get; }
    public static Func<double, T> ConvertFromDouble { get; }
    public static Func<TArg, T> ConvertFrom<TArg>()
    {
        var T_arg = Expression.Parameter(typeof(TArg));
        var conv = Expression.Convert(T_arg, typeof(T));
        return Expression.Lambda<Func<TArg, T>>(conv, T_arg).Compile() as Func<TArg, T>;
    }
    public static Func<T, T> FromFunction<TArg>(Func<TArg, TArg> function)
    {
        var T_arg1 = Expression.Parameter(typeof(T));
        var method = ((Delegate)function).Method;
        var convTArg = Expression.Convert(T_arg1, typeof(TArg));
        var callFunc = Expression.Call(method, convTArg);
        var result = Expression.Convert(callFunc, typeof(T));
        return Expression.Lambda<Func<T, T>>(result, T_arg1).Compile() as Func<T, T>;
    }
    public static Func<T, T, T> FromFunction<TArg>(Func<TArg, TArg, TArg> function)
    {
        var T_arg1 = Expression.Parameter(typeof(T));
        var T_arg2 = Expression.Parameter(typeof(T));
        var method = ((Delegate)function).Method;
        var convTArg1 = Expression.Convert(T_arg1, typeof(TArg));
        var convTArg2 = Expression.Convert(T_arg2, typeof(TArg));
        var callFunc = Expression.Call(method, convTArg1, convTArg2);
        var result = Expression.Convert(callFunc, typeof(T));
        return Expression.Lambda<Func<T,T,T>>(result, T_arg1, T_arg2).Compile() as Func<T, T, T>;
    }

    public static Func<T[], T> Sum
    {
        get
        {
            var add = Add;
            return (array) =>
            {
                if (array.Length==0)
                {
                    return Zero;
                }
                if (array.Length==1)
                {
                    return array[0];
                }
                T sum = array[0];
                for (int i = 1; i < array.Length; i++)
                {
                    sum = add(sum, array[i]);
                }
                return sum;
            };
        }
    }
}

上面的可重用类具有

Operation<T>
的静态属性,只要定义了适当的
T
,它就可以对任何
operator
类型进行数学运算。

一个例子是

public static Func<T, T, T> Multiply { get; }

返回适当乘法运算符的属性。

对于

Sum
属性,将通过使用
Operation<T>.Add
函数的循环返回 lambda。

© www.soinside.com 2019 - 2024. All rights reserved.