按特定顺序重新排列乌龙

时间:2019-11-21 15:13:04

标签: c# sorting bit-manipulation

对于计算机游戏,我使用存储在 ulong 中的位掩码(希望我正确使用此术语)。 该位掩码正在与其他位掩码交互,当另一个位掩码重叠时,该位掩码会将位的1s变为0s。与其他位掩码的交互结束后,我要检查特定位是否为1,一个接一个。但是,它的顺序不符合ulong中的顺序。

在交互阶段,由于对称性原因,它们最初的排序方式有所不同,这使得可以对预排序的位掩码进行旋转。

我写下了原始位的顺序。如果需要该命令帮助我,这里是:

31, 39, 34, 27, 43, 30, 38, 26, 42, 33, 29, 37, 23, 47, 25, 41, 22, 46, 32, 28, 36, 21, 45, 24, 40, 20, 44, 48, 16, 49, 17, 50, 18, 51, 19, 12, 52, 8, 56, 13, 53, 4, 60, 0, 14, 54, 9, 57, 15, 55, 5, 61, 1, 10, 58, 6, 62, 11, 59, 2, 7, 63, 3

(例如,第一个数字为31。这意味着预排序的ulong的第31位(从0开始计数)必须进入已排序的ulong的第一位。然后,第39位的值进入进入排序的ulong中的第二个,依此类推)

我的游戏代码的这一部分是最关键的性能,因此我寻求实现这一目标的最有效方法。它与人群机制有关,并且该代码显示的效率越高,我可以并且将包含的人群成员越多。

我也愿意使用低MB大小的查找表。我的一个想法是将ulong分为四个短裤,将它们放入一个可查询的表中,每个返回一个排序后的短裤。但是,由于不仅需要在这四个块之间移动许多位,所以仅此一项是行不通的。

我的另一个想法是:

ulong unsorted = x;
ulong sorted = 0;
sorted += (unsorted & 0x0001000010000000) >> 18 //bits that are shifted the same manner
//go on

2 个答案:

答案 0 :(得分:2)

一些初步注意事项:

  • 您的示例数据只有63个值。出于下面我的代码示例的目的,我将35的缺失值随意添加到列表的末尾。
  • 在您的问题中,没有任何关于您如何首先陷入这种混乱状态的细节。不用说,但是无论如何我都会提到: best 解决方案是完全消除问题的解决方案。在您的确切情况下是否有可能这样做,不要紧怎么做,我不能说,因为问题中缺少细节。

所有所说的…

  

我的另一个想法是:

     
ulong unsorted = x;
ulong sorted = 0;
sorted += (unsorted & 0x0001000010000000) >> 18 //bits that are shifted the same manner
//go on

该想法可能会奏效。但是您没有在问题中提供足够的详细信息让其他人知道,因为我们没有有关为何对位进行重新排序的任何信息。您似乎在想编写特殊情况的代码,以利用首先导致位排序的任何模式,但是由于没有关于该模式的具体细节,没有其他人可以充实这种想法。

  

我的一个想法是将ulong分为四个短裤,将它们放入一个可查询的表中,每个返回一个排序后的短裤。但是,由于不仅需要在这四个块之间移动许多位,所以仅此一项是行不通的。

这个想法也行得通。没错,您无法映射到short值(甚至是ushort)。但是您可以映射到ulong值,然后合并结果。

当然,对于任何代码来说,第一件事也是最重要的事情就是让它起作用。因此,恕我直言,从一个可以验证的更简单的实现入手是有意义的。完成之后,您就可以继续进行其他选择了。

这里是一种选择:

static readonly int[] bitOrder = // NOTE: 35 not in original example, added here at end arbitrarily
{                                // so that a valid implementation can be demonstrated
    31, 39, 34, 27, 43, 30, 38, 26, 42, 33, 29, 37, 23, 47, 25, 41,
    22, 46, 32, 28, 36, 21, 45, 24, 40, 20, 44, 48, 16, 49, 17, 50,
    18, 51, 19, 12, 52,  8, 56, 13, 53,  4, 60,  0, 14, 54,  9, 57,
    15, 55,  5, 61,  1, 10, 58,  6, 62, 11, 59,  2,  7, 63,  3, 35
};

