Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
97 views
in Technique[技术] by (71.8m points)

performance - C++ - retrieving current date and time fast

I've been experimenting with different ways of retrieving the current date and time, but frankly, none of them satisfy my performance needs.

Here is the most performant function I've found of all

std::string format_time(const char* format)
{
    char date[256];
    auto now = std::chrono::system_clock::now();
    auto in_time_t = std::chrono::system_clock::to_time_t(now);

    std::strftime(date, sizeof(date), format, std::gmtime(&in_time_t));
    return date;
}

I have benchmarked with a simple timer class, and according to it, the function takes ~15μs
I know that it's humanly impossible to notice any overhead from this, however, my curiosity still stands.

Is there a way to retrieve the current date and time faster than this?
I am more than willing to trade code elegancy for performance, even if the code goes near the metal

question from:https://stackoverflow.com/questions/65646395/c-retrieving-current-date-and-time-fast

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I used this open-source preview of the C++20 chrono library (which works with C++11/14/17) to achieve about 100ns per call on macOS.

#include "date/tz.h"
#include <algorithm>
#include <array>
#include <iostream>

void
to_chars(char*, char* l, unsigned long long x)
{
    do
    {
        *--l = static_cast<char>((x % 10) + '0');
        x /= 10;
    } while(x != 0);
}

// yyyy-mm-dd hh:mm:ss.ffffff
// 01234567890123456789012345
auto
format_current_time()
{
    using namespace std;
    using namespace std::chrono;
    using namespace date;
    auto tp = system_clock::now();
    static auto const tz = current_zone();
    static auto info = tz->get_info(tp);
    if (tp >= info.end)
        info = tz->get_info(tp);
    auto tpl = local_days{} + (tp + info.offset - sys_days{});
    auto tpd = floor<days>(tpl);
    year_month_day ymd{tpd};
    hh_mm_ss hms{tpl-tpd};
    array<char, 21+hms.fractional_width> result{};
    auto r = result.data();
    fill(r, r + result.size()-1, '0');
    r[4] = r[7] = '-';
    r[10] = ' ';
    r[13] = r[16] = ':';
    r[19] = '.';
    ::to_chars(r, r+4, int{ymd.year()});
    ::to_chars(r+5, r+7, unsigned{ymd.month()});
    ::to_chars(r+8, r+10, unsigned{ymd.day()});
    ::to_chars(r+11, r+13, hms.hours().count());
    ::to_chars(r+14, r+16, hms.minutes().count());
    ::to_chars(r+17, r+19, hms.seconds().count());
    ::to_chars(r+20, r+20+hms.fractional_width, hms.subseconds().count());
    return result;
}

int
main()
{
    auto t0 = format_current_time();
    auto t1 = format_current_time();
    auto t2 = format_current_time();
    auto t3 = format_current_time();
    auto t4 = format_current_time();
    auto t5 = format_current_time();
    auto t6 = format_current_time();
    auto t7 = format_current_time();
    auto t8 = format_current_time();
    auto t9 = format_current_time();
    auto t10 = format_current_time();
    auto t11 = format_current_time();
    auto t12 = format_current_time();
    auto t13 = format_current_time();
    auto t14 = format_current_time();
    auto t15 = format_current_time();
    auto t16 = format_current_time();
    auto t17 = format_current_time();
    auto t18 = format_current_time();
    auto t19 = format_current_time();
    std::cout << t0.data() << '
';
    std::cout << t1.data() << '
';
    std::cout << t2.data() << '
';
    std::cout << t3.data() << '
';
    std::cout << t4.data() << '
';
    std::cout << t5.data() << '
';
    std::cout << t6.data() << '
';
    std::cout << t7.data() << '
';
    std::cout << t8.data() << '
';
    std::cout << t9.data() << '
';
    std::cout << t10.data() << '
';
    std::cout << t11.data() << '
';
    std::cout << t12.data() << '
';
    std::cout << t13.data() << '
';
    std::cout << t14.data() << '
';
    std::cout << t15.data() << '
';
    std::cout << t16.data() << '
';
    std::cout << t17.data() << '
';
    std::cout << t18.data() << '
';
    std::cout << t19.data() << '
';
}

This just output for me:

2021-01-09 17:44:55.778356
2021-01-09 17:44:56.034498
2021-01-09 17:44:56.034498
2021-01-09 17:44:56.034498
2021-01-09 17:44:56.034498
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034499
2021-01-09 17:44:56.034500
2021-01-09 17:44:56.034500
2021-01-09 17:44:56.034500
2021-01-09 17:44:56.034500

This code assumes that the device does not change the current time zone during the application run (which may not be true for a mobile device). If that assumption can't be made, then the time per call balloons to about 50us. However a compromise could be made to check current_zone() say only once per second (or whatever) to bring the average time back down.

As written, the first step is to get the current UTC time:

auto tp = system_clock::now();

The next step is to get the current local time zone setting, and all the information there is to know about this time zone setting at this time. All of this information is stored in function-local statics as it is assumed that the time zone doesn't change at all, and that the UTC offset changes rarely (e.g. twice a year):

static auto const tz = current_zone();
static auto info = tz->get_info(tp);

info contains an end member that is a UTC timestamp that marks the end of when this information is valid. If tp exceeds the range of validity, then the information is updated (this typically happens at most twice a year):

if (tp >= info.end)
    info = tz->get_info(tp);

Next the UTC offset is applied to the current system_clock time_point and stored in distinct chrono::time_point type called local_time (with the same precision as system_clock::time_point):

auto tpl = local_days{} + (tp + info.offset - sys_days{});

Then this chrono::time_point is broken down into two data structures. The first is a {year, month, day} data structure:

auto tpd = floor<days>(tpl);
year_month_day ymd{tpd};

The first line simply truncates the time_point to days precision (a count of days since the local epoch). Then the second line converts the count of days into a {year, month, day} data structure in the civil calendar.

Then the local time of day is converted into a {hours, minutes, seconds, subseconds} data structure:

hh_mm_ss hms{tpl-tpd};

The units of "subseconds" depends on system_clock::duration. On macOS it is 1us. On Windows it is 100ns.

Everything after this is formatting the values in ymd and hms. The results are stored in a std::array<char, N> to avoid an allocation. I know you have your own formatting, but I show mine so that you know how to access the values in ymd and hms. On Windows, hms.fractional_width is the compile-time integral constant 7 (6 on macOS, 9 when using gcc).


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...