日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 人文社科 > 生活经验 >内容正文

生活经验

nginx源码分析--内存对齐处理

發(fā)布時(shí)間:2023/11/27 生活经验 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 nginx源码分析--内存对齐处理 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1.nginx內(nèi)存對(duì)齊主要是做2件事情:

1) 內(nèi)存池的內(nèi)存地址對(duì)齊;

2) 長(zhǎng)度按照2的冪取整.因?yàn)榍懊娼Y(jié)構(gòu)體已經(jīng)是對(duì)齊了,如果后面的內(nèi)存池每一小塊不是2的冪,那么后面的就不能對(duì)齊


2.通用內(nèi)存對(duì)齊理論

內(nèi)存對(duì)齊數(shù)據(jù)項(xiàng)只能存儲(chǔ)在地址是數(shù)據(jù)項(xiàng)大小的整數(shù)倍的內(nèi)存位置上

例如int類型占用4個(gè)字節(jié),地址只能在0,4,8等位置上。


數(shù)據(jù)的對(duì)齊(alignment)

指數(shù)據(jù)的地址和由硬件條件決定的內(nèi)存塊大小之間的關(guān)系。一個(gè)變量的地址是它大小的倍數(shù)的時(shí)候,這就叫做自然對(duì)齊(naturally aligned)。例如,對(duì)于一個(gè)32bit的變量,如果它的地址是4的倍數(shù),-- 就是說(shuō),如果地址的低兩位是0,那么這就是自然對(duì)齊了。所以,如果一個(gè)類型的大小是2n個(gè)字節(jié),那么它的地址中,至少低n位是0。對(duì)齊的規(guī)則是由硬件引起的。一些體系的計(jì)算機(jī)在數(shù)據(jù)對(duì)齊這方面有著很嚴(yán)格的要求。在一些系統(tǒng)上,一個(gè)不對(duì)齊的數(shù)據(jù)的載入可能會(huì)引起進(jìn)程的陷入。在另外一些系統(tǒng),對(duì)不對(duì)齊的數(shù)據(jù)的訪問(wèn)是安全的,但卻會(huì)引起性能的下降。在編寫可移植的代碼的時(shí)候,對(duì)齊的問(wèn)題是必須避免的,所有的類型都該自然對(duì)齊。


預(yù)對(duì)齊內(nèi)存的分配

在大多數(shù)情況下,編譯器和C庫(kù)透明地幫你處理對(duì)齊問(wèn)題。POSIX 標(biāo)明了通過(guò)malloc( ), calloc( ), 和realloc( ) 返回的地址對(duì)于任何的C類型來(lái)說(shuō)都是對(duì)齊的。在Linux中,這些函數(shù)返回的地址在32位系統(tǒng)是以8字節(jié)為邊界對(duì)齊,在64位系統(tǒng)是以16字節(jié)為邊界對(duì)齊的。有時(shí)候,對(duì)于更大的邊界,例如頁(yè)面,程序員需要?jiǎng)討B(tài)的對(duì)齊。雖然動(dòng)機(jī)是多種多樣的,但最常見(jiàn)的是直接塊I/O的緩存的對(duì)齊或者其它的軟件對(duì)硬件的交互,因此,POSIX 1003.1d提供一個(gè)叫做posix_memalign( )的函數(shù)


2.1對(duì)較小結(jié)構(gòu)體進(jìn)行機(jī)器字對(duì)齊--避免多次讀

對(duì)于現(xiàn)代計(jì)算機(jī)硬件來(lái)說(shuō),內(nèi)存只能通過(guò)特定的對(duì)齊地址(比如按照機(jī)器字)進(jìn)行訪問(wèn)。舉個(gè)例子來(lái)說(shuō),比如在64位的機(jī)器上,不管我們是要讀取第0個(gè)字節(jié)還是要讀取第1個(gè)字節(jié),在硬件上傳輸?shù)男盘?hào)都是一樣的。因?yàn)樗紩?huì)把地址0到地址7,這8個(gè)字節(jié)全部讀到CPU,只是當(dāng)我們是需要讀取第0個(gè)字節(jié)時(shí),丟掉后面7個(gè)字節(jié),當(dāng)我們是需要讀取第1個(gè)字節(jié),丟掉第1個(gè)和后面6個(gè)字節(jié)。
當(dāng)我們要讀取的字節(jié)剛好落在兩個(gè)機(jī)器字內(nèi)時(shí),就出現(xiàn)兩次訪問(wèn)內(nèi)存的情況,同時(shí)通過(guò)一些邏輯計(jì)算才能得到最終的結(jié)果。

