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

时间:2017-12-29 18:45:53

标签: 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

在相关过程的线索中抓住了这个

% 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

最后发现线程在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);
    }
}

4 个答案:

答案 0 :(得分:4)

循环中没有任何其他注意事项,因此您在System.currentTimeMillis()

上旋转

vDSO将有助于提高System.currentTimeMillis()的性能,但它是否真的会改变CPU的分类?#34; System&#34;到&#34;用户&#34;?我不知道,抱歉。

这个线程将消耗100%的CPU,它是否会被分类为&#34; System&#34;它会产生很大的不同。或&#34;用户&#34;?

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

这里你的实际问题是什么?

  

我无法理解的是,在存在vDSO的情况下,这些内核调用应该只发生在用户的地址空间中。但是从perf报告中可以清楚地看到,内核调用确实发生在内核空间中。关于这个的任何指示?

为什么在这个自旋锁中花费的CPU时间是如何分类的呢?

根据User CPU time vs System CPU time?&#34;系统CPU时间&#34;是:

  

系统CPU时间:处理器处理与该特定程序相关的操作系统功能的时间。

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

答案 1 :(得分:2)

通过读取代码,除了publisher.publish(events)queue.poll()之外,没有用于阻止while循环的控制代码,这意味着此线程在while循环中忙,永远不会休息。

在我看来,你需要限制对System.currentTimeMillis()的调用。一个很好的选择是make queue.poll()阻塞,一些伪代码:

while (!stopWork) {
    try {
        // wait for messages with 10 seconds timeout,if no message or timeout return empty list
        // this is easy to impl with BlockingQueue
        List<String> events = queue.poll(10,TimeUnit.SECOND);
        if (events.isEmpty()) {
            continue;
        }
        new java.util.Timer().schedule( 
            new java.util.TimerTask() {
                @Override
                public void run() {
                    pushEvents(events, true);
                }
            }, 1000*10 );
    } catch (Exception e) {
        // Log and do something
    }
}

答案 2 :(得分:2)

  

我无法理解的是,在存在vDSO的情况下,这些内核调用应该只发生在用户的地址空间中。但是从perf报告中可以清楚地看到,内核调用确实发生在内核空间中。关于这个的任何指示?

可以在虚拟系统上禁用vDSO。 KVM使用PVClock(你可以在这个很好的article中阅读更多内容),这取决于内核版本。 例如,我们可以看到here VCLOCK_MODE永远不会被覆盖。 另一方面,here也为vDSO更改了vclock_mode - 和vclock_mode indicator

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

一般来说,在我的练习中,如果你在里面打电话,那么(真实)&#34;很长一段时间,你总会看到很大的CPU消耗。

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

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

答案 3 :(得分:2)

所以我在这里找出了问题。为了提供更多背景信息,问题更多的是vDSO进行系统调用的事实(如果原始帖子具有误导性,则道歉!)。此内核版本(kvmclock)的时钟源不支持虚拟系统调用,因此发生了真正的系统调用。它在此提交https://github.com/torvalds/linux/commit/3dc4f7cfb7441e5e0fed3a02fc81cdaabd28300a#diff-5a34e1e52f50e00cef4b0d0ff3fef8f7中被引入(感谢egorlitvinenko指出这一点。

另外,我确实理解while(true)中的任何内容都会占用CPU。由于这是在apache风暴上下文中,调用是在进行HTTP调用之前基本上批处理事件,所以这可以通过使用apache storm的tick元组支持以更好的方式完成。