簡體   English   中英

VS:_BitScanReverse64固有的意外優化行為

[英]VS: unexpected optimization behavior with _BitScanReverse64 intrinsic

以下代碼在調試模式下可以正常工作,因為_BitScanReverse64被定義為在未設置Bit的情況下返回0。 引用MSDN :(返回值為)“如果設置了索引,則為非零;如果未找到設置位,則為0”。

如果我以發布模式編譯此代碼,它仍然可以工作,但是如果啟用了編譯器優化(例如\\ O1或\\ O2),則索引不為零,並且assert()失敗。

#include <iostream>
#include <cassert>

using namespace std;

int main()
{
  unsigned long index = 0;
  _BitScanReverse64(&index, 0x0ull);

  cout << index << endl;

  assert(index == 0);

  return 0;
}

這是預期的行為嗎? 我正在使用Visual Studio Community 2015,版本14.0.25431.01更新3。(我留了cout,以便在優化過程中不刪除變量索引)。 還有一種有效的解決方法,還是我不應該直接使用此內在編譯器?

AFAICT, 當輸入為零時內在函數在index留下垃圾,比asm指令的行為弱。 這就是為什么它具有單獨的布爾返回值和整數輸出操作數的原因。

盡管index arg被引用,編譯器仍將其視為僅輸出。


unsigned char _BitScanReverse64 (unsigned __int32* index, unsigned __int64 mask)
英特爾針對相同內在函數的內在函數指南文檔似乎比您鏈接的Microsoft文檔更清晰,並且為MS文檔試圖說的內容提供了一些啟示。 但是仔細閱讀后,他們似乎都說了同樣的話,並在bsr指令周圍描述了一個薄包裝。

當輸入為0時, Intel將BSR指令記錄為產生“未定義的值”,但在這種情況下設置ZF。 但是,AMD記錄為目標不變:

AMD的BSFAMD64體系結構程序員手冊第3卷:通用和系統說明中的 條目

...如果第二個操作數包含0,則指令將ZF設置為1,並且不更改目標寄存器的內容。 ...

在當前的Intel硬件上,實際行為與AMD的文檔相符:當src操作數為0時,目標寄存器保持不變。這也許就是為什么MS將其描述為僅在輸入為非零時設置Index (並且內部函數的返回值為非零)。

在Intel( 但可能不是AMD )上,這甚至不會將64位寄存器截斷為32位。 例如mov rax,-1 ; bsf eax, ecx (ECX為零)離開RAX = -1(64位),而不是從xor eax, 0獲得的0x00000000ffffffff 但是對於非零ECX, bsf eax, ecx具有零擴展到RAX的通常效果,例如留下RAX = 3。


IDK為什么英特爾仍未對此進行記錄。 也許真正的舊x86 CPU(例如原始386?)以不同的方式實現它? 英特爾和AMD經常超越x86手冊中記錄的內容,以免破壞現有的廣泛使用的代碼(例如Windows) ,這可能就是這樣開始的。

在這一點上,英特爾似乎不太可能會放棄對輸出的依賴性,並讓實際的垃圾或輸入== 0時為-1或32,但是缺少文檔使該選項處於打開狀態。

SKYLAKE微架構放棄了假依賴於lzcnttzcnt (和稍后的uarch下降虛假DEP的popcnt ),同時仍然保留依賴bsr / bsf 為什么破壞LZCNT的“輸出依賴性”很重要?


當然,由於MSVC優化了index = 0初始化,因此大概只使用它想要的任何目標寄存器,而不必使用保存C變量先前值的寄存器。 因此,即使您願意,我也不認為您可以利用未經修改的dst行為,即使AMD對此有保證。

因此,以C ++術語來說,內在函數對index沒有輸入依賴性 但是在asm中,該指令確實對dst寄存器具有輸入依賴性,就像add dst, src指令一樣。 如果編譯器不小心,可能會導致意外的性能問題。

不幸的是,在Intel硬件 ,即使結果從不依賴於popcnt / lzcnt / tzcnt asm指令,它們也對目標地址有錯誤的依賴 不過,編譯器現在已經知道了,因此可以解決此問題,因此,在使用內部函數時,您不必擔心它(除非您擁有超過兩年的編譯器,因為它是最近才發現的)。


您需要檢查它以確保index有效,除非您知道輸入為非零。 例如

if(_BitScanReverse64(&idx, input)) {
    // idx is valid.
    // (MS docs say "Index was set")
} else {
    // input was zero, idx holds garbage.
    // (MS docs don't say Index was even set)
    idx = -1;     // might make sense, one lower than the result for bsr(1)
}

如果要避免執行此額外的檢查分支,那么如果您要瞄准足夠新的硬件(例如Intel Haswell或AMD Bulldozer IIRC),則可以通過不同的內在函數使用lzcnt指令 即使輸入全零,它也“起作用”,並且實際上計數前導零而不是返回最高設置位的索引。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM