在未对齐的字节边界上有效地打包10位数据

时间:2016-01-13 19:37:43

标签: c++ bit-manipulation

我正在尝试对不与字节边界对齐的倍数进行一些打包。这是我正在努力做的事情。

我有一个512位数组(8个64位整数)的数据。在该数组内部是10位数据,对齐为2个字节。我需要做的是将512位降低到仅仅10位数据的320位(5个64位整数)。

我可以想到手动方式这样做,我遍历512位阵列的每个2字节部分,屏蔽掉10位,或者它一起考虑字节边界并创建输出64-位整数。像这样的东西:

void pack512to320bits(uint64 (&array512bits)[8], uint64 (&array320bits)[5])
{
    array320bits[0] = (array512bits[0] & maskFor10bits) | ((array512bits[0] & (maskFor10bits << 16)) << 10) | 
                  ((array512bits[0] & (maskFor10bits << 32)) << 20) | ((array512bits[0] << 48) << 30) | 
                  ((arrayFor512bits[1] & (maskFor10bits)) << 40) | ((arrayFor512bits[1] & (maskFor10bits << 16)) << 50) |
                  ((arrayFor512bits[1] & (0xF << 32)) << 60);
    array320bits[1] = 0;
    array320bits[2] = 0;
    array320bits[3] = 0;
    array320bits[4] = 0;
}

我知道这会有效,但它似乎容易出错,并且不容易扩展到更大的字节序列。

或者我可以通过输入数组,将所有10位值去掉一个向量,然后在最后连接它们,再次确保我对齐到字节边界。像这样:

void pack512to320bits(uint64 (&array512bits)[8], uint64 (&array320bits)[5])
{
    static uint64 maskFor10bits = 0x3FF;
    std::vector<uint16> maskedPixelBytes(8 * 4);

    for (unsigned int qword = 0; qword < 8; ++qword)
    {
        for (unsigned int pixelBytes = 0; pixelBytes < 4; ++pixelBytes)
        {
        maskedPixelBytes[qword * 4 + pixelBytes] = (array512bits[qword] & (maskFor10bits << (16 * pixelbytes)));
        } 
    }
    array320bits[0] = maskedPixelBytes[0] | (maskedPixelBytes[1] << 10) | (maskedPixelBytes[2] << 20) | (maskedPixelBytes[3] << 30) |
                  (maskedPixelBytes[4] << 40) | (maskedPixelBytes[5] << 50) | (maskedPixelBytes[6] << 60);
    array320bits[1] = (maskedPixelBytes[6] >> 4) | (maskedPixelBytes[7] << 6) ...


    array320bits[2] = 0;
    array320bits[3] = 0;
    array320bits[4] = 0;
}

这种方式更容易调试/读取,但效率低下,再次无法扩展到更大的字节序列。我想知道是否有一种更容易/算法的方法来进行这种打包。

1 个答案:

答案 0 :(得分:2)

您可以做什么,但这取决于某些条件以及您认为有效的条件。

首先,如果2个数组总是1个512位和1个320位数组,也就是说,如果传递的数组总是uint64 (&array512bits)[8]uint64 (&array320bits)[5],那么它就是&#39 ; s实际上是命令,如果幅度更高效,硬编码填充。

如果你想考虑更大的字节序列,你可以创建一个算法,将填充考虑在内并相应地移位这些位,然后迭代uint64的{​​{1}}值。较大的位数组。然而,使用这种方法,在程序集中引入了增加计算时间的分支(例如if (total_shifted < bit_size)等)。即使进行了优化,生成的程序集仍然比手动执行移位更复杂,执行此操作的代码需要考虑每个数组的大小,以确保它们可以适当地相互适应,从而添加更多计算时间(或一般代码复杂性)。

例如,请考虑此手动班次代码:

