我在看论文时看到如下两个代码,论文中说代码2比代码1时间上有20%提高,请问怎么理解呢?代码1:
// . . . some computations
for ( int p = 0 ; p < 2 ; p++)
{
r e a l Rr = 0 ;
r e a l Ri = 0 ;
for ( int i = 0 ; i < 2 ; i++)
{
int ind = ( p i )%2;
Rr += R[ i ] [ 0 ] * Wr[ ind ] [ 2 ] - R[ i ] [ 1 ] * Wi[ ind ] [ 2 ] ;
Ri += R[ i ] [ 0 ] * Wi [ ind ] [ 2 ] + R[ i ] [ 1 ] * Wr[ ind ] [ 2 ] ;
}
// . . . some computations
// . . . w r i t e back Rr and
}
代码2:
// . . . some computations
for ( int p = 0 ; p < 2 ; p++)
{
r e a l Rr = 0 ;
r e a l Ri = 0 ;
for ( int i = 0 ; i < 2 ; i++)
{
int ind = ( p i )%2;
Rr += R[ i ] [ 0 ] * Wr[ ind ] [ 2 ] ;
Rr -= R[ i ] [ 1 ] * Wi [ ind ] [ 2 ] ;
Ri += R[ i ] [ 0 ] * Wi [ ind ] [ 2 ] ;
Ri += R[ i ] [ 1 ] * Wr[ ind ] [ 2 ] ;
}
// . . . some computations
// . . . w r i t e back Rr and
}
LZ您好:
ILP一般可以用在访存上,将一些从global memory读入数据的操作尽量往前安排,并在读取数据到使用该数据之间安排一些无关的计算操作,这样可以利用中间的无关操作来掩盖一些访存的延迟,以达到提速的目的。(同时可能会使用更多的寄存器作为代价。)
以及不同的架构下可能具体效果不一样,在resident线程较少的时候,ILP可能效果更为显著一些。
具体到您的代码,目测并无明显的差别,所以我也无法判定这段代码是否与20%的提速能力,或者在什么情况下具备20%的提速能力,或者原文代码的提速是否仅依靠这一段来实现。
大致如此,个人意见供您参考。
祝您好运~
這兩種代碼我目視的結果應該是不會有差異的。
我認為編譯器應該會把兩種代碼都優化掉,詳細可以查看編譯後ptx
ILP是打破Low Occupancy就低效率的迷思,
概念上是說我一個thread用更多register雖然Occupancy變低了,但register效率更好!
你的代碼要做ILP的話應該是要做4組Ri,Rj,Rk,Rl而不是說把Ri.Rj拆開成兩行吧!
www . cs . berkeley . edu / ~volkov/volkov10-GTC.pdf
這邊有文章可以參考
感謝來自彼岸的iHakka發表觀點。
論壇不僅僅需要版主們,也需要您這樣的古道熱腸。
感謝大家的蒞臨。
LZ您好:
在和玫瑰斑竹深入讨论您的代码之后,终于发现了原论文中宣称“代码2快20%”的原因所在,我先说一下结论:
1:后者确实快
2:这不是ILP引起的,而是因为后者的不同写法减少了计算量,缩短了逻辑依赖链。
3:严格说两种写法是不完全等价的。
具体情况为:
a:第一种写法,编译后,会自动展开循环,Rr的计算需要两次乘法,两次FMA和一次加法,需要5条指令,而Ri也需要5条指令,所以一共是10条指令,这些指令都是每周期一条的吞吐量。
b:第二种写法,编译后,也会自动展开循环,以及Rr的两行计算分别编译为一条FMA,算上循环次数和Ri,一共为4+4=8条FMA指令,同时FMA也是每周期一条的吞吐量。
c:因此,后者快20%,指令更少。
d:因为浮点数本身不满足交换律和结合律,任何改变计算顺序和结合性质的做法都可能造成结果的差异,因此这两种写法是不等价的。同时FMA和单独的一次乘法配合一次加法不同,FMA保留了较多的中间过程精度,两个实现的FMA的用法也不同,因而也有着不同的截断误差。
e:这两种写法都与ILP无关,不知原文中是否真的宣称这20%的速度提升是ILP引起的,如果原文这样宣称,那么应该是不正确的说法,请LZ注意甄别。
大致如此,祝您编码顺利~
ice版主您好,代码1中为什么是5条指令还不太理解,我的理解是Rr的计算是两次乘法 一次加法和一次FMA,请指教。
LZ您好:
第一种写法两次循环展开以后大致是如下的样子:
Rr = (R[ 0 ] [ 0 ] * Wr[ n ] [ 2 ] - R[ 0 ] [ 1 ] * Wi[ n ] [ 2 ]) + (R[ 1 ] [ 0 ] * Wr[ n’ ] [ 2 ] - R[ 1 ] [ 1 ] * Wi[ n’ ] [ 2 ]);
1:这里有两个括号,每个括号里面后面一个乘法先计算,这是两次乘法。然后每个括号内部再计算一次乘加得到两个括号的结果,最后执行一次加法。因为您Rr的初值是0比较特殊,所以不用加上初值,可以省一条。
2:由此,在展开循环以后,Rr需要5条指令,Ri也一样,一共需要10条。
大致如此,祝您编码顺利~
这次彻底明白了 谢谢ice
不客气的,欢迎您常来论坛~
其实对于好的编译器,这个根本没差别。我一直就觉得CUDA编译器比较差。
wzk6_3_8您好:
话不能这么说哦,NVCC定有各种不足,但是这个例子却不能说明NVCC的不足。
这两种写法的逻辑依赖链是不同的,而编译器是不能改变实现顺序的,也就是说编译器没有权利把第一种代码的写法编译成第二种实现(两次FMA)。
当然人在为自己的算法选择具体实现的时候,是可以写成第二种方法的,由此带来的浮点数结果上的差别,也是需要写程序的人自己有一个预判。
我认为这个才是中肯的结论,您觉得呢?
祝您在论坛过的开心~
恩…編譯器的做法確實可能跟我想的不太一樣…
補充:
會用到ILP的情況大概是register用的或很少kernel工作量少,每條thread大部份的時間都在等待上個執行結果。
這個時候即使開了大量的thread也不會增加效率,反而要縮減thread讓1個thread做原本2個thread、4個thread的事情。
這樣thread才會夠忙碌,不會空等。對於這樣的情況Occupancy反而變得不是衡量效率的關鍵。
说的很好,我也认为ILP远比多线程重要。
gpu您好:
12#的观点有前提条件,我觉得不能直接推广到您的13#的结论。
当然您可以继续保留自己的观点,此处仅作简要说明,以免其他看帖的网友迷惑。
祝您好运~
开普勒不是每次每个WARP发射2条指令码?
如果算法设计指令依赖严重,导致每次每个WARP只能发射1条指令,线程再多也就50%利用率。
如果算法的ILP设计很好,很少线程足矣高效率。
当然如果算法本身不能ILP,那也没办法的事。但尽力挖掘ILP远比尽力多线程重要得多,我想这个是正确的。难道不是这样的吗?
这个是没错的.但是一般情况下,编译器会尽量安排平均4条指令里有2条可以同时发射的(因为实际上只需要有50%的机会可以双发射即可满足SP们的要求.当然为了照顾其他单元,越多越好)
但建议kepler上还是多少ILP一下,不用多,能保证2条同时进行足矣.
NV的编译器很好很智能,很多东西做到了编译器里面,真的很不错。
另外,你这个50%的数据是如何得到的呢?这个我没弄明白。