当前位置: 首页 > 知识库问答 >
问题:

如何有效地计算小于或等于给定数字的2的最大幂?[副本]

咸昊昊
2023-03-14

到目前为止,我想出了三个解决方案:

效率极低的标准库< code > power 和< code>log2函数:

int_fast16_t powlog(uint_fast16_t n)
{
  return static_cast<uint_fast16_t>(pow(2, floor(log2(n))));
}

计算2的后续幂要有效得多,直到我达到比我必须达到的更大的数字:

uint_fast16_t multiply(uint_fast16_t n)
{
  uint_fast16_t maxpow = 1;
  while(2*maxpow <= n)
    maxpow *= 2;
  return maxpow;
}

迄今为止最有效的二次幂预计算表:

uint_fast16_t binsearch(uint_fast16_t n)
{
  static array<uint_fast16_t, 20> pows {1,2,4,8,16,32,64,128,256,512,
    1024,2048,4096,8192,16384,32768,65536,131072,262144,524288};

  return *(upper_bound(pows.begin(), pows.end(), n)-1);
}

这还能再优化吗?有什么技巧可以用在这里吗?

我使用的完整基准:

#include <iostream>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <array>
#include <algorithm>
using namespace std;
using namespace chrono;

uint_fast16_t powlog(uint_fast16_t n)
{
  return static_cast<uint_fast16_t>(pow(2, floor(log2(n))));
}

uint_fast16_t multiply(uint_fast16_t n)
{
  uint_fast16_t maxpow = 1;
  while(2*maxpow <= n)
    maxpow *= 2;
  return maxpow;
}

uint_fast16_t binsearch(uint_fast16_t n)
{
  static array<uint_fast16_t, 20> pows {1,2,4,8,16,32,64,128,256,512,
    1024,2048,4096,8192,16384,32768,65536,131072,262144,524288};

  return *(upper_bound(pows.begin(), pows.end(), n)-1);
}

high_resolution_clock::duration test(uint_fast16_t(powfunct)(uint_fast16_t))
{
  auto tbegin = high_resolution_clock::now();
  volatile uint_fast16_t sink;
  for(uint_fast8_t i = 0; i < UINT8_MAX; ++i)
    for(uint_fast16_t n = 1; n <= 999999; ++n)
      sink = powfunct(n);
  auto tend = high_resolution_clock::now();
  return tend - tbegin;
}

int main()
{
  cout << "Pow and log took " << duration_cast<milliseconds>(test(powlog)).count() << " milliseconds." << endl;
  cout << "Multiplying by 2 took " << duration_cast<milliseconds>(test(multiply)).count() << " milliseconds." << endl;
  cout << "Binsearching precomputed table of powers took " << duration_cast<milliseconds>(test(binsearch)).count() << " milliseconds." << endl;
}

使用-O2编译,这在我的笔记本电脑上给出了以下结果:

Pow and log took 19294 milliseconds.
Multiplying by 2 took 2756 milliseconds.
Binsearching precomputed table of powers took 2278 milliseconds.

共有3个答案

羊舌旭尧
2023-03-14

爬升速度更快,但回落速度相同。

        uint multiply_quick(uint n)
        {
            if (n < 2u) return 1u;
            uint maxpow = 1u;

            if (n > 256u)
            {
                maxpow = 256u * 128u;

                // fast fixing the overshoot
                while (maxpow > n)
                    maxpow = maxpow >> 2;
                // fixing the undershoot
                while (2u * maxpow <= n)
                    maxpow *= 2u;
            }
            else
            {

                // quicker scan
                while (maxpow < n && maxpow != 256u)
                    maxpow *= maxpow;

                // fast fixing the overshoot
                while (maxpow > n)
                    maxpow = maxpow >> 2;

                // fixing the undershoot
                while (2u * maxpow <= n)
                    maxpow *= 2u;
            }
            return maxpow;
        }

也许这更适合使用65k常量而不是256的32位变量。

微生青青
2023-03-14

查找表似乎是这里的最佳选择。因此,回答

这还能再优化吗?有什么技巧可以用在这里吗?

是的,我们可以!让我们打败标准库二进制搜索!

template <class T>
inline size_t
choose(T const& a, T const& b, size_t const& src1, size_t const& src2)
{
    return b >= a ? src2 : src1;
}
template <class Container>
inline typename Container::const_iterator
fast_upper_bound(Container const& cont, typename Container::value_type const& value)
{
    auto size = cont.size();
    size_t low = 0;

    while (size > 0) {
        size_t half = size / 2;
        size_t other_half = size - half;
        size_t probe = low + half;
        size_t other_low = low + other_half;
        auto v = cont[probe];
        size = half;
        low = choose(v, value, low, other_low);
    }

    return begin(cont)+low;
}

使用upper_bound的这个实现给了我一个实质性的改进:

g++ -std=c++14 -O2 -Wall -Wno-unused-but-set-variable -Werror main.cpp && ./a.out
Pow and log took 2536 milliseconds.
Multiplying by 2 took 320 milliseconds.
Binsearching precomputed table of powers took 349 milliseconds.
Binsearching (opti) precomputed table of powers took 167 milliseconds.

(生活在大肠杆菌上)请注意,我已经改进了您的基准测试以使用随机值;通过这样做,我删除了分支预测偏差。

