Why my three dimensions array is getting so slow when I assign another array to it?
SerializableQuaternion is a custom type for saving, so it's different from Quaternion.
Is there a better way to increase the copy performance?
var anro = Quaternion[60,60,60]; //216000 element
Rotate = new SerializableQuaternion[anro.GetLength(0), anro.GetLength(1), anro.GetLength(2)];
if (anro != null)
{
for (int a = 0; a < anro.GetLength(0); a )
{
for (int b = 0; b < anro.GetLength(1); b )
{
for (int c = 0; c < anro.GetLength(2); c )
{
Rotate[a, b, c] = anro[a, b, c];
}
}
}
}
or how make the array.copy can copy the different version types?
my custom type
[System.Serializable]
public struct SerializableQuaternion
{
public float x;
public float y;
public float z;
public float w;
public SerializableQuaternion(float rX, float rY, float rZ, float rW)
{
x = rX;
y = rY;
z = rZ;
w = rW;
}
public override string ToString()
{
return String.Format("[{0}, {1}, {2}, {3}]", x, y, z, w);
}
public static implicit operator Quaternion(SerializableQuaternion rValue)
{
return new Quaternion(rValue.x, rValue.y, rValue.z, rValue.w);
}
public static implicit operator SerializableQuaternion(Quaternion rValue)
{
return new SerializableQuaternion(rValue.x, rValue.y, rValue.z, rValue.w);
}
}
CodePudding user response:
Just tested and it takes about 11ms
which isn't actually that slow in my eyes - depends of course how often you have to do this.
However,
Array.Copy
would be fast but the main issue is thatQuaternion
andSerializableQuaternion
are different types so you can not use it.You can also not use
Buffer.BlockCopy
since the types are not primitives (float
,int
, etc).
But what you can do is using Marhsal.Copy
.
For this to work you have to know your how your structs are layed out in bytes which luckily we happen to know from the Source Code of Quaternion
[StructLayout(LayoutKind.Sequential)] [Unity.IL2CPP.CompilerServices.Il2CppEagerStaticClassConstruction] public partial struct Quaternion : IEquatable<Quaternion>, IFormattable { // X component of the Quaternion. Don't modify this directly unless you know quaternions inside out. public float x; // Y component of the Quaternion. Don't modify this directly unless you know quaternions inside out. public float y; // Z component of the Quaternion. Don't modify this directly unless you know quaternions inside out. public float z; // W component of the Quaternion. Don't modify this directly unless you know quaternions inside out. public float w; ....
so you only need to make sure that your type looks exactly the same byte wise (which seems already the case).
Now I am no marshal expert so there might even be a more direct way of doing this but you could have an in between step converting the Quternion[,,]
to byte[]
and from there to SerializableQuaternion[,,]
like e.g.
private static void MarshalCopy<TFrom, TTo>(TFrom[,,] source, TTo[,,] destination) where TFrom : struct where TTo : struct
{
if (Marshal.SizeOf(typeof(TFrom)) != Marshal.SizeOf(typeof(TTo)))
{
throw new InvalidOperationException($"{typeof(TFrom).AssemblyQualifiedName} can not by marshaled into {typeof(TTo).AssemblyQualifiedName}");
}
var sourceHandle = GCHandle.Alloc(source, GCHandleType.Pinned);
var destinationHandle = GCHandle.Alloc(destination, GCHandleType.Pinned);
try
{
var sourcePointer = sourceHandle.AddrOfPinnedObject();
var bytes = new byte[source.Length * Marshal.SizeOf(typeof(TFrom))];
Marshal.Copy(sourcePointer, bytes, 0, bytes.Length);
var destinationPointer = destinationHandle.AddrOfPinnedObject();
Marshal.Copy(bytes, 0, destinationPointer, bytes.Length);
}
finally
{
if (sourceHandle.IsAllocated)
sourceHandle.Free();
if (destinationHandle.IsAllocated)
destinationHandle.Free();
}
}
and a little demo
var origin = new Quaternion[60, 60, 60];
for (var a = 0; a < origin.GetLength(0); a )
{
for (var b = 0; b < origin.GetLength(1); b )
{
for (var c = 0; c < origin.GetLength(2); c )
{
origin[a, b, c] = Quaternion.Euler(UnityEngine.Random.Range(-360f, 360f), UnityEngine.Random.Range(-360f, 360f), UnityEngine.Random.Range(-360f, 360f));
}
}
}
var target = new SerializableQuaternion[origin.GetLength(0), origin.GetLength(1), origin.GetLength(2)];
var sw = new Stopwatch();
sw.Restart();
for (var a = 0; a < origin.GetLength(0); a )
{
for (var b = 0; b < origin.GetLength(1); b )
{
for (var c = 0; c < origin.GetLength(2); c )
{
target[a, b, c] = origin[a, b, c];
}
}
}
sw.Stop();
UnityEngine.Debug.Log("Your way: " sw.ElapsedMilliseconds "ms");
sw.Restart();
var target2 = new SerializableQuaternion[origin.GetLength(0), origin.GetLength(1), origin.GetLength(2)];
MarshalCopy(origin, target2);
sw.Stop();
UnityEngine.Debug.Log("Alternative way: " sw.ElapsedMilliseconds "ms");
for (var a = 0; a < origin.GetLength(0); a )
{
for (var b = 0; b < origin.GetLength(1); b )
{
for (var c = 0; c < origin.GetLength(2); c )
{
if ((Quaternion)target2[a, b, c] != target[a, b, c])
{
UnityEngine.Debug.LogError("Oh no error in copy!");
}
}
}
}
=>
Your way: 12ms
Alternative way: 1ms
and from another test run with 600, 60, 60
Your way: 123ms
Alternative way: 12ms
so I think we can say mine is faster by a factor of 10
;)