3

I want to convert a multidimensional array of int[,] to an array of ushort[,] and if possible without looping over each dimension.

I found a post where object[,] is converted to double[,] by using Array.Copy. Unfortunately this only works because the objects are allready of type double. Is there any chance to achieve a similar result for converting int to ushort (assuming it always fits)?

var input = new [,]
{
    {1,1,1},
    {2,2,2},
    {3,3,3}
};
var output = new ushort[3, 3];

// Convert
Array.Copy(input, output, input.Length);

The above code compiles, but fails on execution because it can not convert from int to ushort. I know why this happens, I just want to tell .NET that it should just convert. As I said I know the easiest solution are two loops. I am just curious if there is an alternative.

Conclusion: Unfortunately there is no fast and built-in way to do this. Therefore I recommend the obvious and readable solution of going for the double loop unless you really need lightning speed fast conversion.

for(var i=0; i<input.GetLength(0); i++)
for(var j=0; j<input.GetLength(1); j++)
    output[i,j] = (ushort)input[i,j];

However this is not the accepted solution since for my personal interest I was asking for the fastest alternative and that is, as expected, and evil and unsafe pointer conversion.

Community
  • 1
  • 1
Toxantron
  • 2,218
  • 12
  • 23
  • 1
    Are you certain that your int values wont exceed the threshold of a ushort? – CathalMF Apr 13 '16 at 15:54
  • Yes, based on the input of the method that creates `int[,]` I can be sure. – Toxantron Apr 13 '16 at 15:56
  • You can use LINQ, although it just loops over the array internally. It *does* look prettier though. – EvilTak Apr 13 '16 at 16:10
  • 1
    @EvilTak Linq cannot be used to create 2-D arrays. At best you can iterate over all the source items and flatten it to a 1-D array. – D Stanley Apr 13 '16 at 16:11
  • Which method do you suggest? – Toxantron Apr 13 '16 at 16:12
  • 1
    No, it is not a duplicate as it refers to an array of reference types. – Toxantron Apr 13 '16 at 16:13
  • Possible duplicate of : http://stackoverflow.com/questions/6065695/how-can-i-convert-a-boxed-two-dimensional-array-to-a-two-dimensional-string-arra – Olivier Citeau Apr 13 '16 at 16:14
  • @Toxantron The concept is the same, though - there's no build-in method to "convert" a 2-D array, whether they are value types or reference types. – D Stanley Apr 13 '16 at 16:15
  • If you compare it to the post I linked `Array.Copy` can be used for an array of object if the elements are castable. – Toxantron Apr 13 '16 at 16:17
  • 1
    True, but you can't "cast" an `int` to a `ushort`. The "cast" operator in C# actually does a conversion in that case, which Array.Copy` does not support. – D Stanley Apr 13 '16 at 16:22
  • I spent a bit of time trying to figure this out and even posted my own question See the answer to this. Its shorter than your accepted answer. http://stackoverflow.com/questions/36618959/converting-a-2d-array-into-a-2d-array-of-a-different-type-int-ushort/36620155#36620155 – CathalMF Apr 14 '16 at 10:26
  • Thanks for letting my know. ;) I think the shortest are nested for loops, but you found the LinQ solution and this thread has the fastest solution. – Toxantron Apr 14 '16 at 10:33

3 Answers3

3

This will work without any conversion overhead. Also, it's evil, but I'd do it.

    static unsafe void FastCopy(int[,] source, ushort[,] destination)
    {
        //pin the source and destination arrays
        fixed (int* pSource = source) fixed (ushort* pDestination = destination)
        {
            //compute the pointers for the elements just past the arrays
            //as termination conditions
            var pSourceEnd = pSource + source.Length;
            var pDestinationEnd = pDestination + destination.Length;

            //setup our iterator variables
            var pSourceCurrent = pSource;
            var pDestinationCurrent = pDestination;

            //loop over each element until you run out of elements in 
            //either the source or destination
            while (pSourceCurrent < pSourceEnd && pDestinationCurrent < pDestinationEnd)
            {
                //copy the two lowest bytes in each source int to the
                //destination ushort
                *pDestinationCurrent++ = *(ushort*)pSourceCurrent++;
            }
        }
    }
