【问题标题】:Optimizing Bitshift into Array将位移优化为数组
【发布时间】:2017-01-23 00:08:55
【问题描述】:

我有一段代码在执行一些任务后每秒运行约 120 万次,其中最大的部分是设置一个 uint8_t 数组,其中包含来自两个 uint32_t 数据的位移数据。摘录代码如下:

    static inline uint32_t RotateRight(uint32_t val, int n)
{
    return (val >> n) + (val << (32 - n));

}

static inline uint32_t CSUInt32BE(const uint8_t *b)
{
    return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | (uint32_t)b[3];
}

static uint32_t ReverseBits(uint32_t val) // Usually just static, tried inline/static inline
{
    //  uint32_t res = 0;
    //  for (int i = 0; i<32; i++)
    //  {
    //      res <<= 1;
    //      res |= val & 1;
    //      val >>= 1;
    //  }
    // Original code above, benched ~220k l/s

    //val = ((val & 0x55555555) << 1) | ((val >> 1) & 0x55555555);
    //val = ((val & 0x33333333) << 2) | ((val >> 2) & 0x33333333);
    //val = ((val & 0x0F0F0F0F) << 4) | ((val >> 4) & 0x0F0F0F0F);
    //val = ((val & 0x00FF00FF) << 8) | ((val >> 8) & 0x00FF00FF);
    //val = (val << 16) | (val >> 16);
    // Option 0, benched ~770k on MBP

    uint32_t c = 0;
    c = (BitReverseTable256[val & 0xff] << 24) |
        (BitReverseTable256[(val >> 8) & 0xff] << 16) |
        (BitReverseTable256[(val >> 16) & 0xff] << 8) |
        (BitReverseTable256[val >> 24]); // was (val >> 24) & 0xff
                                         // Option 1, benched ~970k l/s on MBP, Current, minor tweak to 24

                                         //unsigned char * p = (unsigned char *)&val;
                                         //unsigned char * q = (unsigned char *)&c;
                                         //q[3] = BitReverseTable256[p[0]];
                                         //q[2] = BitReverseTable256[p[1]];
                                         //q[1] = BitReverseTable256[p[2]];
                                         //q[0] = BitReverseTable256[p[3]];
                                         // Option 2 at ~970k l/s on MBP from http://stackoverflow.com/questions/746171/best-algorithm-for-bit-reversal-from-msb-lsb-to-lsb-msb-in-c


    return c; // Current
              //    return val; // option 0
              //    return res; // original


              //uint32_t m;
              //val = (val >> 16) | (val << 16);                            // swap halfwords
              //m = 0x00ff00ff; val = ((val >> 8) & m) | ((val << 8) & ~m); // swap bytes
              //m = m^(m << 4); val = ((val >> 4) & m) | ((val << 4) & ~m); // swap nibbles
              //m = m^(m << 2); val = ((val >> 2) & m) | ((val << 2) & ~m);
              //m = m^(m << 1); val = ((val >> 1) & m) | ((val << 1) & ~m);
              //return val;
              // Benches at 850k l/s on MBP

              //uint32_t t;
              //val = (val << 15) | (val >> 17);
              //t = (val ^ (val >> 10)) & 0x003f801f;
              //val = (t + (t << 10)) ^ val;
              //t = (val ^ (val >>  4)) & 0x0e038421;
              //val = (t + (t <<  4)) ^ val;
              //t = (val ^ (val >>  2)) & 0x22488842;
              //val = (t + (t <<  2)) ^ val;
              //return val;
              // Benches at 820k l/s on MBP
}
static void StuffItDESCrypt(uint8_t data[8], StuffItDESKeySchedule *ks, BOOL enc)
{
uint32_t left = ReverseBits(CSUInt32BE(&data[0]));
uint32_t right = ReverseBits(CSUInt32BE(&data[4]));

right = RotateRight(right, 29);
left = RotateRight(left, 29);

//Encryption function runs here

left = RotateRight(left, 3);
right = RotateRight(right, 3);

uint32_t left1 = ReverseBits(left);
uint32_t right1 = ReverseBits(right);

data[0] = right1 >> 24;
data[1] = (right1 >> 16) & 0xff;
data[2] = (right1 >> 8) & 0xff;
data[3] = right1 & 0xff;
data[4] = left1 >> 24;
data[5] = (left1 >> 16) & 0xff;
data[6] = (left1 >> 8) & 0xff;
data[7] = left1 & 0xff;

这是实现这一目标的最佳方式吗?我也有一个 uint64_t 版本:

uint64_t both = ((uint64_t)ReverseBits(left) << 32) | (uint64_t)ReverseBits(right);

data[0] = (both >> 24 & 0xff);
data[1] = (both >> 16) & 0xff;
data[2] = (both >> 8) & 0xff;
data[3] = both & 0xff; 
data[4] = (both >> 56);
data[5] = (both >> 48) & 0xff;
data[6] = (both >> 40) & 0xff;
data[7] = (both >> 32) & 0xff;

我测试了如果我完全跳过这个任务会发生什么(ReverseBits 函数仍然完成),并且代码以每秒约 650 万次运行。此外,如果我只做一个,也会发生这种速度冲击,即使不涉及其他 7 个任务,也能达到 120 万。

我不想认为这个操作会因为这项工作而导致 80% 的速度大幅下降,而且不能再快了。

这是在 Windows Visual Studio 2015 上的(尽管我尽量保持源代码可移植到 macOS 和 Linux)。

编辑:完整的基本代码位于Github。我不是代码的原始作者,但是我已经分叉了它并使用修改后的速度版本维护了一个密码恢复解决方案。你可以看到我在 ReverseBits 中通过各种解决方案和基准速度加速成功。

这些文件已有 20 多年的历史,并且已成功恢复文件,尽管多年来一直处于低速状态。见blog post

【问题讨论】:

  • 我们无法回答提出的问题。 “最佳”至少在某种程度上取决于您提供的 sn-ps 的上下文以及您使用的 C 实现。但是,如果您提供minimal reproducible example,那么我们至少可以提出一些尝试的建议。
  • 发布您的所有定义。剩下什么? ReverseBits 在做什么?等等。
  • data[] 是什么数据类型?它是字节还是无符号字符?
  • 他说 uint8_t
  • 我正在尝试优化函数 StufitDESCrypt here。最具体地说,通过尝试替换 CSSetUInt32BE(&data[0], ReverseBits(right));和 CSSetUInt32BE(&data[4], ReverseBits(left));

标签: c++ optimization bit-shift uint8t uint32


【解决方案1】:

您所做的工作肯定比您需要做的要多。请注意函数ReverseBits() 是如何努力将反转字的字节按正确顺序放置的,以及接下来发生的事情(您归因于减速的部分)是如何重新排序这些相同的字节。

您可以编写和使用ReverseBits() 的修改版本,它将反转表示的字节直接放入数组中的正确位置,而不是将它们打包成整数以再次解包它们。这应该至少快一点,因为您将严格删除操作。

【讨论】:

  • 我做了一个修改过的 ReverseBits static inline void ReverseBits_direct(uint8_t *b, uint64_t val) { b[0] = (BitReverseTable256[val &amp; 0xff]); b[1] = (BitReverseTable256[(val &gt;&gt; 8 &amp; 0xff)]); b[2] = (BitReverseTable256[(val &gt;&gt; 16 &amp; 0xff)]); b[3] = (BitReverseTable256[(val &gt;&gt; 24 &amp; 0xff)]); b[4] = (BitReverseTable256[(val &gt;&gt; 32 &amp; 0xff)]); b[5] = (BitReverseTable256[(val &gt;&gt; 40 &amp; 0xff)]); b[6] = (BitReverseTable256[(val &gt;&gt; 48 &amp; 0xff)]); b[7] = (BitReverseTable256[(val &gt;&gt; 56)]); } 我会说速度已经提高到 130 万(而且代码肯定更干净)
  • @GregEsposito,我不能说我对增强相对较小感到惊讶。老实说,我发现您的相对性能测量很可疑;如果在数组中记录更新数据的成本确实与您(认为您已经)测量的一样昂贵,那么可能会产生更微妙的影响,例如缓存使用的有效性。事实上,您观察到的加速可能主要是因为不必将修改后的数据写回主内存,因此感知到的改进机会是虚幻的。
  • 是的,通过注释掉整个函数,它是 600 万,但只需一次设置它的 1.2-130 万(以 1m、10m、100m、10 亿的行/秒计数来衡量)尝试)。我绝对可以相信,打击来自仅仅接触数据而不是不操作数据。我现在的目标是将数据类型移动到 uint64 并采用其他方法来优化代码库。考虑到我使用的是双 Xeon X5365 处理器,有一些技术我无法利用,但有很多内核可供使用。
【解决方案2】:

我的直接想法是“查看”int32_t,就好像它们是 int8_t 的数组一样

uint8_t data2[8];
*((uint32_t*)&data2[0]) = right1;
*((uint32_t*)&data2[4]) = left1;

但是,您将right1 的最高有效位存储在data[0] 中,而这种方法让最低有效位转到data[0]。无论如何,因为我不知道ReverseBits 做了什么以及您是否还可以根据不同的顺序调整您的代码,也许它会有所帮助...

【讨论】:

  • 如果产生了正确的结果,那么值得一试。不幸的是,它不会在 little-endian 平台上这样做,例如所有运行 MSVC 的平台,因为字节是以大端顺序记录在 data 中的。
猜你喜欢
  • 1970-01-01
  • 2020-08-09
  • 2016-11-07
  • 1970-01-01
  • 2010-11-18
  • 1970-01-01
  • 2015-03-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多