// 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 #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 { static const size_t value = 128; }; template <> struct ResourcePoolBlockMaxItem { static const size_t value = 3; }; template <> struct ResourcePoolFreeChunkMaxItem { static size_t value() { return 5; } }; template <> struct ResourcePoolValidator { 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 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 a[2]; ASSERT_EQ(0, a[0]); ASSERT_EQ(0, a[1]); } } } int nc = 0; int nd = 0; std::set 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(); ResourcePoolInfo zero_info = { 0, 0, 0, 0, 3, 3, 0 }; ASSERT_EQ(0, memcmp(&info, &zero_info, sizeof(info))); ResourceId id = { 0 }; get_resource(&id); std::cout << describe_resources() << std::endl; return_resource(id); std::cout << describe_resources() << 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 id0 = { 0 }; get_resource(&id0, 10); ASSERT_EQ(10, address_resource(id0)->_value); get_resource(&id0, 100, 30); ASSERT_EQ(130, address_resource(id0)->_value); printf("BLOCK_NITEM=%lu\n", ResourcePool::BLOCK_NITEM); nc = 0; nd = 0; { ResourceId id1; YellObj* o1 = get_resource(&id1); ASSERT_TRUE(o1); ASSERT_EQ(o1, address_resource(id1)); ASSERT_EQ(1, nc); ASSERT_EQ(0, nd); ResourceId 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(); 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 id = { 0 }; Foo* foo = get_resource(&id); if (foo) { ASSERT_EQ(1, foo->x); ++nfoo; } } ASSERT_EQ(nfoo + nfoo_dtor, 100); ASSERT_EQ((size_t)nfoo, describe_resources().item_num); } TEST_F(ResourcePoolTest, get_int) { clear_resources(); // Perf of this test is affected by previous case. const size_t N = 100000; butil::Timer tm; ResourceId 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::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() << std::endl; clear_resources(); std::cout << describe_resources() << std::endl; } struct SilentObj { char buf[sizeof(YellObj)]; }; TEST_F(ResourcePoolTest, get_perf) { const size_t N = 10000; std::vector new_list; new_list.reserve(N); ResourceId 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(); // 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() << 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 > v; v.reserve(N); butil::Timer tm0, tm1, tm2; ResourceId 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=%.1f return=%.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 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=%.1f delete=%.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() << std::endl; clear_resources(); ResourcePoolInfo info = describe_resources(); ResourcePoolInfo zero_info = { 0, 0, 0, 0, ResourcePoolBlockMaxItem::value, ResourcePoolBlockMaxItem::value, 0}; ASSERT_EQ(0, memcmp(&info, &zero_info, sizeof(info))); } TEST_F(ResourcePoolTest, verify_get) { clear_resources(); std::cout << describe_resources() << std::endl; std::vector > > v; v.reserve(100000); ResourceId 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(); } } // namespace