You are on page 1of 9

Memory pool

Memory pool là cơ chế quản lý, cấp phát và thu hồi vùng nhớ nằm trong PJLIB, việc sử
dụng cơ chế này cho phép vùng nhớ của ứng dụng giảm thiểu việc phân mảnh vùng nhớ, tiết kiệm
thời gian cấp phát và tránh được 1 số rủi ro khi sử dụng malloc/free của C không cẩn thận. Để thực
hiện những việc đó memory pool sử dụng 1 factory và 1 hoặc nhiều policy khác nhau, trong đó
factory sẽ chịu trách nhiệm về việc tạo lập, giải phóng pool, cấp phát và quản lý bộ nhớ của pool.
Còn policy sẽ lo việc cấp phát bộ nhớ hệ thống cho pool.

1. Thành phần chính:


 Caching pool:

Figure 1- caching pool data structure

Figure 2 - caching pool memory layout

o Factory và policy: factory và policy là các struct chứa các con trỏ hàm đến các hàm
thao tác trực tiếp lên các thành phần trong pool.
 Factory:
 create_pool: tạo pool.
 release_pool: xóa pool.
 dump_status: dump trạng thái của pool.
 on_block_alloc: hàm callback khi tạo 1 block bộ nhớ mới, có thể có
hoặc không.
 on_block_free: hàm callback khi giải phóng 1 block bộ nhớ, có thể có
hoặc không.
 Policy:
 block_allock: Được gọi bởi memory pool để cấp phát memory block.
 block_free: giải phóng memory block.
 pool_callback: callback mặc định được gọi khi việc cấp phát bộ nhớ
thất bại.
o Free list: là 1 mảng các danh sách liên kết. Trong PJLIB thì mảng free list có 16 phần
từ. 16 phần tử tương ứng với 16 kích thước mà pool có th ể được tạo ra. Nếu ứng
dụng yêu cầu tạo ra pool vượt quá những kích thước này thì pool đó sẽ nằm trong
danh sách liên kết cuối cùng (thứ 15). free list dùng cho việc tái sử d ụng l ại b ộ nh ớ
đã tạo nên lưu theo kích thước như vậy sẽ giúp ta chọn đúng kích thước thích hợp
mà ta muốn tái sử dụng.
o Used list: Cũng giống như free list, nhưng used list sẽ lưu tất cả các pool đang được
sử dụng, tức là sao khi gọi create_pool và trước khi gọi release_pool, vào 1 danh
sách liên kết. Bên cạnh đó pool sẽ lưu lại index của từng pool. Việc lưu index này
phục vụ cho việc sau khi sử dụng thì pool sẽ được lưu trở lại vào free list và s ẽ
không bị free khỏi bộ nhớ của ứng dụng cho tới khi release caching pool.
o Other components: Ngoài 3 thành phần chính trên caching_pool còn lưu mốt số dữ
liệu khác:
 capacity: lưu tổng kích thước bộ nhớ của các pool trong free list, tức là kích
thước bộ nhớ mà ứng dụng có thể tái sử dụng lại.
 max_capacity: kích thước bộ nhớ tối đa mà free list có thể chứa, nếu capacity
đạt đến giới hạn của max_capacity thì ta không thể add thêm pool vào free
list được nữa
 used_count: số lương pool đang được sử dụng trong ứng dụng (số lượng phần
tử trong danh sách used_list).
 used_size: Tổng kích thước bộ nhớ của các pool mà ứng dụng đang sử dụng.
 peak_used_size: tổng kích thước bộ nhớ mà ứng dụng đã sử dụng của
caching pool trong suốt thời gian sống của nó.

 Pool:

Figure 3 - pool data structure

Figure 4 - pool memory layout

