【问题标题】:Fastest way of counting occurences of 1's in multiple std::bitset<N>?计算多个 std::bitset<N> 中出现 1 的最快方法?
【发布时间】:2016-07-04 11:46:01
【问题描述】:

我想计算1 在同一位置的多个位集中的出现次数。每个位置的计数存储在一个向量中。

例如

b0 = 1011
b1 = 1110
b2 = 0110
     ----
 c = 2231 (1+1+0,0+1+1,1+1+1,1+0+0)

我可以使用下面的代码轻松做到这一点,但这段代码似乎缺乏性能,但我不确定。所以我的问题很简单:有没有更快的方法来计算1

#include <bitset>
#include <vector>
#include <iostream>
#include <string>

int main(int argc, char ** argv)
{
  std::vector<std::bitset<4>> bitsets;
  bitsets.push_back(std::bitset<4>("1011"));
  bitsets.push_back(std::bitset<4>("1110"));
  bitsets.push_back(std::bitset<4>("0110"));

  std::vector<unsigned> counts;

  for (int i=0,j=4; i<j; ++i)
  {
    counts.push_back(0);
    for (int p=0,q=bitsets.size(); p<q; ++p)
    {
      if (bitsets[p][(4-1)-i]) // reverse order
      {
        counts[i] += 1;
      }
    }
  }

  for (auto const & count: counts)
  {
      std::cout << count << " ";
  }
}

for (int i=0,j=4; i<j; ++i)
{
  for (int p=0,q=b.size(); p<q; ++p)
  {
    if(b[p][i])
    {
      c[p] += 1;
    }
  }
}

【问题讨论】:

  • 忘记了这个在线编译器link
  • 一些事情: 1. 为什么使用std::vector 而不是std::array,因为您所有的位集都有编译时常量大小?如果您需要std::vector,请使用正确的大小对其进行初始化,而不是使用push_back。 2. 通过交换两个循环(使内循环成为外循环),您可能会更快一些 - 这样可以避免多次加载同一块内存。
  • 你真的需要std::bitset&lt;&gt;吗?如果没有,为什么不使用一个简单的 char 并将二进制文件放入其中并进行一些简单的低级位操作?
  • [OT] 您可以编辑以添加链接而不是添加评论。
  • std::bitset&lt;N&gt; 存储在向量中,因为我不知道编译时的大小。进一步std::bitset&lt;N&gt; 用于执行逻辑操作。

标签: c++ stdvector std-bitset


【解决方案1】:

一种表驱动的方法。它显然有其局限性*,但取决于应用程序可能证明非常合适:

#include <array>
#include <bitset>
#include <string>
#include <iostream>
#include <cstdint>

static const uint32_t expand[] = {
        0x00000000,
        0x00000001,
        0x00000100,
        0x00000101,
        0x00010000,
        0x00010001,
        0x00010100,
        0x00010101,
        0x01000000,
        0x01000001,
        0x01000100,
        0x01000101,
        0x01010000,
        0x01010001,
        0x01010100,
        0x01010101
};

int main(int argc, char* argv[])
{
        std::array<std::bitset<4>, 3> bits = {
            std::bitset<4>("1011"),
            std::bitset<4>("1110"),
            std::bitset<4>("0110")
        };

        uint32_t totals = 0;

        for (auto& x : bits)
        {
                totals += expand[x.to_ulong()];
        }

        std::cout << ((totals >> 24) & 0xff) << ((totals >> 16) & 0xff) << ((totals >> 8) & 0xff) << ((totals >> 0) & 0xff) << std::
endl;
        return 0;
}

Edit:: * 实际上,它的限制比人们想象的要少...