现在,如果您真的需要更加努力,可以使用x86_64 asm优化choose函数以实现clang:

template <class T> inline size_t choose(T const& a, T const& b, size_t const& src1, size_t const& src2)
{
#if defined(__clang__) && defined(__x86_64)
    size_t res = src1;
    asm("cmpq %1, %2; cmovaeq %4, %0"
        :
    "=q" (res)
        :
        "q" (a),
        "q" (b),
        "q" (src1),
        "q" (src2),
        "0" (res)
        :
        "cc");
    return res;
#else
    return b >= a ? src2 : src1;
#endif
}

带输出:

clang++ -std=c++14 -O2 -Wall -Wno-unused-variable -Wno-missing-braces -Werror main.cpp && ./a.out
Pow and log took 1408 milliseconds.
Multiplying by 2 took 351 milliseconds.
Binsearching precomputed table of powers took 359 milliseconds.
Binsearching (opti) precomputed table of powers took 153 milliseconds.

(在科利鲁直播)

慕胡媚
2023-03-14

带有内部函数的版本已经在评论中提出,所以这里有一个不依赖它们的版本:

uint32_t highestPowerOfTwoIn(uint32_t x)
{
  x |= x >> 1;
  x |= x >> 2;
  x |= x >> 4;
  x |= x >> 8;
  x |= x >> 16;
  return x ^ (x >> 1);
}

这的工作原理是首先“涂抹”右侧的最高设置位,然后x ^(x

因为没有人真正张贴它,你可以用内部函数来写(GCC,Clang)

uint32_t highestPowerOfTwoIn(uint32_t x)
{
  return 0x80000000 >> __builtin_clz(x);
}

或者(MSVC,可能,未经测试)

uint32_t highestPowerOfTwoIn(uint32_t x)
{
  unsigned long index;
  // ignoring return value, assume x != 0
  _BitScanReverse(&index, x);
  return 1u << index;
}

当目标硬件直接支持时,应该更好。

coliru上的结果,以及coliru上的延迟结果(也与基线进行比较,基线应该大致指示开销)。在延迟结果中,< code>highestPowerOfTwoIn的第一个版本看起来不再那么好了(仍然可以,但它是一个很长的依赖指令链,所以它扩大了与内函数版本的差距也就不足为奇了)。哪一个是最相关的比较取决于你的实际使用情况。

如果您有一些具有快速位反转操作(但可能是慢速移位或慢速< code>clz)的奇怪硬件,姑且称之为< code>_rbit,那么您可以这样做

uint32_t highestPowerOfTwoIn(uint32_t x)
{
  x = _rbit(x);
  return _rbit(x & -x);
}

这当然是基于旧的x

 类似资料:
  • 我有一个大小为的整数值数组和一个给定的。 我想找到子序列的总数,使得每个子序列的元素总和小于。例如:设 ,,数组的元素为 ,则其总子序列为 作为- 但是,所需的子序列是: 也就是说,不被取,因为它的元素和是,这大于,即

  • 给定一个2D数组和一个数字。 问题:我们有一个矩阵,矩阵的每个单元格表示遍历该单元格的成本。我们从左上角开始,我们必须到达最后一个单元格(右下角)。我必须编写一个函数,返回到达而不超过的最大代价路径的代价。 如果找不到最大和小于或等于的路径,则返回,矩阵的值不能为负 解决方案:我尝试了很多代码,但没有一个返回我期望的结果。 我的第一个解决方案是在一个简单的数组中转换2D数组,并应用背包算法,但它不

  • 我的问题很简单,但我不知道如何解决我想要的。我必须找到小于给定数字的最大数素数,如果不存在则打印消息。 代码是有效的,如果数字是10,它会打印7,但我想做2个新的修改,我找不到解决方案。例如,如果给定的数字是1,我的程序应该如何修改以打印消息?我试着写一个if-else,但是如果我用if修改了while,这将不会有帮助。第二件事,如果给定的数是素数,代码仍然会找到比给定数少的数。如果我给数字7,输

  • 给定一个数组形式的未排序(多)整数集,求其和大于或等于常量整数x的最小基数子集。 我们的集合是{4 5 8 10 10},x=15,所以最小基数子集和 这个问题与以下问题相关但不同:给定一个n个整数的列表,找到大于X的最小子集和在前面的问题中,作者要求得到一个和最接近X的子集,这里我们想要任何子集

  • 问题内容: 这可能很简单,但是找不到方法。我试图找到每个实体的最大修订小于或等于给定的修订号。 上面的代码按降序返回同一实体的多个修订。我想获得每个实体的最新不同修订版本,该修订版本应小于或等于给定的修订版本号。 作为一种解决方法,我正在过滤resultSet,如下所示。我希望可以在AuditQuery本身上进行此过滤。 解: 我们需要使用 [https://hibernate.atlassian

  • 问题内容: 给出以下表1: 和Table2包含一些RefID和intVals,例如 需要SQL语句来获取每个RefID和NULL的下一个更大的intValue(如果在表1中找不到),则以下是预期结果 帮助将不胜感激! 问题答案: 派生表从给定的表1和表2检索最小值。外部查询仅检索someValue。 这是带有现场测试的Sql Fiddle。