// Copyright (c) 2014 Baidu, Inc. // Author: Ge,Jun (gejun@baidu.com) // Date: Sun Jul 13 15:04:18 CST 2014 #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