“靠汇编获得高效率”纯属谬论

gigix 2003-04-11 10:15:05
Solstice:

 

关于汇编、优化、效率等议题,再聊两句:
Computer Organization & Design 2/e by Hennessy & Patterson 一书中,186页:

Fallacy: Write in assembly language to obtain the highest performance.
Fallacy 是“谬论”的意思。

This battle between compilers and assembly language coders is one situation in which humans are losing ground. 在编译器与汇编语言编码员的抗衡中,人类的领地正在缩小。一个汇编程序员要想与编译器竞争,至少深入了解流水线、内存架构(即cache)。

现在芯片技术发展太快了,以至于以前的“常识”变成了认识的误区:整数运算比浮点运算快,加法比乘法快,单精度浮点数比双精度快等等。
而具我实测的结果,整数的加法、减法、乘法几乎一样快;浮点数(无论单双精度)的加法、减法、乘法几乎一样快;而且,浮点数的乘法和整数加法几乎一样快。这一现象,使得某些“优化行为”(用数次加法取代一次乘法)实际上变成“劣化”。
目前看来,除法和整数的取模运算要慢得多,但或许过不了几年,这种认识又将成为历史。

cache对程序性能的影响,我举一个亲身遇到的例子(我的CPU的L2 Cache = 256k):
FFT算法的运算量是N log N,N 是2的整数幂。
如果用单精度浮点数(float)执行运算,当 N <= 32768 时,运算时间确实是符合 N log N 的规律,但如果 N >= 65536,速度立刻下降 7~9 倍。
如果用双精度浮点数(double)执行运算,当 N <= 16384 时,运算时间确实是符合 N log N 的规律,但如果 N >= 32768,速度下降 4~10 倍。
用多个编译器进行测试,结果相近。为什么会这样?

float的情况 sizeof(float) == 4:32768 * 4 * 2 = 256k (乘以2是因为一个复数的实部和虚部各占一个float)
sizeof(double) == 8 : 16384 * 8 * 2 = 256k。
可以看出,速度的转折点处,数据量正好等于CPU L2 Cache的大小。

我又在一台 CPU L2 Cache = 512k 的机器上进行测试,这次 float 的阈值是65536,double 的阈值是32768。又正好和 Cache 大小匹配。有多少人写程序(尤其在“优化”程序时)考虑到了CPU Cache的影响?至少,密集操作的数据要放在一起,

字节的对齐,有时也会成问题。x86中,存放double类型的数据的地址,最好是8的倍数,否则影响速度。比如VC和GCC的malloc返回的指针是按8字节对齐,而BCC5.5是按4字节对齐。结果使得BCC版的FFT程序,如果使用double类型,那么有时(当malloc返回的地址是4的奇数倍的时候)会出现执行速度慢一倍的情况。当然,这个问题解决起来比较容易。

指令的调度(scheduling)也是一个大问题。例如有的 CPU 执行一次加法只要1个周期,但是在接下来的两个周期内,不能立即取用答数。如果在这种机器上写汇编程序,在执行加法之后,立即取用答数,会空等(浪费)两个周期。指令调度还包括合理利用 Pentium的U V两条流水线,使相邻指令能并行执行繁杂议题等等。而Pentium是1993年上市的,距今已有10年,10年来,CPU又复杂了许多。

我要说的是,不要想当然地认为汇编就会比较快,指令级的优化非常复杂、琐碎,这种优化绝大多数情况应该交给编译器去做。只有编译器(尤其是CPU厂商自己的编译器、实力数一数二的大公司的编译器或是广泛使用的经过众多一流黑客高手调校的免费编译器)才能同时兼顾许许多多的优化细则。70 年代以来,主流的操作系统(Unix系列从早期的Version 5、7到SVR4、BSD4.4,Windows系列等等)都主要采用 C 来编写(辅以少量的汇编代码,一般 5%以下)也可算是一个佐证。

Kernighan 对他的学生们说(见他的课程主页):

