system.currentTimeMillis()导致系统CPU使用率过高

Rah*_*Jha 12 java linux kernel vdso

我正在我们的风暴监督员(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
Run Code Online (Sandbox Code Playgroud)

在相关过程的线程中捕获了这一点

% 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
Run Code Online (Sandbox Code Playgroud)

终于弄清楚线程正在运行while(true),其中一个调用是System.currentTimeMillis().我禁用了相同的功能,系统CPU%从50%下降到3%.很清楚这就是问题所在.我无法理解的是,在存在vDSO的情况下,这些内核调用应该只发生在用户的地址空间中.但是从perf报告中可以清楚地看到,内核调用确实发生在内核空间中.有关于此的任何指示?内核版本:3.2.0-4-amd64 Debian 3.2.86-1 x86_64 GNU/Linux
时钟类型:kvm

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

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

ego*_*nko 4

我不明白的是,在存在 vDSO 的情况下,这些内核调用应该只发生在用户的地址空间中。但从性能报告中可以清楚地看出,内核调用确实是在内核空间中发生的。有这方面的指点吗?

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

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

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

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

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