就地使用 Vector2f 结构数组作为浮点数数组
Use an array of Vector2f structs as an array of floats in-place
我有一个数组 Vector2f
结构,每个结构包含两个浮点数,我想将它传递给一个接受浮点数数组的函数。这些结构表示 2d 坐标,我希望最终结果为 [x0, y0, x1, y1, ... xn, yn]
。一些代码来演示:
using System;
using System.Runtime.InteropServices;
public class Test
{
[StructLayout(LayoutKind.Sequential)]
public struct Vector2f
{
float x;
float y;
public Vector2f(float x, float y)
{
this.x = x;
this.y = y;
}
}
public static void Main()
{
Vector2f[] structs = new Vector2f[]
{
new Vector2f(1f, 2f),
new Vector2f(3f, 4f)
};
// I want this to contain 1f, 2f, 3f, 4f
// But Syntax error, cannot convert type!
float[] floats = (float[])structs;
}
}
通过将内容复制到一个新的浮点数组中很容易,但是数据会变大,最好不要复制它。
由于内存布局,这可能无法实现。
Vector2f[] structs = new Vector2f[]
{
new Vector2f(1f, 2f),
new Vector2f(3f, 4f)
}
也许试试这个。
也许您应该尝试从数组中取出 1f、2f、3f 和 4f,然后将其放入浮点数组中。
转换它的一种方法是实现枚举:
IEnumerable<Single> GetFloats(IEnumerable<Vector2f> vectorList)
{
foreach (var vect in vectorList)
{
yield return vect.x;
yield return vect.y;
}
}
但这仍然会产生很多副本:一份到 var
一份到每个值(当屈服时)。
Struct 是值类型,因此无论如何您都必须进行复制。
我了解到您希望能够将同一块内存转换为一种新类型。但不幸的是,对于您的情况,C# 中的数组不仅仅是指向一块内存的指针。还有一些与之关联的元数据(如数组的 Length
)和一些特殊的内存对齐方式。所以我认为不可能这样做。
编辑: 即使你能做到(这远非我的专业知识,但也许有一些 IL 代码发出它是可能的),你可能会遇到问题托管内存已处理:它可以移动很多次。它在内存中没有静态位置。垃圾收集器在每次生成收集后压缩内存(不是每次,但仍然)。
虽然这是一个有趣的问题。我希望其他专家在这里给我们一些启发 ;)
编辑: 一种更有效的避免垃圾收集的方法,就像评论中建议的那样:
float[] GetFloats(Vector2f[] vectorArray)
{
var floats = new float[vectorArray.Length*2];
for (int i = 0; i < vectorArray.Length; ++i)
{
floats[i*2] = vectorArray[i].x;
floats[i*2 + 1] = vectorArray[i].y;
}
return floats;
}
编辑: 仍然不是您问题的直接答案,而是使用指针的更有效的副本,改编自 Unsafe Code Tutorial(请注意,这需要使用 /unsafe
):
static unsafe void FastCopy(Vector2f[] src, float[] dst, Int32 count)
{
if (src == null || dst == null)
{
throw new ArgumentException();
}
int srcLen = src.Length;
int dstLen = dst.Length;
if (srcLen < count ||
dstLen < count*2)
{
throw new ArgumentException();
}
// The following fixed statement pins the location of
// the src and dst objects in memory so that they will
// not be moved by garbage collection.
fixed (Vector2f* pSrc = src)
{
fixed (float* pDst = dst)
{
byte* ps = (byte*)pSrc;
byte* pd = (byte*)pDst;
count *= 8;
// Loop over the count in blocks of 4 bytes, copying a
// float (4 bytes) at a time:
for (int n = 0; n < count/4; n++)
{
*((float*)pd) = *((float*)ps);
pd += 4;
ps += 4;
}
}
}
}
如果您实际上不需要传递真正的数组,而只是需要像数组一样可以访问的东西,您可以这样做(未经测试):
public sealed class FloatArrayAdaptor : IReadOnlyList<float>
{
private Vector2f[] _data;
public FloatArrayAdaptor(Vector2f[] data)
{
_data = data;
}
public IEnumerator<float> GetEnumerator()
{
for (int i = 0; i < _data.Length; i++)
{
yield return _data[i].x;
yield return _data[i].y;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public int Count
{
get { return 2*_data.Length; }
}
public float this[int index]
{
get
{
//TODO: Add appropriate range checking and whatnot
int i = index>>1;
bool isX = (index & 0x1) == 0;
return isX ? _data[i].x : _data[i].y;
}
}
}
您将无法像在 C 中那样在 C# 中 'recast' 类型。最接近的方法是使用不安全代码并获取实际的浮点数*,但即便如此您也可以' 将该指针视为传递给采用数组的方法的安全数组。
我做了一些实验,'convert' 类型的不安全代码是可能的,但这是一个非常非常糟糕的 hack,你实际上不应该这样做。然而,它展示了一些关于 CLR object header data:
的有趣的事情
public static unsafe void Main()
{
Vector2f[] data = new Vector2f[10];
float[] dummy = new float[1];
//NOTE: This is horrible and you should never actually do it
//After this code, the original data array cannot be used safely anymore
fixed (void* rawData = &data[0])
fixed (void* rawDummy = &dummy[0])
{
int* intData = (int*)rawData;
int* intDummy = (int*)rawDummy;
//method table pointer is at X-4-sizeof(IntPtr)
//This is what identifies the type via RTTI
//We're going to forge our identity and change our size to change our type
//This assumes x86
intData[-2] = intDummy[-2];
//Our length is doubled
intData[-1] = 2*intData[-1];
}
if (data.GetType() == typeof(float[]))
Console.WriteLine("Type is now float[]!");
float[] floatData = (float[])(object)data;
Console.ReadLine();
}
基本上我们替换了方法 table 指针,这样类型现在看起来是 float[]
,然后我们将数组的长度字段加倍以进行补偿。这会编译、运行并报告类型现在是 float[]
。也就是说,这很可能会在以后以某种惊人的方式破坏 GC,而且它肯定非常依赖于实现,而且这不涉及 x64 与 x86。尽管如此,还是很有趣...不过,将其称为 'unsafe' 是有原因的。希望这有助于说明为什么不能以安全的方式支持它,因为 RTTI(通过方法 table 指针)被烘焙到存储数据的内存中。
现在尝试回答为什么不可能。
C# 是一种类型安全的语言。这意味着在兼容类型上只允许某些转换(强制转换)。这解释了为什么不允许使用此代码:
Vector2f[] structs;
float[] floats = (float[])structs;
的确,C# 使用引用而不是指针。其中一个区别是引用不是内存中的静态位置。垃圾回收期间可以移动对象。
但是,C# 允许使用 unsafe
代码进行一些指针运算。为此,必须通知垃圾收集器不应为所考虑的对象移动内存(并且不得使引用无效)。这是通过 fixed
关键字完成的。
换句话说,要获得指向引用对象的指针(结构体的相同逻辑),您首先需要冻结内存中的对象位置(这也称为固定内存):
fixed (Vector2f* pStructs = structs)
fixed (float* pFloats = floats)
{
...
现在一切都已修复,您不能更改这些指针的地址。例如,这是不允许的:
pFloats = (float*)pStructs // this will change the address of pFloats which is fixed: illegal
此外,您不能将指针转换回引用:
float[] floats = (float[])pFloats; // not allowed
总之,一旦获得指针,就可以将一些字节从一个位置移动到另一个位置,但不能更改相应引用的位置(只能移动数据)。
希望这能回答您的问题。
旁注,如果你有很多性能关键的操作,你可以考虑用C++实现,暴露一些高级函数并从中调用一些函数C#.
我有一个数组 Vector2f
结构,每个结构包含两个浮点数,我想将它传递给一个接受浮点数数组的函数。这些结构表示 2d 坐标,我希望最终结果为 [x0, y0, x1, y1, ... xn, yn]
。一些代码来演示:
using System;
using System.Runtime.InteropServices;
public class Test
{
[StructLayout(LayoutKind.Sequential)]
public struct Vector2f
{
float x;
float y;
public Vector2f(float x, float y)
{
this.x = x;
this.y = y;
}
}
public static void Main()
{
Vector2f[] structs = new Vector2f[]
{
new Vector2f(1f, 2f),
new Vector2f(3f, 4f)
};
// I want this to contain 1f, 2f, 3f, 4f
// But Syntax error, cannot convert type!
float[] floats = (float[])structs;
}
}
通过将内容复制到一个新的浮点数组中很容易,但是数据会变大,最好不要复制它。
由于内存布局,这可能无法实现。
Vector2f[] structs = new Vector2f[]
{
new Vector2f(1f, 2f),
new Vector2f(3f, 4f)
}
也许试试这个。
也许您应该尝试从数组中取出 1f、2f、3f 和 4f,然后将其放入浮点数组中。
转换它的一种方法是实现枚举:
IEnumerable<Single> GetFloats(IEnumerable<Vector2f> vectorList)
{
foreach (var vect in vectorList)
{
yield return vect.x;
yield return vect.y;
}
}
但这仍然会产生很多副本:一份到 var
一份到每个值(当屈服时)。
Struct 是值类型,因此无论如何您都必须进行复制。
我了解到您希望能够将同一块内存转换为一种新类型。但不幸的是,对于您的情况,C# 中的数组不仅仅是指向一块内存的指针。还有一些与之关联的元数据(如数组的 Length
)和一些特殊的内存对齐方式。所以我认为不可能这样做。
编辑: 即使你能做到(这远非我的专业知识,但也许有一些 IL 代码发出它是可能的),你可能会遇到问题托管内存已处理:它可以移动很多次。它在内存中没有静态位置。垃圾收集器在每次生成收集后压缩内存(不是每次,但仍然)。
虽然这是一个有趣的问题。我希望其他专家在这里给我们一些启发 ;)
编辑: 一种更有效的避免垃圾收集的方法,就像评论中建议的那样:
float[] GetFloats(Vector2f[] vectorArray)
{
var floats = new float[vectorArray.Length*2];
for (int i = 0; i < vectorArray.Length; ++i)
{
floats[i*2] = vectorArray[i].x;
floats[i*2 + 1] = vectorArray[i].y;
}
return floats;
}
编辑: 仍然不是您问题的直接答案,而是使用指针的更有效的副本,改编自 Unsafe Code Tutorial(请注意,这需要使用 /unsafe
):
static unsafe void FastCopy(Vector2f[] src, float[] dst, Int32 count)
{
if (src == null || dst == null)
{
throw new ArgumentException();
}
int srcLen = src.Length;
int dstLen = dst.Length;
if (srcLen < count ||
dstLen < count*2)
{
throw new ArgumentException();
}
// The following fixed statement pins the location of
// the src and dst objects in memory so that they will
// not be moved by garbage collection.
fixed (Vector2f* pSrc = src)
{
fixed (float* pDst = dst)
{
byte* ps = (byte*)pSrc;
byte* pd = (byte*)pDst;
count *= 8;
// Loop over the count in blocks of 4 bytes, copying a
// float (4 bytes) at a time:
for (int n = 0; n < count/4; n++)
{
*((float*)pd) = *((float*)ps);
pd += 4;
ps += 4;
}
}
}
}
如果您实际上不需要传递真正的数组,而只是需要像数组一样可以访问的东西,您可以这样做(未经测试):
public sealed class FloatArrayAdaptor : IReadOnlyList<float>
{
private Vector2f[] _data;
public FloatArrayAdaptor(Vector2f[] data)
{
_data = data;
}
public IEnumerator<float> GetEnumerator()
{
for (int i = 0; i < _data.Length; i++)
{
yield return _data[i].x;
yield return _data[i].y;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public int Count
{
get { return 2*_data.Length; }
}
public float this[int index]
{
get
{
//TODO: Add appropriate range checking and whatnot
int i = index>>1;
bool isX = (index & 0x1) == 0;
return isX ? _data[i].x : _data[i].y;
}
}
}
您将无法像在 C 中那样在 C# 中 'recast' 类型。最接近的方法是使用不安全代码并获取实际的浮点数*,但即便如此您也可以' 将该指针视为传递给采用数组的方法的安全数组。
我做了一些实验,'convert' 类型的不安全代码是可能的,但这是一个非常非常糟糕的 hack,你实际上不应该这样做。然而,它展示了一些关于 CLR object header data:
的有趣的事情 public static unsafe void Main()
{
Vector2f[] data = new Vector2f[10];
float[] dummy = new float[1];
//NOTE: This is horrible and you should never actually do it
//After this code, the original data array cannot be used safely anymore
fixed (void* rawData = &data[0])
fixed (void* rawDummy = &dummy[0])
{
int* intData = (int*)rawData;
int* intDummy = (int*)rawDummy;
//method table pointer is at X-4-sizeof(IntPtr)
//This is what identifies the type via RTTI
//We're going to forge our identity and change our size to change our type
//This assumes x86
intData[-2] = intDummy[-2];
//Our length is doubled
intData[-1] = 2*intData[-1];
}
if (data.GetType() == typeof(float[]))
Console.WriteLine("Type is now float[]!");
float[] floatData = (float[])(object)data;
Console.ReadLine();
}
基本上我们替换了方法 table 指针,这样类型现在看起来是 float[]
,然后我们将数组的长度字段加倍以进行补偿。这会编译、运行并报告类型现在是 float[]
。也就是说,这很可能会在以后以某种惊人的方式破坏 GC,而且它肯定非常依赖于实现,而且这不涉及 x64 与 x86。尽管如此,还是很有趣...不过,将其称为 'unsafe' 是有原因的。希望这有助于说明为什么不能以安全的方式支持它,因为 RTTI(通过方法 table 指针)被烘焙到存储数据的内存中。
现在尝试回答为什么不可能。
C# 是一种类型安全的语言。这意味着在兼容类型上只允许某些转换(强制转换)。这解释了为什么不允许使用此代码:
Vector2f[] structs;
float[] floats = (float[])structs;
的确,C# 使用引用而不是指针。其中一个区别是引用不是内存中的静态位置。垃圾回收期间可以移动对象。
但是,C# 允许使用 unsafe
代码进行一些指针运算。为此,必须通知垃圾收集器不应为所考虑的对象移动内存(并且不得使引用无效)。这是通过 fixed
关键字完成的。
换句话说,要获得指向引用对象的指针(结构体的相同逻辑),您首先需要冻结内存中的对象位置(这也称为固定内存):
fixed (Vector2f* pStructs = structs)
fixed (float* pFloats = floats)
{
...
现在一切都已修复,您不能更改这些指针的地址。例如,这是不允许的:
pFloats = (float*)pStructs // this will change the address of pFloats which is fixed: illegal
此外,您不能将指针转换回引用:
float[] floats = (float[])pFloats; // not allowed
总之,一旦获得指针,就可以将一些字节从一个位置移动到另一个位置,但不能更改相应引用的位置(只能移动数据)。
希望这能回答您的问题。
旁注,如果你有很多性能关键的操作,你可以考虑用C++实现,暴露一些高级函数并从中调用一些函数C#.