hoodaticus
  • 3,772
  • 1
  • 18
  • 28
  • @Toxantron: I don't see how. It might look fancy with pointers and all but this solution still iterates through the array which was what you wanted to avoid in the first place. That said, it will be fast although I'm not sure I'd implement this solution unless I had no other choice due to performance constraints. – InBetween Apr 13 '16 at 18:37
  • 1
    @InBetween: It loops but it doesn't loop over both dimensions, thus meeting his requirement. – hoodaticus Apr 13 '16 at 18:40
  • 2
    Yes it still iterates but not in a double loop (I know it has the same number of iterations). Like I said, I was curious if there was something faster than the obvious solution. I agree with you that it should only be used when everything else is too slow. – Toxantron Apr 13 '16 at 18:40
  • 1
    It eliminates the overhead of the second loop. It also eliminates the per-element array bounds check. Finally, no conversion overhead as you're not casting a value - just a pointer. The only way this can be made faster AFAIK is to copy from int to int, in which case CIL's cpblk instruction or x86's rep movsb would be faster and need no loop. – hoodaticus Apr 13 '16 at 18:43
  • 2
    @Toxantron & hoodaticus Very true I misread the requirements. I'd still avoid this whenever possible ;) but it does the job fast. +1 – InBetween Apr 13 '16 at 18:44
  • It _implicitly_ loops over both dimensions since a 2-D array is stored in memory as contiguous 1-D arrays. – D Stanley Apr 13 '16 at 18:47
  • 1
    Like I said, I know it has the same number of iterations but it loses the overhead of an additional nested loop. – Toxantron Apr 13 '16 at 18:59
  • 1
    I updated the post to make sure future readers don't follow our evil path of unsafe code just because this is what **I** was looking for. ;) – Toxantron Apr 13 '16 at 19:05
  • 1
    Good idea :). Unsafe code is for those of us who love it and know how to debug heap and stack corruptions XD Also, performance. – hoodaticus Apr 13 '16 at 19:46
  • You can trivially make it not evil by, you know, verifying the sizes before the copy loop: `if (source.Length != destination.Length) throw new ArgumentException(...)` (null checks first, of course). `unsafe` doesn't mean "is required to corrupt memory if incorrect arguments are passed", just "is not validated for full safety by the CLR". – Jeroen Mostert Apr 14 '16 at 11:38
  • @JeroenMostert, I agree and do have a mechanism in there that prevents memory errors. An exception would be better though for that particular method signature, you're right. – hoodaticus Apr 14 '16 at 12:47
  • Since we're obsessed with performance in this case, pre-emptively checking might help you because you can reduce the loop to a simple counting loop, so the inner copy is just `pDestination[i] = unchecked((ushort) pSource[i])`. Disclaimer: I have no idea what ultimately produces more efficient code after the JIT compiler is through with it; this isn't C, after all. But it should definitely be checked by those who *are* interested in performance; "pointers everywhere = faster" does not necessarily hold. – Jeroen Mostert Apr 14 '16 at 14:19
  • @JeroenMostert whether or not merging the counts into one is more efficient depends on the length of the loop. Though I am curious why you made this comment ""pointers everywhere = faster" does not necessarily hold" since no one made that claim. It seems like a wild strawman which leads me further to question your motivation for making it. – hoodaticus Apr 14 '16 at 14:33
  • You're not making an argument, I'm not trying to refute it, there is no strawman. I'm simply pointing out (and I hope this is uncontroversial, but if not just Google it) that a lot of novice programmers (who may encounter this code in the future) do believe using pointers where possible (including through aliasing) conveys magical performance properties. I'm just saying that, if you're using code like this for the performance benefits, you'll have to test it (and the alternatives), even more so than for the C code this resembles (where compilers have a lot more experience). – Jeroen Mostert Apr 14 '16 at 14:46
  • On the other hand, not having pointers in your toolkit because of the wanton spread of Fear, Uncertainty, and Doubt will result in people writing less performant code when the opportunity to use pointers for optimization comes along. – hoodaticus Apr 14 '16 at 14:49