o Block list: đây là thành phần quan trọng nhất của pool. Pool sẽ chia bộ nh ớ đ ược
factory cấp phát ra thành các block, block list là 1 danh sách liên k ết. Sau đó ứng
dụng sẽ lấy các bộ nhớ trên các block ra sử dụng. Block quản lý bộ nhớ của mình
bằng 3 con trỏ buf, cur, end.
 con trỏ buf trỏ đến vị trí bắt đầu vùng nhớ của block.
 con trỏ cur trỏ đến vị trí hiện tại mà block đang dùng đến.
 con trỏ end trỏ đến vị trí kết thức của vùng nhớ.
Và chỉ có block đầu tiên của block list là không bị giải phóng khi gọi hàm release
pool.

o Other conponents: Các thành phần còn lại của pool bao gồm:
 obj_name: để lưu tên của pool.
 factory: lưu factory từ caching pool lại.
 factory_data: như đã nói bên phần caching pool thì factory_data để lưu lại
index của pool để lưu vào free_list.
 Capacity: khối lượng bộ nhớ còn sử dụng được trong pool.
 increment_size: kích thước của khối bộ nhớ sẽ được cấp phát thêm khi pool
đã hết bộ nhớ.

2. Cách sử dụng và cơ chế hoạt động:


 Cách hoạt động của pool:

o Ứng dụng sẽ tạo caching pool khi bắt đầu chạy và init các hàm cho factory và policy
(chúng ta có thể thay đổi các hàm mặc định của policy bằng hàm của mình). Sau đó
lưu vào biến pjsua_var.

o Lúc này khi cần sử dụng 1 vùng nhớ, chúng ta sẽ gọi pjsip_endpt_create_pool
và truyền vào kích thước bộ nhớ cần tạo, kích thước bộ nhớ có thể tăng thêm khi
thiếu để tạo ra 1 memory pool. Hàm này sẽ kiểm tra xem kích thước được truyền
vào và chọn ra giá trị trong 16 kích thước PJLIB qui định s ẵn thích h ợp v ới giá tr ị ta
truyền vào.

static pj_size_t pool_sizes[PJ_CACHING_POOL_ARRAY_SIZE] =


{
256, 512, 1024, 2048, 4096, 8192, 12288, 16384,
20480, 24576, 28672, 32768, 40960, 49152, 57344, 65536
};

o Kích thước thích hợp là kích thước nhỏ nhất nhưng lớn hơn kích thước ta truyền vào.
Ví dụ: truyền vào 500 thì hàm sẽ chọn ra 512. Như bảng mô tả trường dữ liệu của
caching_pool, hàm create sẽ tìm trong free list những pool có kích thước 512 xem có
trong free list hay không, nếu có thì chỉ cần đổi tên pool lại và đổi biến increment
của pool đấy. Sau đó chuyển pool này từ free list sang used list và lấy ra sử dụng.

o Nếu trong free list không có thì chúng ta sẽ tạo 1 buffer bộ nhớ mới và c ấp phát b ộ
nhớ cho pool. Việc tạo buffer này sẽ do hàm của policy đảm nhiệm, nên sẽ được đề
cập ở phần sau. Vùng nhớ vừa được tao sẽ bị lấy 1 phần để lưu các dữ liệu của struct
pool, kế đến là lưu dữ liệu của struct block. Phần còn lại sẽ là vùng nhớ mà ứng dụng
có thể sử dụng từ pool. Tương tự như trên thì pool này cũng sẽ được đưa vào used
list và lấy ra sử dụng.
Ví dụ: ta tạo pool 500 bytes thì 100bytes đầu ta sẽ lưu thông tin của struct pool, sau
đó lấy 100bytes tiếp theo để lưu cho thông tin của block và insert block đó vào block
list của pool. 300bytes còn lại sẽ để dùng cho ứng dụng. Các con trỏ của block g ồm
buf trỏ vào đầu của vùng nhớ còn lại đó, cur trỏ vào vị trí còn chưa xài và end trỏ
vào vị trí kết thúc sẽ giúp việc quản lý pool memory dễ dàng hơn.
Figure 5 - memory layout of a memory pool after being created

