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\n");
	long long start=mytic();
	for(i=0;i<k;++i){
		res=ilog2_cmath(data[i]);
	}
	start=mytic()-start;
	printf("%d\n",res);
	printf("Time usage: %lld us\n",start);
}
void testU(int k){
	int i;
	printf("iterate log method\n");
	long long start=mytic();
	for(i=0;i<k;++i){
		res=ilog2_iter(data[i]);
	}
	start=mytic()-start;
	printf("%d\n",res);
	printf("Time usage: %lld us\n",start);
}
void testP(int k){
	int i;
	printf("binary divide log method\n");
	long long start=mytic();
	for(i=0;i<k;++i){
		res=ilog2_bin(data[i]);
	}
	start=mytic()-start;
	printf("%d\n",res);
	printf("Time usage: %lld us\n",start);
}
void testUP(int k){
	int i;
	printf("float convertion log method\n");
	long long start=mytic();
	for(i=0;i<k;++i){
		res=ilog2_kf(data[i]);
	}
	start=mytic()-start;
	printf("%d\n",res);
	printf("Time usage: %lld us\n",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\n");
		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\n",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)$时间复杂度的算法.

时间: 2024-11-08 16:42:57

log2取整效率测试的相关文章

java的四种取整方法

java 中取整操作提供了四种方法:分别是: public static double ceil(double a)//向上取整  public static double floor(double a)//向下取整  public static long round(double a)//四舍五入取整  public static double rint(double a)//最近取整   第一种:ceil是天花板的意思,表示向上取整.   测试: System.out.println(Mat

关于pgsql 几个操作符的效率测试比较

关于pgsql 几个操作符的效率测试比较1. json::->> 和 ->> 测试方法:单次运行100次,运行10个单次取平均时间.测试结果:->> 效率高 5% 左右 功能差异:json::->> 在使用前需要对对象转换为jsonb 然后再执行 ->> 操作,所以比->>更耗时 .所以如果我们需要对返回的对象进行jsonb操作,用jsonb_* 相关函数时,建议用jsonb_* 而不用 jsonb_*_text ,后者会把结果的js

大数据量分页存储过程效率测试附代码

在项目中,我们经常遇到或用到分页,那么在大数据量(百万级以上)下,哪种分页算法效率最优呢?我们不妨用事实说话. 测试环境 硬件:CPU 酷睿双核T5750  内存:2G 软件:Windows server 2003    +   Sql server 2005 OK,我们首先创建一数据库:data_Test,并在此数据库中创建一表:tb_TestTable 按 Ctrl+C 复制代码1create database data_Test --创建数据库data_Test 2GO 3use data

c# 四舍五入、上取整、下取整、百分比

在处理一些数据时,我们希望能用“四舍五入”法实现,但是C#采用的是“四舍六入五成双”的方法,如下面的例子,就是用“四舍六入五成双”得到的结果: double d1 = Math.Round(1.25, 1);//1.2double d2 = Math.Round(1.24, 1);//1.2double d3 = Math.Round(1.26, 1);//1.3double d4 = Math.Round(1.35, 1);//1.4 为了用C#来实现“四舍五入”,我写了下面的函数: 代码 /

简单的方式实现javascript 小数取整

JS: function truncateNumber(n){ return n|0; } 测试: console.log(truncateNumber(12.345)); 浏览器打印出12 简单的方式实现javascript 小数取整

JavaScript基础 Math.ceil() 向上取整 小数部分不四舍五入了,有小数就入。小数再小,都进位

镇场诗: 清心感悟智慧语,不着世间名与利.学水处下纳百川,舍尽贡高我慢意. 学有小成返哺根,愿铸一良心博客.诚心于此写经验,愿见文者得启发.------------------------------------------ code: 1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=ut

JavaScript基础 Math.floor() 向下取整 小数部分不四舍五入了,有小数就舍去。小数再大,都舍

镇场诗: 清心感悟智慧语,不着世间名与利.学水处下纳百川,舍尽贡高我慢意. 学有小成返哺根,愿铸一良心博客.诚心于此写经验,愿见文者得启发.------------------------------------------ code: 1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=ut

一些取整方法

看到一篇文章浮点型巧变整型,讲的是用按位取反~来取整 ~~1.5 // 输出1 想到平时看源码时也看到过各种各样取整方法 1.5 | 0 // 输出1 1.5 >>> 0 //输出1 原来浮点数不支持位运算,会自动转换成整数.所以凡是对一个数进行位运算且不改变它的值就可以达到取整的效果. 在chrome里进行测试 var a=new Date(); var b; for(i=0;i<10000000;i++){ b = ~~1.5; // 1.5 | 0 ; 1.5 <<

c# 四舍五入、上取整、下取整

在处理一些数据时,我们希望能用“四舍五入”法实现,但是C#采用的是“四舍六入五成双”的方法,如下面的例子,就是用“四舍六入五成双”得到的结果: double d1 = Math.Round(1.25, 1);//1.2double d2 = Math.Round(1.24, 1);//1.2double d3 = Math.Round(1.26, 1);//1.3double d4 = Math.Round(1.35, 1);//1.4 为了用C#来实现“四舍五入”,我写了下面的函数: 代码 /