[pypy-commit] benchmarks default: add optimized lee router

Raemi noreply at buildbot.pypy.org
Fri Nov 6 09:46:01 EST 2015


Author: Remi Meier <remi.meier at gmail.com>
Branch: 
Changeset: r339:08504e346e20
Date: 2015-11-06 15:47 +0100
http://bitbucket.org/pypy/benchmarks/changeset/08504e346e20/

Log:	add optimized lee router

	This version's goal is to work well with TM. The original benchmark
	violated the assumption that the write-set of a transaction should
	be relatively small. The benchmark was writing to
	LeeThread.tempgrid, which is always old and contains thousands of
	small list-objects.

	In this version, I switched the implementation of the grid to a one-
	dimensional list. This change improved the performance for standard
	PyPy, too. However, pypy-stm crashes immediately in the JIT ->
	investigate.

diff --git a/multithread/lee_routing/lee_router_tm.py b/multithread/lee_routing/lee_router_tm.py
new file mode 100755
--- /dev/null
+++ b/multithread/lee_routing/lee_router_tm.py
@@ -0,0 +1,513 @@
+#!/usr/bin/python
+
+#
+# BSD License
+#
+# Copyright (c) 2007, The University of Manchester (UK)
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#     - Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     - Redistributions in binary form must reproduce the above
+#       copyright notice, this list of conditions and the following
+#       disclaimer in the documentation and/or other materials provided
+#       with the distribution.
+#     - Neither the name of the University of Manchester nor the names
+#       of its contributors may be used to endorse or promote products
+#       derived from this software without specific prior written
+#       permission.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+#  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Simple Lee's Routing Algorithm
+# Author: IW
+# Translated from Java to Python by Remi Meier
+
+
+import time, Tkinter
+import sys, math
+import threading
+
+DEBUG = True
+
+CYAN = "#00FFFF"
+MAGENTA = "#FF00FF"
+YELLOW = "#FFFF00"
+GREEN = "#00FF00"
+RED = "#FF0000"
+BLUE = "#0000FF"
+
+GRID_SIZE = 600
+EMPTY = 0
+TEMP_EMPTY = 10000
+OCC = 5120
+VIA = 6000
+BVIA = 6001
+TRACK = 8192
+MAX_WEIGHT = 1
+
+# used as loop indices to look at neighbouring cells
+NEIGHBOUR_OFFS = ((1,0), (-1,0), (0,1), (0,-1))
+
+class Grid(object):
+
+    def __init__(self, width, height, depth):
+        self.width = width
+        self.height = height
+        self.depth = depth
+        # self._data = [[[0 for _ in range(depth)]
+        #              for _ in range(height)]
+        #              for _ in range(width)]
+        self._data = None
+        self.reset(EMPTY)
+
+    def _idx(self, x, y, z):
+        return (x * self.height + y) * self.depth + z
+
+    def __getitem__(self, args):
+        #x, y, z = args
+        #return self._data[x][y][z]
+        return self._data[self._idx(*args)]
+
+    def __setitem__(self, args, value):
+        self._data[self._idx(*args)] = value
+        # x, y, z = args
+        # self._data[x][y][z] = value
+
+    def reset(self, val):
+        self._data = [val] * (self.width * self.height * self.depth)
+        # for col in self._data:
+        #     for r in range(len(col)):
+        #         col[r] = [val] * self.depth
+
+    def occupy(self, lo_x, lo_y, up_x, up_y):
+        for x in range(lo_x, up_x + 1):
+            for y in range(lo_y, up_y + 1):
+                for c in range(len(self.depth)):
+                    self[x, y, c] = OCC
+                # depth = self._data[x][y]
+                # for c in range(len(depth)):
+                #     depth[c] = OCC
+
+
+    def add_weights(self):
+        for i in range(MAX_WEIGHT):
+            # this loop iteratively propagates weights
+            # if for MAX_WEIGHT > 1...
+            for z in range(self.depth):
+                for x in range(1, self.width - 1):
+                    for y in range(1, self.height - 1):
+                        val = self[x, y, z]
+                        if val == OCC:
+                            # for OCC fields, we set EMPTY neighbours to
+                            # MAX_WEIGHT
+                            for dx, dy in NEIGHBOUR_OFFS:
+                                if self[x + dx, y + dy, z] == EMPTY:
+                                    self[x + dx, y + dy, z] = MAX_WEIGHT
+                        elif val != EMPTY:
+                            # for MAX_WEIGHT fields, set EMPTY neighbours to
+                            # "our value - 1" --> 0 = EMPTY if MAX_WEIGHT is 1
+                            for dx, dy in NEIGHBOUR_OFFS:
+                                if self[x + dx, y + dy, z] == EMPTY:
+                                    self[x + dx, y + dy, z] = val - 1
+
+
+
+
+class WorkQueue(object):
+    def __init__(self, xx1=0, yy1=0, xx2=0, yy2=0, n=0):
+        self.next = None
+        self.x1 = xx1
+        self.y1 = yy1
+        self.x2 = xx2
+        self.y2 = yy2
+        self.nn = n
+
+    def enqueue(self, x1, y1, x2, y2, n):
+        q = WorkQueue(x1, y1, x2, y2, n)
+        q.next = self.next
+        return q
+
+    def dequeue(self):
+        q = self.next
+        self.next = self.next.next
+        return q
+
+    # def length(self):
+    #     curr = self.next
+    #     retval = 0
+    #     while curr is not None:
+    #         retval += 1
+    #         curr = curr.next
+    #     return retval
+
+    def _less(self, other):
+        return (((self.x2 - self.x1) * (self.x2 - self.x1)
+                 + (self.y2 - self.y1) * (self.y2 - self.y1))
+                > ((other.x2 - other.x1) * (other.x2 - other.x1)
+                   + (other.y2 - other.y1) * (other.y2 - other.y1)))
+
+    def _pass(self):
+        done = True
+        ent = self
+        a = ent.next
+        while a.next is not None:
+            b = a.next
+            if a._less(b):
+                ent.next = b
+                a.next = b.next
+                b.next = a
+                done = False
+            ent = a
+            a = b
+            b = b.next
+        return done
+
+    def sort(self):
+        while not self._pass():
+            pass
+
+
+
+
+class LeeThread(threading.Thread):
+
+    def __init__(self, lr):
+        super(LeeThread, self).__init__()
+        self.lr = lr
+        self.wq = None
+        self.tempgrid = Grid(GRID_SIZE, GRID_SIZE, 2)
+
+    def run(self):
+        while True:
+            with atomic:
+                self.wq = self.lr.get_next_track()
+                #
+                if self.wq is None:
+                    print "finished"
+                    return
+                #
+                #self.tempgrid = Grid(GRID_SIZE, GRID_SIZE, 2)
+                self.lr.lay_next_track(self.wq, self.tempgrid)
+
+
+
+from pypystm import atomic, hint_commit_soon
+
+class LeeRouter(object):
+
+    def __init__(self, file):
+        self.grid = Grid(GRID_SIZE, GRID_SIZE, 2)
+        self.work = WorkQueue()
+        self.net_no = 0
+        self._parse_data_file(file)
+        self.grid.add_weights()
+        self.work.sort()
+        self.queue_lock = threading.Lock()
+        self.grid_lock = atomic#threading.Lock()
+        self.view = Viewer()
+
+    def _parse_data_file(self, file_name):
+        with open(file_name, 'r') as file:
+            for line in file:
+                line = line.strip()
+                line = line.split()
+                c, params = line[0], map(int, line[1:])
+                if c == 'E':
+                    break # end of file
+                elif c == 'C':
+                    # chip bounding box
+                    x0, y0, x1, y1 = params
+                    self.grid.occupy(x0, y0, x1, y1)
+                elif c == 'P':
+                    # pad
+                    x0, y0 = params
+                    self.grid.occupy(x0, y0, x0, y0)
+                elif c == 'J':
+                    # join connection pts
+                    x0, y0, x1, y1 = params
+                    self.net_no += 1
+                    self.work.next = self.work.enqueue(x0, y0, x1, y1, self.net_no)
+
+    def get_next_track(self):
+        with self.queue_lock:
+            if self.work.next is not None:
+                return self.work.dequeue()
+        return None
+
+    def lay_next_track(self, wq, tempgrid):
+        # start transaction
+        with self.grid_lock:
+            done = self._connect(wq.x1, wq.y1, wq.x2, wq.y2,
+                                wq.nn, tempgrid, self.grid)
+        return done # end transaction
+
+    def create_thread(self):
+        return LeeThread(self)
+
+    @staticmethod
+    def _expand_from_to(x, y, x_goal, y_goal, num,
+                        tempgrid, grid):
+        # this method should use Lee's expansion algorithm from
+	# coordinate (x,y) to (x_goal, y_goal) for the num iterations
+	# it should return true if the goal is found and false if it is not
+	# reached within the number of iterations allowed.
+        #
+        # g[x_goal][y_goal][0] = EMPTY; // set goal as empty
+	# g[x_goal][y_goal][1] = EMPTY; // set goal as empty
+        front = []
+        tmp_front = []
+        tempgrid[x, y, 0] = 1
+        tempgrid[x, y, 1] = 1
+        #
+        front.append((x, y, 0, 0)) # (x y z dw)
+        front.append((x, y, 1, 0)) # we can start from either side
+        #
+        reached0, reached1 = False, False
+        while front:
+            while front:
+                fx, fy, fz, fdw = front.pop(0)
+                #
+                if fdw > 0:
+                    tmp_front.append((fx, fy, fz, fdw - 1))
+                else:
+                    for dx, dy in NEIGHBOUR_OFFS:
+                        fdx, fdy = fx + dx, fy + dy
+
+                        weight = grid[fdx, fdy, fz] + 1
+                        prev_val = tempgrid[fdx, fdy, fz]
+                        reached = (fdx == x_goal) and (fdy == y_goal)
+                        if reached or (
+                                prev_val > tempgrid[fx, fy, fz] + weight and weight < OCC):
+                            # check that a point is actually within the bounds of grid array:
+                            if 0 < fdx < GRID_SIZE - 1 and 0 < fdy < GRID_SIZE - 1:
+                                tempgrid[fdx, fdy, fz] = tempgrid[fx, fy, fz] + weight
+                                if not reached:
+                                    tmp_front.append((fdx, fdy, fz, 0))
+                    #
+                    not_fz = 1 - fz
+                    weight = grid[fx, fy, not_fz] + 1
+                    if tempgrid[fx, fy, not_fz] > tempgrid[fx, fy, fz] and weight < OCC:
+                        tempgrid[fx, fy, not_fz] = tempgrid[fx, fy, fz]
+                        tmp_front.append((fx, fy, not_fz, 0))
+                    #
+                    # must check if found goal, if so, return True
+                    reached0 = tempgrid[x_goal, y_goal, 0] != TEMP_EMPTY
+                    reached1 = tempgrid[x_goal, y_goal, 1] != TEMP_EMPTY
+                    if reached0 and reached1: # both
+                        return True # (x_goal, y_goal) can be found in time
+            #
+            front, tmp_front = tmp_front, front
+        return False
+
+    @staticmethod
+    def _path_from_other_side(tempgrid, x, y, z):
+        zo = 1 - z # other side
+        sqval = tempgrid[x, y, zo]
+        if sqval in (VIA, BVIA):
+            return False
+        #
+        if tempgrid[x, y, zo] <= tempgrid[x, y, z]:
+            return (tempgrid[x-1, y, zo] < sqval or tempgrid[x+1, y, zo] < sqval
+                    or tempgrid[x, y-1, zo] < sqval or tempgrid[x, y+1, zo] < sqval)
+        return False
+
+    @staticmethod
+    def _tlength(x1, y1, x2, y2):
+        sq = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)
+	return math.sqrt(sq);
+
+    def _backtrack_from(self, x_goal, y_goal, x_start, y_start,
+                      track_no, tempgrid, grid):
+        # this method should backtrack from the goal position (x_goal, y_goal)
+	# back to the starting position (x_start, y_start) filling in the
+	# grid array g with the specified track number track_no ( + TRACK).
+	# ***
+	# CurrentPos = Goal
+	# Loop
+	# Find dir to start back from current position
+	# Loop
+	# Keep going in current dir and Fill in track (update currentPos)
+	# Until box number increases in this current dir
+	# Until back at starting point
+	# ***
+        distsofar = 0
+        if abs(x_goal - x_start) > abs(y_goal - y_start):
+            z_goal = 0
+        else:
+            z_goal = 1
+        #
+        if tempgrid[x_goal, y_goal, z_goal] == TEMP_EMPTY:
+            z_goal = 1 - z_goal
+        #
+        # arrays used for looking NSEW:
+        DX = (( -1, 1, 0, 0 ),
+              ( 0, 0, -1, 1 ))
+        DY = (( 0, 0, -1, 1 ),
+              ( -1, 1, 0, 0 ))
+        #
+        temp_y, temp_x, temp_z = y_goal, x_goal, z_goal
+        while (temp_x != x_start) or (temp_y != y_start): # PDL: until back
+            # at starting point
+            advanced = False
+            min_dir = 0
+            min_square = 100000
+            for d in range(4): # PDL: find dir to start back from
+                # current position
+                temp_dx, temp_dy = temp_x + DX[temp_z][d], temp_y + DY[temp_z][d]
+                if (tempgrid[temp_dx, temp_dy, temp_z] < tempgrid[temp_x, temp_y, temp_z]
+                    and tempgrid[temp_dx, temp_dy, temp_z] != TEMP_EMPTY):
+                    if tempgrid[temp_dx, temp_dy, temp_z] < min_square:
+                        min_square = tempgrid[temp_dx, temp_dy, temp_z]
+                        min_dir = d
+                        advanced = True
+            if advanced:
+                distsofar += 1
+            #
+            if (self._path_from_other_side(tempgrid, temp_x, temp_y, temp_z)
+                # not preferred dir for this layer
+                and ((min_dir > 1 and distsofar > 15
+                      and self._tlength(temp_x, temp_y, x_start, y_start) > 15)
+                     or (not advanced and
+                         grid[temp_x, temp_y, temp_z] not in (VIA, BVIA)
+                     ))):
+                t_z = 1 - temp_z
+                viat = VIA if advanced else BVIA # BVIA is nowhere else to go
+                # mark via
+                tempgrid[temp_x, temp_y, temp_z] = viat
+                grid[temp_x, temp_y, temp_z] = viat
+                # go to the other side:
+                temp_z = t_z
+                tempgrid[temp_x, temp_y, temp_z] = viat
+                grid[temp_x, temp_y, temp_z] = viat
+                distsofar = 0
+            else:
+                if grid[temp_x, temp_y, temp_z] < OCC:
+		    # PDL: fill in track unless connection point
+		    grid[temp_x, temp_y, temp_z] = TRACK
+                # PDL: updating current position
+                temp_x = temp_x + DX[temp_z][min_dir];
+		temp_y = temp_y + DY[temp_z][min_dir];
+
+    def _connect(self, xs, ys, xg, yg, net_no, tempgrid, grid):
+        # calls expand_from and backtrack_from to create connection
+	# This is the only real change needed to make the program
+	# transactional.
+	# Instead of using the grid 'in place' to do the expansion, we take a
+	# copy but the backtrack writes to the original grid.
+	# This is not a correctness issue. The transactions would still
+	# complete eventually without it.
+	# However the expansion writes are only temporary and do not logically
+	# conflict.
+	# There is a question as to whether a copy is really necessary as a
+	# transaction will anyway create
+	# its own copy. if we were then to distinguish between writes not to be
+	# committed (expansion) and
+	# those to be committed (backtrack), we would not need an explicit
+	# copy.
+	# Taking the copy is not really a computational(time) overhead because
+	# it avoids the grid 'reset' phase
+	# needed if we do the expansion in place.
+        tempgrid.reset(TEMP_EMPTY)
+        # call the expansion method to return found/not found boolean
+        found = self._expand_from_to(xs, ys, xg, yg, GRID_SIZE * 5, tempgrid, grid)
+        if found:
+            self._backtrack_from(xg, yg, xs, ys, net_no, tempgrid, grid)
+        return found
+
+    def disp_grid(self, z):
+        laycol = (MAGENTA, GREEN)[z]
+        for y in reversed(range(GRID_SIZE)): #WTF
+            for x in range(GRID_SIZE):
+                gg = self.grid[x, y, z]
+                if gg == OCC:
+                    self.view.point(x, y, CYAN)
+                elif gg == VIA:
+                    self.view.point(x, y, YELLOW)
+                elif gg == BVIA:
+                    self.view.point(x, y, RED)
+                elif gg == TRACK:
+                    self.view.point(x, y, laycol)
+
+
+
+def main(args):
+    if len(args) != 2:
+        print "Params: [numthreads] [input-file]"
+        sys.exit(-1)
+    #
+    num_threads = int(args[0])
+    filename = args[1]
+    lr = LeeRouter(filename)
+    #
+    # setup the benchmark
+    start_time = 0
+    current_time = 0
+    #
+    thread = [lr.create_thread() for _ in range(num_threads)]
+    start_time = time.time()
+    for t in thread:
+        t.start()
+    current_time = time.time()
+    for t in thread:
+        t.join()
+    #
+    elapsed_time = current_time - start_time
+    print "Numthreads:", num_threads
+    print "ElapsedTime:", elapsed_time, "s"
+    report(start_time)
+    if DEBUG:
+        lr.disp_grid(0)
+        lr.disp_grid(1)
+        lr.view.show()
+
+
+
+def report(start_time):
+    stop_time = time.time()
+    elapsed = stop_time - start_time
+    print "Elapsed time:", elapsed, "s"
+    print "-------------------------"
+
+
+
+class Viewer(object):
+    def __init__(self):
+        self.points = []
+
+    def point(self, x, y, col):
+        self.points.append((x, y, col))
+
+    def show(self, width=GRID_SIZE, height=GRID_SIZE):
+        master = Tkinter.Tk()
+        c = Tkinter.Canvas(master, width=width, height=height,
+                           background="black")
+        c.pack()
+        img = Tkinter.PhotoImage(width=width, height=height)
+        c.create_image((width/2, height/2), image=img,
+                       state="normal")
+        # draw
+        for (x, y, col) in self.points:
+            img.put(col, (x, y))
+            #c.create_oval(x-1, y-1, x+1, y+1, fill=col, width=0)
+        Tkinter.mainloop()
+
+
+
+if __name__ == '__main__':
+    main(sys.argv[1:])


More information about the pypy-commit mailing list