| // 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 <string> |
| |
| #include "runtime/mem-pool.h" |
| #include "runtime/mem-tracker.h" |
| #include "testutil/gtest-util.h" |
| #include "util/bit-util.h" |
| |
| #include "common/names.h" |
| |
| // Maximum allocation size which exceeds 32-bit. |
| #define LARGE_ALLOC_SIZE (1LL << 32) |
| |
| namespace impala { |
| |
| // Utility class to call private functions on MemPool. |
| class MemPoolTest { |
| public: |
| static bool CheckIntegrity(MemPool* pool, bool current_chunk_empty) { |
| return pool->CheckIntegrity(current_chunk_empty); |
| } |
| |
| static const int INITIAL_CHUNK_SIZE = MemPool::INITIAL_CHUNK_SIZE; |
| static const int MAX_CHUNK_SIZE = MemPool::MAX_CHUNK_SIZE; |
| }; |
| |
| const int MemPoolTest::INITIAL_CHUNK_SIZE; |
| const int MemPoolTest::MAX_CHUNK_SIZE; |
| |
| TEST(MemPoolTest, Basic) { |
| MemTracker tracker; |
| MemPool p(&tracker); |
| MemPool p2(&tracker); |
| MemPool p3(&tracker); |
| |
| uint8_t* ptr = NULL; |
| |
| for (int iter = 0; iter < 2; ++iter) { |
| // Allocate 768 25 + 7 byte allocations. |
| for (int i = 0; i < 768; ++i) { |
| ptr = p.Allocate(25); |
| EXPECT_TRUE(ptr != NULL); |
| EXPECT_EQ(0, reinterpret_cast<uintptr_t>(ptr) % MemPool::DEFAULT_ALIGNMENT) |
| << "Allocation should be aligned"; |
| |
| // Allocate the padding to get 32 bytes |
| ptr = p.TryAllocateAligned(7, 1); |
| EXPECT_TRUE(ptr != NULL); |
| } |
| // We allocated 24K from 28K of chunks (4, 8, 16) |
| EXPECT_EQ(24 * 1024, p.total_allocated_bytes()); |
| EXPECT_EQ(28 * 1024, p.GetTotalChunkSizes()); |
| |
| // we're passing on the first two chunks, containing 12K of data; we're left with |
| // one chunk of 16K containing 12K of data |
| p2.AcquireData(&p, true); |
| EXPECT_EQ(12 * 1024, p.total_allocated_bytes()); |
| EXPECT_EQ(16 * 1024, p.GetTotalChunkSizes()); |
| |
| // we allocate 8K, for which there isn't enough room in the current chunk, |
| // so another one is allocated (32K) |
| p.Allocate(8 * 1024); |
| EXPECT_EQ((16 + 32) * 1024, p.GetTotalChunkSizes()); |
| |
| // we allocate 65K, which doesn't fit into the current chunk or the default |
| // size of the next allocated chunk (64K) |
| p.Allocate(65 * 1024); |
| EXPECT_EQ((12 + 8 + 65) * 1024, p.total_allocated_bytes()); |
| EXPECT_EQ((16 + 32 + 65) * 1024, p.GetTotalChunkSizes()); |
| |
| // Clear() resets allocated data, but doesn't remove any chunks |
| p.Clear(); |
| EXPECT_EQ(0, p.total_allocated_bytes()); |
| EXPECT_EQ((16 + 32 + 65) * 1024, p.GetTotalChunkSizes()); |
| |
| // next allocation reuses existing chunks |
| p.Allocate(1024); |
| EXPECT_EQ(1024, p.total_allocated_bytes()); |
| EXPECT_EQ((16 + 32 + 65) * 1024, p.GetTotalChunkSizes()); |
| |
| // ... unless it doesn't fit into any available chunk |
| p.Allocate(120 * 1024); |
| EXPECT_EQ((1 + 120) * 1024, p.total_allocated_bytes()); |
| EXPECT_EQ((130 + 16 + 32 + 65) * 1024, p.GetTotalChunkSizes()); |
| |
| // ... Try another chunk that fits into an existing chunk |
| p.Allocate(33 * 1024); |
| EXPECT_EQ((1 + 120 + 33) * 1024, p.total_allocated_bytes()); |
| EXPECT_EQ((130 + 16 + 32 + 65) * 1024, p.GetTotalChunkSizes()); |
| |
| // we're releasing 3 chunks, which get added to p2 |
| p2.AcquireData(&p, false); |
| EXPECT_EQ(0, p.total_allocated_bytes()); |
| EXPECT_EQ(0, p.GetTotalChunkSizes()); |
| |
| p3.AcquireData(&p2, true); // we're keeping the 65k chunk |
| EXPECT_EQ(33 * 1024, p2.total_allocated_bytes()); |
| EXPECT_EQ(65 * 1024, p2.GetTotalChunkSizes()); |
| |
| p.FreeAll(); |
| p2.FreeAll(); |
| p3.FreeAll(); |
| } |
| |
| // Test zero byte allocation. |
| ptr = p.Allocate(0); |
| EXPECT_TRUE(ptr != NULL); |
| EXPECT_EQ(0, p.GetTotalChunkSizes()); |
| } |
| |
| // Test that we can keep an allocated chunk and a free chunk. |
| // This case verifies that when chunks are acquired by another memory pool the |
| // remaining chunks are consistent if there were more than one used chunk and some |
| // free chunks. |
| TEST(MemPoolTest, Keep) { |
| MemTracker tracker; |
| MemPool p(&tracker); |
| p.Allocate(4*1024); |
| p.Allocate(8*1024); |
| p.Allocate(16*1024); |
| EXPECT_EQ((4 + 8 + 16) * 1024, p.total_allocated_bytes()); |
| EXPECT_EQ((4 + 8 + 16) * 1024, p.GetTotalChunkSizes()); |
| p.Clear(); |
| EXPECT_EQ(0, p.total_allocated_bytes()); |
| EXPECT_EQ((4 + 8 + 16) * 1024, p.GetTotalChunkSizes()); |
| p.Allocate(1*1024); |
| p.Allocate(4*1024); |
| EXPECT_EQ((1 + 4) * 1024, p.total_allocated_bytes()); |
| EXPECT_EQ((4 + 8 + 16) * 1024, p.GetTotalChunkSizes()); |
| MemPool p2(&tracker); |
| p2.AcquireData(&p, true); |
| EXPECT_EQ(4 * 1024, p.total_allocated_bytes()); |
| EXPECT_EQ((8 + 16) * 1024, p.GetTotalChunkSizes()); |
| EXPECT_EQ(1 * 1024, p2.total_allocated_bytes()); |
| EXPECT_EQ(4 * 1024, p2.GetTotalChunkSizes()); |
| |
| p.FreeAll(); |
| p2.FreeAll(); |
| } |
| |
| #ifdef ADDRESS_SANITIZER |
| |
| /// These tests confirm that ASAN will catch use-after-return errors (even though the |
| /// memory is still valid, just cached by the MemPool). |
| TEST(MemPoolTest, UseAfterClear) { |
| MemTracker tracker; |
| MemPool p(&tracker); |
| uint8_t* ptr = p.Allocate(1024); |
| ptr[10] = 'A'; |
| p.Clear(); |
| ASSERT_DEATH({ptr[10] = 'B';}, "use-after-poison"); |
| p.FreeAll(); |
| } |
| |
| TEST(MemPoolTest, UseAfterPartialReturn) { |
| MemTracker tracker; |
| MemPool p(&tracker); |
| uint8_t* ptr = p.Allocate(1024); |
| ptr[10] = 'A'; |
| p.ReturnPartialAllocation(512); |
| ptr[511] = 'B'; |
| ASSERT_DEATH({ptr[512] = 'X';}, "use-after-poison"); |
| p.FreeAll(); |
| } |
| |
| #endif |
| |
| // Tests that we can return partial allocations. |
| TEST(MemPoolTest, ReturnPartial) { |
| MemTracker tracker; |
| MemPool p(&tracker); |
| uint8_t* ptr = p.Allocate(1024); |
| EXPECT_EQ(1024, p.total_allocated_bytes()); |
| memset(ptr, 0, 1024); |
| p.ReturnPartialAllocation(1024); |
| |
| uint8_t* ptr2 = p.Allocate(1024); |
| EXPECT_EQ(1024, p.total_allocated_bytes()); |
| EXPECT_TRUE(ptr == ptr2); |
| p.ReturnPartialAllocation(1016); |
| |
| ptr2 = p.Allocate(1016); |
| EXPECT_EQ(1024, p.total_allocated_bytes()); |
| EXPECT_TRUE(ptr2 == ptr + 8); |
| p.ReturnPartialAllocation(512); |
| memset(ptr2, 1, 1016 - 512); |
| |
| uint8_t* ptr3 = p.Allocate(512); |
| EXPECT_EQ(1024, p.total_allocated_bytes()); |
| EXPECT_TRUE(ptr3 == ptr + 512); |
| memset(ptr3, 2, 512); |
| |
| for (int i = 0; i < 8; ++i) { |
| EXPECT_EQ(0, ptr[i]); |
| } |
| for (int i = 8; i < 512; ++i) { |
| EXPECT_EQ(1, ptr[i]); |
| } |
| for (int i = 512; i < 1024; ++i) { |
| EXPECT_EQ(2, ptr[i]); |
| } |
| |
| // Try ReturnPartialAllocations() with 64-bit values. |
| uint8_t* ptr4 = p.Allocate(LARGE_ALLOC_SIZE + 512); |
| EXPECT_EQ(1024 + LARGE_ALLOC_SIZE + 512, p.total_allocated_bytes()); |
| memset(ptr4, 3, 512 * 2); |
| p.ReturnPartialAllocation(LARGE_ALLOC_SIZE); |
| uint8_t* ptr5 = p.Allocate(512); |
| EXPECT_TRUE(ptr5 == ptr4 + 512); |
| memset(ptr5, 4, 512); |
| |
| for (int i = 0; i < 512; ++i) { |
| EXPECT_EQ(3, ptr4[i]); |
| } |
| for (int i = 512; i < 512 * 2; ++i) { |
| EXPECT_EQ(4, ptr4[i]); |
| } |
| |
| p.FreeAll(); |
| } |
| |
| TEST(MemPoolTest, Limits) { |
| MemTracker limit3(4 * MemPoolTest::INITIAL_CHUNK_SIZE); |
| MemTracker limit1(2 * MemPoolTest::INITIAL_CHUNK_SIZE, "", &limit3); |
| MemTracker limit2(3 * MemPoolTest::INITIAL_CHUNK_SIZE, "", &limit3); |
| |
| MemPool* p1 = new MemPool(&limit1); |
| EXPECT_FALSE(limit1.AnyLimitExceeded(MemLimit::HARD)); |
| |
| MemPool* p2 = new MemPool(&limit2); |
| EXPECT_FALSE(limit2.AnyLimitExceeded(MemLimit::HARD)); |
| |
| // p1 exceeds a non-shared limit |
| p1->Allocate(MemPoolTest::INITIAL_CHUNK_SIZE); |
| EXPECT_FALSE(limit1.LimitExceeded(MemLimit::HARD)); |
| EXPECT_EQ(MemPoolTest::INITIAL_CHUNK_SIZE, limit1.consumption()); |
| EXPECT_FALSE(limit3.LimitExceeded(MemLimit::HARD)); |
| EXPECT_EQ(MemPoolTest::INITIAL_CHUNK_SIZE, limit3.consumption()); |
| |
| p1->Allocate(MemPoolTest::INITIAL_CHUNK_SIZE); |
| EXPECT_TRUE(limit1.LimitExceeded(MemLimit::HARD)); |
| EXPECT_EQ(MemPoolTest::INITIAL_CHUNK_SIZE * 3, limit1.consumption()); |
| EXPECT_FALSE(limit3.LimitExceeded(MemLimit::HARD)); |
| EXPECT_EQ(MemPoolTest::INITIAL_CHUNK_SIZE * 3, limit3.consumption()); |
| |
| // p2 exceeds a shared limit |
| p2->Allocate(MemPoolTest::INITIAL_CHUNK_SIZE); |
| EXPECT_FALSE(limit2.LimitExceeded(MemLimit::HARD)); |
| EXPECT_EQ(MemPoolTest::INITIAL_CHUNK_SIZE, limit2.consumption()); |
| EXPECT_FALSE(limit3.LimitExceeded(MemLimit::HARD)); |
| EXPECT_EQ(MemPoolTest::INITIAL_CHUNK_SIZE * 4, limit3.consumption()); |
| |
| p2->Allocate(1); |
| EXPECT_FALSE(limit2.LimitExceeded(MemLimit::HARD)); |
| EXPECT_EQ(MemPoolTest::INITIAL_CHUNK_SIZE * 3, limit2.consumption()); |
| EXPECT_TRUE(limit3.LimitExceeded(MemLimit::HARD)); |
| EXPECT_EQ(MemPoolTest::INITIAL_CHUNK_SIZE * 6, limit3.consumption()); |
| |
| // deleting pools reduces consumption |
| p1->FreeAll(); |
| delete p1; |
| EXPECT_EQ(0, limit1.consumption()); |
| EXPECT_EQ(MemPoolTest::INITIAL_CHUNK_SIZE * 3, limit2.consumption()); |
| EXPECT_EQ(MemPoolTest::INITIAL_CHUNK_SIZE * 3, limit3.consumption()); |
| |
| // Allocate another chunk |
| p2->FreeAll(); |
| EXPECT_FALSE(limit2.LimitExceeded(MemLimit::HARD)); |
| uint8_t* result = p2->TryAllocate(MemPoolTest::INITIAL_CHUNK_SIZE); |
| ASSERT_TRUE(result != NULL); |
| ASSERT_TRUE(MemPoolTest::CheckIntegrity(p2, false)); |
| |
| // Try To allocate over the limit, this should fail. |
| result = p2->TryAllocate(MemPoolTest::INITIAL_CHUNK_SIZE * 4); |
| ASSERT_TRUE(result == NULL); |
| ASSERT_TRUE(MemPoolTest::CheckIntegrity(p2, false)); |
| |
| // Try To allocate 20 bytes, this should succeed. TryAllocate() should leave the |
| // pool in a functional state. |
| result = p2->TryAllocate(20); |
| ASSERT_TRUE(result != NULL); |
| ASSERT_TRUE(MemPoolTest::CheckIntegrity(p2, false)); |
| |
| p2->FreeAll(); |
| delete p2; |
| } |
| |
| TEST(MemPoolTest, MaxAllocation) { |
| int64_t int_max_rounded = BitUtil::RoundUp(LARGE_ALLOC_SIZE, 8); |
| |
| // Allocate a single LARGE_ALLOC_SIZE chunk |
| MemTracker tracker; |
| MemPool p1(&tracker); |
| uint8_t* ptr = p1.Allocate(LARGE_ALLOC_SIZE); |
| EXPECT_TRUE(ptr != NULL); |
| EXPECT_EQ(int_max_rounded, p1.GetTotalChunkSizes()); |
| EXPECT_EQ(int_max_rounded, p1.total_allocated_bytes()); |
| p1.FreeAll(); |
| |
| // Allocate a small chunk (INITIAL_CHUNK_SIZE) followed by an LARGE_ALLOC_SIZE chunk |
| MemPool p2(&tracker); |
| p2.Allocate(8); |
| EXPECT_EQ(MemPoolTest::INITIAL_CHUNK_SIZE, p2.GetTotalChunkSizes()); |
| EXPECT_EQ(8, p2.total_allocated_bytes()); |
| ptr = p2.Allocate(LARGE_ALLOC_SIZE); |
| EXPECT_TRUE(ptr != NULL); |
| EXPECT_EQ(MemPoolTest::INITIAL_CHUNK_SIZE + int_max_rounded, |
| p2.GetTotalChunkSizes()); |
| EXPECT_EQ(8LL + int_max_rounded, p2.total_allocated_bytes()); |
| p2.FreeAll(); |
| |
| // Allocate three LARGE_ALLOC_SIZE chunks followed by a small chunk |
| // followed by another LARGE_ALLOC_SIZE chunk. |
| MemPool p3(&tracker); |
| p3.Allocate(LARGE_ALLOC_SIZE); |
| // Allocates new int_max_rounded chunk |
| ptr = p3.Allocate(LARGE_ALLOC_SIZE); |
| EXPECT_TRUE(ptr != NULL); |
| EXPECT_EQ(int_max_rounded * 2, p3.GetTotalChunkSizes()); |
| EXPECT_EQ(int_max_rounded * 2, p3.total_allocated_bytes()); |
| // Allocates new int_max_rounded chunk |
| ptr = p3.Allocate(LARGE_ALLOC_SIZE); |
| EXPECT_TRUE(ptr != NULL); |
| EXPECT_EQ(int_max_rounded * 3, p3.GetTotalChunkSizes()); |
| EXPECT_EQ(int_max_rounded * 3, p3.total_allocated_bytes()); |
| |
| // Allocates new MAX_CHUNK_SIZE chunk |
| #if !defined (ADDRESS_SANITIZER) || (__clang_major__ >= 3 && __clang_minor__ >= 7) |
| ptr = p3.Allocate(8); |
| EXPECT_TRUE(ptr != NULL); |
| EXPECT_EQ(int_max_rounded * 3 + MemPoolTest::MAX_CHUNK_SIZE, p3.GetTotalChunkSizes()); |
| EXPECT_EQ(int_max_rounded * 3 + 8, p3.total_allocated_bytes()); |
| // Allocates new int_max_rounded chunk |
| ptr = p3.Allocate(LARGE_ALLOC_SIZE); |
| EXPECT_TRUE(ptr != NULL); |
| EXPECT_EQ(int_max_rounded * 4 + MemPoolTest::MAX_CHUNK_SIZE, p3.GetTotalChunkSizes()); |
| EXPECT_EQ(int_max_rounded * 4 + 8, p3.total_allocated_bytes()); |
| #endif |
| p3.FreeAll(); |
| |
| } |
| |
| // Test for IMPALA-2742: chunk size can grow unbounded when calling AcquireData with |
| // MemPools with keep_current = true. |
| TEST(MemPoolTest, GrowthWithTransfer) { |
| MemTracker tracker; |
| MemPool src(&tracker); |
| MemPool dst(&tracker); |
| const int64_t total_to_alloc = 100L * 1024L * 1024L * 1024L; |
| // Choose size that will straddle chunk boundaries to exercise boundary cases. |
| const int alloc_size = 100 * 1024 + 12345; |
| // We should have at most one chunk allocated at a time. |
| const int max_consumption = MemPoolTest::MAX_CHUNK_SIZE; |
| |
| for (int i = 0; i < total_to_alloc / alloc_size; ++i) { |
| uint8_t* mem = src.Allocate(alloc_size); |
| ASSERT_TRUE(mem != NULL); |
| |
| dst.AcquireData(&src, true); |
| dst.FreeAll(); |
| |
| // Check that the chunk size limits memory consumption. |
| ASSERT_LE(src.GetTotalChunkSizes(), max_consumption); |
| ASSERT_EQ(0, dst.GetTotalChunkSizes()); |
| ASSERT_LE(tracker.consumption(), max_consumption); |
| } |
| |
| dst.AcquireData(&src, false); |
| dst.FreeAll(); |
| } |
| |
| // Test that the MemPool overhead is bounded when we make allocations of |
| // INITIAL_CHUNK_SIZE. |
| TEST(MemPoolTest, MemoryOverhead) { |
| MemTracker tracker; |
| MemPool p(&tracker); |
| const int alloc_size = MemPoolTest::INITIAL_CHUNK_SIZE; |
| const int num_allocs = 1000; |
| int64_t total_allocated = 0; |
| |
| for (int i = 0; i < num_allocs; ++i) { |
| uint8_t* mem = p.Allocate(alloc_size); |
| ASSERT_TRUE(mem != NULL); |
| total_allocated += alloc_size; |
| |
| int64_t wasted_memory = p.GetTotalChunkSizes() - total_allocated; |
| // The initial chunk fits evenly into MAX_CHUNK_SIZE, so should have at most |
| // one empty chunk at the end. |
| EXPECT_LE(wasted_memory, MemPoolTest::MAX_CHUNK_SIZE); |
| // The chunk doubling algorithm should not allocate chunks larger than the total |
| // amount of memory already allocated. |
| EXPECT_LE(wasted_memory, total_allocated); |
| } |
| |
| p.FreeAll(); |
| } |
| |
| // Test that the MemPool overhead is bounded when we make alternating large and small |
| // allocations. |
| TEST(MemPoolTest, FragmentationOverhead) { |
| MemTracker tracker; |
| MemPool p(&tracker); |
| const int num_allocs = 100; |
| int64_t total_allocated = 0; |
| |
| for (int i = 0; i < num_allocs; ++i) { |
| int alloc_size = i % 2 == 0 ? 1 : MemPoolTest::MAX_CHUNK_SIZE; |
| uint8_t* mem = p.Allocate(alloc_size); |
| ASSERT_TRUE(mem != NULL); |
| total_allocated += alloc_size; |
| |
| int64_t wasted_memory = p.GetTotalChunkSizes() - total_allocated; |
| // Fragmentation should not waste more than half of each completed chunk. |
| EXPECT_LE(wasted_memory, total_allocated + MemPoolTest::MAX_CHUNK_SIZE); |
| } |
| |
| p.FreeAll(); |
| } |
| |
| /// Test interaction between returning a full allocation and transferring chunks. |
| TEST(MemPoolTest, ReturnAllocationThenTransfer) { |
| MemTracker tracker; |
| MemPool src(&tracker); |
| MemPool dst(&tracker); |
| |
| const int64_t alloc_size = 1L * 1024 * 1024; |
| uint8_t* mem = src.Allocate(alloc_size); |
| ASSERT_TRUE(mem != NULL); |
| |
| src.ReturnPartialAllocation(alloc_size); |
| ASSERT_TRUE(MemPoolTest::CheckIntegrity(&src, true)); |
| |
| // IMPALA-3946: this would hit a DCHECK because the current chunk was empty. |
| dst.AcquireData(&src, true); |
| ASSERT_EQ(0, dst.GetTotalChunkSizes()); // Src chunk shouldn't be transferred. |
| ASSERT_TRUE(MemPoolTest::CheckIntegrity(&dst, true)); |
| ASSERT_TRUE(MemPoolTest::CheckIntegrity(&src, true)); |
| src.FreeAll(); |
| dst.FreeAll(); |
| } |
| |
| /// Test that making a large allocation that doesn't fit in the current chunk after |
| /// returning a full allocation works correctly. |
| TEST(MemPoolTest, ReturnAllocationThenLargeAllocation) { |
| MemTracker tracker; |
| MemPool src(&tracker); |
| |
| const int64_t alloc_size = 1L * 1024 * 1024; |
| uint8_t* mem = src.Allocate(alloc_size); |
| ASSERT_TRUE(mem != NULL); |
| |
| src.ReturnPartialAllocation(alloc_size); |
| ASSERT_TRUE(MemPoolTest::CheckIntegrity(&src, true)); |
| |
| // IMPALA-3946: chunks ended in invalid order because the first chunk was empty. |
| // This resulted in the the integrity check failing. |
| mem = src.Allocate(alloc_size + 1); |
| ASSERT_TRUE(mem != NULL); |
| ASSERT_TRUE(MemPoolTest::CheckIntegrity(&src, false)); |
| src.FreeAll(); |
| } |
| |
| /// Test that making a large allocation that fails after returning a full allocation works |
| /// correctly. |
| TEST(MemPoolTest, ReturnAllocationThenFailedAllocation) { |
| const int64_t alloc_size = 1L * 1024 * 1024; |
| const int64_t mem_limit = alloc_size * 10; |
| MemTracker tracker(mem_limit); |
| MemPool src(&tracker); |
| |
| uint8_t* mem; |
| // Allocate two chunks then clear them so they are empty. |
| for (int i = 0; i < 2; ++i) { |
| mem = src.Allocate(alloc_size); |
| ASSERT_TRUE(mem != NULL); |
| } |
| src.Clear(); |
| |
| // IMPALA-3946: chunk index wasn't reset correctly to before the empty chunks when an |
| // allocation failed. This resulted in the the integrity check failing. |
| mem = src.TryAllocate(mem_limit); |
| ASSERT_TRUE(mem == NULL); |
| ASSERT_TRUE(MemPoolTest::CheckIntegrity(&src, false)); |
| src.FreeAll(); |
| } |
| |
| TEST(MemPoolTest, TryAllocateAligned) { |
| MemTracker tracker(-1); |
| MemPool pool(&tracker); |
| int alignment = 1; |
| int64_t size = 1; |
| constexpr int NUM_ALLOCATIONS = 100000; |
| constexpr int MAX_ALLOCATION_SIZE = 113; |
| |
| for (int i = 0; i < NUM_ALLOCATIONS; ++i) { |
| uint8_t* ptr = pool.TryAllocateAligned(size, alignment); |
| ASSERT_TRUE(ptr != NULL) << "size=" << size << " alignment=" << alignment; |
| ASSERT_EQ(0, reinterpret_cast<uintptr_t>(ptr) % alignment) |
| << "size=" << size << " alignment=" << alignment; |
| alignment = alignment == alignof(std::max_align_t) ? 1 : alignment * 2; |
| size = (size + 1) % MAX_ALLOCATION_SIZE; |
| } |
| |
| pool.FreeAll(); |
| } |
| } |
| |