性能实战(一) --- clock_gettime造成系统整体cpu过高定位过程

本文介绍了如何分析Linux服务器CPU使用率过高问题,通过perf火焰图发现大量clock_gettime调用。进一步研究发现,异常环境下clock_gettime频繁进行系统调用而非使用vdso。分析根因与时钟源选择,指出TSC不稳定可能导致此类问题。解决方案是在内核启动参数中设置`tsc=reliable`。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题背景

有一台linux服务器测试环境cpu经常到达80%,造成系统卡顿,部分功能不可用.

分析步骤

1.使用perf制作cpu火焰图

通过制作cpu火焰图,发现很多进程都存在大量的clock_gettime系统调用.

在这里插入图片描述

2. 使用bcc工具funclatency`进一步查看clock_gettime的调用次数
# /usr/share/bcc/tools/funclatency '*clock_gettime*' -d 30
Tracing 6 functions for "*clock_gettime*"... Hit Ctrl-C to end.

     nsecs               : count     distribution
         0 -> 1          : 0        |                                        |
         2 -> 3          : 0        |                                        |
         4 -> 7          : 0        |                                        |
         8 -> 15         : 0        |                                        |
        16 -> 31         : 0        |                                        |
        32 -> 63         : 0        |                                        |
        64 -> 127        : 0        |                                        |
       128 -> 255        : 0        |                                        |
       256 -> 511        : 0        |                                        |
       512 -> 1023       : 0        |                                        |
      1024 -> 2047       : 413453   |****                                    |
      2048 -> 4095       : 1815900  |********************                    |
      4096 -> 8191       : 3576475  |****************************************|
      8192 -> 16383      : 2350047  |**************************              |
     16384 -> 32767      : 405074   |****                                    |
     32768 -> 65535      : 28429    |                                        |
     65536 -> 131071     : 9908     |                                        |
    131072 -> 262143     : 631      |                                        |
    262144 -> 524287     : 248      |                                        |
    524288 -> 1048575    : 58       |                                        |
   1048576 -> 2097151    : 128      |                                        |
   2097152 -> 4194303    : 1707     |                                        |
   4194304 -> 8388607    : 2        |                                        |

avg = 7966 nsecs, total: 68529765602 nsecs, count: 8602313

怎么样?是不是很夸张,30s内调用了近千万次,耗时也很可观.

3.对比另外一个正常环境的情况
# /usr/share/bcc/tools/funccount "*clock_gettime*" -d 10
Tracing 6 functions for "*clock_gettime*"... Hit Ctrl-C to end.

FUNC                                    COUNT
__x64_sys_clock_gettime                   334
Detaching...

这根本不是一个量级好吧?为什么异常的服务器会多出如此多的系统调用呢?

4.进一下分析clock_gettime的实现原理
4.1 系统调用

系统调用需要从用户态切换到内核态,因此相对开销较大,为了优化频繁调用的函数的开销,linux使用了vdso机制.

4.2 哪些函数可以使用vdso
x86-64 functions
   The table below lists the symbols exported by the vDSO.  All of
   these symbols are also available without the "__vdso_" prefix,
   but you should ignore those and stick to the names below.
    symbol                 version
     ─────────────────────────────────
    __vdso_clock_gettime   LINUX_2.6
    __vdso_getcpu          LINUX_2.6
    __vdso_gettimeofday    LINUX_2.6
    __vdso_time            LINUX_2.6
4.3 clock_gettime为什么是系统调用

既然clock_gettime可以通过vdso,为什么异常系统上还会有这么多系统调用呢?

4.4 根因

时间相关的几个函数与系统时钟源密切相关,linux启动时会根据精度和开销选取最佳时钟源.

下文是关于时钟源的说明:

内核在启动过程中会根据既定的优先级选择时钟源。优先级的排序根据时钟的精度与访问速度。
其中CPU中的TSC寄存器是精度最高(与CPU最高主频等同),访问速度最快(只需一条指令,一个时钟周期)的时钟源,因此内核优选TSC作为计时的时钟源。其它的时钟源,如HPET, ACPI-PM,PIT等则作为备选。
但是,TSC不同与HPET等时钟,它的频率不是预知的。因此,内核必须在初始化过程中,利用HPET,PIT等始终来校准TSC的频率。如果两次校准结果偏差较大,则认为TSC是不稳定的,则使用其它时钟源。并打印内核日志:Clocksource tsc unstable.

正常来说,TSC的频率很稳定且不受CPU调频的影响(如果CPU支持constant-tsc)。内核不应该侦测到它是unstable的。但是,计算机系统中存在一种名为SMI(System Management Interrupt)的中断,该中断不可被操作系统感知和屏蔽。如果内核校准TSC频率的计算过程quick_ pit_ calibrate ()被SMI中断干扰,就会导致计算结果偏差较大(超过1%),结果是tsc基准频率不准确。最后导致机器上的时间戳信息都不准确,可能偏慢或者偏快。

当内核认为TSC unstable时,切换到HPET等时钟,不会给你的系统带来过大的影响。当然,时钟精度或访问时钟的速度会受到影响。通过实验测试,访问HPET的时间开销为访问TSC时间开销的7倍左右。如果您的系统无法忍受这些,可以尝试以下解决方法: 在内核启动时,加入启动参数:tsc=reliable
tsc是最优时钟源,当使用它时,时间相关函数可以通过vdso实现,而当退化而使用其它时钟源时,就需要走真正的系统调用了.那么,我们系统上是这种情况吗?

dmesg查看到如下输出:

dmesg | grep clock

[ 3007.005852] clocksource: timekeeping watchdog on CPU0: Marking clocksource 'tsc' as unstable because the skew is too large:
[ 3007.005853] clocksource:  'acpi_pm' wd_now: 13f3cd wd_last: f657df mask: ffffff
[ 3007.005854] clocksource: 'tsc' cs_now: 96685727fda cs_last: 95aef19df08 mask: ffffffffffffffff
[ 3007.005854] tsc: Marking TSC unstable due to clocksource watchdog
[ 3007.006767] sched_clock: Marking unstable (3006972692137, 33974437)<-(3007014491200, -7725623)
[ 3007.007138] clocksource: Switched to clocksource acpi_pm
tsc: Marking TSC unstable due to clocksource watchdog ------------  `可以看到确实如此,tsc已经被抛弃了`

因此,有问题的系统上由于tsc失效,时间相关函数由vdso退化为系统调用,造成系统负载过高.

解决方法

修改/etc/default/grub配置tsc:

GRUB_CMDLINE_LINUX="... clocksource=tsc tsc=reliable"
<think>嗯,用户现在想准备C++相关的频交易系统面试问题和解决方案。我需要先理解频交易系统的特点,然后结合C++在这方面的应用,找出常见的面试问题,并给出解决方案。首先,频交易系统性能要求极,延迟要尽可能低,所以面试可能会涉及如何优化C++代码、处理并发、内存管理等问题。 根据用户提供的引用,比如引用[4]提到传统C++11架构在并发下的问题,比如锁竞争和内存碎片。这可能涉及到锁的选择、无锁数据结构、内存池等优化方法。另外,引用[3]中的虚函数、继承、多态相关的问题,虽然在频交易中可能较少使用继承,但面试官可能会问及如何避免虚函数带来的开销,或者如何设计效的类结构。 接下来,我需要整理常见的问题点。例如,低延迟优化可能包括缓存友好设计、避免动态内存分配、使用原子操作等。并发处理方面,可能涉及无锁编程、线程同步机制的选择,比如自旋锁 vs 互斥锁,以及如何避免假共享。内存管理方面,内存池和避免碎片是关键。系统设计层面,可能会问到事件驱动架构、网络优化(如TCP vs UDP)、时钟同步等。 另外,引用[2]提到了多线程编程中的并发问题,比如数据竞争、死锁,所以需要分析工具和方法,如Valgrind、TSan,以及日志和断言的使用。解决方案中可能需要提到这些工具的使用案例。 还要考虑C++11/14/17的特性,比如右值引用、移动语义、智能指针等,如何在频交易系统中应用。例如,使用std::atomic进行无锁操作,或者用alignas避免缓存行伪共享。 可能的问题还包括如何处理订单匹配算法,比如使用哈希表或二叉树,以及如何优化这些数据结构。时间戳的精度和时钟同步也是重点,比如使用Linux的clock_gettime或DPDK的计时器。 需要结合用户提供的引用中的知识点,比如引用[1]中的算法与数据结构,可能涉及动态规划或图算法,但频交易更关注排序、查找的效率,比如快速排序的优化版本是否适用。 在解决方案中,要强调实际案例,比如引用[4]中的内存碎片问题,解决方案是使用内存池或对象池。锁竞争问题则采用无锁队列或细分锁粒度。 可能还需要解释为什么某些传统方法不适合,比如虚函数会增加虚表查找开销,所以在系统中应避免。或者,使用模板元编程在编译期优化代码。 最后,需要确保问题覆盖全面,包括语言特性、系统设计、并发优化、网络和内存管理等方面,并给出具体的解决方案和工具推荐,比如使用gcc的__attribute__((aligned))来对齐缓存行。</think># C++频交易系统面试核心问题与解决方案 ## 、低延迟优化(频交易核心考点) 1. **缓存优化策略** - 使用`alignas(64)`强制数据结构按缓存行对齐,避免伪共享 - 热点数据预加载到L1缓存:例如订单簿使用`__builtin_prefetch` - 案例:某做市商系统通过结构体紧凑化,将延迟降低23%[^4] 2. **原子操作陷阱** ```cpp // 错误示例:看似原子的double操作 std::atomic<double> price; // 实际可能引发总线锁 // 正确实践:使用定点数或整型替代 using fix_t = int64_t; // 1e-6精度 std::atomic<fix_t> atomic_price; ``` 3. **内存管理禁忌** - 禁用`new/delete`:采用boost::pool或tcmalloc内存池 - 对象复用模式:实现对象池模板类,预分配百万级对象 ## 二、并发编程实战难点 1. **锁机制选择矩阵** | 场景 | 推荐方案 | 时延范围 | |---------------------|---------------------|-------------| | 订单队列写入 | 无锁SPSC队列 | 15-40ns | | 风控数据读取 | RCU(read-copy-update)| 20-50ns | | 日志批量写入 | 自旋锁+批量提交 | 50-100ns | 2. **无锁编程进阶** ```cpp template<typename T> class LockFreeQueue { struct Node { std::atomic<Node*> next; T data; }; // 使用CAS实现多生产者队列 bool enqueue(const T& value) { Node* newNode = pool.allocate(); // ...CAS操作更新tail指针 } }; ``` ## 三、系统级优化关键技术 1. **网络栈改造方案** ```bash # 使用DPDK绑定网卡 sudo ./dpdk-setup.sh # 配置大页内存 # 编译时添加-march=native优化指令集 ``` 2. **时间戳精度对比** | 方法 | 精度 | 开销 | 适用场景 | |--------------------|--------|--------|------------------| | clock_gettime | 1ns | 22ns | 常规订单处理 | | TSC寄存器 | CPU周期| 6ns | 核心交易逻辑 | | FPGA硬件时钟 | 0.1ns | 硬件级 |频套利 | ## 四、频交易特有设计模式 1. **订单簿优化方案** - 使用多层红黑树结构:`std::map`替代方案 - 价格梯度压缩:将浮点价格映射为整型范围 - 批量撤单算法:实现O(1)复杂度区域删除 2. **风控系统熔断机制** ```cpp class CircuitBreaker { std::atomic<int64_t> error_count; std::chrono::nanoseconds check_interval; public: bool allow_request() { return error_count.load(std::memory_order_relaxed) < threshold; } }; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

self-motivation

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值