Reputation: 142
I have a class that wraps 2d float[][] array into 1d float[] array:
class Wrapper
{
int CountX;
int CountY;
float[] Values;
}
for example something like this
{1, 2, 3, 4}
{5, 6, 7, 8}
would be wrapped into
var wr = new Wrapper
{
Values = new float[8]{1,2,3,4,5,6,7,8},
CountX = 4,
CountY = 2
};
And I want to find the fastest way to get its row or column. Currently I'm using these methods
class Wrapper
{
int CountX;
int CountY;
float[] Values;
public float[] GetRow(int row)
{
var res = new float[CountX];
for(int i = 0; i < CountX; i++)
{
res[i] = Values[CountX*row + i];
}
return res;
}
public float[] GetColumn(int column)
{
var res = new float[CountY];
for (int i = 0; i < CountY; i++)
{
res[i] = Values[column + CountX*i];
}
return res;
}
}
With this usage:
var wr = new Wrapper
{
Values = new float[8]{1,2,3,4,5,6,7,8},
CountX = 4,
CountY = 2
};
//{1, 2, 3, 4}
//{5, 6, 7, 8}
wr.GetRow(1) // 5 6 7 8
wr.GetColumn(3) // 4 8
And what I am trying to accomplish is increasing performance. I'm pretty sure there is a way to do it faster using unsafe code, but I don't really know how to use pointers in C#
Upvotes: 0
Views: 651
Reputation: 36639
For rows you should return a ReadonlySpan<T>
since that would be a zero-copy operation. This is assuming your storage is row-major.
For columns you will need to copy elements. It can be useful to take the destination as a parameter. That way it might be possible to avoid repeated allocations. You can also update the index directly in the loop, I would expect that to help a little bit, but I have not done any profiling.
public void CopyColumn(int column, Span<float> res)
{
for (int i = column; i < Values.Length; i += CountX)
{
res[i] = Values[i];
}
}
If you want an array as a result you can add a helper method:
public float[] GetColumn(int column){
var res = new float[CountY];
CopyColumn(column, res);
return res;
}
Upvotes: 1
Reputation: 1064024
The fastest way to do this would usually be to not allocate or copy anything. Switching to unsafe
is not going to help much with the real cost here, which is the allocation and copy; at best you can avoid some bounds checks.
Assuming you keep a 1D backing array, on the minor axis (by which I mean: contiguous data), it should be trivially possible to get a Span<float>
of the relevant chunk of data: nothing more than that i.e. new ReadOnlySpan<float>(Values, CountX*row, CountX)
; on the major axis, maybe return something that is simply a flyweight readonly struct
with an indexer into the data?
However, honestly I wonder if you should just use a float[,]
and use regular x/y indexing.
Example; note that choosing which dimension to use as the inner one is important, as the direct Span<T>
access will be faster than the indirect Row<T>
access:
using System.Runtime.InteropServices;
var obj = new ArrayWrapper<float>(2, 3);
obj[1, 2] = 4;
Write(obj);
var row = obj.GetColumn(1);
for (int i = 0; i < row.Length; i++)
row[i] = i;
Write(obj);
var col = obj.GetRow(1);
for (int i = 0; i < col.Length; i++)
col[i] = i + 10;
Write(obj);
col = obj.GetRow(2);
for (int i = 0; i < col.Length; i++)
col[i] = i + 20;
Write(obj);
static void Write(ArrayWrapper<float> arr)
{
for (int y = 0; y < arr.Height; y++)
{
for (int x = 0; x < arr.Width; x++)
{
Console.Write(arr[x, y]);
Console.Write('\t');
}
Console.WriteLine();
}
Console.WriteLine();
}
readonly struct ArrayWrapper<T>
{
private readonly T[,] _array;
public int Width => _array.GetLength(0);
public int Height => _array.GetLength(1);
public ArrayWrapper(int width, int height) => _array = new T[width, height];
public ref T this[int x, int y] => ref _array[x, y];
public readonly Span<T> GetColumn(int x)
=> MemoryMarshal.CreateSpan(ref _array[x, 0], Height);
public readonly Row<T> GetRow(int y) => new(_array, y);
}
readonly struct Row<T>
{
private readonly T[,] _array;
private readonly int _y;
public Row(T[,] array, int y)
{
_array = array;
_y = y;
}
public bool IsEmpty => Length == 0;
public int Length => _array.GetLength(0); // Width
public ref T this[int x] => ref _array[x, _y];
}
Upvotes: 2