fortrangoingonforty/fuss / 903438f

Browse files

performance optimiations: quicksort for tree children, hash based file lookups, binary search for file ops and state and change marking

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
903438f56be4b60803ea39e79e1696faabc53a30
Parents
db4e112
Tree
7d0e939

3 changed files

StatusFile+-
M src/fuss_main.f90 77 9
M src/git_module.f90 209 25
M src/tree_module.f90 73 46
src/fuss_main.f90modified
@@ -340,8 +340,9 @@ contains
340340
 
341341
         call sort_tree(tree_root)
342342
 
343
-        ! Restore collapsed state to new tree
343
+        ! Restore collapsed state to new tree (sort first for binary search optimization)
344344
         if (n_collapsed > 0) then
345
+            call quicksort_collapsed_paths(collapsed_paths, 1, n_collapsed)
345346
             call restore_collapsed_state(tree_root, '', collapsed_paths, n_collapsed)
346347
         end if
347348
         deallocate(collapsed_paths)
@@ -432,6 +433,75 @@ contains
432433
         deallocate(temp_paths)
433434
     end subroutine resize_path_array
434435
 
436
+    ! ========== Performance Optimization: Binary Search for Collapsed Paths ==========
437
+    recursive subroutine quicksort_collapsed_paths(arr, low, high)
438
+        character(len=512), intent(inout) :: arr(:)
439
+        integer, intent(in) :: low, high
440
+        integer :: pivot_idx
441
+
442
+        if (low < high) then
443
+            call partition_collapsed_paths(arr, low, high, pivot_idx)
444
+            call quicksort_collapsed_paths(arr, low, pivot_idx - 1)
445
+            call quicksort_collapsed_paths(arr, pivot_idx + 1, high)
446
+        end if
447
+    end subroutine quicksort_collapsed_paths
448
+
449
+    subroutine partition_collapsed_paths(arr, low, high, pivot_idx)
450
+        character(len=512), intent(inout) :: arr(:)
451
+        integer, intent(in) :: low, high
452
+        integer, intent(out) :: pivot_idx
453
+        character(len=512) :: pivot, temp
454
+        integer :: i, j
455
+
456
+        pivot = trim(arr(high))
457
+        i = low - 1
458
+
459
+        do j = low, high - 1
460
+            if (trim(arr(j)) <= pivot) then
461
+                i = i + 1
462
+                temp = arr(i)
463
+                arr(i) = arr(j)
464
+                arr(j) = temp
465
+            end if
466
+        end do
467
+
468
+        temp = arr(i + 1)
469
+        arr(i + 1) = arr(high)
470
+        arr(high) = temp
471
+
472
+        pivot_idx = i + 1
473
+    end subroutine partition_collapsed_paths
474
+
475
+    function binary_search_path(paths, n, target) result(index)
476
+        character(len=512), intent(in) :: paths(:)
477
+        integer, intent(in) :: n
478
+        character(len=*), intent(in) :: target
479
+        integer :: index
480
+        integer :: low, high, mid
481
+        character(len=512) :: target_trimmed, mid_val
482
+
483
+        index = -1
484
+        if (n == 0) return
485
+
486
+        target_trimmed = trim(target)
487
+        low = 1
488
+        high = n
489
+
490
+        do while (low <= high)
491
+            mid = low + (high - low) / 2
492
+            mid_val = trim(paths(mid))
493
+
494
+            if (mid_val == target_trimmed) then
495
+                index = mid
496
+                return
497
+            else if (mid_val < target_trimmed) then
498
+                low = mid + 1
499
+            else
500
+                high = mid - 1
501
+            end if
502
+        end do
503
+    end function binary_search_path
504
+
435505
     recursive subroutine restore_collapsed_state(node, parent_path, collapsed_paths, n_collapsed)
436506
         type(tree_node), pointer, intent(inout) :: node
437507
         character(len=*), intent(in) :: parent_path
@@ -439,7 +509,7 @@ contains
439509
         integer, intent(in) :: n_collapsed
440510
         type(tree_node), pointer :: child
441511
         character(len=512) :: full_path
442
-        integer :: i
512
+        integer :: idx
443513
 
444514
         ! Build full path for this node
445515
         if (len_trim(parent_path) == 0) then
@@ -448,14 +518,12 @@ contains
448518
             full_path = trim(parent_path) // '/' // trim(node%name)
