« 又让 bjam 郁闷了一把 | 返回首页 | 汪达与巨像 »

_ftol 的优化

_ftol 是什么? 当你写 C 程序的时候,(int)float_v 就会被编译器产生一个对 _ftol 这个 CRT 函数的调用。 上个世纪听一个做 3d 的朋友提起过,用 x87 指令实现的 _ftol 会很慢,一般用整数指令提供。当时提在心里,2000 年的时候在 RISC 上做开发 (ARM 指令集) 曾经写过一些整数模拟浮点的函数,曾经写过这个转换函数,日子久了,现在也找不回来代码。不过对浮点的 IEEE 标准还是比较清楚的。去年写过一篇 浮点数的精度控制问题 的帖子放在流言中。当时已经被骂过了。 今天工作时又遇到关于浮点数的问题,再写篇 blog 吧,或许还是找骂贴 :)
懒的重写 _ftol 的整数指令版本了,google 搜了下,发现果然有人也做过。http://www.flipcode.com/cgi-bin/fcarticles.cgi?show=64008 就是这么一个函数:
int ftol(float f)
{ 
    int a         = *(int*)(&f);
    int sign      = (a>>31); 
    int mantissa  = (a&((1<<23)-1))|(1<<23);
    int exponent  = ((a&0x7fffffff)>>23)-127;
    int r         = ((unsigned int)(mantissa)<<8)>>(31-exponent);
    return ((r ^ (sign)) - sign ) &~ (exponent>>31);       
}
当是效率比较高的。我想,日子已经过去这么久了。当初朋友跟我提这个事情的事情大约是 98,99 年。上面翻出来的老帖是 01 年的。我现在的机器不错,今年新买的 P4 双核的,还是测试一下比较放心。 注:这个函数不能直接替换 CRT 中的 _ftol , CRT 的 _ftol 并不通过堆栈传递参数。 马上随手写了下面的测试程序:
#include <stdio.h>

#define RDTSC  _asm _emit 0x0f _asm _emit 0x31

#pragma warning (push)
#pragma warning (disable: 4035)
inline unsigned __int64 timestamp()
{
	__asm RDTSC
}
#pragma warning (pop)


int int_chop (float f)
{ 
    int a         = *(int*)(&f);
    int sign      = (a>>31); 
    int mantissa  = (a&((1<<23)-1))|(1<<23);
    int exponent  = ((a&0x7fffffff)>>23)-127;
    int r         = ((unsigned int)(mantissa)<<8)>>(31-exponent);
    return ((r ^ (sign)) - sign ) &~ (exponent>>31);    
}

int test1(float f)
{
	return int_chop(f);
}

int test2(float f)
{
	return (int)f;
}

int test3(float  x)
{
	int   t;
	__asm  fld   x  
	__asm  fistp t
	return t;
}


void test(int t,int (*f)(float))
{
	int i;
	for (i=0;i<t;i++) {
		f(-0.8f);
	}
}

void main()
{
	int i;
	for (i=0;i<3;i++) {
		__int64 t;
		printf("---timing %d---\n",i);
		t=timestamp();

		test(100000,test1);

		t=timestamp()-t;

		printf("use int\t%I64d\n",t);

		t=timestamp();

		test(100000,test2);

		t=timestamp()-t;

		printf("(int)\t%I64d\n",t);

		t=timestamp();

		test(100000,test3);

		t=timestamp()-t;

		printf("use x87\t%I64d\n",t);
	}
}
运行结果如下:
---timing 0---
use int 4449676
(int)   4583873
use x87 1491980
---timing 1---
use int 6097315
(int)   4603592
use x87 1662360
---timing 2---
use int 2427691
(int)   4532759
use x87 1445269
compiler 内置的 _ftol 表现不怎么样,比整数版还是慢了一倍。那个浮点版本是做参考的,虽然快,但是语义和 C 语言要求的不太一样,依赖 rounding mode 的设置。所以不推荐使用。 关于 double 向 int 转换,参考另一篇 blog :double to int 神奇的 magic number

Comments

VC6->VC2005
可以用编译器选项
/QIfist

我也写了一篇blog文章,讨论了各种浮点数取整的实现和速度对比:《代码优化-之-优化浮点数取整》
http://blog.csdn.net/housisong/archive/2007/05/19/1616026.aspx

不太清楚情况,说几句.

程序员 也可以 对事情发表一些看法嘛. 我们对事,不对人. 为的是交换观点,解决问题,警诫自己.
至于别人怎样,就怎样去吧. 如果有些人想与真正的程序员划分界限,他们到头来害的是自己.


既然x87指令那么快为什么还要用软件方法呢?

VS2005变慢的原因是因为VS2005传递参数用了浮点堆栈来设置参数的,多了2条浮点指令,而VS2003是直接push一个常数的。
还有一个奇怪的地方是我自己写了个转换函数,用了那个SSE指令转换,却得到了一个非常慢的结果,没暂时还没找到原因。

vs2003 和 vs2005 是在同一台机器上测的吗? 怎么几个函数的速度差那么远? 除了 (int) 这个,另外两个都变慢了?

我用VS2003测得的结果是:
use int 1706568
(int) 3702616
use x87 1234884

用VS2005测得的结果是:
use int 2405772
(int) 2178028
use x87 2580324

VS2005使用了SSE指令优化取得了更快的结果。

还有,如果你觉得刚才那个留言不方便发表,就不用发表了,你看到就可以了,你也不用猜我是谁,你猜不到的,呵呵,因为我是个潜水艇,只不过最近我也发生跟你一样的事情,深刻明白你的感受,所以来共鸣一下而已,哈哈

云飞兄,看到你的blog,感触万分,因为最近我也跟你犯了同样错误,不过你是因为失言得罪不少网友,我是因为失言得罪了一家大公司(因为这家大公司大家都知道,所以不便说出来),不过我马上醒悟,并且道歉了,你可能就比较困难拉,有时候想,我们做开发的,表达能力本来就有限,对这些事不是很了解,而且专工有专职,公关的事情有公关做,千万不要自做主张,踏这个混水呢,不过这个事情上说csdn的责任编辑则有点不厚道了,就算批驳你的文章也应该先提个醒,因为大家都知道你是不清楚事情内幕的,责任编辑觉得你说的地方有错也应该先告知你,先确认你是否理解无误才批驳.
名也不留拉,这个世界还是慎言好啊.毕竟我们不是表达能力和公关能力很强的人,我们是做开发的,说些开发外的话(特别是在公众场合),本是出与善意,也会被人误解,曲解(故意的也有,无意的也有,但是结果还是一样,被人利用了)

Post a comment

非这个主题相关的留言请到:留言本