如何将通用数组转换为另一种类型?

时间:2022-08-02 21:19:36

I'm still trying to find a fast way of how to convert a generic array of type TOutput to another array of type TInput. All my arrays are always of a numeric datatype, but since C# has no type constraint to Numeric as often requested, I currently have to live with this constraint. The suggested methods, like casting to an object before, seems to slow down my cast tremendously. Currently I have a large if/else construct that check for a type and cast to a defined type using pointer arithmetic, but this is way to large to handle for the future. Parallel.For seems a good way to get rid of pointers and to speed up thing, but still the C# generic constraints seem to be a problem, but still the Tout in the code below is a problem. Here's my code:

我仍在尝试找到一种快速方法,将TOutput类型的通用数组转换为另一个TInput类型的数组。我的所有数组总是一个数值数据类型,但由于C#没有经常请求的数字类型约束,我现在不得不忍受这个约束。建议的方法,比如之前投射到一个物体,似乎极大地减慢了我的演员阵容。目前我有一个大的if / else构造,它检查一个类型并使用指针算法强制转换为已定义的类型,但这是为了将来处理大的方法。 Parallel.For似乎是摆脱指针和加快速度的好方法,但C#泛型约束似乎仍然存在问题,但下面代码中的Tout仍然是个问题。这是我的代码:

    public static OutputType[] Cast<InputType, OutputType>(InputType[] inputArray_in)
    {
        var aRange = Partitioner.Create(0, inputArray_in.Length);
        OutputType[] aResult = new OutputType[inputArray_in.Length];

        Parallel.ForEach(aRange, (r) =>
        {
            for (int i = r.Item1; i < r.Item2; i++)
            {
                aResult[i] = (OutputType)(inputArray_in[i]);
            }
        });

        return aResult;
    }

Example:

float[] A = { 0.1f, 0.2f, 0.6f };
int []B = Cast<float, int>(A);

In all cases my array types are numerical values (float, short, double,...) and most of the time, the arrays are about 512x512 images, but in a stack of about 1000 slices in a volume. Do you see any chance to have a simple way of performing this?

在所有情况下,我的数组类型都是数值(float,short,double,...),大多数情况下,数组大约是512x512个图像,但是在一个卷中大约1000个切片的堆栈中。你有没有机会有一个简单的方法来执行此操作?

Test Code

public static class CastTest
{
    delegate double[] CastMethod(int[] input);

    public static unsafe double[] Cast1(int[] input)
    {
        int N = input.Length;
        double[] output = new double[N];

        for (int i = 0; i < N; i++) output[i] = (double)(input[i]);

        return output;
    }

    public static unsafe double[] Cast2(int[] input)
    {
        int N = input.Length;
        double[] output = new double[N];

        fixed (double* output_pinned = output)
        {
            double* outp = output_pinned;

            fixed (int* input_pinned = input)
            {
                int* inp = input_pinned;

                for (int i = 0; i < N; i++, inp++, outp++) *outp = (double)(*inp);
            }

            return output;
        }
    }

    public static unsafe double[] Cast3(int[] input)
    {
        int N = input.Length;
        double[] output = new double[N];

        fixed (double* output_pinned = output)
        {
            double* outp = output_pinned;

            fixed (int* input_pinned = input)
            {
                int* inp = input_pinned;

                for (int i = 0; i < N; i++) outp[i] = (double)(inp[i]);
            }

            return output;
        }
    }

    public static unsafe double[] Cast4(int[] input)
    {
        int N = input.Length;
        double[] output = new double[N];

        fixed (double* output_pinned = output)
        {
            fixed (int* input_pinned = input)
            {
                for (int i = 0; i < N; i++) output_pinned[i] = (double)(input_pinned[i]);
            }
        }

        return output;
    }

    public static unsafe double[] Cast5(int[] input)
    {
        return Array.ConvertAll<int, double>(input, x => (double)x);
    }

    public static double[] Cast6(int[] input)
    {
        var aRange = Partitioner.Create(0, input.Length);

        int N = input.Length;
        double[] output = new double[N];

        Parallel.ForEach(aRange, (r) =>
            {
                for (int i = r.Item1; i < r.Item2; i++) output[i] = (double)(input[i]);
            });

        return output;
    }

