創作內容

7 GP

艾莉兒的特殊配備:改造calling convention

作者:Shark│2017-04-26 18:21:57│巴幣:112│人氣:861


還記得創作者之夏裡的這一段嗎?我在艾莉兒身上用了很多提升速度的技巧,所以她的執行速度相當快,底層效能夠好寫上層就不太需要煩惱效能。
我製作艾莉兒是用重視速度和火力,犧牲易用性的設計,以RPG來比喻的話,她的速度快但耐久力低。

本篇介紹其中一招:修改calling convention,寫好定義之後讓編譯器自己處理就行了,不用自己寫演算法,是比較容易做的優化。
先只介紹PC用的x86架構,手機常用的ARM架構還沒實際寫過。

碰過組合語言才比較能體會calling convention是什麼,這裡簡單介紹一下。字面意思是「呼叫協議」,像C/C++之類編譯成機器碼的語言,編譯之後就沒有變數名稱和資料型態這些東西了,變成只是記憶體裡的一堆byte,要有個規則規定各個參數放在記憶體裡的哪個位置或暫存器,否則如果呼叫函式時把參數放在esp+4的位置,但函式裡以為參數在esp+8就會出錯了。

CPU內部的暫存器存取比CPU外面的主記憶體快,因此一個提升速度的技巧是儘量把參數放在暫存器,這樣儲存、計算都可以在CPU裡完成,不用跑到外面的記憶體。
下面提到的SSE全名Streaming SIMD Extensions,它的向量運算功能也可以用來加速,但想用也要懂一點組合語言,以後有機會再介紹。



-x86 32位元-

這裡「32位元」指的是程式的位元數,而不是作業系統和CPU的位元數,如果在64位元OS執行32位元程式,那還是照32位元的規則。
32位元有兩個標準calling convention:cdecl和stdcall,都是把參數放在記憶體,最常用的兩個compiler:GCC和VC有提供方法改成用暫存器傳參數。
但這是非標準的編譯器特殊功能,所以最好只用在自己用的函式庫,或是exe(直接拿來執行,不會有其他程式call裡面的函式),如果要做成函式庫給別人用,那還是要照標準的方式。

GCC

有兩個function attribute可以用:regparm和sseregparm,最多可以用3個整數暫存器和3個SSE暫存器,剩下的參數才放在記憶體,其中整數和指標放在整數暫存器,浮點數和向量放在SSE。
用法是宣告函式時這樣寫
int __attribute((regparm(3),sseregparm)) rectHitRect(const float* obj, float* v, const float* target);
如果翻GCC的compiler option說明可以看到有兩個flag:-mregparm和-msseregparm,加上去則所有函式都會用上面的calling convention,可是在Linux用的話所有不是你寫的函式庫,包括系統函式都要用這個flag編譯才能用,所以還是得用一個一個函式指定的方式。
參考:GCC說明文件關於x86的部分,function attributecompiler option

VC

很久以來加速的方法只有fastcall,用兩個整數暫存器。VC 2013新增一個vectorcall,可以用2個整數和6個SSE暫存器放參數。(這是我在Cyber Sprite 2改用VC 2013的原因)
使用方法有二,一個是在函式宣告時加上__vectorcall
int __vectorcall rectHitRect(const float* obj, float* v, const float* target);
另一個是編譯時命令列加上/Gv,這樣只要沒指定calling convention的函式都會用vectorcall。跟Linux環境不同,Windows API裡所有函式宣告都有指定calling convention,沒有不相容的問題。
參考:VC 2013說明文件,vectorcallcompiler option



-x86 64位元-

64位元程式就沒有這種改造了,也比較不需要,因為標準就是用暫存器傳參數。x86_32發明的時候還沒有SSE,想用SSE得用後來新增非標準的方法,而x86_64出現的時候已經有SSE2了,設計時有把SSE考慮進去。

標準calling convention有這兩個。
sysv:用在Linux、Mac OSX等類UNIX系統。使用6個整數、8個SSE,前6個整數放在整數,前8個浮點數放在SSE,最多可以用14個暫存器。
Microsoft:用在Windows。4個整數或SSE,跟sysv不一樣的是,即使參數是整數、浮點數混合,也只有前4個放在暫存器,第5個以後的放記憶體。

