Add gpu_test_buddy_dirty_tracker_performance to demonstrate the key
advantage of the decoupled dirty-tracker design over the old dual-tree
/ force_merge approach.

The test runs two scenarios on a 4 GiB pool after alternating
clear/dirty fragmentation at 4 KiB granularity:

  1. Contiguous 4 GiB alloc: the old design requires __force_merge()
     to rebuild max_order from scratch; the new design coalesces during
     free() so the alloc is O(log N).

       old (force_merge) - 71 ms
       dirty tracker design - 17 ms

  2. Repeated 256 KiB alloc throughput: the old design pays
     __force_merge() on every alloc; the new design does not.

       old (force_merge) - 95 ms
       dirty tracker design - 24 ms

Assisted-by: Claude:claude-opus-4-8
Cc: Matthew Auld <[email protected]>
Cc: Christian König <[email protected]>
Signed-off-by: Arunpravin Paneer Selvam <[email protected]>
---
 drivers/gpu/tests/gpu_buddy_test.c | 110 +++++++++++++++++++++++++++++
 1 file changed, 110 insertions(+)

diff --git a/drivers/gpu/tests/gpu_buddy_test.c 
b/drivers/gpu/tests/gpu_buddy_test.c
index e31f368ada95..e37505849ecc 100644
--- a/drivers/gpu/tests/gpu_buddy_test.c
+++ b/drivers/gpu/tests/gpu_buddy_test.c
@@ -283,6 +283,115 @@ static void 
gpu_test_buddy_fragmentation_performance(struct kunit *test)
        gpu_buddy_fini(&mm);
 }
 
