1. 程式人生 > >在C/C++程式碼中使用SSE等指令集的指令(3)SSE指令集基礎

在C/C++程式碼中使用SSE等指令集的指令(3)SSE指令集基礎

相關參考:

scalar packed

(1)Summary:

前面瞭解到了可以在程式碼中使用intrinsics函式來實現類似彙編的高階指令集(SSE等)指令,在這裡,為了加深理解,再次分析一下SSE指令。

(2)MMX指令集

首先要提到MMX指令集,MMX指令集是在SSE之前的,後來的SSE指令集覆蓋了MMX指令集的內容,現在的大多數CPU也都支援SSE指令集了,SSE指令集之後還有SSE2、SSE3、SSE4等,最新的Intel處理器支援AVX指令集。

(3)SIMD

single instruction multiple data,單指令流多資料流,也就是說一次運算指令可以執行多個數據流,這樣在很多時候可以提高程式的運算速度。

SIMD是CPU實現DLP(Data Level Parallelism)的關鍵,DLP就是按照SIMD模式完成計算的。

(4)SSE

SSE(為Streaming SIMD Extensions的縮寫)是由 Intel公司,在1999年推出Pentium III處理器時,同時推出的新指令集。如同其名稱所表示的,SSE是一種SIMD指令集。所謂的SIMD是指single instruction, multiple data,也就是一個指令同時對多個資料進行相同的動作。較早的MMX和 AMD的3DNow!也都是SIMD指令集。因此,SSE本質上是非常類似一個向量處理器的。SSE指令包括了四個主要的部份:單精確度浮點數運算指令、整數運算指令(此為MMX之延伸,並和MMX使用同樣的暫存器)、Cache控制指令、和狀態控制指令


(5)SSE新增的暫存器(用於浮點運算指令)

SSE新增了八 ​​個新的128位元暫存器,xmm0 ~ xmm7。這些128位元的暫存器,可以用來存放四個32位元的單精確度浮點數。SSE的浮點數運算指令就是使用這些暫存器。和之前的MMX或3DNow!不同,這些暫存器並不是原來己有的暫存器(MMX和3DNow!均是使用x87浮點數暫存器),所以不需要像MMX或3DNow!一樣,要使用x87指令之前,需要利用一個EMMS指令來清除暫存器的狀態。因此,不像MMX或3DNow!指令,SSE的浮點數運算指令,可以很自由地和x87指令,或是MMX指令共用。但是,這樣做的主要缺點是,因為多工作業系統在進行context switch時,需要儲存所有暫存器的內容。而這些多出來的新暫存器,也是需要儲存的。因此,既存的作業系統需要修改,在context switch時,儲存這八個新暫存器的內容,才能正確支援SSE浮點運算指令。

下圖是SSE新增暫存器的結構:


(6)SSE浮點運算指令分類

SSE的浮點運算指令分為兩大類:packed和scalar。(有些地方翻譯為“包裹指令和”“標量指令” :) )
Packed指令是一次對XMM暫存器中的四個浮點數(即DATA0 ~ DATA3)均進行計算,而scalar則只對XMM暫存器中的DATA0進行計算。如下圖所示:

下面是SSE指令的一般格式,由三部分組成,第一部分是表示指令的作用,比如加法add等,第二部分是s或者p分別表示scalar或packed,第三部分為s,表示單精度浮點數(single precision floating point data)。


(7)SSE新的資料型別:

根據上面知道,SSE新增的暫存器是128bit的,那麼SSE就需要使用128bit的資料型別,SSE使用4個浮點數(4*32bit)組合成一個新的資料型別,用於表示128bit型別,SSE指令的返回結果也是128bit的。

(8)SSE定址/定址方式:

SSE 指令和一般的x86 指令很類似,基本上包括兩種定址方式:暫存器-暫存器方式(reg-reg)和暫存器-記憶體方式(reg-mem):

addps xmm0, xmm1 ; reg-reg
addps xmm0, [ebx] ; reg-mem

(10)intrinsics的SSE指令

要使用SSE指令,可以使用intrinsics來簡化程式設計,前面已經介紹過intrinsics的基礎了,這裡也不會展開。

SSE指令的intrinsics函式名稱一般為:_m_operation[u/r...]_ss/ps,和上面的SSE指令的命名類似,只是增加了_m_字首,另外,表示指令作用的操作後面可能會有一個可選的修飾符,表示一些特殊的作用,比如從記憶體載入,可能是反過來的順序載入(不知道彙編指令有沒有對應的修飾符,理論上應該沒有,這個修飾符只是給編譯器用於進行一些轉換用的,具體待查)。

