Skip to content

Commit 34fef5a

Browse files
authored
Timeline view: Add string memoization (#143)
The timeline data consists of a lot of repeating strings, so it is a good candidate for string memoization to reduce the amount of duplicate variable values being stored in memory. In local testing this PR reduces load time memory use (memoizing raw trace) and dynamic viewer memory use (memoizing processed trace) by around 10%. Fixes #142
1 parent 19a8c3f commit 34fef5a

File tree

2 files changed

+134
-37
lines changed

2 files changed

+134
-37
lines changed

lglpy/timeline/data/processed_trace.py

Lines changed: 85 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,31 @@ class GPUWorkload:
6161
PARENS = re.compile(r'(\(.*\))')
6262
RESOLUTION = re.compile(r'\d+x\d+')
6363
WHITESPACE = re.compile(r'\s\s+')
64+
MEMO: dict[str, str] = dict()
65+
66+
@classmethod
67+
def memoize(cls, string: str) -> str:
68+
'''
69+
Get a memoized version of a string to reduce runtime memory use and
70+
improve rendering performance.
71+
72+
Args:
73+
string: User string to memoize.
74+
75+
Return:
76+
Memoized copy of a string.
77+
'''
78+
memo = GPUWorkload.MEMO
79+
if string not in memo:
80+
memo[string] = string
81+
return memo[string]
82+
83+
@classmethod
84+
def clear_memoize_cache(cls) -> None:
85+
'''
86+
Clear the local memoization cache.
87+
'''
88+
GPUWorkload.MEMO.clear()
6489

6590
def __init__(
6691
self, event: RenderstageEvent, metadata: Optional[MetadataWork]):
@@ -105,7 +130,8 @@ def get_label_name_full(self) -> Optional[str]:
105130
return None
106131

107132
if not LABEL_HEURISTICS:
108-
self.parsed_label_name_full = self.label_stack[-1]
133+
label = GPUWorkload.memoize(self.label_stack[-1])
134+
self.parsed_label_name_full = label
109135
return self.parsed_label_name_full
110136

111137
# Create a copy we can edit ...
@@ -146,6 +172,7 @@ def get_label_name_full(self) -> Optional[str]:
146172
else:
147173
label = '.'.join(labels)
148174

175+
label = GPUWorkload.memoize(label)
149176
self.parsed_label_name_full = label
150177
return self.parsed_label_name_full
151178

@@ -181,6 +208,7 @@ def get_label_name(self) -> Optional[str]:
181208
postfix = label[-half_max:]
182209
label = f'{prefix}...{postfix}'
183210

211+
label = GPUWorkload.memoize(label)
184212
self.parsed_label_name = label
185213
return self.parsed_label_name
186214

@@ -239,7 +267,8 @@ def get_long_label(self) -> str:
239267
'''
240268
# Subclass will override this if metadata exists
241269
# Submit ID isn't useful, but traces back to Perfetto data for debug
242-
return f'Submit: {self.submit_id}'
270+
label = f'Submit: {self.submit_id}'
271+
return GPUWorkload.memoize(label)
243272

244273
def get_short_label(self) -> str:
245274
'''
@@ -250,7 +279,8 @@ def get_short_label(self) -> str:
250279
'''
251280
# Subclass will override this if metadata exists
252281
# Submit ID isn't useful, but traces back to Perfetto data for debug
253-
return f'Submit: {self.submit_id}'
282+
label = f'Submit: {self.submit_id}'
283+
return GPUWorkload.memoize(label)
254284

255285
def get_key_value_properties(self) -> dict[str, str]:
256286
'''
@@ -353,7 +383,9 @@ def get_resolution_str(self) -> str:
353383
Returns:
354384
Returns the label for use in the UI.
355385
'''
356-
return f'{self.width}x{self.height}'
386+
label = f'{self.width}x{self.height}'
387+
label = self.memoize(label)
388+
return label
357389

358390
def get_draw_count_str(self) -> str:
359391
'''
@@ -368,7 +400,8 @@ def get_draw_count_str(self) -> str:
368400
if self.draw_call_count == 1:
369401
return '1 draw'
370402

371-
return f'{self.draw_call_count} draws'
403+
label = f'{self.draw_call_count} draws'
404+
return self.memoize(label)
372405

373406
def get_subpass_count_str(self) -> str:
374407
'''
@@ -378,7 +411,8 @@ def get_subpass_count_str(self) -> str:
378411
Returns the label for use in the UI.
379412
'''
380413
es = '' if self.subpass_count == 1 else 'es'
381-
return f'{self.subpass_count} subpass{es}'
414+
label = f'{self.subpass_count} subpass{es}'
415+
return self.memoize(label)
382416

383417
def get_attachment_present_str(self) -> str:
384418
'''
@@ -388,7 +422,8 @@ def get_attachment_present_str(self) -> str:
388422
Returns the label for use in the UI.
389423
'''
390424
bindings = [x.binding for x in self.attachments]
391-
return self.get_compact_string(bindings)
425+
label = self.get_compact_str(bindings)
426+
return GPUWorkload.memoize(label)
392427

393428
def get_attachment_loadop_str(self) -> str:
394429
'''
@@ -398,7 +433,8 @@ def get_attachment_loadop_str(self) -> str:
398433
Returns the label for use in the UI.
399434
'''
400435
bindings = [x.binding for x in self.attachments if x.is_loaded]
401-
return self.get_compact_string(bindings)
436+
label = self.get_compact_str(bindings)
437+
return GPUWorkload.memoize(label)
402438

403439
def get_attachment_storeop_str(self) -> str:
404440
'''
@@ -408,10 +444,11 @@ def get_attachment_storeop_str(self) -> str:
408444
Returns the label for use in the UI.
409445
'''
410446
bindings = [x.binding for x in self.attachments if x.is_stored]
411-
return self.get_compact_string(bindings)
447+
label = self.get_compact_str(bindings)
448+
return GPUWorkload.memoize(label)
412449

413450
@classmethod
414-
def get_compact_string(cls, bindings: list[str]) -> str:
451+
def get_compact_str(cls, bindings: list[str]) -> str:
415452
'''
416453
Get the compact UI string for a set of attachment bind points.
417454
@@ -422,7 +459,8 @@ def get_compact_string(cls, bindings: list[str]) -> str:
422459
A binding string of the form, e.g. "C0124DS".
423460
'''
424461
merge = ''.join(bindings)
425-
return ''.join([j for i, j in enumerate(merge) if j not in merge[:i]])
462+
label = ''.join([j for i, j in enumerate(merge) if j not in merge[:i]])
463+
return GPUWorkload.memoize(label)
426464

427465
def get_attachment_long_label(self) -> str:
428466
'''
@@ -441,7 +479,8 @@ def get_attachment_long_label(self) -> str:
441479
if stored:
442480
stored = f' > store({stored}) '
443481

444-
return f'{loaded}[{present}]{stored}'
482+
label = f'{loaded}[{present}]{stored}'
483+
return GPUWorkload.memoize(label)
445484

446485
def get_attachment_short_label(self) -> str:
447486
'''
@@ -451,7 +490,8 @@ def get_attachment_short_label(self) -> str:
451490
A string showing attachments without load/storeOp usage.
452491
'''
453492
present = self.get_attachment_present_str()
454-
return f'[{present}]'
493+
label = f'[{present}]'
494+
return GPUWorkload.memoize(label)
455495

456496
def get_long_label(self) -> str:
457497
'''
@@ -471,7 +511,8 @@ def get_long_label(self) -> str:
471511
line = self.get_attachment_long_label()
472512
lines.append(line)
473513

474-
return '\n'.join(lines)
514+
label = '\n'.join(lines)
515+
return GPUWorkload.memoize(label)
475516

476517
def get_short_label(self) -> str:
477518
'''
@@ -488,7 +529,8 @@ def get_short_label(self) -> str:
488529
line = self.get_attachment_short_label()
489530
lines.append(line)
490531

491-
return '\n'.join(lines)
532+
label = '\n'.join(lines)
533+
return GPUWorkload.memoize(label)
492534

493535

494536
class GPUDispatch(GPUWorkload):
@@ -547,7 +589,8 @@ def get_resolution_str(self) -> str:
547589
if self.groups_z > 1:
548590
dims.append(self.groups_z)
549591

550-
return f'{"x".join([str(dim) for dim in dims])} groups'
592+
label = f'{"x".join([str(dim) for dim in dims])} groups'
593+
return GPUWorkload.memoize(label)
551594

552595
def get_long_label(self) -> str:
553596
'''
@@ -562,7 +605,8 @@ def get_long_label(self) -> str:
562605
lines.append(label_name)
563606

564607
lines.append(self.get_short_label())
565-
return '\n'.join(lines)
608+
label = '\n'.join(lines)
609+
return GPUWorkload.memoize(label)
566610

567611
def get_short_label(self) -> str:
568612
'''
@@ -574,7 +618,8 @@ def get_short_label(self) -> str:
574618
lines = []
575619
line = self.get_resolution_str()
576620
lines.append(line)
577-
return '\n'.join(lines)
621+
label = '\n'.join(lines)
622+
return GPUWorkload.memoize(label)
578623

579624

580625
class GPUTraceRays(GPUWorkload):
@@ -633,7 +678,8 @@ def get_resolution_str(self) -> str:
633678
if self.items_z > 1:
634679
dims.append(self.items_z)
635680

636-
return f'{"x".join([str(dim) for dim in dims])} items'
681+
label = f'{"x".join([str(dim) for dim in dims])} items'
682+
return GPUWorkload.memoize(label)
637683

638684
def get_long_label(self) -> str:
639685
'''
@@ -648,7 +694,8 @@ def get_long_label(self) -> str:
648694
lines.append(label_name)
649695

650696
lines.append(self.get_short_label())
651-
return '\n'.join(lines)
697+
label = '\n'.join(lines)
698+
return GPUWorkload.memoize(label)
652699

653700
def get_short_label(self) -> str:
654701
'''
@@ -659,7 +706,8 @@ def get_short_label(self) -> str:
659706
'''
660707
lines = []
661708
lines.append(self.get_resolution_str())
662-
return '\n'.join(lines)
709+
label = '\n'.join(lines)
710+
return GPUWorkload.memoize(label)
663711

664712

665713
class GPUImageTransfer(GPUWorkload):
@@ -715,7 +763,8 @@ def get_transfer_size_str(self) -> str:
715763
return f'? pixels'
716764

717765
s = 's' if self.pixel_count != 1 else ''
718-
return f'{self.pixel_count} pixel{s}'
766+
label = f'{self.pixel_count} pixel{s}'
767+
return GPUWorkload.memoize(label)
719768

720769
def get_long_label(self) -> str:
721770
'''
@@ -732,7 +781,8 @@ def get_long_label(self) -> str:
732781
line = f'{self.transfer_type} ({self.get_transfer_size_str()})'
733782
lines.append(line)
734783

735-
return '\n'.join(lines)
784+
label = '\n'.join(lines)
785+
return GPUWorkload.memoize(label)
736786

737787
def get_short_label(self) -> str:
738788
'''
@@ -797,7 +847,8 @@ def get_transfer_size_str(self) -> str:
797847
return f'? bytes'
798848

799849
s = 's' if self.byte_count != 1 else ''
800-
return f'{self.byte_count} byte{s}'
850+
label = f'{self.byte_count} byte{s}'
851+
return GPUWorkload.memoize(label)
801852

802853
def get_long_label(self) -> str:
803854
'''
@@ -814,7 +865,8 @@ def get_long_label(self) -> str:
814865
line = f'{self.transfer_type} ({self.get_transfer_size_str()})'
815866
lines.append(line)
816867

817-
return '\n'.join(lines)
868+
label = '\n'.join(lines)
869+
return GPUWorkload.memoize(label)
818870

819871
def get_short_label(self) -> str:
820872
'''
@@ -884,7 +936,8 @@ def get_transfer_size_str(self) -> str:
884936
return f'? primitives'
885937

886938
s = 's' if self.primitive_count != 1 else ''
887-
return f'{self.primitive_count} primitive{s}'
939+
label = f'{self.primitive_count} primitive{s}'
940+
return GPUWorkload.memoize(label)
888941

889942
def get_long_label(self) -> str:
890943
'''
@@ -901,7 +954,8 @@ def get_long_label(self) -> str:
901954
line = f'{self.build_type} ({self.get_transfer_size_str()})'
902955
lines.append(line)
903956

904-
return '\n'.join(lines)
957+
label = '\n'.join(lines)
958+
return GPUWorkload.memoize(label)
905959

906960
def get_short_label(self) -> str:
907961
'''
@@ -963,7 +1017,8 @@ def get_transfer_size_str(self) -> str:
9631017
return f'? bytes'
9641018

9651019
s = 's' if self.byte_count != 1 else ''
966-
return f'{self.byte_count} byte{s}'
1020+
label = f'{self.byte_count} byte{s}'
1021+
return GPUWorkload.memoize(label)
9671022

9681023
def get_long_label(self) -> str:
9691024
'''
@@ -980,7 +1035,8 @@ def get_long_label(self) -> str:
9801035
line = f'{self.transfer_type} ({self.get_transfer_size_str()})'
9811036
lines.append(line)
9821037

983-
return '\n'.join(lines)
1038+
label = '\n'.join(lines)
1039+
return GPUWorkload.memoize(label)
9841040

9851041
def get_short_label(self) -> str:
9861042
'''

0 commit comments

Comments
 (0)