xref: /netbsd-src/external/bsd/jemalloc/dist/test/unit/cache_bin.c (revision f8cf1a9151c7af1cb0bd8b09c13c66bca599c027)
1 #include "test/jemalloc_test.h"
2 
3 static void
4 do_fill_test(cache_bin_t *bin, cache_bin_info_t *info, void **ptrs,
5     cache_bin_sz_t ncached_max, cache_bin_sz_t nfill_attempt,
6     cache_bin_sz_t nfill_succeed) {
7 	bool success;
8 	void *ptr;
9 	assert_true(cache_bin_ncached_get_local(bin, info) == 0, "");
10 	CACHE_BIN_PTR_ARRAY_DECLARE(arr, nfill_attempt);
11 	cache_bin_init_ptr_array_for_fill(bin, info, &arr, nfill_attempt);
12 	for (cache_bin_sz_t i = 0; i < nfill_succeed; i++) {
13 		arr.ptr[i] = &ptrs[i];
14 	}
15 	cache_bin_finish_fill(bin, info, &arr, nfill_succeed);
16 	expect_true(cache_bin_ncached_get_local(bin, info) == nfill_succeed,
17 	    "");
18 	cache_bin_low_water_set(bin);
19 
20 	for (cache_bin_sz_t i = 0; i < nfill_succeed; i++) {
21 		ptr = cache_bin_alloc(bin, &success);
22 		expect_true(success, "");
23 		expect_ptr_eq(ptr, (void *)&ptrs[i],
24 		    "Should pop in order filled");
25 		expect_true(cache_bin_low_water_get(bin, info)
26 		    == nfill_succeed - i - 1, "");
27 	}
28 	expect_true(cache_bin_ncached_get_local(bin, info) == 0, "");
29 	expect_true(cache_bin_low_water_get(bin, info) == 0, "");
30 }
31 
32 static void
33 do_flush_test(cache_bin_t *bin, cache_bin_info_t *info, void **ptrs,
34     cache_bin_sz_t nfill, cache_bin_sz_t nflush) {
35 	bool success;
36 	assert_true(cache_bin_ncached_get_local(bin, info) == 0, "");
37 
38 	for (cache_bin_sz_t i = 0; i < nfill; i++) {
39 		success = cache_bin_dalloc_easy(bin, &ptrs[i]);
40 		expect_true(success, "");
41 	}
42 
43 	CACHE_BIN_PTR_ARRAY_DECLARE(arr, nflush);
44 	cache_bin_init_ptr_array_for_flush(bin, info, &arr, nflush);
45 	for (cache_bin_sz_t i = 0; i < nflush; i++) {
46 		expect_ptr_eq(arr.ptr[i], &ptrs[nflush - i - 1], "");
47 	}
48 	cache_bin_finish_flush(bin, info, &arr, nflush);
49 
50 	expect_true(cache_bin_ncached_get_local(bin, info) == nfill - nflush,
51 	    "");
52 	while (cache_bin_ncached_get_local(bin, info) > 0) {
53 		cache_bin_alloc(bin, &success);
54 	}
55 }
56 
57 static void
58 do_batch_alloc_test(cache_bin_t *bin, cache_bin_info_t *info, void **ptrs,
59     cache_bin_sz_t nfill, size_t batch) {
60 	assert_true(cache_bin_ncached_get_local(bin, info) == 0, "");
61 	CACHE_BIN_PTR_ARRAY_DECLARE(arr, nfill);
62 	cache_bin_init_ptr_array_for_fill(bin, info, &arr, nfill);
63 	for (cache_bin_sz_t i = 0; i < nfill; i++) {
64 		arr.ptr[i] = &ptrs[i];
65 	}
66 	cache_bin_finish_fill(bin, info, &arr, nfill);
67 	assert_true(cache_bin_ncached_get_local(bin, info) == nfill, "");
68 	cache_bin_low_water_set(bin);
69 
70 	void **out = malloc((batch + 1) * sizeof(void *));
71 	size_t n = cache_bin_alloc_batch(bin, batch, out);
72 	assert_true(n == ((size_t)nfill < batch ? (size_t)nfill : batch), "");
73 	for (cache_bin_sz_t i = 0; i < (cache_bin_sz_t)n; i++) {
74 		expect_ptr_eq(out[i], &ptrs[i], "");
75 	}
76 	expect_true(cache_bin_low_water_get(bin, info) == nfill -
77 	    (cache_bin_sz_t)n, "");
78 	while (cache_bin_ncached_get_local(bin, info) > 0) {
79 		bool success;
80 		cache_bin_alloc(bin, &success);
81 	}
82 	free(out);
83 }
84 
85 static void
86 test_bin_init(cache_bin_t *bin, cache_bin_info_t *info) {
87 	size_t size;
88 	size_t alignment;
89 	cache_bin_info_compute_alloc(info, 1, &size, &alignment);
90 	void *mem = mallocx(size, MALLOCX_ALIGN(alignment));
91 	assert_ptr_not_null(mem, "Unexpected mallocx failure");
92 
93 	size_t cur_offset = 0;
94 	cache_bin_preincrement(info, 1, mem, &cur_offset);
95 	cache_bin_init(bin, info, mem, &cur_offset);
96 	cache_bin_postincrement(info, 1, mem, &cur_offset);
97 	assert_zu_eq(cur_offset, size, "Should use all requested memory");
98 }
99 
100 TEST_BEGIN(test_cache_bin) {
101 	const int ncached_max = 100;
102 	bool success;
103 	void *ptr;
104 
105 	cache_bin_info_t info;
106 	cache_bin_info_init(&info, ncached_max);
107 	cache_bin_t bin;
108 	test_bin_init(&bin, &info);
109 
110 	/* Initialize to empty; should then have 0 elements. */
111 	expect_d_eq(ncached_max, cache_bin_info_ncached_max(&info), "");
112 	expect_true(cache_bin_ncached_get_local(&bin, &info) == 0, "");
113 	expect_true(cache_bin_low_water_get(&bin, &info) == 0, "");
114 
115 	ptr = cache_bin_alloc_easy(&bin, &success);
116 	expect_false(success, "Shouldn't successfully allocate when empty");
117 	expect_ptr_null(ptr, "Shouldn't get a non-null pointer on failure");
118 
119 	ptr = cache_bin_alloc(&bin, &success);
120 	expect_false(success, "Shouldn't successfully allocate when empty");
121 	expect_ptr_null(ptr, "Shouldn't get a non-null pointer on failure");
122 
123 	/*
124 	 * We allocate one more item than ncached_max, so we can test cache bin
125 	 * exhaustion.
126 	 */
127 	void **ptrs = mallocx(sizeof(void *) * (ncached_max + 1), 0);
128 	assert_ptr_not_null(ptrs, "Unexpected mallocx failure");
129 	for  (cache_bin_sz_t i = 0; i < ncached_max; i++) {
130 		expect_true(cache_bin_ncached_get_local(&bin, &info) == i, "");
131 		success = cache_bin_dalloc_easy(&bin, &ptrs[i]);
132 		expect_true(success,
133 		    "Should be able to dalloc into a non-full cache bin.");
134 		expect_true(cache_bin_low_water_get(&bin, &info) == 0,
135 		    "Pushes and pops shouldn't change low water of zero.");
136 	}
137 	expect_true(cache_bin_ncached_get_local(&bin, &info) == ncached_max,
138 	    "");
139 	success = cache_bin_dalloc_easy(&bin, &ptrs[ncached_max]);
140 	expect_false(success, "Shouldn't be able to dalloc into a full bin.");
141 
142 	cache_bin_low_water_set(&bin);
143 
144 	for (cache_bin_sz_t i = 0; i < ncached_max; i++) {
145 		expect_true(cache_bin_low_water_get(&bin, &info)
146 		    == ncached_max - i, "");
147 		expect_true(cache_bin_ncached_get_local(&bin, &info)
148 		    == ncached_max - i, "");
149 		/*
150 		 * This should fail -- the easy variant can't change the low
151 		 * water mark.
152 		 */
153 		ptr = cache_bin_alloc_easy(&bin, &success);
154 		expect_ptr_null(ptr, "");
155 		expect_false(success, "");
156 		expect_true(cache_bin_low_water_get(&bin, &info)
157 		    == ncached_max - i, "");
158 		expect_true(cache_bin_ncached_get_local(&bin, &info)
159 		    == ncached_max - i, "");
160 
161 		/* This should succeed, though. */
162 		ptr = cache_bin_alloc(&bin, &success);
163 		expect_true(success, "");
164 		expect_ptr_eq(ptr, &ptrs[ncached_max - i - 1],
165 		    "Alloc should pop in stack order");
166 		expect_true(cache_bin_low_water_get(&bin, &info)
167 		    == ncached_max - i - 1, "");
168 		expect_true(cache_bin_ncached_get_local(&bin, &info)
169 		    == ncached_max - i - 1, "");
170 	}
171 	/* Now we're empty -- all alloc attempts should fail. */
172 	expect_true(cache_bin_ncached_get_local(&bin, &info) == 0, "");
173 	ptr = cache_bin_alloc_easy(&bin, &success);
174 	expect_ptr_null(ptr, "");
175 	expect_false(success, "");
176 	ptr = cache_bin_alloc(&bin, &success);
177 	expect_ptr_null(ptr, "");
178 	expect_false(success, "");
179 
180 	for (cache_bin_sz_t i = 0; i < ncached_max / 2; i++) {
181 		cache_bin_dalloc_easy(&bin, &ptrs[i]);
182 	}
183 	cache_bin_low_water_set(&bin);
184 
185 	for (cache_bin_sz_t i = ncached_max / 2; i < ncached_max; i++) {
186 		cache_bin_dalloc_easy(&bin, &ptrs[i]);
187 	}
188 	expect_true(cache_bin_ncached_get_local(&bin, &info) == ncached_max,
189 	    "");
190 	for (cache_bin_sz_t i = ncached_max - 1; i >= ncached_max / 2; i--) {
191 		/*
192 		 * Size is bigger than low water -- the reduced version should
193 		 * succeed.
194 		 */
195 		ptr = cache_bin_alloc_easy(&bin, &success);
196 		expect_true(success, "");
197 		expect_ptr_eq(ptr, &ptrs[i], "");
198 	}
199 	/* But now, we've hit low-water. */
200 	ptr = cache_bin_alloc_easy(&bin, &success);
201 	expect_false(success, "");
202 	expect_ptr_null(ptr, "");
203 
204 	/* We're going to test filling -- we must be empty to start. */
205 	while (cache_bin_ncached_get_local(&bin, &info)) {
206 		cache_bin_alloc(&bin, &success);
207 		expect_true(success, "");
208 	}
209 
210 	/* Test fill. */
211 	/* Try to fill all, succeed fully. */
212 	do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max, ncached_max);
213 	/* Try to fill all, succeed partially. */
214 	do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max,
215 	    ncached_max / 2);
216 	/* Try to fill all, fail completely. */
217 	do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max, 0);
218 
219 	/* Try to fill some, succeed fully. */
220 	do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max / 2,
221 	    ncached_max / 2);
222 	/* Try to fill some, succeed partially. */
223 	do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max / 2,
224 	    ncached_max / 4);
225 	/* Try to fill some, fail completely. */
226 	do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max / 2, 0);
227 
228 	do_flush_test(&bin, &info, ptrs, ncached_max, ncached_max);
229 	do_flush_test(&bin, &info, ptrs, ncached_max, ncached_max / 2);
230 	do_flush_test(&bin, &info, ptrs, ncached_max, 0);
231 	do_flush_test(&bin, &info, ptrs, ncached_max / 2, ncached_max / 2);
232 	do_flush_test(&bin, &info, ptrs, ncached_max / 2, ncached_max / 4);
233 	do_flush_test(&bin, &info, ptrs, ncached_max / 2, 0);
234 
235 	do_batch_alloc_test(&bin, &info, ptrs, ncached_max, ncached_max);
236 	do_batch_alloc_test(&bin, &info, ptrs, ncached_max, ncached_max * 2);
237 	do_batch_alloc_test(&bin, &info, ptrs, ncached_max, ncached_max / 2);
238 	do_batch_alloc_test(&bin, &info, ptrs, ncached_max, 2);
239 	do_batch_alloc_test(&bin, &info, ptrs, ncached_max, 1);
240 	do_batch_alloc_test(&bin, &info, ptrs, ncached_max, 0);
241 	do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2,
242 	    ncached_max / 2);
243 	do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2, ncached_max);
244 	do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2,
245 	    ncached_max / 4);
246 	do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2, 2);
247 	do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2, 1);
248 	do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2, 0);
249 	do_batch_alloc_test(&bin, &info, ptrs, 2, ncached_max);
250 	do_batch_alloc_test(&bin, &info, ptrs, 2, 2);
251 	do_batch_alloc_test(&bin, &info, ptrs, 2, 1);
252 	do_batch_alloc_test(&bin, &info, ptrs, 2, 0);
253 	do_batch_alloc_test(&bin, &info, ptrs, 1, 2);
254 	do_batch_alloc_test(&bin, &info, ptrs, 1, 1);
255 	do_batch_alloc_test(&bin, &info, ptrs, 1, 0);
256 	do_batch_alloc_test(&bin, &info, ptrs, 0, 2);
257 	do_batch_alloc_test(&bin, &info, ptrs, 0, 1);
258 	do_batch_alloc_test(&bin, &info, ptrs, 0, 0);
259 
260 	free(ptrs);
261 }
262 TEST_END
263 
264 static void
265 do_flush_stashed_test(cache_bin_t *bin, cache_bin_info_t *info, void **ptrs,
266     cache_bin_sz_t nfill, cache_bin_sz_t nstash) {
267 	expect_true(cache_bin_ncached_get_local(bin, info) == 0,
268 	    "Bin not empty");
269 	expect_true(cache_bin_nstashed_get_local(bin, info) == 0,
270 	    "Bin not empty");
271 	expect_true(nfill + nstash <= info->ncached_max, "Exceeded max");
272 
273 	bool ret;
274 	/* Fill */
275 	for (cache_bin_sz_t i = 0; i < nfill; i++) {
276 		ret = cache_bin_dalloc_easy(bin, &ptrs[i]);
277 		expect_true(ret, "Unexpected fill failure");
278 	}
279 	expect_true(cache_bin_ncached_get_local(bin, info) == nfill,
280 	    "Wrong cached count");
281 
282 	/* Stash */
283 	for (cache_bin_sz_t i = 0; i < nstash; i++) {
284 		ret = cache_bin_stash(bin, &ptrs[i + nfill]);
285 		expect_true(ret, "Unexpected stash failure");
286 	}
287 	expect_true(cache_bin_nstashed_get_local(bin, info) == nstash,
288 	    "Wrong stashed count");
289 
290 	if (nfill + nstash == info->ncached_max) {
291 		ret = cache_bin_dalloc_easy(bin, &ptrs[0]);
292 		expect_false(ret, "Should not dalloc into a full bin");
293 		ret = cache_bin_stash(bin, &ptrs[0]);
294 		expect_false(ret, "Should not stash into a full bin");
295 	}
296 
297 	/* Alloc filled ones */
298 	for (cache_bin_sz_t i = 0; i < nfill; i++) {
299 		void *ptr = cache_bin_alloc(bin, &ret);
300 		expect_true(ret, "Unexpected alloc failure");
301 		/* Verify it's not from the stashed range. */
302 		expect_true((uintptr_t)ptr < (uintptr_t)&ptrs[nfill],
303 		    "Should not alloc stashed ptrs");
304 	}
305 	expect_true(cache_bin_ncached_get_local(bin, info) == 0,
306 	    "Wrong cached count");
307 	expect_true(cache_bin_nstashed_get_local(bin, info) == nstash,
308 	    "Wrong stashed count");
309 
310 	cache_bin_alloc(bin, &ret);
311 	expect_false(ret, "Should not alloc stashed");
312 
313 	/* Clear stashed ones */
314 	cache_bin_finish_flush_stashed(bin, info);
315 	expect_true(cache_bin_ncached_get_local(bin, info) == 0,
316 	    "Wrong cached count");
317 	expect_true(cache_bin_nstashed_get_local(bin, info) == 0,
318 	    "Wrong stashed count");
319 
320 	cache_bin_alloc(bin, &ret);
321 	expect_false(ret, "Should not alloc from empty bin");
322 }
323 
324 TEST_BEGIN(test_cache_bin_stash) {
325 	const int ncached_max = 100;
326 
327 	cache_bin_t bin;
328 	cache_bin_info_t info;
329 	cache_bin_info_init(&info, ncached_max);
330 	test_bin_init(&bin, &info);
331 
332 	/*
333 	 * The content of this array is not accessed; instead the interior
334 	 * addresses are used to insert / stash into the bins as test pointers.
335 	 */
336 	void **ptrs = mallocx(sizeof(void *) * (ncached_max + 1), 0);
337 	assert_ptr_not_null(ptrs, "Unexpected mallocx failure");
338 	bool ret;
339 	for (cache_bin_sz_t i = 0; i < ncached_max; i++) {
340 		expect_true(cache_bin_ncached_get_local(&bin, &info) ==
341 		    (i / 2 + i % 2), "Wrong ncached value");
342 		expect_true(cache_bin_nstashed_get_local(&bin, &info) == i / 2,
343 		    "Wrong nstashed value");
344 		if (i % 2 == 0) {
345 			cache_bin_dalloc_easy(&bin, &ptrs[i]);
346 		} else {
347 			ret = cache_bin_stash(&bin, &ptrs[i]);
348 			expect_true(ret, "Should be able to stash into a "
349 			    "non-full cache bin");
350 		}
351 	}
352 	ret = cache_bin_dalloc_easy(&bin, &ptrs[0]);
353 	expect_false(ret, "Should not dalloc into a full cache bin");
354 	ret = cache_bin_stash(&bin, &ptrs[0]);
355 	expect_false(ret, "Should not stash into a full cache bin");
356 	for (cache_bin_sz_t i = 0; i < ncached_max; i++) {
357 		void *ptr = cache_bin_alloc(&bin, &ret);
358 		if (i < ncached_max / 2) {
359 			expect_true(ret, "Should be able to alloc");
360 			uintptr_t diff = ((uintptr_t)ptr - (uintptr_t)&ptrs[0])
361 			    / sizeof(void *);
362 			expect_true(diff % 2 == 0, "Should be able to alloc");
363 		} else {
364 			expect_false(ret, "Should not alloc stashed");
365 			expect_true(cache_bin_nstashed_get_local(&bin, &info) ==
366 			    ncached_max / 2, "Wrong nstashed value");
367 		}
368 	}
369 
370 	test_bin_init(&bin, &info);
371 	do_flush_stashed_test(&bin, &info, ptrs, ncached_max, 0);
372 	do_flush_stashed_test(&bin, &info, ptrs, 0, ncached_max);
373 	do_flush_stashed_test(&bin, &info, ptrs, ncached_max / 2, ncached_max / 2);
374 	do_flush_stashed_test(&bin, &info, ptrs, ncached_max / 4, ncached_max / 2);
375 	do_flush_stashed_test(&bin, &info, ptrs, ncached_max / 2, ncached_max / 4);
376 	do_flush_stashed_test(&bin, &info, ptrs, ncached_max / 4, ncached_max / 4);
377 }
378 TEST_END
379 
380 int
381 main(void) {
382 	return test(test_cache_bin,
383 		test_cache_bin_stash);
384 }
385