@@ -76,7 +76,9 @@ import type {
7676 StackAddressInfo ,
7777 AddressTimings ,
7878 Address ,
79+ IndexIntoAddressSetTable ,
7980} from 'firefox-profiler/types' ;
81+ import { SetCollectionBuilder } from 'firefox-profiler/utils/set-collection' ;
8082
8183/**
8284 * For each stack in `stackTable`, and one specific native symbol, compute the
@@ -112,67 +114,38 @@ import type {
112114 * If there is recursion, and the same address is present in multiple frames in
113115 * the same stack, the address is only counted once - the addresses are stored
114116 * in a set.
115- *
116- * The returned StackAddressInfo is computed as follows:
117- * selfAddress[stack]:
118- * For stacks whose stack.frame.nativeSymbol is the given native symbol,
119- * this is stack.frame.address.
120- * For all other stacks this is null.
121- * stackAddresses[stack]:
122- * For stacks whose stack.frame.nativeSymbol is the given native symbol,
123- * this is the stackAddresses of its prefix stack, plus stack.frame.address
124- * added to the set.
125- * For all other stacks this is the same as the stackAddresses set of the
126- * stack's prefix.
127117 */
128118export function getStackAddressInfo (
129119 stackTable : StackTable ,
130120 frameTable : FrameTable ,
131121 _funcTable : FuncTable ,
132122 nativeSymbol : IndexIntoNativeSymbolTable
133123) : StackAddressInfo {
134- // "self address" == "the address which a stack's self time is contributed to"
135- const selfAddressForAllStacks = [ ] ;
136- // "total addresses" == "the set of addresses whose total time this stack contributes to"
137- const totalAddressesForAllStacks : Array < Set < Address > | null > = [ ] ;
124+ const builder = new SetCollectionBuilder < number > ( ) ;
125+ const stackIndexToAddressSetIndex = new Int32Array ( stackTable . length ) ;
138126
139- // This loop takes advantage of the fact that the stack table is topologically ordered:
140- // Prefix stacks are always visited before their descendants.
141- // Each stack inherits the "total" addresses from its parent stack, and then adds its
142- // self address to that set. If the stack doesn't have a self address in the library, we just
143- // re-use the prefix's set object without copying it.
144127 for ( let stackIndex = 0 ; stackIndex < stackTable . length ; stackIndex ++ ) {
145- const frame = stackTable . frame [ stackIndex ] ;
146128 const prefixStack = stackTable . prefix [ stackIndex ] ;
147- const nativeSymbolOfThisStack = frameTable . nativeSymbol [ frame ] ;
129+ const prefixAddressSet : IndexIntoAddressSetTable | - 1 =
130+ prefixStack !== null ? stackIndexToAddressSetIndex [ prefixStack ] : - 1 ;
148131
149- let selfAddress : Address | null = null ;
150- let totalAddresses : Set < Address > | null =
151- prefixStack !== null ? totalAddressesForAllStacks [ prefixStack ] : null ;
132+ const frame = stackTable . frame [ stackIndex ] ;
133+ const nativeSymbolOfThisStack = frameTable . nativeSymbol [ frame ] ;
134+ const matchesNativeSymbol = nativeSymbolOfThisStack === nativeSymbol ;
135+ if ( prefixAddressSet === - 1 && ! matchesNativeSymbol ) {
136+ stackIndexToAddressSetIndex [ stackIndex ] = - 1 ;
137+ } else {
138+ const selfAddress = matchesNativeSymbol ? frameTable . address [ frame ] : - 1 ;
152139
153- if ( nativeSymbolOfThisStack === nativeSymbol ) {
154- selfAddress = frameTable . address [ frame ] ;
155- if ( selfAddress !== - 1 ) {
156- // Add this stack's address to this stack's totalAddresses. The rest of this stack's
157- // totalAddresses is the same as for the parent stack.
158- // We avoid creating new Set objects unless the new set is actually
159- // different.
160- if ( totalAddresses === null ) {
161- // None of the ancestor stack nodes have hit a address in the given library.
162- totalAddresses = new Set ( [ selfAddress ] ) ;
163- } else if ( ! totalAddresses . has ( selfAddress ) ) {
164- totalAddresses = new Set ( totalAddresses ) ;
165- totalAddresses . add ( selfAddress ) ;
166- }
167- }
140+ stackIndexToAddressSetIndex [ stackIndex ] = builder . extend (
141+ prefixAddressSet !== - 1 ? prefixAddressSet : null ,
142+ selfAddress
143+ ) ;
168144 }
169-
170- selfAddressForAllStacks . push ( selfAddress ) ;
171- totalAddressesForAllStacks . push ( totalAddresses ) ;
172145 }
173146 return {
174- selfAddress : selfAddressForAllStacks ,
175- stackAddresses : totalAddressesForAllStacks ,
147+ stackIndexToAddressSetIndex ,
148+ addressSetTable : builder . finish ( ) ,
176149 } ;
177150}
178151
@@ -192,30 +165,58 @@ export function getAddressTimings(
192165 if ( stackAddressInfo === null ) {
193166 return emptyAddressTimings ;
194167 }
195- const { selfAddress, stackAddresses } = stackAddressInfo ;
196- const totalAddressHits : Map < Address , number > = new Map ( ) ;
197- const selfAddressHits : Map < Address , number > = new Map ( ) ;
198168
199- // Iterate over all the samples, and aggregate the sample's weight into the
200- // addresses which are hit by the sample's stack.
201- // TODO: Maybe aggregate sample count per stack first, and then visit each stack only once?
169+ const { stackIndexToAddressSetIndex, addressSetTable } = stackAddressInfo ;
170+ // Iterate over all the samples, and aggregate the sample's weight into
171+ // selfPerAddressSet.
172+ const selfPerAddressSet = new Float64Array ( addressSetTable . length ) ;
202173 for ( let sampleIndex = 0 ; sampleIndex < samples . length ; sampleIndex ++ ) {
203174 const stackIndex = samples . stack [ sampleIndex ] ;
204175 if ( stackIndex === null ) {
205176 continue ;
206177 }
207- const weight = samples . weight ? samples . weight [ sampleIndex ] : 1 ;
208- const setOfHitAddresses = stackAddresses [ stackIndex ] ;
209- if ( setOfHitAddresses !== null ) {
210- for ( const address of setOfHitAddresses ) {
211- const oldHitCount = totalAddressHits . get ( address ) ?? 0 ;
212- totalAddressHits . set ( address , oldHitCount + weight ) ;
178+ const addressSetIndex = stackIndexToAddressSetIndex [ stackIndex ] ;
179+ if ( addressSetIndex !== - 1 ) {
180+ const weight = samples . weight ? samples . weight [ sampleIndex ] : 1 ;
181+ selfPerAddressSet [ addressSetIndex ] += weight ;
182+ }
183+ }
184+
185+ const totalAddressHits : Map < Address , number > = new Map ( ) ;
186+ const selfAddressHits : Map < Address , number > = new Map ( ) ;
187+ const selfSumOfAddressSetDescendants = new Float64Array (
188+ addressSetTable . length
189+ ) ;
190+
191+ for (
192+ let addressSetIndex = addressSetTable . length - 1 ;
193+ addressSetIndex >= 0 ;
194+ addressSetIndex --
195+ ) {
196+ const selfWeight = selfPerAddressSet [ addressSetIndex ] ;
197+ if ( selfWeight !== 0 ) {
198+ const selfAddress = addressSetTable . self [ addressSetIndex ] ;
199+ if ( selfAddress !== - 1 ) {
200+ const oldHitCount = selfAddressHits . get ( selfAddress ) ?? 0 ;
201+ selfAddressHits . set ( selfAddress , oldHitCount + selfWeight ) ;
213202 }
214203 }
215- const address = selfAddress [ stackIndex ] ;
216- if ( address !== null ) {
217- const oldHitCount = selfAddressHits . get ( address ) ?? 0 ;
218- selfAddressHits . set ( address , oldHitCount + weight ) ;
204+
205+ const selfSumOfThisAddressSetDescendants =
206+ selfSumOfAddressSetDescendants [ addressSetIndex ] ;
207+ const thisAddressSetWeight =
208+ selfWeight + selfSumOfThisAddressSetDescendants ;
209+ const addressSetParent = addressSetTable . parent [ addressSetIndex ] ;
210+ if ( addressSetParent !== null ) {
211+ selfSumOfAddressSetDescendants [ addressSetParent ] += thisAddressSetWeight ;
212+ }
213+
214+ if ( thisAddressSetWeight !== 0 ) {
215+ const address = addressSetTable . value [ addressSetIndex ] ;
216+ if ( address !== - 1 ) {
217+ const oldHitCount = totalAddressHits . get ( address ) ?? 0 ;
218+ totalAddressHits . set ( address , oldHitCount + thisAddressSetWeight ) ;
219+ }
219220 }
220221 }
221222 return { totalAddressHits, selfAddressHits } ;
0 commit comments