我這里有一些例程都做同樣的事情:它們將浮點數限制在 [0,65535] 范圍內。令我驚訝的是編譯器(gcc -O3)使用了三種,count 'em,三種不同的方式來實作float-min和float-max。我想了解為什么它會生成三種不同的實作。好的,這是 C 代碼:
float clamp1(float x) {
x = (x < 0.0f) ? 0.0f : x;
x = (x > 65535.0f) ? 65535.0f : x;
return x;
}
float clamp2(float x) {
x = std::max(0.0f, x);
x = std::min(65535.0f, x);
return x;
}
float clamp3(float x) {
x = std::min(65535.0f, x);
x = std::max(0.0f, x);
return x;
}
所以這是生成的程式集(洗掉了一些樣板)。可在帶有 GCC10.3的https://godbolt.org/z/db775on4j-O3
上重現。(也顯示了 clang14 的選擇。)
CLAMP1:
movaps %xmm0, %xmm1
pxor %xmm0, %xmm0
comiss %xmm1, %xmm0
ja .L9
movss .LC1(%rip), %xmm0 # 65535.0f
movaps %xmm0, %xmm2
cmpltss %xmm1, %xmm2
andps %xmm2, %xmm0
andnps %xmm1, %xmm2
orps %xmm2, %xmm0
.L9:
ret
CLAMP2:
pxor %xmm1, %xmm1
comiss %xmm1, %xmm0
ja .L20
pxor %xmm0, %xmm0
ret
.L20:
minss .LC1(%rip), %xmm0 # 65535.0f
ret
CLAMP3:
movaps %xmm0, %xmm1
movss .LC1(%rip), %xmm0 # 65535.0f
comiss %xmm1, %xmm0
ja .L28
ret
.L28:
maxss .LC2(%rip), %xmm1 # 0.0f
movaps %xmm1, %xmm0
ret
所以這里似乎有 MIN 和 MAX 的三種不同實作:
- 使用比較和分支
- 使用
minss
和maxss
- 使用比較、
andps
、andnps
和orps
.
有人可以澄清以下內容:
- 這些都是相同的速度,還是其中一個更快?
- 編譯器如何最終選擇所有這些不同的實作?
andps
,andnps
, 等等到底是什么東西?- 同時 使用
minss
andmaxss
并且不使用分支會更快嗎?
uj5u.com熱心網友回復:
這些都是相同的速度,還是其中一個更快?
它不一樣,但差異取決于機器和給定的輸入。
編譯器如何最終選擇所有這些不同的實作?
因為編譯器并不像你想象的那么聰明。
andps、andnps 等等到底是什么東西?
就是下面這個邏輯。
float y = 65535;
float m = std::bit_cast<float>(-(x < y));
return x & m | y & ~m;
您不能&
在 C (和 C)中使用浮點數,但您會明白的。
同時使用 minss 和 maxss 并且不使用分支會更快嗎?
通常,是的。您可以在 ( https://uops.info/table.html )查看每條指令的延遲和吞吐量。我會在匯編中寫下以下內容。
xorps xmm1, xmm1
minss xmm0, [_ffff]
maxss xmm0, xmm1
但是,如果您知道某個分支很可能會被采用,那么使用一個分支跳過一個可能會更快。讓我們看看編譯器對分支提示做了什么。
float clamp_minmax(float x) {
return std::max(std::min(x, 65535.0f), 0.0f);
}
float clamp_hint(float x) {
if (__builtin_expect(x > 65535, 0)) {
return 65535;
}
if (__builtin_expect(x < 0, 0)) {
return 0;
}
return x;
}
GCC 基本上不在乎,但有趣的是,Clang 會產生不同的代碼。
clamp_minmax(float):
movss xmm1, dword ptr [rip .LCPI0_0]
minss xmm1, xmm0
xorps xmm0, xmm0
maxss xmm0, xmm1
ret
clamp_hint(float):
movaps xmm1, xmm0
movss xmm0, dword ptr [rip .LCPI1_0]
ucomiss xmm1, xmm0
ja .LBB1_2
xorps xmm0, xmm0
maxss xmm0, xmm1
.LBB1_2:
ret
ucomiss -> ja
并不比 single 快minss
,但是如果你可以通過分支來跳過maxss
大部分時間,那么分支版本可能會比無分支版本運行得更快,因為你可以跳過無分支版本maxss
中由于依賴于上一個minss
。
另請參閱,(在 x86 上給出無分支 FP min 和 max 的指令是什么?)。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/495912.html