o Sau khi có được 1 pool với kích thước ta mong muốn. Ta cần sử dụng bộ nhớ trong
pool đó thì tạ gọi PJ_POOL_ZALLOC_T để cấp phát bộ nhớ ta cần dùng. Đầu tiên pool
sẽ kiểm tra xem khối lượng bộ nhớ trong block hiện tại còn đủ để cấp phát không.
Nếu trong trường hợp đủ thì ta sẽ lấy vùng nhớ đó ra sử dụng bình thường và d ịch
con trỏ cur của block lên, việc này giúp bộ nhớ sẽ không bị phân mảnh.
Ví dụ: Theo ví dụ trên thì ta sử dụng 200 bytes cho pool và block nên ta sẽ còn 300
bytes. Lúc này muốn dùng 150bytes cho ứng dụng thì pool sẽ lấy 150bytes tiếp theo
trong memory buffer ra sử dùng và dịch con trỏ cur thêm 150 bytes.

Figure 6 - memory layout of a memory pool after being used

o Nếu vùng nhớ không đủ thì pool sẽ dựa vào biến increment_size để xử lý, có 3
trường hợp sẽ xảy ra:
 Nếu biến bằng 0 thì sẽ không cấp phát thêm vùng nhớ cho pool và lấy vùng
nhớ ra không thành công.
 Nếu >0 và nhỏ hơn kích thước vùng nhớ ta cần xài thì ứng dụng sẽ cấp phát
thêm tích các increment_size sao cho vừa đủ kích thước ta cần.
 Nếu >0 và > kích thước ta cần cấp phát thì ứng dụng ch ỉ c ần c ấp phát thêm
vùng nhớ với kích thước là increment_size.
Vùng nhớ được cấp phát thêm là 1 block và được đưa vào block_list.
Ví dụ: Bộ nhớ còn lại chỉ còn 150 bytes, trong khi đó ta cần xài đến 400 bytes (tính
luôn cho việc chứa thông tin của block). Vì không còn đủ bộ nhớ trong buffer ban
đầu tạo, nên ta sẽ cấp phát 1 vùng nhớ mới và tạo 1 block để quản lý vùng nhớ đó.
Vùng nhớ mới có kích thước bao nhiêu thì tùy vào biến increment_size lúc tạo pool
ta đưa vào, nếu increment_size là 150 bytes thì ta sẽ cấp phát thêm 3 * 150 là 450
bytes nữa, còn nếu increment_size là 400 thì ta sẽ tạo 1* 400 là 400 bytes nữa. Sau
khi có block mới ta insert block đó vào trong block list của pool là có thể sử dụng.

Figure 7 - memory layout of a memory pool after insert new block

o Nhiều vùng nhớ sử dụng chung 1 pool sẽ đảm bảo tính liên tục và không b ị phân
mảnh.

o Sau khi xài xong pool ta sẽ gọi hàm release pool để chuyển pool từ used list sang
free list tương thích với kích thước pool. Trong hàm này các block trong pool sẽ
được giải phóng vùng nhớ, và việc giải phóng này sẽ được hàm của policy thực hiện,
nhưng chỉ duy nhất block đầu tiên là không được giải phóng, vì block này ch ứa
thông tin của pool nên sẽ được giữ lại (vì pool vẫn còn được giữ lại ở free list).
Figure 8 - memory layout of a memory pool after call release pool

o Cuối cùng là xóa caching pool thì toàn bộ vùng nhớ sẽ được giải phóng hết.

 Policy và cơ chế cấp phát, thu hồi vùng nhớ: Ở phần trên thì cơ chế tạo pool và giải phóng
pool sẽ được factory đảm nhiệm, nhưng những phần liên quan đến cấp phát vùng nh ớ h ệ
thống và giải phóng vùng nhớ hệ thống sẽ do các hàm trong policy quản lí.
Ngoài ra factory có thể được cung cấp các policy khác nhau trong suốt th ời gian s ống c ủa
caching pool để có thể có các cách cấp phát và giải phóng vùng nhớ khác nhau. Dưới đây là
cách policy mặc định của PJLIB hoạt động.