【讨论】:

    【解决方案2】:

    我会亲自转换您订购位的方式。

    1011              110
    1110    becomes   011
    0110              111
                      100
    

    两个主要原因:您可以使用 stl 算法,并且在处理更大尺寸时可以使用数据局部性来提高性能。

    #include <bitset>
    #include <vector>
    #include <iostream>
    #include <string>
    #include <iterator>
    
    int main()
    {
        std::vector<std::bitset<3>> bitsets_transpose;  
        bitsets_transpose.reserve(4);
        bitsets_transpose.emplace_back(std::bitset<3>("110"));
        bitsets_transpose.emplace_back(std::bitset<3>("011"));
        bitsets_transpose.emplace_back(std::bitset<3>("111"));
        bitsets_transpose.emplace_back(std::bitset<3>("100"));
    
        std::vector<size_t> counts;
        counts.reserve(4);
        for (auto &el : bitsets_transpose) {
            counts.emplace_back(el.count()); // use bitset::count()
        }
    
        // print counts result
        std::copy(counts.begin(), counts.end(), std::ostream_iterator<size_t>(std::cout, " "));
    }
    

    Live code

    输出是

    2 2 3 1

    【讨论】:

    • 如果您要手动预处理数据,为什么不全力以赴呢?例如` std::cout
    • @Jeremy 不要发表评论。在计算之前转置矩阵是一种众所周知的方法。因此,如果他可以改变他在输入时订购数据的方式,那对他来说就是食物。如果不是,这个答案仍然是一个有趣的使用方法,OP 应该实现一个转置方法,然后测量性能。
    • 好吧,我有点开玩笑,我同意转置数据是一个值得考虑的好方法。但是您的示例跳过了转置阶段,这可能结果与就地统计数据一样昂贵。当然,这在很大程度上取决于您在指定输入数据的形式方面有多大的灵活性。
    【解决方案3】:

    将计数逻辑与向量管理分离的重构使我们能够检查计数算法的效率:

    #include <bitset>
    #include <vector>
    #include <iostream>
    #include <string>
    #include <iterator>
    
    __attribute__((noinline))
    void count(std::vector<unsigned> counts, 
               const std::vector<std::bitset<4>>& bitsets)
    {
      for (int i=0,j=4; i<j; ++i)
      {
        for (int p=0,q=bitsets.size(); p<q; ++p)
        {
          if (bitsets[p][(4-1)-i]) // reverse order
          {
            counts[i] += 1;
          }
        }
      }
    }
    
    int main(int argc, char ** argv)
    {
      std::vector<std::bitset<4>> bitsets;
      bitsets.push_back(std::bitset<4>("1011"));
      bitsets.push_back(std::bitset<4>("1110"));
      bitsets.push_back(std::bitset<4>("0110"));
    
      std::vector<unsigned> counts(bitsets.size(), 0);
    
      count(counts, bitsets);
    
      for (auto const & count: counts)
      {
          std::cout << count << " ";
      }
    }
    

    带有 -O2 的 gcc5.3 会产生这样的结果:

    count(std::vector<unsigned int, std::allocator<unsigned int> >, std::vector<std::bitset<4ul>, std::allocator<std::bitset<4ul> > > const&):
            movq    (%rsi), %r8
            xorl    %r9d, %r9d
            movl    $3, %r10d
            movl    $1, %r11d
            movq    8(%rsi), %rcx
            subq    %r8, %rcx
            shrq    $3, %rcx
    .L4:
            shlx    %r10, %r11, %rsi
            xorl    %eax, %eax
            testl   %ecx, %ecx
            jle     .L6
    .L10:
            testq   %rsi, (%r8,%rax,8)
            je      .L5
            movq    %r9, %rdx
            addq    (%rdi), %rdx
            addl    $1, (%rdx)
    .L5:
            addq    $1, %rax
            cmpl    %eax, %ecx
            jg      .L10
    .L6:
            addq    $4, %r9
            subl    $1, %r10d
            cmpq    $16, %r9
            jne     .L4
            ret
    

    这对我来说似乎一点也不低效。

    【讨论】:

      【解决方案4】:

      您的程序中有多余的内存重新分配和一些其他代码。 例如,在使用方法push_back 之前,您可以先在向量中保留足够的内存。

      程序可能如下所示。

      #include <iostream>
      #include <bitset>
      #include <vector>
      
      const size_t N = 4;
      
      int main() 
      {
          std::vector<std::bitset<N>> bitsets = 
          { 
              std::bitset<N>( "1011" ), 
              std::bitset<N>( "1110" ),
              std::bitset<N>( "0110" )
          };
      
          std::vector<unsigned int> counts( N );
      
          for ( const auto &b : bitsets )
          {
              for ( size_t i = 0; i < N; i++ ) counts[i] += b[N - i -1]; 
          }
      
          for ( unsigned int val : counts ) std::cout << val;
          std::cout << std::endl;
      
          return 0;
      }
      

      它的输出是

      2231
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-06-12
        • 2023-02-10
        • 2013-11-14
        • 2019-10-24
        • 2012-09-09
        • 2020-02-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多