blob: cfc891aff7eb50faac1f60bc4632a72786caae77 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include <gtest/gtest.h>
#include "butil/time.h"
#include "butil/macros.h"
#define BAIDU_CLEAR_OBJECT_POOL_AFTER_ALL_THREADS_QUIT
#include "butil/object_pool.h"
namespace {
struct MyObject {};
int nfoo_dtor = 0;
struct Foo {
Foo() {
x = rand() % 2;
}
~Foo() {
++nfoo_dtor;
}
int x;
};
}
namespace butil {
template <> struct ObjectPoolBlockMaxSize<MyObject> {
static const size_t value = 128;
};
template <> struct ObjectPoolBlockMaxItem<MyObject> {
static const size_t value = 3;
};
template <> struct ObjectPoolFreeChunkMaxItem<MyObject> {
static size_t value() { return 5; }
};
template <> struct ObjectPoolValidator<Foo> {
static bool validate(const Foo* foo) {
return foo->x != 0;
}
};
}
namespace {
using namespace butil;
class ObjectPoolTest : public ::testing::Test{
protected:
ObjectPoolTest(){
};
virtual ~ObjectPoolTest(){};
virtual void SetUp() {
srand(time(0));
};
virtual void TearDown() {
};
};
int nc = 0;
int nd = 0;
std::set<void*> ptr_set;
struct YellObj {
YellObj() {
++nc;
ptr_set.insert(this);
printf("Created %p\n", this);
}
~YellObj() {
++nd;
ptr_set.erase(this);
printf("Destroyed %p\n", this);
}
char _dummy[96];
};
TEST_F(ObjectPoolTest, change_config) {
int a[2];
printf("%lu\n", ARRAY_SIZE(a));
ObjectPoolInfo info = describe_objects<MyObject>();
ObjectPoolInfo zero_info = { 0, 0, 0, 0, 3, 3, 0 };
ASSERT_EQ(0, memcmp(&info, &zero_info, sizeof(info)));
MyObject* p = get_object<MyObject>();
std::cout << describe_objects<MyObject>() << std::endl;
return_object(p);
std::cout << describe_objects<MyObject>() << std::endl;
}
struct NonDefaultCtorObject {
explicit NonDefaultCtorObject(int value) : _value(value) {}
NonDefaultCtorObject(int value, int dummy) : _value(value + dummy) {}
int _value;
};
TEST_F(ObjectPoolTest, sanity) {
ptr_set.clear();
NonDefaultCtorObject* p1 = get_object<NonDefaultCtorObject>(10);
ASSERT_EQ(10, p1->_value);
NonDefaultCtorObject* p2 = get_object<NonDefaultCtorObject>(100, 30);
ASSERT_EQ(130, p2->_value);
printf("BLOCK_NITEM=%lu\n", ObjectPool<YellObj>::BLOCK_NITEM);
nc = 0;
nd = 0;
{
YellObj* o1 = get_object<YellObj>();
ASSERT_TRUE(o1);
ASSERT_EQ(1, nc);
ASSERT_EQ(0, nd);
YellObj* o2 = get_object<YellObj>();
ASSERT_TRUE(o2);
ASSERT_EQ(2, nc);
ASSERT_EQ(0, nd);
return_object(o1);
ASSERT_EQ(2, nc);
ASSERT_EQ(0, nd);
return_object(o2);
ASSERT_EQ(2, nc);
ASSERT_EQ(0, nd);
}
ASSERT_EQ(0, nd);
clear_objects<YellObj>();
ASSERT_EQ(2, nd);
ASSERT_TRUE(ptr_set.empty()) << ptr_set.size();
}
TEST_F(ObjectPoolTest, validator) {
nfoo_dtor = 0;
int nfoo = 0;
for (int i = 0; i < 100; ++i) {
Foo* foo = get_object<Foo>();
if (foo) {
ASSERT_EQ(1, foo->x);
++nfoo;
}
}
ASSERT_EQ(nfoo + nfoo_dtor, 100);
ASSERT_EQ((size_t)nfoo, describe_objects<Foo>().item_num);
}
TEST_F(ObjectPoolTest, get_int) {
clear_objects<int>();
// Perf of this test is affected by previous case.
const size_t N = 100000;
butil::Timer tm;
// warm up
int* p = get_object<int>();
*p = 0;
return_object(p);
delete (new int);
tm.start();
for (size_t i = 0; i < N; ++i) {
*get_object<int>() = i;
}
tm.stop();
printf("get a int takes %.1fns\n", tm.n_elapsed()/(double)N);
tm.start();
for (size_t i = 0; i < N; ++i) {
*(new int) = i;
}
tm.stop();
printf("new a int takes %" PRId64 "ns\n", tm.n_elapsed()/N);
std::cout << describe_objects<int>() << std::endl;
clear_objects<int>();
std::cout << describe_objects<int>() << std::endl;
}
struct SilentObj {
char buf[sizeof(YellObj)];
};
TEST_F(ObjectPoolTest, get_perf) {
const size_t N = 10000;
std::vector<SilentObj*> new_list;
new_list.reserve(N);
butil::Timer tm1, tm2;
// warm up
return_object(get_object<SilentObj>());
delete (new SilentObj);
// Run twice, the second time will be must faster.
for (size_t j = 0; j < 2; ++j) {
tm1.start();
for (size_t i = 0; i < N; ++i) {
get_object<SilentObj>();
}
tm1.stop();
printf("get a SilentObj takes %" PRId64 "ns\n", tm1.n_elapsed()/N);
//clear_objects<SilentObj>(); // free all blocks
tm2.start();
for (size_t i = 0; i < N; ++i) {
new_list.push_back(new SilentObj);
}
tm2.stop();
printf("new a SilentObj takes %" PRId64 "ns\n", tm2.n_elapsed()/N);
for (size_t i = 0; i < new_list.size(); ++i) {
delete new_list[i];
}
new_list.clear();
}
std::cout << describe_objects<SilentObj>() << std::endl;
}
struct D { int val[1]; };
void* get_and_return_int(void*) {
// Perf of this test is affected by previous case.
const size_t N = 100000;
std::vector<D*> v;
v.reserve(N);
butil::Timer tm0, tm1, tm2;
D tmp = D();
int sr = 0;
// warm up
tm0.start();
return_object(get_object<D>());
tm0.stop();
printf("[%lu] warmup=%" PRId64 "\n", (size_t)pthread_self(), tm0.n_elapsed());
for (int j = 0; j < 5; ++j) {
v.clear();
sr = 0;
tm1.start();
for (size_t i = 0; i < N; ++i) {
D* p = get_object<D>();
*p = tmp;
v.push_back(p);
}
tm1.stop();
std::random_shuffle(v.begin(), v.end());
tm2.start();
for (size_t i = 0; i < v.size(); ++i) {
sr += return_object(v[i]);
}
tm2.stop();
if (0 != sr) {
printf("%d return_object failed\n", sr);
}
printf("[%lu:%d] get<D>=%.1f return<D>=%.1f\n",
(size_t)pthread_self(), j, tm1.n_elapsed()/(double)N,
tm2.n_elapsed()/(double)N);
}
return NULL;
}
void* new_and_delete_int(void*) {
const size_t N = 100000;
std::vector<D*> v2;
v2.reserve(N);
butil::Timer tm0, tm1, tm2;
D tmp = D();
for (int j = 0; j < 3; ++j) {
v2.clear();
// warm up
delete (new D);
tm1.start();
for (size_t i = 0; i < N; ++i) {
D *p = new D;
*p = tmp;
v2.push_back(p);
}
tm1.stop();
std::random_shuffle(v2.begin(), v2.end());
tm2.start();
for (size_t i = 0; i < v2.size(); ++i) {
delete v2[i];
}
tm2.stop();
printf("[%lu:%d] new<D>=%.1f delete<D>=%.1f\n",
(size_t)pthread_self(), j, tm1.n_elapsed()/(double)N,
tm2.n_elapsed()/(double)N);
}
return NULL;
}
TEST_F(ObjectPoolTest, get_and_return_int_single_thread) {
get_and_return_int(NULL);
new_and_delete_int(NULL);
}
TEST_F(ObjectPoolTest, get_and_return_int_multiple_threads) {
pthread_t tid[16];
for (size_t i = 0; i < ARRAY_SIZE(tid); ++i) {
ASSERT_EQ(0, pthread_create(&tid[i], NULL, get_and_return_int, NULL));
}
for (size_t i = 0; i < ARRAY_SIZE(tid); ++i) {
pthread_join(tid[i], NULL);
}
pthread_t tid2[16];
for (size_t i = 0; i < ARRAY_SIZE(tid2); ++i) {
ASSERT_EQ(0, pthread_create(&tid2[i], NULL, new_and_delete_int, NULL));
}
for (size_t i = 0; i < ARRAY_SIZE(tid2); ++i) {
pthread_join(tid2[i], NULL);
}
std::cout << describe_objects<D>() << std::endl;
clear_objects<D>();
ObjectPoolInfo info = describe_objects<D>();
ObjectPoolInfo zero_info = { 0, 0, 0, 0, ObjectPoolBlockMaxItem<D>::value,
ObjectPoolBlockMaxItem<D>::value, 0 };
ASSERT_EQ(0, memcmp(&info, &zero_info, sizeof(info)));
}
TEST_F(ObjectPoolTest, verify_get) {
clear_objects<int>();
std::cout << describe_objects<int>() << std::endl;
std::vector<int*> v;
v.reserve(100000);
for (int i = 0; (size_t)i < v.capacity(); ++i) {
int* p = get_object<int>();
*p = i;
v.push_back(p);
}
int i;
for (i = 0; (size_t)i < v.size() && *v[i] == i; ++i);
ASSERT_EQ(v.size(), (size_t)i) << "i=" << i << ", " << *v[i];
clear_objects<int>();
}
} // namespace