559 lines
13 KiB
C++
559 lines
13 KiB
C++
/* Copyright (c) 2010, 2016, 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 <stddef.h>
|
|
|
|
#include "bounded_queue.h"
|
|
#include "fake_costmodel.h"
|
|
#include "filesort_utils.h"
|
|
#include "my_sys.h"
|
|
#include "opt_costmodel.h"
|
|
#include "test_utils.h"
|
|
|
|
#include "bounded_queue_c.h"
|
|
#include "bounded_queue_std.h"
|
|
#include "bounded_queue_boost.h"
|
|
#include "bounded_queue_boost.cc"
|
|
|
|
namespace bounded_queue_unittest {
|
|
|
|
const int num_elements= 14;
|
|
|
|
static int count_int_ptr_compare= 0;
|
|
static int count_operator= 0;
|
|
static int count_test_key= 0;
|
|
|
|
/*
|
|
Elements to be sorted by tests below.
|
|
We put some data in front of 'val' to verify (when debugging)
|
|
that all the reinterpret_casts involved when using QUEUE are correct.
|
|
*/
|
|
struct Test_element
|
|
{
|
|
Test_element() { *this= -1; }
|
|
Test_element(int i) { *this= i; }
|
|
|
|
Test_element &operator=(int i)
|
|
{
|
|
val= i;
|
|
my_snprintf(text, array_size(text), "%4d", i);
|
|
return *this;
|
|
}
|
|
|
|
char text[8]; // Some data.
|
|
int val; // The value we use for generating the key.
|
|
};
|
|
|
|
|
|
/*
|
|
The key, which is actually sorted by queue_xxx() functions.
|
|
We sort on the key only.
|
|
*/
|
|
struct Test_key
|
|
{
|
|
Test_key() : element(NULL), key(-1) {}
|
|
|
|
Test_element *element; // The actual data element.
|
|
int key; // The generated key for the data element.
|
|
};
|
|
|
|
|
|
/*
|
|
Comparison function for Test_key objects.
|
|
*/
|
|
int test_key_compare(size_t *cmp_arg, Test_key **a, Test_key **b)
|
|
{
|
|
EXPECT_EQ(*cmp_arg, sizeof(int));
|
|
|
|
int a_num= (*a)->key;
|
|
int b_num= (*b)->key;
|
|
|
|
if (a_num > b_num)
|
|
return +1;
|
|
if (a_num < b_num)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
class Test_key_compare
|
|
{
|
|
public:
|
|
bool operator()(const Test_key *a, const Test_key *b) const
|
|
{
|
|
++count_test_key;
|
|
return a->key < b->key;
|
|
}
|
|
size_t m_size;
|
|
};
|
|
|
|
|
|
/*
|
|
Generates a Test_key for a given Test_element.
|
|
*/
|
|
class Test_keymaker
|
|
{
|
|
public:
|
|
uint make_sortkey(Test_key *key, Test_element *element)
|
|
{
|
|
key->element= element;
|
|
key->key= element->val;
|
|
return sizeof(key->key);
|
|
}
|
|
size_t compare_length() const { return sizeof(int); }
|
|
};
|
|
|
|
|
|
/*
|
|
A struct to wrap the actual keys, and an array of pointers to the keys.
|
|
*/
|
|
template<int sz, typename Key_type>
|
|
struct Key_container
|
|
{
|
|
Key_container()
|
|
{
|
|
for (int ix= 0; ix <= sz; ++ix)
|
|
key_ptrs[ix]= &key_data[ix];
|
|
}
|
|
|
|
Key_type *key_ptrs[sz+1];
|
|
Key_type key_data[sz+1];
|
|
};
|
|
|
|
|
|
class BoundedQueueTest : public ::testing::Test
|
|
{
|
|
protected:
|
|
BoundedQueueTest() {}
|
|
|
|
virtual void SetUp()
|
|
{
|
|
int ix;
|
|
for (ix=0; ix < array_size(m_test_data); ++ix)
|
|
m_test_data[ix]= ix;
|
|
std::random_shuffle(&m_test_data[0], &m_test_data[array_size(m_test_data)]);
|
|
}
|
|
|
|
void insert_test_data()
|
|
{
|
|
for (int ix= 0; ix < array_size(m_test_data); ++ix)
|
|
m_queue.push(&m_test_data[ix]);
|
|
}
|
|
|
|
void insert_test_data_heap()
|
|
{
|
|
for (int ix= 0; ix < array_size(m_test_data); ++ix)
|
|
{
|
|
m_heap.push(&m_test_data[ix]);
|
|
}
|
|
}
|
|
|
|
// Key pointers and data, used by the queue_xxx() functions.
|
|
Key_container<num_elements / 2, Test_key> m_keys;
|
|
|
|
// Some random intput data, to be sorted.
|
|
Test_element m_test_data[num_elements];
|
|
|
|
Test_keymaker m_keymaker;
|
|
Bounded_QUEUE<Test_element *, Test_key *, Test_keymaker> m_queue;
|
|
Bounded_queue<Test_element *, Test_key *,
|
|
Test_keymaker, Test_key_compare> m_heap;
|
|
private:
|
|
GTEST_DISALLOW_COPY_AND_ASSIGN_(BoundedQueueTest);
|
|
};
|
|
|
|
|
|
// Google Test recommends DeathTest suffix for classes used in death tests.
|
|
typedef BoundedQueueTest BoundedQueueDeathTest;
|
|
|
|
#if !defined(DBUG_OFF)
|
|
/*
|
|
Verifies that we DBUG_ASSERT if trying to push to an un-initialized queue.
|
|
*/
|
|
TEST_F(BoundedQueueDeathTest, DieIfNotInitialized)
|
|
{
|
|
::testing::FLAGS_gtest_death_test_style = "threadsafe";
|
|
Test_element foo= 1;
|
|
EXPECT_DEATH_IF_SUPPORTED(m_queue.push(&foo),
|
|
".*Assertion .*is_initialized.*");
|
|
}
|
|
|
|
/*
|
|
Verifies that popping an empty queue hits a DBUG_ASSERT.
|
|
*/
|
|
TEST_F(BoundedQueueDeathTest, DieIfPoppingEmptyQueue)
|
|
{
|
|
EXPECT_EQ(0, m_queue.init(0, true, test_key_compare,
|
|
&m_keymaker, m_keys.key_ptrs));
|
|
::testing::FLAGS_gtest_death_test_style = "threadsafe";
|
|
EXPECT_DEATH_IF_SUPPORTED(m_queue.pop(),
|
|
".*Assertion .*elements > 0.*");
|
|
}
|
|
#endif // !defined(DBUG_OFF)
|
|
|
|
|
|
/*
|
|
Verifies that construct, initialize, destroy works.
|
|
*/
|
|
TEST_F(BoundedQueueTest, ConstructAndDestruct)
|
|
{
|
|
EXPECT_EQ(0, m_queue.init(num_elements/2, true,
|
|
test_key_compare,
|
|
&m_keymaker, m_keys.key_ptrs));
|
|
}
|
|
|
|
|
|
/*
|
|
Verifies that we reject too large queues.
|
|
*/
|
|
TEST_F(BoundedQueueTest, TooManyElements)
|
|
{
|
|
EXPECT_EQ(1, m_queue.init(UINT_MAX, true,
|
|
test_key_compare,
|
|
&m_keymaker, m_keys.key_ptrs));
|
|
EXPECT_EQ(1, m_queue.init(UINT_MAX - 1, true,
|
|
test_key_compare,
|
|
&m_keymaker, m_keys.key_ptrs));
|
|
}
|
|
|
|
|
|
/*
|
|
Verifies that zero-size queue works.
|
|
*/
|
|
TEST_F(BoundedQueueTest, ZeroSizeQueue)
|
|
{
|
|
EXPECT_EQ(0, m_queue.init(0, true, test_key_compare,
|
|
&m_keymaker, m_keys.key_ptrs));
|
|
insert_test_data();
|
|
// There is always one extra element in the queue.
|
|
EXPECT_EQ(1U, m_queue.num_elements());
|
|
}
|
|
|
|
|
|
/*
|
|
Verifies that push and bounded size works, and that pop() gives sorted order.
|
|
*/
|
|
TEST_F(BoundedQueueTest, PushAndPopKeepLargest)
|
|
{
|
|
EXPECT_EQ(0, m_queue.init(num_elements/2, false, test_key_compare,
|
|
&m_keymaker, m_keys.key_ptrs));
|
|
insert_test_data();
|
|
// We expect the queue to contain [7 .. 13]
|
|
const int max_key_val= array_size(m_test_data) - 1;
|
|
EXPECT_EQ(static_cast<uint>(num_elements / 2 + 1), m_queue.num_elements());
|
|
while (m_queue.num_elements() > 0)
|
|
{
|
|
Test_key **top= m_queue.pop();
|
|
int expected_key_val= max_key_val - m_queue.num_elements();
|
|
int key_val= (*top)->key;
|
|
EXPECT_EQ(expected_key_val, key_val);
|
|
Test_element *element= (*top)->element;
|
|
EXPECT_EQ(expected_key_val, element->val);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Verifies that push and bounded size works, and that pop() gives sorted order.
|
|
Note that with max_at_top == true, we will pop() in reverse order.
|
|
*/
|
|
TEST_F(BoundedQueueTest, PushAndPopKeepSmallest)
|
|
{
|
|
EXPECT_EQ(0, m_queue.init(num_elements/2, true, test_key_compare,
|
|
&m_keymaker, m_keys.key_ptrs));
|
|
insert_test_data();
|
|
// We expect the queue to contain [6 .. 0]
|
|
while (m_queue.num_elements() > 0)
|
|
{
|
|
Test_key **top= m_queue.pop();
|
|
int expected_key_val= m_queue.num_elements();
|
|
int key_val= (*top)->key;
|
|
EXPECT_EQ(expected_key_val, key_val);
|
|
Test_element *element= (*top)->element;
|
|
EXPECT_EQ(expected_key_val, element->val);
|
|
}
|
|
}
|
|
|
|
|
|
static void my_string_ptr_sort(uchar *base, uint items, size_t size)
|
|
{
|
|
#if INT_MAX > 65536L
|
|
uchar **ptr=0;
|
|
|
|
if (radixsort_is_appliccable(items, size) &&
|
|
(ptr= (uchar**) my_malloc(PSI_NOT_INSTRUMENTED,
|
|
items*sizeof(char*),MYF(0))))
|
|
{
|
|
radixsort_for_str_ptr((uchar**) base,items,size,ptr);
|
|
my_free(ptr);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
if (size && items)
|
|
{
|
|
my_qsort2(base,items, sizeof(uchar*), my_testing::get_ptr_compare(size),
|
|
(void*) &size);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Verifies that push, with bounded size, followed by sort() works.
|
|
*/
|
|
TEST_F(BoundedQueueTest, InsertAndSort)
|
|
{
|
|
EXPECT_EQ(0, m_queue.init(num_elements/2, true, test_key_compare,
|
|
&m_keymaker, m_keys.key_ptrs));
|
|
insert_test_data();
|
|
uchar *base= (uchar*) &m_keys.key_ptrs[0];
|
|
size_t size= sizeof(Test_key);
|
|
// We sort our keys as strings, so erase all the element pointers first.
|
|
for (int ii= 0; ii < array_size(m_keys.key_data); ++ii)
|
|
m_keys.key_data[ii].element= NULL;
|
|
|
|
my_string_ptr_sort(base, array_size(m_keys.key_ptrs), size);
|
|
for (int ii= 0; ii < num_elements/2; ++ii)
|
|
{
|
|
Test_key *sorted_key= m_keys.key_ptrs[ii];
|
|
EXPECT_EQ(ii, sorted_key->key);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
A test of the function get_merge_many_buffs_cost_fast()
|
|
*/
|
|
TEST(CostEstimationTest, MergeManyBuff)
|
|
{
|
|
ha_rows num_rows= 512;
|
|
ulong num_keys= 100;
|
|
ulong row_lenght= 100;
|
|
double prev_cost= 0.0;
|
|
|
|
// Make a cost model object that the merge code will use
|
|
Fake_Cost_model_table cost_model_table;
|
|
|
|
while (num_rows <= MAX_FILE_SIZE/4)
|
|
{
|
|
const double merge_cost=
|
|
get_merge_many_buffs_cost_fast(num_rows, num_keys, row_lenght,
|
|
&cost_model_table);
|
|
EXPECT_LT(0.0, merge_cost);
|
|
EXPECT_LT(prev_cost, merge_cost);
|
|
num_rows*= 2;
|
|
prev_cost= merge_cost;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Comparison function for integers.
|
|
*/
|
|
int int_ptr_compare(size_t *cmp_arg, int **a, int **b)
|
|
{
|
|
EXPECT_EQ(*cmp_arg, sizeof(int));
|
|
|
|
++count_int_ptr_compare;
|
|
|
|
int a_num= **a;
|
|
int b_num= **b;
|
|
|
|
if (a_num > b_num)
|
|
return +1;
|
|
if (a_num < b_num)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
class Int_ptr_compare
|
|
{
|
|
public:
|
|
bool operator()(const int *a, const int *b) const
|
|
{
|
|
++count_operator;
|
|
return *a > *b;
|
|
}
|
|
size_t m_compare_length;
|
|
};
|
|
|
|
|
|
/*
|
|
Generates an integer key for a given integer element.
|
|
*/
|
|
class Int_keymaker
|
|
{
|
|
public:
|
|
uint make_sortkey(int *to, int *from)
|
|
{
|
|
memcpy(to, from, sizeof(int));
|
|
return sizeof(int);
|
|
}
|
|
size_t compare_length() const { return sizeof(int); }
|
|
};
|
|
|
|
|
|
/*
|
|
Some basic performance testing, to compute the overhead of Bounded_QUEUE.
|
|
Run the with 'bounded_queue-t --disable-tap-output' to see the
|
|
millisecond output from Google Test.
|
|
|
|
./bounded_queue-t --disable-tap-output --gtest_filter="PerfTest*"
|
|
|
|
For testing, I used
|
|
num_rows = 100000
|
|
row_limit = { 1, 10, 100, 1000, 10000 }
|
|
num_iterations = 100
|
|
|
|
The inser_into_xxx() functions insert rand() data.
|
|
Also test with
|
|
identical data : queue.push(0)
|
|
increasing keys: queue.push(ix)
|
|
decreasing keys: queue.push(-ix)
|
|
*/
|
|
const ha_rows num_rows= 100000;
|
|
const ha_rows row_limit= 1;
|
|
const int num_iterations= 1;
|
|
|
|
inline int test_data(int ix MY_ATTRIBUTE((unused)))
|
|
{
|
|
return rand();
|
|
// return 42;
|
|
// return ix;
|
|
// return -ix;
|
|
}
|
|
|
|
|
|
class PerfTest : public ::testing::Test
|
|
{
|
|
public:
|
|
virtual void SetUp()
|
|
{
|
|
count_int_ptr_compare= 0;
|
|
count_operator= 0;
|
|
count_test_key= 0;
|
|
}
|
|
virtual void TearDown()
|
|
{
|
|
std::cout << "C-compare " << count_int_ptr_compare
|
|
<< " Cpp-compare " << count_operator << std::endl << std::endl;
|
|
}
|
|
/*
|
|
The extra overhead of malloc/free should be part of the measurement,
|
|
so we do not define the key container as a member here.
|
|
*/
|
|
typedef Key_container<row_limit, int> Container;
|
|
};
|
|
|
|
|
|
// Different queue.init, so separate insert function, see insert_into_heap.
|
|
void insert_into_queue()
|
|
{
|
|
typedef Key_container<row_limit, int> Container;
|
|
std::cout << "insert " << num_rows
|
|
<< " rows into queue size " << row_limit << std::endl;
|
|
for (int it= 0; it < num_iterations; ++it)
|
|
{
|
|
Container *keys= new Container;
|
|
srand(0);
|
|
Int_keymaker int_keymaker;
|
|
Bounded_QUEUE<int *, int *, Int_keymaker> queue;
|
|
EXPECT_EQ(0, queue.init(row_limit, false, int_ptr_compare,
|
|
&int_keymaker, keys->key_ptrs));
|
|
for (ha_rows ix= 0; ix < num_rows; ++ix)
|
|
{
|
|
int data= test_data(ix);
|
|
queue.push(&data);
|
|
}
|
|
delete keys;
|
|
}
|
|
}
|
|
|
|
|
|
// Inserts test data into Queue.
|
|
template<typename Queue_t>
|
|
void insert_into_heap()
|
|
{
|
|
typedef Key_container<row_limit, int> Container;
|
|
std::cout << "insert " << num_rows
|
|
<< " rows into heap size " << row_limit << std::endl;
|
|
for (int it= 0; it < num_iterations; ++it)
|
|
{
|
|
Container *keys= new Container;
|
|
srand(0);
|
|
Int_keymaker int_keymaker;
|
|
Queue_t queue;
|
|
EXPECT_EQ(0, queue.init(row_limit,
|
|
&int_keymaker, keys->key_ptrs));
|
|
for (ha_rows ix= 0; ix < num_rows; ++ix)
|
|
{
|
|
int data= test_data(ix);
|
|
queue.push(&data);
|
|
}
|
|
delete keys;
|
|
}
|
|
}
|
|
|
|
|
|
TEST_F(PerfTest, InsertIntoQUEUE)
|
|
{
|
|
insert_into_queue();
|
|
}
|
|
|
|
TEST_F(PerfTest, InsertIntoPriorityQueue)
|
|
{
|
|
insert_into_heap<Bounded_queue<int *, int *,
|
|
Int_keymaker, Int_ptr_compare> >();
|
|
}
|
|
|
|
TEST_F(PerfTest, InsertIntoStdQueue)
|
|
{
|
|
insert_into_heap<Bounded_queue_std<int *, int *,
|
|
Int_keymaker, Int_ptr_compare> >();
|
|
}
|
|
|
|
TEST_F(PerfTest, InsertIntoBoostQueue)
|
|
{
|
|
insert_into_heap<Bounded_queue_boost<int *, int *,
|
|
Int_keymaker, Int_ptr_compare> >();
|
|
}
|
|
|
|
|
|
/*
|
|
Computes the overhead of setting up sort arrays, and rand() calls.
|
|
*/
|
|
TEST_F(PerfTest, NoSorting)
|
|
{
|
|
for (int it= 0; it < num_iterations; ++it)
|
|
{
|
|
Container *keys= new Container;
|
|
srand(0);
|
|
for (ha_rows ix= 0; ix < row_limit; ++ix)
|
|
{
|
|
int data= rand();
|
|
keys->key_data[ix]= data;
|
|
}
|
|
delete keys;
|
|
}
|
|
}
|
|
|
|
} // namespace
|