1 /*
2  * Copyright (c) Facebook, Inc. and its affiliates.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <folly/experimental/JemallocHugePageAllocator.h>
18 
19 #include <folly/container/F14Map.h>
20 
21 #include <folly/memory/Malloc.h>
22 #include <folly/portability/GTest.h>
23 
24 #include <vector>
25 
26 using jha = folly::JemallocHugePageAllocator;
27 
kb(int kilos)28 static constexpr int kb(int kilos) {
29   return kilos * 1024;
30 }
31 
mb(int megs)32 static constexpr int mb(int megs) {
33   return kb(megs * 1024);
34 }
35 
TEST(JemallocHugePageAllocatorTest,Basic)36 TEST(JemallocHugePageAllocatorTest, Basic) {
37   EXPECT_FALSE(jha::initialized());
38 
39   // Allocation should work even if uninitialized
40   auto ptr = jha::allocate(kb(1));
41   EXPECT_NE(nullptr, ptr);
42   jha::deallocate(ptr);
43 
44   bool initialized = jha::init(1);
45   if (initialized) {
46     EXPECT_NE(0, jha::freeSpace());
47   }
48 
49   ptr = jha::allocate(kb(1));
50   EXPECT_NE(nullptr, ptr);
51 
52   if (initialized) {
53     EXPECT_TRUE(jha::addressInArena(ptr));
54   }
55 
56   // Allocate some arrays on huge page
57   auto array_of_arrays = new (ptr) std::array<int, 100>[5];
58 
59   if (initialized) {
60     EXPECT_FALSE(jha::addressInArena(&array_of_arrays));
61     EXPECT_TRUE(jha::addressInArena(&array_of_arrays[0]));
62     EXPECT_TRUE(jha::addressInArena(&array_of_arrays[0][0]));
63   }
64 
65   jha::deallocate(ptr);
66 }
67 
TEST(JemallocHugePageAllocatorTest,LargeAllocations)68 TEST(JemallocHugePageAllocatorTest, LargeAllocations) {
69   // Allocate before init - will not use huge pages
70   void* ptr0 = jha::allocate(kb(1));
71 
72   // One 2MB huge page
73   bool initialized = jha::init(1);
74   if (initialized) {
75     EXPECT_NE(0, jha::freeSpace());
76   }
77 
78   // This fits
79   void* ptr1 = jha::allocate(mb(2));
80   EXPECT_NE(nullptr, ptr1);
81 
82   if (initialized) {
83     EXPECT_TRUE(jha::addressInArena(ptr1));
84   }
85 
86   // This is too large to fit
87   void* ptr2 = jha::allocate(mb(1));
88   EXPECT_NE(nullptr, ptr2);
89 
90   EXPECT_FALSE(jha::addressInArena(ptr2));
91 
92   // Free and reuse huge page area
93   jha::deallocate(ptr2);
94   jha::deallocate(ptr0);
95   ptr2 = jha::allocate(kb(64));
96 
97   // No memory in the huge page arena was freed - ptr0 was allocated
98   // before init and ptr2 didn't fit
99   EXPECT_FALSE(jha::addressInArena(ptr2));
100 
101   jha::deallocate(ptr1);
102   void* ptr3 = jha::allocate(mb(1) + kb(512));
103   EXPECT_NE(nullptr, ptr3);
104 
105   if (initialized) {
106     EXPECT_EQ(ptr1, ptr3);
107     EXPECT_TRUE(jha::addressInArena(ptr3));
108   }
109 
110   // Just using free works equally well
111   free(ptr3);
112   ptr3 = jha::allocate(mb(1) + kb(512));
113   EXPECT_NE(nullptr, ptr3);
114 
115   if (initialized) {
116     EXPECT_TRUE(jha::addressInArena(ptr3));
117   }
118 
119   jha::deallocate(ptr2);
120   jha::deallocate(ptr3);
121 }
122 
TEST(JemallocHugePageAllocatorTest,MemoryUsageTest)123 TEST(JemallocHugePageAllocatorTest, MemoryUsageTest) {
124   bool initialized = jha::init(80);
125   if (initialized) {
126     EXPECT_GE(jha::freeSpace(), mb(160));
127   }
128 
129   struct c32 {
130     char val[32];
131   };
132   using Vec32 = std::vector<c32, folly::CxxHugePageAllocator<c32>>;
133   Vec32 vec32;
134   for (int i = 0; i < 10; i++) {
135     vec32.push_back({});
136   }
137   void* ptr1 = jha::allocate(32);
138   if (initialized) {
139     EXPECT_GE(jha::freeSpace(), mb(158));
140   }
141   struct c320 {
142     char val[320];
143   };
144   using Vec320 = std::vector<c320, folly::CxxHugePageAllocator<c320>>;
145   Vec320 vec320;
146   for (int i = 0; i < 10; i++) {
147     vec320.push_back({});
148   }
149   void* ptr2 = jha::allocate(320);
150   if (initialized) {
151     EXPECT_GE(jha::freeSpace(), mb(158));
152   }
153 
154   // Helper to ensure all allocations are freed at the end
155   auto deleter = [](void* data) { jha::deallocate(data); };
156   std::vector<std::unique_ptr<void, decltype(deleter)>> ptr_vec;
157   auto alloc = [&ptr_vec, &deleter](size_t size) {
158     ptr_vec.emplace_back(jha::allocate(size), deleter);
159   };
160 
161   for (int i = 0; i < 10; i++) {
162     alloc(kb(1));
163   }
164   void* ptr3 = jha::allocate(kb(1));
165   if (initialized) {
166     EXPECT_GE(jha::freeSpace(), mb(158));
167   }
168   for (int i = 0; i < 10; i++) {
169     alloc(kb(4));
170   }
171   void* ptr4 = jha::allocate(kb(4));
172   if (initialized) {
173     EXPECT_GE(jha::freeSpace(), mb(158));
174   }
175   for (int i = 0; i < 10; i++) {
176     alloc(kb(10));
177   }
178   void* ptr5 = jha::allocate(kb(10));
179   if (initialized) {
180     EXPECT_GE(jha::freeSpace(), mb(158));
181   }
182   alloc(kb(512));
183   alloc(mb(1));
184   void* ptr6 = jha::allocate(mb(1));
185   if (initialized) {
186     EXPECT_GE(jha::freeSpace(), mb(156));
187   }
188   alloc(mb(2));
189   alloc(mb(4));
190   void* ptr7 = jha::allocate(mb(4));
191   if (initialized) {
192     EXPECT_GE(jha::freeSpace(), mb(146));
193   }
194   alloc(kb(512));
195   alloc(kb(512));
196   if (initialized) {
197     EXPECT_GE(jha::freeSpace(), 145);
198   }
199   void* ptr8 = jha::allocate(mb(64));
200   if (initialized) {
201     EXPECT_GE(jha::freeSpace(), mb(80));
202   }
203   alloc(mb(64));
204   if (initialized) {
205     EXPECT_GE(jha::freeSpace(), mb(16));
206   }
207   alloc(mb(256));
208   alloc(mb(256));
209   alloc(mb(256));
210 
211   // Now free a bunch of objects and then reallocate
212   // the same size objects again.
213   // This should not result in usage of free space.
214   size_t free = jha::freeSpace();
215   jha::deallocate(ptr1);
216   jha::deallocate(ptr2);
217   jha::deallocate(ptr3);
218   jha::deallocate(ptr4);
219   jha::deallocate(ptr5);
220   jha::deallocate(ptr6);
221   jha::deallocate(ptr7);
222   jha::deallocate(ptr8);
223   alloc(32);
224   alloc(320);
225   alloc(kb(1));
226   alloc(kb(4));
227   alloc(kb(10));
228   alloc(mb(1));
229   alloc(mb(4));
230   alloc(mb(64));
231 
232   if (initialized) {
233     EXPECT_EQ(free, jha::freeSpace());
234   }
235 }
236 
TEST(JemallocHugePageAllocatorTest,STLAllocator)237 TEST(JemallocHugePageAllocatorTest, STLAllocator) {
238   using MyVecAllocator = folly::CxxHugePageAllocator<int>;
239   using MyVec = std::vector<int, MyVecAllocator>;
240 
241   using MyMapAllocator =
242       folly::CxxHugePageAllocator<folly::f14::detail::MapValueType<int, MyVec>>;
243   using MyMap = folly::F14FastMap<
244       int,
245       MyVec,
246       folly::f14::DefaultHasher<int>,
247       folly::f14::DefaultKeyEqual<int>,
248       MyMapAllocator>;
249 
250   MyVec vec;
251   // This should work, just won't get huge pages since
252   // init hasn't been called yet
253   vec.reserve(100);
254   EXPECT_NE(nullptr, vec.data());
255 
256   // Reserve & initialize, not on huge pages
257   MyVec vec2(100);
258   EXPECT_NE(nullptr, vec.data());
259 
260   // F14 maps need quite a lot of memory by default
261   bool initialized = jha::init(4);
262   if (initialized) {
263     EXPECT_NE(0, jha::freeSpace());
264   }
265 
266   // Reallocate, this time on huge pages
267   vec.reserve(200);
268   EXPECT_NE(nullptr, vec.data());
269 
270   MyMap map1;
271   map1[0] = {1, 2, 3};
272   auto map2_ptr = std::make_unique<MyMap>();
273   MyMap& map2 = *map2_ptr;
274   map2[0] = {1, 2, 3};
275 
276   if (initialized) {
277     EXPECT_TRUE(jha::addressInArena(vec.data()));
278     EXPECT_TRUE(jha::addressInArena(&map1[0]));
279     EXPECT_TRUE(jha::addressInArena(&map1[0][0]));
280     EXPECT_TRUE(jha::addressInArena(&map2[0]));
281     EXPECT_TRUE(jha::addressInArena(&map2[0][0]));
282   }
283 
284   // This will be on the huge page arena
285   map1[0] = std::move(vec);
286 
287   // But not this, since vec2 content was allocated before init
288   map1[1] = std::move(vec2);
289 
290   if (initialized) {
291     EXPECT_TRUE(jha::addressInArena(&map1[0]));
292     EXPECT_TRUE(jha::addressInArena(&map1[1]));
293     EXPECT_TRUE(jha::addressInArena(&map1[0][0]));
294     EXPECT_FALSE(jha::addressInArena(&map1[1][0]));
295   }
296 
297   // realloc on huge pages
298   map1[1].reserve(200);
299 
300   if (initialized) {
301     EXPECT_TRUE(jha::addressInArena(&map1[1][0]));
302   }
303 }
304 
TEST(JemallocHugePageAllocatorTest,Grow)305 TEST(JemallocHugePageAllocatorTest, Grow) {
306   bool initialized = jha::init(2, 8192);
307   if (initialized) {
308     EXPECT_NE(0, jha::freeSpace());
309   }
310 
311   // This fits
312   void* ptr1 = jha::allocate(mb(1));
313   EXPECT_NE(nullptr, ptr1);
314 
315   // This partially fits
316   void* ptr2 = jha::allocate(mb(6));
317   EXPECT_NE(nullptr, ptr2);
318 
319   void* ptr3 = jha::allocate(mb(500));
320   EXPECT_NE(nullptr, ptr3);
321 
322   void* ptr4 = jha::allocate(kb(4));
323   EXPECT_NE(nullptr, ptr4);
324 
325   void* ptr5 = jha::allocate(kb(4));
326   EXPECT_NE(nullptr, ptr5);
327 
328   if (initialized) {
329     EXPECT_TRUE(jha::addressInArena(ptr1));
330     EXPECT_TRUE(jha::addressInArena(ptr2));
331     EXPECT_TRUE(jha::addressInArena(ptr3));
332     EXPECT_TRUE(jha::addressInArena(ptr4));
333     EXPECT_TRUE(jha::addressInArena(ptr5));
334   }
335 
336   jha::deallocate(ptr1);
337   jha::deallocate(ptr2);
338   jha::deallocate(ptr3);
339   jha::deallocate(ptr4);
340   jha::deallocate(ptr5);
341 }
342