<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 24章 使用x64下的SIMD來處理浮點數 當然,在增加了x64擴展這個特性之后,FPU在x86兼容處理器中還是存在的。但是同事,SIMD擴展(SSE, SSE2等)已經有了,他們也可以處理浮點數。數字格式依然相同(使用IEEE754標準)。 所以,x86-64編譯器通常都使用SIMD指令。可以說這是一個好消息,因為這讓我們可以更容易的使用他們。 24.1 簡單的例子 ``` #!cpp double f (double a, double b) { return a/3.14 + b*4.1; }; ``` 清單24.1: MSFC 2012 x64 /Ox ``` #!bash __real@4010666666666666 DQ 04010666666666666r ; 4.1 __real@40091eb851eb851f DQ 040091eb851eb851fr ; 3.14 a$ = 8 b$ = 16 f PROC divsd xmm0, QWORD PTR __real@40091eb851eb851f mulsd xmm1, QWORD PTR __real@4010666666666666 addsd xmm0, xmm1 ret 0 f ENDP ``` 輸入的浮點數被傳入了XMM0-XMM3寄存器,其他的通過棧來傳遞。 a被傳入了XMM0,b則是通過XMM1。 XMM寄存器是128位的(可以參考SIMD22一節),但是我們的類型是double型的,也就意味著只有一半的寄存器會被使用。 DIVSD是一個SSE指令,意思是“Divide Scalar Double-Precision Floating-Point Values”(除以標量雙精度浮點數值),它只是把一個double除以另一個double,然后把結果存在操作符的低一半位中。 常量會被編譯器以IEEE754格式提前編碼。 MULSD和ADDSD也是類似的,只不過一個是乘法,一個是加法。 函數處理double的結果將保存在XMM0寄存器中。 這是無優化的MSVC編譯器的結果: 清單24.2: MSVC 2012 x64 ``` #!bash __real@4010666666666666 DQ 04010666666666666r ; 4.1 __real@40091eb851eb851f DQ 040091eb851eb851fr ; 3.14 a$ = 8 b$ = 16 f PROC movsdx QWORD PTR [rsp+16], xmm1 movsdx QWORD PTR [rsp+8], xmm0 movsdx xmm0, QWORD PTR a$[rsp] divsd xmm0, QWORD PTR __real@40091eb851eb851f movsdx xmm1, QWORD PTR b$[rsp] mulsd xmm1, QWORD PTR __real@4010666666666666 addsd xmm0, xmm1 ret 0 f ENDP ``` 有一些繁雜,輸入參數保存在“shadow space”(影子空間,7.2.1節),但是只有低一半的寄存器,也即只有64位存了這個double的值。 GCC編譯器生成了幾乎一樣的代碼。 ## 24.2 通過參數傳遞浮點型變量 ``` #!cpp #include <math.h> #include <stdio.h> int main () { printf ("32.01 ^ 1.54 = %lf\n", pow (32.01,1.54)); return 0; } ``` 他們通過XMM0-XMM3的低一半寄存器傳遞。 清單24.3: MSVC 2012 x64 /Ox ``` #!bash $SG1354 DB ’32.01 ^ 1.54 = %lf’, 0aH, 00H __real@40400147ae147ae1 DQ 040400147ae147ae1r ; 32.01 __real@3ff8a3d70a3d70a4 DQ 03ff8a3d70a3d70a4r ; 1.54 main PROC sub rsp, 40 ; 00000028H movsdx xmm1, QWORD PTR __real@3ff8a3d70a3d70a4 movsdx xmm0, QWORD PTR __real@40400147ae147ae1 call pow lea rcx, OFFSET FLAT:$SG1354 movaps xmm1, xmm0 movd rdx, xmm1 call printf xor eax, eax add rsp, 40 ; 00000028H ret 0 main ENDP ``` 在Intel和AMD的手冊中(見14章和1章)并沒有MOVSDX這個指令,而只有MOVSD一個。所以在x86中有兩個指令共享了同一個名字(另一個見B.6.2)。顯然,微軟的開發者想要避免弄得一團糟,所以他們把它重命名為MOVSDX,它只是會多把一個值載入XMM寄存器的低一半中。 pow()函數從XMM0和XMM1中加載參數,然后返回結果到XMM0中。 然后把值移動到RDX中,因為接下來printf()需要調用這個函數。為什么?老實說我也不知道,也許是因為printf()是一個參數不定的函數? 清單24.4:GCC 4.4.6 x64 -O3 ``` #!bash .LC2: .string "32.01 ^ 1.54 = %lf\n" main: sub rsp, 8 movsd xmm1, QWORD PTR .LC0[rip] movsd xmm0, QWORD PTR .LC1[rip] call pow ; result is now in XMM0 mov edi, OFFSET FLAT:.LC2 mov eax, 1 ; number of vector registers passed call printf xor eax, eax add rsp, 8 ret .LC0: .long 171798692 .long 1073259479 .LC1: .long 2920577761 .long 1077936455 ``` GCC讓結果更清晰,printf()的值傳入到了XMM0中。順帶一提,這是一個因為printf()才把1寫入EAX中的例子。這意味著參數會被傳遞到向量寄存器中,就像標準需求一樣(見21章)。 ## 24.3 比較式的例子 ``` #!cpp double d_max (double a, double b) { if (a>b) return a; return b; }; ``` 清單 24.5: MSVC 2012 x64 /Ox ``` #!bash a$ = 8 b$ = 16 d_max PROC comisd xmm0, xmm1 ja SHORT $LN2@d_max movaps xmm0, xmm1 $LN2@d_max: fatret 0 d_max ENDP ``` 優化過的MSVC產生了很容易理解的代碼。 COMISD是“Compare Scalar Ordered Double-Precision Floating-Point Values and Set EFLAGS”(比較標量雙精度浮點數的值然后設置EFLAG)的縮寫,顯然,看著名字就知道他要干啥了。 非優化的MSVC代碼產生了更加豐富的代碼,但是仍然不難理解: 清單 24.6: MSVC 2012 x64 ``` #!bash a$ = 8 b$ = 16 d_max PROC comisd xmm0, xmm1 ja SHORT $LN2@d_max movaps xmm0, xmm1 $LN2@d_max: fatret 0 d_max ENDP ``` 但是,GCC 4.4.6生成了更多的優化代碼,并且使用了MAXSD(“Return Maximum Scalar Double-Precision Floating-Point Value”,返回最大的雙精度浮點數的值)指令,它將選中其中一個最大數。 清單24.7: GCC 4.4.6 x64 -O3 ``` #!bash a$ = 8 b$ = 16 d_max PROC movsdx QWORD PTR [rsp+16], xmm1 movsdx QWORD PTR [rsp+8], xmm0 movsdx xmm0, QWORD PTR a$[rsp] comisd xmm0, QWORD PTR b$[rsp] jbe SHORT $LN1@d_max movsdx xmm0, QWORD PTR a$[rsp] jmp SHORT $LN2@d_max $LN1@d_max: movsdx xmm0, QWORD PTR b$[rsp] $LN2@d_max: fatret 0 d_max ENDP ``` ## 24.4 總結 只有低一半的XMM寄存器會被使用,一組IEEE754格式的數字也會被存在這里。 顯然,所有的指令都有SD后綴(標量雙精度數),這些操作數是可以用于IEEE754浮點數的,他們存在XMM寄存器的低64位中。 比FPU更簡單的是,顯然SIMD擴展并不像FPU以前那么混亂,棧寄存器模型也沒使用。 如果你像試著將例子中的double替換成float的話,它們還是會使用同樣的指令,但是后綴是SS(標量單精度數),例如MOVSS,COMISS,ADDSS等等。 標量(Scalar)代表著SIMD寄存器會包含僅僅一個值,而不是所有的。可以在所有類型的值中生效的指令都被“封裝”成同一個名字。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看