mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 03:52:35 +01:00
MDEV-23568 Improve performance of my_{time|date|datetime}_to_str()
This patch improves functions my_xxx_to_str() using the idea introduced in this change in MySQL-8.0: commit 8d10f2fff6bbdea7f436b868ebb5fd811defc68a Author: Knut Anders Hatlen <knut.hatlen@oracle.com> Date: Thu Oct 10 13:55:07 2019 +0200 Bug#30472888: IMPROVE THE PERFORMANCE OF INTEGER HANDLING IN THE TEXT PROTOCOL The new way prints 2 digits at a time and demonstrates a very impressing query time reduce: 10% to 38%, depending on the exact data type and the number of fractional digits: SELECT BENCHMARK(10*1000*1000,CONCAT(TIME'10:20:30')); SELECT BENCHMARK(10*1000*1000,CONCAT(TIME'10:20:30.123456')); SELECT BENCHMARK(10*1000*1000,CONCAT(DATE'2001-01-01')); SELECT BENCHMARK(10*1000*1000,CONCAT(TIMESTAMP'2001-01-01 10:20:30')); SELECT BENCHMARK(10*1000*1000,CONCAT(TIMESTAMP'2001-01-01 10:20:30.123456')); See MDEV for details on the benchmark results.
This commit is contained in:
parent
482cf29e16
commit
c14ecc7500
1 changed files with 149 additions and 24 deletions
|
@ -1454,23 +1454,148 @@ void set_zero_time(MYSQL_TIME *tm, enum enum_mysql_timestamp_type time_type)
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Helper function for datetime formatting.
|
A formatting routine to print a 2 digit zero padded number.
|
||||||
Format number as string, left-padded with 0.
|
It prints 2 digits at a time, which gives a performance improvement.
|
||||||
|
The idea is taken from "class TwoDigitWriter" in MySQL.
|
||||||
|
|
||||||
The reason to use own formatting rather than sprintf() is performance - in a
|
The old implementation printed one digit at a time, using the division
|
||||||
datetime benchmark it helped to reduced the datetime formatting overhead
|
and the remainder operators, which appeared to be slow.
|
||||||
from ~30% down to ~4%.
|
It's cheaper to have a cached array of 2-digit numbers
|
||||||
|
in their string representation.
|
||||||
|
|
||||||
|
Benchmark results showed a 10% to 23% time reduce for these queries:
|
||||||
|
SELECT BENCHMARK(10*1000*1000,CONCAT(TIME'10:20:30'));
|
||||||
|
SELECT BENCHMARK(10*1000*1000,CONCAT(DATE'2001-01-01'));
|
||||||
|
SELECT BENCHMARK(10*1000*1000,CONCAT(TIMESTAMP'2001-01-01 10:20:30'));
|
||||||
|
SELECT BENCHMARK(10*1000*1000,CONCAT(TIME'10:20:30.123456'));
|
||||||
|
SELECT BENCHMARK(10*1000*1000,CONCAT(TIMESTAMP'2001-01-01 10:20:30.123456'));
|
||||||
|
(depending on the exact data type and fractional precision).
|
||||||
|
|
||||||
|
The array has extra elements for uint8 values 100..255.
|
||||||
|
This is done for safety. If the caller passes a value
|
||||||
|
outside of the expected range 0..99, it will be printed as "XX".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static char* fmt_number(uint val, char *out, uint digits)
|
static const char two_digit_numbers[512+1]=
|
||||||
{
|
{
|
||||||
uint i;
|
/* 0..99 */
|
||||||
for(i= 0; i < digits; i++)
|
"00010203040506070809"
|
||||||
|
"10111213141516171819"
|
||||||
|
"20212223242526272829"
|
||||||
|
"30313233343536373839"
|
||||||
|
"40414243444546474849"
|
||||||
|
"50515253545556575859"
|
||||||
|
"60616263646566676869"
|
||||||
|
"70717273747576777879"
|
||||||
|
"80818283848586878889"
|
||||||
|
"90919293949596979899"
|
||||||
|
/* 100..199 - safety */
|
||||||
|
"XXXXXXXXXXXXXXXXXXXX"
|
||||||
|
"XXXXXXXXXXXXXXXXXXXX"
|
||||||
|
"XXXXXXXXXXXXXXXXXXXX"
|
||||||
|
"XXXXXXXXXXXXXXXXXXXX"
|
||||||
|
"XXXXXXXXXXXXXXXXXXXX"
|
||||||
|
"XXXXXXXXXXXXXXXXXXXX"
|
||||||
|
"XXXXXXXXXXXXXXXXXXXX"
|
||||||
|
"XXXXXXXXXXXXXXXXXXXX"
|
||||||
|
"XXXXXXXXXXXXXXXXXXXX"
|
||||||
|
"XXXXXXXXXXXXXXXXXXXX"
|
||||||
|
/* 200..255 - safety */
|
||||||
|
"XXXXXXXXXXXXXXXXXXXX"
|
||||||
|
"XXXXXXXXXXXXXXXXXXXX"
|
||||||
|
"XXXXXXXXXXXXXXXXXXXX"
|
||||||
|
"XXXXXXXXXXXXXXXXXXXX"
|
||||||
|
"XXXXXXXXXXXXXXXXXXXX"
|
||||||
|
"XXXXXXXXXXXX"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static inline char* fmt_number2(uint8 val, char *out)
|
||||||
|
{
|
||||||
|
const char *src= two_digit_numbers + val * 2;
|
||||||
|
*out++= *src++;
|
||||||
|
*out++= *src++;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
We tried the same trick with an array of 16384 zerofill 4-digit numbers,
|
||||||
|
with 10000 elements with numbers 0000..9999, and a tail filled with "XXXX".
|
||||||
|
|
||||||
|
Benchmark results for a RelWithDebInfo build:
|
||||||
|
|
||||||
|
SELECT BENCHMARK(10*1000*1000,CONCAT(TIMESTAMP'2001-01-01 10:20:30.123456'));
|
||||||
|
- 0.379 sec (current)
|
||||||
|
- 0.369 sec (array)
|
||||||
|
|
||||||
|
SELECT BENCHMARK(10*1000*1000,CONCAT(DATE'2001-01-01'));
|
||||||
|
- 0.225 sec (current)
|
||||||
|
- 0.219 sec (array)
|
||||||
|
|
||||||
|
It demonstrated an additional 3% performance imrovement one these queries.
|
||||||
|
However, as the array size is too huge, we afraid that it will flush data
|
||||||
|
from the CPU memory cache, which under real load may affect negatively.
|
||||||
|
|
||||||
|
Let's keep using the fmt_number4() version with division and remainder
|
||||||
|
for now. This can be revised later. We could try some smaller array,
|
||||||
|
e.g. for YEARs in the range 1970..2098 (fitting into a 256 element array).
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
static inline char* fmt_number4(uint16 val, char *out)
|
||||||
|
{
|
||||||
|
const char *src= four_digit_numbers + (val & 0x3FFF) * 4;
|
||||||
|
memcpy(out, src, 4);
|
||||||
|
return out + 4;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
A formatting routine to print a 4 digit zero padded number.
|
||||||
|
*/
|
||||||
|
static inline char* fmt_number4(uint16 val, char *out)
|
||||||
|
{
|
||||||
|
out= fmt_number2((uint8) (val / 100), out);
|
||||||
|
out= fmt_number2((uint8) (val % 100), out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
A formatting routine to print a 6 digit zero padded number.
|
||||||
|
*/
|
||||||
|
static inline char* fmt_number6(uint val, char *out)
|
||||||
|
{
|
||||||
|
out= fmt_number2((uint8) (val / 10000), out);
|
||||||
|
val%= 10000;
|
||||||
|
out= fmt_number2((uint8) (val / 100), out);
|
||||||
|
out= fmt_number2((uint8) (val % 100), out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static char* fmt_usec(uint val, char *out, uint digits)
|
||||||
|
{
|
||||||
|
switch (digits)
|
||||||
{
|
{
|
||||||
out[digits-i-1]= '0' + val%10;
|
case 1:
|
||||||
val/=10;
|
*out++= '0' + (val % 10);
|
||||||
|
return out;
|
||||||
|
case 2:
|
||||||
|
return fmt_number2((uint8) val, out);
|
||||||
|
case 3:
|
||||||
|
*out++= '0' + (val / 100) % 10;
|
||||||
|
return fmt_number2((uint8) (val % 100), out);
|
||||||
|
case 4:
|
||||||
|
return fmt_number4((uint16) val, out);
|
||||||
|
case 5:
|
||||||
|
*out++= '0' + (val / 10000) % 10;
|
||||||
|
return fmt_number4((uint16) (val % 10000), out);
|
||||||
|
case 6:
|
||||||
|
return fmt_number6(val, out);
|
||||||
}
|
}
|
||||||
return out + digits;
|
DBUG_ASSERT(0);
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1480,13 +1605,13 @@ static int my_mmssff_to_str(const MYSQL_TIME *ltime, char *to, uint fsp)
|
||||||
if (fsp == AUTO_SEC_PART_DIGITS)
|
if (fsp == AUTO_SEC_PART_DIGITS)
|
||||||
fsp= ltime->second_part ? TIME_SECOND_PART_DIGITS : 0;
|
fsp= ltime->second_part ? TIME_SECOND_PART_DIGITS : 0;
|
||||||
DBUG_ASSERT(fsp <= TIME_SECOND_PART_DIGITS);
|
DBUG_ASSERT(fsp <= TIME_SECOND_PART_DIGITS);
|
||||||
pos= fmt_number(ltime->minute, pos, 2);
|
pos= fmt_number2((uint8) ltime->minute, pos);
|
||||||
*pos++= ':';
|
*pos++= ':';
|
||||||
pos= fmt_number(ltime->second, pos, 2);
|
pos= fmt_number2((uint8) ltime->second, pos);
|
||||||
if (fsp)
|
if (fsp)
|
||||||
{
|
{
|
||||||
*pos++= '.';
|
*pos++= '.';
|
||||||
pos= fmt_number((uint)sec_part_shift(ltime->second_part, fsp), pos, fsp);
|
pos= fmt_usec((uint)sec_part_shift(ltime->second_part, fsp), pos, fsp);
|
||||||
}
|
}
|
||||||
return (int) (pos - to);
|
return (int) (pos - to);
|
||||||
}
|
}
|
||||||
|
@ -1506,7 +1631,7 @@ int my_interval_DDhhmmssff_to_str(const MYSQL_TIME *ltime, char *to, uint fsp)
|
||||||
pos= longlong10_to_str((longlong) hour / 24, pos, 10);
|
pos= longlong10_to_str((longlong) hour / 24, pos, 10);
|
||||||
*pos++= ' ';
|
*pos++= ' ';
|
||||||
}
|
}
|
||||||
pos= fmt_number(hour % 24, pos, 2);
|
pos= fmt_number2((uint8) (hour % 24), pos);
|
||||||
*pos++= ':';
|
*pos++= ':';
|
||||||
pos+= my_mmssff_to_str(ltime, pos, fsp);
|
pos+= my_mmssff_to_str(ltime, pos, fsp);
|
||||||
*pos= 0;
|
*pos= 0;
|
||||||
|
@ -1538,7 +1663,7 @@ int my_time_to_str(const MYSQL_TIME *l_time, char *to, uint digits)
|
||||||
/* Need more than 2 digits for hours in string representation. */
|
/* Need more than 2 digits for hours in string representation. */
|
||||||
pos= longlong10_to_str((longlong)hour, pos, 10);
|
pos= longlong10_to_str((longlong)hour, pos, 10);
|
||||||
else
|
else
|
||||||
pos= fmt_number(hour, pos, 2);
|
pos= fmt_number2((uint8) hour, pos);
|
||||||
|
|
||||||
*pos++= ':';
|
*pos++= ':';
|
||||||
pos+= my_mmssff_to_str(l_time, pos, digits);
|
pos+= my_mmssff_to_str(l_time, pos, digits);
|
||||||
|
@ -1550,11 +1675,11 @@ int my_time_to_str(const MYSQL_TIME *l_time, char *to, uint digits)
|
||||||
int my_date_to_str(const MYSQL_TIME *l_time, char *to)
|
int my_date_to_str(const MYSQL_TIME *l_time, char *to)
|
||||||
{
|
{
|
||||||
char *pos=to;
|
char *pos=to;
|
||||||
pos= fmt_number(l_time->year, pos, 4);
|
pos= fmt_number4((uint16) l_time->year, pos);
|
||||||
*pos++='-';
|
*pos++='-';
|
||||||
pos= fmt_number(l_time->month, pos, 2);
|
pos= fmt_number2((uint8) l_time->month, pos);
|
||||||
*pos++='-';
|
*pos++='-';
|
||||||
pos= fmt_number(l_time->day, pos, 2);
|
pos= fmt_number2((uint8) l_time->day, pos);
|
||||||
*pos= 0;
|
*pos= 0;
|
||||||
return (int)(pos - to);
|
return (int)(pos - to);
|
||||||
}
|
}
|
||||||
|
@ -1563,13 +1688,13 @@ int my_date_to_str(const MYSQL_TIME *l_time, char *to)
|
||||||
int my_datetime_to_str(const MYSQL_TIME *l_time, char *to, uint digits)
|
int my_datetime_to_str(const MYSQL_TIME *l_time, char *to, uint digits)
|
||||||
{
|
{
|
||||||
char *pos= to;
|
char *pos= to;
|
||||||
pos= fmt_number(l_time->year, pos, 4);
|
pos= fmt_number4((uint16) l_time->year, pos);
|
||||||
*pos++='-';
|
*pos++='-';
|
||||||
pos= fmt_number(l_time->month, pos, 2);
|
pos= fmt_number2((uint8) l_time->month, pos);
|
||||||
*pos++='-';
|
*pos++='-';
|
||||||
pos= fmt_number(l_time->day, pos, 2);
|
pos= fmt_number2((uint8) l_time->day, pos);
|
||||||
*pos++=' ';
|
*pos++=' ';
|
||||||
pos= fmt_number(l_time->hour, pos, 2);
|
pos= fmt_number2((uint8) l_time->hour, pos);
|
||||||
*pos++= ':';
|
*pos++= ':';
|
||||||
pos+= my_mmssff_to_str(l_time, pos, digits);
|
pos+= my_mmssff_to_str(l_time, pos, digits);
|
||||||
*pos= 0;
|
*pos= 0;
|
||||||
|
@ -1625,7 +1750,7 @@ int my_timeval_to_str(const struct timeval *tm, char *to, uint dec)
|
||||||
if (dec)
|
if (dec)
|
||||||
{
|
{
|
||||||
*pos++= '.';
|
*pos++= '.';
|
||||||
pos= fmt_number((uint) sec_part_shift(tm->tv_usec, dec), pos, dec);
|
pos= fmt_usec((uint) sec_part_shift(tm->tv_usec, dec), pos, dec);
|
||||||
}
|
}
|
||||||
*pos= '\0';
|
*pos= '\0';
|
||||||
return (int) (pos - to);
|
return (int) (pos - to);
|
||||||
|
|
Loading…
Reference in a new issue