首页
社区
课程
招聘
代码优化方面的一个问题
发表于: 2010-8-11 17:34 5324

代码优化方面的一个问题

2010-8-11 17:34
5324
x = x + (x < 0 ? 100 : 0);
这句不用条件语句该怎么改写?(就是改成一些位运算)以前学过一些,现在忘记了。。。
因为有些地方运算次数达到几亿次,所以条件语句要慢很多。。。

[课程]Linux pwn 探索篇!

收藏
免费 0
支持
分享
最新回复 (18)
雪    币: 459
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
c++里写一下呗,release版
2010-8-11 20:51
0
雪    币: 459
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
debug下代码:
00401826   xor         ecx,ecx
00401828   cmp         dword ptr [ebp-4],0
0040182C   setge       cl
0040182F   dec         ecx
00401830   and         ecx,64h
00401833   mov         edx,dword ptr [ebp-4]
00401836   add         edx,ecx
00401838   mov         dword ptr [ebp-4],edx
2010-8-11 21:09
0
雪    币: 459
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
release版:
00401010  |.  8B4424 00     MOV EAX,DWORD PTR SS:[ESP]
00401014  |.  33C9          XOR ECX,ECX
00401016  |.  85C0          TEST EAX,EAX
00401018  |.  0F9DC1        SETGE CL
0040101B  |.  49            DEC ECX
0040101C  |.  83E1 64       AND ECX,64
0040101F  |.  03C1          ADD EAX,ECX
2010-8-11 21:29
0
雪    币: 401
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
为什么要写x = x + (x < 0 ? 100 : 0);这样的代码呢?完全可以写成这样:
if(x < 0)
    x += 100;

这样编译出的汇编代码应该是这样的:
test       eax,eax
jge        Label
add        eax,64
Label:

岂不是更有效率?(我没编译,你可以自己编译一下看是不是这样。)
如果想获得最高效的程序,那就写简单的C代码,一句代码只执行一个最简单基本的操作,经过编译器优化后,往往可以得到比自己写汇编还高效的机器码,毕竟现在的编译器优化太无敌了。
把C代码写的太紧凑的话,得到的机器码往往不会被最佳优化。
2010-8-11 22:32
0
雪    币: 452
活跃值: (72)
能力值: ( LV9,RANK:330 )
在线值:
发帖
回帖
粉丝
6
5楼的有个误区
if(x < 0)
x += 100;
反而是不优化的
跳转指令导致指令预取流水线效率低下
1楼和4楼已经是不用SIMD的情况下C/C++最优化的结果了

用asm的话,还可以优化一点
__asm {
  mov eax, x
  cdq              ; edx = x >= 0 ? 0 : 0xFFFFFFFF
  and edx, 0x64    ; edx = x >= 0 ? 0 : 100
  add x, edx
}

这样就避免了判断和跳转

想要进一步优化,把数据对齐,然后用MMX/SSE2/SSE3/SSE4......

PS:想让编译器最大限度优化,尽量多用 ? : 少用 if
不过代码可读性会变差,自己权衡吧
2010-8-12 01:30
0
雪    币: 401
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
竟忘了cdq,膜拜ls,不过如果这段代码运行次数很多的话(lz说上亿次),跳转指令就不会影响效率了,在经过前面的几次跳转后CPU会记住正确的跳转目标,以后就不会错了。
2010-8-12 14:46
0
雪    币: 468
活跃值: (52)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
CPU会记住正确的跳转目标吗?
不会吧?
CPU只会千篇一律地按固定格式解码。
也就是说,如果第一次跳转CPU跳错了,又清除流水线重来。
那么下次的时候,CPU还是会跳错,因为CPU判断预取跳转后
的指令的算法是不变的。在同一个地方只会发生同样的事情,
就是同样的跳错。
当然,可以使用跳转预示前缀来告诉CPU,预取指令是取跳转后
的指令还是非跳转而紧接着的下一个EIP。
2010-8-12 15:18
0
雪    币: 468
活跃值: (52)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
test       eax,eax
jge        Label
add        eax,64
Label:
=========================以上为代码段A
__asm {
  mov eax, x
  cdq              ; edx = x >= 0 ? 0 : 0xFFFFFFFF
  and edx, 0x64    ; edx = x >= 0 ? 0 : 100
  add x, edx
}
=========================以上为代码段B

我并不认为代码段B比代码段A的 执行速度会更快?
并不一定的。
2010-8-12 15:28
0
雪    币: 468
活跃值: (52)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
代码段A,我改为

cmp x,o
jnb lable
add x,64
lable:
2010-8-12 15:32
0
雪    币: 468
活跃值: (52)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
Nehalem同时也降低了同步原语(起始同步),比如LOCK prefix、XCHG和CMPXCHG等指令的延迟。同步原语在多线程编程中是必需的,而多线程的扩展性被同步所限制,通过降低延迟,可以提高现在多线程软件的性能。INTEL宣称,Nehalem的LOCK CMPXCHG指令(其作用是使整个流水线串行化)的延迟是P4的约20%,Core 2的约60%。尽管降低了延迟,但行为仍然和以前的CPU还是一样的,锁定指令(Lock)并不是管道化的,即使后面的操作可以被提前到锁定指令之前来执行。
==========================

