我选择signed int类型的percpu变量作为Linux内核中的半连接数量的统计值,我没有选择atomic类型的全局变量,我更是没有采用spinlock去保护一个一般的unsigned int类型的全局变量。
之所以选择signed int类型的percpu变量是因为要考虑到连接的处理在不同CPU之间的迁移。
到此为止,percpu变量的一切都很完美,轻量且无锁的操作是它作为统计计数器的最大优势。作为统计计数器,我们在读数的时候是允许一定的误差的,所以我们一般采用无锁的方式去读数:
for_each_possible_cpu(i) {
res += per_cpu_ptr(ptr, i);
}
我们知道,以上语句的执行是需要时间的,无论遍历所有CPU有多快,但也是需要时间的,在这段时间内,percpu变量就可能发生遍历累加逻辑在执行期间无法捕捉的变化。
幸亏这些统计值是给人来读的,而人读这些统计值的目的是通过数值去导出一些规律,通常对数据分辨率的要求不会达到毫秒以下。
然而,如果内核需要这些值怎么办?
比如内核依照这些变量的值来执行是否释放一个数据对象的逻辑:
for_each_possible_cpu(i) {
res += per_cpu_ptr(ptr, i);
}
if (res == 0)
free_something(...);
以上的代码显然是危险⚠️的,那么怎么办?
很简单,一把读写锁即可。但是和通常的读写锁的用法正好相反:
- 更新percpu变量时拿read锁。
- 读取percpu变量时拿write锁。
更新逻辑如下:
signed int *per_cpu_counter = per_cpu_ptr(..., this_cpu);
...
read_lock(&percpu_wrlock);
*per_cpu_counter ++;
read_unlock(&percpu_wrlock);
读取逻辑如下:
write_lock(&percpu_wrlock);
for_each_possible_cpu(i) {
res += per_cpu_ptr(ptr, i);
}
if (res == 0)
free_something(...);
write_unlock(&percpu_wrlock);
虽然说,read/write lock期间会禁用抢占,并且会插入屏障,但具体到特定的体系结构以及具体的Linux内核平台,这些看似会 影响性能 的操作大多数都是可以忽略的,比方说,大部分的服务器内核都是关闭内核抢占选项来编译的。
如果从总体上看而不是抠细节的话,遍历所有CPU并取出percpu变量累加的过程是非常快的,至少这是一个
O
(
1
)
O(1)
O(1)操作,更何况,绝大多数机器的CPU基本也就是3位数量级以下,所以这个
O
(
1
)
O(1)
O(1)的常数值本身也是非常小的。
经理不待见trick,经理也不待见遍历,哪怕for一个20也不行。
2007年夏天,经理说第一次慢,经理不管DNS,所以一个工人就把北京一个机房的服务器IP写死在代码里了。后来这个服务器搬迁了,这个工人也离职了,事情到了我这里,我查到了这个硬编码,但是我很认同这种做法,虽然我更倾向于用配置文件而不是代码的宏定义…
我在配置文件里写死了20个常用的IP地址,前10个是服务器的IP,后10个是我首选的10个DNS,然后就上线了,效果真不错!
但是经理要看代码,经理看见了一个for,for了20个IP,经理说这个循环是耗时的,经理非要让我改,我百口难辩,于是,我改成了下面的样子:
if (是IP1)
...
else if (是IP2)
...
else if (是IP3)
...
else if (是IP4)
...
else if (是IP5)
...
else if (是IP6)
...
...
else if (是IP20)
...
else
...
OK,消除了循环,增加了代码量,大家唱着歌,下班了。
浙江温州皮鞋湿,下雨进水不会胖。
原文链接: https://blog.csdn.net/dog250/article/details/105087167
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;
也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/406063
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!