[Tarantool-patches] [PATCH v3 2/9] lua: built-in module datetime

Safin Timur tsafin at tarantool.org
Sun Aug 15 23:52:50 MSK 2021


There are updates with jot.on() vs jit.off() numbers, and further 
investigations for C benchmarks of double vs int64_t, please see below...

On 12.08.2021 23:47, Safin Timur via Tarantool-patches wrote:
> 
> 
> On 10.08.2021 15:21, Igor Munkin wrote:
>> Vlad,
>>
>> On 10.08.21, Vladislav Shpilevoy wrote:
>>> Hi! Thanks for the fixes!
>>>
>>> On 08.08.2021 19:35, Safin Timur wrote:
>>>> Much respect that you've found some time for review, even while 
>>>> being on vacation! Thanks!
>>>>
>>>> On 08.08.2021 14:26, Vladislav Shpilevoy wrote:
>>>>>>>> +local datetime_index_handlers = {
>>>>>>>> +    unixtime = function(self)
>>>>>>>> +        return self.secs
>>>>>>>> +    end,
>>>>>>>> +
>>>>>>>> +    timestamp = function(self)
>>>>>>>> +        return tonumber(self.secs) + self.nsec / 1e9
>>>>>>>
>>>>>>> 11. If you are saying the Lua number value range is enough,
>>>>>>> why do you store them as 64bit integers in 'self' and
>>>>>>> convert to a number only for the external API?
>>>>>>
>>>>>> May be I misunderstood your sentence, but let me elaborate here.
>>>>>> I need seconds and nanoseconds kept separately for their efficient 
>>>>>> and precise handling, and for passing to c-dt.
>>>>>>
>>>>>> If we would keep 32-bit integers in seconds then we would be able 
>>>>>> to handle only dates upto 2038 year, thus we need 64-bit seconds 
>>>>>> for extended range.
>>>>>>
>>>>>> OTOH, not whole 64-bit range is practical, and required for 
>>>>>> expected in real-life datetime range values. It's not a problem 
>>>>>> that Lua number and int64_t have very different range for precise 
>>>>>> integer values. Lua number could handle integer values upto 9e15, 
>>>>>> which is corresponding to ...
>>>>>
>>>>> I know all that. The question is why do you keep storing cdata 64 
>>>>> bits numbers
>>>>> inside of the datetime objects, if you convert them all to Lua 
>>>>> numbers before
>>>>> return? You could store self.secs as just number. And the other 
>>>>> fields too. Lua
>>>>> number is double, it does not loose precision for integers < 2^53, 
>>>>> which should
>>>>> be enough for the ranges you want to support. Correct?
>>>>
>>>> I keep using 64-bit because the primary code operating with fields 
>>>> is on C-side, and we need Lua number only on rare cases when user 
>>>> asked for composed attribute date.timestamp. Whenever we deal with 
>>>> seconds within arthmetic operations or transfer to c-dt, we need 
>>>> integer C type, larger than 32-bit. It's good fit for int64_t.
>>>
>>> But it is slower. Notably slower last time I benched, when I also
>>> thought integers should be faster than doubles. But cdata 64 bit 
>>> integers
>>> are slower than plain Lua numbers. Perhaps because they involve too much
>>> work with metatables for everything. Besides, doubles are larger than 32
>>> bits - they are 53 bits until they start loosing int precision. And 
>>> it is
>>> just enough for you, isn't it?
>>
>> Sorry for breaking into the party, but reasoning is much simpler: Lua
>> numbers are just double values stored on the guest stack (or other
>> TValue storage); cdata 64-bit integers are GCcdata objects, so like all
>> others GC objects it has its lifetime, has to be allocated and freed and
>> traversed by GC. You can consider them as GCstr except they are not
>> interned. Hence, if the precision is fine (and it is AFAICS), then there
>> is not need to use GCcdata instead of Lua native numbers. In other
>> words, I totally agree with you.
>>
>>>
>>
>> <snipped>
>>
>>
> 
> 
> Either I do something totally wrong, or may be I'm absolutely clueless.
> Oh rather both...
> 
> Here is experiment I've proceed
> https://gist.github.com/tsafin/f7f21aad53f23801839b3b278cfac380
> 
> I try to compare speed of accessing datetime.secs field:
> - when it is current int64_t redirected via ffi to the `struct datetime`;
> - when it is wrapped as (calculated) attribute of type double which we
> access via FFI datetime_secs() function accessor;
> - or when it's declared as `double` in the similar `struct 
> datetime_double`.
> 
> No surpise that calculated attribute is 3x orders of magnitude slower
> than direct ffi access to either int64_t or double field.
> 
> OTOH, differences for timings to access to int64_t (which should be
> boxed) and double (which should be unboxed) are negligible, and
> essentially the same:
> 
> ```
> ✔ ~/datetime/tarantoolt/build [tsafin/gh-5941-datetime-v4 ↑·4|✚ 3…4⚑ 3]
> 23:40 $ ./src/tarantool ../../bench-datetime-secs.lua
> ctype<struct datetime>
> date.secs       0.00035929679870605
> ctype<struct datetime>
> date.secsf      0.49544525146484
> ctype<struct datetime_double>
> date_double.secs        0.00042939186096191
> ✔ ~/datetime/tarantoolt/build [tsafin/gh-5941-datetime-v4 ↑·4|✚ 3…4⚑ 3]
> 23:40 $ ./src/tarantool ../../bench-datetime-secs.lua
> ctype<struct datetime>
> date.secs       0.00034856796264648
> ctype<struct datetime>
> date.secsf      0.40926361083984
> ctype<struct datetime_double>
> date_double.secs        0.00043344497680664
> ✔ ~/datetime/tarantoolt/build [tsafin/gh-5941-datetime-v4 ↑·4|✚ 3…4⚑ 3]
> 23:40 $ ./src/tarantool ../../bench-datetime-secs.lua
> ctype<struct datetime>
> date.secs       0.00034213066101074
> ctype<struct datetime>
> date.secsf      0.46818256378174
> ctype<struct datetime_double>
> date_double.secs        0.00037813186645508
> ✔ ~/datetime/tarantoolt/build [tsafin/gh-5941-datetime-v4 ↑·4|✚ 3…4⚑ 3]
> 23:40 $ ./src/tarantool ../../bench-datetime-secs.lua
> ctype<struct datetime>
> date.secs       0.00051259994506836
> ctype<struct datetime>
> date.secsf      0.6695671081543
> ctype<struct datetime_double>
> date_double.secs        0.00048208236694336
> ```
> 
> What did I do wrong?
> 
> Thanks,
> Timur

After discussions with Igor it was decided to investiage differences in 
behavior of Lua code when jit.on() or off().

https://gist.github.com/tsafin/f7f21aad53f23801839b3b278cfac380#file-jit-on-vs-off-md

jit.off()
=========


```
✔ ~/datetime/tarantoolt/build [tsafin/msgpack-format L|✚ 1…6⚑ 3]
14:47 $ ./src/tarantool ../../bench-hash-dt.lua
date.secs       0.25970935821533
date_double.secs        0.15149474143982
✔ ~/datetime/tarantoolt/build [tsafin/msgpack-format L|✚ 1…6⚑ 3]
14:48 $ ./src/tarantool ../../bench-hash-dt.lua
date.secs       0.33055567741394
date_double.secs        0.18803310394287
✔ ~/datetime/tarantoolt/build [tsafin/msgpack-format L|✚ 1…6⚑ 3]
14:48 $ ./src/tarantool ../../bench-hash-dt.lua
date.secs       0.29576468467712
date_double.secs        0.15472149848938
✔ ~/datetime/tarantoolt/build [tsafin/msgpack-format L|✚ 1…6⚑ 3]
14:48 $ ./src/tarantool ../../bench-hash-dt.lua
date.secs       0.2386200428009
date_double.secs        0.1287829875946
```

jit.on()
========
```
✔ ~/datetime/tarantoolt/build [tsafin/msgpack-format L|✚ 1…6⚑ 3]
14:46 $ ./src/tarantool ../../bench-hash-dt.lua
date.secs       0.0029795169830322
date_double.secs        0.00043296813964844
✔ ~/datetime/tarantoolt/build [tsafin/msgpack-format L|✚ 1…6⚑ 3]
14:47 $ ./src/tarantool ../../bench-hash-dt.lua
date.secs       0.00053858757019043
date_double.secs        0.00038242340087891
✔ ~/datetime/tarantoolt/build [tsafin/msgpack-format L|✚ 1…6⚑ 3]
14:47 $ ./src/tarantool ../../bench-hash-dt.lua
date.secs       0.00038433074951172
date_double.secs        0.00033235549926758
✔ ~/datetime/tarantoolt/build [tsafin/msgpack-format L|✚ 1…6⚑ 3]
14:47 $ ./src/tarantool ../../bench-hash-dt.lua
date.secs       0.0003509521484375
date_double.secs        0.00031447410583496
```
i.e. for jit.on() mode there is no much difference in timings of boxed 
(ffi.int64_t) or unboxed (double) types used. Igor assumes that type 
narrowing was working well in this case.

But with jit.off() we have dramatically diferent picture - double field 
is clearly faster than int64_t field. Upto 2x faster.

So it was interesting to see how it looks from the other side - if we 
compare C code with structures using double instead of int64_t.

Please see (shocking for me) results here -
https://gist.github.com/tsafin/618fbf847d258f6e7f5a75fdf9ea945b

Here is excerpt from result for SSE2 mode (which is default):

| Test Name                             | Median  | slower/faster |
|---------------------------------------|---------|-----|
| `DateTime_Assign70<uint64_t>`         | 5254 ns |  -  |
| `DateTime_Assign70<double>`           | 4948 ns |  +  |
| `DateTime_AssignCompare70<uint64_t>`  | 4948 ns |  -  |
| `DateTime_AssignCompare70<double>`    | 4784 ns |  +  |
| `DateTime_Compare20<uint64_t>`        | 4 ns    |  -  |
| `DateTime_Compare20<double>`          | 3 ns    |  +  |
| `DateTime_ToString1<uint64_t>`        | 515 ns  |  +  |
| `DateTime_ToString1<double>`          | 522 ns  |  -  |

Comparison shows that double provides similar or sometimes better 
timings, despite our prior experience with older x86 processors. (Be it 
SSE2, AVX1 or AVX2 code).

The only benchmark which has been constantly slower with doubles -
was stringization `DatetTime_ToString` which is consistently showing 
worse numbers with doubles than with int64s. But difference is very 
small and negligible in a longer run.

So, I'm finally convinced (data-driven decision!) that double might be a 
good type for using within `datetime.secs`, noth from LuaJIT and C point 
of view.


If you are curious enough, please see below the additional patch with 
benchmark code, which I've pushed to the branch...
-----------------------------------------------------------------
commit f2080cc7df2567cf2ec66d8ad341572ae76a77a4
Author: Timur Safin <tsafin at tarantool.org>
Date:   Sun Aug 15 17:19:26 2021 +0300

     datetime: perf test for datetime parser

     It was told that if field `datetime.secs` would be `double` we 
should get
     better performance in LuaJIT instead of `uint64_t` type, which is 
used at the
     moment.

     So we have created benchmark, which was comparing implementations 
of functions
     from `datetime.c` if we would use `double` or `int64_t` for 
`datetime.secs` field.

     Despite expectations, based on prior experience with floaing-point 
on x86
     processors, comparison shows that `double` provides similar or
     sometimes better timings. And picture stays consistent be it SSE2, 
AVX1 or
     AVX2 code.

diff --git a/perf/CMakeLists.txt b/perf/CMakeLists.txt
index 3651de5b4..b5d7caf81 100644
--- a/perf/CMakeLists.txt
+++ b/perf/CMakeLists.txt
@@ -12,3 +12,6 @@ include_directories(${CMAKE_SOURCE_DIR}/third_party)

  add_executable(tuple.perftest tuple.cc)
  target_link_libraries(tuple.perftest core box tuple benchmark::benchmark)
+
+add_executable(datetime.perftest datetime-parser.cc datetime-compare.cc)
+target_link_libraries(datetime.perftest cdt core benchmark::benchmark)
diff --git a/perf/datetime-common.h b/perf/datetime-common.h
new file mode 100644
index 000000000..6fd4e1e3b
--- /dev/null
+++ b/perf/datetime-common.h
@@ -0,0 +1,105 @@
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+#include <benchmark/benchmark.h>
+
+#include "dt.h"
+#include "datetime.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static const char sample[] = "2012-12-24T15:30Z";
+
+#define S(s)               \
+	{ s, sizeof(s) - 1 }
+
+static struct
+{
+	const char *sz;
+	size_t len;
+} tests[] = {
+    S("2012-12-24 15:30Z"),
+    S("2012-12-24 15:30z"),
+    S("2012-12-24 15:30"),
+    S("2012-12-24 16:30+01:00"),
+    S("2012-12-24 16:30+0100"),
+    S("2012-12-24 16:30+01"),
+    S("2012-12-24 14:30-01:00"),
+    S("2012-12-24 14:30-0100"),
+    S("2012-12-24 14:30-01"),
+    S("2012-12-24 15:30:00Z"),
+    S("2012-12-24 15:30:00z"),
+    S("2012-12-24 15:30:00"),
+    S("2012-12-24 16:30:00+01:00"),
+    S("2012-12-24 16:30:00+0100"),
+    S("2012-12-24 14:30:00-01:00"),
+    S("2012-12-24 14:30:00-0100"),
+    S("2012-12-24 15:30:00.123456Z"),
+    S("2012-12-24 15:30:00.123456z"),
+    S("2012-12-24 15:30:00.123456"),
+    S("2012-12-24 16:30:00.123456+01:00"),
+    S("2012-12-24 16:30:00.123456+01"),
+    S("2012-12-24 14:30:00.123456-01:00"),
+    S("2012-12-24 14:30:00.123456-01"),
+    S("2012-12-24t15:30Z"),
+    S("2012-12-24t15:30z"),
+    S("2012-12-24t15:30"),
+    S("2012-12-24t16:30+01:00"),
+    S("2012-12-24t16:30+0100"),
+    S("2012-12-24t14:30-01:00"),
+    S("2012-12-24t14:30-0100"),
+    S("2012-12-24t15:30:00Z"),
+    S("2012-12-24t15:30:00z"),
+    S("2012-12-24t15:30:00"),
+    S("2012-12-24t16:30:00+01:00"),
+    S("2012-12-24t16:30:00+0100"),
+    S("2012-12-24t14:30:00-01:00"),
+    S("2012-12-24t14:30:00-0100"),
+    S("2012-12-24t15:30:00.123456Z"),
+    S("2012-12-24t15:30:00.123456z"),
+    S("2012-12-24t16:30:00.123456+01:00"),
+    S("2012-12-24t14:30:00.123456-01:00"),
+    S("2012-12-24 16:30 +01:00"),
+    S("2012-12-24 14:30 -01:00"),
+    S("2012-12-24 15:30 UTC"),
+    S("2012-12-24 16:30 UTC+1"),
+    S("2012-12-24 16:30 UTC+01"),
+    S("2012-12-24 16:30 UTC+0100"),
+    S("2012-12-24 16:30 UTC+01:00"),
+    S("2012-12-24 14:30 UTC-1"),
+    S("2012-12-24 14:30 UTC-01"),
+    S("2012-12-24 14:30 UTC-01:00"),
+    S("2012-12-24 14:30 UTC-0100"),
+    S("2012-12-24 15:30 GMT"),
+    S("2012-12-24 16:30 GMT+1"),
+    S("2012-12-24 16:30 GMT+01"),
+    S("2012-12-24 16:30 GMT+0100"),
+    S("2012-12-24 16:30 GMT+01:00"),
+    S("2012-12-24 14:30 GMT-1"),
+    S("2012-12-24 14:30 GMT-01"),
+    S("2012-12-24 14:30 GMT-01:00"),
+    S("2012-12-24 14:30 GMT-0100"),
+    S("2012-12-24 14:30 -01:00"),
+    S("2012-12-24 16:30:00 +01:00"),
+    S("2012-12-24 14:30:00 -01:00"),
+    S("2012-12-24 16:30:00.123456 +01:00"),
+    S("2012-12-24 14:30:00.123456 -01:00"),
+    S("2012-12-24 15:30:00.123456 -00:00"),
+    S("20121224T1630+01:00"),
+    S("2012-12-24T1630+01:00"),
+    S("20121224T16:30+01"),
+    S("20121224T16:30 +01"),
+};
+#undef S
+
+#define DIM(a) (sizeof(a) / sizeof(a[0]))
+
+int
+parse_datetime(const char *str, size_t len, int64_t *sp, int32_t *np,
+	       int32_t *op);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/perf/datetime-compare.cc b/perf/datetime-compare.cc
new file mode 100644
index 000000000..efd0dba0e
--- /dev/null
+++ b/perf/datetime-compare.cc
@@ -0,0 +1,212 @@
+#include "dt.h"
+#include <string.h>
+#include <assert.h>
+
+#include "datetime-common.h"
+
+template <typename T>
+struct datetime_bench
+{
+	T secs;
+	uint32_t nsec;
+	uint32_t offset;
+
+static struct datetime_bench date_array[];
+};
+template<typename T>
+struct datetime_bench<T> datetime_bench<T>::date_array[DIM(tests)];
+
+/// Parse 70 datetime literals of various lengths
+template <typename T>
+static void
+Assign70()
+{
+	size_t index;
+	int64_t secs_expected;
+	int nanosecs;
+	int ofs;
+	using dt_bench = datetime_bench<T>;
+
+	for (index = 0; index < DIM(tests); index++) {
+		int64_t secs;
+		int rc = parse_datetime(tests[index].sz, tests[index].len,
+					&secs, &nanosecs, &ofs);
+		assert(rc == 0);
+		dt_bench::date_array[index].secs = (T)secs;
+		dt_bench::date_array[index].nsec = nanosecs;
+		dt_bench::date_array[index].offset = ofs;
+	}
+}
+
+template <typename T>
+static void
+DateTime_Assign70(benchmark::State &state)
+{
+	for (auto _ : state)
+		Assign70<T>();
+}
+BENCHMARK_TEMPLATE1(DateTime_Assign70, uint64_t);
+BENCHMARK_TEMPLATE1(DateTime_Assign70, double);
+
+#define COMPARE_RESULT_BENCH(a, b) (a < b ? -1 : a > b)
+
+template <typename T>
+int datetime_compare(const struct datetime_bench<T> *lhs,
+		     const struct datetime_bench<T> *rhs)
+{
+	int result = COMPARE_RESULT_BENCH(lhs->secs, rhs->secs);
+	if (result != 0)
+		return result;
+
+	return COMPARE_RESULT_BENCH(lhs->nsec, rhs->nsec);
+}
+
+template <typename T>
+static void
+AssignCompare70()
+{
+	size_t index;
+	int nanosecs;
+	int ofs;
+	using dt_bench = datetime_bench<T>;
+
+	size_t arrays_sz = DIM(tests);
+	for (index = 0; index < arrays_sz; index++) {
+		int64_t secs;
+		int rc = parse_datetime(tests[index].sz, tests[index].len,
+					&secs, &nanosecs, &ofs);
+		assert(rc == 0);
+		dt_bench::date_array[index].secs = (T)secs;
+		dt_bench::date_array[index].nsec = nanosecs;
+		dt_bench::date_array[index].offset = ofs;
+	}
+
+	for (index = 0; index < (arrays_sz - 1); index++) {
+		volatile int rc = datetime_compare<T>(&dt_bench::date_array[index],
+					     &dt_bench::date_array[index + 1]);
+		assert(rc == 0 || rc == -1 || rc == 1);
+	}
+}
+
+template <typename T>
+static void
+DateTime_AssignCompare70(benchmark::State &state)
+{
+	for (auto _ : state)
+		AssignCompare70<T>();
+}
+BENCHMARK_TEMPLATE1(DateTime_AssignCompare70, uint64_t);
+BENCHMARK_TEMPLATE1(DateTime_AssignCompare70, double);
+
+template <typename T>
+static void
+Compare20()
+{
+	size_t index;
+	int nanosecs;
+	int ofs;
+	using dt_bench = datetime_bench<T>;
+
+	for (size_t i = 0; i < 10; i++) {
+		volatile int rc = datetime_compare<T>(&dt_bench::date_array[i],
+					     &dt_bench::date_array[32 + i]);
+		assert(rc == 0 || rc == -1 || rc == 1);
+	}
+}
+
+template <typename T>
+static void
+DateTime_Compare20(benchmark::State &state)
+{
+	for (auto _ : state)
+		Compare20<T>();
+}
+BENCHMARK_TEMPLATE1(DateTime_Compare20, uint64_t);
+BENCHMARK_TEMPLATE1(DateTime_Compare20, double);
+
+
+#define SECS_EPOCH_1970_OFFSET ((int64_t)DT_EPOCH_1970_OFFSET * 
SECS_PER_DAY)
+
+template<typename T>
+int
+datetime_to_string(const struct datetime_bench<T> *date, char *buf, 
uint32_t len)
+{
+#define ADVANCE(sz)		\
+	if (buf != NULL) { 	\
+		buf += sz; 	\
+		len -= sz; 	\
+	}			\
+	ret += sz;
+
+	int offset = date->offset;
+	/* for negative offsets around Epoch date we could get
+	 * negative secs value, which should be attributed to
+	 * 1969-12-31, not 1970-01-01, thus we first shift
+	 * epoch to Rata Die then divide by seconds per day,
+	 * not in reverse
+	 */
+	int64_t secs = (int64_t)date->secs + offset * 60 + SECS_EPOCH_1970_OFFSET;
+	assert((secs / SECS_PER_DAY) <= INT_MAX);
+	dt_t dt = dt_from_rdn(secs / SECS_PER_DAY);
+
+	int year, month, day, sec, ns, sign;
+	dt_to_ymd(dt, &year, &month, &day);
+
+	int hour = (secs / 3600) % 24,
+	    minute = (secs / 60) % 60;
+	sec = secs % 60;
+	ns = date->nsec;
+
+	int ret = 0;
+	uint32_t sz = snprintf(buf, len, "%04d-%02d-%02dT%02d:%02d",
+			       year, month, day, hour, minute);
+	ADVANCE(sz);
+	if (sec || ns) {
+		sz = snprintf(buf, len, ":%02d", sec);
+		ADVANCE(sz);
+		if (ns) {
+			if ((ns % 1000000) == 0)
+				sz = snprintf(buf, len, ".%03d", ns / 1000000);
+			else if ((ns % 1000) == 0)
+				sz = snprintf(buf, len, ".%06d", ns / 1000);
+			else
+				sz = snprintf(buf, len, ".%09d", ns);
+			ADVANCE(sz);
+		}
+	}
+	if (offset == 0) {
+		sz = snprintf(buf, len, "Z");
+		ADVANCE(sz);
+	}
+	else {
+		if (offset < 0)
+			sign = '-', offset = -offset;
+		else
+			sign = '+';
+
+		sz = snprintf(buf, len, "%c%02d:%02d", sign, offset / 60, offset % 60);
+		ADVANCE(sz);
+	}
+	return ret;
+}
+#undef ADVANCE
+
+template <typename T>
+static void
+ToString1()
+{
+	char buf[48];
+	struct datetime_bench<T> dateval = datetime_bench<T>::date_array[13];
+
+	volatile auto len = datetime_to_string<T>(&dateval, buf, sizeof buf);
+}
+
+template <typename T>
+static void
+DateTime_ToString1(benchmark::State &state)
+{
+	for (auto _ : state)
+		ToString1<T>();
+}
+BENCHMARK_TEMPLATE1(DateTime_ToString1, uint64_t);
+BENCHMARK_TEMPLATE1(DateTime_ToString1, double);
diff --git a/perf/datetime-parser.cc b/perf/datetime-parser.cc
new file mode 100644
index 000000000..61557fe8f
--- /dev/null
+++ b/perf/datetime-parser.cc
@@ -0,0 +1,105 @@
+#include "dt.h"
+#include <string.h>
+#include <assert.h>
+
+#include "datetime-common.h"
+
+/* p5-time-moment/src/moment_parse.c: parse_string_lenient() */
+int
+parse_datetime(const char *str, size_t len, int64_t *sp, int32_t *np,
+	       int32_t *op)
+{
+	size_t n;
+	dt_t dt;
+	char c;
+	int sod = 0, nanosecond = 0, offset = 0;
+
+	n = dt_parse_iso_date(str, len, &dt);
+	if (!n)
+		return 1;
+	if (n == len)
+		goto exit;
+
+	c = str[n++];
+	if (!(c == 'T' || c == 't' || c == ' '))
+		return 1;
+
+	str += n;
+	len -= n;
+
+	n = dt_parse_iso_time(str, len, &sod, &nanosecond);
+	if (!n)
+		return 1;
+	if (n == len)
+		goto exit;
+
+	if (str[n] == ' ')
+	n++;
+
+	str += n;
+	len -= n;
+
+	n = dt_parse_iso_zone_lenient(str, len, &offset);
+	if (!n || n != len)
+		return 1;
+
+exit:
+	*sp = ((int64_t)dt_rdn(dt) - 719163) * 86400 + sod - offset * 60;
+	*np = nanosecond;
+	*op = offset;
+
+	return 0;
+}
+
+/// Parse 70 datetime literals of various lengths
+static void
+ParseTimeStamps()
+{
+	size_t index;
+	int64_t secs_expected;
+	int nanosecs;
+	int ofs;
+	parse_datetime(sample, sizeof(sample) - 1, &secs_expected,
+		       &nanosecs, &ofs);
+
+	for (index = 0; index < DIM(tests); index++)
+	{
+		int64_t secs;
+		int rc = parse_datetime(tests[index].sz, tests[index].len,
+					&secs, &nanosecs, &ofs);
+		assert(rc == 0);
+		assert(secs == secs_expected);
+	}
+}
+
+static void
+CDT_Parse70(benchmark::State &state)
+{
+	for (auto _ : state)
+		ParseTimeStamps();
+}
+BENCHMARK(CDT_Parse70);
+
+/// Parse single datetime literal of longest length
+static void
+Parse1()
+{
+	const char civil_string[] = "2015-02-18T10:50:31.521345123+10:00";
+	int64_t secs;
+	int nanosecs;
+	int ofs;
+	int rc = parse_datetime(civil_string, sizeof(civil_string) - 1,
+				&secs, &nanosecs, &ofs);
+	assert(rc == 0);
+	assert(nanosecs == 521345123);
+}
+
+static void
+CDT_Parse1(benchmark::State &state)
+{
+	for (auto _ : state)
+		Parse1();
+}
+BENCHMARK(CDT_Parse1);
+
+BENCHMARK_MAIN();
-----------------------------------------------------------------

Running to switch to doubles in all references to `struct datetime` now...

Thanks,
Timur


More information about the Tarantool-patches mailing list