preface

The previous article “Design and Implementation of Cobar SQL Auditing” raised a question about timestamp fetching performance

Get the operating System time and call System.CurrentTimemillis () directly from Java; This is fine, but in Cobar if you take the time this way, the performance loss is very high. Check out the code on Cobar’s Github repository).

CurrentTimeMillis () returns a millisecond timestamp, but the accuracy depends on the operating System. Many operating systems return an accuracy of 10 milliseconds.

/** * Returns the current time in milliseconds. Note that * while the unit of time of the return value is a millisecond,  * the granularity of the value depends on the underlying * operating system and may be larger. For example, many * operating systems measure time in units of tens of * milliseconds. * * <p> See the description of the class <code>Date</code> for * a discussion of slight discrepancies that may arise between * "computer time" and coordinated universal time (UTC). * *@return  the difference, measured in milliseconds, between
     *          the current time and midnight, January 1, 1970 UTC.
     * @see     java.util.Date
     */
    public static native long currentTimeMillis(a);
Copy the code

Why is System.currentTimemillis () slow

Pzemtsov. Making. IO / 2017/07/23 /…

System.currenttimemillis calls getTimeofDay ()

  • Calling getTimeofday () requires switching from user mode to kernel mode;
  • Gettimeofday () is affected by Linux timers (clock sources), especially under HPET timers;
  • The system has only one global clock source. High concurrency or frequent access may cause serious contention.

CurrentTimeMillis () is used to test the performance of System. CurrentTimeMillis () under different threads. Here we use JHM commonly used in middleware to test how long it takes to obtain 10 million time stamps between 1 and 128 threads.

Benchmark Mode Cnt Score Error Units TimeStampTest. Test1Thread avgt 0.271 s/op TimeStampTest. Test2Thread avgt 0.272 s/op TimeStampTest. Test4Thread avgt 0.278 s/op TimeStampTest. Test8Thread avgt 0.375 s/op TimeStampTest. Test16Thread avgt 0.737 s/op TimeStampTest. Test32Thread avgt 1.474 s/op TimeStampTest. Test64Thread avgt 2.907 s/op TimeStampTest. Test128Thread avgt 5.732 s/opCopy the code

As you can see, it’s fast at 1-4 threads, and it grows linearly after 8 threads. Test code reference:

@State(Scope.Benchmark)
public class TimeStampTest {

    private static final int MAX = 10000000;

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(TimeStampTest.class.getSimpleName())
                .forks(1)
                .warmupIterations(1)
                .measurementIterations(1)
                .warmupTime(TimeValue.seconds(5))
                .measurementTime(TimeValue.seconds(5))
                .mode(Mode.AverageTime)
                .syncIterations(false)
                .build();

        new Runner(opt).run();
    }

    @Benchmark
    @Threads(1)
    public void test1Thread(a) {
        for (int i = 0; i < MAX; i++) { currentTimeMillis(); }}@Benchmark
    @Threads(2)
    public void test2Thread(a) {
        for (int i = 0; i < MAX; i++) { currentTimeMillis(); }}@Benchmark
    @Threads(4)
    public void test4Thread(a) {
        for (int i = 0; i < MAX; i++) { currentTimeMillis(); }}@Benchmark
    @Threads(8)
    public void test8Thread(a) {
        for (int i = 0; i < MAX; i++) { currentTimeMillis(); }}@Benchmark
    @Threads(16)
    public void test16Thread(a) {
        for (int i = 0; i < MAX; i++) { currentTimeMillis(); }}@Benchmark
    @Threads(32)
    public void test32Thread(a) {
        for (int i = 0; i < MAX; i++) { currentTimeMillis(); }}@Benchmark
    @Threads(64)
    public void test64Thread(a) {
        for (int i = 0; i < MAX; i++) { currentTimeMillis(); }}@Benchmark
    @Threads(128)
    public void test128Thread(a) {
        for (int i = 0; i < MAX; i++) { currentTimeMillis(); }}private static long currentTimeMillis(a) {
        returnSystem.currentTimeMillis(); }}Copy the code

solution

The easiest way to think about it is to cache the timestamp and use a separate thread to update it. This fetch is just fetching from memory, which is very cheap, but the disadvantage is obvious: the frequency of updates determines the accuracy of the timestamp.

Cobar

Cobar obtains and updates the timestamp related code in

Github.com/alibaba/cob…

/** * Weak precision timer, considering performance does not use synchronization strategy. * *@authorXianmao. Hexm 2011-1-18 PM 06:10:55 */
public class TimeUtil {
    private static long CURRENT_TIME = System.currentTimeMillis();

    public static final long currentTimeMillis(a) {
        return CURRENT_TIME;
    }

    public static final void update(a) { CURRENT_TIME = System.currentTimeMillis(); }}Copy the code

The timing scheduling code is located

Github.com/alibaba/cob…

timer.schedule(updateTime(), 0L, TIME_UPDATE_PERIOD); .// The system time is updated periodically
private TimerTask updateTime(a) {
    return new TimerTask() {
        @Override
        public void run(a) { TimeUtil.update(); }}; }Copy the code

In Cobar, the update interval TIME_UPDATE_PERIOD is 20 milliseconds

Sentinel

Sentinel also uses a cache timestamp, and its code is located

Github.com/alibaba/Sen…

public final class TimeUtil {

    private static volatile long currentTimeMillis;

    static {
        currentTimeMillis = System.currentTimeMillis();
        Thread daemon = new Thread(new Runnable() {
            @Override
            public void run(a) {
                while (true) {
                    currentTimeMillis = System.currentTimeMillis();
                    try {
                        TimeUnit.MILLISECONDS.sleep(1);
                    } catch (Throwable e) {

                    }
                }
            }
        });
        daemon.setDaemon(true);
        daemon.setName("sentinel-time-tick-thread");
        daemon.start();
    }

    public static long currentTimeMillis(a) {
        returncurrentTimeMillis; }}Copy the code

As you can see, Sentinel implements caching every 1 millisecond. Let’s modify the test code to test the performance of Sentinel implementation on 1-128 threads

Benchmark Mode Cnt Score Error Units TimeStampTest. Test1Thread avgt material 10 ⁻ ⁴ s/op TimeStampTest. Test2Thread avgt material 10 ⁻ ⁴ S/op TimeStampTest. Test4Thread avgt material 10 ⁻ ⁴ s/op TimeStampTest. Test8Thread avgt material 10 ⁻ after s/op TimeStampTest. Test16Thread 0.001 s/op avgt TimeStampTest. Test32Thread avgt 0.001 s/op TimeStampTest. Test64Thread avgt 0.003 s/op TimeStampTest. Test128Thread avgt 0.006 s/opCopy the code

This can be compared to using System.CurrentTimemillis directly.

The last

Although cache timestamp performance can improve a lot, but this is only in very high concurrency systems, generally suitable for high concurrency middleware, if the average system to do this optimization, the effect is not obvious. Performance optimization or to grasp the main contradiction, solve the bottleneck, avoid excessive optimization.


Wechat search public account “bug catching master”, to recommend you plain and boring technical articles [Dog head]