o Hàm cấp phát (default_block_alloc): Hàm thực hiện công việc khá đơn giản là gọi
callback on_block_alloc của factory và cấp phát vùng nhớ bằng hàm malloc của C.
Sau đó trả về buffer vừa được cấp phát đó.

o Hàm giải phóng (default_block_free): Hàm giải phóng sẽ gọi callback


on_block_free của factory và gọi hàm free của C để giải phóng vùng nhớ truyền vào.

3. Ưu điểm:
PJLIB's pool có rất nhiều ưu điểm vượt trội so với toán t ủ malloc/new truy ền th ống ho ặc các cài
đặt memory pool khác, bởi vì:

 không giống như các cài đặt memory pool khác, nó cho phép cấp phát nhiều khối bộ nhớ với
kích thước khác nhau.

 PJLIB's pool rất nhanh. Việc cấp phát các khối bộ nhớ không chỉ chạy với độ phức tạp O(1)
mà nó còn rất đơn giản (sử dụng rất ít con trỏ hàm) và không cần yêu c ầu khóa b ất kì mutex
nào.

 Không xảy ra memory leaks, vì pool đã có cơ chế quản lí bộ nhớ riêng, nên ta không cần
phải thu hồi bộ nhớ được cấp phát bởi memory pool, memory pool sẽ tự xóa hết các vùng
nhớ khi nó bị hủy.
Ví dụ: khi ta cấp phát 1 vùng nhớ bằng malloc, nhưng vô tình thay đổi con tr ỏ đang tr ỏ đ ến
đấy làm vùng nhớ đó không thể sử dụng được và cũng không giải phóng được dần dần x ảy
ra hiện tượng memory leak. Nhưng khi cấp phát bộ nhớ từ pool, ta cứ thoải mái sử dụng vì
các vùng nhớ luôn được quản lý bởi pool và các block, các vùng nhớ sẽ đều đ ược h ủy khi
gọi release pool vì vậy sẽ không có việc có vùng nhớ không ai tr ỏ đ ến mà cũng không ai có
thể hủy trong pool.

 PJLIB's memory pool có tốc độ nhanh gấp 30 lần nếu so sánh với malloc()/free() chuẩn.

4. Nhược điểm:
 Vì khi khởi tạo pool ta phải truyền vào kích thước ta mu ốn t ạo và h ệ th ống s ẽ c ấp phát vùng
nhớ bằng kích thước đó, nên việc này có thể gây ra việc thiếu vùng nhớ hoặc phí vùng nhớ
của hệ thống. Nhưng trường hợp thiếu vùng nhớ thì ta có thể mở rộng ra thêm vì PJLIB có
hổ trợ điều đó bằng biếng increment_size. Nhưng sẽ không thể thu hẹp vùng nhớ lại, vì vậy
khi ta chỉ xài 50bytes mà cấp phát lên đến 512 bytes thì sẽ rất tốn kém bộ nhớ của hệ thống.

 Ngoài ra việc đảm bảo cho vùng nhớ trong pool được liên tục, không bị phân mảnh và
không phải tốn nhiều thời gian cho việc tìm kiếm vị trí vùng nhớ khi c ấp phát thì memory
pool của PJLIB phải chấp nhận việc duy trì vùng nhớ không còn xài đ ến n ữa trong 1 pool
cho đến khi gọi release pool.
Ví dụ: khi cấp phát 10 bytes từ pool ta sẽ lấy 10bytes đầu tiên trong block ra dùng, sau đó
cấp phát thêm 20bytes nữa, lúc này ta không xài 10bytes trước nữa thì pool v ẫn duy trì s ố
lượng bytes đã xài là 30bytes, và 10bytes trước vẫn để đó mặc dù không còn dùng nữa cho
đến khi release pool.

5. Cách khắc phục việc phí vùng nhớ:

 Vì memory pool của PJLIB không hổ trợ việc thu hẹp vùng nhớ đã cấp phát, mà ch ỉ h ổ tr ợ