因此,為了更好的提升性能,我們須盡量將結(jié)構(gòu)體做到機(jī)器字(或倍數(shù))對(duì)齊,而結(jié)構(gòu)體中一些頻繁訪問(wèn)的字段也盡量安排在機(jī)器字對(duì)齊的位置。


操作系統(tǒng)的默認(rèn)對(duì)齊系數(shù)
每個(gè)操作系統(tǒng)都有自己的默認(rèn)內(nèi)存對(duì)齊系數(shù),如果是新版本的操作系統(tǒng),默認(rèn)對(duì)齊系數(shù)一般都是8,因?yàn)椴僮飨到y(tǒng)定義的最大類型存儲(chǔ)單元就是8個(gè)字節(jié),例如 long long,不存在超過(guò)8個(gè)字節(jié)的類型(例如int是4,char是1,long在32位編譯時(shí)是4,64位編譯時(shí)是8)。當(dāng)操作系統(tǒng)的默認(rèn)對(duì)齊系數(shù)與第一節(jié)所講的內(nèi)存對(duì)齊的理論產(chǎn)生沖突時(shí),以操作系統(tǒng)的對(duì)齊系數(shù)為基準(zhǔn)。


編譯器是按照什么樣的原則進(jìn)行對(duì)齊的。首先有3個(gè)重要的概念:自身對(duì)齊值,指定對(duì)齊值和有效對(duì)齊值。
?
自身對(duì)齊值:即數(shù)據(jù)類型的自身的對(duì)齊值。例如char型的數(shù)據(jù),其自身對(duì)齊值為1字節(jié);short型的數(shù)據(jù),其自身對(duì)齊值為2字節(jié);int,float,long類型,其自身對(duì)齊值為4字節(jié);double類型,其自身對(duì)齊值為4字節(jié);而struct和class類型的數(shù)據(jù)其自身對(duì)齊值為其成員變量中自身對(duì)齊值最大的那個(gè)值。
?
指定對(duì)齊值:#pragma pack (value)時(shí)指定的對(duì)齊值value
?
有效對(duì)齊值:上述兩個(gè)對(duì)齊值中最小的那個(gè)。
?
我們一般說(shuō)的對(duì)齊在N上,都是指有效對(duì)齊在N上。


2.2對(duì)較大結(jié)構(gòu)體進(jìn)行CACHE LINE對(duì)齊--避免占用過(guò)多CACHE LINE
我們知道,CACHE與內(nèi)存交換的最小單位為CACHE LINE,一個(gè)CACHE LINE大小以64字節(jié)為例。當(dāng)我們的結(jié)構(gòu)體大小沒(méi)有與64字節(jié)對(duì)齊時(shí),一個(gè)結(jié)構(gòu)體可能就要占用比原本需要更多的CACHE LINE。比如,把一個(gè)內(nèi)存中沒(méi)有64字節(jié)長(zhǎng)的結(jié)構(gòu)體緩存到CACHE時(shí),即使該結(jié)構(gòu)體本身長(zhǎng)度或許沒(méi)有還沒(méi)有64字節(jié),但由于其前后搭占在兩條CACHE LINE上,那么對(duì)其進(jìn)行淘汰時(shí)就會(huì)淘汰出去兩條CACHE LINE。
這還不是最嚴(yán)重的問(wèn)題,非CACHE LINE對(duì)齊結(jié)構(gòu)體在SMP機(jī)器上容易引發(fā)名為錯(cuò)誤共享的CACHE問(wèn)題。

#include <stdio.h>struct xx{char b;long long a;int c;char d;
};int main()
{struct xx bb;printf("&a = %p\n", &bb.a);printf("&b = %p\n", &bb.b);printf("&c = %p\n", &bb.c);printf("&d = %p\n", &bb.d);printf("sizeof(xx) = %d\n", sizeof(struct xx));return 0;
}


例如假設(shè)沒(méi)有內(nèi)存對(duì)齊,結(jié)構(gòu)體xx的變量位置會(huì)出現(xiàn)如下情況:

