From: Marc Alff Date: April 1 2010 8:08am Subject: bzr commit into mysql-next-mr-bugfixing branch (marc.alff:3144) Bug#52502 List-Archive: http://lists.mysql.com/commits/104765 X-Bug: 52502 Message-Id: <20100401080817.8FA1D3343D1@MarcBook.local> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="Boundary_(ID_bN3mV7uZ2PgN+F/saEPgBg)" --Boundary_(ID_bN3mV7uZ2PgN+F/saEPgBg) MIME-version: 1.0 Content-type: text/plain; CHARSET=US-ASCII Content-transfer-encoding: 7BIT Content-disposition: inline #At file:///Users/malff/BZR_TREE/mysql-next-mr-bugfixing-52502/ based on revid:alik@stripped 3144 Marc Alff 2010-04-01 Bug#52502 Performance schema does not start with large mutex_instance buffers This is a performance issue affecting scalability. Prior to this fix, the performance schema code did not scale well when a very large number of instruments are created in the code. The root cause was create_mutex() spinning "forever", trying to find an empty slot in a full mutex_array. With this fix: - functions like create_mutex() abort sooner, when used with a very large array which is full or almost full. - randomize_index() has been revised to improve the statistical spread of values in the mutex_array. See the comments in the code for details. Other allocation functions similar to create_mutex() have been also fixed. No MTR test case provided (performance scalability issue). Fix tested manually. modified: storage/perfschema/pfs_global.h storage/perfschema/pfs_instr.cc storage/perfschema/pfs_instr.h === modified file 'storage/perfschema/pfs_global.h' --- a/storage/perfschema/pfs_global.h 2010-01-12 01:47:27 +0000 +++ b/storage/perfschema/pfs_global.h 2010-04-01 08:08:12 +0000 @@ -1,4 +1,4 @@ -/* Copyright (C) 2008-2009 Sun Microsystems, Inc +/* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -30,27 +30,49 @@ void pfs_free(void *ptr); inline uint randomized_index(const void *ptr, uint max_size) { + static uint seed1= 0; + static uint seed2= 0; + uint result; + register intptr value; + if (unlikely(max_size == 0)) return 0; /* - ptr is typically an aligned structure, - so the last bits are not really random, but this has no effect. - Apply a factor A*x to spread - close values of ptr further apart (which helps with arrays), - and to spread values way beyond a typical max_size. - Then, apply a modulo to end within [0, max_size - 1]. - A is big prime numbers, to avoid resonating with max_size, - to have a uniform distribution in [0, max_size - 1]. - The value of A is chosen so that index(ptr) and index(ptr + N) (for arrays) - are likely to be not similar for typical values of max_size - (50, 100, 1000, etc). - In other words, (sizeof(T)*A % max_size) should not be a small number, - to avoid that with 'T array[max_size]', index(array[i]) - and index(array[i + 1]) end up pointing in the same area in [0, max_size - 1]. + ptr is typically an aligned structure, and can be in an array. + - The last bits are not random because of alignment, + so we divide by 8. + - The high bits are mostly constant, especially with 64 bits architectures, + but we keep most of them anyway, by doing computation in intptr. + The high bits are significant depending on where the data is + stored (the data segment, the stack, the heap, ...). + - To spread consecutive cells in an array further, we multiply by + a factor A. This factor should not be too high, which would cause + an overflow and cause loss of randomness (droping the top high bits). + The factor is a prime number, to help spread the distribution. + - To add more noise, and to be more robust if the calling code is + passing a constant value instead of a random identity, + we add the previous results, for hysteresys, with a degree 2 polynom, + X^2 + X + 1. + - Last, a modulo is applied to be within the [0, max_size - 1] range. + Note that seed1 and seed2 are static, and are *not* thread safe, + which is even better. + Effect with arrays: T array[N] + - ptr(i) = & array[i] = & array[0] + i * sizeof(T) + - ptr(i+1) = ptr(i) + sizeof(T). + What we want here, is to have index(i) and index(i+1) fall into + very different areas in [0, max_size - 1], to avoid locality. */ - return static_cast - (((reinterpret_cast (ptr)) * 2166179) % max_size); + value= (reinterpret_cast (ptr)) >> 3; + value*= 1789; + value+= seed2 + seed1 + 1; + value%= max_size; + + result= static_cast (value); + seed2= seed1*seed1; + seed1= result; + + return result; } void pfs_print_error(const char *format, ...); === modified file 'storage/perfschema/pfs_instr.cc' --- a/storage/perfschema/pfs_instr.cc 2010-03-22 12:48:18 +0000 +++ b/storage/perfschema/pfs_instr.cc 2010-04-01 08:08:12 +0000 @@ -445,6 +445,77 @@ void cleanup_file_hash(void) } } +void PFS_scan::init(uint random, uint max_size) +{ + if (max_size == 0) + { + /* Degenerated case, no buffer */ + m_pass= 0; + m_pass_max= 0; + return; + } + + DBUG_ASSERT(random < max_size); + + if (PFS_MAX_ALLOC_RETRY < max_size) + { + /* + The buffer is big compared to PFS_MAX_ALLOC_RETRY, + scan it only partially. + */ + if (random + PFS_MAX_ALLOC_RETRY < max_size) + { + /* + Pass 1: [random, random + PFS_MAX_ALLOC_RETRY - 1] + Pass 2: not used. + */ + m_pass= 0; + m_pass_max= 1; + m_first[0]= random; + m_last[0]= random + PFS_MAX_ALLOC_RETRY; + m_first[1]= 0; + m_last[1]= 0; + } + else + { + /* + Pass 1: [random, max_size - 1] + Pass 2: [0, ...] + The combined length of pass 1 and 2 is PFS_MAX_ALLOC_RETRY. + */ + m_pass= 0; + m_pass_max= 2; + m_first[0]= random; + m_last[0]= max_size; + m_first[1]= 0; + m_last[1]= PFS_MAX_ALLOC_RETRY - (max_size - random); + } + } + else + { + /* + The buffer is small compared to PFS_MAX_ALLOC_RETRY, + scan it in full in two passes. + Pass 1: [random, max_size - 1] + Pass 2: [0, random - 1] + */ + m_pass= 0; + m_pass_max= 2; + m_first[0]= random; + m_last[0]= max_size; + m_first[1]= 0; + m_last[1]= random; + } + + DBUG_ASSERT(m_first[0] < max_size); + DBUG_ASSERT(m_first[1] < max_size); + DBUG_ASSERT(m_last[1] <= max_size); + DBUG_ASSERT(m_last[1] <= max_size); + /* The combined length of all passes should not exceed PFS_MAX_ALLOC_RETRY. */ + DBUG_ASSERT((m_last[0] - m_first[0]) + + (m_last[1] - m_first[1]) <= PFS_MAX_ALLOC_RETRY); +} + /** Create instrumentation for a mutex instance. @param klass the mutex class @@ -453,17 +524,15 @@ void cleanup_file_hash(void) */ PFS_mutex* create_mutex(PFS_mutex_class *klass, const void *identity) { - int pass; - uint i= randomized_index(identity, mutex_max); + PFS_scan scan; + uint random= randomized_index(identity, mutex_max); - /* - Pass 1: [random, mutex_max - 1] - Pass 2: [0, mutex_max - 1] - */ - for (pass= 1; pass <= 2; i=0, pass++) + for (scan.init(random, mutex_max); + scan.has_pass(); + scan.next_pass()) { - PFS_mutex *pfs= mutex_array + i; - PFS_mutex *pfs_last= mutex_array + mutex_max; + PFS_mutex *pfs= mutex_array + scan.first(); + PFS_mutex *pfs_last= mutex_array + scan.last(); for ( ; pfs < pfs_last; pfs++) { if (pfs->m_lock.is_free()) @@ -511,17 +580,15 @@ void destroy_mutex(PFS_mutex *pfs) */ PFS_rwlock* create_rwlock(PFS_rwlock_class *klass, const void *identity) { - int pass; - uint i= randomized_index(identity, rwlock_max); + PFS_scan scan; + uint random= randomized_index(identity, rwlock_max); - /* - Pass 1: [random, rwlock_max - 1] - Pass 2: [0, rwlock_max - 1] - */ - for (pass= 1; pass <= 2; i=0, pass++) + for (scan.init(random, rwlock_max); + scan.has_pass(); + scan.next_pass()) { - PFS_rwlock *pfs= rwlock_array + i; - PFS_rwlock *pfs_last= rwlock_array + rwlock_max; + PFS_rwlock *pfs= rwlock_array + scan.first(); + PFS_rwlock *pfs_last= rwlock_array + scan.last(); for ( ; pfs < pfs_last; pfs++) { if (pfs->m_lock.is_free()) @@ -575,17 +642,15 @@ void destroy_rwlock(PFS_rwlock *pfs) */ PFS_cond* create_cond(PFS_cond_class *klass, const void *identity) { - int pass; - uint i= randomized_index(identity, cond_max); + PFS_scan scan; + uint random= randomized_index(identity, cond_max); - /* - Pass 1: [random, cond_max - 1] - Pass 2: [0, cond_max - 1] - */ - for (pass= 1; pass <= 2; i=0, pass++) + for (scan.init(random, cond_max); + scan.has_pass(); + scan.next_pass()) { - PFS_cond *pfs= cond_array + i; - PFS_cond *pfs_last= cond_array + cond_max; + PFS_cond *pfs= cond_array + scan.first(); + PFS_cond *pfs_last= cond_array + scan.last(); for ( ; pfs < pfs_last; pfs++) { if (pfs->m_lock.is_free()) @@ -633,17 +698,15 @@ void destroy_cond(PFS_cond *pfs) PFS_thread* create_thread(PFS_thread_class *klass, const void *identity, ulong thread_id) { - int pass; - uint i= randomized_index(identity, thread_max); + PFS_scan scan; + uint random= randomized_index(identity, thread_max); - /* - Pass 1: [random, thread_max - 1] - Pass 2: [0, thread_max - 1] - */ - for (pass= 1; pass <= 2; i=0, pass++) + for (scan.init(random, thread_max); + scan.has_pass(); + scan.next_pass()) { - PFS_thread *pfs= thread_array + i; - PFS_thread *pfs_last= thread_array + thread_max; + PFS_thread *pfs= thread_array + scan.first(); + PFS_thread *pfs_last= thread_array + scan.last(); for ( ; pfs < pfs_last; pfs++) { if (pfs->m_lock.is_free()) @@ -727,7 +790,7 @@ find_or_create_file(PFS_thread *thread, const char *filename, uint len) { PFS_file *pfs; - int pass; + PFS_scan scan; if (! filename_hash_inited) { @@ -839,17 +902,14 @@ search: } /* filename is not constant, just using it for noise on create */ - uint i= randomized_index(filename, file_max); + uint random= randomized_index(filename, file_max); - /* - Pass 1: [random, file_max - 1] - Pass 2: [0, file_max - 1] - */ - for (pass= 1; pass <= 2; i=0, pass++) + for (scan.init(random, file_max); + scan.has_pass(); + scan.next_pass()) { - pfs= file_array + i; - PFS_file *pfs_last= file_array + file_max; - + pfs= file_array + scan.first(); + PFS_file *pfs_last= file_array + scan.last(); for ( ; pfs < pfs_last; pfs++) { if (pfs->m_lock.is_free()) @@ -934,17 +994,15 @@ void destroy_file(PFS_thread *thread, PF */ PFS_table* create_table(PFS_table_share *share, const void *identity) { - int pass; - uint i= randomized_index(identity, table_max); + PFS_scan scan; + uint random= randomized_index(identity, table_max); - /* - Pass 1: [random, table_max - 1] - Pass 2: [0, table_max - 1] - */ - for (pass= 1; pass <= 2; i=0, pass++) + for (scan.init(random, table_max); + scan.has_pass(); + scan.next_pass()) { - PFS_table *pfs= table_array + i; - PFS_table *pfs_last= table_array + table_max; + PFS_table *pfs= table_array + scan.first(); + PFS_table *pfs_last= table_array + scan.last(); for ( ; pfs < pfs_last; pfs++) { if (pfs->m_lock.is_free()) === modified file 'storage/perfschema/pfs_instr.h' --- a/storage/perfschema/pfs_instr.h 2010-01-12 01:47:27 +0000 +++ b/storage/perfschema/pfs_instr.h 2010-04-01 08:08:12 +0000 @@ -136,6 +136,48 @@ struct PFS_table : public PFS_instr */ #define LOCKER_STACK_SIZE 3 +/** + @def PFS_MAX_ALLOC_RETRY + Maximum number of times the code attempts to allocate an item + from internal buffers, before giving up. +*/ +#define PFS_MAX_ALLOC_RETRY 1000 + +#define PFS_MAX_SCAN_PASS 2 + +/** + Helper to scan circular buffers. + Given a buffer of size [0, max_size - 1], + and a random starting point in the buffer, + this helper returns up to two [first, last -1] intervals that: + - fit into the [0, max_size - 1] range, + - have a maximum combined length of at most PFS_MAX_ALLOC_RETRY. +*/ +struct PFS_scan +{ +public: + void init(uint random, uint max_size); + + bool has_pass() const + { return (m_pass < m_pass_max); } + + void next_pass() + { m_pass++; } + + uint first() + { return m_first[m_pass]; } + + uint last() + { return m_last[m_pass]; } + +private: + uint m_pass; + uint m_pass_max; + uint m_first[PFS_MAX_SCAN_PASS]; + uint m_last[PFS_MAX_SCAN_PASS]; +}; + + /** Instrumented thread implementation. @see PSI_thread. */ struct PFS_thread { --Boundary_(ID_bN3mV7uZ2PgN+F/saEPgBg) MIME-version: 1.0 Content-type: text/bzr-bundle; CHARSET=US-ASCII; name="bzr/marc.alff@stripped" Content-transfer-encoding: 7BIT Content-disposition: inline; filename="bzr/marc.alff@stripped" # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: marc.alff@stripped # target_branch: file:///Users/malff/BZR_TREE/mysql-next-mr-bugfixing-\ # 52502/ # testament_sha1: 1dc689e700ceef95ac2f23c17ac4cf6f48fb0007 # timestamp: 2010-04-01 02:08:17 -0600 # base_revision_id: alik@stripped # # Begin bundle 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 --Boundary_(ID_bN3mV7uZ2PgN+F/saEPgBg)--