• log2取整效率测试


    RMQ问题中有个ST算法,当然还有个标准算法.LCA问题可以转化为带限制的RMQ(RMQ+-1)问题来解决.我们姑且认为这些问题的时间复杂度是查询$O(1)$的.但是,注意到对于RMQ(/+-1)问题,这个问题有个长度的限制,我们记为n.那么对于每个查询,我们都要询问一个范围[L,R],1<=L<=R<=n.这个区间的长度为R-L+1.然后我们将原区间分成两个Sparse Table上的项,即长度为int_log2(R-L+1)-1的两个子区间求解min,即合并两个子区间的信息.

    那么问题来了,int_log2(R-L+1)-1也是要花时间的.有些人推荐用floor(log(n)/lg2-1),非常仪赖于cmath库的log函数,潜意识里认为它是$O(1)$的.我非常反对这种"眼不见为净"的人,于是我想出了一系列算法求解int_log2(n)-1.我做了一些测试来对比这些算法的效率.

    1) cmath log函数求解
    2) iterate 迭代右移求解
    3) binary 二分右移求解
    4) float conversion 转换为浮点数进行位运算求解

    下面简述一下这些求解法.

    log函数求解

    <cmath>库中提供了函数log.直接调用log计算.

    代码

    inline int ilog2_cmath(int n){
    	return floor(log(n+.0)/l2)-1;
    }

    就这样.非常简单.

    迭代右移求解

    我们循环右移n,当n0时退出循环.每次循环将一个计数器加1.

    inline int ilog2_iter(int n){
    	int i;for(i=0,n>>=1;n;++i) n>>=1;
    	return i-1;
    }

    二分右移求解

    我们二分n有的bits.

    inline int ilog2_bin(int n){
    	int i=0;
    	if(n>>16) i|=16,n>>=16;
    	if(n>>8) i|=8,n>>=8;
    	if(n>>4) i|=4,n>>=4;
    	if(n>>2) i|=2,n>>=2;
    	if(n>>1) i|=1,n>>=1;
    	return i-1;
    }

    转换为浮点数进行位运算求解

    这个办法比较难以理解了.

    我们需要从浮点数的构造着手.

    Float: [1bit sign bit][8bit exponent bits][23bit mantissa bits]

           00000000101000100010001000100010
           ^ 符号位
            ^------^ 指数位
                    ^---------------------^ 尾数位(有效数字.[开头的1已省去])

    我们要获取的,就是这个符号位的信息.

    这个符号位恰好是int_log2(n)-1.因此,我们甚至无需减1.这是一个非常好的性质.

    那么我们只需要把一个整数转成Float,右移23位再用31与&运算掩码即可.

    inline int ilog2_kf(int n){
    	float q=(float)n;
    	return (*(int*)&q)>>23&31;
    }

    代码很短.开O3时很快.

    测试

    实践是检验真理的唯一标准.

    代码

    #define sizex 100000000
    #define l2 0.6931471805599453
    #include <cmath>
    inline int ilog2_cmath(int n){
    	return floor(log(n+.0)/l2)-1;
    }
    inline int ilog2_iter(int n){
    	int i;for(i=0,n>>=1;n;++i) n>>=1;
    	return i-1;
    }
    inline int ilog2_bin(int n){
    	int i=0;
    	if(n>>16) i|=16,n>>=16;
    	if(n>>8) i|=8,n>>=8;
    	if(n>>4) i|=4,n>>=4;
    	if(n>>2) i|=2,n>>=2;
    	if(n>>1) i|=1,n>>=1;
    	return i-1;
    }
    inline int ilog2_kf(int n){
    	float q=(float)n;
    	return (*(int*)&q)>>23&31;
    }
    #include <cstdio>
    #include <random>
    #include <malloc.h>
    #include <sys/time.h>
    using namespace std;
    int *data,res;
    long long mytic(){
    	long long result = 0.0;
    	struct timeval tv;
    	gettimeofday( &tv, NULL );
    	result = ((long long)tv.tv_sec)*1000000 + (long long)tv.tv_usec;
    	return result;
    }
    #define dic1() disA(generator)
    void genData(int a){
    	mt19937 generator;
    	uniform_int_distribution<int> disA(0,2147483647);
    	int i=0;
    	for(;i<a;++i) data[i]=dic1();
    }
    void testN(int k){
    	int i;
    	printf("cmath log method
    ");
    	long long start=mytic();
    	for(i=0;i<k;++i){
    		res=ilog2_cmath(data[i]);
    	}
    	start=mytic()-start;
    	printf("%d
    ",res);
    	printf("Time usage: %lld us
    ",start);
    }
    void testU(int k){
    	int i;
    	printf("iterate log method
    ");
    	long long start=mytic();
    	for(i=0;i<k;++i){
    		res=ilog2_iter(data[i]);
    	}
    	start=mytic()-start;
    	printf("%d
    ",res);
    	printf("Time usage: %lld us
    ",start);
    }
    void testP(int k){
    	int i;
    	printf("binary divide log method
    ");
    	long long start=mytic();
    	for(i=0;i<k;++i){
    		res=ilog2_bin(data[i]);
    	}
    	start=mytic()-start;
    	printf("%d
    ",res);
    	printf("Time usage: %lld us
    ",start);
    }
    void testUP(int k){
    	int i;
    	printf("float convertion log method
    ");
    	long long start=mytic();
    	for(i=0;i<k;++i){
    		res=ilog2_kf(data[i]);
    	}
    	start=mytic()-start;
    	printf("%d
    ",res);
    	printf("Time usage: %lld us
    ",start);
    }
    int main(){
    	int a,b,c,i,j,k,l,m,n,N,U,P,UP;
    	data=(int*)malloc(400000000*sizeof(int));
    	while(printf("0 to quit> "),scanf("%d",&a),a){
    		printf("CMLog Iter Bina Flcv
    ");
    		scanf("%d%d%d%d",&N,&U,&P,&UP);
    		if(a>400000000) continue;
    		genData(a);
    		if(N) testN(a);
    		if(U) testU(a);
    		if(P) testP(a);
    		if(UP) testUP(a);
    		printf("%d %d %d %d
    ",ilog2_cmath(a),ilog2_iter(a),ilog2_bin(a),ilog2_kf(a));
    	}
    	free(data);
    	return 0;
    }

    测试结果

    //数据大小: 4×10^8数
    
    //cmath log
    19277285 us
    ~
    19.3 s
    
    //iterate
    6197113 us
    ~
    6.2 s
    
    3.1x faster than cmath log
    
    //binary iterate
    2018023 us
    ~
    2.0 s
    
    3.1x faster than iterate
    
    //float bit operation
    406996 us
    ~
    0.41 s
    
    5.0x faster than binary iterate
    and
    47.4x faster than cmath log

    数据无误.结果无误.

    (机器数据:i7 4700m 2.0GHz (16GB=15.6GiB RAM DDR3 800MHz)?)
    (编译命令:gcc ... -O3)

    结果分析

    第四种方法特别快.事实上从用时中看得出来每一次运算几乎是整的2个时钟周期.

    由此看来i7 int2float的效率是1时钟周期.

    下面贴-O2的数据.依次为CMLog Iter Binary FloatConvBitOperation 单位us 数据均由mt19937随机数算法随机生成

    19301840 6186242 2379282 400056

    下面贴-O1的数据.

    19267001 6466776 2446129 385642

    下面贴-O的数据.

    19302953 6472134 2460882 400694

    下面贴-Os的数据.

    19247815 8508664 2500930 390131

    下面贴不带optimize选项的数据.

    19198380 25362664 6802623 1290716

    下面贴-O3带-march=corei7-avx的数据.

    19301717 6196286 2010706 377347

    数据分析:

    只要带optimize选项,fclm都是最快的,在0.4s左右.否则fclm还是最快的,1.2s左右.

    -Os的Iter从6.2s变成8.5s,变慢了许多.

    不开optimize的除了cmath log(已编译好直接连接)外都慢了很多很多,3-4x左右.大约是函数调用开销!

    corei7-avx减少了fclm的时间.

    程序优化notes: 开-O2的地方基本不用担心速度了.

    评测程序: 修改了并查集测试的程序用.

    编程建议: 使用fclm方法来获取highbit等intlog2的应用.
    优点:
    1) 代码短
    2) 没有判断.充分利用处理器架构.
    3) 代码不容易看懂.
    4) 在IEEE754 compatiable的机器上均可使用.(几乎没有不能使用的机器.)
    5) 非常非常快.几乎相当于lowbit的速度,然而求出lowbit必须用fclm转换成指数.

    缺点:

    1) 在特别特别特别老或特别奇葩的机器上不能用.
    2) 动态类型语言不可用.可以用native extension或biniter解决.动态语言不需要高效率.

    该问题完美解决.

    2个时钟周期的算法无论如何都不能看作$O(log{log{n}})$了.显然是$O(1)$时间复杂度的算法.

  • 相关阅读:
    团队项目 第一次作业
    20165201 课下作业第十周(选做)
    20165201 实验三敏捷开发与XP实践
    20165201 2017-2018-2 《Java程序设计》第9周学习总结
    20165201 结对编程练习_四则运算(第二周)
    20165201 2017-2018-2 《Java程序设计》第8周学习总结
    20165201 实验二面向对象程序设计
    20165326 java实验五
    20165326 课程总结
    20165326 java实验四
  • 原文地址:https://www.cnblogs.com/tmzbot/p/calc_log2_n.html
Copyright © 2020-2023  润新知