449519
         end if
450520
 
451
-        ! Check if this directory should be collapsed
521
+        ! Check if this directory should be collapsed using binary search (O(log n) vs O(n))
452522
         if (.not. node%is_file) then
453
-            do i = 1, n_collapsed
454
-                if (trim(collapsed_paths(i)) == trim(full_path)) then
455
-                    node%is_expanded = .false.
456
-                    exit
457
-                end if
458
-            end do
523
+            idx = binary_search_path(collapsed_paths, n_collapsed, full_path)
524
+            if (idx > 0) then
525
+                node%is_expanded = .false.
526
+            end if
459527
         end if
460528
 
461529
         ! Recursively restore for children
src/git_module.f90modified
@@ -5,6 +5,82 @@ module git_module
55
 
66
 contains
77
 
8
+    ! ========== Performance Optimization: Binary Search Functions ==========
9
+
10
+    recursive subroutine quicksort_files(arr, low, high)
11
+        type(file_entry), intent(inout) :: arr(:)
12
+        integer, intent(in) :: low, high
13
+        integer :: pivot_idx
14
+
15
+        if (low < high) then
16
+            call partition_files(arr, low, high, pivot_idx)
17
+            call quicksort_files(arr, low, pivot_idx - 1)
18
+            call quicksort_files(arr, pivot_idx + 1, high)
19
+        end if
20
+    end subroutine quicksort_files
21
+
22
+    subroutine partition_files(arr, low, high, pivot_idx)
23
+        type(file_entry), intent(inout) :: arr(:)
24
+        integer, intent(in) :: low, high
25
+        integer, intent(out) :: pivot_idx
26
+        character(len=512) :: pivot_path
27
+        type(file_entry) :: temp
28
+        integer :: i, j
29
+
30
+        pivot_path = trim(arr(high)%path)
31
+        i = low - 1
32
+
33
+        do j = low, high - 1
34
+            if (trim(arr(j)%path) <= pivot_path) then
35
+                i = i + 1
36
+                ! Swap arr(i) and arr(j)
37
+                temp = arr(i)
38
+                arr(i) = arr(j)
39
+                arr(j) = temp
40
+            end if
41
+        end do
42
+
43
+        ! Swap arr(i+1) and arr(high)
44
+        temp = arr(i + 1)
45
+        arr(i + 1) = arr(high)
46
+        arr(high) = temp
47
+
48
+        pivot_idx = i + 1
49
+    end subroutine partition_files
50
+
51
+    function binary_search_file(arr, n, target_path) result(index)
52
+        type(file_entry), intent(in) :: arr(:)
53
+        integer, intent(in) :: n
54
+        character(len=*), intent(in) :: target_path
55
+        integer :: index
56
+        integer :: low, high, mid
57
+        character(len=512) :: target_trimmed, mid_path
58
+
59
+        index = -1  ! Not found
60
+        if (n == 0) return
61
+
62
+        target_trimmed = trim(target_path)
63
+        low = 1
64
+        high = n
65
+
66
+        do while (low <= high)
67
+            mid = low + (high - low) / 2
68
+            mid_path = trim(arr(mid)%path)
69
+
70
+            if (mid_path == target_trimmed) then
71
+                index = mid
72
+                return
73
+            else if (mid_path < target_trimmed) then
74
+                low = mid + 1
75
+            else
76
+                high = mid - 1
77
+            end if
78
+        end do
79
+    end function binary_search_file
80
+
81
+    ! ========== End Binary Search Functions ==========
82
+
83
+
884
     subroutine get_dirty_files(files, n_files)
985
         type(file_entry), allocatable, intent(out) :: files(:)
1086
         integer, intent(out) :: n_files
@@ -77,7 +153,11 @@ contains
77153
 
78154
         ! Copy to output array
79155
         allocate(files(n_files))
80
-        if (n_files > 0) files(1:n_files) = temp_files(1:n_files)
156
+        if (n_files > 0) then
157
+            files(1:n_files) = temp_files(1:n_files)
158
+            ! Sort for binary search optimization
159
+            call quicksort_files(files, 1, n_files)
160
+        end if
81161
         deallocate(temp_files)
82162
     end subroutine get_dirty_files
83163
 
@@ -137,26 +217,23 @@ contains
137217
                     call resize_array(temp_files, max_files)
