Skip to content

Commit 1dec2d2

Browse files
committed
Bug#35916912 Performance degradation from 8.0.30 onwards related to performance_schema
This fix reduces performance overhead of the statement instrumentation, in the performance_schema implementation. Problem ======= 1) The new g_telemetry global atomic pointer is not protected against false sharing on the CPU cache line. 2) The statement instrumentation makes unnecessary copies of MESSAGE_TEXT, which is using 512 bytes, and is most of the time empty. Fix === 1) Declared g_telemetry using a dedicated cache line, to avoid false sharing. This helps because this atomic is expected to be "read only", and will not change during the server lifetime except when installing or uninstalling a telemetry component. Keeping a shared copy in each CPU cache should reduce the atomic overhead. 2) The diagnostics area has been fixed to keep track of the message text length. The performance schema instrumentation now only copies the message text when it is not empty, saving overhead in memcpy(). The data layout in events_statements_current / history / history_long has changed to improve data locality for other attributes, and also reduce overhead in memcpy(). Change-Id: I6a80af3547e98eccbb1e39c806f603462b665cef
1 parent 8fea1c0 commit 1dec2d2

11 files changed

+87
-22
lines changed

sql/sql_error.cc

+8-2
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ Diagnostics_area::Diagnostics_area(bool allow_unlimited_conditions)
347347
memset(m_current_statement_cond_count_by_qb, 0,
348348
sizeof(m_current_statement_cond_count_by_qb));
349349
m_message_text[0] = '\0';
350+
m_message_text_length = 0;
350351
}
351352