SSE指令中的intrinsics函式的資料型別為:__m128,正好對應 了上面提到的SSE新的資料型別,當然,這種資料型別只是一種抽象表示,實際是要轉換為基本的資料型別的。

(9)SSE指令的記憶體對齊要求

SSE中大部分指令要求地址16bytes對齊的,要理解這個問題,以_mm_load_ps函式來解釋,這個函式對應於loadps的SSE指令。

其原型為:extern __m128 _mm_load_ps(float const*_A);

可以看到,它的輸入是一個指向float的指標,返回的就是一個__m128型別的資料,從函式的角度理解,就是把一個float陣列的四個元素依次讀取,返回一個組合的__m128型別的SSE資料型別,從而可以使用這個返回的結果傳遞給其它的SSE指令進行運算,比如加法等;從彙編的角度理解,它對應的就是讀取記憶體中連續四個地址的float資料,將其放入SSE新的暫存器(xmm0~8)中,從而給其他的指令準備好資料進行計算。其使用示例如下:

float input[4] = { 1.0f, 2.0f, 3.0f, 4.0f };
__m128 a = _mm_load_ps(input);
這裡載入正確的前提是:input這個浮點數陣列都是對齊在16 bytes的邊上。否則載入的結果和預期的不一樣。如果沒有對齊,就需要使用_mm_loadu_ps函式,這個函式用於處理沒有對齊在16bytes上的資料,但是其速度會比較慢。關於記憶體對齊的問題,這裡就不詳細討論什麼是記憶體對齊了,以及如何指定記憶體對齊方式。這裡主要提一下,SSE的intrinsics函式中的擴充套件的方式:

對於上面的例子,如果要將input指定為16bytes對齊,可以採用的方式是:__declspec(align(16)) float input[4];

那麼,為了簡化,在xmmintrin.h中定義了一個巨集_MM_ALIGN16來表示上面的含義,即:_MM_ALIGN16 float input[4];

(11)大小端問題:

這個只是使用SSE指令的時候要注意一下,我們知道,x86的little-endian特性,位址較低的byte會放在暫存器的右邊。也就是說,若以上面的input為例,在載入到XMM暫存器後,暫存器中的DATA0會是1.0,而DATA1是2.0,DATA2是3.0,DATA3是4.0。如果需要以相反的順序載入的話,可以用_mm_loadr_ps 這個intrinsic,根據需要進行選擇。

(12)總結:瞭解SIMD、DLP、向量化、SSE基礎等。

相關推薦

.NET/C# 在程式碼測量程式碼執行耗時的建議(比較系統性能計數器和系統時間)

我們有很多種方法評估一個方法的執行耗時,比如使用效能分析工具,使用基準效能測試。不過傳統的在程式碼中編寫計時的方式依然有效,因為它可以生產環境或使用者端得到真實環境下的執行耗時。 如果你希望在 .NET/C# 程式碼中編寫計時,那麼閱讀本文可以獲得一些建議。閱讀本文也可以瞭解到 Qu

C/C++程式碼使用SSE指令指令(3)SSE指令基礎

相關參考: scalar packed (1)Summary: 前面瞭解到了可以在程式碼中使用intrinsics函式來實現類似彙編的高階指令集(SSE等)指令,在這裡,為了加深理解,再次分析一下SSE指令。 (2)MMX指令集 首先要提到MMX指令集,MMX指令集

C/C++程式碼使用SSE指令指令(1)介紹

我們知道,在C/C++程式碼中,可以插入彙編程式碼提高效能。現在的指令集有了很多的高階指令,如果我們希望使用這些高階指令來實現一些高效的演算法,就可以在程式碼中嵌入彙編,使用SSE等高階指令,這是可行的,但是如果對彙編不太熟悉,不願意使用匯編的人來說,其實也是可以的,這就是Compiler Intrinsi

c/c++ 程式碼使用sse指令加速

使用SSE指令,首先要了解這一類用於進行初始化載入資料以及將暫存器的資料儲存到記憶體相關的指令, 我們知道,大多數SSE指令是使用的xmm0到xmm8的暫存器,那麼使用之前,就需要將資料從記憶體載入到這些暫存器。 1. load系列,用於載入資料,從記憶體到暫存器

SSE指令 c,c++程式程式碼優化

基於SSE指令集的程式設計簡介 SSE技術簡介   Intel公司的單指令多資料流式擴充套件(SSE,Streaming SIMD Extensions)技術能夠有效增強CPU浮點運算的能力。

UE4 C++程式碼使用材質,字型資源

當我使用ue4 editor製作了一個材質(Material)或字型(Font)之後,該材質和字型等資源不僅可以在editor中使用,還可以在C++程式碼中使用。 下圖為我在editor中製作的資源: 我們可以通過右鍵點選該資源,點選“Copy Reference”來獲得資源的參考路徑。