static void pack512to320_manual(uint64 (&a512)[8], uint64 (&a320)[5])
{
    a320[0] = (
        (a512[0] & 0x00000000000003FF)         | // 10 -> 10
        ((a512[0] & 0x0000000003FF0000) >> 6)  | // 10 -> 20
        ((a512[0] & 0x000003FF00000000) >> 12) | // 10 -> 30
        ((a512[0] & 0x03FF000000000000) >> 18) | // 10 -> 40
        ((a512[1] & 0x00000000000003FF) << 40) | // 10 -> 50
        ((a512[1] & 0x0000000003FF0000) << 34) | // 10 -> 60
        ((a512[1] & 0x0000000F00000000) << 28)); // 4  -> 64

    a320[1] = (
        ((a512[1] & 0x000003F000000000) >> 36) | // 6  -> 6
        ((a512[1] & 0x03FF000000000000) >> 42) | // 10 -> 16
        ((a512[2] & 0x00000000000003FF) << 16) | // 10 -> 26
        ((a512[2] & 0x0000000003FF0000) << 10) | // 10 -> 36
        ((a512[2] & 0x000003FF00000000) << 4)  | // 10 -> 46
        ((a512[2] & 0x03FF000000000000) >> 2)  | // 10 -> 56
        ((a512[3] & 0x00000000000000FF) << 56)); // 8  -> 64

    a320[2] = (
        ((a512[3] & 0x0000000000000300) >> 8)  | // 2  -> 2
        ((a512[3] & 0x0000000003FF0000) >> 14) | // 10 -> 12
        ((a512[3] & 0x000003FF00000000) >> 20) | // 10 -> 22
        ((a512[3] & 0x03FF000000000000) >> 26) | // 10 -> 32
        ((a512[4] & 0x00000000000003FF) << 32) | // 10 -> 42
        ((a512[4] & 0x0000000003FF0000) << 26) | // 10 -> 52
        ((a512[4] & 0x000003FF00000000) << 20) | // 10 -> 62
        ((a512[4] & 0x0003000000000000) << 14)); // 2  -> 64

    a320[3] = (
        ((a512[4] & 0x03FC000000000000) >> 50) | // 8  -> 8
        ((a512[5] & 0x00000000000003FF) << 8)  | // 10 -> 18
        ((a512[5] & 0x0000000003FF0000) << 2)  | // 10 -> 28
        ((a512[5] & 0x000003FF00000000) >> 4)  | // 10 -> 38
        ((a512[5] & 0x03FF000000000000) >> 10) | // 10 -> 48
        ((a512[6] & 0x00000000000003FF) << 48) | // 10 -> 58
        ((a512[6] & 0x00000000003F0000) << 42)); // 6  -> 64

    a320[4] = (
        ((a512[6] & 0x0000000003C00000) >> 22) | // 4  -> 4
        ((a512[6] & 0x000003FF00000000) >> 28) | // 10 -> 14
        ((a512[6] & 0x03FF000000000000) >> 34) | // 10 -> 24
        ((a512[7] & 0x00000000000003FF) << 24) | // 10 -> 34
        ((a512[7] & 0x0000000003FF0000) << 18) | // 10 -> 44
        ((a512[7] & 0x000003FF00000000) << 12) | // 10 -> 54
        ((a512[7] & 0x03FF000000000000) << 6));  // 10 -> 64
}

此代码仅接受uint64类型的数组,这些数组将相互适合,并考虑10位边界并相应地移位,以便将512位数组打包到320位数组中,所以做uint64* a512p = a512; pack512to320_manual(a512p, a320);这样的事情会在编译时失败,因为a512p不是uint64 (&)[8](即类型安全)。请注意,此代码已完全展开以显示位移序列,但您可以使用#defineenum来避免&#34;幻数&#34;并使代码更清晰。

如果您想扩展它以考虑更大的字节序列,您可以执行以下操作:

template < std::size_t X, std::size_t Y >
static void pack512to320_loop(const uint64 (&array512bits)[X], uint64 (&array320bits)[Y])
{
    const uint64* start = array512bits;
    const uint64* end = array512bits + (X-1);
    uint64 tmp = *start;
    uint64 tmask = 0;
    int i = 0, tot = 0, stot = 0, rem = 0, z = 0;
    bool excess = false;
    while (start <= end) {
        while (stot < bit_size) {
            array320bits[i] |= ((tmp & 0x00000000000003FF) << tot);
            tot += 10; // increase shift left by 10 bits
            tmp = tmp >> 16; // shift off 2 bytes
            stot += 16; // increase shifted total
            if ((excess = ((tot + 10) >= bit_size))) { break; }
        }
        if (stot == bit_size) {
            tmp = *(++start); // get next value
            stot = 0;
        }
        if (excess) {
            rem = (bit_size - tot); // remainder bits to shift off
            tot = 0;
            // create the mask
            tmask = 0;
            for (z = 0; z < rem; ++z) { tmask |= (1 << z); }
            // get the last bits
            array320bits[i++] |= ((tmp & tmask) << (bit_size - rem));
            // shift off and adjust
            tmp = tmp >> rem;
            rem = (10 - rem);
            // new mask
            tmask = 0;
            for (z = 0; z < rem; ++z) { tmask |= (1 << z); }
            array320bits[i] = (tmp & tmask);

            tot += rem; // increase shift left by remainder bits
            tmp = tmp >> (rem + 6); // shift off 2 bytes
            stot += 16;
            excess = false;
        }
    }
}