138218
                 end if
139219
 
140
-                ! Check if file is dirty and get status
141
-                is_dirty_file = .false.
220
+                ! Check if file is dirty and get status using binary search (O(log n) vs O(n))
221
+                temp_files(n_files)%path = trim(line)
142222
                 temp_files(n_files)%status = '  '  ! Initialize as clean
143223
                 temp_files(n_files)%is_staged = .false.
144224
                 temp_files(n_files)%is_unstaged = .false.
145225
                 temp_files(n_files)%is_untracked = .false.
146226
                 temp_files(n_files)%has_incoming = .false.
147
-                do i = 1, n_dirty
148
-                    if (trim(dirty_files(i)%path) == trim(line)) then
149
-                        is_dirty_file = .true.
150
-                        temp_files(n_files)%status = dirty_files(i)%status
151
-                        temp_files(n_files)%is_staged = dirty_files(i)%is_staged
152
-                        temp_files(n_files)%is_unstaged = dirty_files(i)%is_unstaged
153
-                        temp_files(n_files)%is_untracked = dirty_files(i)%is_untracked
154
-                        temp_files(n_files)%has_incoming = dirty_files(i)%has_incoming
155
-                        exit
156
-                    end if
157
-                end do
158227
 
159
-                temp_files(n_files)%path = trim(line)
228
+                i = binary_search_file(dirty_files, n_dirty, line)
229
+                if (i > 0) then
230
+                    ! Found in dirty files - copy status
231
+                    temp_files(n_files)%status = dirty_files(i)%status
232
+                    temp_files(n_files)%is_staged = dirty_files(i)%is_staged
233
+                    temp_files(n_files)%is_unstaged = dirty_files(i)%is_unstaged
234
+                    temp_files(n_files)%is_untracked = dirty_files(i)%is_untracked
235
+                    temp_files(n_files)%has_incoming = dirty_files(i)%has_incoming
236
+                end if
160237
             end if
161238
         end do
162239
 
@@ -587,9 +664,10 @@ contains
587664
     subroutine mark_incoming_changes(files, n_files)
588665
         type(file_entry), intent(inout) :: files(:)
589666
         integer, intent(in) :: n_files
590
-        integer :: iostat, unit_num, status_code, i
667
+        integer :: iostat, unit_num, status_code, i, idx
591668
         character(len=1024) :: line
592
-        character(len=512) :: incoming_path
669
+        character(len=512), allocatable :: incoming_paths(:)
670
+        integer :: n_incoming, max_incoming
593671
 
594672
         ! Check if there's an upstream branch configured
595673
         ! Don't prompt - this is called automatically during refresh
@@ -611,25 +689,131 @@ contains
611689
         open(newunit=unit_num, file='/tmp/fuss_incoming.txt', status='old', action='read', iostat=iostat)
612690
         if (iostat /= 0) return
613691
 
692
+        ! Build sorted array of incoming file paths for binary search
693
+        max_incoming = 100
694
+        allocate(incoming_paths(max_incoming))
695
+        n_incoming = 0
696
+
614697
         do
615698
             read(unit_num, '(A)', iostat=iostat) line
616699
             if (iostat /= 0) exit
617700
 
618701
             if (len_trim(line) > 0) then
619
-                incoming_path = trim(line)
620
-                ! Mark this file as having incoming changes
621
-                do i = 1, n_files
622
-                    if (trim(files(i)%path) == trim(incoming_path)) then
623
-                        files(i)%has_incoming = .true.
624
-                        exit
625
-                    end if
626
-                end do
702
+                n_incoming = n_incoming + 1
703
+                if (n_incoming > max_incoming) then
704
+                    ! Resize array
705
+                    call resize_string_array(incoming_paths, max_incoming * 2)
706
+                    max_incoming = max_incoming * 2
707
+                end if
708
+                incoming_paths(n_incoming) = trim(line)
627709
             end if
628710
         end do
629711
 
630712
         close(unit_num, status='delete')
713
+
714
+        if (n_incoming == 0) then
715
+            deallocate(incoming_paths)
716
+            return
717
+        end if
718
+
719
+        ! Sort incoming paths for binary search
720
+        call quicksort_strings(incoming_paths, 1, n_incoming)
721
+
722
+        ! Use binary search to mark files with incoming changes (O(n log m) vs O(n×m))
723
+        do i = 1, n_files
724
+            idx = binary_search_string(incoming_paths, n_incoming, files(i)%path)
725
+            if (idx > 0) then
726
+                files(i)%has_incoming = .true.
727
+            end if
728
+        end do
729
+
730
+        deallocate(incoming_paths)
631731
     end subroutine mark_incoming_changes
