@@ -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,70 @@ 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+
171+ // We do two passes to compute the timings:
172+ // 1. One pass over the samples to accumulate the sample weight onto the
173+ // nodes in the addressSetTable.
174+ // 2. One pass (from back to front) over the addressSetTable, propagating
175+ // values up the tree and, at the same time, accumulating per-address
176+ // totals.
177+
178+ // First, do the pass over the samples to compute the weight per address set.
179+ const selfPerAddressSet = new Float64Array ( addressSetTable . length ) ;
202180 for ( let sampleIndex = 0 ; sampleIndex < samples . length ; sampleIndex ++ ) {
203181 const stackIndex = samples . stack [ sampleIndex ] ;
204182 if ( stackIndex === null ) {
205183 continue ;
206184 }
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 ) ;
185+ const addressSetIndex = stackIndexToAddressSetIndex [ stackIndex ] ;
186+ if ( addressSetIndex !== - 1 ) {
187+ const weight = samples . weight ? samples . weight [ sampleIndex ] : 1 ;
188+ selfPerAddressSet [ addressSetIndex ] += weight ;
189+ }
190+ }
191+
192+ // Now, do a pass over the addressSetTable, from back to front.
193+ // This is a similar idea to what we do for the call tree or the function
194+ // list. The upwards propagation of a sample's weight will not contribute
195+ // to the same address multiple times thanks to the guarantees of the
196+ // addressSetTable - there are no duplicate values on a node's path to the
197+ // root.
198+ const totalAddressHits : Map < Address , number > = new Map ( ) ;
199+ const selfAddressHits : Map < Address , number > = new Map ( ) ;
200+ const selfSumOfAddressSetDescendants = new Float64Array (
201+ addressSetTable . length
202+ ) ;
203+ for (
204+ let addressSetIndex = addressSetTable . length - 1 ;
205+ addressSetIndex >= 0 ;
206+ addressSetIndex --
207+ ) {
208+ const selfWeight = selfPerAddressSet [ addressSetIndex ] ;
209+ if ( selfWeight !== 0 ) {
210+ const selfAddress = addressSetTable . self [ addressSetIndex ] ;
211+ if ( selfAddress !== - 1 ) {
212+ const oldHitCount = selfAddressHits . get ( selfAddress ) ?? 0 ;
213+ selfAddressHits . set ( selfAddress , oldHitCount + selfWeight ) ;
213214 }
214215 }
215- const address = selfAddress [ stackIndex ] ;
216- if ( address !== null ) {
217- const oldHitCount = selfAddressHits . get ( address ) ?? 0 ;
218- selfAddressHits . set ( address , oldHitCount + weight ) ;
216+
217+ const selfSumOfThisAddressSetDescendants =
218+ selfSumOfAddressSetDescendants [ addressSetIndex ] ;
219+ const thisAddressSetWeight =
220+ selfWeight + selfSumOfThisAddressSetDescendants ;
221+ const addressSetParent = addressSetTable . parent [ addressSetIndex ] ;
222+ if ( addressSetParent !== null ) {
223+ selfSumOfAddressSetDescendants [ addressSetParent ] += thisAddressSetWeight ;
224+ }
225+
226+ if ( thisAddressSetWeight !== 0 ) {
227+ const address = addressSetTable . value [ addressSetIndex ] ;
228+ if ( address !== - 1 ) {
229+ const oldHitCount = totalAddressHits . get ( address ) ?? 0 ;
230+ totalAddressHits . set ( address , oldHitCount + thisAddressSetWeight ) ;
231+ }
219232 }
220233 }
221234 return { totalAddressHits, selfAddressHits } ;
0 commit comments