Fortran · 22013 bytes Raw Blame History
1 ! ==============================================================================
2 ! Module: string_pool_v2
3 ! Purpose: Efficient string memory management with true zero-copy pooling
4 !
5 ! This implements Phase 3 of the memory pooling project - eliminating double
6 ! allocation by using direct pointers to pool memory.
7 ! ==============================================================================
8 module string_pool
9 use iso_fortran_env, only: int32, int64
10 implicit none
11 private
12
13 ! Public interface
14 public :: pool_get_string, pool_release_string, pool_intern_string
15 public :: pool_statistics, pool_cleanup, pool_init
16 public :: string_ref, pool_copy_to_ref, pool_get_string_ptr
17
18 ! Constants
19 integer, parameter :: NUM_BUCKETS = 5
20 ! Bucket sizes: 64, 256, 1024, 4096, 16384 bytes
21 integer, parameter :: INITIAL_SLOTS = 100
22 integer, parameter :: MAX_SLOTS = 10000
23
24 ! String reference type - points directly to pool memory
25 type :: string_ref
26 integer :: pool_index = 0 ! Encoded bucket and slot index
27 integer :: ref_count = 0
28 integer :: str_len = 0 ! Actual string length
29 character(:), pointer :: data => null()
30 end type string_ref
31
32 ! Pool statistics
33 type :: pool_stats
34 integer(int64) :: total_allocations = 0
35 integer(int64) :: total_deallocations = 0
36 integer :: current_strings = 0
37 integer :: peak_strings = 0
38 integer(int64) :: cache_hits = 0
39 integer(int64) :: cache_misses = 0
40 end type pool_stats
41
42 ! MODULE-LEVEL TARGET STORAGE - This is the key!
43 ! We declare these at module level with TARGET so we can point to them
44 character(len=64), target, allocatable :: pool_64(:)
45 character(len=256), target, allocatable :: pool_256(:)
46 character(len=1024), target, allocatable :: pool_1024(:)
47 character(len=4096), target, allocatable :: pool_4096(:)
48 character(len=16384), target, allocatable :: pool_16384(:)
49
50 ! Tracking arrays for each pool
51 logical, allocatable :: in_use_64(:), in_use_256(:), in_use_1024(:), in_use_4096(:), in_use_16384(:)
52 integer, allocatable :: ref_counts_64(:), ref_counts_256(:), ref_counts_1024(:), ref_counts_4096(:), ref_counts_16384(:)
53
54 ! Pool sizes
55 integer :: size_64 = 0, size_256 = 0, size_1024 = 0, size_4096 = 0, size_16384 = 0
56
57 ! Interned strings for deduplication
58 character(len=256), allocatable :: interned_strings(:)
59 integer, allocatable :: interned_refs(:)
60 integer :: num_interned = 0
61
62 ! Global statistics
63 type(pool_stats) :: stats
64 logical :: pool_initialized = .false.
65
66 contains
67
68 ! Initialize the string pool
69 subroutine pool_init()
70 if (pool_initialized) return
71
72 ! Allocate initial pool storage
73 allocate(pool_64(INITIAL_SLOTS))
74 allocate(pool_256(INITIAL_SLOTS))
75 allocate(pool_1024(INITIAL_SLOTS))
76 allocate(pool_4096(INITIAL_SLOTS))
77 allocate(pool_16384(INITIAL_SLOTS/10)) ! Fewer slots for large strings
78
79 ! Allocate tracking arrays
80 allocate(in_use_64(INITIAL_SLOTS))
81 allocate(in_use_256(INITIAL_SLOTS))
82 allocate(in_use_1024(INITIAL_SLOTS))
83 allocate(in_use_4096(INITIAL_SLOTS))
84 allocate(in_use_16384(INITIAL_SLOTS/10))
85
86 allocate(ref_counts_64(INITIAL_SLOTS))
87 allocate(ref_counts_256(INITIAL_SLOTS))
88 allocate(ref_counts_1024(INITIAL_SLOTS))
89 allocate(ref_counts_4096(INITIAL_SLOTS))
90 allocate(ref_counts_16384(INITIAL_SLOTS/10))
91
92 ! Initialize tracking arrays
93 in_use_64 = .false.
94 in_use_256 = .false.
95 in_use_1024 = .false.
96 in_use_4096 = .false.
97 in_use_16384 = .false.
98
99 ref_counts_64 = 0
100 ref_counts_256 = 0
101 ref_counts_1024 = 0
102 ref_counts_4096 = 0
103 ref_counts_16384 = 0
104
105 size_64 = INITIAL_SLOTS
106 size_256 = INITIAL_SLOTS
107 size_1024 = INITIAL_SLOTS
108 size_4096 = INITIAL_SLOTS
109 size_16384 = INITIAL_SLOTS/10
110
111 ! Initialize interned strings
112 allocate(interned_strings(100))
113 allocate(interned_refs(100))
114 interned_refs = 0
115 num_interned = 0
116
117 pool_initialized = .true.
118 end subroutine pool_init
119
120 ! Get a string from the pool - ZERO COPY VERSION!
121 recursive function pool_get_string(length) result(ref)
122 integer, intent(in) :: length
123 type(string_ref) :: ref
124 integer :: bucket_idx, slot_idx
125
126 if (.not. pool_initialized) call pool_init()
127
128 ! Determine which bucket to use
129 if (length <= 64) then
130 bucket_idx = 1
131 slot_idx = find_free_slot_64()
132 if (slot_idx > 0) then
133 in_use_64(slot_idx) = .true.
134 ref_counts_64(slot_idx) = 1
135 ! DIRECT POINTER - NO ALLOCATION!
136 ref%data => pool_64(slot_idx)(1:length)
137 end if
138 else if (length <= 256) then
139 bucket_idx = 2
140 slot_idx = find_free_slot_256()
141 if (slot_idx > 0) then
142 in_use_256(slot_idx) = .true.
143 ref_counts_256(slot_idx) = 1
144 ! DIRECT POINTER - NO ALLOCATION!
145 ref%data => pool_256(slot_idx)(1:length)
146 end if
147 else if (length <= 1024) then
148 bucket_idx = 3
149 slot_idx = find_free_slot_1024()
150 if (slot_idx > 0) then
151 in_use_1024(slot_idx) = .true.
152 ref_counts_1024(slot_idx) = 1
153 ! DIRECT POINTER - NO ALLOCATION!
154 ref%data => pool_1024(slot_idx)(1:length)
155 end if
156 else if (length <= 4096) then
157 bucket_idx = 4
158 slot_idx = find_free_slot_4096()
159 if (slot_idx > 0) then
160 in_use_4096(slot_idx) = .true.
161 ref_counts_4096(slot_idx) = 1
162 ! DIRECT POINTER - NO ALLOCATION!
163 ref%data => pool_4096(slot_idx)(1:length)
164 end if
165 else if (length <= 16384) then
166 bucket_idx = 5
167 slot_idx = find_free_slot_16384()
168 if (slot_idx > 0) then
169 in_use_16384(slot_idx) = .true.
170 ref_counts_16384(slot_idx) = 1
171 ! DIRECT POINTER - NO ALLOCATION!
172 ref%data => pool_16384(slot_idx)(1:length)
173 end if
174 else
175 ! Too large for pool - allocate directly
176 ! NOTE: Direct allocation on macOS ARM64 (flang-new) should be avoided
177 ! for strings >127 bytes due to compiler limitations UNLESS the C string
178 ! library is enabled (which handles dangerous operations safely)
179 bucket_idx = 0
180 slot_idx = -1
181 #if defined(__APPLE__) && !defined(USE_C_STRINGS)
182 ! On macOS WITHOUT C string library, cap direct allocations at 127 bytes
183 ! When USE_C_STRINGS is defined, the C library handles large strings safely
184 if (length > 127) then
185 ! Allocation would exceed safe limit - return null ref
186 ref%pool_index = 0
187 ref%ref_count = 0
188 ref%str_len = 0
189 ref%data => null()
190 stats%cache_misses = stats%cache_misses + 1
191 return
192 end if
193 #endif
194 allocate(character(len=length) :: ref%data)
195 stats%cache_misses = stats%cache_misses + 1
196 end if
197
198 ! Set up reference
199 if (slot_idx > 0) then
200 ref%pool_index = bucket_idx * 10000 + slot_idx
201 ref%ref_count = 1
202 ref%str_len = length
203 stats%cache_hits = stats%cache_hits + 1
204 else if (bucket_idx > 0) then
205 ! Pool was full, need to expand
206 call expand_pool(bucket_idx)
207 ! Retry after expansion
208 ref = pool_get_string(length)
209 return
210 else
211 ! Direct allocation
212 ref%pool_index = -1
213 ref%ref_count = 1
214 ref%str_len = length
215 end if
216
217 ! Update statistics
218 stats%total_allocations = stats%total_allocations + 1
219 stats%current_strings = stats%current_strings + 1
220 if (stats%current_strings > stats%peak_strings) then
221 stats%peak_strings = stats%current_strings
222 end if
223
224 end function pool_get_string
225
226 ! Find a free slot in the 64-byte pool
227 function find_free_slot_64() result(slot)
228 integer :: slot, i
229 slot = 0
230 do i = 1, size_64
231 if (.not. in_use_64(i)) then
232 slot = i
233 exit
234 end if
235 end do
236 end function find_free_slot_64
237
238 ! Find a free slot in the 256-byte pool
239 function find_free_slot_256() result(slot)
240 integer :: slot, i
241 slot = 0
242 do i = 1, size_256
243 if (.not. in_use_256(i)) then
244 slot = i
245 exit
246 end if
247 end do
248 end function find_free_slot_256
249
250 ! Find a free slot in the 1024-byte pool
251 function find_free_slot_1024() result(slot)
252 integer :: slot, i
253 slot = 0
254 do i = 1, size_1024
255 if (.not. in_use_1024(i)) then
256 slot = i
257 exit
258 end if
259 end do
260 end function find_free_slot_1024
261
262 ! Find a free slot in the 4096-byte pool
263 function find_free_slot_4096() result(slot)
264 integer :: slot, i
265 slot = 0
266 do i = 1, size_4096
267 if (.not. in_use_4096(i)) then
268 slot = i
269 exit
270 end if
271 end do
272 end function find_free_slot_4096
273
274 ! Find a free slot in the 16384-byte pool
275 function find_free_slot_16384() result(slot)
276 integer :: slot, i
277 slot = 0
278 do i = 1, size_16384
279 if (.not. in_use_16384(i)) then
280 slot = i
281 exit
282 end if
283 end do
284 end function find_free_slot_16384
285
286 ! Expand a pool when it's full
287 subroutine expand_pool(bucket_idx)
288 integer, intent(in) :: bucket_idx
289 integer :: old_size, new_size
290
291 select case(bucket_idx)
292 case(1) ! 64-byte pool
293 old_size = size_64
294 new_size = min(old_size * 2, MAX_SLOTS)
295 if (new_size > old_size) then
296 call resize_pool_64(new_size)
297 size_64 = new_size
298 end if
299 case(2) ! 256-byte pool
300 old_size = size_256
301 new_size = min(old_size * 2, MAX_SLOTS)
302 if (new_size > old_size) then
303 call resize_pool_256(new_size)
304 size_256 = new_size
305 end if
306 case(3) ! 1024-byte pool
307 old_size = size_1024
308 new_size = min(old_size * 2, MAX_SLOTS)
309 if (new_size > old_size) then
310 call resize_pool_1024(new_size)
311 size_1024 = new_size
312 end if
313 case(4) ! 4096-byte pool
314 old_size = size_4096
315 new_size = min(old_size * 2, MAX_SLOTS)
316 if (new_size > old_size) then
317 call resize_pool_4096(new_size)
318 size_4096 = new_size
319 end if
320 case(5) ! 16384-byte pool
321 old_size = size_16384
322 new_size = min(old_size * 2, MAX_SLOTS/10)
323 if (new_size > old_size) then
324 call resize_pool_16384(new_size)
325 size_16384 = new_size
326 end if
327 end select
328 end subroutine expand_pool
329
330 ! Resize helper functions for each pool
331 subroutine resize_pool_64(new_size)
332 integer, intent(in) :: new_size
333 character(len=64), allocatable, target :: temp(:)
334 logical, allocatable :: temp_use(:)
335 integer, allocatable :: temp_refs(:)
336 integer :: old_size
337
338 old_size = size(pool_64)
339
340 ! Save old data
341 allocate(temp(old_size))
342 allocate(temp_use(old_size))
343 allocate(temp_refs(old_size))
344 temp = pool_64
345 temp_use = in_use_64
346 temp_refs = ref_counts_64
347
348 ! Reallocate
349 deallocate(pool_64, in_use_64, ref_counts_64)
350 allocate(pool_64(new_size))
351 allocate(in_use_64(new_size))
352 allocate(ref_counts_64(new_size))
353
354 ! Restore data
355 pool_64(1:old_size) = temp
356 in_use_64(1:old_size) = temp_use
357 in_use_64(old_size+1:) = .false.
358 ref_counts_64(1:old_size) = temp_refs
359 ref_counts_64(old_size+1:) = 0
360
361 deallocate(temp, temp_use, temp_refs)
362 end subroutine resize_pool_64
363
364 subroutine resize_pool_256(new_size)
365 integer, intent(in) :: new_size
366 character(len=256), allocatable, target :: temp(:)
367 logical, allocatable :: temp_use(:)
368 integer, allocatable :: temp_refs(:)
369 integer :: old_size
370
371 old_size = size(pool_256)
372
373 allocate(temp(old_size))
374 allocate(temp_use(old_size))
375 allocate(temp_refs(old_size))
376 temp = pool_256
377 temp_use = in_use_256
378 temp_refs = ref_counts_256
379
380 deallocate(pool_256, in_use_256, ref_counts_256)
381 allocate(pool_256(new_size))
382 allocate(in_use_256(new_size))
383 allocate(ref_counts_256(new_size))
384
385 pool_256(1:old_size) = temp
386 in_use_256(1:old_size) = temp_use
387 in_use_256(old_size+1:) = .false.
388 ref_counts_256(1:old_size) = temp_refs
389 ref_counts_256(old_size+1:) = 0
390
391 deallocate(temp, temp_use, temp_refs)
392 end subroutine resize_pool_256
393
394 subroutine resize_pool_1024(new_size)
395 integer, intent(in) :: new_size
396 character(len=1024), allocatable, target :: temp(:)
397 logical, allocatable :: temp_use(:)
398 integer, allocatable :: temp_refs(:)
399 integer :: old_size
400
401 old_size = size(pool_1024)
402
403 allocate(temp(old_size))
404 allocate(temp_use(old_size))
405 allocate(temp_refs(old_size))
406 temp = pool_1024
407 temp_use = in_use_1024
408 temp_refs = ref_counts_1024
409
410 deallocate(pool_1024, in_use_1024, ref_counts_1024)
411 allocate(pool_1024(new_size))
412 allocate(in_use_1024(new_size))
413 allocate(ref_counts_1024(new_size))
414
415 pool_1024(1:old_size) = temp
416 in_use_1024(1:old_size) = temp_use
417 in_use_1024(old_size+1:) = .false.
418 ref_counts_1024(1:old_size) = temp_refs
419 ref_counts_1024(old_size+1:) = 0
420
421 deallocate(temp, temp_use, temp_refs)
422 end subroutine resize_pool_1024
423
424 subroutine resize_pool_4096(new_size)
425 integer, intent(in) :: new_size
426 character(len=4096), allocatable, target :: temp(:)
427 logical, allocatable :: temp_use(:)
428 integer, allocatable :: temp_refs(:)
429 integer :: old_size
430
431 old_size = size(pool_4096)
432
433 allocate(temp(old_size))
434 allocate(temp_use(old_size))
435 allocate(temp_refs(old_size))
436 temp = pool_4096
437 temp_use = in_use_4096
438 temp_refs = ref_counts_4096
439
440 deallocate(pool_4096, in_use_4096, ref_counts_4096)
441 allocate(pool_4096(new_size))
442 allocate(in_use_4096(new_size))
443 allocate(ref_counts_4096(new_size))
444
445 pool_4096(1:old_size) = temp
446 in_use_4096(1:old_size) = temp_use
447 in_use_4096(old_size+1:) = .false.
448 ref_counts_4096(1:old_size) = temp_refs
449 ref_counts_4096(old_size+1:) = 0
450
451 deallocate(temp, temp_use, temp_refs)
452 end subroutine resize_pool_4096
453
454 subroutine resize_pool_16384(new_size)
455 integer, intent(in) :: new_size
456 character(len=16384), allocatable, target :: temp(:)
457 logical, allocatable :: temp_use(:)
458 integer, allocatable :: temp_refs(:)
459 integer :: old_size
460
461 old_size = size(pool_16384)
462
463 allocate(temp(old_size))
464 allocate(temp_use(old_size))
465 allocate(temp_refs(old_size))
466 temp = pool_16384
467 temp_use = in_use_16384
468 temp_refs = ref_counts_16384
469
470 deallocate(pool_16384, in_use_16384, ref_counts_16384)
471 allocate(pool_16384(new_size))
472 allocate(in_use_16384(new_size))
473 allocate(ref_counts_16384(new_size))
474
475 pool_16384(1:old_size) = temp
476 in_use_16384(1:old_size) = temp_use
477 in_use_16384(old_size+1:) = .false.
478 ref_counts_16384(1:old_size) = temp_refs
479 ref_counts_16384(old_size+1:) = 0
480
481 deallocate(temp, temp_use, temp_refs)
482 end subroutine resize_pool_16384
483
484 ! Release a string back to the pool
485 subroutine pool_release_string(ref)
486 type(string_ref), intent(inout) :: ref
487 integer :: bucket_idx, slot_idx
488
489 if (ref%pool_index == 0) then
490 ! Never allocated
491 return
492 else if (ref%pool_index == -1) then
493 ! Direct allocation
494 if (associated(ref%data)) deallocate(ref%data)
495 stats%total_deallocations = stats%total_deallocations + 1
496 stats%current_strings = stats%current_strings - 1
497 else
498 ! From pool
499 bucket_idx = ref%pool_index / 10000
500 slot_idx = mod(ref%pool_index, 10000)
501
502 select case(bucket_idx)
503 case(1)
504 ref_counts_64(slot_idx) = ref_counts_64(slot_idx) - 1
505 if (ref_counts_64(slot_idx) <= 0) then
506 in_use_64(slot_idx) = .false.
507 pool_64(slot_idx) = '' ! Clear content
508 end if
509 case(2)
510 ref_counts_256(slot_idx) = ref_counts_256(slot_idx) - 1
511 if (ref_counts_256(slot_idx) <= 0) then
512 in_use_256(slot_idx) = .false.
513 pool_256(slot_idx) = ''
514 end if
515 case(3)
516 ref_counts_1024(slot_idx) = ref_counts_1024(slot_idx) - 1
517 if (ref_counts_1024(slot_idx) <= 0) then
518 in_use_1024(slot_idx) = .false.
519 pool_1024(slot_idx) = ''
520 end if
521 case(4)
522 ref_counts_4096(slot_idx) = ref_counts_4096(slot_idx) - 1
523 if (ref_counts_4096(slot_idx) <= 0) then
524 in_use_4096(slot_idx) = .false.
525 pool_4096(slot_idx) = ''
526 end if
527 case(5)
528 ref_counts_16384(slot_idx) = ref_counts_16384(slot_idx) - 1
529 if (ref_counts_16384(slot_idx) <= 0) then
530 in_use_16384(slot_idx) = .false.
531 pool_16384(slot_idx) = ''
532 end if
533 end select
534
535 stats%total_deallocations = stats%total_deallocations + 1
536 stats%current_strings = stats%current_strings - 1
537 end if
538
539 ! Clear reference
540 ref%pool_index = 0
541 ref%ref_count = 0
542 ref%str_len = 0
543 ref%data => null()
544
545 end subroutine pool_release_string
546
547 ! Copy data to a pooled string reference
548 subroutine pool_copy_to_ref(ref, source_str)
549 type(string_ref), intent(inout) :: ref
550 character(len=*), intent(in) :: source_str
551 integer :: copy_len
552
553 if (.not. associated(ref%data)) return
554
555 copy_len = min(len(source_str), ref%str_len)
556 ref%data = ' ' ! Clear first
557 ref%data(1:copy_len) = source_str(1:copy_len)
558
559 end subroutine pool_copy_to_ref
560
561 ! Get a pointer to the string data
562 function pool_get_string_ptr(ref) result(ptr)
563 type(string_ref), intent(in) :: ref
564 character(:), pointer :: ptr
565
566 if (associated(ref%data)) then
567 ptr => ref%data
568 else
569 ptr => null()
570 end if
571
572 end function pool_get_string_ptr
573
574 ! Intern a string for deduplication
575 ! WARNING: Uses allocatable strings - may be problematic on macOS ARM64 with flang-new
576 function pool_intern_string(str) result(ref)
577 character(len=*), intent(in) :: str
578 type(string_ref) :: ref
579 integer :: i
580 integer :: str_len
581
582 if (.not. pool_initialized) call pool_init()
583
584 str_len = len_trim(str)
585
586 #if defined(__APPLE__) && !defined(USE_C_STRINGS)
587 ! On macOS WITHOUT C string library, cap interned string length to 127 bytes
588 ! When USE_C_STRINGS is defined, the C library handles large strings safely
589 if (str_len > 127) then
590 ! String too long for safe interning on macOS - use regular pool instead
591 ref = pool_get_string(min(str_len, 127))
592 if (associated(ref%data)) then
593 call pool_copy_to_ref(ref, str(1:min(str_len, 127)))
594 end if
595 return
596 end if
597 #endif
598
599 ! Check if already interned
600 do i = 1, num_interned
601 if (interned_strings(i) == str) then
602 interned_refs(i) = interned_refs(i) + 1
603 ref%pool_index = -10000 - i
604 ref%ref_count = interned_refs(i)
605 ref%str_len = str_len
606 allocate(character(len=str_len) :: ref%data)
607 ref%data = trim(str)
608 stats%cache_hits = stats%cache_hits + 1
609 return
610 end if
611 end do
612
613 ! Add new interned string
614 if (num_interned >= size(interned_strings)) then
615 call expand_interned_pool()
616 end if
617
618 num_interned = num_interned + 1
619 interned_strings(num_interned) = str
620 interned_refs(num_interned) = 1
621
622 ref%pool_index = -10000 - num_interned
623 ref%ref_count = 1
624 ref%str_len = str_len
625 allocate(character(len=str_len) :: ref%data)
626 ref%data = trim(str)
627
628 stats%cache_misses = stats%cache_misses + 1
629
630 end function pool_intern_string
631
632 ! Expand interned string pool
633 subroutine expand_interned_pool()
634 character(len=256), allocatable :: temp_strings(:)
635 integer, allocatable :: temp_refs(:)
636 integer :: old_size, new_size
637
638 old_size = size(interned_strings)
639 new_size = old_size * 2
640
641 allocate(temp_strings(old_size))
642 allocate(temp_refs(old_size))
643 temp_strings = interned_strings
644 temp_refs = interned_refs
645
646 deallocate(interned_strings, interned_refs)
647 allocate(interned_strings(new_size))
648 allocate(interned_refs(new_size))
649
650 interned_strings(1:old_size) = temp_strings
651 interned_refs(1:old_size) = temp_refs
652 interned_refs(old_size+1:) = 0
653
654 deallocate(temp_strings, temp_refs)
655 end subroutine expand_interned_pool
656
657 ! Get pool statistics
658 subroutine pool_statistics(total_allocs, total_deallocs, current, peak, hit_rate)
659 integer, intent(out) :: total_allocs, total_deallocs, current, peak
660 real, intent(out) :: hit_rate
661
662 total_allocs = int(stats%total_allocations)
663 total_deallocs = int(stats%total_deallocations)
664 current = stats%current_strings
665 peak = stats%peak_strings
666
667 if (stats%cache_hits + stats%cache_misses > 0) then
668 hit_rate = real(stats%cache_hits) / real(stats%cache_hits + stats%cache_misses)
669 else
670 hit_rate = 0.0
671 end if
672
673 end subroutine pool_statistics
674
675 ! Clean up the entire pool
676 subroutine pool_cleanup()
677 if (.not. pool_initialized) return
678
679 ! Deallocate all pools
680 if (allocated(pool_64)) deallocate(pool_64)
681 if (allocated(pool_256)) deallocate(pool_256)
682 if (allocated(pool_1024)) deallocate(pool_1024)
683 if (allocated(pool_4096)) deallocate(pool_4096)
684 if (allocated(pool_16384)) deallocate(pool_16384)
685
686 ! Deallocate tracking arrays
687 if (allocated(in_use_64)) deallocate(in_use_64)
688 if (allocated(in_use_256)) deallocate(in_use_256)
689 if (allocated(in_use_1024)) deallocate(in_use_1024)
690 if (allocated(in_use_4096)) deallocate(in_use_4096)
691 if (allocated(in_use_16384)) deallocate(in_use_16384)
692
693 if (allocated(ref_counts_64)) deallocate(ref_counts_64)
694 if (allocated(ref_counts_256)) deallocate(ref_counts_256)
695 if (allocated(ref_counts_1024)) deallocate(ref_counts_1024)
696 if (allocated(ref_counts_4096)) deallocate(ref_counts_4096)
697 if (allocated(ref_counts_16384)) deallocate(ref_counts_16384)
698
699 ! Clean up interned strings
700 if (allocated(interned_strings)) deallocate(interned_strings)
701 if (allocated(interned_refs)) deallocate(interned_refs)
702
703 ! Reset statistics
704 stats%total_allocations = 0
705 stats%total_deallocations = 0
706 stats%current_strings = 0
707 stats%peak_strings = 0
708 stats%cache_hits = 0
709 stats%cache_misses = 0
710 num_interned = 0
711
712 pool_initialized = .false.
713
714 end subroutine pool_cleanup
715
716 end module string_pool