việc mở rộng. Nên khi cấp phát ta nên cấp phát không quá lớn - khoảng 512 byte (nhưng
cũng không quá nhỏ, vì nếu cấp phát nhỏ vùng nhớ sẽ bị chia thành nhiều block độc lập, nên
sẽ không giữ được việc vùng nhớ liên tục đc nữa), và cấp thêm increment_size để hổ trợ cho
pool có thể mở rộng khi thiếu vùng nhớ. Và những vùng nhớ mở rộng sẽ bị xóa sau khi gọi
release_pool vì vậy việc phí vùng nhớ sẽ xãy ra ít hơn.

 Bên cạnh đó việc sử dụng pool cũng có thể khắc phục điều này. Khi sử dụng xong h ết t ất c ả
các vùng nhớ được cấp phát từ pool thì ta nên gọi release pool đ ể sử d ụng vùng nh ớ l ại t ừ
đầu. Nếu không gọi release thì vùng nhớ dùng xong đó vẫn ở đấy và pool sẽ cấp phát ph ần
tiếp theo cho lời gọi yêu cầu cấp phát vùng nhớ tiếp theo. Vì vậy ta sẽ phí vùng nhớ đầu do
không sử dụng. Vì vậy ta nên gọi release khi dùng xong và g ọi create khi s ử d ụng l ại, b ản
chất của việc release và create này chỉ là dịch chuyển con trỏ của block đầu tiên trong pool
về vị trí bắt đầu của vùng nhớ, và lấy pool đã bị release trước đó s ử d ụng l ại nên s ẻ không
tốn nhiều chi phí.

6. Danh sách các pool trong csipsimple:


Các pool sử dụng nhiều bộ nhớ của hệ thống như:
 pept (pool endpoint): sử dụng nhiều bộ nhớ nhất, khoảng 80096 bytes trên bảng pc và
276096 bytes trên bảng android.
o Bộ nhớ này dùng để cấp phát cho:
 timer_heap
 ioqueue
 transport_manager cho sip
 resolver
o Trong đó ioqueue chiếm gần như toàn bộ vùng nhớ của pool pept (hơn 90%). Vì khi
bắt đầu ứng dụng ioqueue phải được tạo sẵn 1 free_list các ioqueue_key (xem lại bên
phần ioqueue) để sau này sử dụng. Và do free_list có rất nhiều phần từ nên chi ếm r ất
nhiều vùng nhớ của pool pept.
o Ở bảng PC, free_list chứa 64 phần từ, trong khi đó bên bảng android chứa 1024 phần
tử. Đó cũng là lí do vì sao có sự chêch lệch bộ nhớ của 2 pool trên 2 b ảng PC và
Android.
 inv: với việc sử dụng 30464 bytes bộ nhớ lúc ứng dụng nhận cuộc gọi (chỉ sử dụng khoảng
1000->3000 khi ứng dụng thực hiện cuộc gọi) thì inv pool là pool sử dụng vùng nhớ nhiều
thứ 2 của ứng dụng. pool được tạo ra khi bắt đầu thực hiện cuộc gọi ho ặc khi v ừa nh ận cu ộc
gọi. Và bị hủy khi kết thúc cuộc gọi. Vùng nhớ của inv sử dụng chủ yếu cho việc tạo các
SDP session trong lúc bắt đầu cuộc gọi:
o Tạo sdp session answer: ứng dụng gọi hàm pjsip_inv_set_local_sdp để set giá trị cho
sdp session answer. Trong quá trình tạo ra các thành phần của sdp session này thì
ứng dụng sử dụng khoảng 6000 bytes của inv pool.
o Tạo SDP negotiator: việc này được thực hiện trong hàm
pjmedia_sdp_neg_create_w_remote_offer, và SDP negotiator này chiếm khoảng
10000->15000 bytes của inv pool.
 pjsua: là pool sử dụng vùng nhớ nhiều thứ 3 sau pool pept và inv, chêch lệch bộ nhớ giữa 2
