optimize cpu_profile_transformer to not create intermediate lists (#8839)

Uses an iterator over the stack frame values in CpuProfileTransformer, instead of allocating new lists for they keys and values in the map, which are then read by index. I also removed the late/nullable fields in favor of local variables, since the state was never used outside of a single method call.

As far as I could tell the keys were never actually used except to look up values in the map, so I dropped that entirely. There was an extra lookup in the map and local variable which I am pretty certain wasn't needed.

This isn't directly related to any specific issue, I just noticed it as I was perusing through some of the code for the cpu profiler and figured I would send something out, to dip my toes in here :).

No tests were added, the behavior should be unchanged so hopefully existing tests should cover it.

I did also add a TODO because I noticed a piece of logic that was O(N^2) worst case here but I don't know enough about how large the N is expected to be or whether its a common path, and it would be more involved to fix.
diff --git a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_transformer.dart b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_transformer.dart
index 6651cfb..f8ef215 100644
--- a/packages/devtools_app/lib/src/screens/profiler/cpu_profile_transformer.dart
+++ b/packages/devtools_app/lib/src/screens/profiler/cpu_profile_transformer.dart
@@ -13,12 +13,6 @@
   /// Number of stack frames we will process in each batch.
   static const _defaultBatchSize = 100;
 
-  late int _stackFramesCount;
-
-  List<String?>? _stackFrameKeys;
-
-  List<CpuStackFrame>? _stackFrameValues;
-
   int _stackFramesProcessed = 0;
 
   String? _activeProcessId;
@@ -34,21 +28,25 @@
     reset();
 
     _activeProcessId = processId;
-    _stackFramesCount = cpuProfileData.stackFrames.length;
-    _stackFrameKeys = cpuProfileData.stackFrames.keys.toList();
-    _stackFrameValues = cpuProfileData.stackFrames.values.toList();
+    final stackFramesCount = cpuProfileData.stackFrames.length;
+    final stackFrameValues = cpuProfileData.stackFrames.values.iterator;
 
     // At minimum, process the data in 4 batches to smooth the appearance of
     // the progress indicator.
-    final quarterBatchSize = (_stackFramesCount / 4).round();
+    final quarterBatchSize = (stackFramesCount / 4).round();
     final batchSize = math.min(
       _defaultBatchSize,
       quarterBatchSize == 0 ? 1 : quarterBatchSize,
     );
 
     // Use batch processing to maintain a responsive UI.
-    while (_stackFramesProcessed < _stackFramesCount) {
-      _processBatch(batchSize, cpuProfileData, processId: processId);
+    while (_stackFramesProcessed < stackFramesCount) {
+      _processBatch(
+        batchSize,
+        cpuProfileData,
+        processId: processId,
+        stackFrameValues: stackFrameValues,
+      );
 
       // Await a small delay to give the UI thread a chance to update the
       // progress indicator. Use a longer delay than the default (0) so that the
@@ -74,6 +72,8 @@
           nodeIndicesToRemove.add(i);
         }
       }
+      // TODO(jakemac53): This is O(N^2), we should have a function to remove
+      // multiple children at once.
       nodeIndicesToRemove.forEach(
         cpuProfileData.cpuProfileRoot.removeChildAtIndex,
       );
@@ -98,19 +98,14 @@
     int batchSize,
     CpuProfileData cpuProfileData, {
     required String processId,
+    required Iterator<CpuStackFrame> stackFrameValues,
   }) {
-    final batchEnd = math.min(
-      _stackFramesProcessed + batchSize,
-      _stackFramesCount,
-    );
-    for (int i = _stackFramesProcessed; i < batchEnd; i++) {
+    for (int i = 0; i < batchSize && stackFrameValues.moveNext(); i++) {
       if (processId != _activeProcessId) {
         throw ProcessCancelledException();
       }
-      final key = _stackFrameKeys![i];
-      final value = _stackFrameValues![i];
-      final stackFrame = cpuProfileData.stackFrames[key]!;
-      final parent = cpuProfileData.stackFrames[value.parentId];
+      final stackFrame = stackFrameValues.current;
+      final parent = cpuProfileData.stackFrames[stackFrame.parentId];
       _processStackFrame(stackFrame, parent, cpuProfileData);
       _stackFramesProcessed++;
     }
@@ -150,8 +145,6 @@
   void reset() {
     _activeProcessId = null;
     _stackFramesProcessed = 0;
-    _stackFrameKeys = null;
-    _stackFrameValues = null;
   }
 }