@@ -339,13 +339,11 @@ impl<D: Deps> DepGraphData<D> {
339
339
let ( result, edges) = if cx. dep_context ( ) . is_eval_always ( key. kind ) {
340
340
( with_deps ( TaskDepsRef :: EvalAlways ) , EdgesVec :: new ( ) )
341
341
} else {
342
- let task_deps = Lock :: new ( TaskDeps {
342
+ let task_deps = Lock :: new ( TaskDeps :: new (
343
343
#[ cfg( debug_assertions) ]
344
- node : Some ( key) ,
345
- reads : EdgesVec :: new ( ) ,
346
- read_set : Default :: default ( ) ,
347
- phantom_data : PhantomData ,
348
- } ) ;
344
+ Some ( key) ,
345
+ 0 ,
346
+ ) ) ;
349
347
( with_deps ( TaskDepsRef :: Allow ( & task_deps) ) , task_deps. into_inner ( ) . reads )
350
348
} ;
351
349
@@ -377,12 +375,18 @@ impl<D: Deps> DepGraphData<D> {
377
375
{
378
376
debug_assert ! ( !cx. is_eval_always( dep_kind) ) ;
379
377
380
- let task_deps = Lock :: new ( TaskDeps :: default ( ) ) ;
378
+ // Large numbers of reads are common enough here that pre-sizing `read_set`
379
+ // to 128 actually helps perf on some benchmarks.
380
+ let task_deps = Lock :: new ( TaskDeps :: new (
381
+ #[ cfg( debug_assertions) ]
382
+ None ,
383
+ 128 ,
384
+ ) ) ;
381
385
let result = D :: with_deps ( TaskDepsRef :: Allow ( & task_deps) , op) ;
382
386
let task_deps = task_deps. into_inner ( ) ;
383
- let task_deps = task_deps. reads ;
387
+ let reads = task_deps. reads ;
384
388
385
- let dep_node_index = match task_deps . len ( ) {
389
+ let dep_node_index = match reads . len ( ) {
386
390
0 => {
387
391
// Because the dep-node id of anon nodes is computed from the sets of its
388
392
// dependencies we already know what the ID of this dependency-less node is
@@ -393,7 +397,7 @@ impl<D: Deps> DepGraphData<D> {
393
397
}
394
398
1 => {
395
399
// When there is only one dependency, don't bother creating a node.
396
- task_deps [ 0 ]
400
+ reads [ 0 ]
397
401
}
398
402
_ => {
399
403
// The dep node indices are hashed here instead of hashing the dep nodes of the
@@ -402,7 +406,7 @@ impl<D: Deps> DepGraphData<D> {
402
406
// combining it with the per session random number `anon_id_seed`. This hash only need
403
407
// to map the dependencies to a single value on a per session basis.
404
408
let mut hasher = StableHasher :: new ( ) ;
405
- task_deps . hash ( & mut hasher) ;
409
+ reads . hash ( & mut hasher) ;
406
410
407
411
let target_dep_node = DepNode {
408
412
kind : dep_kind,
@@ -420,7 +424,7 @@ impl<D: Deps> DepGraphData<D> {
420
424
// memory impact of this `anon_node_to_index` map remains tolerable, and helps
421
425
// us avoid useless growth of the graph with almost-equivalent nodes.
422
426
self . current . anon_node_to_index . get_or_insert_with ( target_dep_node, || {
423
- self . current . alloc_new_node ( target_dep_node, task_deps , Fingerprint :: ZERO )
427
+ self . current . alloc_new_node ( target_dep_node, reads , Fingerprint :: ZERO )
424
428
} )
425
429
}
426
430
} ;
@@ -471,18 +475,17 @@ impl<D: Deps> DepGraph<D> {
471
475
data. current . total_read_count . fetch_add ( 1 , Ordering :: Relaxed ) ;
472
476
}
473
477
474
- // As long as we only have a low number of reads we can avoid doing a hash
475
- // insert and potentially allocating/reallocating the hashmap
476
- let new_read = if task_deps. reads . len ( ) < EdgesVec :: INLINE_CAPACITY {
477
- task_deps. reads . iter ( ) . all ( |other| * other != dep_node_index)
478
+ // Has `dep_node_index` been seen before? Use either a linear scan or a hashset
479
+ // lookup to determine this. See `TaskDeps::read_set` for details.
480
+ let new_read = if task_deps. reads . len ( ) <= TaskDeps :: LINEAR_SCAN_MAX {
481
+ ! task_deps. reads . contains ( & dep_node_index)
478
482
} else {
479
483
task_deps. read_set . insert ( dep_node_index)
480
484
} ;
481
485
if new_read {
482
486
task_deps. reads . push ( dep_node_index) ;
483
- if task_deps. reads . len ( ) == EdgesVec :: INLINE_CAPACITY {
484
- // Fill `read_set` with what we have so far so we can use the hashset
485
- // next time
487
+ if task_deps. reads . len ( ) == TaskDeps :: LINEAR_SCAN_MAX + 1 {
488
+ // Fill `read_set` with what we have so far. Future lookups will use it.
486
489
task_deps. read_set . extend ( task_deps. reads . iter ( ) . copied ( ) ) ;
487
490
}
488
491
@@ -1292,18 +1295,30 @@ pub enum TaskDepsRef<'a> {
1292
1295
pub struct TaskDeps {
1293
1296
#[ cfg( debug_assertions) ]
1294
1297
node : Option < DepNode > ,
1298
+
1299
+ /// A vector of `DepNodeIndex`, basically.
1295
1300
reads : EdgesVec ,
1301
+
1302
+ /// When adding new edges to `reads` in `DepGraph::read_index` we need to determine if the edge
1303
+ /// has been seen before. If the number of elements in `reads` is small, we just do a linear
1304
+ /// scan. If the number is higher, a hashset has better perf. This field is that hashset. It's
1305
+ /// only used if the number of elements in `reads` exceeds `LINEAR_SCAN_MAX`.
1296
1306
read_set : FxHashSet < DepNodeIndex > ,
1307
+
1297
1308
phantom_data : PhantomData < DepNode > ,
1298
1309
}
1299
1310
1300
- impl Default for TaskDeps {
1301
- fn default ( ) -> Self {
1302
- Self {
1311
+ impl TaskDeps {
1312
+ /// See `TaskDeps::read_set` above.
1313
+ const LINEAR_SCAN_MAX : usize = 16 ;
1314
+
1315
+ #[ inline]
1316
+ fn new ( #[ cfg( debug_assertions) ] node : Option < DepNode > , read_set_capacity : usize ) -> Self {
1317
+ TaskDeps {
1303
1318
#[ cfg( debug_assertions) ]
1304
- node : None ,
1319
+ node,
1305
1320
reads : EdgesVec :: new ( ) ,
1306
- read_set : FxHashSet :: with_capacity_and_hasher ( 128 , Default :: default ( ) ) ,
1321
+ read_set : FxHashSet :: with_capacity_and_hasher ( read_set_capacity , Default :: default ( ) ) ,
1307
1322
phantom_data : PhantomData ,
1308
1323
}
1309
1324
}
0 commit comments