352353
Diagnostics_area::~Diagnostics_area() {}
@@ -357,6 +358,7 @@ void Diagnostics_area::reset_diagnostics_area() {
357358
set_overwrite_status(false);
358359
// Don't take chances in production.
359360
m_message_text[0] = '\0';
361+
m_message_text_length = 0;
360362
m_mysql_errno = 0;
361363
m_affected_rows = 0;
362364
m_last_insert_id = 0;
@@ -381,10 +383,13 @@ void Diagnostics_area::set_ok_status(ulonglong affected_rows,
381383
m_last_statement_cond_count = current_statement_cond_count();
382384
m_affected_rows = affected_rows;
383385
m_last_insert_id = last_insert_id;
384-
if (message_text)
386+
if (message_text) {
385387
strmake(m_message_text, message_text, sizeof(m_message_text) - 1);
386-
else
388+
m_message_text_length = strlen(m_message_text);
389+
} else {
387390
m_message_text[0] = '\0';
391+
m_message_text_length = 0;
392+
}
388393
m_status = DA_OK;
389394
}
390395

@@ -443,6 +448,7 @@ void Diagnostics_area::set_error_status(uint mysql_errno,
443448
memcpy(m_returned_sqlstate, returned_sqlstate, SQLSTATE_LENGTH);
444449
m_returned_sqlstate[SQLSTATE_LENGTH] = '\0';
445450
strmake(m_message_text, message_text, sizeof(m_message_text) - 1);
451+
m_message_text_length = strlen(m_message_text);
446452

447453
m_status = DA_ERROR;
448454
}

sql/sql_error.h

+10
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,11 @@ class Diagnostics_area {
377377
return m_message_text;
378378
}
379379

380+
uint message_text_length() const {
381+
assert(m_status == DA_ERROR || m_status == DA_OK);
382+
return m_message_text_length;
383+
}
384+
380385
uint mysql_errno() const {
381386
assert(m_status == DA_ERROR);
382387
return m_mysql_errno;
@@ -624,6 +629,11 @@ class Diagnostics_area {
624629
*/
625630
char m_message_text[MYSQL_ERRMSG_SIZE];
626631

632+
/**
633+
Length, in bytes, of m_message_text.
634+
*/
635+
uint m_message_text_length;
636+
627637
/**
628638
SQL RETURNED_SQLSTATE condition item.
629639
This member is always NUL terminated.

storage/perfschema/mysql_server_telemetry_traces_service_imp.cc

+6-6
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ bool server_telemetry_traces_service_initialized = false;
199199
#endif /* HAVE_PSI_SERVER_TELEMETRY_TRACES_INTERFACE */
200200

201201
// currently registered collection of telemetry trace callbacks
202-
std::atomic<telemetry_t *> g_telemetry = nullptr;
202+
PFS_ALIGNED PFS_cacheline_atomic_ptr<telemetry_t *> g_telemetry;
203203

204204
// locking for callback register/unregister
205205
mysql_mutex_t LOCK_pfs_tracing_callback;
@@ -213,7 +213,7 @@ static PSI_mutex_info info_LOCK_pfs_tracing_callback = {
213213

214214
void initialize_mysql_server_telemetry_traces_service() {
215215
#ifdef HAVE_PSI_SERVER_TELEMETRY_TRACES_INTERFACE
216-
g_telemetry = nullptr;
216+
g_telemetry.m_ptr = nullptr;
217217

218218
assert(!server_telemetry_traces_service_initialized);
219219

@@ -231,7 +231,7 @@ void cleanup_mysql_server_telemetry_traces_service() {
231231
mysql_mutex_destroy(&LOCK_pfs_tracing_callback);
232232
server_telemetry_traces_service_initialized = false;
233233
}
234-
g_telemetry = nullptr;
234+
g_telemetry.m_ptr = nullptr;
235235
#endif /* HAVE_PSI_SERVER_TELEMETRY_TRACES_INTERFACE */
236236
}
237237

@@ -254,7 +254,7 @@ bool impl_register_telemetry(telemetry_t *telemetry [[maybe_unused]]) {
254254
// telemetry available, if we would need to uninstall previous component using
255255
// this before installing new one
256256
mysql_mutex_lock(&LOCK_pfs_tracing_callback);
257-
g_telemetry = telemetry;
257+
g_telemetry.m_ptr = telemetry;
258258
mysql_mutex_unlock(&LOCK_pfs_tracing_callback);
259259
// Success
260260
return false;
@@ -268,8 +268,8 @@ bool impl_unregister_telemetry(telemetry_t *telemetry [[maybe_unused]]) {
268268
#ifdef HAVE_PSI_SERVER_TELEMETRY_TRACES_INTERFACE
269269
if (!server_telemetry_traces_service_initialized) return true;
270270
mysql_mutex_lock(&LOCK_pfs_tracing_callback);
271-
if (g_telemetry == telemetry) {
272-
g_telemetry = nullptr;
271+
if (g_telemetry.m_ptr == telemetry) {
272+
g_telemetry.m_ptr = nullptr;
273273
mysql_mutex_unlock(&LOCK_pfs_tracing_callback);
274274
// Success
275275
return false;

storage/perfschema/mysql_server_telemetry_traces_service_imp.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
#include <mysql/components/services/mysql_server_telemetry_traces_service.h>
2727
#include <mysql/plugin.h>
2828

29+
#include "pfs_global.h"
30+
2931
/**
3032
@file storage/perfschema/mysql_server_telemetry_traces_service_imp.h
3133
The performance schema implementation of server telemetry traces service.
@@ -45,7 +47,7 @@ bool impl_unregister_telemetry(telemetry_t *telemetry);
4547

4648
extern mysql_mutex_t LOCK_pfs_tracing_callback;
4749
#ifdef HAVE_PSI_SERVER_TELEMETRY_TRACES_INTERFACE
48-
extern std::atomic<telemetry_t *> g_telemetry;
50+
extern PFS_cacheline_atomic_ptr<telemetry_t *> g_telemetry;
4951
#endif /* HAVE_PSI_SERVER_TELEMETRY_TRACES_INTERFACE */
5052

5153
#endif /* MYSQL_SERVER_TELEMETRY_TRACES_SERVICE_IMP_H */

storage/perfschema/pfs.cc

+20-6
Original file line numberDiff line numberDiff line change
@@ -3672,14 +3672,14 @@ void pfs_detect_telemetry_vc(PSI_thread *thread [[maybe_unused]]) {
36723672
assert(pfs_thread != nullptr);
36733673

36743674
// Dirty read
3675-
telemetry_t *actual_telemetry = g_telemetry.load();
3675+
telemetry_t *actual_telemetry = g_telemetry.m_ptr.load();
36763676

36773677
telemetry_t *expected_telemetry = pfs_thread->m_telemetry;
36783678

36793679
if (actual_telemetry != expected_telemetry) {
36803680
server_telemetry_tracing_lock();
36813681
// Safe read
3682-
actual_telemetry = g_telemetry.load();
3682+
actual_telemetry = g_telemetry.m_ptr.load();
36833683
if (actual_telemetry != expected_telemetry) {
36843684
if (expected_telemetry == nullptr) {
36853685
pfs_thread->m_telemetry = actual_telemetry;
@@ -6608,6 +6608,7 @@ void pfs_start_statement_vc(PSI_statement_locker *locker, const char *db,
66086608
pfs->m_sqltext_cs_number = system_charset_info->number; /* default */
66096609

66106610
pfs->m_message_text[0] = '\0';
6611+
pfs->m_message_text_length = 0;
66116612
pfs->m_sql_errno = 0;
66126613
pfs->m_sqlstate[0] = '\0';
66136614
pfs->m_error_count = 0;
@@ -6957,15 +6958,22 @@ void pfs_end_statement_vc(PSI_statement_locker *locker, void *stmt_da) {
69576958
reinterpret_cast<PFS_events_statements *>(state->m_statement);
69586959
assert(pfs != nullptr);
69596960

6961+
size_t message_text_length;
69606962
pfs_dirty_state dirty_state;
69616963
thread->m_stmt_lock.allocated_to_dirty(&dirty_state);
69626964

69636965
switch (da->status()) {
69646966
case Diagnostics_area::DA_EMPTY:
69656967
break;
69666968
case Diagnostics_area::DA_OK:
6967-
memcpy(pfs->m_message_text, da->message_text(), MYSQL_ERRMSG_SIZE);
6968-
pfs->m_message_text[MYSQL_ERRMSG_SIZE] = 0;
6969+
message_text_length = da->message_text_length();
6970+
if (message_text_length > 0) {
6971+
memcpy(pfs->m_message_text, da->message_text(),
6972+
message_text_length);
6973+
}
6974+
pfs->m_message_text[message_text_length] = '\0';
6975+
pfs->m_message_text_length = message_text_length;
6976+
69696977
pfs->m_rows_affected = da->affected_rows();
69706978
pfs->m_warning_count = da->last_statement_cond_count();
69716979
memcpy(pfs->m_sqlstate, "00000", SQLSTATE_LENGTH);
@@ -6974,8 +6982,14 @@ void pfs_end_statement_vc(PSI_statement_locker *locker, void *stmt_da) {
69746982
pfs->m_warning_count = da->last_statement_cond_count();
69756983
break;
69766984
case Diagnostics_area::DA_ERROR:
6977-
memcpy(pfs->m_message_text, da->message_text(), MYSQL_ERRMSG_SIZE);
6978-
pfs->m_message_text[MYSQL_ERRMSG_SIZE] = 0;
6985+
message_text_length = da->message_text_length();
6986+
if (message_text_length > 0) {
6987+
memcpy(pfs->m_message_text, da->message_text(),
6988+
message_text_length);
6989+
}
6990+
pfs->m_message_text[message_text_length] = '\0';
6991+
pfs->m_message_text_length = message_text_length;
6992+
69796993
pfs->m_sql_errno = da->mysql_errno();
69806994
memcpy(pfs->m_sqlstate, da->returned_sqlstate(), SQLSTATE_LENGTH);
69816995
pfs->m_error_count++;

storage/perfschema/pfs_events_statements.cc

+10-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ void cleanup_events_statements_history_long() {
153153

154154
static inline void copy_events_statements(PFS_events_statements *dest,
155155
const PFS_events_statements *source) {
156-
/* Copy all attributes except SQL TEXT and DIGEST */
156+
/* Copy all attributes except SQL TEXT, DIGEST and MESSAGE_TEXT */
157157
dest->PFS_events::operator=(*source);
158158
memcpy(&dest->m_statement_id, &source->m_statement_id,
159159
pointer_cast<const char *>(&source->m_sqltext) -
@@ -171,6 +171,15 @@ static inline void copy_events_statements(PFS_events_statements *dest,
171171

172172
/* Copy DIGEST */
173173
dest->m_digest_storage.copy(&source->m_digest_storage);
174+
175+
/* Copy MESSAGE_TEXT */
176+
const uint message_text_length = source->m_message_text_length;
177+
178+
if (message_text_length > 0) {
179+
memcpy(dest->m_message_text, source->m_message_text, message_text_length);
180+
}
181+
dest->m_message_text[message_text_length] = '\0';
182+
dest->m_message_text_length = message_text_length;
174183
}
175184

176185
/**

storage/perfschema/pfs_events_statements.h

+8-2
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@ struct PFS_events_statements : public PFS_events {
6363
/** Locked time. */
6464
ulonglong m_lock_time;
6565

66-
/** Diagnostics area, message text. */
67-
char m_message_text[MYSQL_ERRMSG_SIZE + 1];
6866
/** Diagnostics area, error number. */
6967
uint m_sql_errno;
7068
/** Diagnostics area, @c SQLSTATE. */
@@ -144,6 +142,14 @@ struct PFS_events_statements : public PFS_events {
144142
and always point to pre allocated memory.
145143
*/
146144
sql_digest_storage m_digest_storage;
145+
146+
/**
147+
Length of @c m_message_text.
148+
This is placed __before__ m_message_text[], for data locality.
149+
*/
150+
uint m_message_text_length;
151+
/** Diagnostics area, message text. */
152+
char m_message_text[MYSQL_ERRMSG_SIZE + 1];
147153
};
148154

149155
void insert_events_statements_history(PFS_thread *thread,

storage/perfschema/pfs_global.h

+12
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,18 @@ struct PFS_cacheline_atomic_size_t {
102102
PFS_cacheline_atomic_size_t() : m_size_t(0) {}
103103
};
104104

105+
/**
106+
An atomic<T> variable, guaranteed to be alone in a CPU cache line.
107+
This is for performance, for variables accessed very frequently.
108+
*/
109+
template <class T>
110+
struct PFS_cacheline_atomic_ptr {
111+
std::atomic<T> m_ptr;
112+
char m_full_cache_line[PFS_CACHE_LINE_SIZE - sizeof(std::atomic<T>)];
113+
114+
PFS_cacheline_atomic_ptr() : m_ptr(nullptr) {}
115+
};
116+
105117
struct PFS_builtin_memory_class;
106118

107119
/** Memory allocation for the performance schema. */

storage/perfschema/table_events_statements.cc

+8-3
Original file line numberDiff line numberDiff line change
@@ -342,8 +342,13 @@ int table_events_statements_common::make_row_part_1(
342342
m_row.m_source, sizeof(m_row.m_source),
343343
m_row.m_source_length);
344344

345-
memcpy(m_row.m_message_text, statement->m_message_text,
346-
sizeof(m_row.m_message_text));
345+
m_row.m_message_text_length = statement->m_message_text_length;
346+
if (m_row.m_message_text_length > 0) {
347+
memcpy(m_row.m_message_text, statement->m_message_text,
348+
m_row.m_message_text_length);
349+
}
350+
m_row.m_message_text[m_row.m_message_text_length] = '\0';
351+
347352
memcpy(m_row.m_sqlstate, statement->m_sqlstate, SQLSTATE_LENGTH);
348353

349354
m_row.m_sql_errno = statement->m_sql_errno;
@@ -532,7 +537,7 @@ int table_events_statements_common::read_row_values(TABLE *table,
532537
}
533538
break;
534539
case 19: /* MESSAGE_TEXT */
535-
len = (uint)strlen(m_row.m_message_text);
540+
len = m_row.m_message_text_length;
536541
if (len) {
537542
set_field_varchar_utf8mb4(f, m_row.m_message_text, len);
538543
} else {

storage/perfschema/table_events_statements.h

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ struct row_events_statements {
116116

117117
/** Column MESSAGE_TEXT. */
118118
char m_message_text[MYSQL_ERRMSG_SIZE + 1];
119+
uint m_message_text_length;
119120
/** Column MYSQL_ERRNO. */
120121
uint m_sql_errno;
121122
/** Column RETURNED_SQLSTATE. */

storage/perfschema/unittest/stub_server_telemetry.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@ SERVICE_TYPE(mysql_server_telemetry_traces_v1)
3333
SERVICE_IMPLEMENTATION(performance_schema, mysql_server_telemetry_traces_v1){
3434
nullptr, nullptr, nullptr};
3535

36-
std::atomic<telemetry_t *> g_telemetry = nullptr;
36+
PFS_cacheline_atomic_ptr<telemetry_t *> g_telemetry;

0 commit comments

Comments
 (0)