struct xx{
??????? char b;???????? //0xffbff5e8
??????? int a;??????????? //0xffbff5e9????? ?
??????? int c;???????????? //0xffbff5ed???? ?
??????? char d;???????? //0xffbff5f1
};
操作系統(tǒng)先讀取0xffbff5e8-0xffbff5ef的內(nèi)存,然后在讀取0xffbff5f0-0xffbff5f8的內(nèi)存,為了獲得值c,就需要將兩組內(nèi)存合并,進(jìn)行整合,這樣嚴(yán)重降低了內(nèi)存的訪問(wèn)效率。



VC和GCC默認(rèn)的都是4字節(jié)對(duì)齊,編程中可以使用#pragma pack(n)指定對(duì)齊模數(shù)。出現(xiàn)以上差異的原因在于,VC和GCC中對(duì)于double類型的對(duì)齊方式不同。
??? Win32平臺(tái)下的微軟VC編譯器在默認(rèn)情況下采用如下的對(duì)齊規(guī)則: 任何基本數(shù)據(jù)類型T的對(duì)齊模數(shù)就是T的大小,即sizeof(T)。比如對(duì)于double類型(8字節(jié)),就要求該類型數(shù)據(jù)的地址總是8的倍數(shù),而char類型數(shù)據(jù)(1字節(jié))則可以從任何一個(gè)地址開(kāi)始。
??? Linux下的GCC奉行的是另外一套規(guī)則:任何2字節(jié)大小(包括單字節(jié)嗎?)的數(shù)據(jù)類型(比如short)的對(duì)齊模數(shù)是2,而其它所有超過(guò)2字節(jié)的數(shù)據(jù)類型(比如long,double)都以4為對(duì)齊模數(shù)。

??? 復(fù)雜類型(如結(jié)構(gòu))的默認(rèn)對(duì)齊方式是它最長(zhǎng)的成員的對(duì)齊方式,這樣在成員是復(fù)雜類型時(shí),可以最小化長(zhǎng)度。
struct{char a;double b;}
??????? 在VC中,因?yàn)榻Y(jié)構(gòu)中存在double和char,按照最長(zhǎng)數(shù)據(jù)類型對(duì)齊,char只占1B,但是加上后面的double所占空間超過(guò)8B,所以char獨(dú)占8B;而double占8B,一共16Byte。
??? 在GCC中,double長(zhǎng)度超過(guò)4字節(jié),按照4字節(jié)對(duì)齊,原理同上,不過(guò)char占4字節(jié),double占兩個(gè)4字節(jié),一共12Byte。


3.Nginx中的內(nèi)存處理

ngx_alloc.c部分代碼

ngx_uint_t  ngx_cacheline_size;
#if (NGX_HAVE_POSIX_MEMALIGN)void *
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
{void  *p;int    err;err = posix_memalign(&p, alignment, size);if (err) {ngx_log_error(NGX_LOG_EMERG, log, err,"posix_memalign(%uz, %uz) failed", alignment, size);p = NULL;}ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,"posix_memalign: %p:%uz @%uz", p, size, alignment);return p;
}#elif (NGX_HAVE_MEMALIGN)void *
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
{void  *p;p = memalign(alignment, size);if (p == NULL) {ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,"memalign(%uz, %uz) failed", alignment, size);}ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,"memalign: %p:%uz @%uz", p, size, alignment);return p;
}#endif

ngx_cpuinfo.c

得到ngx_cacheline_size = 64;


ngx_config.h

#ifndef NGX_ALIGNMENT
#define NGX_ALIGNMENT   sizeof(unsigned long)    /* platform word */
#endif#define ngx_align(d, a)     (((d) + (a - 1)) & ~(a - 1))
#define ngx_align_ptr(p, a)                                                   \(u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

sizeof(unsigned long)=4

先分析? (((d) + (a - 1)) & ~(a - 1))表達(dá)式

兩個(gè)參數(shù)d和a,d代表數(shù)據(jù),a代表對(duì)其單位。假設(shè)a是8,那么用二進(jìn)制表示就是1000,a-1就是0111. d + a-1之后在第四位可能進(jìn)位一次(如果d的前三位不為000,則會(huì)進(jìn)位。反之,則不會(huì)),~(a-1)是1111...1000,&之后的結(jié)過(guò)就自然在4位上對(duì)其了。注意二進(jìn)制中第四位的單位是8,也就是以8為單位對(duì)其。


理解表達(dá)式? ~(a - 1)注意,其中a為2的冪,設(shè)右數(shù)第n位為非零位,則a-1為右數(shù)的n-1位均為1, 則有~(a-1)為最后的n-1位全為0;

