高性能编程的困境

之前收到公司一个大牛的PPT,里边详细分析了一个典型的代码段在短短2~3秒钟时间内的内存访问特征。内容翔实紧凑,说的有理有据。技术类PPT的惯例,文中有几页折线图表达了整个过程中每一细微时间粒度上的内存带宽变化,扫了一眼细细密密的横坐标,一方面感慨大牛的数据之精确,另一方面忽然有了放challenge的冲动。

立即回邮件,请教如此细粒度的时间切片数据是如何抓取的。答,自己修改了PCM的源码,调小了采样间隔到50us(微秒)。再回邮件要求上代码,大牛附上了源码。我一下子推翻了整个PPT。

PCM是一个CPU/硬件级别事件监控软件。它通过硬件上集成的event counter(事件计数器)的读数变化统计需要关注的事件发生次数。过程有点像抄水表:本月用水量=这月抄表数-上月抄表数。对于PCM的实现,这个过程就变成了:读数1->sleep->读数2->计算两次读数的差->输出。

原本这两次读数之间的sleep都是秒级别,可为什么到了50us级别这个过程就会出现问题呢?这就要从当下的非实时操作系统实现来说。

从操作系统context switch上下文切换的过程来看。Linux是一个“时分多路”操作系统,意味着同一个CPU核心在不同的时间上运行的进程或线程是不同的。在多个进程或线程之间切换的过程就叫上下文切换,而sleep或者写屏、写磁盘调用将会触发上下文切换。
每一次都会都会触发吗?不一定, /proc/sys/kernel/sched_min_granularity_ns 文件约定了最小的调度间隔,这个间隔大多都在ms(毫秒)级别。且下一次切回到本进程至少也要再等一次最小调度时间间隔。相比50us的取样周期,3个数量级的差异直接导致了采样数据的不可信。

如果你有过编程基础,你可能会觉得解决这个问题的方法只要增加一个时间校准, 减少写屏、落盘就好了,然而问题又来了。

时间校准上如果采用大多数的实现,都是简单的调用glibc中 clock_gettime 。这条指令可以通过系统调用直接读取硬件时钟的数据,纳秒级别的精度按理说已经是足够高了。不过里边还有一个坑。
/sys/devices/system/clocksource/clocksource0/current_clocksource 是当前的系统默认时钟源的设定,有几种可用的时钟可用,比如我这台主机可以使用 TSC(Time Stamp Counter),HPET(High Precision Event Timer),ACPI PM Timer(ACPI Power Management Timer) 3个时钟源,默认使用的是TSC。要知道3种硬件时钟的调用方式是不同的,调用时延也是10倍以上的。TSC代价较低,简单的指令返回,100 cycle左右,大约60ns可以完成调用;而hpet则是通过内存映射方式的调用,至少2倍内存延时,超过500ns;ACPI?好吧,ACPI需要通过PCIe bus完成内存地址映射,这个过程相当于读取SSD,微秒级别的操作了!
坑的是在不同的主机版本、内核设置上,硬件时钟源的设定是不同的。据说,Kernel优先使用TSC,一旦kernel“觉得tsc有问题”,比如从刚从休眠唤醒或者调整频率之后,就会切换到HPET时钟。

那么另一条路呢?
对于输出数据以50us的间隔读写对内存,在这个例子中意味着超过2GB/s的带宽消耗,几乎是一根DDR4-2666带宽的10%。加上这个数据本身就是针对内存性能的分析,如此的误差导致的是数据不可用。如果你对于现有的多核x86架构有所了解的话,一个socket上的多个core是共享内存控制器的,相互的带宽抢占是不可避免的。

最后,你要是接触过event count特别是uncore部分的event counter,你就会知道真正的问题在这里:计算内存带宽需要UNC_M_CAS_COUNT.RD之类的内存读写计数器。这些计数器的访问事实上是通过一个虚拟的PCIe设备进行映射的。说到PCIe设备,之前已经提到了,这是一个需要用us级别访问的通道……

话说到这里,你也许会问我的最终实现方法,肯定不是通过uncore了。这里不做阐述,欢迎私聊!

其实挺想吐槽的,一个常年研究硬件架构的大牛尚且在关键的数据采集上犯了这么大的错误。这些年,所谓高性能高并发、微秒级响应、C25K甚至C100K 困境为啥在某些人嘴里仿佛就成了hello word级别难度?传统上对于一个最小调度时间片是3ms的话意味着单核心只能响应300连接每秒,就算对于100个CPU core的主机来说,光是建立连接这30K的TCP链接就是系统的极限了,更别谈什么数据读写处理。

从CPU的眼光讲讲时间的概念,给大家一个概念。

  • 对于2.0GHz的CPU来说,一个cycle大约是0.5ns。
  • 访问内存 >150ns,这是300个cycle。
  • SSD或者网卡的读取 >150us, 这是 300000个cycle。
  • 一次内网微服务请求 5ms,这是10000000个cycle。
  • 一次网页响应 1.5s 这是30000000000个cycle
  • ……
  • 光速在一个cycle之内只能传输15厘米。

推荐阅读:
时延 latency(亦称为延
事出前些日子有人咨询我:“在某
尽管当前已经是多核心SMP时代
有感于CPU的各种电源状态描述

发表评论

电子邮件地址不会被公开。 必填项已用*标注

请补全下列算式: *

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据