github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/gcdense/test.py (about) 1 #!/usr/bin/python3 2 # -*- coding: utf-8 -*- 3 4 import math 5 import random 6 import collections 7 8 import numpy as np 9 import matplotlib.pyplot as plt 10 11 class Graph: 12 def __init__(self, nnodes): 13 self.nnodes = nnodes 14 self.out = [set() for i in range(nnodes)] 15 16 def pageOf(self, node): 17 return self.address[node] // ADDRS_PER_PAGE 18 19 def bucketOf(self, node): 20 return self.address[node] // ADDRS_PER_BUCKET 21 22 ADDRS_PER_PAGE = 10 23 PAGES_PER_BUCKET = 10 24 ADDRS_PER_BUCKET = ADDRS_PER_PAGE * PAGES_PER_BUCKET 25 26 def addressGraph(g, density=0.7): 27 """Assign an allocation address to each node.""" 28 addresses = list(range(int(math.ceil(g.nnodes / density)))) 29 random.shuffle(addresses) 30 g.address = addresses[:g.nnodes] 31 32 #TLB_ENTRIES = 64 + 1024 # Haswell 33 TLB_ENTRIES = 64 34 35 class TLB: 36 def __init__(self): 37 self.cache = collections.OrderedDict() 38 self.misses = 0 39 40 def touch(self, obj): 41 page = obj // ADDRS_PER_PAGE 42 if page in self.cache: 43 self.cache.move_to_end(page) 44 return 45 self.misses += 1 46 if len(self.cache) >= TLB_ENTRIES: 47 # Evict. 48 self.cache.popitem(last=False) 49 self.cache[page] = True 50 51 def genERGraph(n, p): 52 """Generate an Erdős-Rényi random graph of n nodes.""" 53 54 g = Graph(n) 55 for i in range(n): 56 # for j in range(n): 57 # if random.random() < p: 58 # g.out[i].add(j) 59 # Approximate binomial distribution. 60 nout = int(0.5 + random.gauss(n * p, n * p * (1 - p))) 61 out = g.out[i] 62 while len(out) < nout: 63 out.add(random.randrange(n)) 64 return g 65 66 def genDeBruijn(degree, power): 67 n = degree ** power 68 g = Graph(n) 69 for i in range(n): 70 nextnode = i * degree % n 71 for digit in range(degree): 72 g.out[i].add(nextnode + digit) 73 return g 74 75 def costLinear(n): 76 return n 77 78 def costSqrt(n): 79 return n**0.5 80 81 def costAffine10(n): 82 return 10 + n 83 84 costs = [ 85 ("linear", costLinear), 86 #("sqrt", costSqrt), 87 # Minimizing affine cost just means minimizing step count 88 #("affine10", costAffine10), 89 ] 90 91 def argmax(iterable): 92 return 93 94 def pickFullest(buckets): 95 return max(enumerate(buckets), key=lambda x: len(x[1]))[0] 96 97 def pickEmptiest(buckets): 98 minidx = None 99 for i, b in enumerate(buckets): 100 if b and (minidx is None or len(b) < len(buckets[minidx])): 101 minidx = i 102 return minidx 103 104 def pickRandom(buckets): 105 nonempty = [i for i, b in enumerate(buckets) if b] 106 return random.choice(nonempty) 107 108 def pickFirst(buckets): 109 for i, b in enumerate(buckets): 110 if b: 111 return i 112 113 def pickQuantile(quantile): 114 def pick(buckets): 115 nonempty = [i for i, b in enumerate(buckets) if b] 116 nonempty.sort(key=lambda i: len(buckets[i])) 117 return nonempty[int(math.floor((len(nonempty) - 1) * quantile))] 118 return pick 119 120 def pickAlternate10(buckets): 121 fullest = pickFullest(buckets) 122 emptiest = pickEmptiest(buckets) 123 if len(buckets[fullest]) >= 10 * len(buckets[emptiest]): 124 return fullest 125 return emptiest 126 127 picks = [ 128 ("fullest", pickFullest), 129 ("Q3", pickQuantile(0.75)), 130 ("median", pickQuantile(0.5)), 131 ("Q1", pickQuantile(0.25)), 132 ("emptiest", pickEmptiest), 133 ("random", pickRandom), 134 ("first", pickFirst), 135 #("alternate10", pickAlternate10), # Not very interesting. 136 ] 137 138 REPROCESS = True 139 140 def run(g, nroots, pick, cost): 141 visited = [False] * g.nnodes 142 buckets = [[] for i in range(g.nnodes)] 143 tlb = TLB() 144 145 # Queue roots 146 for node in range(nroots): 147 buckets[g.bucketOf(node)].append(node) 148 149 # Process 150 scanCost, steps, capacity = 0, 0, [] 151 while any(buckets): 152 bidx = pick(buckets) 153 154 # Fetch and clear bucket, since we may add more pointers while 155 # processing this bucket. 156 nodes = buckets[bidx] 157 buckets[bidx] = [] 158 159 # Process bucket 160 for node in nodes: 161 # Assume an edge queuing model 162 tlb.touch(-g.address[node]/32 - 1) 163 if visited[node]: 164 continue 165 visited[node] = True 166 167 tlb.touch(g.address[node]) 168 for nextnode in g.out[node]: 169 nextbucket = g.bucketOf(nextnode) 170 if REPROCESS and nextbucket == bidx: 171 nodes.append(nextnode) 172 else: 173 buckets[nextbucket].append(nextnode) 174 175 scanCost += cost(len(nodes)) 176 steps += 1 177 capacity.append(len(nodes)) 178 179 meanCapacity = sum(capacity) / len(capacity) 180 return scanCost, steps, meanCapacity, capacity, tlb.misses 181 182 def runGlobalQueue(g, nroots): 183 """Simulate the regular global work queue algorithm.""" 184 visited = [False] * g.nnodes 185 queue = collections.deque(range(nroots)) 186 tlb = TLB() 187 188 while len(queue): 189 obj = queue.pop() 190 #page = g.pageOf(obj) 191 192 # Assume the mark bits cover 32X less than the objects. 193 tlb.touch(-g.address[obj]/32 - 1) 194 if visited[obj]: 195 continue 196 visited[obj] = True 197 198 # Scan the object. 199 tlb.touch(g.address[obj]) 200 for nextnode in g.out[obj]: 201 queue.appendleft(nextnode) 202 203 # steps is number of buckets, but misses is number of pages. Scale 204 # misses so they're comparable. 205 return tlb.misses // PAGES_PER_BUCKET, tlb.misses 206 207 def ecdf(data): 208 yvals = (np.arange(len(data)) + 1) / len(data) 209 plt.plot(np.sort(data), yvals, drawstyle='steps') 210 211 def main(): 212 NROOTS = 10 213 214 graph = genERGraph(2000, 0.01) 215 addressGraph(graph) 216 217 globalMisses, _ = runGlobalQueue(graph, NROOTS) 218 print("%s\t\t\t\t%s" % ("global", globalMisses)) 219 220 for (costName, cost) in costs: 221 for (pickName, pick) in picks: 222 scanCost, steps, meanCapacity, capacity, _ = run(graph, NROOTS, pick, cost) 223 print("%s\t%-10s\t%g\t%s\t%g" % (costName, pickName, scanCost, steps, meanCapacity)) 224 ecdf(capacity) 225 plt.show() 226 227 def curve(): 228 NROOTS = 10 229 230 for nodes in range(1000, 10000+1000, 1000): 231 graph = genERGraph(nodes, 0.001) 232 # for power in range(8, 15): 233 # graph = genDeBruijn(2, power) 234 # for power in range(4, 9): 235 # graph = genDeBruijn(4, power) 236 addressGraph(graph) 237 238 heapsize = sum(len(o) for o in graph.out) 239 240 _, misses = runGlobalQueue(graph, NROOTS) 241 print("%d,%d,global" % (heapsize, misses)) 242 _, _, _, _, misses = run(graph, NROOTS, pickFullest, costLinear) 243 print("%d,%d,sharded" % (heapsize, misses)) 244 245 main() 246 #curve()