此代码还考虑字节边界并将它们打包到512位数组中。但是,此代码进行任何错误检查以确保大小正确匹配,因此如果X % 8 != 0Y % 5 != 0X和{{1} }}&gt; 0),你可能得到无效的结果!此外,由于循环,临时和转换,它比手动版慢得多,同时,功能代码的第一次读者可能需要更多时间来破译循环的完整意图和上下文代码与位移版本的代码。

如果你想要两者之间的东西,你可以使用手动打包功能,并在8和5组中迭代较大的字节数组,以确保字节正确对齐;类似于以下内容:

Y

这类似于手动打包功能,只会为检查增加一些微不足道的时间,但可以处理更大的阵列,这些阵列将相互打包(再次展开以显示序列)。

使用template < std::size_t X, std::size_t Y > static void pack512to320_manual_loop(const uint64 (&array512bits)[X], uint64 (&array320bits)[Y]) { if (((X == 0) || (X % 8 != 0)) || ((Y == 0) || (Y % 5 != 0)) || ((X < Y) || (Y % X != Y))) { // handle invalid sizes how you need here std::cerr << "Invalid sizes!" << std::endl; return; } uint64* a320 = array320bits; const uint64* end = array512bits + (X-1); for (const uint64* a512 = array512bits; a512 < end; a512 += 8) { *a320 = ( (a512[0] & 0x00000000000003FF) | // 10 -> 10 ((a512[0] & 0x0000000003FF0000) >> 6) | // 10 -> 20 ((a512[0] & 0x000003FF00000000) >> 12) | // 10 -> 30 ((a512[0] & 0x03FF000000000000) >> 18) | // 10 -> 40 ((a512[1] & 0x00000000000003FF) << 40) | // 10 -> 50 ((a512[1] & 0x0000000003FF0000) << 34) | // 10 -> 60 ((a512[1] & 0x0000000F00000000) << 28)); // 4 -> 64 ++a320; *a320 = ( ((a512[1] & 0x000003F000000000) >> 36) | // 6 -> 6 ((a512[1] & 0x03FF000000000000) >> 42) | // 10 -> 16 ((a512[2] & 0x00000000000003FF) << 16) | // 10 -> 26 ((a512[2] & 0x0000000003FF0000) << 10) | // 10 -> 36 ((a512[2] & 0x000003FF00000000) << 4) | // 10 -> 46 ((a512[2] & 0x03FF000000000000) >> 2) | // 10 -> 56 ((a512[3] & 0x00000000000000FF) << 56)); // 8 -> 64 ++a320; *a320 = ( ((a512[3] & 0x0000000000000300) >> 8) | // 2 -> 2 ((a512[3] & 0x0000000003FF0000) >> 14) | // 10 -> 12 ((a512[3] & 0x000003FF00000000) >> 20) | // 10 -> 22 ((a512[3] & 0x03FF000000000000) >> 26) | // 10 -> 32 ((a512[4] & 0x00000000000003FF) << 32) | // 10 -> 42 ((a512[4] & 0x0000000003FF0000) << 26) | // 10 -> 52 ((a512[4] & 0x000003FF00000000) << 20) | // 10 -> 62 ((a512[4] & 0x0003000000000000) << 14)); // 2 -> 64 ++a320; *a320 = ( ((a512[4] & 0x03FC000000000000) >> 50) | // 8 -> 8 ((a512[5] & 0x00000000000003FF) << 8) | // 10 -> 18 ((a512[5] & 0x0000000003FF0000) << 2) | // 10 -> 28 ((a512[5] & 0x000003FF00000000) >> 4) | // 10 -> 38 ((a512[5] & 0x03FF000000000000) >> 10) | // 10 -> 48 ((a512[6] & 0x00000000000003FF) << 48) | // 10 -> 58 ((a512[6] & 0x00000000003F0000) << 42)); // 6 -> 64 ++a320; *a320 = ( ((a512[6] & 0x0000000003C00000) >> 22) | // 4 -> 4 ((a512[6] & 0x000003FF00000000) >> 28) | // 10 -> 14 ((a512[6] & 0x03FF000000000000) >> 34) | // 10 -> 24 ((a512[7] & 0x00000000000003FF) << 24) | // 10 -> 34 ((a512[7] & 0x0000000003FF0000) << 18) | // 10 -> 44 ((a512[7] & 0x000003FF00000000) << 12) | // 10 -> 54 ((a512[7] & 0x03FF000000000000) << 6)); // 10 -> 64 ++a320; } } 在i7@2.2GHz上使用g++ 4.2.1对上述示例进行定时,得出这些平均时间:

  

-O3:0.135我们

     

pack512to320_loop:0.0017我们

     

pack512to320_manual:0.0020我们

以下是用于测试输入/输出和一般时序的测试代码:

pack512to320_manual_loop

同样,这只是通用测试代码,您的结果可能会有所不同。

希望可以提供帮助。