    public unsafe static double[] Cast7(int[] input)
    {
        var aRange = Partitioner.Create(0, input.Length);

        int N = input.Length;
        double[] output = new double[N];

        Parallel.ForEach(aRange, (r) =>
        {
            fixed (double* output_pinned = output)
            {
                double* outp = output_pinned + r.Item1;

                fixed (int* input_pinned = input)
                {
                    int* inp = input_pinned + r.Item1;

                    for (int i = r.Item1; i < r.Item2; i++, outp++, inp++) *outp = (double)(*inp);
                }
            }
        });

        return output;
    }

    public unsafe static double[] Cast8(int[] input)
    {
        var result = (from m in input.AsParallel() select (double)m).ToArray();

        return result;
    }


    public static double[] Cast9(int[] input)
    {
        return  (from m in input select (double)m).ToArray(); 
    }

    public static double[] Cast10(int[] input)
    {
        return (from m in input.AsParallel() select (double)m).ToArray(); 
    }

    public static double[] Cast11(int[] input)
    {
        return new List<double>(input.Select(p => (double)p)).ToArray(); 
    }

    static int[] A = new int[100000];
    const int runs = 10000;

    public static void StartTest()
    {
        TestMethod("1", Cast1);
        TestMethod("2", Cast2);
        TestMethod("3", Cast3);
        TestMethod("4", Cast4);
        TestMethod("5", Cast5);
        TestMethod("6", Cast6);
        TestMethod("7", Cast7);
        TestMethod("8", Cast8);
        TestMethod("9", Cast9);
        TestMethod("10", Cast10);
        TestMethod("11", Cast11);
    }

    static void TestMethod(string Name, CastMethod method)
    {
        var timer = Stopwatch.StartNew();

        for (int i = 0; i < runs; i++) { double[] res = method(A); }

        timer.Stop();

        Console.WriteLine(String.Format("{0}: {1}ms", Name, timer.ElapsedMilliseconds));
    }
}

Thank you Martin

谢谢马丁

4 个解决方案

#1


8  

There is no magic conversion (when using generics etc) between numeric types like this; there are tricks like Convert.ChangeType, or dynamic, but both involve an intermediate box/unbox.

像这样的数字类型之间没有神奇的转换(当使用泛型等时);有一些技巧,如Convert.ChangeType,或动态,但都涉及一个中间框/ unbox。

Personally, I'd just be using:

就个人而言,我只是在使用:

float[] A = { 0.1f, 0.2f, 0.6f };
int[] B = Array.ConvertAll(A, x => (int)x);

This offloads the conversion logic to the compiler (to use the correct conversion from float to int without intermediaries or reflection). It is, however, not usable inside generics - i.e. x => (OutputType)x will not work.

这将转换逻辑卸载到编译器(使用从float到int的正确转换,无需中间件或反射)。但是,它在泛型内部不可用 - 即x =>(OutputType)x将不起作用。

#2


2  

Did you try this?

你试过这个吗?

public static TOut[] Cast<TOut,TIn>(TIn[] arr) {
        return arr.Select(x => (TOut)Convert.ChangeType(x,typeof(TOut))).ToArray();
}

#3


1  

Why don't you use simply Cast for lists, it's LINQ Cast method member of System.Linq.Enumerable :

为什么不使用Cast for lists,它是System.Linq.Enumerable的LINQ Cast方法成员:

float[] A = { 0.1f, 0.2f, 0.6f };
int[] B = A.Cast(Of int).ToArray();

You can read about it here : Enumerable.Cast(Of TResult) Method

你可以在这里阅读:Enumerable.Cast(Of TResult)方法

#4


0  

I did three trivial experiments over a float array with 38988 items in it (I just cut and pasted a bunch of arbitrary values over and over again)

我在一个浮点数组中进行了三次微不足道的实验,其中有38988个项目(我只是一遍又一遍地剪切并粘贴了一堆任意值)

//_a = array of floats


