// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- // Copyright (c) 2005, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // --- // Unittest for the TCMalloc implementation. // // * The test consists of a set of threads. // * Each thread maintains a set of allocated objects, with // a bound on the total amount of data in the set. // * Each allocated object's contents are generated by // hashing the object pointer, and a generation count // in the object. This allows us to easily check for // data corruption. // * At any given step, the thread can do any of the following: // a. Allocate an object // b. Increment an object's generation count and update // its contents. // c. Pass the object to another thread // d. Free an object // Also, at the end of every step, object(s) are freed to maintain // the memory upper-bound. // // If this test is compiled with -DDEBUGALLOCATION, then we don't // run some tests that test the inner workings of tcmalloc and // break on debugallocation: that certain allocations are aligned // in a certain way (even though no standard requires it), and that // realloc() tries to minimize copying (which debug allocators don't // care about). #include "config_for_unittests.h" // Complicated ordering requirements. tcmalloc.h defines (indirectly) // _POSIX_C_SOURCE, which it needs so stdlib.h defines posix_memalign. // unistd.h, on the other hand, requires _POSIX_C_SOURCE to be unset, // at least on FreeBSD, in order to define sbrk. The solution // is to #include unistd.h first. This is safe because unistd.h // doesn't sub-include stdlib.h, so we'll still get posix_memalign // when we #include stdlib.h. Blah. #ifdef HAVE_UNISTD_H #include // for testing sbrk hooks #endif #include "tcmalloc.h" // must come early, to pick up posix_memalign #include #include #include #if defined HAVE_STDINT_H #include // for intptr_t #endif #include // for size_t #ifdef HAVE_FCNTL_H #include // for open; used with mmap-hook test #endif #ifdef HAVE_MMAP #include // for testing mmap hooks #endif #ifdef HAVE_MALLOC_H #include // defines pvalloc/etc on cygwin #endif #include #include #include #include #include #include "base/logging.h" #include "base/simple_mutex.h" #include "gperftools/malloc_hook.h" #include "gperftools/malloc_extension.h" #include "gperftools/tcmalloc.h" #include "thread_cache.h" #include "system-alloc.h" #include "tests/testutil.h" // Windows doesn't define pvalloc and a few other obsolete unix // functions; nor does it define posix_memalign (which is not obsolete). #if defined(_WIN32) # define cfree free // don't bother to try to test these obsolete fns # define valloc malloc # define pvalloc malloc // I'd like to map posix_memalign to _aligned_malloc, but _aligned_malloc // must be paired with _aligned_free (not normal free), which is too // invasive a change to how we allocate memory here. So just bail static bool kOSSupportsMemalign = false; static inline void* Memalign(size_t align, size_t size) { //LOG(FATAL) << "memalign not supported on windows"; exit(1); return NULL; } static inline int PosixMemalign(void** ptr, size_t align, size_t size) { //LOG(FATAL) << "posix_memalign not supported on windows"; exit(1); return -1; } // OS X defines posix_memalign in some OS versions but not others; // it's confusing enough to check that it's easiest to just not to test. #elif defined(__APPLE__) static bool kOSSupportsMemalign = false; static inline void* Memalign(size_t align, size_t size) { //LOG(FATAL) << "memalign not supported on OS X"; exit(1); return NULL; } static inline int PosixMemalign(void** ptr, size_t align, size_t size) { //LOG(FATAL) << "posix_memalign not supported on OS X"; exit(1); return -1; } #else static bool kOSSupportsMemalign = true; static inline void* Memalign(size_t align, size_t size) { return memalign(align, size); } static inline int PosixMemalign(void** ptr, size_t align, size_t size) { return posix_memalign(ptr, align, size); } #endif // On systems (like freebsd) that don't define MAP_ANONYMOUS, use the old // form of the name instead. #ifndef MAP_ANONYMOUS # define MAP_ANONYMOUS MAP_ANON #endif #define LOGSTREAM stdout using std::vector; using std::string; DECLARE_double(tcmalloc_release_rate); DECLARE_int32(max_free_queue_size); // in debugallocation.cc DECLARE_int64(tcmalloc_sample_parameter); namespace testing { static const int FLAGS_numtests = 50000; static const int FLAGS_log_every_n_tests = 50000; // log exactly once // Testing parameters static const int FLAGS_lgmaxsize = 16; // lg() of the max size object to alloc static const int FLAGS_numthreads = 10; // Number of threads static const int FLAGS_threadmb = 4; // Max memory size allocated by thread static const int FLAGS_lg_max_memalign = 18; // lg of max alignment for memalign static const double FLAGS_memalign_min_fraction = 0; // min expected% static const double FLAGS_memalign_max_fraction = 0.4; // max expected% static const double FLAGS_memalign_max_alignment_ratio = 6; // alignment/size // Weights of different operations static const int FLAGS_allocweight = 50; // Weight for picking allocation static const int FLAGS_freeweight = 50; // Weight for picking free static const int FLAGS_updateweight = 10; // Weight for picking update static const int FLAGS_passweight = 1; // Weight for passing object static const int kSizeBits = 8 * sizeof(size_t); static const size_t kMaxSize = ~static_cast(0); static const size_t kMaxSignedSize = ((size_t(1) << (kSizeBits-1)) - 1); static const size_t kNotTooBig = 100000; // We want an allocation that is definitely more than main memory. OS // X has special logic to discard very big allocs before even passing // the request along to the user-defined memory allocator; we're not // interested in testing their logic, so we have to make sure we're // not *too* big. static const size_t kTooBig = kMaxSize - 100000; static int news_handled = 0; // Global array of threads class TesterThread; static TesterThread** threads; // To help with generating random numbers class TestHarness { private: // Information kept per type struct Type { string name; int type; int weight; }; public: TestHarness(int seed) : types_(new vector), total_weight_(0), num_tests_(0) { srandom(seed); } ~TestHarness() { delete types_; } // Add operation type with specified weight. When starting a new // iteration, an operation type is picked with probability // proportional to its weight. // // "type" must be non-negative. // "weight" must be non-negative. void AddType(int type, int weight, const char* name); // Call this to get the type of operation for the next iteration. // It returns a random operation type from the set of registered // operations. Returns -1 if tests should finish. int PickType(); // If n == 0, returns the next pseudo-random number in the range [0 .. 0] // If n != 0, returns the next pseudo-random number in the range [0 .. n) int Uniform(int n) { if (n == 0) { return random() * 0; } else { return random() % n; } } // Pick "base" uniformly from range [0,max_log] and then return // "base" random bits. The effect is to pick a number in the range // [0,2^max_log-1] with bias towards smaller numbers. int Skewed(int max_log) { const int base = random() % (max_log+1); return random() % (1 << base); } private: vector* types_; // Registered types int total_weight_; // Total weight of all types int num_tests_; // Num tests run so far }; void TestHarness::AddType(int type, int weight, const char* name) { Type t; t.name = name; t.type = type; t.weight = weight; types_->push_back(t); total_weight_ += weight; } int TestHarness::PickType() { if (num_tests_ >= FLAGS_numtests) return -1; num_tests_++; assert(total_weight_ > 0); // This is a little skewed if total_weight_ doesn't divide 2^31, but it's close int v = Uniform(total_weight_); int i; for (i = 0; i < types_->size(); i++) { v -= (*types_)[i].weight; if (v < 0) { break; } } assert(i < types_->size()); if ((num_tests_ % FLAGS_log_every_n_tests) == 0) { fprintf(LOGSTREAM, " Test %d out of %d: %s\n", num_tests_, FLAGS_numtests, (*types_)[i].name.c_str()); } return (*types_)[i].type; } class AllocatorState : public TestHarness { public: explicit AllocatorState(int seed) : TestHarness(seed), memalign_fraction_(0) { if (kOSSupportsMemalign) { CHECK_GE(FLAGS_memalign_max_fraction, 0); CHECK_LE(FLAGS_memalign_max_fraction, 1); CHECK_GE(FLAGS_memalign_min_fraction, 0); CHECK_LE(FLAGS_memalign_min_fraction, 1); double delta = FLAGS_memalign_max_fraction - FLAGS_memalign_min_fraction; CHECK_GE(delta, 0); memalign_fraction_ = (Uniform(10000)/10000.0 * delta + FLAGS_memalign_min_fraction); //fprintf(LOGSTREAM, "memalign fraction: %f\n", memalign_fraction_); } } virtual ~AllocatorState() {} // Allocate memory. Randomly choose between malloc() or posix_memalign(). void* alloc(size_t size) { if (Uniform(100) < memalign_fraction_ * 100) { // Try a few times to find a reasonable alignment, or fall back on malloc. for (int i = 0; i < 5; i++) { size_t alignment = 1 << Uniform(FLAGS_lg_max_memalign); if (alignment >= sizeof(intptr_t) && (size < sizeof(intptr_t) || alignment < FLAGS_memalign_max_alignment_ratio * size)) { void *result = reinterpret_cast(static_cast(0x1234)); int err = PosixMemalign(&result, alignment, size); if (err != 0) { CHECK_EQ(err, ENOMEM); } return err == 0 ? result : NULL; } } } return malloc(size); } private: double memalign_fraction_; }; // Info kept per thread class TesterThread { private: // Info kept per allocated object struct Object { char* ptr; // Allocated pointer int size; // Allocated size int generation; // Generation counter of object contents }; Mutex lock_; // For passing in another thread's obj int id_; // My thread id AllocatorState rnd_; // For generating random numbers vector heap_; // This thread's heap vector passed_; // Pending objects passed from others size_t heap_size_; // Current heap size int locks_ok_; // Number of OK TryLock() ops int locks_failed_; // Number of failed TryLock() ops // Type of operations enum Type { ALLOC, FREE, UPDATE, PASS }; // ACM minimal standard random number generator. (re-entrant.) class ACMRandom { int32 seed_; public: explicit ACMRandom(int32 seed) { seed_ = seed; } int32 Next() { const int32 M = 2147483647L; // 2^31-1 const int32 A = 16807; // In effect, we are computing seed_ = (seed_ * A) % M, where M = 2^31-1 uint32 lo = A * (int32)(seed_ & 0xFFFF); uint32 hi = A * (int32)((uint32)seed_ >> 16); lo += (hi & 0x7FFF) << 16; if (lo > M) { lo &= M; ++lo; } lo += hi >> 15; if (lo > M) { lo &= M; ++lo; } return (seed_ = (int32) lo); } }; public: TesterThread(int id) : id_(id), rnd_(id+1), heap_size_(0), locks_ok_(0), locks_failed_(0) { } virtual ~TesterThread() { if (FLAGS_verbose) fprintf(LOGSTREAM, "Thread %2d: locks %6d ok; %6d trylocks failed\n", id_, locks_ok_, locks_failed_); if (locks_ok_ + locks_failed_ >= 1000) { CHECK_LE(locks_failed_, locks_ok_ / 2); } } virtual void Run() { rnd_.AddType(ALLOC, FLAGS_allocweight, "allocate"); rnd_.AddType(FREE, FLAGS_freeweight, "free"); rnd_.AddType(UPDATE, FLAGS_updateweight, "update"); rnd_.AddType(PASS, FLAGS_passweight, "pass"); while (true) { AcquirePassedObjects(); switch (rnd_.PickType()) { case ALLOC: AllocateObject(); break; case FREE: FreeObject(); break; case UPDATE: UpdateObject(); break; case PASS: PassObject(); break; case -1: goto done; default: assert(NULL == "Unknown type"); } ShrinkHeap(); } done: DeleteHeap(); } // Allocate a new object void AllocateObject() { Object object; object.size = rnd_.Skewed(FLAGS_lgmaxsize); object.ptr = static_cast(rnd_.alloc(object.size)); CHECK(object.ptr); object.generation = 0; FillContents(&object); heap_.push_back(object); heap_size_ += object.size; } // Mutate a random object void UpdateObject() { if (heap_.empty()) return; const int index = rnd_.Uniform(heap_.size()); CheckContents(heap_[index]); heap_[index].generation++; FillContents(&heap_[index]); } // Free a random object void FreeObject() { if (heap_.empty()) return; const int index = rnd_.Uniform(heap_.size()); Object object = heap_[index]; CheckContents(object); free(object.ptr); heap_size_ -= object.size; heap_[index] = heap_[heap_.size()-1]; heap_.pop_back(); } // Delete all objects in the heap void DeleteHeap() { while (!heap_.empty()) { FreeObject(); } } // Free objects until our heap is small enough void ShrinkHeap() { while (heap_size_ > FLAGS_threadmb << 20) { assert(!heap_.empty()); FreeObject(); } } // Pass a random object to another thread void PassObject() { // Pick object to pass if (heap_.empty()) return; const int index = rnd_.Uniform(heap_.size()); Object object = heap_[index]; CheckContents(object); // Pick thread to pass const int tid = rnd_.Uniform(FLAGS_numthreads); TesterThread* thread = threads[tid]; if (thread->lock_.TryLock()) { // Pass the object locks_ok_++; thread->passed_.push_back(object); thread->lock_.Unlock(); heap_size_ -= object.size; heap_[index] = heap_[heap_.size()-1]; heap_.pop_back(); } else { locks_failed_++; } } // Grab any objects passed to this thread by another thread void AcquirePassedObjects() { // We do not create unnecessary contention by always using // TryLock(). Plus we unlock immediately after swapping passed // objects into a local vector. vector copy; { // Locking scope if (!lock_.TryLock()) { locks_failed_++; return; } locks_ok_++; swap(copy, passed_); lock_.Unlock(); } for (int i = 0; i < copy.size(); ++i) { const Object& object = copy[i]; CheckContents(object); heap_.push_back(object); heap_size_ += object.size; } } // Fill object contents according to ptr/generation void FillContents(Object* object) { ACMRandom r(reinterpret_cast(object->ptr) & 0x7fffffff); for (int i = 0; i < object->generation; ++i) { r.Next(); } const char c = static_cast(r.Next()); memset(object->ptr, c, object->size); } // Check object contents void CheckContents(const Object& object) { ACMRandom r(reinterpret_cast(object.ptr) & 0x7fffffff); for (int i = 0; i < object.generation; ++i) { r.Next(); } // For large objects, we just check a prefix/suffix const char expected = static_cast(r.Next()); const int limit1 = object.size < 32 ? object.size : 32; const int start2 = limit1 > object.size - 32 ? limit1 : object.size - 32; for (int i = 0; i < limit1; ++i) { CHECK_EQ(object.ptr[i], expected); } for (int i = start2; i < object.size; ++i) { CHECK_EQ(object.ptr[i], expected); } } }; static void RunThread(int thread_id) { threads[thread_id]->Run(); } static void TryHugeAllocation(size_t s, AllocatorState* rnd) { void* p = rnd->alloc(s); CHECK(p == NULL); // huge allocation s should fail! } static void TestHugeAllocations(AllocatorState* rnd) { // Check that asking for stuff tiny bit smaller than largest possible // size returns NULL. for (size_t i = 0; i < 70000; i += rnd->Uniform(20)) { TryHugeAllocation(kMaxSize - i, rnd); } // Asking for memory sizes near signed/unsigned boundary (kMaxSignedSize) // might work or not, depending on the amount of virtual memory. #ifndef DEBUGALLOCATION // debug allocation takes forever for huge allocs for (size_t i = 0; i < 100; i++) { void* p = NULL; p = rnd->alloc(kMaxSignedSize + i); if (p) free(p); // if: free(NULL) is not necessarily defined p = rnd->alloc(kMaxSignedSize - i); if (p) free(p); } #endif // Check that ReleaseFreeMemory has no visible effect (aka, does not // crash the test): MallocExtension* inst = MallocExtension::instance(); CHECK(inst); inst->ReleaseFreeMemory(); } static void TestCalloc(size_t n, size_t s, bool ok) { char* p = reinterpret_cast(calloc(n, s)); if (FLAGS_verbose) fprintf(LOGSTREAM, "calloc(%" PRIxS ", %" PRIxS "): %p\n", n, s, p); if (!ok) { CHECK(p == NULL); // calloc(n, s) should not succeed } else { CHECK(p != NULL); // calloc(n, s) should succeed for (int i = 0; i < n*s; i++) { CHECK(p[i] == '\0'); } free(p); } } // This makes sure that reallocing a small number of bytes in either // direction doesn't cause us to allocate new memory. static void TestRealloc() { #ifndef DEBUGALLOCATION // debug alloc doesn't try to minimize reallocs // When sampling, we always allocate in units of page-size, which // makes reallocs of small sizes do extra work (thus, failing these // checks). Since sampling is random, we turn off sampling to make // sure that doesn't happen to us here. const int64 old_sample_parameter = FLAGS_tcmalloc_sample_parameter; FLAGS_tcmalloc_sample_parameter = 0; // turn off sampling int start_sizes[] = { 100, 1000, 10000, 100000 }; int deltas[] = { 1, -2, 4, -8, 16, -32, 64, -128 }; for (int s = 0; s < sizeof(start_sizes)/sizeof(*start_sizes); ++s) { void* p = malloc(start_sizes[s]); CHECK(p); // The larger the start-size, the larger the non-reallocing delta. for (int d = 0; d < (s+1) * 2; ++d) { void* new_p = realloc(p, start_sizes[s] + deltas[d]); CHECK(p == new_p); // realloc should not allocate new memory } // Test again, but this time reallocing smaller first. for (int d = 0; d < s*2; ++d) { void* new_p = realloc(p, start_sizes[s] - deltas[d]); CHECK(p == new_p); // realloc should not allocate new memory } free(p); } FLAGS_tcmalloc_sample_parameter = old_sample_parameter; #endif } static void TestNewHandler() throw (std::bad_alloc) { ++news_handled; throw std::bad_alloc(); } static void TestOneNew(void* (*func)(size_t)) { // success test try { void* ptr = (*func)(kNotTooBig); if (0 == ptr) { fprintf(LOGSTREAM, "allocation should not have failed.\n"); abort(); } } catch (...) { fprintf(LOGSTREAM, "allocation threw unexpected exception.\n"); abort(); } // failure test // we should always receive a bad_alloc exception try { (*func)(kTooBig); fprintf(LOGSTREAM, "allocation should have failed.\n"); abort(); } catch (const std::bad_alloc&) { // correct } catch (...) { fprintf(LOGSTREAM, "allocation threw unexpected exception.\n"); abort(); } } static void TestNew(void* (*func)(size_t)) { news_handled = 0; // test without new_handler: std::new_handler saved_handler = std::set_new_handler(0); TestOneNew(func); // test with new_handler: std::set_new_handler(TestNewHandler); TestOneNew(func); if (news_handled != 1) { fprintf(LOGSTREAM, "new_handler was not called.\n"); abort(); } std::set_new_handler(saved_handler); } static void TestOneNothrowNew(void* (*func)(size_t, const std::nothrow_t&)) { // success test try { void* ptr = (*func)(kNotTooBig, std::nothrow); if (0 == ptr) { fprintf(LOGSTREAM, "allocation should not have failed.\n"); abort(); } } catch (...) { fprintf(LOGSTREAM, "allocation threw unexpected exception.\n"); abort(); } // failure test // we should always receive a bad_alloc exception try { if ((*func)(kTooBig, std::nothrow) != 0) { fprintf(LOGSTREAM, "allocation should have failed.\n"); abort(); } } catch (...) { fprintf(LOGSTREAM, "nothrow allocation threw unexpected exception.\n"); abort(); } } static void TestNothrowNew(void* (*func)(size_t, const std::nothrow_t&)) { news_handled = 0; // test without new_handler: std::new_handler saved_handler = std::set_new_handler(0); TestOneNothrowNew(func); // test with new_handler: std::set_new_handler(TestNewHandler); TestOneNothrowNew(func); if (news_handled != 1) { fprintf(LOGSTREAM, "nothrow new_handler was not called.\n"); abort(); } std::set_new_handler(saved_handler); } // These are used as callbacks by the sanity-check. Set* and Reset* // register the hook that counts how many times the associated memory // function is called. After each such call, call Verify* to verify // that we used the tcmalloc version of the call, and not the libc. // Note the ... in the hook signature: we don't care what arguments // the hook takes. #define MAKE_HOOK_CALLBACK(hook_type, ...) \ static volatile int g_##hook_type##_calls = 0; \ static void IncrementCallsTo##hook_type(__VA_ARGS__) { \ g_##hook_type##_calls++; \ } \ static void Verify##hook_type##WasCalled() { \ CHECK_GT(g_##hook_type##_calls, 0); \ g_##hook_type##_calls = 0; /* reset for next call */ \ } \ static void Set##hook_type() { \ CHECK(MallocHook::Add##hook_type( \ (MallocHook::hook_type)&IncrementCallsTo##hook_type)); \ } \ static void Reset##hook_type() { \ CHECK(MallocHook::Remove##hook_type( \ (MallocHook::hook_type)&IncrementCallsTo##hook_type)); \ } // We do one for each hook typedef in malloc_hook.h MAKE_HOOK_CALLBACK(NewHook, const void*, size_t); MAKE_HOOK_CALLBACK(DeleteHook, const void*); MAKE_HOOK_CALLBACK(MmapHook, const void*, const void*, size_t, int, int, int, off_t); MAKE_HOOK_CALLBACK(MremapHook, const void*, const void*, size_t, size_t, int, const void*); MAKE_HOOK_CALLBACK(MunmapHook, const void *, size_t); MAKE_HOOK_CALLBACK(SbrkHook, const void *, ptrdiff_t); static void TestAlignmentForSize(int size) { fprintf(LOGSTREAM, "Testing alignment of malloc(%d)\n", size); static const int kNum = 100; void* ptrs[kNum]; for (int i = 0; i < kNum; i++) { ptrs[i] = malloc(size); uintptr_t p = reinterpret_cast(ptrs[i]); CHECK((p % sizeof(void*)) == 0); CHECK((p % sizeof(double)) == 0); // Must have 16-byte (or 8-byte in case of -DTCMALLOC_ALIGN_8BYTES) // alignment for large enough objects if (size >= kMinAlign) { CHECK((p % kMinAlign) == 0); } } for (int i = 0; i < kNum; i++) { free(ptrs[i]); } } static void TestMallocAlignment() { for (int lg = 0; lg < 16; lg++) { TestAlignmentForSize((1<(arg); if (state->ptr >= r->address && state->ptr < r->address + r->length) { if (state->expected_type == base::MallocRange::FREE) { // We are expecting r->type == FREE, but ReleaseMemory // may have already moved us to UNMAPPED state instead (this happens in // approximately 0.1% of executions). Accept either state. CHECK(r->type == base::MallocRange::FREE || r->type == base::MallocRange::UNMAPPED); } else { CHECK_EQ(r->type, state->expected_type); } CHECK_GE(r->length, state->min_size); state->matched = true; } } // Check that at least one of the callbacks from Ranges() contains // the specified address with the specified type, and has size // >= min_size. static void CheckRangeCallback(void* ptr, base::MallocRange::Type type, size_t min_size) { RangeCallbackState state; state.ptr = reinterpret_cast(ptr); state.expected_type = type; state.min_size = min_size; state.matched = false; MallocExtension::instance()->Ranges(&state, RangeCallback); CHECK(state.matched); } } static bool HaveSystemRelease = TCMalloc_SystemRelease(TCMalloc_SystemAlloc(kPageSize, NULL, 0), kPageSize); static void TestRanges() { static const int MB = 1048576; void* a = malloc(MB); void* b = malloc(MB); base::MallocRange::Type releasedType = HaveSystemRelease ? base::MallocRange::UNMAPPED : base::MallocRange::FREE; CheckRangeCallback(a, base::MallocRange::INUSE, MB); CheckRangeCallback(b, base::MallocRange::INUSE, MB); free(a); CheckRangeCallback(a, base::MallocRange::FREE, MB); CheckRangeCallback(b, base::MallocRange::INUSE, MB); MallocExtension::instance()->ReleaseFreeMemory(); CheckRangeCallback(a, releasedType, MB); CheckRangeCallback(b, base::MallocRange::INUSE, MB); free(b); CheckRangeCallback(a, releasedType, MB); CheckRangeCallback(b, base::MallocRange::FREE, MB); } #ifndef DEBUGALLOCATION static size_t GetUnmappedBytes() { size_t bytes; CHECK(MallocExtension::instance()->GetNumericProperty( "tcmalloc.pageheap_unmapped_bytes", &bytes)); return bytes; } #endif class AggressiveDecommitChanger { size_t old_value_; public: AggressiveDecommitChanger(size_t new_value) { MallocExtension *inst = MallocExtension::instance(); bool rv = inst->GetNumericProperty("tcmalloc.aggressive_memory_decommit", &old_value_); CHECK_CONDITION(rv); rv = inst->SetNumericProperty("tcmalloc.aggressive_memory_decommit", new_value); CHECK_CONDITION(rv); } ~AggressiveDecommitChanger() { MallocExtension *inst = MallocExtension::instance(); bool rv = inst->SetNumericProperty("tcmalloc.aggressive_memory_decommit", old_value_); CHECK_CONDITION(rv); } }; static void TestReleaseToSystem() { // Debug allocation mode adds overhead to each allocation which // messes up all the equality tests here. I just disable the // teset in this mode. TODO(csilvers): get it to work for debugalloc? #ifndef DEBUGALLOCATION if(!HaveSystemRelease) return; const double old_tcmalloc_release_rate = FLAGS_tcmalloc_release_rate; FLAGS_tcmalloc_release_rate = 0; AggressiveDecommitChanger disabler(0); static const int MB = 1048576; void* a = malloc(MB); void* b = malloc(MB); MallocExtension::instance()->ReleaseFreeMemory(); size_t starting_bytes = GetUnmappedBytes(); // Calling ReleaseFreeMemory() a second time shouldn't do anything. MallocExtension::instance()->ReleaseFreeMemory(); EXPECT_EQ(starting_bytes, GetUnmappedBytes()); // ReleaseToSystem shouldn't do anything either. MallocExtension::instance()->ReleaseToSystem(MB); EXPECT_EQ(starting_bytes, GetUnmappedBytes()); free(a); // The span to release should be 1MB. MallocExtension::instance()->ReleaseToSystem(MB/2); EXPECT_EQ(starting_bytes + MB, GetUnmappedBytes()); // Should do nothing since the previous call released too much. MallocExtension::instance()->ReleaseToSystem(MB/4); EXPECT_EQ(starting_bytes + MB, GetUnmappedBytes()); free(b); // Use up the extra MB/4 bytes from 'a' and also release 'b'. MallocExtension::instance()->ReleaseToSystem(MB/2); EXPECT_EQ(starting_bytes + 2*MB, GetUnmappedBytes()); // Should do nothing since the previous call released too much. MallocExtension::instance()->ReleaseToSystem(MB/2); EXPECT_EQ(starting_bytes + 2*MB, GetUnmappedBytes()); // Nothing else to release. MallocExtension::instance()->ReleaseFreeMemory(); EXPECT_EQ(starting_bytes + 2*MB, GetUnmappedBytes()); a = malloc(MB); free(a); EXPECT_EQ(starting_bytes + MB, GetUnmappedBytes()); // Releasing less than a page should still trigger a release. MallocExtension::instance()->ReleaseToSystem(1); EXPECT_EQ(starting_bytes + 2*MB, GetUnmappedBytes()); FLAGS_tcmalloc_release_rate = old_tcmalloc_release_rate; #endif // #ifndef DEBUGALLOCATION } static void TestAggressiveDecommit() { // Debug allocation mode adds overhead to each allocation which // messes up all the equality tests here. I just disable the // teset in this mode. #ifndef DEBUGALLOCATION if(!HaveSystemRelease) return; fprintf(LOGSTREAM, "Testing aggressive de-commit\n"); AggressiveDecommitChanger enabler(1); static const int MB = 1048576; void* a = malloc(MB); void* b = malloc(MB); size_t starting_bytes = GetUnmappedBytes(); // ReleaseToSystem shouldn't do anything either. MallocExtension::instance()->ReleaseToSystem(MB); EXPECT_EQ(starting_bytes, GetUnmappedBytes()); free(a); // The span to release should be 1MB. EXPECT_EQ(starting_bytes + MB, GetUnmappedBytes()); free(b); EXPECT_EQ(starting_bytes + 2*MB, GetUnmappedBytes()); // Nothing else to release. MallocExtension::instance()->ReleaseFreeMemory(); EXPECT_EQ(starting_bytes + 2*MB, GetUnmappedBytes()); a = malloc(MB); free(a); EXPECT_EQ(starting_bytes + 2*MB, GetUnmappedBytes()); fprintf(LOGSTREAM, "Done testing aggressive de-commit\n"); #endif // #ifndef DEBUGALLOCATION } // On MSVC10, in release mode, the optimizer convinces itself // g_no_memory is never changed (I guess it doesn't realize OnNoMemory // might be called). Work around this by setting the var volatile. volatile bool g_no_memory = false; std::new_handler g_old_handler = NULL; static void OnNoMemory() { g_no_memory = true; std::set_new_handler(g_old_handler); } static void TestSetNewMode() { int old_mode = tc_set_new_mode(1); g_old_handler = std::set_new_handler(&OnNoMemory); g_no_memory = false; void* ret = malloc(kTooBig); EXPECT_EQ(NULL, ret); EXPECT_TRUE(g_no_memory); g_old_handler = std::set_new_handler(&OnNoMemory); g_no_memory = false; ret = calloc(1, kTooBig); EXPECT_EQ(NULL, ret); EXPECT_TRUE(g_no_memory); g_old_handler = std::set_new_handler(&OnNoMemory); g_no_memory = false; ret = realloc(NULL, kTooBig); EXPECT_EQ(NULL, ret); EXPECT_TRUE(g_no_memory); if (kOSSupportsMemalign) { // Not really important, but must be small enough such that // kAlignment + kTooBig does not overflow. const int kAlignment = 1 << 5; g_old_handler = std::set_new_handler(&OnNoMemory); g_no_memory = false; ret = Memalign(kAlignment, kTooBig); EXPECT_EQ(NULL, ret); EXPECT_TRUE(g_no_memory); g_old_handler = std::set_new_handler(&OnNoMemory); g_no_memory = false; EXPECT_EQ(ENOMEM, PosixMemalign(&ret, kAlignment, kTooBig)); EXPECT_EQ(NULL, ret); EXPECT_TRUE(g_no_memory); } tc_set_new_mode(old_mode); } static void TestErrno(void) { void* ret; if (kOSSupportsMemalign) { errno = 0; ret = Memalign(128, kTooBig); EXPECT_EQ(NULL, ret); EXPECT_EQ(ENOMEM, errno); } errno = 0; ret = malloc(kTooBig); EXPECT_EQ(NULL, ret); EXPECT_EQ(ENOMEM, errno); errno = 0; ret = tc_malloc_skip_new_handler(kTooBig); EXPECT_EQ(NULL, ret); EXPECT_EQ(ENOMEM, errno); } static int RunAllTests(int argc, char** argv) { // Optional argv[1] is the seed AllocatorState rnd(argc > 1 ? atoi(argv[1]) : 100); SetTestResourceLimit(); // TODO(odo): This test has been disabled because it is only by luck that it // does not result in fragmentation. When tcmalloc makes an allocation which // spans previously unused leaves of the pagemap it will allocate and fill in // the leaves to cover the new allocation. The leaves happen to be 256MiB in // the 64-bit build, and with the sbrk allocator these allocations just // happen to fit in one leaf by luck. With other allocators (mmap, // memfs_malloc when used with small pages) the allocations generally span // two leaves and this results in a very bad fragmentation pattern with this // code. The same failure can be forced with the sbrk allocator just by // allocating something on the order of 128MiB prior to starting this test so // that the test allocations straddle a 256MiB boundary. // TODO(csilvers): port MemoryUsage() over so the test can use that #if 0 # include // for getpid() // Allocate and deallocate blocks of increasing sizes to check if the alloc // metadata fragments the memory. (Do not put other allocations/deallocations // before this test, it may break). { size_t memory_usage = MemoryUsage(getpid()); fprintf(LOGSTREAM, "Testing fragmentation\n"); for ( int i = 200; i < 240; ++i ) { int size = i << 20; void *test1 = rnd.alloc(size); CHECK(test1); for ( int j = 0; j < size; j += (1 << 12) ) { static_cast(test1)[j] = 1; } free(test1); } // There may still be a bit of fragmentation at the beginning, until we // reach kPageMapBigAllocationThreshold bytes so we check for // 200 + 240 + margin. CHECK_LT(MemoryUsage(getpid()), memory_usage + (450 << 20) ); } #endif // Check that empty allocation works fprintf(LOGSTREAM, "Testing empty allocation\n"); { void* p1 = rnd.alloc(0); CHECK(p1 != NULL); void* p2 = rnd.alloc(0); CHECK(p2 != NULL); CHECK(p1 != p2); free(p1); free(p2); } // This code stresses some of the memory allocation via STL. // It may call operator delete(void*, nothrow_t). fprintf(LOGSTREAM, "Testing STL use\n"); { std::vector v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(0); std::stable_sort(v.begin(), v.end()); } // Test each of the memory-allocation functions once, just as a sanity-check fprintf(LOGSTREAM, "Sanity-testing all the memory allocation functions\n"); { // We use new-hook and delete-hook to verify we actually called the // tcmalloc version of these routines, and not the libc version. SetNewHook(); // defined as part of MAKE_HOOK_CALLBACK, above SetDeleteHook(); // ditto void* p1 = malloc(10); CHECK(p1 != NULL); // force use of this variable VerifyNewHookWasCalled(); // Also test the non-standard tc_malloc_size size_t actual_p1_size = tc_malloc_size(p1); CHECK_GE(actual_p1_size, 10); CHECK_LT(actual_p1_size, 100000); // a reasonable upper-bound, I think free(p1); VerifyDeleteHookWasCalled(); p1 = tc_malloc_skip_new_handler(10); CHECK(p1 != NULL); VerifyNewHookWasCalled(); free(p1); VerifyDeleteHookWasCalled(); p1 = calloc(10, 2); CHECK(p1 != NULL); VerifyNewHookWasCalled(); // We make sure we realloc to a big size, since some systems (OS // X) will notice if the realloced size continues to fit into the // malloc-block and make this a noop if so. p1 = realloc(p1, 30000); CHECK(p1 != NULL); VerifyNewHookWasCalled(); VerifyDeleteHookWasCalled(); cfree(p1); // synonym for free VerifyDeleteHookWasCalled(); if (kOSSupportsMemalign) { CHECK_EQ(PosixMemalign(&p1, sizeof(p1), 40), 0); CHECK(p1 != NULL); VerifyNewHookWasCalled(); free(p1); VerifyDeleteHookWasCalled(); p1 = Memalign(sizeof(p1) * 2, 50); CHECK(p1 != NULL); VerifyNewHookWasCalled(); free(p1); VerifyDeleteHookWasCalled(); } // Windows has _aligned_malloc. Let's test that that's captured too. #if (defined(_MSC_VER) || defined(__MINGW32__)) && !defined(PERFTOOLS_NO_ALIGNED_MALLOC) p1 = _aligned_malloc(sizeof(p1) * 2, 64); CHECK(p1 != NULL); VerifyNewHookWasCalled(); _aligned_free(p1); VerifyDeleteHookWasCalled(); #endif p1 = valloc(60); CHECK(p1 != NULL); VerifyNewHookWasCalled(); free(p1); VerifyDeleteHookWasCalled(); p1 = pvalloc(70); CHECK(p1 != NULL); VerifyNewHookWasCalled(); free(p1); VerifyDeleteHookWasCalled(); char* p2 = new char; CHECK(p2 != NULL); VerifyNewHookWasCalled(); delete p2; VerifyDeleteHookWasCalled(); p2 = new char[100]; CHECK(p2 != NULL); VerifyNewHookWasCalled(); delete[] p2; VerifyDeleteHookWasCalled(); p2 = new(std::nothrow) char; CHECK(p2 != NULL); VerifyNewHookWasCalled(); delete p2; VerifyDeleteHookWasCalled(); p2 = new(std::nothrow) char[100]; CHECK(p2 != NULL); VerifyNewHookWasCalled(); delete[] p2; VerifyDeleteHookWasCalled(); // Another way of calling operator new p2 = static_cast(::operator new(100)); CHECK(p2 != NULL); VerifyNewHookWasCalled(); ::operator delete(p2); VerifyDeleteHookWasCalled(); // Try to call nothrow's delete too. Compilers use this. p2 = static_cast(::operator new(100, std::nothrow)); CHECK(p2 != NULL); VerifyNewHookWasCalled(); ::operator delete(p2, std::nothrow); VerifyDeleteHookWasCalled(); // Try strdup(), which the system allocates but we must free. If // all goes well, libc will use our malloc! p2 = strdup("test"); CHECK(p2 != NULL); VerifyNewHookWasCalled(); free(p2); VerifyDeleteHookWasCalled(); // Test mmap too: both anonymous mmap and mmap of a file // Note that for right now we only override mmap on linux // systems, so those are the only ones for which we check. SetMmapHook(); SetMremapHook(); SetMunmapHook(); #if defined(HAVE_MMAP) && defined(__linux) && \ (defined(__i386__) || defined(__x86_64__)) int size = 8192*2; p1 = mmap(NULL, size, PROT_WRITE|PROT_READ, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); CHECK(p1 != NULL); VerifyMmapHookWasCalled(); p1 = mremap(p1, size, size/2, 0); CHECK(p1 != NULL); VerifyMremapHookWasCalled(); size /= 2; munmap(p1, size); VerifyMunmapHookWasCalled(); int fd = open("/dev/zero", O_RDONLY); CHECK_GE(fd, 0); // make sure the open succeeded p1 = mmap(NULL, 8192, PROT_READ, MAP_SHARED, fd, 0); CHECK(p1 != NULL); VerifyMmapHookWasCalled(); munmap(p1, 8192); VerifyMunmapHookWasCalled(); close(fd); #else // this is just to quiet the compiler: make sure all fns are called IncrementCallsToMmapHook(NULL, NULL, 0, 0, 0, 0, 0); IncrementCallsToMunmapHook(NULL, 0); IncrementCallsToMremapHook(NULL, NULL, 0, 0, 0, NULL); VerifyMmapHookWasCalled(); VerifyMremapHookWasCalled(); VerifyMunmapHookWasCalled(); #endif // Test sbrk SetSbrkHook(); #if defined(HAVE_SBRK) && defined(__linux) && \ (defined(__i386__) || defined(__x86_64__)) p1 = sbrk(8192); CHECK(p1 != NULL); VerifySbrkHookWasCalled(); p1 = sbrk(-8192); CHECK(p1 != NULL); VerifySbrkHookWasCalled(); // However, sbrk hook should *not* be called with sbrk(0) p1 = sbrk(0); CHECK(p1 != NULL); CHECK_EQ(g_SbrkHook_calls, 0); #else // this is just to quiet the compiler: make sure all fns are called IncrementCallsToSbrkHook(NULL, 0); VerifySbrkHookWasCalled(); #endif // Reset the hooks to what they used to be. These are all // defined as part of MAKE_HOOK_CALLBACK, above. ResetNewHook(); ResetDeleteHook(); ResetMmapHook(); ResetMremapHook(); ResetMunmapHook(); ResetSbrkHook(); } // Check that "lots" of memory can be allocated fprintf(LOGSTREAM, "Testing large allocation\n"); { const int mb_to_allocate = 100; void* p = rnd.alloc(mb_to_allocate << 20); CHECK(p != NULL); // could not allocate free(p); } TestMallocAlignment(); // Check calloc() with various arguments fprintf(LOGSTREAM, "Testing calloc\n"); TestCalloc(0, 0, true); TestCalloc(0, 1, true); TestCalloc(1, 1, true); TestCalloc(1<<10, 0, true); TestCalloc(1<<20, 0, true); TestCalloc(0, 1<<10, true); TestCalloc(0, 1<<20, true); TestCalloc(1<<20, 2, true); TestCalloc(2, 1<<20, true); TestCalloc(1000, 1000, true); TestCalloc(kMaxSize, 2, false); TestCalloc(2, kMaxSize, false); TestCalloc(kMaxSize, kMaxSize, false); TestCalloc(kMaxSignedSize, 3, false); TestCalloc(3, kMaxSignedSize, false); TestCalloc(kMaxSignedSize, kMaxSignedSize, false); // Test that realloc doesn't always reallocate and copy memory. fprintf(LOGSTREAM, "Testing realloc\n"); TestRealloc(); fprintf(LOGSTREAM, "Testing operator new(nothrow).\n"); TestNothrowNew(&::operator new); fprintf(LOGSTREAM, "Testing operator new[](nothrow).\n"); TestNothrowNew(&::operator new[]); fprintf(LOGSTREAM, "Testing operator new.\n"); TestNew(&::operator new); fprintf(LOGSTREAM, "Testing operator new[].\n"); TestNew(&::operator new[]); // Create threads fprintf(LOGSTREAM, "Testing threaded allocation/deallocation (%d threads)\n", FLAGS_numthreads); threads = new TesterThread*[FLAGS_numthreads]; for (int i = 0; i < FLAGS_numthreads; ++i) { threads[i] = new TesterThread(i); } // This runs all the tests at the same time, with a 1M stack size each RunManyThreadsWithId(RunThread, FLAGS_numthreads, 1<<20); for (int i = 0; i < FLAGS_numthreads; ++i) delete threads[i]; // Cleanup // Do the memory intensive tests after threads are done, since exhausting // the available address space can make pthread_create to fail. // Check that huge allocations fail with NULL instead of crashing fprintf(LOGSTREAM, "Testing huge allocations\n"); TestHugeAllocations(&rnd); // Check that large allocations fail with NULL instead of crashing #ifndef DEBUGALLOCATION // debug allocation takes forever for huge allocs fprintf(LOGSTREAM, "Testing out of memory\n"); for (int s = 0; ; s += (10<<20)) { void* large_object = rnd.alloc(s); if (large_object == NULL) break; free(large_object); } #endif TestHugeThreadCache(); TestRanges(); TestReleaseToSystem(); TestAggressiveDecommit(); TestSetNewMode(); TestErrno(); return 0; } } using testing::RunAllTests; int main(int argc, char** argv) { #ifdef DEBUGALLOCATION // debug allocation takes forever for huge allocs FLAGS_max_free_queue_size = 0; // return freed blocks to tcmalloc immediately #endif RunAllTests(argc, argv); // Test tc_version() fprintf(LOGSTREAM, "Testing tc_version()\n"); int major; int minor; const char* patch; char mmp[64]; const char* human_version = tc_version(&major, &minor, &patch); snprintf(mmp, sizeof(mmp), "%d.%d%s", major, minor, patch); CHECK(!strcmp(PACKAGE_STRING, human_version)); CHECK(!strcmp(PACKAGE_VERSION, mmp)); fprintf(LOGSTREAM, "PASS\n"); }