+static void gpu_test_buddy_dirty_tracker_performance(struct kunit *test)
+{
+       struct gpu_buddy_block *block, *tmp;
+       unsigned long elapsed_ms;
+       LIST_HEAD(clear_blocks);
+       LIST_HEAD(dirty_blocks);
+       LIST_HEAD(allocated);
+       struct gpu_buddy mm;
+       LIST_HEAD(results);
+       ktime_t start, end;
+       int i, count;
+
+       /*
+        * Contiguous alloc latency after alternating clear/dirty fragmentation
+        *
+        * Fill a 4 GiB pool with 4 KiB allocations, partition them into
+        * alternating cleared and dirty sets, then free both.  In the old
+        * dual-tree design every adjacent buddy pair has one cleared half and
+        * one dirty half, so the pair sits on opposite sides of the clear/dirty
+        * merge barrier and cannot be coalesced at free() time.  The pool
+        * stays fully fragmented and the subsequent contiguous 4 GiB allocation
+        * has to invoke __force_merge() to climb back up to max_order before
+        * it can succeed.  With the dirty-tracker design buddy pairs coalesce
+        * unconditionally during free(), so the pool is already at max_order
+        * before the timed alloc begins and __force_merge() is not needed.
+        */
+       KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, SZ_4G, SZ_4K),
+                              "buddy_init failed\n");
+
+       for (i = 0; i < SZ_4G / SZ_4K; i++)
+               KUNIT_ASSERT_FALSE_MSG(test,
+                                      gpu_buddy_alloc_blocks(&mm, 0, SZ_4G, 
SZ_4K, SZ_4K,
+                                                             &allocated, 0),
+                                      "buddy_alloc hit an error size=%u\n", 
SZ_4K);
+
+       count = 0;
+       list_for_each_entry_safe(block, tmp, &allocated, link) {
+               if (count++ % 2 == 0)
+                       list_move_tail(&block->link, &clear_blocks);
+               else
+                       list_move_tail(&block->link, &dirty_blocks);
+       }
+
+       gpu_buddy_free_list(&mm, &clear_blocks, GPU_BUDDY_CLEARED);
+       gpu_buddy_free_list(&mm, &dirty_blocks, 0);
+
+       start = ktime_get();
+       KUNIT_ASSERT_FALSE_MSG(test,
+                              gpu_buddy_alloc_blocks(&mm, 0, SZ_4G, SZ_4G, 
SZ_4K,
+                                                     &results,
+                                                     
GPU_BUDDY_CONTIGUOUS_ALLOCATION),
+                              "contiguous alloc failed\n");
+       end = ktime_get();
+       elapsed_ms = ktime_to_ms(ktime_sub(end, start));
+
+       kunit_info(test, "Contiguous alloc after fragmentation: %lu ms\n",
+                  elapsed_ms);
+
+       gpu_buddy_free_list(&mm, &results, 0);
+       gpu_buddy_fini(&mm);
+
+       /*
+        * Repeated alloc throughput from a maximally fragmented pool
+        *
+        * Fill a 4 GiB pool with 4 KiB allocations, free even-indexed blocks
+        * as cleared and odd-indexed blocks as dirty.  The alternating pattern
+        * ensures every adjacent buddy pair has one cleared half and one dirty
+        * half, so each pair lands on opposite sides of the old merge barrier.
+        * Each of the 16 384 x 256 KiB allocations in the timed loop has to
+        * pay the __force_merge() cost on the alloc path under the old design.
+        * With the dirty-tracker design the pool collapses to one max_order
+        * block during free(), so each alloc is a simple O(log N) split.
+        */
+       KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, SZ_4G, SZ_4K),
+                              "buddy_init failed\n");
+
+       for (i = 0; i < SZ_4G / SZ_4K; i++)
+               KUNIT_ASSERT_FALSE_MSG(test,
+                                      gpu_buddy_alloc_blocks(&mm, 0, SZ_4G, 
SZ_4K, SZ_4K,
+                                                             &allocated, 0),
+                                      "buddy_alloc hit an error size=%u\n", 
SZ_4K);
+
+       count = 0;
+       list_for_each_entry_safe(block, tmp, &allocated, link) {
+               if (count++ % 2 == 0)
+                       list_move_tail(&block->link, &clear_blocks);
+               else
+                       list_move_tail(&block->link, &dirty_blocks);
+       }
+
+       gpu_buddy_free_list(&mm, &clear_blocks, GPU_BUDDY_CLEARED);
+       gpu_buddy_free_list(&mm, &dirty_blocks, 0);
+
+       start = ktime_get();
+       for (i = 0; i < SZ_4G / SZ_256K; i++)
+               KUNIT_ASSERT_FALSE_MSG(test,
+                                      gpu_buddy_alloc_blocks(&mm, 0, SZ_4G, 
SZ_256K, SZ_4K,
+                                                             &results, 0),
+                                      "buddy_alloc hit an error size=%u\n", 
SZ_256K);
+       end = ktime_get();
+       elapsed_ms = ktime_to_ms(ktime_sub(end, start));
+
+       kunit_info(test, "Repeated 256 KiB allocs from fragmented pool: %lu 
ms\n",
+                  elapsed_ms);
+
+       gpu_buddy_free_list(&mm, &results, 0);
+       gpu_buddy_fini(&mm);
+}
+
 static void gpu_test_buddy_alloc_range_bias(struct kunit *test)
 {
        u32 mm_size, size, ps, bias_size, bias_start, bias_end, bias_rem;
@@ -1396,6 +1505,7 @@ static struct kunit_case gpu_buddy_tests[] = {
        KUNIT_CASE(gpu_test_buddy_alloc_range),
        KUNIT_CASE(gpu_test_buddy_alloc_range_bias),
        KUNIT_CASE_SLOW(gpu_test_buddy_fragmentation_performance),
+       KUNIT_CASE_SLOW(gpu_test_buddy_dirty_tracker_performance),
        KUNIT_CASE(gpu_test_buddy_alloc_exceeds_max_order),
        KUNIT_CASE(gpu_test_buddy_offset_aligned_allocation),
        KUNIT_CASE(gpu_test_buddy_subtree_offset_alignment_stress),
-- 
2.34.1

Reply via email to