// pretty standard way of doing it 4ms
_result = (from m in _a select (int)m).ToArray();

// I was rather disappointed with this 35ms
_result = (from m in _a.AsParallel() select (int)m).ToArray();

// using a list rather surprised me 1ms
_result = new List<int>(_a.Select(p => (int)p)).ToArray();

so I don't know how these compare with your tests but I'd say selecting into the generic list is pretty effective.

所以我不知道这些与你的测试相比如何,但我会说选择通用列表非常有效。

EDIT:

I'm adding my code. I must be missing something because I get quite different results from my example as compared to running your example.

我正在添加我的代码。我必须遗漏一些东西,因为与运行你的例子相比,我得到了与我的例子完全不同的结果。

class Program
{
    static void Main(string[] args)
    {

        using (var x = new ArrayCast())
        {
            x.Run();
        } 

        using (var x = new ArrayCastList())
        {
            x.Run();
        }
        using (var x = new ArrayCastAsParallel())
        {
            x.Run();
        }

        while (Console.Read() != 'q')
        {
            ;    // do nothing...
        }
    }
}

public abstract class Experiment : IAmATest, IDisposable
{
    private Stopwatch _timer;


    protected bool IgnoreAssert { get; set; }

    #region IAmATest Members

    public abstract void Arrange();
    public abstract void Act();
    public abstract void Assert();

    #endregion

    public void Run()
    {
        _timer = Stopwatch.StartNew();
        Arrange();
        _timer.Stop();

        Console.WriteLine(String.Join(":", "Arrange", _timer.ElapsedMilliseconds));

        _timer = Stopwatch.StartNew();
        for (int i = 1; i < 1000; i++)
        Act();
        _timer.Stop();

        Console.WriteLine(String.Join(":", "Act", _timer.ElapsedMilliseconds));

        if (IgnoreAssert) { return; }

        _timer = Stopwatch.StartNew();
        Assert();
        _timer.Stop();

        Console.WriteLine(String.Join(":", "Assert", _timer.ElapsedMilliseconds));
    }

    public abstract void Dispose();
}

public class ArrayCast : Experiment
{
    private int[] _a;
    double[] _result;

    public override void Arrange()
    {
        IgnoreAssert = true;
        _a = new int[100000];
    }

    public override void Act()
    {
            _result = (from m in _a select (double)m).ToArray();
    }

    public override void Assert() { }

    public override void Dispose() { _a = null; }
}

public class ArrayCastAsParallel : Experiment
{
    private int[] _a;
    double[] _result;

    public override void Arrange()
    {
        IgnoreAssert = true;
        _a = new int[100000];
    }

    public override void Act()
    {
        _result = (from m in _a.AsParallel() select (double)m).ToArray();
    }

    public override void Assert() { }

    public override void Dispose() { _a = null; }
}

public class ArrayCastList : Experiment
{
    private int[] _a;
    double[] _result;

    public override void Arrange()
    {
        IgnoreAssert = true;
        _a = new int[100000];
    }

    public override void Act()
    {
        var x = new List<double>(_a.Select(p => (double)p));
    }

    public override void Assert() { }

    public override void Dispose() { _a = null; }
}

#1


8  

There is no magic conversion (when using generics etc) between numeric types like this; there are tricks like Convert.ChangeType, or dynamic, but both involve an intermediate box/unbox.

像这样的数字类型之间没有神奇的转换(当使用泛型等时);有一些技巧,如Convert.ChangeType,或动态,但都涉及一个中间框/ unbox。

Personally, I'd just be using:

就个人而言,我只是在使用:

float[] A = { 0.1f, 0.2f, 0.6f };
int[] B = Array.ConvertAll(A, x => (int)x);

This offloads the conversion logic to the compiler (to use the correct conversion from float to int without intermediaries or reflection). It is, however, not usable inside generics - i.e. x => (OutputType)x will not work.

这将转换逻辑卸载到编译器(使用从float到int的正确转换,无需中间件或反射)。但是,它在泛型内部不可用 - 即x =>(OutputType)x将不起作用。

#2


2  

Did you try this?

你试过这个吗?

