time: microoptimise the Time formating methods (use custom number->string conversion, instead of string interpolation) (#20917)

This commit is contained in:
Hitalo Souza 2024-02-28 05:55:33 -04:00 committed by GitHub
parent 87320f8f93
commit d5370bd220
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 268 additions and 31 deletions

View File

@ -5,54 +5,243 @@ module time
import strings import strings
// int_to_byte_array_no_pad fulfill buffer by part
// it doesn't pad with leading zeros for performance reasons
@[direct_array_access]
fn int_to_byte_array_no_pad(value int, mut arr []u8, size int) {
mut num := value
if size <= 0 || num < 0 {
return
}
// Start from the end of the array
mut i := size - 1
// Convert each digit to a character and store it in the array
for num > 0 && i >= 0 {
arr[i] = (num % 10) + `0`
num /= 10
i--
}
}
// format returns a date string in "YYYY-MM-DD HH:mm" format (24h). // format returns a date string in "YYYY-MM-DD HH:mm" format (24h).
@[manualfree]
pub fn (t Time) format() string { pub fn (t Time) format() string {
return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}' mut buf := [u8(`0`), `0`, `0`, `0`, `-`, `0`, `0`, `-`, `0`, `0`, ` `, `0`, `0`, `:`, `0`,
`0`]
defer {
unsafe { buf.free() }
}
int_to_byte_array_no_pad(t.year, mut buf, 4)
int_to_byte_array_no_pad(t.month, mut buf, 7)
int_to_byte_array_no_pad(t.day, mut buf, 10)
int_to_byte_array_no_pad(t.hour, mut buf, 13)
int_to_byte_array_no_pad(t.minute, mut buf, 16)
return buf.bytestr()
} }
// format_ss returns a date string in "YYYY-MM-DD HH:mm:ss" format (24h). // format_ss returns a date string in "YYYY-MM-DD HH:mm:ss" format (24h).
@[manualfree]
pub fn (t Time) format_ss() string { pub fn (t Time) format_ss() string {
return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}' mut buf := [u8(`0`), `0`, `0`, `0`, `-`, `0`, `0`, `-`, `0`, `0`, ` `, `0`, `0`, `:`, `0`,
`0`, `:`, `0`, `0`]
defer {
unsafe { buf.free() }
}
int_to_byte_array_no_pad(t.year, mut buf, 4)
int_to_byte_array_no_pad(t.month, mut buf, 7)
int_to_byte_array_no_pad(t.day, mut buf, 10)
int_to_byte_array_no_pad(t.hour, mut buf, 13)
int_to_byte_array_no_pad(t.minute, mut buf, 16)
int_to_byte_array_no_pad(t.second, mut buf, 19)
return buf.bytestr()
} }
// format_ss_milli returns a date string in "YYYY-MM-DD HH:mm:ss.123" format (24h). // format_ss_milli returns a date string in "YYYY-MM-DD HH:mm:ss.123" format (24h).
@[manualfree]
pub fn (t Time) format_ss_milli() string { pub fn (t Time) format_ss_milli() string {
return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.nanosecond / 1_000_000):03d}' mut buf := [u8(`0`), `0`, `0`, `0`, `-`, `0`, `0`, `-`, `0`, `0`, ` `, `0`, `0`, `:`, `0`,
`0`, `:`, `0`, `0`, `.`, `0`, `0`, `0`]
defer {
unsafe { buf.free() }
}
int_to_byte_array_no_pad(t.year, mut buf, 4)
int_to_byte_array_no_pad(t.month, mut buf, 7)
int_to_byte_array_no_pad(t.day, mut buf, 10)
int_to_byte_array_no_pad(t.hour, mut buf, 13)
int_to_byte_array_no_pad(t.minute, mut buf, 16)
int_to_byte_array_no_pad(t.second, mut buf, 19)
// Extract and format milliseconds
millis := t.nanosecond / 1_000_000
int_to_byte_array_no_pad(millis, mut buf, 23)
return buf.bytestr()
} }
// format_ss_micro returns a date string in "YYYY-MM-DD HH:mm:ss.123456" format (24h). // format_ss_micro returns a date string in "YYYY-MM-DD HH:mm:ss.123456" format (24h).
@[manualfree]
pub fn (t Time) format_ss_micro() string { pub fn (t Time) format_ss_micro() string {
return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${(t.nanosecond / 1_000):06d}' mut buf := [u8(`0`), `0`, `0`, `0`, `-`, `0`, `0`, `-`, `0`, `0`, ` `, `0`, `0`, `:`, `0`,
`0`, `:`, `0`, `0`, `.`, `0`, `0`, `0`, `0`, `0`, `0`]
defer {
unsafe { buf.free() }
}
int_to_byte_array_no_pad(t.year, mut buf, 4)
int_to_byte_array_no_pad(t.month, mut buf, 7)
int_to_byte_array_no_pad(t.day, mut buf, 10)
int_to_byte_array_no_pad(t.hour, mut buf, 13)
int_to_byte_array_no_pad(t.minute, mut buf, 16)
int_to_byte_array_no_pad(t.second, mut buf, 19)
// Extract and format microseconds
micros := t.nanosecond / 1_000
int_to_byte_array_no_pad(micros, mut buf, 26)
return buf.bytestr()
} }
// format_ss_nano returns a date string in "YYYY-MM-DD HH:mm:ss.123456789" format (24h). // format_ss_nano returns a date string in "YYYY-MM-DD HH:mm:ss.123456789" format (24h).
@[manualfree]
pub fn (t Time) format_ss_nano() string { pub fn (t Time) format_ss_nano() string {
return '${t.year:04d}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}.${t.nanosecond:09d}' mut buf := [u8(`0`), `0`, `0`, `0`, `-`, `0`, `0`, `-`, `0`, `0`, ` `, `0`, `0`, `:`, `0`,
`0`, `:`, `0`, `0`, `.`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`]
defer {
unsafe { buf.free() }
}
int_to_byte_array_no_pad(t.year, mut buf, 4)
int_to_byte_array_no_pad(t.month, mut buf, 7)
int_to_byte_array_no_pad(t.day, mut buf, 10)
int_to_byte_array_no_pad(t.hour, mut buf, 13)
int_to_byte_array_no_pad(t.minute, mut buf, 16)
int_to_byte_array_no_pad(t.second, mut buf, 19)
int_to_byte_array_no_pad(t.nanosecond, mut buf, 29) // Adjusted index for 9 digits
return buf.bytestr()
} }
// format_rfc3339 returns a date string in "YYYY-MM-DDTHH:mm:ss.123Z" format (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html) // format_rfc3339 returns a date string in "YYYY-MM-DDTHH:mm:ss.123Z" format (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html)
// RFC3339 is an Internet profile, based on the ISO 8601 standard for for representation of dates and times using the Gregorian calendar. // RFC3339 is an Internet profile, based on the ISO 8601 standard for for representation of dates and times using the Gregorian calendar.
// It is intended to improve consistency and interoperability, when representing and using date and time in Internet protocols. // It is intended to improve consistency and interoperability, when representing and using date and time in Internet protocols.
@[markused] @[manualfree; markused]
pub fn (t Time) format_rfc3339() string { pub fn (t Time) format_rfc3339() string {
u := t.local_to_utc() mut buf := [u8(`0`), `0`, `0`, `0`, `-`, `0`, `0`, `-`, `0`, `0`, `T`, `0`, `0`, `:`, `0`,
return '${u.year:04d}-${u.month:02d}-${u.day:02d}T${u.hour:02d}:${u.minute:02d}:${u.second:02d}.${(u.nanosecond / 1_000_000):03d}Z' `0`, `:`, `0`, `0`, `.`, `0`, `0`, `0`, `Z`]
defer {
unsafe { buf.free() }
}
if t.unix == 0 && t.nanosecond == 0 {
return buf.bytestr()
}
if t.is_local {
utc_time := t.local_to_utc()
int_to_byte_array_no_pad(utc_time.year, mut buf, 4)
int_to_byte_array_no_pad(utc_time.month, mut buf, 7)
int_to_byte_array_no_pad(utc_time.day, mut buf, 10)
int_to_byte_array_no_pad(utc_time.hour, mut buf, 13)
int_to_byte_array_no_pad(utc_time.minute, mut buf, 16)
int_to_byte_array_no_pad(utc_time.second, mut buf, 19)
int_to_byte_array_no_pad(utc_time.nanosecond / 1_000_000, mut buf, 23)
} else {
int_to_byte_array_no_pad(t.year, mut buf, 4)
int_to_byte_array_no_pad(t.month, mut buf, 7)
int_to_byte_array_no_pad(t.day, mut buf, 10)
int_to_byte_array_no_pad(t.hour, mut buf, 13)
int_to_byte_array_no_pad(t.minute, mut buf, 16)
int_to_byte_array_no_pad(t.second, mut buf, 19)
int_to_byte_array_no_pad(t.nanosecond / 1_000_000, mut buf, 23)
}
return buf.bytestr()
} }
// format_rfc3339_nano returns a date string in "YYYY-MM-DDTHH:mm:ss.123456789Z" format (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html) // format_rfc3339_nano returns a date string in "YYYY-MM-DDTHH:mm:ss.123456789Z" format (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html)
@[manualfree]
pub fn (t Time) format_rfc3339_nano() string { pub fn (t Time) format_rfc3339_nano() string {
u := t.local_to_utc() mut buf := [u8(`0`), `0`, `0`, `0`, `-`, `0`, `0`, `-`, `0`, `0`, `T`, `0`, `0`, `:`, `0`,
return '${u.year:04d}-${u.month:02d}-${u.day:02d}T${u.hour:02d}:${u.minute:02d}:${u.second:02d}.${(u.nanosecond):09d}Z' `0`, `:`, `0`, `0`, `.`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `Z`]
defer {
unsafe { buf.free() }
}
if t.unix == 0 && t.nanosecond == 0 {
return buf.bytestr()
}
if t.is_local {
utc_time := t.local_to_utc()
int_to_byte_array_no_pad(utc_time.year, mut buf, 4)
int_to_byte_array_no_pad(utc_time.month, mut buf, 7)
int_to_byte_array_no_pad(utc_time.day, mut buf, 10)
int_to_byte_array_no_pad(utc_time.hour, mut buf, 13)
int_to_byte_array_no_pad(utc_time.minute, mut buf, 16)
int_to_byte_array_no_pad(utc_time.second, mut buf, 19)
int_to_byte_array_no_pad(utc_time.nanosecond, mut buf, 29)
} else {
int_to_byte_array_no_pad(t.year, mut buf, 4)
int_to_byte_array_no_pad(t.month, mut buf, 7)
int_to_byte_array_no_pad(t.day, mut buf, 10)
int_to_byte_array_no_pad(t.hour, mut buf, 13)
int_to_byte_array_no_pad(t.minute, mut buf, 16)
int_to_byte_array_no_pad(t.second, mut buf, 19)
int_to_byte_array_no_pad(t.nanosecond, mut buf, 29)
}
return buf.bytestr()
} }
// hhmm returns a date string in "HH:mm" format (24h). // hhmm returns a date string in "HH:mm" format (24h).
@[manualfree]
pub fn (t Time) hhmm() string { pub fn (t Time) hhmm() string {
return '${t.hour:02d}:${t.minute:02d}' mut buf := [u8(`0`), `0`, `:`, `0`, `0`]
defer {
unsafe { buf.free() }
}
int_to_byte_array_no_pad(t.hour, mut buf, 2)
int_to_byte_array_no_pad(t.minute, mut buf, 5)
return buf.bytestr()
} }
// hhmmss returns a date string in "HH:mm:ss" format (24h). // hhmmss returns a date string in "HH:mm:ss" format (24h).
@[manualfree]
pub fn (t Time) hhmmss() string { pub fn (t Time) hhmmss() string {
return '${t.hour:02d}:${t.minute:02d}:${t.second:02d}' mut buf := [u8(`0`), `0`, `:`, `0`, `0`, `:`, `0`, `0`]
defer {
unsafe { buf.free() }
}
int_to_byte_array_no_pad(t.hour, mut buf, 2)
int_to_byte_array_no_pad(t.minute, mut buf, 5)
int_to_byte_array_no_pad(t.second, mut buf, 8)
return buf.bytestr()
} }
// hhmm12 returns a date string in "hh:mm" format (12h). // hhmm12 returns a date string in "hh:mm" format (12h).
@ -75,6 +264,7 @@ pub fn (t Time) md() string {
return t.get_fmt_date_str(.space, .mmmd) return t.get_fmt_date_str(.space, .mmmd)
} }
// TODO test, improve performance
// appends ordinal suffix to a number // appends ordinal suffix to a number
fn ordinal_suffix(n int) string { fn ordinal_suffix(n int) string {
if n > 3 && n < 21 { if n > 3 && n < 21 {

View File

@ -1,7 +1,7 @@
import time import time
import math import math
const time_to_test = time.Time{ const local_time_to_test = time.Time{
year: 1980 year: 1980
month: 7 month: 7
day: 11 day: 11
@ -10,6 +10,19 @@ const time_to_test = time.Time{
second: 42 second: 42
nanosecond: 123456789 nanosecond: 123456789
unix: 332198622 unix: 332198622
is_local: true
}
const utc_time_to_test = time.Time{
year: 1980
month: 7
day: 11
hour: 21
minute: 23
second: 42
nanosecond: 123456789
unix: 332198622
is_local: false
} }
fn test_is_leap_year() { fn test_is_leap_year() {
@ -83,39 +96,64 @@ fn test_unix() {
fn test_format_rfc3339() { fn test_format_rfc3339() {
// assert '1980-07-11T19:23:42.123Z' // assert '1980-07-11T19:23:42.123Z'
res := time_to_test.format_rfc3339() res := local_time_to_test.format_rfc3339()
assert res.ends_with('23:42.123Z') assert res.ends_with('23:42.123Z')
assert res.starts_with('1980-07-1') assert res.starts_with('1980-07-1')
assert res.contains('T') assert res.contains('T')
// assert '1980-07-11T19:23:42.123Z'
utc_res := utc_time_to_test.format_rfc3339()
assert utc_res.ends_with('23:42.123Z')
assert utc_res.starts_with('1980-07-1')
assert utc_res.contains('T')
} }
fn test_format_rfc3339_nano() { fn test_format_rfc3339_nano() {
res := time_to_test.format_rfc3339_nano() res := local_time_to_test.format_rfc3339_nano()
assert res.ends_with('23:42.123456789Z') assert res.ends_with('23:42.123456789Z')
assert res.starts_with('1980-07-1') assert res.starts_with('1980-07-1')
assert res.contains('T') assert res.contains('T')
utc_res := utc_time_to_test.format_rfc3339_nano()
assert utc_res.ends_with('23:42.123456789Z')
assert utc_res.starts_with('1980-07-1')
assert utc_res.contains('T')
} }
fn test_format_ss() { fn test_format_ss() {
assert '11.07.1980 21:23:42' == time_to_test.get_fmt_str(.dot, .hhmmss24, .ddmmyyyy) assert '11.07.1980 21:23:42' == local_time_to_test.get_fmt_str(.dot, .hhmmss24, .ddmmyyyy)
assert '11.07.1980 21:23:42' == utc_time_to_test.get_fmt_str(.dot, .hhmmss24, .ddmmyyyy)
} }
fn test_format_ss_milli() { fn test_format_ss_milli() {
assert '11.07.1980 21:23:42.123' == time_to_test.get_fmt_str(.dot, .hhmmss24_milli, assert '11.07.1980 21:23:42.123' == local_time_to_test.get_fmt_str(.dot, .hhmmss24_milli,
.ddmmyyyy) .ddmmyyyy)
assert '1980-07-11 21:23:42.123' == time_to_test.format_ss_milli() assert '1980-07-11 21:23:42.123' == local_time_to_test.format_ss_milli()
assert '11.07.1980 21:23:42.123' == utc_time_to_test.get_fmt_str(.dot, .hhmmss24_milli,
.ddmmyyyy)
assert '1980-07-11 21:23:42.123' == utc_time_to_test.format_ss_milli()
} }
fn test_format_ss_micro() { fn test_format_ss_micro() {
assert '11.07.1980 21:23:42.123456' == time_to_test.get_fmt_str(.dot, .hhmmss24_micro, assert '11.07.1980 21:23:42.123456' == local_time_to_test.get_fmt_str(.dot, .hhmmss24_micro,
.ddmmyyyy) .ddmmyyyy)
assert '1980-07-11 21:23:42.123456' == time_to_test.format_ss_micro() assert '1980-07-11 21:23:42.123456' == local_time_to_test.format_ss_micro()
assert '11.07.1980 21:23:42.123456' == utc_time_to_test.get_fmt_str(.dot, .hhmmss24_micro,
.ddmmyyyy)
assert '1980-07-11 21:23:42.123456' == utc_time_to_test.format_ss_micro()
} }
fn test_format_ss_nano() { fn test_format_ss_nano() {
assert '11.07.1980 21:23:42.123456789' == time_to_test.get_fmt_str(.dot, .hhmmss24_nano, assert '11.07.1980 21:23:42.123456789' == local_time_to_test.get_fmt_str(.dot, .hhmmss24_nano,
.ddmmyyyy) .ddmmyyyy)
assert '1980-07-11 21:23:42.123456789' == time_to_test.format_ss_nano() assert '1980-07-11 21:23:42.123456789' == local_time_to_test.format_ss_nano()
assert '11.07.1980 21:23:42.123456789' == utc_time_to_test.get_fmt_str(.dot, .hhmmss24_nano,
.ddmmyyyy)
assert '1980-07-11 21:23:42.123456789' == utc_time_to_test.format_ss_nano()
} }
fn test_smonth() { fn test_smonth() {
@ -194,23 +232,23 @@ fn test_add() {
d_nanoseconds := 13 d_nanoseconds := 13
duration := time.Duration(d_seconds * time.second + d_nanoseconds * time.nanosecond) duration := time.Duration(d_seconds * time.second + d_nanoseconds * time.nanosecond)
// dump(duration.debug()) // dump(duration.debug())
t1 := time_to_test t1 := local_time_to_test
// dump(t1.debug()) // dump(t1.debug())
t2 := time_to_test.add(duration) t2 := local_time_to_test.add(duration)
// dump(t2.debug()) // dump(t2.debug())
assert t2.second == t1.second + d_seconds assert t2.second == t1.second + d_seconds
assert t2.nanosecond == t1.nanosecond + d_nanoseconds assert t2.nanosecond == t1.nanosecond + d_nanoseconds
assert t2.unix == t1.unix + d_seconds assert t2.unix == t1.unix + d_seconds
assert t2.is_local == t1.is_local assert t2.is_local == t1.is_local
// //
t3 := time_to_test.add(-duration) t3 := local_time_to_test.add(-duration)
// dump(t3.debug()) // dump(t3.debug())
assert t3.second == t1.second - d_seconds assert t3.second == t1.second - d_seconds
assert t3.nanosecond == t1.nanosecond - d_nanoseconds assert t3.nanosecond == t1.nanosecond - d_nanoseconds
assert t3.unix == t1.unix - d_seconds assert t3.unix == t1.unix - d_seconds
assert t3.is_local == t1.is_local assert t3.is_local == t1.is_local
// //
t4 := time_to_test.as_local() t4 := local_time_to_test.as_local()
// dump(t4.debug()) // dump(t4.debug())
t5 := t4.add(duration) t5 := t4.add(duration)
// dump(t5.debug()) // dump(t5.debug())
@ -219,13 +257,15 @@ fn test_add() {
fn test_add_days() { fn test_add_days() {
num_of_days := 3 num_of_days := 3
t := time_to_test.add_days(num_of_days) t := local_time_to_test.add_days(num_of_days)
assert t.day == time_to_test.day + num_of_days assert t.day == local_time_to_test.day + num_of_days
assert t.unix == time_to_test.unix + 86400 * num_of_days assert t.unix == local_time_to_test.unix + 86400 * num_of_days
} }
fn test_str() { fn test_str() {
assert '1980-07-11 21:23:42' == time_to_test.str() assert '1980-07-11 21:23:42' == local_time_to_test.str()
assert '1980-07-11 21:23:42' == utc_time_to_test.str()
} }
// not optimal test but will find obvious bugs // not optimal test but will find obvious bugs
@ -322,7 +362,9 @@ fn test_recursive_local_call() {
} }
fn test_strftime() { fn test_strftime() {
assert '1980 July 11' == time_to_test.strftime('%Y %B %d') assert '1980 July 11' == local_time_to_test.strftime('%Y %B %d')
assert '1980 July 11' == utc_time_to_test.strftime('%Y %B %d')
} }
fn test_add_seconds_to_time() { fn test_add_seconds_to_time() {

View File

@ -86,7 +86,12 @@ fn benchmark_measure_encode_by_type() ! {
println(@FN) println(@FN)
dump('👈') dump('👈')
measure_json_encode_old_vs_new(StructType[string]{})! measure_json_encode_old_vs_new(StructType[string]{})!
println('time.Time]{}')
measure_json_encode_old_vs_new(StructType[time.Time]{})! measure_json_encode_old_vs_new(StructType[time.Time]{})!
println('time.utc()')
measure_json_encode_old_vs_new(StructType[time.Time]{time.utc()})!
println('time.now()')
measure_json_encode_old_vs_new(StructType[time.Time]{time.now()})!
measure_json_encode_old_vs_new(StructType[int]{})! measure_json_encode_old_vs_new(StructType[int]{})!
measure_json_encode_old_vs_new(StructType[f64]{})! measure_json_encode_old_vs_new(StructType[f64]{})!
measure_json_encode_old_vs_new(StructType[bool]{})! measure_json_encode_old_vs_new(StructType[bool]{})!