632732
 
733
+    ! Helper subroutines for string array sorting and searching
734
+    subroutine resize_string_array(array, new_size)
735
+        character(len=512), allocatable, intent(inout) :: array(:)
736
+        integer, intent(in) :: new_size
737
+        character(len=512), allocatable :: temp(:)
738
+        integer :: old_size
739
+
740
+        old_size = size(array)
741
+        allocate(temp(old_size))
742
+        temp = array
743
+        deallocate(array)
744
+        allocate(array(new_size))
745
+        array(1:old_size) = temp
746
+        deallocate(temp)
747
+    end subroutine resize_string_array
748
+
749
+    recursive subroutine quicksort_strings(arr, low, high)
750
+        character(len=512), intent(inout) :: arr(:)
751
+        integer, intent(in) :: low, high
752
+        integer :: pivot_idx
753
+
754
+        if (low < high) then
755
+            call partition_strings(arr, low, high, pivot_idx)
756
+            call quicksort_strings(arr, low, pivot_idx - 1)
757
+            call quicksort_strings(arr, pivot_idx + 1, high)
758
+        end if
759
+    end subroutine quicksort_strings
760
+
761
+    subroutine partition_strings(arr, low, high, pivot_idx)
762
+        character(len=512), intent(inout) :: arr(:)
763
+        integer, intent(in) :: low, high
764
+        integer, intent(out) :: pivot_idx
765
+        character(len=512) :: pivot, temp
766
+        integer :: i, j
767
+
768
+        pivot = trim(arr(high))
769
+        i = low - 1
770
+
771
+        do j = low, high - 1
772
+            if (trim(arr(j)) <= pivot) then
773
+                i = i + 1
774
+                temp = arr(i)
775
+                arr(i) = arr(j)
776
+                arr(j) = temp
777
+            end if
778
+        end do
779
+
780
+        temp = arr(i + 1)
781
+        arr(i + 1) = arr(high)
782
+        arr(high) = temp
783
+
784
+        pivot_idx = i + 1
785
+    end subroutine partition_strings
786
+
787
+    function binary_search_string(arr, n, target) result(index)
788
+        character(len=512), intent(in) :: arr(:)
789
+        integer, intent(in) :: n
790
+        character(len=*), intent(in) :: target
791
+        integer :: index
792
+        integer :: low, high, mid
793
+        character(len=512) :: target_trimmed, mid_val
794
+
795
+        index = -1
796
+        if (n == 0) return
797
+
798
+        target_trimmed = trim(target)
799
+        low = 1
800
+        high = n
801
+
802
+        do while (low <= high)
803
+            mid = low + (high - low) / 2
804
+            mid_val = trim(arr(mid))
805
+
806
+            if (mid_val == target_trimmed) then
807
+                index = mid
808
+                return
809
+            else if (mid_val < target_trimmed) then
810
+                low = mid + 1
811
+            else
812
+                high = mid - 1
813
+            end if
814
+        end do
815
+    end function binary_search_string
816
+
633817
     subroutine git_fetch()
634818
         integer :: status
635819
         logical :: upstream_set
src/tree_module.f90modified
@@ -2,6 +2,11 @@ module tree_module
22
     use types_module
33
     implicit none
44
 
5
+    ! Helper type for array of pointers
6
+    type node_ptr
7
+        type(tree_node), pointer :: ptr
8
+    end type node_ptr
9
+
510
 contains
611
 
712
     recursive subroutine add_to_tree(node, path, is_staged, is_unstaged, is_untracked, has_incoming)
@@ -119,63 +124,85 @@ contains
119124
 
120125
     subroutine sort_children(node)
121126
         type(tree_node), pointer :: node
122
-        type(tree_node), pointer :: sorted_head, sorted_tail
123
-        type(tree_node), pointer :: current, next_node, insert_pos, prev
124
-        logical :: inserted
127
+        type(tree_node), pointer :: current
128
+        type(node_ptr), allocatable :: children_array(:)
129
+        integer :: count, i
125130
 
