Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

bpf: avoid grabbing spin_locks of all cpus when no free elems

This patch use head->first in pcpu_freelist_head to check freelist
having free or not. If having, grab spin_lock, or check next cpu's
freelist.

Before patch: hash_map performance
./map_perf_test 1
0:hash_map_perf pre-alloc 1043397 events per sec
...
The average of the test results is around 1050000 events per sec.

hash_map the worst: no free
./run_bench_bpf_hashmap_full_update.sh
Setting up benchmark 'bpf-hashmap-ful-update'...
Benchmark 'bpf-hashmap-ful-update' started.
1:hash_map_full_perf 15687 events per sec
...
The average of the test results is around 16000 events per sec.

ftrace trace:
0) | htab_map_update_elem() {
0) | __pcpu_freelist_pop() {
0) | _raw_spin_lock()
0) | _raw_spin_unlock()
0) | ...
0) + 25.188 us | }
0) + 28.439 us | }

The test machine is 16C, trying to get spin_lock 17 times, in addition
to 16c, there is an extralist.

after patch: hash_map performance
./map_perf_test 1
0:hash_map_perf pre-alloc 1053298 events per sec
...
The average of the test results is around 1050000 events per sec.

hash_map worst: no free
./run_bench_bpf_hashmap_full_update.sh
Setting up benchmark 'bpf-hashmap-ful-update'...
Benchmark 'bpf-hashmap-ful-update' started.
1:hash_map_full_perf 555830 events per sec
...
The average of the test results is around 550000 events per sec.

ftrace trace:
0) | htab_map_update_elem() {
0) | alloc_htab_elem() {
0) 0.586 us | __pcpu_freelist_pop();
0) 0.945 us | }
0) 8.669 us | }

It can be seen that after adding this patch, the map performance is
almost not degraded, and when free=0, first check head->first instead of
directly acquiring spin_lock.

Co-developed-by: Chengming Zhou <zhouchengming@bytedance.com>
Signed-off-by: Chengming Zhou <zhouchengming@bytedance.com>
Signed-off-by: Feng Zhou <zhoufeng.zf@bytedance.com>
Link: https://lore.kernel.org/r/20220610023308.93798-2-zhoufeng.zf@bytedance.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

authored by

Feng Zhou and committed by
Alexei Starovoitov
54a9c3a4 fe928335

+14 -6
+14 -6
kernel/bpf/percpu_freelist.c
··· 31 31 struct pcpu_freelist_node *node) 32 32 { 33 33 node->next = head->first; 34 - head->first = node; 34 + WRITE_ONCE(head->first, node); 35 35 } 36 36 37 37 static inline void ___pcpu_freelist_push(struct pcpu_freelist_head *head, ··· 130 130 orig_cpu = cpu = raw_smp_processor_id(); 131 131 while (1) { 132 132 head = per_cpu_ptr(s->freelist, cpu); 133 + if (!READ_ONCE(head->first)) 134 + goto next_cpu; 133 135 raw_spin_lock(&head->lock); 134 136 node = head->first; 135 137 if (node) { 136 - head->first = node->next; 138 + WRITE_ONCE(head->first, node->next); 137 139 raw_spin_unlock(&head->lock); 138 140 return node; 139 141 } 140 142 raw_spin_unlock(&head->lock); 143 + next_cpu: 141 144 cpu = cpumask_next(cpu, cpu_possible_mask); 142 145 if (cpu >= nr_cpu_ids) 143 146 cpu = 0; ··· 149 146 } 150 147 151 148 /* per cpu lists are all empty, try extralist */ 149 + if (!READ_ONCE(s->extralist.first)) 150 + return NULL; 152 151 raw_spin_lock(&s->extralist.lock); 153 152 node = s->extralist.first; 154 153 if (node) 155 - s->extralist.first = node->next; 154 + WRITE_ONCE(s->extralist.first, node->next); 156 155 raw_spin_unlock(&s->extralist.lock); 157 156 return node; 158 157 } ··· 169 164 orig_cpu = cpu = raw_smp_processor_id(); 170 165 while (1) { 171 166 head = per_cpu_ptr(s->freelist, cpu); 167 + if (!READ_ONCE(head->first)) 168 + goto next_cpu; 172 169 if (raw_spin_trylock(&head->lock)) { 173 170 node = head->first; 174 171 if (node) { 175 - head->first = node->next; 172 + WRITE_ONCE(head->first, node->next); 176 173 raw_spin_unlock(&head->lock); 177 174 return node; 178 175 } 179 176 raw_spin_unlock(&head->lock); 180 177 } 178 + next_cpu: 181 179 cpu = cpumask_next(cpu, cpu_possible_mask); 182 180 if (cpu >= nr_cpu_ids) 183 181 cpu = 0; ··· 189 181 } 190 182 191 183 /* cannot pop from per cpu lists, try extralist */ 192 - if (!raw_spin_trylock(&s->extralist.lock)) 184 + if (!READ_ONCE(s->extralist.first) || !raw_spin_trylock(&s->extralist.lock)) 193 185 return NULL; 194 186 node = s->extralist.first; 195 187 if (node) 196 - s->extralist.first = node->next; 188 + WRITE_ONCE(s->extralist.first, node->next); 197 189 raw_spin_unlock(&s->extralist.lock); 198 190 return node; 199 191 }