1

Well, you can't convert in-place because arrays are low-level structures, so the memory footprint of an int[,] and a ushort[,] would be different. Also, there's not a built-in method to convert a 2-D array, so the simplest option is to loop and fill a new array with the converted values.

D Stanley
  • 149,601
  • 11
  • 178
  • 240
  • I was hoping there was some neat overload of `ConvertAll` somewhere that uses fancy unsafe code and pointers to do this. – Toxantron Apr 13 '16 at 16:21
  • 1
    @Toxantron: such code would hardly be more efficient than simple managed loops, since converting an `int` to a `ushort` can't just be done by aliasing stuff through pointers, no matter how fancy you get. – Jeroen Mostert Apr 13 '16 at 16:29
  • 1
    I think for a positive integer the two smallest bytes of `int` should be castable to `ushort`. Am I wrong? – Toxantron Apr 13 '16 at 16:33
  • For a single value, yes. But an array is a set of contiguous values in memory. So the resulting array would be a smaller size. So it's a conversion, not a cast. – D Stanley Apr 13 '16 at 16:40
  • You are right, the simplest solution is a loop, but my posts starts by saying that I am aware of this. Anyway thanks for the clarification for future readers. – Toxantron Apr 13 '16 at 19:01
1

You seem to be conflating very different types of "casts". Do not mix reference conversions with type conversions and boxing conversions:

 class Foo: IFoo { ... }
 var foo = new Foo();
 var iFoo = foo; //implicit reference conversion

 var i = 1;
 var o = (object)i; //boxing
 var u = (uint)i; //type conversion

Reference conversions do not change the object at all, they only change the type of the reference pointing at it. Type conversions (for lack of a better term, english is not my native language) give you back an alltogether different object. Boxing and unboxing conversions are special but behavior wise, in the scope of your question, are similar to reference conversions.

That C# has the same syntax for all these semantically very different casts is, in my opnion, unfortunate.

Array.Copy will allow you to copy between arrays of different type when there is a reference type conversion or boxing/unboxing coversion between them. The reason being that you are not really changing the object, just it's reference (the bits that make up the object stay the same and therefore its size too (again, not strictly true with boxing/unboxing)). That is, the following are all valid:

var foos = new Foo[] { null, null, null };
var iFoos = new IFoo[3];
Array.Copy(foos, iFoos, 3); //reference conversion

var ints = new[] { 1, 2, 3 };
var objs = new object[3];
Array.Copy(ints, objs, 3); //boxing conversion
Array.Copy(objs, ints, 3); //unboxing conversion

While the following is not:

var ints = new[] { 1, 2, 3 };
var uints = new uint[3];
Array.Copy(ints, uints, 3); //type conversion. Runtime error.

Worth noting that the following is also not valid:

var objs = new object[] { 1, 2, 3 }; //boxed int[]
var uints = new uint[3];
Array.Copy(objs, uints, 3); // Unboxing conversion. Runtime error. Huh?

Why is that? Isn't there an unboxing conversion from object to uint? Yes there is, but the boxed value is really an int and you can only unbox a boxed value to its real type. Due to the same reason, the following will also give you a runtime error:

obect i = 1;
var u = (uint)i;

So what does that have to do with anything you've asked?

Well, that there is no inbuilt way to convert a n-dimensional array from one type array to another if the conversion is not a simple reference or boxing/unboxing conversion. You either have to flatten the array using Linq or create your own extension method and in either case you will have to iterate the array to perform the conversion because you have to create a new object (potentially with different size, bits, etc.) for each element in the source array.

Obviosuly, performance will be significantly slower than Array.Copy.

InBetween
  • 32,319
  • 3
  • 50
  • 90