下个2的幂-一个简单而优雅的算法优化介绍

简介: 在并行计算和图形图像等处理中,经常会遇到一类叫做"下个2的幂"的问题,简单说来就是给定一个数,需要找到满足如下条件的一个数: 1. 最靠近这个数 2. 大于或等于这个数 3. 是2的N次方 简单函数描述就是 ` int nextPowerOfTwo(int num);` 首先想到的一般算法可能是: ``` int nextPowerOfTwo(int

在并行计算和图形图像等处理中,经常会遇到一类叫做"下个2的幂"的问题,简单说来就是给定一个数,需要找到满足如下条件的一个数:

  1. 最靠近这个数
  2. 大于或等于这个数
  3. 是2的N次方

简单函数描述就是
int nextPowerOfTwo(int num);

首先想到的一般算法可能是:

 int  nextPowerOfTwo(int num)
 {
  int npot = 1;
  while( npot < num ) npot <<= 1;  
  return npot;      
 }

但明显上述实现随着num的增大,时间就会成倍上升!

有没有什么好办法呢?答案当然是:有

这里给出了一个优雅的改进算法:
http://acius2.blogspot.com/2007/11/calculating-next-power-of-2.html

下面是稍加改动后的一个实现

int  nextPowerOfTwo(int num)
{
    if (0 == num--) 
          return 1;
    }

    num = (num >> 1) | num;
    num = (num >> 2) | num;
    num = (num >> 4) | num;
    num = (num >> 8) | num;
    num = (num >> 16) | num;
    //num = (num >> 32) | num;//如果是64位机器则需要增加一次计算

    return ++num;            
}

这个算法妙就妙在无论这个数多大,只要进行5次右移并或的操作(32位)就够了,堪称精妙!
原理原文有述,简单来说就是通过移位然后与原值进行或操作使得1的位成倍增加,因为是32位,所以重复进行5次1就覆盖了所有位(2的5次方),原例子不太直观,这里再举个例子说明:

假设给定的数是65537,那么
65537 - 1 = 65536
写成二进制形式:

0000 0000 0000 0001 0000 0000 0000 0000
1
0000 0000 0000 0000 1000 0000 0000 0000 //右移1位
0000 0000 0000 0001 1000 0000 0000 0000 //与原值或 ,1的位数翻倍
2
0000 0000 0000 0000 0110 0000 0000 0000 //右移2位
0000 0000 0000 0001 1110 0000 0000 0000 //与原值或,1的位数再翻倍
3
0000 0000 0000 0000 0001 1110 0000 0000 //右移4位
0000 0000 0000 0001 1111 1110 0000 0000 //与原值或,1的位数再翻倍
4
0000 0000 0000 0000 0000 0001 1111 1110 //右移8位
0000 0000 0000 0001 1111 1111 1111 1110 //与原值或,1的位数再翻倍
5
0000 0000 0000 0000 0000 0000 0000 0001 //右移16位
0000 0000 0000 0001 1111 1111 1111 1111 //与原值或,1的位数再翻倍

+1
0000 0000 0000 0010 0000 0000 0000 0000 // 131072

眼尖的同学对比下原文的例子可能会发现某些数(比如947这个数)并不需要循环5次1就已经占位满了,这里再贴下947的一个重复过程:

0000 0011 1011 0011
0000 0011 1111 1011
0000 0011 1111 1111
//...with the remaining steps staying at this value.

恩,在允许范围内有部分数其实不需要重复完5次,我特别实验了一下,比如在4096(可能还可以更大,有兴趣同学可以进一步验证下)范围内,只要重复4次就已经满足要求!这有什么意义呢,比如你给定的数能够确定在某一个范围,那完全可以减少重复次数!特别的,比如GL中纹理的尺寸一般不可能很大,这时就可以减少一次重复!5次减少一次,大约相当于减少五分之一!

上述算法通过取统计平均值计算耗时仅仅为开始算法的1/20左右!

这就结束了吗?No

仔细观察上述二进制串,可以发现如果能够取到1之前的0的个数n,则通过1简单的左移(32-n)就可以获得想要的值,也就是1<<(32-n),这样就可以一次就搞定!
这里的关键是n怎么取得,可以有很多算法来做这个事情,但上述我们已经优化到只是几次简单的位操作,如果这里取n再使用c算法算一遍,效率可能就要跪了!

恩,估计有同学想到了gcc中的一系列高效的built-in函数,__builtin_clz就可以用来取得1前面的0的个数,下面是简单的实现:

int  nextPowerOfTwo(int num)
{
    return 1 << (32 - __builtin_clz(num - 1));    
}

上述算法通过取统计平均值计算耗时在前一算法的基础上几乎又提升了一倍!

这个算法前提是编译器必须支持builtin函数,可以加一些编译开关来获得兼容性平衡!
builtin函数介绍可以参看:
https://gcc.gnu.org/onlinedocs/gcc-4.5.4/gcc/Other-Builtins.html

比如开源浏览器 mozilla FireFox 源码中就有这么一个实现:

https://dxr.mozilla.org/mozilla-central/rev/aa90f482e16db77cdb7dea84564ea1cbd8f7f6b3/gfx/gl/GLUploadHelpers.cpp

static int NextPowerOfTwo(int aNumber)
{
#if defined(__arm__)
    return 1 << (32 - __builtin_clz(aNumber - 1));
#else
    --aNumber;
    aNumber |= aNumber >> 1;
    aNumber |= aNumber >> 2;
    aNumber |= aNumber >> 4;
    aNumber |= aNumber >> 8;
    aNumber |= aNumber >> 16;
    return ++aNumber;
#endif
}
目录
相关文章
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习中的优化算法及其应用
【10月更文挑战第8天】 本文将探讨深度学习中常用的优化算法,包括梯度下降法、Adam和RMSProp等,介绍这些算法的基本原理与应用场景。通过实例分析,帮助读者更好地理解和应用这些优化算法,提高深度学习模型的训练效率与性能。
169 63
|
9天前
|
算法
基于大爆炸优化算法的PID控制器参数寻优matlab仿真
本研究基于大爆炸优化算法对PID控制器参数进行寻优,并通过Matlab仿真对比优化前后PID控制效果。使用MATLAB2022a实现核心程序,展示了算法迭代过程及最优PID参数的求解。大爆炸优化算法通过模拟宇宙大爆炸和大收缩过程,在搜索空间中迭代寻找全局最优解,特别适用于PID参数优化,提升控制系统性能。
|
15天前
|
存储 关系型数据库 分布式数据库
PolarDB的PolarStore存储引擎以其高效的索引结构、优化的数据压缩算法、出色的事务处理能力著称
PolarDB的PolarStore存储引擎以其高效的索引结构、优化的数据压缩算法、出色的事务处理能力著称。本文深入解析PolarStore的内部机制及优化策略,包括合理调整索引、优化数据分布、控制事务规模等,旨在最大化其性能优势,提升数据存储与访问效率。
22 5
|
1月前
|
算法 调度
基于遗传模拟退火混合优化算法的车间作业最优调度matlab仿真,输出甘特图
车间作业调度问题(JSSP)通过遗传算法(GA)和模拟退火算法(SA)优化多个作业在并行工作中心上的加工顺序和时间,以最小化总完成时间和机器闲置时间。MATLAB2022a版本运行测试,展示了有效性和可行性。核心程序采用作业列表表示法,结合遗传操作和模拟退火过程,提高算法性能。
|
1月前
|
人工智能 算法 大数据
Linux内核中的调度算法演变:从O(1)到CFS的优化之旅###
本文深入探讨了Linux操作系统内核中进程调度算法的发展历程,聚焦于O(1)调度器向完全公平调度器(CFS)的转变。不同于传统摘要对研究背景、方法、结果和结论的概述,本文创新性地采用“技术演进时间线”的形式,简明扼要地勾勒出这一转变背后的关键技术里程碑,旨在为读者提供一个清晰的历史脉络,引领其深入了解Linux调度机制的革新之路。 ###
|
2月前
|
人工智能 算法 数据安全/隐私保护
基于遗传优化的SVD水印嵌入提取算法matlab仿真
该算法基于遗传优化的SVD水印嵌入与提取技术,通过遗传算法优化水印嵌入参数,提高水印的鲁棒性和隐蔽性。在MATLAB2022a环境下测试,展示了优化前后的性能对比及不同干扰下的水印提取效果。核心程序实现了SVD分解、遗传算法流程及其参数优化,有效提升了水印技术的应用价值。
|
1月前
|
存储 缓存 算法
优化轮询算法以提高资源分配的效率
【10月更文挑战第13天】通过以上这些优化措施,可以在一定程度上提高轮询算法的资源分配效率,使其更好地适应不同的应用场景和需求。但需要注意的是,优化策略的选择和实施需要根据具体情况进行详细的分析和评估,以确保优化效果的最大化。
|
2月前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
2月前
|
存储 缓存 算法
前端算法:优化与实战技巧的深度探索
【10月更文挑战第21天】前端算法:优化与实战技巧的深度探索
24 1
|
2月前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于贝叶斯优化CNN-LSTM网络的数据分类识别算法matlab仿真
本项目展示了基于贝叶斯优化(BO)的CNN-LSTM网络在数据分类中的应用。通过MATLAB 2022a实现,优化前后效果对比明显。核心代码附带中文注释和操作视频,涵盖BO、CNN、LSTM理论,特别是BO优化CNN-LSTM网络的batchsize和学习率,显著提升模型性能。