Nehalem改进了宏操作融合(macro-op fusion)。Core 2可以在32bit模式下,把TEST/CMP(比较指令)和其后的Jcc(条件分支指令)给解码融合成一个微操作(uop):CMP+Jcc。这样就增加了解码带宽,并减少了微操作数量。而Nehalem的宏操作融合则包括了更多的条件分支指令:JL/JNGE, JGE/JNL, JLE/JNG, JG/JNLE。TEST/CMP和这些条件分支指令都将被解码成一个微操作:CMP+ Jcc。而且,Nehalem的宏操作融合可以是32bit和64bit模式。这是很重要的,因为现在大多数服务器都运行的是64bit操作系统,即使是桌面电脑也开始更多的欢迎64bit操作系统
2010-8-12 15:48
0
雪    币: 724
活跃值: (81)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
12
...
Nehalem改进了宏操作融合(macro-op fusion)。Core 2可以在32bit模式下,把TEST/CMP(比较指令)和其后的Jcc(条件分支指令)给解码融合成一个微操作(uop):CMP+Jcc。这样就增加了解码带宽,并减少了微操作数量。
...

是micro-op fusion(“微码融合”)吧。
2010-8-12 17:27
0
雪    币: 452
活跃值: (72)
能力值: ( LV9,RANK:330 )
在线值:
发帖
回帖
粉丝
13
就这段代码来说,分支是不可预测的,因为每次x的值都不一样
fusion不代表没有penalty

intel的manual也说道:

[LEFT]3.4.1.1 Eliminating Branches
Eliminating branches improves performance because:[/LEFT]


[LEFT]It reduces the possibility of mispredictions.
[LEFT]
• [/LEFT]
[/LEFT]
[LEFT]It reduces the number of required branch target buffer (BTB) entries. Conditional
[LEFT]branches, which are never taken, do not consume BTB resources.

There are four principal ways of eliminating branches:
• [/LEFT]
[/LEFT]
[LEFT]Arrange code to make basic blocks contiguous.
[LEFT]
• [/LEFT]
[/LEFT]
[LEFT]Unroll loops, as discussed in Section 3.4.1.7, “Loop Unrolling.”
[LEFT]
• [/LEFT]
[/LEFT]
[LEFT]Use the CMOV instruction.
Use the SETCC instruction.


fusion是在无法消除branch时最后的办法

不过比起这个,cache miss的penalty更多
x的值最好在内存中连续分布,并且size尽量不要超过L1/L2/L3

PS:
cmp x, 0
jnb lable
这个不可以fusion

[LEFT]The first instruction of the macro-fused pair must be a CMP or TEST instruction. This
instruction can be REG-REG, REG-IMM, or a micro-fused REG-MEM comparison. The
second instruction (adjacent in the instruction stream) should be a conditional[/LEFT]
branch.

MEM-IMM是不支持的
[/LEFT]
2010-8-12 21:25
0
雪    币: 401
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
CPU会记住正确的跳转目标吗?
不会吧?
CPU只会千篇一律地按固定格式解码。
也就是说,如果第一次跳转CPU跳错了,又清除流水线重来。
那么下次的时候,CPU还是会跳错,因为CPU判断预取跳转后
的指令的算法是不变的。在同一个地方只会发生同样的事情,
就是同样的跳错。

如果是这样的话那么分支预测技术全部都没用喽?
就这段代码来说,分支是不可预测的,因为每次x的值都不一样

嗯,如果单从这两行代码来看的话,分支确实是不可预测的,但是lz所说“运算次数达到几亿次”,我理解为x为一个很大的负数,或者在一个执行次数很多的循环内,就像这样:
for(;;)
{
x = x + ( x < 0 ? 100 : 0);
//其它操作,还有决定啥时候break
}
否则很难会有哪行代码执行上亿次,如果是这样的话,BTB显然可以工作的很好,而且效率会很高。
2010-8-12 22:31
0
雪    币: 452
活跃值: (72)
能力值: ( LV9,RANK:330 )
在线值:
发帖
回帖
粉丝
15
对循环来说,分支预测是有效的
对x = x + ( x < 0 ? 100 : 0)来说,分支预测是无效的
而且,就算分支预测正确,还是有penalty的
消除分支才是上策
2010-8-12 23:17
0
雪    币: 209
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
真的很感谢大家的回复。很抱歉最近Kx币都用完了。。就只能给10个了。。。
2010-8-13 01:18
0
雪    币: 209
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
如果x初始值为3,每次x = x + ( x < 0 ? 100 : 0)之前都x-=5,那分支预测有效吗?
2010-8-13 01:26
0
雪    币: 452
活跃值: (72)
能力值: ( LV9,RANK:330 )
在线值:
发帖
回帖
粉丝
18
在Pentium 4中,BTB只能保存16个最近的跳转.如果你的循环体有太多条件跳转,分支预测就不行了
可以用0x2E,0x3E前缀来帮助CPU进行分支预测
参见:http://software.intel.com/en-us/articles/branch-and-loop-reorganization-to-prevent-mispredicts/
2010-8-13 02:42
0
雪    币: 209
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
看来是用不到分支预测的。。。谢谢你~
2010-8-13 10:30
0
游客
登录 | 注册 方可回帖
返回
//