Perf / Ring Buffer對比
Perf Buffer常規用法:
struct addrinfo //需要上傳給應用層的數據結構
{
int ai_flags; /* Input flags. */
int ai_family; /* Protocol family for socket. */
int ai_socktype; /* Socket type. */
int ai_protocol; /* Protocol for socket. */
u32 ai_addrlen; /* Length of socket address. */ // CHANGED from socklen_t
struct sockaddr *ai_addr; /* Socket address for socket. */
char *ai_canonname; /* Canonical name for service location. */
struct addrinfo *ai_next; /* Pointer to next in list. */
};
struct //Perf Map聲明
{
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(int));
__uint(value_size, sizeof(int)); //這里不是 struct addrinfo大小,這里指的是key對應的fd的大小 *****
__uint(max_entries, 1024); //最大 fd 數量,這里可以不設置,在應用層設置,會覆蓋這里的值,盡量保證一個cpu對應一個buffer
// https://github.com/cilium/ebpf/pull/300
// https://github.com/cilium/ebpf/issues/209
// https://github.com/cilium/ebpf/blob/02ebf28c2b0cd7c2c6aaf56031bc54f4684c5850/map.go 的函數 clampPerfEventArraySize() 里面
} events SEC(".maps");
SEC("uretprobe/getaddrinfo")
int getaddrinfo_return(struct pt_regs *ctx) {
...
struct data_t data = {}; //創建棧上結構體,第一次內存拷貝
data.xxx = xxx; //獲取需要的數據
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data, sizeof(data)); //將棧上結構體復制到Perf Map中,第二次內存拷貝
...
return 0;
}
總結: 在棧中申請的結構體,此時ebpf verify驗證器會限制結構體不能超過512字節,影響功能開發。
發生了2次內存拷貝,消耗性能。
在對結構體成員賦值完成后,調用bpf_perf_event_output時,如果Perf Map已經滿了。則會發生上傳數據失敗。
Perf Buffer高階用法:
struct addrinfo //需要上傳給應用層的數據結構
{
int ai_flags; /* Input flags. */
int ai_family; /* Protocol family for socket. */
int ai_socktype; /* Socket type. */
int ai_protocol; /* Protocol for socket. */
u32 ai_addrlen; /* Length of socket address. */ // CHANGED from socklen_t
struct sockaddr *ai_addr; /* Socket address for socket. */
char *ai_canonname; /* Canonical name for service location. */
struct addrinfo *ai_next; /* Pointer to next in list. */
};
struct { //Perf Map聲明
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(int));
__uint(value_size, sizeof(int));
__uint(max_entries, 1024);
} events SEC(".maps");
struct { //高階用法,改為Map堆中創建數據結構
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 1);
__type(key, int);
__type(value, struct event);
} heap SEC(".maps");
SEC("uretprobe/getaddrinfo")
int getaddrinfo_return(struct pt_regs *ctx) {
...
struct data_t *data; //差異點,不創建棧上數據結構
int zero = 0;
data = bpf_map_lookup_elem(&heap, &zero); //改為創建在Map堆中
data.xxx = xxx; //獲取需要的數據
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, data, sizeof(*data)); //上傳數據
...
return 0;
}
總結:內存申請發生在Map提供的堆中,規避棧上申請512字節的限制
還是存在調用bpf_perf_event_output時,如果Perf Map已經滿了。則會發生上傳數據失敗。
Ring Buffer用法
struct addrinfo //需要上傳給應用層的數據結構
{
int ai_flags; /* Input flags. */
int ai_family; /* Protocol family for socket. */
int ai_socktype; /* Socket type. */
int ai_protocol; /* Protocol for socket. */
u32 ai_addrlen; /* Length of socket address. */ // CHANGED from socklen_t
struct sockaddr *ai_addr; /* Socket address for socket. */
char *ai_canonname; /* Canonical name for service location. */
struct addrinfo *ai_next; /* Pointer to next in list. */
};
struct { //Ring buffer聲明,注意此時max_entries代表的是buffer的大小,和Perf buffer中該字段的含義有所不同
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024 /* 256 KB */);
} events SEC(".maps");
SEC("uretprobe/getaddrinfo")
int getaddrinfo_return(struct pt_regs *ctx) {
...
struct data_t *data; //差異點,不創建棧上數據結構
data = bpf_ringbuf_reserve(&events, sizeof(*data), 0); //直接在ring buffer中申請空間
if (!data)
return 0;
data.xxx = xxx; //獲取需要的數據
bpf_ringbuf_submit(data, 0); //上傳數據
return 0;
}
總結:函數一開始直接在ring buffer中申請空間,申請失敗的話直接就返回了,不會執行后續操作,節省時間,一旦申請成功,即可保證bpf_ringbuf_submit一定不會因為沒有空間失敗,且省去Perf buffer中拷貝結構體的操作。
差異性
總結:
共同點:
-
Perf/Ring Buffer相對于其他種類map(被動輪詢)來說,提供專用api,通知應用層事件就緒,減少cpu消耗,提高性能。
-
采用共享內存,節省復制數據開銷。
-
Perf/Ring Buffer支持傳入可變長結構。
差異:
-
Perf Buffer每個CPU核心一個緩存區,不保證數據順序(fork exec exit),會對我們應用層消費數據造成影響。Ring Buffer多CPU共用一個緩存區且內部實現了自旋鎖,保證數據順序。
-
Perf Buffer有著兩次數據拷貝動作,當空間不足時,效率低下。 Ring Buffer采用先申請內存,再操作形式,提高效率。
-
Ring Buffer性能強于Perf Buffer。參考patch 【ringbuf perfbuf 性能對比】
本文由博客一文多發平臺 OpenWrite 發布!

浙公網安備 33010602011771號