C# 将多个有符号整数打包成一个 64 位值
C# Packing multiple signed integers into a single 64-bit value
我需要打包和解包多个值 to/from 单个 64 位值。我有 3 个有符号整数(x、y、z)。我想将它们打包成一个 64 位值(有符号或无符号对我来说无关紧要),分别使用 24、16 和 24 位作为值。这是我的要求:
1) 我可以提前确保存储的值不超过我用来将它们存储为 64 位值的位数限制,因此不需要进行额外的检查。
2) 初始值是有符号的,所以我认为可能需要执行某种位魔术以确保没有丢失任何内容。
3) 此转换将进行很多次,因此需要快速进行。我知道在 C++ 中,这可以很容易地通过将值存储在指定整数长度的结构中,然后建立一个仅指向可用于 64 位值的第一个值的指针来完成。使用这种方法,实际上不需要做任何数学运算,一切都只是内存读取或正确。据我所知,这不能在 C# 中如此简单地完成,但 C# 是我必须为这个项目工作的。
4)我真的不关心64位值是有符号的还是无符号的,只要我可以双向操作并恢复初始值,无论使用什么类型都可以用于字典键。
口罩和班次可能是您最好的选择。您可以在 C# 中创建显式布局结构,但没有 24 位基元,因此您会被自己绊倒并且必须屏蔽 anyway。一旦你移动,通常最好是无符号工作(尤其是右移时),所以:
ulong val = ((((ulong)x) & 0xFFFFFF) << 40) // 24 bits of x, left-shifted by 40
| ((((ulong)y) & 0xFFFF) << 24) // 16 bits of y, left-shifted by 24
| (((ulong)z) & 0xFFFFFF); // 24 bits of z, no left-shift
并反转它(假设我们想要 uint
值):
uint a = (uint)((val >> 40) & 0xFFFFFF),
b = (uint)((val >> 24) & 0xFFFF),
c = (uint)(val & 0xFFFFFF);
With this method, there really isn't any math that needs done, everything is just memory read or write.
不完全是,当您将部分整数设置到位域中时就完成了数学计算,因此需要进行相当多的数学计算。
As far as I can tell, this can't be done quite so simply in C#, but C# is what I have to work with for this project.
正确,在 C# 中,您需要手动编写将位组合成 long
的代码。假设您已经进行了范围检查,这就相对简单了:
static long Pack(long a24, long b16, long c24) {
// a24 can go with no masking, because its MSB becomes
// the MSB of the 64-bit number. The other two numbers
// need to be truncated to deal with 1s in the upper bits of negatives.
return a24<<40 | (b16&0xffffL)<<24 | (c24&0xffffffL);
}
static void Unpack(long packed, out int a24, out int b16, out int c24) {
a24 = (int)(packed >> 40); // Sign extension is done in the long
b16 = ((int)(packed >> 8)) >> 16; // Sign extension is done in the int
c24 = ((int)(packed << 8)) >> 8; // Sign extension is done in the int
}
这些值在 long 中是字节对齐的,您需要利用 Intel/AMD 处理器能够直接访问它们以使代码尽可能快。杀手级要求是24位大小,处理器只能直接read/write8、16、32或64位。
这也是 C++ 中的一个问题,您必须使用位域。 C# 不支持它们,您必须编写 C++ 编译器自动生成的代码。像这样:
[StructLayout(LayoutKind.Explicit)]
struct MyPackedLong {
[FieldOffset(0)] uint item1; // 24-bit field
[FieldOffset(3)] uint item2; // 24-bit field
[FieldOffset(6)] ushort item3; // 16-bit field
public uint Item1 {
get { return item1 & 0xffffff; }
set { item1 = (item1 & 0xff000000) | value; }
}
public uint Item2 {
get { return item2 & 0xffffff; }
set { item2 = (item2 & 0xff000000) | value; }
}
public ushort Item3 {
get { return item3; }
set { item3 = value; }
}
}
这里有一些技巧,请注意 item2
如何有意偏移 3,这样就不需要移动了。我对这些字段进行了排序,因此它们的访问是最佳的,将 16 位值放在最前面或最后是最好的。没有经过彻底测试,应该在球场上。在线程代码中要小心,写入不是原子的。
我需要打包和解包多个值 to/from 单个 64 位值。我有 3 个有符号整数(x、y、z)。我想将它们打包成一个 64 位值(有符号或无符号对我来说无关紧要),分别使用 24、16 和 24 位作为值。这是我的要求:
1) 我可以提前确保存储的值不超过我用来将它们存储为 64 位值的位数限制,因此不需要进行额外的检查。
2) 初始值是有符号的,所以我认为可能需要执行某种位魔术以确保没有丢失任何内容。
3) 此转换将进行很多次,因此需要快速进行。我知道在 C++ 中,这可以很容易地通过将值存储在指定整数长度的结构中,然后建立一个仅指向可用于 64 位值的第一个值的指针来完成。使用这种方法,实际上不需要做任何数学运算,一切都只是内存读取或正确。据我所知,这不能在 C# 中如此简单地完成,但 C# 是我必须为这个项目工作的。
4)我真的不关心64位值是有符号的还是无符号的,只要我可以双向操作并恢复初始值,无论使用什么类型都可以用于字典键。
口罩和班次可能是您最好的选择。您可以在 C# 中创建显式布局结构,但没有 24 位基元,因此您会被自己绊倒并且必须屏蔽 anyway。一旦你移动,通常最好是无符号工作(尤其是右移时),所以:
ulong val = ((((ulong)x) & 0xFFFFFF) << 40) // 24 bits of x, left-shifted by 40
| ((((ulong)y) & 0xFFFF) << 24) // 16 bits of y, left-shifted by 24
| (((ulong)z) & 0xFFFFFF); // 24 bits of z, no left-shift
并反转它(假设我们想要 uint
值):
uint a = (uint)((val >> 40) & 0xFFFFFF),
b = (uint)((val >> 24) & 0xFFFF),
c = (uint)(val & 0xFFFFFF);
With this method, there really isn't any math that needs done, everything is just memory read or write.
不完全是,当您将部分整数设置到位域中时就完成了数学计算,因此需要进行相当多的数学计算。
As far as I can tell, this can't be done quite so simply in C#, but C# is what I have to work with for this project.
正确,在 C# 中,您需要手动编写将位组合成 long
的代码。假设您已经进行了范围检查,这就相对简单了:
static long Pack(long a24, long b16, long c24) {
// a24 can go with no masking, because its MSB becomes
// the MSB of the 64-bit number. The other two numbers
// need to be truncated to deal with 1s in the upper bits of negatives.
return a24<<40 | (b16&0xffffL)<<24 | (c24&0xffffffL);
}
static void Unpack(long packed, out int a24, out int b16, out int c24) {
a24 = (int)(packed >> 40); // Sign extension is done in the long
b16 = ((int)(packed >> 8)) >> 16; // Sign extension is done in the int
c24 = ((int)(packed << 8)) >> 8; // Sign extension is done in the int
}
这些值在 long 中是字节对齐的,您需要利用 Intel/AMD 处理器能够直接访问它们以使代码尽可能快。杀手级要求是24位大小,处理器只能直接read/write8、16、32或64位。
这也是 C++ 中的一个问题,您必须使用位域。 C# 不支持它们,您必须编写 C++ 编译器自动生成的代码。像这样:
[StructLayout(LayoutKind.Explicit)]
struct MyPackedLong {
[FieldOffset(0)] uint item1; // 24-bit field
[FieldOffset(3)] uint item2; // 24-bit field
[FieldOffset(6)] ushort item3; // 16-bit field
public uint Item1 {
get { return item1 & 0xffffff; }
set { item1 = (item1 & 0xff000000) | value; }
}
public uint Item2 {
get { return item2 & 0xffffff; }
set { item2 = (item2 & 0xff000000) | value; }
}
public ushort Item3 {
get { return item3; }
set { item3 = value; }
}
}
这里有一些技巧,请注意 item2
如何有意偏移 3,这样就不需要移动了。我对这些字段进行了排序,因此它们的访问是最佳的,将 16 位值放在最前面或最后是最好的。没有经过彻底测试,应该在球场上。在线程代码中要小心,写入不是原子的。