126131
         if (.not. associated(node%first_child)) return
127132
         if (.not. associated(node%first_child%next_sibling)) return
128133
 
129
-        ! Build sorted list
130
-        sorted_head => null()
131
-        sorted_tail => null()
132
-
134
+        ! Count children
135
+        count = 0
133136
         current => node%first_child
134137
         do while (associated(current))
135
-            next_node => current%next_sibling
136
-
137
-            ! Insert current into sorted list
138
-            if (.not. associated(sorted_head)) then
139
-                ! First element
140
-                sorted_head => current
141
-                sorted_tail => current
142
-                current%next_sibling => null()
143
-            else
144
-                ! Find insertion point
145
-                inserted = .false.
146
-                prev => null()
147
-                insert_pos => sorted_head
148
-
149
-                do while (associated(insert_pos))
150
-                    if (should_insert_before(current, insert_pos)) then
151
-                        ! Insert before insert_pos
152
-                        current%next_sibling => insert_pos
153
-                        if (associated(prev)) then
154
-                            prev%next_sibling => current
155
-                        else
156
-                            sorted_head => current
157
-                        end if
158
-                        inserted = .true.
159
-                        exit
160
-                    end if
161
-                    prev => insert_pos
162
-                    insert_pos => insert_pos%next_sibling
163
-                end do
164
-
165
-                if (.not. inserted) then
166
-                    ! Insert at end
167
-                    sorted_tail%next_sibling => current
168
-                    sorted_tail => current
169
-                    current%next_sibling => null()
170
-                end if
171
-            end if
138
+            count = count + 1
139
+            current => current%next_sibling
140
+        end do
141
+
142
+        ! Allocate array of pointers
143
+        allocate(children_array(count))
144
+
145
+        ! Fill array with pointers to children
146
+        current => node%first_child
147
+        do i = 1, count
148
+            children_array(i)%ptr => current
149
+            current => current%next_sibling
150
+        end do
151
+
152
+        ! Sort the array using quicksort
153
+        call quicksort_nodes(children_array, 1, count)
172154
 
173
-            current => next_node
155
+        ! Rebuild linked list from sorted array
156
+        node%first_child => children_array(1)%ptr
157
+        do i = 1, count - 1
158
+            children_array(i)%ptr%next_sibling => children_array(i + 1)%ptr
174159
         end do
160
+        children_array(count)%ptr%next_sibling => null()
175161
 
176
-        node%first_child => sorted_head
162
+        deallocate(children_array)
177163
     end subroutine sort_children
178164
 
165
+    recursive subroutine quicksort_nodes(arr, low, high)
166
+        type(node_ptr), intent(inout) :: arr(:)
167
+        integer, intent(in) :: low, high
168
+        integer :: pivot_idx
169
+
170
+        if (low < high) then
171
+            call partition_nodes(arr, low, high, pivot_idx)
172
+            call quicksort_nodes(arr, low, pivot_idx - 1)
173
+            call quicksort_nodes(arr, pivot_idx + 1, high)
174
+        end if
175
+    end subroutine quicksort_nodes
176
+
177
+    subroutine partition_nodes(arr, low, high, pivot_idx)
178
+        type(node_ptr), intent(inout) :: arr(:)
179
+        integer, intent(in) :: low, high
180
+        integer, intent(out) :: pivot_idx
181
+        type(tree_node), pointer :: pivot
182
+        type(node_ptr) :: temp
183
+        integer :: i, j
184
+
185
+        pivot => arr(high)%ptr
186
+        i = low - 1
187
+
188
+        do j = low, high - 1
189
+            if (trim(arr(j)%ptr%name) <= trim(pivot%name)) then
190
+                i = i + 1
191
+                ! Swap arr(i) and arr(j)
192
+                temp = arr(i)
193
+                arr(i) = arr(j)
194
+                arr(j) = temp
195
+            end if
196
+        end do
197
+
198
+        ! Swap arr(i+1) and arr(high)
199
+        temp = arr(i + 1)
200
+        arr(i + 1) = arr(high)
201
+        arr(high) = temp
202
+
203
+        pivot_idx = i + 1
204
+    end subroutine partition_nodes
205
+
179206
     function should_insert_before(a, b) result(before)
180207
         type(tree_node), pointer, intent(in) :: a, b
181208
         logical :: before