如何在ubuntu寫一個簡單的C語言程式碼並編譯執行

首先需要安裝一個編譯器 因為筆者也是剛剛開始學習ubuntu所以不知道各個編譯器之間的區別,筆者所用的是gcc就簡單介紹一下gcc的安裝方法吧。 方法一: 開啟控制檯輸入以下程式碼: sudo apt-get build-dep gcc; sudo apt-get bui

C/C++的預編譯指令

地方 efi def 允許 兩種 常量 打印 string 運算符 程序的編譯過程可以分為預處理、編譯、匯編三部分,其中預處理是首先執行的過程,預處理過程掃描程序源代碼,對其進行初步的轉換,產生新的源代碼提供給編譯器。預處理過程讀入源代碼之後,會檢查代碼裏包含的預處理指令,

C++getline輸入輸出函式的用法

此文轉自 在學習C++的過程中,經常會遇到輸入輸出的問題,以下總結一下下面幾個函式的用法: 1)、cin 2)、cin.get() 3)、cin.getline

封裝C#程式碼為DLL並在C#程式碼引用

1.封裝C#程式碼為DLl 在VS2012中建立專案選擇類庫,命名testMyDll,新建類msg,注意修飾符必須為public using System; using System.Collections.Generic; using System.Linq; using System.T

C# 兩個datatable的資料快速比較返回交集或差

轉自: https://www.cnblogs.com/lacey/p/5893380.html 如果兩個datatable的欄位完全一致的話,可以直接使用Except,Intersect //Except()差集var tempExcept = dt1.AsEnumerable().Excep

JNI開發c程式碼列印日誌

JNI開發在c程式碼中列印日誌 Android.mk檔案增加以下內容 LOCAL_LDLIBS += -llog C程式碼中增加以下內容#include <android/log.h> #define LOG_TAG "System.out" #define LOG

C/C++#,##,__FILE__,__LINE__的用法

#include <stdio.h> #define CAT(N) X ## N //## 在巨集定義中將兩個字元連線起來,構成一個新的識別符號 #define PRINTF_XN(n) printf("X" # n "=%d\n",X ## n); //# 將

單鏈表——求兩個集合的差 A,B集合求差放到C連結串列

#include <stdio.h> #include <malloc.h> typedef struct Node { int data; struct Node *next; }LNode,*LinkList; void list

visual studio c++程式碼使用git版本資訊

總體思路 本篇使用的方法不更改visual studio的任何設定,完全使用的是“蠻力”。 使用python寫成的tool獲取版本資訊,並auto code為一個.h檔案,檔案中僅是一個git版本資訊類 在需要git版本資訊的程式碼中,使用1中自動生成的類

.NET/C# 你可以在程式碼寫多個 Main 函式,然後按需要隨時切換

.NET/C# 程式從 Main 函式開始執行,基本上各種書籍資料都是這麼寫的。不過,我們可以寫多個 Main 函式,然後在專案檔案中設定應該選擇哪一個 Main 函式。 你可能會覺得這樣沒有什麼用,不過如果你的應用程式在不同的編譯條件下有不同的啟動程式碼,或者

C/C++的預編譯指令(轉)

reference:https://blog.csdn.net/sunshinewave/article/details/51020421   程式的編譯過程可以分為預處理、編譯、彙編三部分,其中預處理是首先執行的過程,預處理過程掃描程式原始碼,對其進行初步的轉換,產生新的原始碼提供給編譯器。  預處理過程

使用C#對MongoDB的資料進行查詢,修改操作

首先,使用的是官方提供的C#訪問元件https://github.com/mongodb/mongo-csharp-driver 然後、編譯後引用MongoDB.Bson.dll及MongoDB.Driver.dll,並在cs檔案中宣告引用 using MongoDB.

Android開發在JAVA呼叫C/C++ native程式碼

Android 從Java呼叫C/C++ 當無法用 Java 語言編寫整個應用程式時,JNI 允許您呼叫C/C++本機程式碼。在下列典型情況下,您可能決定使用本機程式碼: 希望用更低階、更快的程式語言C/C++去實現對時間有嚴格要求的程式碼。

C/C++常見的#if、if、#ifdef、#if define區別

#if 與 if條件編譯是C語言中預處理部分的內容,它是編譯器編譯程式碼時最先處理的部分,條件編譯裡面有判斷語句,比如 #if 、#else 、#elif 及 #endif它的意思是如果巨集條件符合,編譯器就編譯這段程式碼,否則,編譯器就忽略這段程式碼而不編譯,如#defin