03Nginx源碼分析之分析記憶體對齊宏ngx_align,ngx_align_ptr
因為ngx_align與ngx_align_ptr實際上是一樣的,隻不過int與char*位址的差別。是以下面隻分析ngx_align_ptr即可。
1 ngx_align_ptr
// p 是記憶體指針,a 是對齊位元組數(必須是2的幂次方結果)
#define ngx_align_ptr(p, a) \
(u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
以下假設為64位機器:
先分析最右邊的~((uintptr_t) a - 1):
當a為2的0次方,即2^0 - 1 = 0時,運算取反後64位全為1;
當a為2的1次方,即2^1 - 1 = 1時,運算取反後64位的隻有低一位為0;
當a為2的2次方,即2^2 - 1 = 3時,運算取反後64位的隻有低兩位為0;
…
可以得出下面結果:
幂次方為幾低位就有幾個零。
對齊位元組數 | 換成2的幂次方 | 64位二進制 |
---|---|---|
1 | 2^0 | 1111111111111111111111111111111111111111111111111111111111111111 |
2 | 2^1 | 1111111111111111111111111111111111111111111111111111111111111110 |
4 | 2^2 | 1111111111111111111111111111111111111111111111111111111111111100 |
8 | 2^3 | 1111111111111111111111111111111111111111111111111111111111111000 |
16 | 2^4 | 1111111111111111111111111111111111111111111111111111111111110000 |
32 | 2^5 | 1111111111111111111111111111111111111111111111111111111111100000 |
64 | 2^6 | 1111111111111111111111111111111111111111111111111111111111000000 |
然後左邊的結果不需要分析,直接看例子。
2 例子
下面的代碼是從ngx的哈希表初始化函數中抽出的。并且本例子按照ngx_cacheline_size = 64位對齊測試。
#include <iostream>
#include <string>
#include <windows.h>
#include <tchar.h>
#include <deque>
#include <assert.h>
using namespace std;
#define ngx_align(d, a) (((d) + (a - 1)) & ~(a - 1))
#define ngx_cacheline_size 64 //本例子按64位對齊
int main(int argc, char* argv[])
{
int i = 0;
short int test[10];
int size = 10;
int len = 0;
//測試對齊位數,4位元組代表32位,8位元組代表64,不過一旦上面設定64,與機器是x86還是x64無關,最終開辟的記憶體仍然一樣
for (i = 0; i < size; i++) {
test[i] = sizeof(void*);
}
//列印
for (i = 0; i < size; i++) {
cout << test[i] << " ";
}
cout << endl;
//将數組test中的數值設為一百多,然後使用對齊宏進行對齊,驗證對齊後的數值
//數組中的數值代表我們要開辟的記憶體,這一步的意思是:當實際要開辟的記憶體大小對齊後,實際将占用多少記憶體(這個例子是參考ngx哈希表的初始化函數)
for (i = 0; i < size; i++) {
test[i] = i + 100;
}
for (i = 0; i < size; i++) {
if (test[i] == sizeof(void *)) {
continue;
}
//cout << &test[i] << endl;
test[i] = static_cast<short>(ngx_align(test[i], ngx_cacheline_size));
//cout << &test[i] << endl;
//len += test[i];
}
//列印對齊後的記憶體開辟大小
for (i = 0; i < size; i++) {
cout << test[i] << " ";
}
cout << endl;
cout << sizeof(ngx_hash_elt_t *) << endl;
system("pause");
return 0;
}
結果:

可以看到,将8+100後,按照64位對齊,每個數組元素的值變成128的大小,即實際占用128的記憶體。這就是記憶體對齊宏的作用。隻有了解了本例子,你才能去了解ngx的哈希表結構源碼。
3 總結
對比上一篇的ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)記憶體對齊函數,兩者的差別是(實際上一篇在開頭的注意講過,即下圖):
這裡再次總結一下兩者的一些使用場合和功能上的差別。
- 1)ngx_memalign記憶體對齊主要是針對開辟記憶體後傳回的記憶體首位址,若想實作記憶體對齊,下一次開辟記憶體必須傳相應的倍數(ngx_memalign的參1),該函數的作用直覺的可以看出,一般是為了開辟大記憶體時進行大記憶體的記憶體對齊,因為參1可以傳256,512這些大倍數。
- 2)而對齊宏ngx_align,是直接将已經開辟好的記憶體首位址傳進(參數d),按照一定倍數直接對齊(即參數a),對齊後占用的記憶體大小大于實際開辟的大小,例如上面的例子。一般該對齊倍數(參數a)一般是32位,64位,128位這些正常的對齊倍數。
- 3)再次說明記憶體對齊宏的參數。
/*
* d為指針的首位址,a為想要對齊的位元組數。
* 其中:一般想要對齊的位元組數會通過sizeof(void*)去自動比對電腦
* 的32位或者64位,例如參考nginx的哈希表的NGX_HASH_ELT_SIZE宏定義。
*
* 記憶體對齊後,CPU就能一大塊一大塊的讀取記憶體。
*/
#define ngx_align(d, a) (((d) + (a - 1)) & ~(a - 1))
至此,對于記憶體對齊宏的介紹到此完畢。