473 lines
12 KiB
C++
473 lines
12 KiB
C++
/* Copyright (c) 2011, 2018, 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
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
|
|
|
|
// First include (the generated) my_config.h, to get correct platform defines.
|
|
#include "my_config.h"
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <vector>
|
|
|
|
#include "sql_select.h"
|
|
#include "mem_root_array.h"
|
|
|
|
/**
|
|
WL#5774 Decrease number of malloc's for normal DML queries.
|
|
One of the malloc's was due to DYNAMIC_ARRAY keyuse;
|
|
We replace the DYNAMIC_ARRAY with a std::vector-like class Mem_root_array.
|
|
|
|
Below are unit tests for comparing performance, and for testing
|
|
functionality of Mem_root_array.
|
|
*/
|
|
|
|
|
|
/*
|
|
Rewrite of sort_keyuse() to comparison operator for use by std::less<>
|
|
It is a template argument, so static rather than in unnamed namespace.
|
|
*/
|
|
static inline bool operator<(const Key_use &a, const Key_use &b)
|
|
{
|
|
if (a.table_ref->tableno() != b.table_ref->tableno())
|
|
return a.table_ref->tableno() < b.table_ref->tableno();
|
|
if (a.key != b.key)
|
|
return a.key < b.key;
|
|
if (a.keypart != b.keypart)
|
|
return a.keypart < b.keypart;
|
|
const bool atab = MY_TEST((a.used_tables & ~OUTER_REF_TABLE_BIT));
|
|
const bool btab = MY_TEST((b.used_tables & ~OUTER_REF_TABLE_BIT));
|
|
if (atab != btab)
|
|
return atab < btab;
|
|
return
|
|
((a.optimize & KEY_OPTIMIZE_REF_OR_NULL) <
|
|
(b.optimize & KEY_OPTIMIZE_REF_OR_NULL));
|
|
}
|
|
|
|
|
|
/*
|
|
Compare for equality.
|
|
It is a template argument, so static rather than in unnamed namespace.
|
|
*/
|
|
static inline bool operator==(const Key_use &lhs, const Key_use &rhs)
|
|
{
|
|
return
|
|
lhs.table_ref->tableno() == rhs.table_ref->tableno() &&
|
|
lhs.key == rhs.key &&
|
|
lhs.keypart == rhs.keypart &&
|
|
MY_TEST((lhs.used_tables & ~OUTER_REF_TABLE_BIT))
|
|
==
|
|
MY_TEST((rhs.used_tables & ~OUTER_REF_TABLE_BIT)) &&
|
|
(lhs.optimize & KEY_OPTIMIZE_REF_OR_NULL)
|
|
==
|
|
(rhs.optimize & KEY_OPTIMIZE_REF_OR_NULL);
|
|
}
|
|
|
|
|
|
static inline std::ostream &operator<<(std::ostream &s, const Key_use &v)
|
|
{
|
|
return s << "{"
|
|
<< v.table_ref->tableno() << ", "
|
|
<< v.key << ", "
|
|
<< v.keypart << ", "
|
|
<< v.used_tables << ", "
|
|
<< v.optimize
|
|
<< "}"
|
|
;
|
|
}
|
|
|
|
|
|
namespace dynarray_unittest {
|
|
|
|
// We still want to unit-test this, to compare performance.
|
|
|
|
/*
|
|
Cut'n paste this function from sql_select.cc,
|
|
to avoid linking in the entire server for this unit test.
|
|
*/
|
|
inline int sort_keyuse(Key_use *a, Key_use *b)
|
|
{
|
|
int res;
|
|
if (a->table_ref->tableno() != b->table_ref->tableno())
|
|
return (int) (a->table_ref->tableno() - b->table_ref->tableno());
|
|
if (a->key != b->key)
|
|
return (int) (a->key - b->key);
|
|
if (a->keypart != b->keypart)
|
|
return (int) (a->keypart - b->keypart);
|
|
// Place const values before other ones
|
|
if ((res= MY_TEST((a->used_tables & ~OUTER_REF_TABLE_BIT)) -
|
|
MY_TEST((b->used_tables & ~OUTER_REF_TABLE_BIT))))
|
|
return res;
|
|
/* Place rows that are not 'OPTIMIZE_REF_OR_NULL' first */
|
|
return (int) ((a->optimize & KEY_OPTIMIZE_REF_OR_NULL) -
|
|
(b->optimize & KEY_OPTIMIZE_REF_OR_NULL));
|
|
}
|
|
|
|
|
|
// We generate some random data at startup, for testing of sorting.
|
|
void generate_test_data(Key_use *keys, TABLE_LIST *tables, int n)
|
|
{
|
|
int ix;
|
|
for (ix= 0; ix < n; ++ix)
|
|
{
|
|
tables[ix].set_tableno(ix % 3);
|
|
keys[ix]=
|
|
Key_use(&tables[ix],
|
|
NULL, // Item *val
|
|
0, // table_map used_tables
|
|
ix % 4, // uint key
|
|
ix % 2, // uint keypart
|
|
0, // uint optimize
|
|
0, // keypart_map
|
|
0, // ha_rows ref_table_rows
|
|
true, // bool null_rejecting
|
|
NULL, // bool *cond_guard
|
|
0 // uint sj_pred_no
|
|
);
|
|
}
|
|
std::random_shuffle(&keys[0], &keys[n]);
|
|
}
|
|
|
|
|
|
// Play around with these constants to see std::sort speedup vs. my_qsort.
|
|
const int num_elements= 200;
|
|
const int num_iterations= 1000;
|
|
|
|
/*
|
|
This class is used for comparing performance of
|
|
std::vector<> and std::sort()
|
|
vs
|
|
DYNAMIC_ARRAY and my_qsort()
|
|
*/
|
|
class DynArrayTest : public ::testing::Test
|
|
{
|
|
public:
|
|
DynArrayTest() {}
|
|
|
|
static void SetUpTestCase()
|
|
{
|
|
generate_test_data(test_data, table_list, num_elements);
|
|
}
|
|
|
|
virtual void SetUp()
|
|
{
|
|
my_init_dynamic_array(&m_keyuse_dyn,
|
|
PSI_NOT_INSTRUMENTED,
|
|
sizeof(Key_use), NULL,
|
|
num_elements, 64);
|
|
m_keyuse_vec.reserve(num_elements);
|
|
}
|
|
|
|
virtual void TearDown()
|
|
{
|
|
delete_dynamic(&m_keyuse_dyn);
|
|
}
|
|
|
|
void insert_and_sort_dynamic()
|
|
{
|
|
reset_dynamic(&m_keyuse_dyn);
|
|
for (int ix= 0; ix < num_elements; ++ix)
|
|
{
|
|
insert_dynamic(&m_keyuse_dyn, &test_data[ix]);
|
|
}
|
|
my_qsort(m_keyuse_dyn.buffer, m_keyuse_dyn.elements, sizeof(Key_use),
|
|
reinterpret_cast<qsort_cmp>(sort_keyuse));
|
|
}
|
|
|
|
void insert_and_sort_vector()
|
|
{
|
|
m_keyuse_vec.clear();
|
|
for (int ix= 0; ix < num_elements; ++ix)
|
|
{
|
|
m_keyuse_vec.push_back(test_data[ix]);
|
|
}
|
|
std::sort(m_keyuse_vec.begin(), m_keyuse_vec.end(), std::less<Key_use>());
|
|
}
|
|
|
|
DYNAMIC_ARRAY m_keyuse_dyn;
|
|
std::vector<Key_use> m_keyuse_vec;
|
|
private:
|
|
static Key_use test_data[num_elements];
|
|
static TABLE_LIST table_list[num_elements];
|
|
|
|
GTEST_DISALLOW_COPY_AND_ASSIGN_(DynArrayTest);
|
|
};
|
|
|
|
Key_use DynArrayTest::test_data[num_elements];
|
|
TABLE_LIST DynArrayTest::table_list[num_elements];
|
|
|
|
|
|
// Test insert_dynamic() and my_qsort().
|
|
TEST_F(DynArrayTest, DynArray)
|
|
{
|
|
for (int ix= 0; ix < num_iterations; ++ix)
|
|
insert_and_sort_dynamic();
|
|
}
|
|
|
|
|
|
// Test vector::push_back() and std::sort()
|
|
TEST_F(DynArrayTest, Vector)
|
|
{
|
|
for (int ix= 0; ix < num_iterations; ++ix)
|
|
insert_and_sort_vector();
|
|
}
|
|
|
|
|
|
/*
|
|
This class is for unit testing of Mem_root_array.
|
|
*/
|
|
class MemRootTest : public ::testing::Test
|
|
{
|
|
protected:
|
|
MemRootTest()
|
|
: m_mem_root_p(&m_mem_root),
|
|
m_array_mysys(m_mem_root_p),
|
|
m_array_std(m_mem_root_p)
|
|
{}
|
|
|
|
virtual void SetUp()
|
|
{
|
|
init_sql_alloc(PSI_NOT_INSTRUMENTED, &m_mem_root, 1024, 0);
|
|
ASSERT_EQ(0, my_set_thread_local(THR_MALLOC, &m_mem_root_p));
|
|
MEM_ROOT *root= *static_cast<MEM_ROOT**>(my_get_thread_local(THR_MALLOC));
|
|
ASSERT_EQ(root, m_mem_root_p);
|
|
|
|
m_array_mysys.reserve(num_elements);
|
|
m_array_std.reserve(num_elements);
|
|
destroy_counter= 0;
|
|
}
|
|
|
|
virtual void TearDown()
|
|
{
|
|
free_root(&m_mem_root, MYF(0));
|
|
}
|
|
|
|
static void SetUpTestCase()
|
|
{
|
|
generate_test_data(test_data, table_list, num_elements);
|
|
ASSERT_EQ(0, my_create_thread_local_key(&THR_THD, NULL));
|
|
THR_THD_initialized= true;
|
|
ASSERT_EQ(0, my_create_thread_local_key(&THR_MALLOC, NULL));
|
|
THR_MALLOC_initialized= true;
|
|
}
|
|
|
|
static void TearDownTestCase()
|
|
{
|
|
my_delete_thread_local_key(THR_THD);
|
|
THR_THD_initialized= false;
|
|
my_delete_thread_local_key(THR_MALLOC);
|
|
THR_MALLOC_initialized= false;
|
|
}
|
|
|
|
void insert_and_sort_mysys()
|
|
{
|
|
m_array_mysys.clear();
|
|
for (int ix= 0; ix < num_elements; ++ix)
|
|
{
|
|
m_array_mysys.push_back(test_data[ix]);
|
|
}
|
|
my_qsort(m_array_mysys.begin(), m_array_mysys.size(),
|
|
m_array_mysys.element_size(),
|
|
reinterpret_cast<qsort_cmp>(sort_keyuse));
|
|
}
|
|
|
|
void insert_and_sort_std()
|
|
{
|
|
m_array_std.clear();
|
|
for (int ix= 0; ix < num_elements; ++ix)
|
|
{
|
|
m_array_std.push_back(test_data[ix]);
|
|
}
|
|
std::sort(m_array_std.begin(), m_array_std.end(), std::less<Key_use>());
|
|
}
|
|
|
|
MEM_ROOT m_mem_root;
|
|
MEM_ROOT *m_mem_root_p;
|
|
Key_use_array m_array_mysys;
|
|
Key_use_array m_array_std;
|
|
public:
|
|
static size_t destroy_counter;
|
|
private:
|
|
static Key_use test_data[num_elements];
|
|
static TABLE_LIST table_list[num_elements];
|
|
|
|
GTEST_DISALLOW_COPY_AND_ASSIGN_(MemRootTest);
|
|
};
|
|
|
|
size_t MemRootTest::destroy_counter;
|
|
Key_use MemRootTest::test_data[num_elements];
|
|
TABLE_LIST MemRootTest::table_list[num_elements];
|
|
|
|
|
|
// Test Mem_root_array::push_back() and my_qsort()
|
|
TEST_F(MemRootTest, KeyUseMysys)
|
|
{
|
|
for (int ix= 0; ix < num_iterations; ++ix)
|
|
insert_and_sort_mysys();
|
|
}
|
|
|
|
|
|
// Test Mem_root_array::push_back() and std::sort()
|
|
TEST_F(MemRootTest, KeyUseStd)
|
|
{
|
|
for (int ix= 0; ix < num_iterations; ++ix)
|
|
insert_and_sort_std();
|
|
}
|
|
|
|
|
|
// Test that my_qsort() and std::sort() generate same order.
|
|
TEST_F(MemRootTest, KeyUseCompare)
|
|
{
|
|
insert_and_sort_mysys();
|
|
insert_and_sort_std();
|
|
for (int ix= 0; ix < num_elements; ++ix)
|
|
{
|
|
Key_use k1= m_array_mysys.at(ix);
|
|
Key_use k2= m_array_std.at(ix);
|
|
EXPECT_EQ(k1, k2);
|
|
}
|
|
}
|
|
|
|
|
|
// Test that Mem_root_array re-expanding works.
|
|
TEST_F(MemRootTest, Reserve)
|
|
{
|
|
Mem_root_array<uint, true> intarr(m_mem_root_p);
|
|
intarr.reserve(2);
|
|
const uint num_pushes= 20;
|
|
for (uint ix=0; ix < num_pushes; ++ix)
|
|
{
|
|
EXPECT_EQ(ix, intarr.size());
|
|
EXPECT_FALSE(intarr.push_back(ix));
|
|
EXPECT_EQ(ix, intarr.at(ix));
|
|
}
|
|
for (uint ix=0; ix < num_pushes; ++ix)
|
|
{
|
|
EXPECT_EQ(ix, intarr.at(ix));
|
|
}
|
|
EXPECT_EQ(sizeof(uint), intarr.element_size());
|
|
EXPECT_EQ(num_pushes, intarr.size());
|
|
EXPECT_LE(num_pushes, intarr.capacity());
|
|
}
|
|
|
|
|
|
// Verify that we can swap mem-root, without any leaks.
|
|
// Run with
|
|
// valgrind --leak-check=full <executable> --gtest_filter='-*DeathTest*' > foo
|
|
TEST_F(MemRootTest, CopyMemRoot)
|
|
{
|
|
Mem_root_array<uint, true> intarr(m_mem_root_p);
|
|
// Take a copy, we do *not* free_root(own_root)
|
|
MEM_ROOT own_root= *m_mem_root_p;
|
|
intarr.set_mem_root(&own_root);
|
|
intarr.push_back(42);
|
|
*m_mem_root_p= own_root;
|
|
}
|
|
|
|
|
|
class DestroyCounter
|
|
{
|
|
public:
|
|
DestroyCounter() : p_counter(&MemRootTest::destroy_counter) {};
|
|
DestroyCounter(const DestroyCounter &rhs) : p_counter(rhs.p_counter) {}
|
|
explicit DestroyCounter(size_t *p) : p_counter(p) {}
|
|
~DestroyCounter() { (*p_counter)+= 1; }
|
|
private:
|
|
size_t *p_counter;
|
|
};
|
|
|
|
|
|
// Test chop() and clear() and that destructors are executed.
|
|
TEST_F(MemRootTest, ChopAndClear)
|
|
{
|
|
Mem_root_array<DestroyCounter, false> array(m_mem_root_p);
|
|
const size_t nn= 4;
|
|
array.reserve(nn);
|
|
size_t counter= 0;
|
|
DestroyCounter foo(&counter);
|
|
for (size_t ix= 0; ix < array.capacity(); ++ix)
|
|
array.push_back(foo);
|
|
|
|
EXPECT_EQ(0U, counter);
|
|
array.chop(nn / 2);
|
|
EXPECT_EQ(nn / 2, counter);
|
|
EXPECT_EQ(nn / 2, array.size());
|
|
|
|
array.clear();
|
|
EXPECT_EQ(nn, counter);
|
|
}
|
|
|
|
|
|
// Test that elements are destroyed if push_back() needs to call reserve().
|
|
TEST_F(MemRootTest, ReserveDestroy)
|
|
{
|
|
Mem_root_array<DestroyCounter, false> array(m_mem_root_p);
|
|
const size_t nn= 4;
|
|
array.reserve(nn / 2);
|
|
size_t counter= 0;
|
|
DestroyCounter foo(&counter);
|
|
for (size_t ix= 0; ix < nn; ++ix)
|
|
array.push_back(foo);
|
|
|
|
EXPECT_EQ(nn / 2, counter);
|
|
EXPECT_EQ(nn, array.size());
|
|
|
|
counter= 0;
|
|
array.clear();
|
|
EXPECT_EQ(nn, counter);
|
|
}
|
|
|
|
TEST_F(MemRootTest, ResizeSame)
|
|
{
|
|
Mem_root_array<DestroyCounter, false> array(m_mem_root_p);
|
|
array.reserve(100);
|
|
size_t counter= 0;
|
|
DestroyCounter foo(&counter);
|
|
for (int ix= 0; ix < 10; ++ix)
|
|
array.push_back(foo);
|
|
EXPECT_EQ(10U, array.size());
|
|
array.resize(10U);
|
|
EXPECT_EQ(10U, array.size());
|
|
array.clear();
|
|
EXPECT_EQ(10U, counter);
|
|
}
|
|
|
|
TEST_F(MemRootTest, ResizeGrow)
|
|
{
|
|
Mem_root_array<DestroyCounter, false> array(m_mem_root_p);
|
|
array.reserve(100);
|
|
size_t counter= 0;
|
|
DestroyCounter foo(&counter);
|
|
array.resize(10, foo);
|
|
EXPECT_EQ(0U, counter);
|
|
array.clear();
|
|
EXPECT_EQ(0U, MemRootTest::destroy_counter);
|
|
EXPECT_EQ(10U, counter);
|
|
}
|
|
|
|
TEST_F(MemRootTest, ResizeShrink)
|
|
{
|
|
size_t counter= 0;
|
|
Mem_root_array<DestroyCounter, false> array(m_mem_root_p);
|
|
array.reserve(100);
|
|
DestroyCounter foo(&counter);
|
|
array.resize(10, foo);
|
|
EXPECT_EQ(0U, counter);
|
|
array.resize(5);
|
|
EXPECT_EQ(5U, counter);
|
|
}
|
|
|
|
|
|
}
|