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()