public static TOut[] Cast<TOut,TIn>(TIn[] arr) {
        return arr.Select(x => (TOut)Convert.ChangeType(x,typeof(TOut))).ToArray();
}

#3


1  

Why don't you use simply Cast for lists, it's LINQ Cast method member of System.Linq.Enumerable :

为什么不使用Cast for lists,它是System.Linq.Enumerable的LINQ Cast方法成员:

float[] A = { 0.1f, 0.2f, 0.6f };
int[] B = A.Cast(Of int).ToArray();

You can read about it here : Enumerable.Cast(Of TResult) Method

你可以在这里阅读:Enumerable.Cast(Of TResult)方法

#4


0  

I did three trivial experiments over a float array with 38988 items in it (I just cut and pasted a bunch of arbitrary values over and over again)

我在一个浮点数组中进行了三次微不足道的实验,其中有38988个项目(我只是一遍又一遍地剪切并粘贴了一堆任意值)

//_a = array of floats


// pretty standard way of doing it 4ms
_result = (from m in _a select (int)m).ToArray();

// I was rather disappointed with this 35ms
_result = (from m in _a.AsParallel() select (int)m).ToArray();

// using a list rather surprised me 1ms
_result = new List<int>(_a.Select(p => (int)p)).ToArray();

so I don't know how these compare with your tests but I'd say selecting into the generic list is pretty effective.

所以我不知道这些与你的测试相比如何,但我会说选择通用列表非常有效。

EDIT:

I'm adding my code. I must be missing something because I get quite different results from my example as compared to running your example.

我正在添加我的代码。我必须遗漏一些东西,因为与运行你的例子相比,我得到了与我的例子完全不同的结果。

class Program
{
    static void Main(string[] args)
    {

        using (var x = new ArrayCast())
        {
            x.Run();
        } 

        using (var x = new ArrayCastList())
        {
            x.Run();
        }
        using (var x = new ArrayCastAsParallel())
        {
            x.Run();
        }

        while (Console.Read() != 'q')
        {
            ;    // do nothing...
        }
    }
}

public abstract class Experiment : IAmATest, IDisposable
{
    private Stopwatch _timer;


    protected bool IgnoreAssert { get; set; }

    #region IAmATest Members

    public abstract void Arrange();
    public abstract void Act();
    public abstract void Assert();

    #endregion

    public void Run()
    {
        _timer = Stopwatch.StartNew();
        Arrange();
        _timer.Stop();

        Console.WriteLine(String.Join(":", "Arrange", _timer.ElapsedMilliseconds));

        _timer = Stopwatch.StartNew();
        for (int i = 1; i < 1000; i++)
        Act();
        _timer.Stop();

        Console.WriteLine(String.Join(":", "Act", _timer.ElapsedMilliseconds));

        if (IgnoreAssert) { return; }

        _timer = Stopwatch.StartNew();
        Assert();
        _timer.Stop();

        Console.WriteLine(String.Join(":", "Assert", _timer.ElapsedMilliseconds));
    }

    public abstract void Dispose();
}

public class ArrayCast : Experiment
{
    private int[] _a;
    double[] _result;

    public override void Arrange()
    {
        IgnoreAssert = true;
        _a = new int[100000];
    }

    public override void Act()
    {
            _result = (from m in _a select (double)m).ToArray();
    }

    public override void Assert() { }

    public override void Dispose() { _a = null; }
}

public class ArrayCastAsParallel : Experiment
{
    private int[] _a;
    double[] _result;

    public override void Arrange()
    {
        IgnoreAssert = true;
        _a = new int[100000];
    }

    public override void Act()
    {
        _result = (from m in _a.AsParallel() select (double)m).ToArray();
    }

    public override void Assert() { }

    public override void Dispose() { _a = null; }
}

public class ArrayCastList : Experiment
{
    private int[] _a;
    double[] _result;

    public override void Arrange()
    {
        IgnoreAssert = true;
        _a = new int[100000];
    }

    public override void Act()
    {
        var x = new List<double>(_a.Select(p => (double)p));
    }

    public override void Assert() { }

    public override void Dispose() { _a = null; }
}