chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,424 @@
|
||||
// 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"
|
||||
#include "butil/fast_rand.h"
|
||||
|
||||
#define BAIDU_CLEAR_RESOURCE_POOL_AFTER_ALL_THREADS_QUIT
|
||||
#include "butil/resource_pool.h"
|
||||
|
||||
namespace {
|
||||
struct MyObject {};
|
||||
|
||||
int nfoo_dtor = 0;
|
||||
struct Foo {
|
||||
Foo() {
|
||||
x = butil::fast_rand() % 2;
|
||||
}
|
||||
~Foo() {
|
||||
++nfoo_dtor;
|
||||
}
|
||||
int x;
|
||||
};
|
||||
}
|
||||
|
||||
namespace butil {
|
||||
template <> struct ResourcePoolBlockMaxSize<MyObject> {
|
||||
static const size_t value = 128;
|
||||
};
|
||||
|
||||
template <> struct ResourcePoolBlockMaxItem<MyObject> {
|
||||
static const size_t value = 3;
|
||||
};
|
||||
|
||||
template <> struct ResourcePoolFreeChunkMaxItem<MyObject> {
|
||||
static size_t value() { return 5; }
|
||||
};
|
||||
|
||||
template <> struct ResourcePoolValidator<Foo> {
|
||||
static bool validate(const Foo* foo) {
|
||||
return foo->x != 0;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
using namespace butil;
|
||||
|
||||
class ResourcePoolTest : public ::testing::Test{
|
||||
protected:
|
||||
ResourcePoolTest(){
|
||||
};
|
||||
virtual ~ResourcePoolTest(){};
|
||||
virtual void SetUp() {
|
||||
srand(time(0));
|
||||
};
|
||||
virtual void TearDown() {
|
||||
};
|
||||
};
|
||||
|
||||
TEST_F(ResourcePoolTest, atomic_array_init) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
if (i == 0) {
|
||||
butil::atomic<int> a[2];
|
||||
a[0] = 1;
|
||||
// The folowing will cause compile error with gcc3.4.5 and the
|
||||
// reason is unknown
|
||||
// a[1] = 2;
|
||||
} else if (i == 2) {
|
||||
butil::atomic<int> a[2];
|
||||
ASSERT_EQ(0, a[0]);
|
||||
ASSERT_EQ(0, a[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(ResourcePoolTest, change_config) {
|
||||
int a[2];
|
||||
printf("%lu\n", ARRAY_SIZE(a));
|
||||
|
||||
ResourcePoolInfo info = describe_resources<MyObject>();
|
||||
ResourcePoolInfo zero_info = { 0, 0, 0, 0, 3, 3, 0 };
|
||||
ASSERT_EQ(0, memcmp(&info, &zero_info, sizeof(info)));
|
||||
|
||||
ResourceId<MyObject> id = { 0 };
|
||||
get_resource<MyObject>(&id);
|
||||
std::cout << describe_resources<MyObject>() << std::endl;
|
||||
return_resource(id);
|
||||
std::cout << describe_resources<MyObject>() << std::endl;
|
||||
}
|
||||
|
||||
struct NonDefaultCtorObject {
|
||||
explicit NonDefaultCtorObject(int value) : _value(value) {}
|
||||
NonDefaultCtorObject(int value, int dummy) : _value(value + dummy) {}
|
||||
|
||||
int _value;
|
||||
};
|
||||
|
||||
TEST_F(ResourcePoolTest, sanity) {
|
||||
ptr_set.clear();
|
||||
ResourceId<NonDefaultCtorObject> id0 = { 0 };
|
||||
get_resource<NonDefaultCtorObject>(&id0, 10);
|
||||
ASSERT_EQ(10, address_resource(id0)->_value);
|
||||
get_resource<NonDefaultCtorObject>(&id0, 100, 30);
|
||||
ASSERT_EQ(130, address_resource(id0)->_value);
|
||||
|
||||
printf("BLOCK_NITEM=%lu\n", ResourcePool<YellObj>::BLOCK_NITEM);
|
||||
|
||||
nc = 0;
|
||||
nd = 0;
|
||||
{
|
||||
ResourceId<YellObj> id1;
|
||||
YellObj* o1 = get_resource(&id1);
|
||||
ASSERT_TRUE(o1);
|
||||
ASSERT_EQ(o1, address_resource(id1));
|
||||
|
||||
ASSERT_EQ(1, nc);
|
||||
ASSERT_EQ(0, nd);
|
||||
|
||||
ResourceId<YellObj> id2;
|
||||
YellObj* o2 = get_resource(&id2);
|
||||
ASSERT_TRUE(o2);
|
||||
ASSERT_EQ(o2, address_resource(id2));
|
||||
|
||||
ASSERT_EQ(2, nc);
|
||||
ASSERT_EQ(0, nd);
|
||||
|
||||
return_resource(id1);
|
||||
ASSERT_EQ(2, nc);
|
||||
ASSERT_EQ(0, nd);
|
||||
|
||||
return_resource(id2);
|
||||
ASSERT_EQ(2, nc);
|
||||
ASSERT_EQ(0, nd);
|
||||
}
|
||||
ASSERT_EQ(0, nd);
|
||||
|
||||
clear_resources<YellObj>();
|
||||
ASSERT_EQ(2, nd);
|
||||
ASSERT_TRUE(ptr_set.empty()) << ptr_set.size();
|
||||
}
|
||||
|
||||
TEST_F(ResourcePoolTest, validator) {
|
||||
nfoo_dtor = 0;
|
||||
int nfoo = 0;
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
ResourceId<Foo> id = { 0 };
|
||||
Foo* foo = get_resource<Foo>(&id);
|
||||
if (foo) {
|
||||
ASSERT_EQ(1, foo->x);
|
||||
++nfoo;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(nfoo + nfoo_dtor, 100);
|
||||
ASSERT_EQ((size_t)nfoo, describe_resources<Foo>().item_num);
|
||||
}
|
||||
|
||||
TEST_F(ResourcePoolTest, get_int) {
|
||||
clear_resources<int>();
|
||||
|
||||
// Perf of this test is affected by previous case.
|
||||
const size_t N = 100000;
|
||||
|
||||
butil::Timer tm;
|
||||
ResourceId<int> id;
|
||||
|
||||
// warm up
|
||||
if (get_resource(&id)) {
|
||||
return_resource(id);
|
||||
}
|
||||
ASSERT_EQ(0UL, id);
|
||||
delete (new int);
|
||||
|
||||
tm.start();
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
*get_resource(&id) = 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 %luns\n", tm.n_elapsed()/N);
|
||||
|
||||
tm.start();
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
id.value = i;
|
||||
*ResourcePool<int>::unsafe_address_resource(id) = i;
|
||||
}
|
||||
tm.stop();
|
||||
printf("unsafe_address a int takes %.1fns\n", tm.n_elapsed()/(double)N);
|
||||
|
||||
tm.start();
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
id.value = i;
|
||||
*address_resource(id) = i;
|
||||
}
|
||||
tm.stop();
|
||||
printf("address a int takes %.1fns\n", tm.n_elapsed()/(double)N);
|
||||
|
||||
std::cout << describe_resources<int>() << std::endl;
|
||||
clear_resources<int>();
|
||||
std::cout << describe_resources<int>() << std::endl;
|
||||
}
|
||||
|
||||
|
||||
struct SilentObj {
|
||||
char buf[sizeof(YellObj)];
|
||||
};
|
||||
|
||||
TEST_F(ResourcePoolTest, get_perf) {
|
||||
const size_t N = 10000;
|
||||
std::vector<SilentObj*> new_list;
|
||||
new_list.reserve(N);
|
||||
ResourceId<SilentObj> id;
|
||||
|
||||
butil::Timer tm1, tm2;
|
||||
|
||||
// warm up
|
||||
if (get_resource(&id)) {
|
||||
return_resource(id);
|
||||
}
|
||||
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_resource(&id);
|
||||
}
|
||||
tm1.stop();
|
||||
printf("get a SilentObj takes %luns\n", tm1.n_elapsed()/N);
|
||||
//clear_resources<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 %luns\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_resources<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<ResourceId<D> > v;
|
||||
v.reserve(N);
|
||||
butil::Timer tm0, tm1, tm2;
|
||||
ResourceId<D> id = {0};
|
||||
D tmp = D();
|
||||
int sr = 0;
|
||||
|
||||
// warm up
|
||||
tm0.start();
|
||||
if (get_resource(&id)) {
|
||||
return_resource(id);
|
||||
}
|
||||
tm0.stop();
|
||||
|
||||
printf("[%lu] warmup=%lu\n", 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) {
|
||||
*get_resource(&id) = tmp;
|
||||
v.push_back(id);
|
||||
}
|
||||
tm1.stop();
|
||||
|
||||
std::random_shuffle(v.begin(), v.end());
|
||||
|
||||
tm2.start();
|
||||
for (size_t i = 0; i < v.size(); ++i) {
|
||||
sr += return_resource(v[i]);
|
||||
}
|
||||
tm2.stop();
|
||||
|
||||
if (0 != sr) {
|
||||
printf("%d return_resource failed\n", sr);
|
||||
}
|
||||
|
||||
printf("[%lu:%d] get<D>=%.1f return<D>=%.1f\n",
|
||||
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",
|
||||
pthread_self(), j, tm1.n_elapsed()/(double)N, tm2.n_elapsed()/(double)N);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TEST_F(ResourcePoolTest, get_and_return_int_single_thread) {
|
||||
get_and_return_int(NULL);
|
||||
new_and_delete_int(NULL);
|
||||
}
|
||||
|
||||
TEST_F(ResourcePoolTest, 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_resources<D>() << std::endl;
|
||||
clear_resources<D>();
|
||||
ResourcePoolInfo info = describe_resources<D>();
|
||||
ResourcePoolInfo zero_info = { 0, 0, 0, 0,
|
||||
ResourcePoolBlockMaxItem<D>::value,
|
||||
ResourcePoolBlockMaxItem<D>::value, 0};
|
||||
ASSERT_EQ(0, memcmp(&info, &zero_info, sizeof(info)));
|
||||
}
|
||||
|
||||
TEST_F(ResourcePoolTest, verify_get) {
|
||||
clear_resources<int>();
|
||||
std::cout << describe_resources<int>() << std::endl;
|
||||
|
||||
std::vector<std::pair<int*, ResourceId<int> > > v;
|
||||
v.reserve(100000);
|
||||
ResourceId<int> id = { 0 };
|
||||
for (int i = 0; (size_t)i < v.capacity(); ++i) {
|
||||
int* p = get_resource(&id);
|
||||
*p = i;
|
||||
v.push_back(std::make_pair(p, id));
|
||||
}
|
||||
int i;
|
||||
for (i = 0; (size_t)i < v.size() && *v[i].first == i; ++i);
|
||||
ASSERT_EQ(v.size(), (size_t)i);
|
||||
for (i = 0; (size_t)i < v.size() && v[i].second == (size_t)i; ++i);
|
||||
ASSERT_EQ(v.size(), (size_t)i) << "i=" << i << ", " << v[i].second;
|
||||
|
||||
clear_resources<int>();
|
||||
}
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user