Windows用VC的話,可以如32位元一樣加上__vectorcall,讓用的暫存器增加到4個整數、6個SSE。



關於calling convention可以配合使用兩個技巧。

第一個是static函式。global函式宣告成static代表「只能在這個編譯單位裡使用,不能讓其他編譯單位引用」,簡單說是只能在一個.c、.cpp檔裡使用。
(這和函式內的static變數及class的static member是不同的東西,C/C++把同一個static關鍵字用在不同的地方)
static int pointHitSegment(const float* obj, float* v,const float* self){
  ……
}

static int pointHitCircle(const float* obj, float* v,const float* self){
  ……
}
compiler確定函式不會在其他地方被用到,就可以做最佳化,例如用非標準calling convention,如果函式夠短也會把它內嵌省下呼叫的消耗。
所以我這裡函式能做成static的就寫成static,class也禁止使用private member function,幾乎都用.cpp裡的static函式代替(除非是寫在header裡inline的)。

第二個我也還沒有廣泛使用。SSE暫存器大小有128位元(16 byte),除了放單個浮點數以外也可以放向量,如果有個float[4]參數,以往通常的的做法是放在主記憶體裡再把它的指標傳入
float objRect[4];
float targetRect[4];
float v[2];
rectHitRect(objRect, v, targetRect);

用上面那些calling convention把SSE暫存器搬出來用的話,將參數宣告成__m128型態,可以把這4個float塞在一個SSE暫存器裡。
#include<xmmintrin.h>

int rectHitRect(__m128 obj, __m128 v, __m128 target);
  ……
__m128 objRect;
__m128 targetRect;
__m128 v;
rectHitRect(objRect, v, targetRect);
但是函式內外也要用SSE指令處理這個向量才能發揮威力,否則函式外多做了把純量包進SSE的工作,函式內再把向量拆成純量,反而比較慢。
還有C/C++ call by reference要用指標模擬,想在return值以外傳回其他東西還是要用指標。



綜合以上的方法,我給艾莉兒加了這一段,自動選擇該平台最快的calling convention
#ifdef _MSC_VER
  #if _MSC_VER>=1800 //VC 2013以後
    #define REGPARM __vectorcall
  #else
    #define REGPARM __fastcall
  #endif
#elif defined __GNUC__
  #ifdef __i386
    #define REGPARM __attribute((regparm(3),sseregparm))
  #elif defined __amd64
    #define REGPARM
  #endif
#endif

然後所有的函式宣告都加上REGPARM,不論獨立的函式或class member,用VC編譯時也加上/Gv。
int REGPARM rectHitRect(const float* obj, float* v, const float* target);

class TileMap{
  int REGPARM load(const char* chunk, int size, int layer);
  int REGPARM hit(int shape, const float* obj, float v[2]) const;
};



昨天一時興起做了另一個改造,把部分碰撞判定演算法改成用SSE來算,實測結果執行時間減少約25%。
因為即使寫了「v[0]=v[0]+obj[0]; v[1]=v[1]+obj[1]; v[2]=…………」,從產生的組合語言程式碼可以看出compiler也不會把它向量化,還是用純量指令一個一個算,compiler在這方面沒有很聰明,想用SSE的向量運算還是得人工寫指令。
雖然有多少實質效益很難說,但是向速度極限挑戰是很有趣的。

寫到這裡好奇一件事,現在一般人都是用64位元作業系統了,很多應用程式也有提供64位元的版本。
如果我這裡以後的作品都編譯成64位元,會不會有人的電腦不能執行?
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=3556690
All rights reserved. 版權所有,保留一切權利

相關創作

同標籤作品搜尋:程式|C|C++

留言共 1 篇留言

龍恩
我目前還是用32位元,但有打算改換64的

04-26 21:43

我要留言提醒:您尚未登入,請先登入再留言

7喜歡★shark0r 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:修一個Inkscape的... 後一篇:CH2電玩主題ONLY遊...

追蹤私訊切換新版閱覽

作品資料夾

yvonne40528歡迎來看小說ゝω・
🌠《星與銀河》校園輕奇幻喜劇|🌕《虛月舞曲》架空奇幻愛情|🦋《羽化之後》校園微戀愛成長看更多我要大聲說昨天14:20


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】