Opened 7 years ago

Closed 5 years ago

#11806 closed Bugs (wontfix)

microsec_clock::universal_time() is needlessly slow

Reported by: jzwinck@… Owned by: James E. King, III
Milestone: To Be Determined Component: date_time
Version: Boost 1.55.0 Severity: Optimization
Keywords: Cc:

Description

The documentation for microsec_clock::universal_time() says:

Get the UTC time using a sub second resolution clock. On Unix systems this is implemented using GetTimeOfDay.

In fact it is implemented partly using gettimeofday() but also gmtime_r(). Once you unwrap all the layers, the flow is something like this:

sec,nsec = gettimeofday() date,time = gmtime(sec) sec,nsec = ptime(date,time) return sec+nsec/1000

The problem is that gmtime_r() is very slow, relative to the other operations involved here, and it is completely unnecessary. A call to gettimeofday() on Unix is all you need to implement universal_time().

Change History (2)

comment:1 by James E. King, III, 5 years ago

Owner: changed from az_sw_dude to James E. King, III
Status: newassigned

comment:2 by James E. King, III, 5 years ago

Resolution: wontfix
Status: assignedclosed

I benchmarked calling the current implementation (boost 1.66.0) in release mode on linux (ubuntu artful) 1M times and each call takes less than 100ns:

test:
  // benchmark for trac 11806
  {
    check("calling microsec_clock::universal_time 1000000 times", true);
    boost::timer::auto_cpu_timer t;
    for (size_t i = 0; i < 1000000; ++i) {
      ptime x = microsec_clock::universal_time();
    }
    return 0;
  }

results:
jking@ubuntu:~/boost/libs/date_time/test$ ../../../bin.v2/libs/date_time/test/testmicrosec_time_clock.test/gcc-gnu-7/release/threadapi-pthread/testmicrosec_time_clock
Pass :: calling microsec_clock::universal_time 1000000 times 
 0.088597s wall, 0.090000s user + 0.000000s system = 0.090000s CPU (101.6%)
jking@ubuntu:~/boost/libs/date_time/test$ ../../../bin.v2/libs/date_time/test/testmicrosec_time_clock.test/gcc-gnu-7/release/threadapi-pthread/testmicrosec_time_clock
Pass :: calling microsec_clock::universal_time 1000000 times 
 0.103956s wall, 0.100000s user + 0.000000s system = 0.100000s CPU (96.2%)
jking@ubuntu:~/boost/libs/date_time/test$ ../../../bin.v2/libs/date_time/test/testmicrosec_time_clock.test/gcc-gnu-7/release/threadapi-pthread/testmicrosec_time_clock
Pass :: calling microsec_clock::universal_time 1000000 times 
 0.096352s wall, 0.090000s user + 0.000000s system = 0.090000s CPU (93.4%)

I then changed the code such that if the clock source is universal time (i.e. gmtime_r) passed into create_time, we take (what I thought would be a) shortcut:

#if defined(BOOST_HAS_GETTIMEOFDAY)
      timeval tv;
      if (-1 == gettimeofday(&tv, 0)) { //gettimeofday does not support TZ adjust on Linux.
        boost::throw_exception(std::runtime_error("gettimeofday failed"));
      }
      if (universal) {
        // if the source is UTC we can take a fast shortcut here instead
        // of calling gmtime_r, which is slow (see Boost Trac 11806)
        static date_type posix_epoch(1970, 1, 1);
        static uint64_t res = resolution_traits_type::res_adjust();
        return time_type(date_type(1970, 1, 1),
                         time_duration_type(0, 0,
                           tv.tv_sec, (static_cast<uint64_t>(tv.tv_usec) * res) / 1000000ull));
      }
      std::time_t t = tv.tv_sec;
      boost::uint32_t sub_sec = tv.tv_usec;
#elif defined(BOOST_HAS_FTIME)

This slowed down the program so much I didn't even let it finish. Asking for a ptime based on a large number of seconds is incredibly inefficient. So I am resolving this as wontfix, unless you have a better suggestion for implementing it.

Note: See TracTickets for help on using tickets.