系统 CPU 使用率过高,因为 system.currentTimeMillis()

2022-09-04 21:18:23

我正在调试我们的风暴主管(Wheezy机器)的高系统CPU使用率(不是用户CPU使用率)。以下是观察结果

相关过程的性能输出:

Events: 10K cpu-clock
16.40%  java  [kernel.kallsyms]   [k] system_call_after_swapgs
13.95%  java  [kernel.kallsyms]   [k] pvclock_clocksource_read
12.76%  java  [kernel.kallsyms]   [k] do_gettimeofday
12.61%  java  [vdso]              [.] 0x7ffe0fea898f
 9.02%  java  perf-17609.map      [.] 0x7fcabb8b85dc
 7.16%  java  [kernel.kallsyms]   [k] copy_user_enhanced_fast_string
 4.97%  java  [kernel.kallsyms]   [k] native_read_tsc
 2.88%  java  [kernel.kallsyms]   [k] sys_gettimeofday
 2.82%  java  libjvm.so           [.] os::javaTimeMillis()
 2.39%  java  [kernel.kallsyms]   [k] arch_local_irq_restore

在相关进程的线程中捕获此内容

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00    0.000247           0     64038           gettimeofday
  0.00    0.000000           0         1           rt_sigreturn
  0.00    0.000000           0         1           futex
------ ----------- ----------- --------- --------- ----------------
100.00    0.000247                 64040           total

最后发现线程正在运行,其中一个调用是 。我禁用了相同的功能,系统CPU百分比从50%下降到3%。很明显,这就是问题所在。我无法理解的是,在存在vDSO的情况下,这些内核调用应该只发生在用户的地址空间中。但从perf报告中可以清楚地看出,内核调用确实发生在内核空间中。对此有什么指点吗?内核版本: 3.2.0-4-amd64 Debian 3.2.86-1 x86_64 GNU/Linux
时钟类型: kvmwhile(true)System.currentTimeMillis()

添加有问题的线程的代码。

@RequiredArgsConstructor
public class TestThread implements Runnable {
    private final Queue<String> queue;
    private final Publisher publisher;
    private final int maxBatchSize;

    private long lastPushTime;
    @Override
    public void run() {
        lastPushTime = System.currentTimeMillis();
        List<String> events = new ArrayList<>();
        while (true) {
            try {
                String message = queue.poll();
                long lastPollTime = System.currentTimeMillis();
                if (message != null) {
                    events.add(message);
                    pushEvents(events, false);
                }

                // if event threshold hasn't reached the size, but it's been there for over 10seconds, push it.
                if ((lastPollTime - lastPushTime > 10000) && (events.size() > 0)) {
                    pushEvents(events, true);
                }
            } catch (Exception e) {
                // Log and do something
            }
        }
    }

    private void pushEvents(List<String> events, boolean forcePush) {
        if (events.size() >= maxBatchSize || forcePush) {
            pushToHTTPEndPoint(events);
            events.clear();
            lastPushTime = System.currentTimeMillis();
        }
    }

    private void pushToHTTPEndPoint(List<String> events) {
        publisher.publish(events);
    }
}

答案 1

我无法理解的是,在存在vDSO的情况下,这些内核调用应该只发生在用户的地址空间中。但从perf报告中可以清楚地看出,内核调用确实发生在内核空间中。对此有什么指点吗?

可以在虚拟系统上禁用 vDSO。KVM使用PVClock(您可以在这篇不错的文章中阅读更多内容),这取决于内核版本。例如,我们可以在这里看到,VCLOCK_MODE永远不会被覆盖。另一方面,这里vclock_mode更改 - vclock_mode vDSO的指标

此支持在此提交中引入,并在 3.8 版本的 Linux 内核中发布。

一般来说,在我的实践中,如果你长时间调用“while(true)”,你总会看到CPU消耗很大。

当然,在大多数情况下,阻塞队列就足够了,但是如果您需要良好的延迟和性能,您也可以使用旋转,而不会阻塞线程,但是您应该限制旋转周期并进行基准测试以衡量此优化的影响。元代码可以是这样的:

int spin = 100;
while(spin-- > 0) {
    // try to get result
}
// still no result -> execute blocking code

答案 2

循环中没有其他值得注意的东西,所以你正在旋转System.currentTimeMillis()

vDSO 将有助于提高 的性能,但它是否真的将 CPU 的分类从“系统”更改为“用户”?我不知道,对不起。System.currentTimeMillis()

这个线程将消耗100%的CPU,它是否被归类为“系统”或“用户”会产生很大差异?

您应该重写此代码以使用非旋转等待,例如BlockingQueue.poll(timeout)

您在这里的实际问题是什么?

我无法理解的是,在存在vDSO的情况下,这些内核调用应该只发生在用户的地址空间中。但从perf报告中可以清楚地看出,内核调用确实发生在内核空间中。对此有什么指点吗?

为什么在这个自旋锁内花费的CPU时间如何分类很重要?

根据用户 CPU 时间与系统 CPU 时间?“系统 CPU 时间”为:

系统 CPU 时间:处理器处理连接到该特定程序的操作系统功能的时间量。

根据该定义,在 上旋转所花费的时间将计为系统时间,即使它由于 vDSO 而不需要用户到内核模式切换。System.currentTimeMillis()


推荐