【问题标题】:Fastest way of computing the power that a "power of 2" number used?计算“2的幂”数使用的幂的最快方法?
【发布时间】:2014-02-21 16:50:26
【问题描述】:

找到某个数字(即 2 的幂)使用的 2 的幂的最快方法是什么?

我对数学不是很熟练,所以我不确定如何最好地描述它。但该函数看起来类似于x = 2^y,其中y 是输出,x 是输入。如果这有助于解释它,这是一个真值表。

0 = f(1)
1 = f(2)
2 = f(4)
3 = f(8)
...
8 = f(256)
9 = f(512)

我已经创建了一个函数来执行此操作,但我担心它不是很有效(或就此而言很优雅)。会有更简单、更有效的方法吗?我正在使用它来计算纹理的哪个区域用于缓冲绘制的完成方式,因此对于每个绘制的对象至少调用一次。这是我到目前为止所做的功能:

uint32 getThePowerOfTwo(uint32 value){
    for(uint32 n = 0; n < 32; ++n){
        if(value <= (1 << n)){
            return n;
        }
    }
    return 32; // should never be called
}

【问题讨论】:

  • 试试logarithm
  • 我同意对数的使用。但是请尝试编写您希望函数使用的数字的二进制表示。 提示提示
  • “这是我到目前为止所做的功能:” - 我认为这个功能很好。 不用担心效率。此外,移动 1 位直到你发现结果大于你的数字是完全“优雅”的,因为它易于阅读/理解和实施。
  • 顺便说一句,你应该学点初等数学。 (如果您不认识对数,您就不会在编程方面走得太远。)
  • @HonkyTonk - 我在表示中进行了编辑,谢谢。 (至于对数的使用,我想它本质上是 2 的对数,但我认为使用 std::log 会更昂贵)

标签: c++ math


【解决方案1】:

您的版本很好,但正如您推测的那样,它是O(n),这意味着它在循环中每一位都需要一个步骤。你可以做得更好。要进入下一步,请尝试分而治之:

unsigned int log2(unsigned int value)
{
  unsigned int val = 0 ;
  unsigned int mask= 0xffff0000 ;
  unsigned int step= 16 ;

  while ( value )
  {
    if ( value & mask ) { val += step ;  value &= ~ mask ; }
    step /= 2 ;
    if ( step ) { mask >>= step ; } else { mask >>= 1 ; }
  }

  return val ;
}

由于我们只是在寻找最高位,因此我们开始询问单词的上半部分是否有任何位。如果有,我们可以丢弃所有低位,否则我们只是缩小搜索范围。

由于问题被标记为C++,这里有一个使用模板的版本,它试图找出初始掩码和步骤:

template <typename T>
  T log2(T val)
  {
    T result = 0 ;
    T step= ( 4 * sizeof( T ) ) ;  // half the number of bits
    T mask= ~ 0L - ( ( 1L << ( 4 * sizeof( T )) ) -1 ) ;

    while ( val && step )
    {
      if ( val & mask ) { result += step ;  val >>= step ; }
      mask >>= ( step + 1) / 2 ;
      step /= 2 ; 
    }

    return result ;
  }

虽然任何一个版本的性能都将成为现代 x86 架构上的一个亮点,但在嵌入式解决方案中我已经遇到了这种情况,在最后一种情况下,我正在解决与此非常相似的位搜索,即使是 @ 987654325@ 对于中断来说太慢了,我们不得不使用分而治之和查表的组合来挤出最后几个周期。

【讨论】:

  • 是的,这很好。理论上。但是,我会对实际的基准感兴趣;我不确定这是否确实更快(我知道,我知道,O(log n)O(n),但是有那些“恒定因素”,你知道 - 这比计算更多 OP的天真版本)。此外,readability &gt;&gt;&gt; performance 直到 OP 没有明确要求为特定目的提供非常非常快速、微调的版本(在对他的程序进行基准测试并得出结论认为 this 功能是主要功能之后瓶颈。)
  • 你看,这就是readability &gt;&gt;&gt; performance的原因。现在,这会在每个循环中产生更多指令,而您甚至第一次都无法正确完成。
  • @H2CO3 我同意你的观点,这是一个很好的教训,优化某些东西意味着更多的代码,而且经常会出现更多的错误;并且通常只在需要时才进行。
  • 我终于开始测试这个功能,看看它是否准确并对其进行分析。它比我的函数要快一点,大约快 10%。这是最快的答案,但是是的,这是以清晰为代价的。但是,我将把它作为答案,因为它是唯一在效率方面胜过我写的答案的便携式答案
  • 我只是在输入数据中使用均匀分布来摆弄测试,发现 Dave 的答案更快。当分布与pow(2, x) 相似时,这个会获胜,因为较小的数字权重更大,但在分布均匀时则不然。所以我必须接受他的回答。