顯然可得一個(gè)數(shù)和2的冪進(jìn)行? 減一取反再按位與操作,即為該數(shù)減去(其與a求余的值)

這種計(jì)算地址或者長(zhǎng)度對(duì)齊,取整的宏還是很有用的。cpu訪問(wèn)對(duì)齊的數(shù)據(jù)較快,不對(duì)齊的的int之類的,有可能區(qū)要多次內(nèi)存訪問(wèn)才能取到值。

?

向上取整倍數(shù),ngx_align內(nèi)存對(duì)齊的宏,對(duì)于a,傳入CPU的二級(jí)cache的line大小,通過(guò)ngx_cpuinf函數(shù),可以獲得ngx_cacheline_size的大小,一般intel為64或128

計(jì)算宏ngx_align(1, 64)=64,只要輸入d<64,則結(jié)果總是64,如果輸入d=65,則結(jié)果為128,以此類推。

進(jìn)行內(nèi)存池管理的時(shí)候,對(duì)于小于64字節(jié)的內(nèi)存,給分配64字節(jié),使之總是cpu二級(jí)緩存讀寫行的大小倍數(shù),從而有利cpu二級(jí)緩存取速度和效率。

ngx_cpuinfo函數(shù)實(shí)際是調(diào)用了匯編代碼,獲取cpu二級(jí)緩存大小!

?

//==================================================

上面式子,當(dāng)a等于2的冪的時(shí)候,比如,4,8,16等值時(shí),

d加上(a-1) 之后的值肯定要比最小的a的倍數(shù)要大的。因?yàn)閍為2的冪,所以(a - 1) 剛好后面幾位都是連續(xù)的1,取反之后再相與一下之后,就把小于a的余數(shù)部分丟掉了。 不過(guò)如果a不是2的冪,比如a=3, d=1 ,那么推算一下,上面的計(jì)算就不成立了。


?? 這種計(jì)算地址或者長(zhǎng)度對(duì)齊,取整的宏還是很有用的。cpu訪問(wèn)對(duì)齊的數(shù)據(jù)跟快把,不對(duì)齊的的int之類的,有可能區(qū)要多次內(nèi)存訪問(wèn)才能取到值出來(lái)。 寫個(gè)簡(jiǎn)單的字符串內(nèi)存池,AllocateString時(shí)打算把所有的字符串放到一個(gè)連續(xù)的內(nèi)存塊上,覺(jué)得還是把長(zhǎng)度取整一下比較好。這樣后續(xù)的對(duì)字符串的memcpy就是內(nèi)存字對(duì)齊,更好吧。

?

//=================================================


ngx_palloc.c部分代碼

ngx_align_ptr的調(diào)用

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{u_char      *m;ngx_pool_t  *p;if (size <= pool->max) {p = pool->current;do {m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);if ((size_t) (p->d.end - m) >= size) {p->d.last = m + size;return m;}p = p->d.next;} while (p);return ngx_palloc_block(pool, size);}return ngx_palloc_large(pool, size);
}

ngx_memalign的調(diào)用

static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{u_char      *m;size_t       psize;ngx_pool_t  *p, *new, *current;psize = (size_t) (pool->d.end - (u_char *) pool);m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);if (m == NULL) {return NULL;}new = (ngx_pool_t *) m;new->d.end = m + psize;new->d.next = NULL;new->d.failed = 0;m += sizeof(ngx_pool_data_t);m = ngx_align_ptr(m, NGX_ALIGNMENT);new->d.last = m + size;current = pool->current;for (p = current; p->d.next; p = p->d.next) {if (p->d.failed++ > 4) {current = p->d.next;}}p->d.next = new;pool->current = current ? current : new;return m;
}

ngx_http.c

ngx_align的調(diào)用

hash.bucket_size = ngx_align(64, ngx_cacheline_size);

4.Nginx內(nèi)存對(duì)齊使用舉例

64=2的6次方


5.編譯器對(duì)齊值

Visualstudio2012



參考《linux系統(tǒng)編程》好累啊,我翻譯的<Linux system programming> 第八章 二 ;《Linux System Programming》中文版

Cache line?
從CPU角度看內(nèi)存訪問(wèn)對(duì)齊

如何高效的訪問(wèn)內(nèi)存

總結(jié)

以上是生活随笔為你收集整理的nginx源码分析--内存对齐处理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。