pool bên PC và Android không đáng kể, 20024 ở bảng PC và 25024 ở bảng Android. Vùng
nhớ này chủ yếu được sử dụng để lưu các giá trị của biến pjsua_var và được duy trì trong
suốt thời gian chạy ứng dụng.
 sndstream/openslstrm: là 2 pool sử dụng cho việc thu và phát âm thanh từ device,
sndstream là của android_jni_dev còn openslstrm là của opensl_dev.
o Openslstrm: opensl sẽ tạo 1 pool có tên openslstrm và cấp phát 1 bộ nhớ là 1024
bytes cho pool đó. sau đó sẽ dùng bộ nhớ này cấp phát cho 2 playerBuffer, 2
playerBuffer này sẽ luân phiên nhận âm thanh được thu âm vào và truyền lên trên đ ể
đóng gói thành rtp packet và gởi đi qua socket. Sau khi kết thúc cuộc gọi thì pool sẽ
bị release nên 2 playerBuffer cũng sẽ được giải phóng, vì vậy ta không cần lo vi ệc
giải phóng vùng nhớ của rtp packet sau khi gởi thành công.
o Sndstream: android_jni_dev cũng tạo 1 pool có bộ nhớ 1024 bytes, nhưng không
dùng để lưu dữ liệu thu âm được, mà chỉ dùng để sử dụng cấp phát vùng nhớ cho 2
thread thu âm và phát âm. tương tự như openslstrm, pool sẽ được release khi kết thúc
cuộc gọi. Và vì không sử dụng pool cho rtp packet nên android_jni_dev sẽ giải
phóng vùng nhớ ngay sau khi gởi gói rtp packet đi.
 Jitter buffer/ delay buffer:
o Jitter buffer: Sử dụng strm (pool của stream) pool để cấp phát vùng nhớ cho
framelist của mình. với framelist 25 phần tử thì jitter buffer s ử d ụng kho ảng 4000
bytes của strm pool. Pool được tạo khi bắt đầu cuộc gọi và bị hủy khi kết thúc cuộc
gọi.
o Delay buffer: Sử dụng pjsua pool để cấp phát vùng nhớ cho circle buffer dùng cho
việc lưu các buffer thu âm được từ thiết bị. delay buffer sử dụng 5120 bytes của
pjsua pool để cấp phát cho circle buffer. pjsua pool sẽ được duy trì cho suốt ứng
dụng như đã đề cập ở trên.
 1 số port của reference:
o master_port: reference sử dụng pool pjsua để cấp phát vùng nhớ cho master port.
master port chiếm 112 bytes trong pjsua pool. master_port sẽ được giữ đến cuối ứng
dụng như những thành phần sửa dụng pjsua pool khác.
o sound_device_port: sẽ sử dụng pjsua_snd pool để cấp phát vùng nhớ, pjsua_snd
được tạo khi vừa nhận được cuộc gọi hoặc vừa gọi đến máy khác với kích th ước là
4000 bytes và được gọi release ngay khi kết thúc cuộc gọi. sound_port sử dụng 273
bytes của pjsua_snd.
o Còn lại 1 số port còn lại được gán vào mảng ports của reference s ử dụng b ộ nh ớ
không đáng kể chỉ từ vài bytes đến vài chục bytes.
 Một số pool còn lại xài bộ nhớ khá ít chủ yếu là xài cho 1 số struct dữ liệu nào đó. Th ường
những pool này được tạo ra không chênh lệch nhiều với kích thước của struct, và ít được mở
rộng. Khi tạo ra pool, thì struct sẽ sử dụng vùng nhớ này và lưu con tr ỏ tr ỏ đ ến pool đó l ại
trong struct. Khi sử dụng song struct này ta sẽ gọi hàm pool_release đ ể gi ải phóng pool đó
và đưa về free_list trong caching pool để tái sử dụng.
o tsxlayer
o ua
o med-ept
o codec-mgr
o speex
o gsm
o g711
o g722
o webrtc codecs
o evt mgr
o evsub
o css
o android
o udp
o glck
o rtd
o acc
o tcplis
o check
o ec
o tmpcall
o dlg
o srtp
o tdta
o tsx
o rtd

You might also like