private static ulong[] BuildBitwiseMapping()
{
    ulong[] bitwiseMapping = new ulong[bitOrder.Length];

    for (int i = 0; i < bitwiseMapping.Length; i++)
    {
        bitwiseMapping[i] = 1UL << bitOrder[i];
    }

    return bitwiseMapping;
}

private static ulong SortBits(ulong[] bitwiseMapping, ulong value)
{
    ulong sorted = 0, mask = 1;

    for (int i = 0; i < bitwiseMapping.Length; i++, mask <<= 1)
    {
        if ((value & bitwiseMapping[i]) != 0)
        {
            sorted |= mask;
        }
    }

    return sorted;
}

您将首先调用BuildBitwiseMapping(),然后对每次调用SortBits()使用返回的数组。自然地,您可以将内置数组作为static readonly字段并入,以简化对SortBits()的调用。

这必须为每个位循环一次,这不一定是最有效的。但是除了循环条件之外,严格来说,它仅是查找和按位“或”运算,因此已经相当快了。

如果您想使用查找表的思想来改进它,那就很简单:

private static ulong[][] BuildWordwiseMapping(ulong[] bitwiseMapping)
{
    ulong[][] result = new ulong[4][];
    ulong increment = 1;

    for (int i = 0; i < result.Length; i++)
    {
        result[i] = new ulong[ushort.MaxValue + 1];
        ulong value = 0;

        for (int j = 0; j <= ushort.MaxValue; j++)
        {
            result[i][j] = SortBits(bitwiseMapping, value);
            value += increment;
        }

        increment = value;
    }

    return result;
}

private static ulong SortBits(ulong[][] wordwiseMapping, ulong value)
{
    ulong sorted = 0;

    for (int i = 0; i < wordwiseMapping.Length; i++, value >>= 16)
    {
        int ushortPart = (int)(value & 0xffff);

        sorted |= wordwiseMapping[i][ushortPart];
    }

    return sorted;
}

上面的代码从第一个示例中的64位按位表开始,并使用它来构建四个更大的表以用于按字查找。

您需要四个不同的表,一个用于原始ulong值的每个位子集,一个表,因为它们的映射当然不同。它们可以分布在整个ulong结果中,因此表元素大小仍然需要为ulong

这将为您提供四个表,每个表包含65536个元素,每个元素为8个字节长,总内存占用为2MB。另一方面,虽然较简单的按位版本每个值需要64次循环迭代,而按字版本仅需要4次循环。这可以显着提高吞吐量。否则可能总体上影响不大;它实际上取决于该代码实际上到底有多大的瓶颈,以及四表版本的速度有多快。

您可能在上面已经注意到,从技术上讲,实际上不需要在每个表中处理值0,因为不会为这些值设置任何位,无论它们出现在什么位置在原始的ulong中。但是,实际上可能是对0进行 compare 并处理这种特殊情况,然后只查找NOP“零映射到零”值,这会在性能上花费更多。

答案 1 :(得分:2)

执行此排列的另一种方法是:

ulong permute(ulong x)
{
    x = bit_permute_step(x, 0x00000000c539d22d, 32);
    x = bit_permute_step(x, 0x0000cbc10000749e, 16);
    x = bit_permute_step(x, 0x0039007e00a9007f, 8);
    x = bit_permute_step(x, 0x06010e04070e0507, 4);
    x = bit_permute_step(x, 0x2113320311022110, 2);
    x = bit_permute_step(x, 0x5401151151100141, 1);
    x = bit_permute_step(x, 0x0032323110011333, 2);
    x = bit_permute_step(x, 0x030b0a0b080e0d00, 4);
    x = bit_permute_step(x, 0x00a5009500220097, 8);
    x = bit_permute_step(x, 0x00005cd6000043cc, 16);
    x = bit_permute_step(x, 0x00000000b3f003f3, 32);
    return x;
}

bit_permute_step的定义为:

ulong bit_permute_step(ulong source, ulong mask, int shift)
{
    ulong t = ((source >> shift) ^ source) & mask;
    return (source ^ t) ^ (t << shift);
}

该构造是一个完整的11步Beneš网络(6 * 2-1,其中6是64的以2为底的对数)。这是一种非常通用的技术,可以处理任何排列,在许多情况下,有些特殊情况下的技术会更好,但我找不到这种方法。

相关问题