Don't worry about performance if it doesn't
matter (and it often doesn't).
When it does,
- get the algorithm right
- use the compiler's optimization
- code tune, as a last resort

Kernighan 这里甚至都没有提到 assembly language 。

...全文
307 50 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
50 条回复
切换为时间正序
请发表友善的回复…
发表回复
陈硕 2003-05-09
  • 打赏
  • 举报
回复
我的测试结果(其中一组):
用VC++ .NET,Release 模式。Celeron 1.2G, 512M
const int TEST_COUNT = 0x1000000;

int_add_test result: 1093750
int_mul_test result: 1406250
fp_add_test result: 781250
fp_mul_test result: 1093750

改 const int TEST_COUNT = 0x10000000;
int_add_test result: 13750000
int_mul_test result: 17812500
fp_add_test result: 15937500
fp_mul_test result: 15781250

chen3feng 2003-05-09
  • 打赏
  • 举报
回复
我是想尽量避开内存操作的,没想到出错了。极少接触浮点指令,见笑了
都用1*1,1/1测试吧,呵呵
你的这个我测试的还是比我的整数乘法慢几倍,我改了一下
int_add_test result: 156250
int_mul_test result: 1875000
fp_mul_test result: 468750

不过整数的乘除仍然很慢。

void fp_mul_test()
{
double a = 1;
double b = 67.89;
double c; // = a * b
__asm
{
mov ecx, TEST_COUNT
next:
fld1
fmul b
fstp c
loop next
}

}
陈硕 2003-05-09
  • 打赏
  • 举报
回复
to chen3feng :

你的测试有问题:fmul 如果溢出,速度慢很多,所以建议你把测试浮点成法的函数改为:

void fp_test()
{
double a = 23.45;
double b = 67.89;
double c; // = a * b
__asm
{
mov ecx, TEST_COUNT
next:
fld a
fmul b
fstp c
loop next
}

}

这样保证fmul不会溢出,我发现 fld+fmul+fstp 比 imul 快一点点。

第2个程序也是一样的道理。
chen3feng 2003-05-09
  • 打赏
  • 举报
回复
好像浮点和乘法还是很慢,也许我的程序有错误。也欢迎指出
chen3feng 2003-05-09
  • 打赏
  • 举报
回复
我的机器: Cy1.7G, 128M RAM.
下面程序打出如下结果:

int_add_test result: 156250
int_mul_test result: 1718750
fp_add_test result: 119531250
fp_mul_test result: 119062500
Press any key to continue

#include <stdio.h>
#include <windows.h>

const int TEST_COUNT = 0x1000000;

unsigned __int64 test_time(void(*fp)())
{
HANDLE hThread = GetCurrentThread();
FILETIME t0, t1;
FILETIME dummy;
GetThreadTimes(hThread, &dummy, &dummy, &dummy, &t0);
(*fp)();
GetThreadTimes(hThread, &dummy, &dummy, &dummy, &t1);
return (unsigned __int64&)t1-(unsigned __int64&)t0;
}

void int_mul_test()
{
__asm
{
mov ecx, TEST_COUNT
next:
imul eax
loop next
}
}

void int_add_test()
{
__asm
{
mov ecx, TEST_COUNT
next:
add eax,eax
loop next
}
}

void fp_add_test()
{
__asm
{
mov ecx, TEST_COUNT
next:
fadd
loop next
}
}

void fp_mul_test()
{
__asm
{
mov ecx, TEST_COUNT
next:
fmul
loop next
}
}

#define TEST_AND_REPORT(fn) printf(#fn " result:\t%I64u\n", test_time(fn))

int main()
{
TEST_AND_REPORT(int_add_test);
TEST_AND_REPORT(int_mul_test);
TEST_AND_REPORT(fp_add_test);
TEST_AND_REPORT(fp_mul_test);
}
chen3feng 2003-05-09
  • 打赏
  • 举报
回复
载我的机器上,下面程序打出如下结果
156250
7187500
Press any key to continue

#include <stdio.h>
#include <windows.h>

const int TEST_COUNT = 0x100000;

void int_test()
{
__asm
{
mov ecx, TEST_COUNT
next:
imul eax
loop next
}
}

unsigned __int64 test_time(void(*fp)())
{
HANDLE hThread = GetCurrentThread();
FILETIME t0, t1;
FILETIME dummy;
GetThreadTimes(hThread, &dummy, &dummy, &dummy, &t0);
(*fp)();
GetThreadTimes(hThread, &dummy, &dummy, &dummy, &t1);
return (unsigned __int64&)t1-(unsigned __int64&)t0;
}

void fp_test()
{
__asm
{
mov ecx, TEST_COUNT
next:
fmul
loop next
}
}

int main()
{
printf("%I64u\n", test_time(int_test));
printf("%I64u\n", test_time(fp_test));
}
brucegong 2003-05-09
  • 打赏
  • 举报
回复




高手用C和菜鸟用汇编,我相信还是C的效率高。如果都是高手……还是会编好啊




chen3feng 2003-05-09
  • 打赏
  • 举报
回复
我測試的結果是,浮點還是比整數慢
chen3feng 2003-05-09
  • 打赏
  • 举报
回复
我的测试结果,也改TEST_COUNT = 0x10000000了
VC6, Release(其实Debug结果也不会改变,VC6和VC.NET也不会有区别,都是手写的汇编)
没有内存操作.
我也测试了几组,结果没明显的差别,GetThreadTimes还是比较可靠的.

int_add_test result: 3593750
int_mul_test result: 28125000
int_div_test result: 101093750
fp_add_test result: 8750000
fp_mul_test result: 12187500
fp_div_test result: 66250000
Press any key to continue
浮点乘法笔浮点加法慢一点点,比整数乘法快。整数加法依然最快。除法都很慢。
晚上回家再用P4试试看.不知道还有疏漏的地方没有.

void fp_add_test()
{
__asm
{
mov ecx, TEST_COUNT
fld1
fld1
next:
fadd st, st(1)
loop next
}
}

void fp_mul_test()
{
__asm
{
mov ecx, TEST_COUNT
fld1
fld1
next:
fmul st, st(1)
loop next
}

}
陈硕 2003-05-04
  • 打赏
  • 举报
回复
说明一点,原帖是我写的,gigix在转贴时把我在帖子中引用的“Fallacy: Write in assembly language to obtain the highest performance.”翻译为本帖的标题:“靠汇编获得高效率”纯属谬论。

感谢gigix转贴这篇帖子,各位的发言尽可往我身上招呼。
step_by_step 2003-05-04
  • 打赏
  • 举报
回复
csdn的确不如以前了。几年前csdn刚开始的时候,气氛比现在好多了,精品文也很多。现在的好文太少了。你稍微提点意见,就会产生口水战。
southdy 2003-05-04
  • 打赏
  • 举报
回复
不能一概而论,他的说法也可以说是个谬论。
celeil 2003-05-04
  • 打赏
  • 举报
回复
汇编语言有没有它的缺陷?毫无疑问,是有的。任何计算机语言都有其局限性,但问题不在于这个,而在于往往CSDN里的这些言论,喜欢走极端,要么捧上天,要么就扁的一钱不值,这是做学问的态度么?这是作为一个程序员的严谨态度么?往往有些人喜欢跟风、追捧,一个帖子骂了,下面很快就会有人回“骂的好”,但骂的是什么,他真的清楚么?
  整日游荡于这帖那帖之间,我们该反省一下,我们整日究竟做了些什么?我们的编程技术提高了么?我们的社会阅历丰富了么?还是我们都把时间号在这没有结果的争论中?不客气地说,gigix这篇文章一钱不值。gigix你有几年的汇编经验?开发过多少汇编的相关工程?你凭什么骂汇编?
  俗话说满瓶子不动半瓶子晃荡。我认识很多常常光顾CSDN的高手,他们除了偶尔来查一些资料、回答一些问题或者在技术版里提问以外,他们都在踏踏实实的干自己的事情,而不是到处对自己不精通的东西喷口水。
  看看现在的技术版,还有几篇真正有价值的技术问题和回答?除了无病呻吟、走极端的对某种计算机语言喷口水、初学者发表对某种语言的长篇大论外,剩下的就是“救命啊”,进去一看,连编译器和计算机语言之间的关系都不懂得外行问题。我不是说不赞成提问,但有些问题是可以通过书本学会的,有些问题才是值得讨论的。这里大多数的问题,除了能说明那个帖的帖主学习不踏实、门外汉之外,体现不了别的什么了。君不见常有这样的回答“看书吧”、“回家好好翻书,书上都有”。这说明了什么呢?
  CSDN最近都干了些什么?程序员杂志我已经停买了,因为看不到什么有用的东西了,长篇大论的人物和管理,以及对最新的、不是很普及的技术的介绍,还有能在CSDN上查到的提问和回答外,还剩下什么?
  CSDN堕落了,很遗憾它的堕落,在这里很难在像以前那样学到东西了。
  记得曾经有个商界牛人说过,在专业品质上投资一成,可以在市场运作上节省三成。忘记是谁说的了,但CSDN人,难道你们没有体会到么?
你们引进了jjhou,你们看过人家现在在做什么?人家主要精力不是到各地宣传,而是把精力花在提高专业品质上。jjhou的网站一个月没更新了,他的网站也很少更新,因为他的精力不在这上面。还有在CSDN上哄啊闹啊的网友们,你们常见pam的身影么?常见孟延的口水么?为什么不想想他们都在做什么?
  每当别人拿高薪的时候,除了羡慕,你们还干了什么?每当有新书发布的时候,除了闲价格贵和到处找免费以外,你们还干了什么。有些人下载了几G的书,我想问,你看了多少?看懂了多少?看会了多少?pam下载的书一定没你多,孟延下载的书也没你多,jjhou家那么几橱柜的书,也没你磁盘里的多。但人家做了些什么,你做了些什么?
  不要整天抱怨书架太贵、老板太吝啬、工作太辛苦。为什么有人能适应这个社会而你不行?一个抱怨社会的人,就像一个抱怨市场没有品位的老板,他不改进自己的产品以适应社会,而一味的抱怨社会上的人没有品位,这不可笑么?而我亲爱的网友们,我们不正是这些可笑的厂长么?
celeil 2003-05-04
  • 打赏
  • 举报
回复
大佛,如果天下所有的事情都得先去实践一遍的话,岂不是太愚蠢了么?回家好好复习有关知识把。该回复的已经有人回复了,我就不再这儿更罗嗦了。
就如同搞建筑的,人家问他他设计的这幢楼结实么?他可以说:“实践出真知,住了才知道”吗?
著名物理学家费曼,他最满意自己的一个优点,你知道是什么吗?就是不迷信权威。
短歌如风 2003-05-03
  • 打赏
  • 举报
回复
还有:浮点乘法已经很快了,但仍然比加法慢。根据我的测试,48次乘法72次加法的斯特拉森方法4*4矩阵乘法要比64次乘法49次加法的标准矩阵乘法快,就是因为浮点乘法比加法慢的原因。不过,如果使用多次加法来代替一次乘法实在是一种错误的做法。
短歌如风 2003-05-03
  • 打赏
  • 举报
回复
汇编的灵活性是其他语言无法比拟的!!
汇编的开发效率之低和不可移植性也是其他语言无法比拟的!!
一般应该只用汇编开发那些其它语言无法做到而又需要的功能,并尽可能限制在最小代码范围内。当然如果你的目标机器只有汇编语言可用那就别无选择了。
至于效率,这并不是采用汇编的理由。对效率影响最大的既不是手工写汇编也不是优秀的编译优化,而是优秀的算法。用汇编写的或用最好的优化编译的冒泡排序在数据量大时绝比不上不加优化用Pascal写的快速排序。当然,有时汇编的一些特殊指令会使时间效率在同一数量级上提高很多(就是说虽然没有降低时间复杂度,但仍然快很多),我在做多边形的扫描线填充处理就发现,由于我是处理32位位图,每个点是一个整数(在intel x86 32位模式下),而填充的每个点都是相同的,如果用STOSD指令会快很多,但我没有用汇编去做这个算法,而是用汇编写了一个FillBy32Bit函数,然后调用这个函数。如果我的程序需要移植到其它机器上,我只要改这个函数就行了,不需要改写整个算法的实现。
至于把手工汇编优化和编译器优化去比较,我觉得,手工汇编可以比编译器优化更快,但很难做到比编译器更快。
stanely 2003-05-03
  • 打赏
  • 举报
回复
看来英语学好比汇编更重要阿
文山wenshan 2003-05-01
  • 打赏
  • 举报
回复
up
hanzac 2003-04-26
  • 打赏
  • 举报
回复
汇编是基础,用来构建 C/C++ 等无法构建的结构
汇编的灵活性是其他语言无法比拟的!!
dieoffool 2003-04-20
  • 打赏
  • 举报
回复
高手真多啊!
学习中。。。
加载更多回复(30)

15,447

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 非技术区
社区管理员
  • 非技术区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