【解决方案2】:

此操作非常受欢迎,处理器供应商可以为其提供硬件支持。查看find first set。编译器供应商为此提供了特定的功能,不幸的是似乎没有标准的命名方式。因此,如果您需要最高性能,则必须创建依赖于编译器的代码:

# ifdef __GNUC__  
    return __builtin_ffs( x ) - 1; // GCC
#endif
#ifdef _MSC_VER
    return CHAR_BIT * sizeof(x)-__lzcnt( x ); // Visual studio
#endif

【讨论】:

  • 不应该将sizeof(x) 改为sizeof(x) * CHAR_BIT 吗?
  • 对。固定的。谢谢。
  • 我的意思是CHAR_BIT,而不是“8”。 (但是,如果我们不是便携狂,那么这是正确的解决方案。)
  • _MSC_VER 条件中的代码并不完全需要可移植。
【解决方案3】:

基于羊毛之星的回答 - 我想知道查找表的二进制搜索是否会稍微快一些? (而且看起来更漂亮)...

int getThePowerOfTwo(int value) {
    static constexpr int twos[] = {
        1<<0,  1<<1,  1<<2,  1<<3,  1<<4,  1<<5,  1<<6,  1<<7,
        1<<8,  1<<9,  1<<10, 1<<11, 1<<12, 1<<13, 1<<14, 1<<15,
        1<<16, 1<<17, 1<<18, 1<<19, 1<<20, 1<<21, 1<<22, 1<<23,
        1<<24, 1<<25, 1<<26, 1<<27, 1<<28, 1<<29, 1<<30, 1<<31
    };

    return std::lower_bound(std::begin(twos), std::end(twos), value) - std::begin(twos);
}

【讨论】:

  • 如果我可以选择两个答案,我也会选择这个。
  • @Clairvoire 相反,您决定选择一个多余的聪明人,它非常聪明,以至于它的作者都无法正确理解。
  • @Clairvoire 所以,我建议你接受这个答案。它与当前接受的答案中的方法一样快,但它非常清晰和简单。
  • @Clairvoire 这支持 32 位,而不仅仅是 16 位。当您使用更大范围的数字(最多 2^32)时,您应该会看到与幼稚实现的更大差异
  • @Dave 顺便说一句,这是一个基准,其中天真的方法击败了其他两个方法:link to CoLiRu
【解决方案4】:

如果输入值仅为2^n 其中n - 整数,则查找n 的最佳方法是使用带有perfect hash function 的哈希表。在这种情况下,32 个无符号整数的哈希函数可以定义为value % 37

template < size_t _Div >
std::array < uint8_t, _Div > build_hash()
{
    std::array < uint8_t, _Div > hash_;

    std::fill(hash_.begin(), hash_.end(), std::numeric_limits<uint8_t>::max());

    for (size_t index_ = 0; index_ < 32; ++index_)
        hash_[(1 << index_) % _Div] = index_;

    return hash_;
}

uint8_t hash_log2(uint32_t value_)
{
    static const std::array < uint8_t, 37 > hash_ = build_hash<37> ();

    return hash_[value_%37];
}

检查

int main()
{
    for (size_t index_ = 0; index_ < 32; ++index_)
        assert(hash_log2(1 << index_) == index_);   
}

【讨论】:

    【解决方案5】:

    如果您知道它确实是 2 的幂(这很容易验证), 试试下面的变种。 完整描述在这里:http://sree.kotay.com/2007/04/shift-registers-and-de-bruijn-sequences_10.html

    //table
    static const int8 xs_KotayBits[32] =    {
           0,  1,  2, 16,  3,  6, 17, 21,
           14,  4,  7,  9, 18, 11, 22, 26,
           31, 15,  5, 20, 13,  8, 10, 25,
           30, 19, 12, 24, 29, 23, 28, 27
           };
    
    
    //only works for powers of 2 inputs
    static inline int32 xs_ILogPow2 (int32 v){
       assert (v && (v&(v-1)==0));
       //constant is binary 10 01010 11010 00110 01110 11111
       return xs_KotayBits[(uint32(v)*uint32( 0x04ad19df ))>>27];
    }     
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-04-14
      • 2011-05-05
      • 2013-03-09
      • 2014-08-21
      • 2011-07-27
      • 2011-07-11
      • 1970-01-01
      • 2019-12-05
      相关资源
      最近更新 更多