【问题标题】:Array of pairs of 3 bit elements3 位元素对数组
【发布时间】:2010-11-06 09:32:22
【问题描述】:

由于内存限制,我必须将一些值对存储在一个 6 位/对(3 位/值)的数组中。当我想根据该对的索引将该数组作为普通数组访问时,问题就来了。 数组是这样的

|--byte 0 | --byte 1 | --byte 2  
|00000011 | 11112222 | 22333333   ...  and so on, the pattern repeats.  
|------|-------|--------|------|  
 pair 0  pair 1  pair 2  pair 3 

 => 4 pairs / 3 bytes

您可以看到,有时(对于可被 1 和 2 整除的索引)提取值需要 2 个字节。
我创建了一个给定索引的函数,返回该对中的第一个值(3 位)和另一个(也是 3 位)。

void GetPair(char *array, int index, int &value1, int &value2) {
    int groupIndex = index >> 2; // Divide by 4 to get the index of the group of 3 bytes (with 4 pairs)
    // We use 16 bits starting with the first byte from the group for indexes divisible by 0 and 1,  
    // 16 bits starting with the second byte when divisible by 2 and 3
    short int value = *(short int *)(array + groupIndex + ((index & 0x02) >> 1));

    switch(index & 0x03) { // index % 4
        case 0: { 
            // extract first 3 bits
            value1 = (value & 0xE000) >> 13;
            // extract the next 3 bits
            value2 = (value & 0x1C00) >> 10;
            break;
        }
        case 1: {
            value1 = (value & 0x380) >> 7;
            value2 = (value & 0x70) >> 4;
            break;
        }
        case 2: {
            value1 = (value & 0xE00) >> 9;
            value2 = (value & 0x1C0) >> 6;
            break;
        }
        case 3: {
            value1 = (value & 0x38) >> 2;
            value2 = value & 0x7;
            break;
        }
}

现在我的问题是:有没有更快的方法来提取这些值?

我做了一个测试,当使用 2 个字节/对(1 个字节/值)时,访问所有对(总共 53 个)大约需要 6 秒 1 亿次。使用紧凑数组时,大约需要 22 秒 :((可能是因为它需要计算所有这些掩码和位移)。
我试图尽可能清楚地解释......如果没有,请原谅我。

【问题讨论】:

    标签: c++ optimization memory


    【解决方案1】:

    现代架构甚至不再处理单个字节;它们寻址 4 字节字并提取您请求的部分。因此,在库存硬件上,您可能会看到每对使用 4 个字节并自行提取片段的改进。每个条目 4 个字节也可能更快,但加载第二个字的成本可能大于屏蔽和移位的成本。或者可能不是;现代处理器很奇怪。剖析一下看看!

    【讨论】:

    • 更准确的说法是,虽然现代处理器仍然使用字节作为其地址空间的基本单位,但它们的宽数据总线和缓存线意味着如果您只要求单个字节,CPU无论如何,最终可能会将整个单词加载到缓存中。确定此类事物的最佳数据大小高度依赖于架构,考虑到极端的内存限制,这可能不是 x86。
    【解决方案2】:

    这是一个用速度换取内存效率的经典案例。我假设您在内存稀缺的环境中工作,并且您需要将很多很多项目推入这个数组,否则这可能不值得您花时间。

    您可以通过使用查找表查找正确的移位和掩码值来消除 switch 语句。

    short int shift1[4] = { 13, 7, 9, 2 };
    short int shift2[4] = { 10, 4, 6, 0 };
    short int mask1[4] = { 0xe000, 0x0380, 0x0e00, 0x38 };
    short int mask2[4] = { 0x1c00, 0x0700, 0x1c, 0x07 };
    
    int index = value % 4; /* you're not saving any time by using bitwise AND but you are making your code less readable */
    value1 = (value & mask1[index]) >> shift1;
    value2 = (value & mask2[index]) >> shift2;
    

    我们的想法是消除任何分支。然而,每条路径都很短,以至于它可能并不重要。在我的测试中(PowerPC 上的 gcc)几乎没有任何区别。但是,这台机器上的内存带宽足够慢,以至于两个版本都比仅使用直接数组访问和每个值 1 个字节要快。

    【讨论】:

    • 这在我的测试中节省了大约 4 秒!谢谢。
    【解决方案3】:

    这个怎么样?它消除了对掩码和移位值的内存访问。 (当然(不可移植的)假设是char是8位,short是16位。还假设index * 6不会溢出int。)

    void GetPair(char *array, int index, int &value1, int &value2)
    {
       unsigned shift = 10 - index * 6 % 8;
       unsigned short data = (*(unsigned short *)(array + index * 6 / 8) >> shift) & 0x3f;
       value2 = data & 7;
       value1 = data >> 3;
    }
    

    不过,读取跨越 16 位边界的短片可能会受到惩罚。当我还在跟踪这些事情时,曾经有过这样的问题。如果是这种情况,最好从 16 位边界开始读取 32 位值并相应地调整移位和掩码。

    【讨论】:

    • 我的:25550 毫秒马特凯恩:23926 毫秒阿里:23114 毫秒
    • 是的,这就是我真正想要的,但我的大脑不太好工作:)
    • 我建议也测试 32 位(甚至 64 位 - 取决于您的硬件)方法。我的经验是,由于缓存和流水线系统的复杂性,这些事情通常会产生违反直觉的结果。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-22
    • 2021-09-18
    • 1970-01-01
    相关资源
    最近更新 更多