@@ -3,8 +3,7 @@ use std::collections::{BTreeMap, HashMap};
3
3
use std:: iter:: FromIterator ;
4
4
use std:: sync:: atomic:: { AtomicUsize , Ordering :: SeqCst } ;
5
5
6
- use graph:: components:: store:: EntityType ;
7
- use graph:: prelude:: { q, DeploymentHash , EntityKey } ;
6
+ use graph:: prelude:: { lazy_static, q} ;
8
7
use rand:: { thread_rng, Rng } ;
9
8
use structopt:: StructOpt ;
10
9
@@ -18,6 +17,149 @@ struct Counter;
18
17
19
18
static ALLOCATED : AtomicUsize = AtomicUsize :: new ( 0 ) ;
20
19
20
+ lazy_static ! {
21
+ // Set 'MAP_MEASURE' to something to use the `CacheWeight` defined here
22
+ // in the `btree` module for `BTreeMap`. If this is not set, use the
23
+ // estimate from `graph::util::cache_weight`
24
+ static ref MAP_MEASURE : bool = std:: env:: var( "MAP_MEASURE" ) . ok( ) . is_some( ) ;
25
+
26
+ // When running the `valuemap` test for BTreeMap, put maps into the
27
+ // values of the generated maps
28
+ static ref NESTED_MAP : bool = std:: env:: var( "NESTED_MAP" ) . ok( ) . is_some( ) ;
29
+ }
30
+ // Yes, a global variable. It gets set at the beginning of `main`
31
+ static mut PRINT_SAMPLES : bool = false ;
32
+
33
+ /// Helpers to estimate the size of a `BTreeMap`. Everything in this module,
34
+ /// except for `node_size()` is copied from `std::collections::btree`.
35
+ ///
36
+ /// It is not possible to know how many nodes a BTree has, as
37
+ /// `BTreeMap` does not expose its depth or any other detail about
38
+ /// the true size of the BTree. We estimate that size, assuming the
39
+ /// average case, i.e., a BTree where every node has the average
40
+ /// between the minimum and maximum number of entries per node, i.e.,
41
+ /// the average of (B-1) and (2*B-1) entries, which we call
42
+ /// `NODE_FILL`. The number of leaf nodes in the tree is then the
43
+ /// number of entries divided by `NODE_FILL`, and the number of
44
+ /// interior nodes can be determined by dividing the number of nodes
45
+ /// at the child level by `NODE_FILL`
46
+
47
+ /// The other difficulty is that the structs with which `BTreeMap`
48
+ /// represents internal and leaf nodes are not public, so we can't
49
+ /// get their size with `std::mem::size_of`; instead, we base our
50
+ /// estimates of their size on the current `std` code, assuming that
51
+ /// these structs will not change
52
+
53
+ mod btree {
54
+ use std:: mem;
55
+ use std:: { mem:: MaybeUninit , ptr:: NonNull } ;
56
+
57
+ const B : usize = 6 ;
58
+ const CAPACITY : usize = 2 * B - 1 ;
59
+
60
+ /// Assume BTree nodes are this full (average of minimum and maximum fill)
61
+ const NODE_FILL : usize = ( ( B - 1 ) + ( 2 * B - 1 ) ) / 2 ;
62
+
63
+ type BoxedNode < K , V > = NonNull < LeafNode < K , V > > ;
64
+
65
+ struct InternalNode < K , V > {
66
+ _data : LeafNode < K , V > ,
67
+
68
+ /// The pointers to the children of this node. `len + 1` of these are considered
69
+ /// initialized and valid, except that near the end, while the tree is held
70
+ /// through borrow type `Dying`, some of these pointers are dangling.
71
+ _edges : [ MaybeUninit < BoxedNode < K , V > > ; 2 * B ] ,
72
+ }
73
+
74
+ struct LeafNode < K , V > {
75
+ /// We want to be covariant in `K` and `V`.
76
+ _parent : Option < NonNull < InternalNode < K , V > > > ,
77
+
78
+ /// This node's index into the parent node's `edges` array.
79
+ /// `*node.parent.edges[node.parent_idx]` should be the same thing as `node`.
80
+ /// This is only guaranteed to be initialized when `parent` is non-null.
81
+ _parent_idx : MaybeUninit < u16 > ,
82
+
83
+ /// The number of keys and values this node stores.
84
+ _len : u16 ,
85
+
86
+ /// The arrays storing the actual data of the node. Only the first `len` elements of each
87
+ /// array are initialized and valid.
88
+ _keys : [ MaybeUninit < K > ; CAPACITY ] ,
89
+ _vals : [ MaybeUninit < V > ; CAPACITY ] ,
90
+ }
91
+
92
+ pub fn node_size < K , V > ( map : & std:: collections:: BTreeMap < K , V > ) -> usize {
93
+ // Measure the size of internal and leaf nodes directly - that's why
94
+ // we copied all this code from `std`
95
+ let ln_sz = mem:: size_of :: < LeafNode < K , V > > ( ) ;
96
+ let in_sz = mem:: size_of :: < InternalNode < K , V > > ( ) ;
97
+
98
+ // Estimate the number of internal and leaf nodes based on the only
99
+ // thing we can measure about a BTreeMap, the number of entries in
100
+ // it, and use our `NODE_FILL` assumption to estimate how the tree
101
+ // is structured. We try to be very good for small maps, since
102
+ // that's what we use most often in our code. This estimate is only
103
+ // for the indirect weight of the `BTreeMap`
104
+ let ( leaves, int_nodes) = if map. is_empty ( ) {
105
+ // An empty tree has no indirect weight
106
+ ( 0 , 0 )
107
+ } else if map. len ( ) <= CAPACITY {
108
+ // We only have the root node
109
+ ( 1 , 0 )
110
+ } else {
111
+ // Estimate based on our `NODE_FILL` assumption
112
+ let leaves = map. len ( ) / NODE_FILL + 1 ;
113
+ let mut prev_level = leaves / NODE_FILL + 1 ;
114
+ let mut int_nodes = prev_level;
115
+ while prev_level > 1 {
116
+ int_nodes += prev_level;
117
+ prev_level = prev_level / NODE_FILL + 1 ;
118
+ }
119
+ ( leaves, int_nodes)
120
+ } ;
121
+
122
+ let sz = leaves * ln_sz + int_nodes * in_sz;
123
+
124
+ if unsafe { super :: PRINT_SAMPLES } {
125
+ println ! (
126
+ " btree: leaves={} internal={} sz={} ln_sz={} in_sz={} len={}" ,
127
+ leaves,
128
+ int_nodes,
129
+ sz,
130
+ ln_sz,
131
+ in_sz,
132
+ map. len( )
133
+ ) ;
134
+ }
135
+ sz
136
+ }
137
+ }
138
+
139
+ struct MapMeasure < K , V > ( BTreeMap < K , V > ) ;
140
+
141
+ impl < K , V > Default for MapMeasure < K , V > {
142
+ fn default ( ) -> MapMeasure < K , V > {
143
+ MapMeasure ( BTreeMap :: new ( ) )
144
+ }
145
+ }
146
+
147
+ impl < K : CacheWeight , V : CacheWeight > CacheWeight for MapMeasure < K , V > {
148
+ fn indirect_weight ( & self ) -> usize {
149
+ if * MAP_MEASURE {
150
+ let kv_sz = self
151
+ . 0
152
+ . iter ( )
153
+ . map ( |( key, value) | key. indirect_weight ( ) + value. indirect_weight ( ) )
154
+ . sum :: < usize > ( ) ;
155
+ let node_sz = btree:: node_size ( & self . 0 ) ;
156
+ kv_sz + node_sz
157
+ } else {
158
+ self . 0 . indirect_weight ( )
159
+ }
160
+ }
161
+ }
162
+
21
163
unsafe impl GlobalAlloc for Counter {
22
164
unsafe fn alloc ( & self , layout : Layout ) -> * mut u8 {
23
165
let ret = System . alloc ( layout) ;
@@ -101,16 +243,18 @@ impl Template<HashMap<String, String>> for HashMap<String, String> {
101
243
}
102
244
}
103
245
104
- type ValueMap = BTreeMap < String , q:: Value > ;
246
+ type ValueMap = MapMeasure < String , q:: Value > ;
105
247
106
248
/// Template for testing roughly a GraphQL response, i.e., a `BTreeMap<String, Value>`
107
249
impl Template < ValueMap > for ValueMap {
108
250
type Item = ValueMap ;
109
251
110
252
fn create ( size : usize ) -> Self {
111
253
let mut map = BTreeMap :: new ( ) ;
254
+ let modulus = if * NESTED_MAP { 9 } else { 8 } ;
255
+
112
256
for i in 0 ..size {
113
- let value = match i % 9 {
257
+ let value = match i % modulus {
114
258
0 => q:: Value :: Boolean ( i % 11 > 5 ) ,
115
259
1 => q:: Value :: Int ( ( i as i32 ) . into ( ) ) ,
116
260
2 => q:: Value :: Null ,
@@ -129,23 +273,24 @@ impl Template<ValueMap> for ValueMap {
129
273
}
130
274
q:: Value :: Object ( map)
131
275
}
132
- _ => q :: Value :: String ( format ! ( "other{}" , i ) ) ,
276
+ _ => unreachable ! ( ) ,
133
277
} ;
134
278
map. insert ( format ! ( "val{}" , i) , value) ;
135
279
}
136
- map
280
+ MapMeasure ( map)
137
281
}
138
282
139
283
fn sample ( & self , size : usize ) -> Box < Self :: Item > {
140
- Box :: new ( BTreeMap :: from_iter (
141
- self . iter ( )
284
+ Box :: new ( MapMeasure ( BTreeMap :: from_iter (
285
+ self . 0
286
+ . iter ( )
142
287
. take ( size)
143
288
. map ( |( k, v) | ( k. to_owned ( ) , v. to_owned ( ) ) ) ,
144
- ) )
289
+ ) ) )
145
290
}
146
291
}
147
292
148
- type UsizeMap = BTreeMap < usize , usize > ;
293
+ type UsizeMap = MapMeasure < usize , usize > ;
149
294
150
295
/// Template for testing roughly a GraphQL response, i.e., a `BTreeMap<String, Value>`
151
296
impl Template < UsizeMap > for UsizeMap {
@@ -156,15 +301,16 @@ impl Template<UsizeMap> for UsizeMap {
156
301
for i in 0 ..size {
157
302
map. insert ( i * 2 , i * 3 ) ;
158
303
}
159
- map
304
+ MapMeasure ( map)
160
305
}
161
306
162
307
fn sample ( & self , size : usize ) -> Box < Self :: Item > {
163
- Box :: new ( BTreeMap :: from_iter (
164
- self . iter ( )
308
+ Box :: new ( MapMeasure ( BTreeMap :: from_iter (
309
+ self . 0
310
+ . iter ( )
165
311
. take ( size)
166
312
. map ( |( k, v) | ( k. to_owned ( ) , v. to_owned ( ) ) ) ,
167
- ) )
313
+ ) ) )
168
314
}
169
315
}
170
316
@@ -221,23 +367,28 @@ fn stress<T: Template<T, Item = T>>(opt: &Opt) {
221
367
222
368
println ! ( "type: {}" , cacheable. name( ) ) ;
223
369
println ! (
224
- "obj: {} iterations: {} cache_size: {}" ,
370
+ "obj: {} iterations: {} cache_size: {}\n " ,
225
371
cacheable. template. weight( ) ,
226
372
opt. niter,
227
373
opt. cache_size
228
374
) ;
229
- println ! ( "heap_factor is heap_size / cache_size" ) ;
230
375
231
376
let mut rng = thread_rng ( ) ;
232
377
let base_mem = ALLOCATED . load ( SeqCst ) ;
233
378
let print_mod = opt. niter / opt. print_count + 1 ;
234
379
let mut should_print = true ;
380
+ let mut print_header = true ;
235
381
for key in 0 ..opt. niter {
236
382
should_print = should_print || key % print_mod == 0 ;
237
383
let before_mem = ALLOCATED . load ( SeqCst ) ;
238
384
if let Some ( ( evicted, _, new_weight) ) = cacheable. cache . evict ( opt. cache_size ) {
239
385
let after_mem = ALLOCATED . load ( SeqCst ) ;
240
386
if should_print {
387
+ if print_header {
388
+ println ! ( "heap_factor is heap_size / cache_size" ) ;
389
+ print_header = false ;
390
+ }
391
+
241
392
let heap_factor = ( after_mem - base_mem) as f64 / opt. cache_size as f64 ;
242
393
println ! (
243
394
"evicted: {:6} dropped: {:6} new_weight: {:8} heap_factor: {:0.2} " ,
@@ -279,6 +430,7 @@ fn stress<T: Template<T, Item = T>>(opt: &Opt) {
279
430
/// the target `cache_size`
280
431
pub fn main ( ) {
281
432
let opt = Opt :: from_args ( ) ;
433
+ unsafe { PRINT_SAMPLES = opt. samples }
282
434
283
435
// Use different Cacheables to see how the cache manages memory with
284
436
// different types of cache entries. Uncomment one of the 'let mut cacheable'
0 commit comments