From 87ad0fff8c00cd3e92d46ce7cf2b39e0cf03cc9b Mon Sep 17 00:00:00 2001 From: "Nicholas D. Matsakis" Date: Wed, 12 Jun 2013 22:17:30 -0400 Subject: [PATCH] Bug 872352 - Simplify edges/liquid-resize benchmarks rs=tests --- js/src/parjs-benchmarks/edges.js | 172 +-- js/src/parjs-benchmarks/edgesArray1D.js | 6 - .../parjs-benchmarks/edgesParallelArray1D.js | 6 - .../parjs-benchmarks/edgesParallelArray2D.js | 5 - js/src/parjs-benchmarks/liquid-resize-par.js | 454 ++++++++ js/src/parjs-benchmarks/liquid-resize.js | 1028 ++++++----------- js/src/parjs-benchmarks/run.sh | 4 +- js/src/parjs-benchmarks/util.js | 3 + 8 files changed, 880 insertions(+), 798 deletions(-) delete mode 100644 js/src/parjs-benchmarks/edgesArray1D.js delete mode 100644 js/src/parjs-benchmarks/edgesParallelArray1D.js delete mode 100644 js/src/parjs-benchmarks/edgesParallelArray2D.js create mode 100644 js/src/parjs-benchmarks/liquid-resize-par.js diff --git a/js/src/parjs-benchmarks/edges.js b/js/src/parjs-benchmarks/edges.js index 0c4400e266a..cd140b4038c 100644 --- a/js/src/parjs-benchmarks/edges.js +++ b/js/src/parjs-benchmarks/edges.js @@ -1,152 +1,82 @@ load(libdir + "util.js"); +var SobelX = [[-1.0, 0.0, 1.0], + [-2.0, 0.0, 2.0], + [-1.0, 0.0, 1.0]]; +var SobelY = [[ 1.0, 2.0, 1.0], + [ 0.0, 0.0, 0.0], + [-1.0, -2.0, -1.0]]; + function stripedImage(w, h) { var resultArray = new Array(w * h); - for (var x = 0; x < w; x++) { - for (var y = 0; y < h; y++) { - resultArray[x*h + y] = (Math.abs(x%100-y%100) < 10) ? 32 : 0; + for (var y = 0; y < h; y++) { + for (var x = 0; x < w; x++) { + resultArray[y*w + x] = (Math.abs(x%100-y%100) < 10) ? 32 : 0; } } return resultArray; } -function edges1dArraySequentially(data, width, height) { - var sobelX = [[-1.0, 0.0, 1.0], - [-2.0, 0.0, 2.0], - [-1.0, 0.0, 1.0]]; - var sobelY = [[1.0, 2.0, 1.0], - [0.0, 0.0, 0.0], - [-1.0, -2.0, -1.0]]; - +function edgesSequentially(data, width, height) { var data1 = new Array(width * height); for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { - // process pixel - var totalX = 0; - var totalY = 0; - for (var offY = -1; offY <= 1; offY++) { - var newY = y + offY; - for (var offX = -1; offX <= 1; offX++) { - var newX = x + offX; - if ((newX >= 0) && (newX < width) && (newY >= 0) && (newY < height)) { - var pointIndex = (x + offX) * height + (y + offY); - var e = data[pointIndex]; - totalX += e * sobelX[offY + 1][offX + 1]; - totalY += e * sobelY[offY + 1][offX + 1]; - } - } - } - var total = ((Math.abs(totalX) + Math.abs(totalY))/8.0)|0; - var index = y*width+x; - data1[index] = total | 0; + var total = compute(x, y); + data1[y*width + x] = total | 0; } } return data1; -} -/* Compute edges using a flat JS array as input */ -function edges1dArrayInParallel(data, width, height) { - var sobelX = [[-1.0, 0.0, 1.0], - [-2.0, 0.0, 2.0], - [-1.0, 0.0, 1.0]]; - var sobelY = [[1.0, 2.0, 1.0], - [0.0, 0.0, 0.0], - [-1.0, -2.0, -1.0]]; - - function computePixel(x, y) { + function compute(x, y) { var totalX = 0; var totalY = 0; - for (var offY = -1; offY <= 1; offY++) { - var newY = y + offY; - for (var offX = -1; offX <= 1; offX++) { - var newX = x + offX; - if ((newX >= 0) && (newX < width) && (newY >= 0) && (newY < height)) { - var pointIndex = (x + offX) * height + (y + offY); - var e = data[pointIndex]; - totalX += e * sobelX[offY + 1][offX + 1]; - totalY += e * sobelY[offY + 1][offX + 1]; - } + + var offYMin = (y == 0 ? 0 : -1); + var offYMax = (y == height - 1 ? 0 : 1); + var offXMin = (x == 0 ? 0 : -1); + var offXMax = (x == width - 1 ? 0 : 1); + + for (var offY = offYMin; offY <= offYMax; offY++) { + for (var offX = offXMin; offX <= offXMax; offX++) { + var e = data[(y + offY) * width + x + offX]; + totalX += e * SobelX[offY + 1][offX + 1]; + totalY += e * SobelY[offY + 1][offX + 1]; } } - var total = (Math.abs(totalX) + Math.abs(totalY))/8.0 | 0; - return total; - } - return new ParallelArray([width, height], computePixel); + return (Math.abs(totalX) + Math.abs(totalY))/8.0 | 0; + } } -/* Compute edges using a flat JS array as input */ -function edges1dParallelArrayInParallel(pa, width, height) { - var sobelX = [[-1.0, 0.0, 1.0], - [-2.0, 0.0, 2.0], - [-1.0, 0.0, 1.0]]; - var sobelY = [[1.0, 2.0, 1.0], - [0.0, 0.0, 0.0], - [-1.0, -2.0, -1.0]]; +function edgesParallel(data) { + var pa = new ParallelArray([Height, Width], function (y, x) { + var totalX = 0; + var totalY = 0; - function computePixel(x, y) { - var totalX = 0; - var totalY = 0; - for (var offY = -1; offY <= 1; offY++) { - var newY = y + offY; - for (var offX = -1; offX <= 1; offX++) { - var newX = x + offX; - if ((newX >= 0) && (newX < width) && (newY >= 0) && (newY < height)) { - var pointIndex = (x + offX) * height + (y + offY); - // var e = pa.buffer[pointIndex]; - var e = pa.get(pointIndex); - totalX += e * sobelX[offY + 1][offX + 1]; - totalY += e * sobelY[offY + 1][offX + 1]; - } - } + var offYMin = (y == 0 ? 0 : -1); + var offYMax = (y == Height - 1 ? 0 : 1); + var offXMin = (x == 0 ? 0 : -1); + var offXMax = (x == Width - 1 ? 0 : 1); + + for (var offY = offYMin; offY <= offYMax; offY++) { + for (var offX = offXMin; offX <= offXMax; offX++) { + var e = data.get(y + offY, x + offX); + totalX += e * SobelX[offY + 1][offX + 1]; + totalY += e * SobelY[offY + 1][offX + 1]; } - var total = (Math.abs(totalX) + Math.abs(totalY))/8.0 | 0; - return total; - } + } - return new ParallelArray([width, height], computePixel); -} - -/* Compute edges using 2D parallel array as input */ -function edges2dParallelArrayInParallel(pa) { - var sobelX = [[-1.0, 0.0, 1.0], - [-2.0, 0.0, 2.0], - [-1.0, 0.0, 1.0]]; - var sobelY = [[1.0, 2.0, 1.0], - [0.0, 0.0, 0.0], - [-1.0, -2.0, -1.0]]; - - var width = pa.shape[0]; - var height = pa.shape[1]; - - function computePixel(x, y) { - var totalX = 0; - var totalY = 0; - for (var offY = -1; offY <= 1; offY++) { - var newY = y + offY; - for (var offX = -1; offX <= 1; offX++) { - var newX = x + offX; - if ((newX >= 0) && (newX < width) && (newY >= 0) && (newY < height)) { - var pointIndex = (x + offX) * height + (y + offY); - // var e = pa.buffer[pointIndex]; - var e = pa.get(x + offX, y + offY); - totalX += e * sobelX[offY + 1][offX + 1]; - totalY += e * sobelY[offY + 1][offX + 1]; - } - } - } - var total = (Math.abs(totalX) + Math.abs(totalY))/8.0 | 0; - return total; - } - - return new ParallelArray([width, height], computePixel); + return (Math.abs(totalX) + Math.abs(totalY))/8.0 | 0; + }); + return pa.flatten(); } var Width = 1024; var Height = 768; -var ArrInput = stripedImage(1024, 768); -var ParArrInput1D = new ParallelArray(ArrInput); -var ParArrInput2D = new ParallelArray([1024, 768], - function (x, y) ArrInput[x*Height + y]); - +var ArrInput = stripedImage(Width, Height); +var ParArrInput2D = new ParallelArray([Height, Width], + function (y, x) ArrInput[y*Width + x]); +benchmark("EDGES", 2, DEFAULT_MEASURE * 20, + function() edgesSequentially(ArrInput, Width, Height), + function() edgesParallel(ParArrInput2D)); diff --git a/js/src/parjs-benchmarks/edgesArray1D.js b/js/src/parjs-benchmarks/edgesArray1D.js deleted file mode 100644 index 075722e3d14..00000000000 --- a/js/src/parjs-benchmarks/edgesArray1D.js +++ /dev/null @@ -1,6 +0,0 @@ -load(libdir + "edges.js"); - -benchmark("EDGES-ARRAY-1D", 1, DEFAULT_MEASURE, - function() {edges1dArraySequentially(ArrInput, Width, Height)}, - function() {edges1dArrayInParallel(ArrInput, Width, Height)}); - diff --git a/js/src/parjs-benchmarks/edgesParallelArray1D.js b/js/src/parjs-benchmarks/edgesParallelArray1D.js deleted file mode 100644 index d2e5014f1fc..00000000000 --- a/js/src/parjs-benchmarks/edgesParallelArray1D.js +++ /dev/null @@ -1,6 +0,0 @@ -load(libdir + "edges.js"); - -benchmark("EDGES-PARALLEL-ARRAY-1D", 1, DEFAULT_MEASURE, - function() {edges1dArraySequentially(ArrInput, Width, Height)}, - function() {edges1dParallelArrayInParallel(ParArrInput1D, Width, Height)}); - diff --git a/js/src/parjs-benchmarks/edgesParallelArray2D.js b/js/src/parjs-benchmarks/edgesParallelArray2D.js deleted file mode 100644 index 3455c6ad4c3..00000000000 --- a/js/src/parjs-benchmarks/edgesParallelArray2D.js +++ /dev/null @@ -1,5 +0,0 @@ -load(libdir + "edges.js"); - -benchmark("EDGES-PARALLEL-ARRAY-2D", 1, DEFAULT_MEASURE, - function() {edges1dArraySequentially(ArrInput, Width, Height)}, - function() {edges2dParallelArrayInParallel(ParArrInput2D)}); diff --git a/js/src/parjs-benchmarks/liquid-resize-par.js b/js/src/parjs-benchmarks/liquid-resize-par.js new file mode 100644 index 00000000000..a55928cbf73 --- /dev/null +++ b/js/src/parjs-benchmarks/liquid-resize-par.js @@ -0,0 +1,454 @@ +// -*- mode: js2; indent-tabs-mode: nil; -*- + +// Adapted from +// +// https://github.com/RiverTrail/RiverTrail/blob/master/examples/liquid-resize/resize-compute-dp.js +// +// which in turn is based on an algorithm from the paper below (which +// also appeared in ACM SIGGRAPH 2007): +// Shai Avidan and Ariel Shamir. 2007. Seam carving for content-aware image resizing. +// ACM Trans. Graph. 26, 3, Article 10 (July 2007). +// DOI=10.1145/1276377.1276390 http://doi.acm.org/10.1145/1276377.1276390 + +/////////////////////////////////////////////////////////////////////////// +// Inputs + +function buildArray(width, height, func) { + var length = width * height; + var array = new Array(length); + var index = 0; + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + array[index++] = func(y, x); + } + } + return array; +} + +function parImage(seqImage, width, height) { + return new ParallelArray([height, width], function (y, x) { + return seqImage[y*width + x]; + }); +} + +var tinyImage = + buildArray(20, 5, function(y, x) { + var ret; + if (6 <= x && x < 8 && 0 <= y && y < 4) + ret = "."; + else if ((x-15)*(x-15)+(y-1)*(y-1) < 2) + ret = "^"; + else if ((x-20)*(x-20)+(y-3)*(y-3) < 2) + ret = "%"; + else if ((x-1)*(x-1)+(y-3)*(y-3) < 2) + ret = "@"; + else + ret = " "; + return ret.charCodeAt(0) - 32; + }); + +var SmallImageWidth = 60; +var SmallImageHeight = 15; +var SmallImage = + buildArray(SmallImageWidth, SmallImageHeight, function(y, x) { + var ret; + if (6 <= x && x < 8 && 0 <= y && y < 7) + ret = "."; + else if ((x-15)*(x-15)+(y-1)*(y-1) < 2) + ret = "^"; + else if ((x-40)*(x-40)+(y-6)*(y-6) < 6) + ret = "%"; + else if ((x-1)*(x-1)+(y-12)*(y-12) < 2) + ret = "@"; + else + ret = " "; + return ret.charCodeAt(0) - 32; + }); + +var SmallImagePar = parImage(SmallImage, + SmallImageWidth, + SmallImageHeight); + +var bigImage = + buildArray(200, 70, function(y, x) { + var ret; + if (4 <= x && x < 7 && 10 <= y && y < 40) + ret = "."; + else if ((x-150)*(x-150)+(y-13)*(y-13) < 70) + ret = "^"; + else if ((x-201)*(x-201)+(y-33)*(y-33) < 200) + ret = "%"; + else if ((x-15)*(x-15)+(y-3)*(y-3) < 7) + ret = "@"; + else + ret = " "; + return ret.charCodeAt(0) - 32; + }); + +// randomImage: Nat Nat Nat Nat -> RectArray +function randomImage(w, h, sparsity, variety) { + return buildArray(w, h, function (x,y) { + if (Math.random() > 1/sparsity) + return 0; + else + return 1+Math.random()*variety|0; + }); +} + +// stripedImage: Nat Nat -> RectArray +function stripedImage(w, h) { + return buildArray(w, h, function (y, x) { + return (Math.abs(x%100-y%100) < 10) ? 32 : 0; + }); +} + +var massiveImage = + buildArray(70, 10000, function(y, x) (Math.abs(x%100-y%100) < 10) ? 32 : 0); + +function printImage(array, width, height) { + print("Width", width, "Height", height); + for (var y = 0; y < height; y++) { + var line = ""; + for (var x = 0; x < width; x++) { + var c = array[y*width + x]; + line += String.fromCharCode(c + 32); + } + print(line); + } +} + +/////////////////////////////////////////////////////////////////////////// +// Common + +var SobelX = [[-1.0, 0.0, 1.0], + [-2.0, 0.0, 2.0], + [-1.0, 0.0, 1.0]]; +var SobelY = [[ 1.0, 2.0, 1.0], + [ 0.0, 0.0, 0.0], + [-1.0, -2.0, -1.0]]; + +// computeEnergy: Array -> RectArray +// +// (The return type is forced upon us, for now at least, until we add +// appropriate API to ParallelArray; there's a dependency from each +// row upon its predecessor, but the contents of each row could be +// computed in parallel.) +function computeEnergy(source, width, height) { + var energy = new Array(width * height); + energy[0] = 0; + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var e = source[y*width + x]; + if (y >= 1) { + var p = energy[(y-1)*width + x]; + if (x > 0) { + p = Math.min(p, energy[(y-1)*width + x-1]); + } + if (x < (width - 1)) { + p = Math.min(p, energy[(y-1)*width + x+1]); + } + e += p; + } + energy[y*width + x] = e; + } + } + return energy; +} + +// findPath: RectArray -> Array +// (This is inherently sequential.) +function findPath(energy, width, height) +{ + var path = new Array(height); + var y = height - 1; + var minPos = 0; + var minEnergy = energy[y*width + minPos]; + + for (var x = 1; x < width; x++) { + if (energy[y+width + x] < minEnergy) { + minEnergy = energy[y*width + x]; + minPos = x; + } + } + + path[y] = minPos; + for (y = height - 2; y >= 0; y--) { + minEnergy = energy[y*width + minPos]; + // var line = energy[y] + var p = minPos; + if (p >= 1 && energy[y*width + p-1] < minEnergy) { + minPos = p-1; minEnergy = energy[y*width + p-1]; + } + if (p < width - 1 && energy[y*width + p+1] < minEnergy) { + minPos = p+1; minEnergy = energy[y*width + p+1]; + } + path[y] = minPos; + } + return path; +} + +/////////////////////////////////////////////////////////////////////////// +// Sequential + +function transposeSeq(array, width, height) { + return buildArray(height, width, function(y, x) { + return array[x*width + y]; + }); +} + +// detectEdgesSeq: Array Nat Nat -> Array +function detectEdgesSeq(data, width, height) { + var data1 = new Array(width * height); + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var total = compute(y, x); + data1[y*width + x] = total | 0; + } + } + return data1; + + function compute(y, x) { + var totalX = 0; + var totalY = 0; + + var offYMin = (y == 0 ? 0 : -1); + var offYMax = (y == height - 1 ? 0 : 1); + var offXMin = (x == 0 ? 0 : -1); + var offXMax = (x == width - 1 ? 0 : 1); + + for (var offY = offYMin; offY <= offYMax; offY++) { + for (var offX = offXMin; offX <= offXMax; offX++) { + var e = data[(y + offY) * width + x + offX]; + totalX += e * SobelX[offY + 1][offX + 1]; + totalY += e * SobelY[offY + 1][offX + 1]; + } + } + + return (Math.abs(totalX) + Math.abs(totalY))/8.0 | 0; + } +} + +function cutPathHorizontallyBWSeq(array, width, height, path) { + return buildArray(width-1, height, function (y, x) { + if (x < path[y]-1) + return array[y*width + x]; + if (x == path[y]-1) + return (array[y*width + x] + array[y*width + x+1]) / 2 | 0; + else + return array[y*width + x+1]; + }); +} + +function cutPathVerticallyBWSeq(array, width, height, path) { + return buildArray(width, height-1, function (y, x) { + if (y < path[x]-1) + return array[y*width + x]; + if (y == path[x]-1) + return (array[y*width + x] + array[(y+1)*width + x]) / 2 | 0; + else + return array[(y+1)*width + x]; + }); +} + +function cutHorizontalSeamBWSeq(array, width, height) +{ + var edges = detectEdgesSeq(array, width, height); + var energy = computeEnergy(edges, width, height); + var path = findPath(energy, width, height); + edges = null; // no longer live + return cutPathHorizontallyBWSeq(array, width, height, path); +} + +function cutVerticalSeamBWSeq(array, width, height) +{ + var arrayT = transposeSeq(array, width, height); + var edges = detectEdgesSeq(arrayT, height, width); + var energy = computeEnergy(edges, height, width); + var path = findPath(energy, height, width); + edges = null; // no longer live + return cutPathVerticallyBWSeq(array, width, height, path); +} + +function reduceImageBWSeq(image, + width, height, + newWidth, newHeight, + intermediateFunc, + finalFunc) { + while (width > newWidth || height > newHeight) { + intermediateFunc(image, width, height); + + if (width > newWidth) { + image = cutHorizontalSeamBWSeq(image, width, height); + width -= 1; + } + + if (height > newHeight) { + image = cutVerticalSeamBWSeq(image, width, height); + height -= 1; + } + } + + finalFunc(image, width, height); + return image; +} + +/////////////////////////////////////////////////////////////////////////// +// Parallel + +function transposePar(image) { + var height = image.shape[0]; + var width = image.shape[1]; + return new ParallelArray([width, height], function (y, x) { + return image.get(x, y); + }); +} + +// detectEdgesSeq: Array Nat Nat -> Array +function detectEdgesPar(image) { + var height = image.shape[0]; + var width = image.shape[1]; + return new ParallelArray([height, width], function (y, x) { + var totalX = 0; + var totalY = 0; + + var offYMin = (y == 0 ? 0 : -1); + var offYMax = (y == height - 1 ? 0 : 1); + var offXMin = (x == 0 ? 0 : -1); + var offXMax = (x == width - 1 ? 0 : 1); + + for (var offY = offYMin; offY <= offYMax; offY++) { + for (var offX = offXMin; offX <= offXMax; offX++) { + var e = image.get(y + offY, x + offX); + totalX += e * SobelX[offY + 1][offX + 1]; + totalY += e * SobelY[offY + 1][offX + 1]; + } + } + + var result = (Math.abs(totalX) + Math.abs(totalY))/8.0 | 0; + return result; + }); +} + +function cutPathHorizontallyBWPar(image, path) { + var height = image.shape[0]; + var width = image.shape[1]; + return new ParallelArray([height, width-1], function (y, x) { + if (x < path[y]-1) + return image.get(y, x); + if (x == path[y]-1) + return (image.get(y, x) + image.get(y, x+1)) / 2 | 0; + else + return image.get(y, x+1); + }); +} + +function cutPathVerticallyBWPar(image, path) { + var height = image.shape[0]; + var width = image.shape[1]; + return new ParallelArray([height-1, width], function (y, x) { + if (y < path[x]-1) + return image.get(y, x); + if (y == path[x]-1) + return (image.get(y, x) + image.get(y+1, x)) / 2 | 0; + else + return image.get(y+1, x); + }); +} + +function cutHorizontalSeamBWPar(image) +{ + var height = image.shape[0]; + var width = image.shape[1]; + var edges = detectEdgesPar(image); + var energy = computeEnergy(edges.buffer, width, height); + var path = findPath(energy, width, height); + edges = null; // no longer live + return cutPathHorizontallyBWPar(image, path); +} + +function cutVerticalSeamBWPar(image) { + var height = image.shape[0]; + var width = image.shape[1]; + var imageT = transposePar(image); + var edges = detectEdgesPar(imageT); + var energy = computeEnergy(edges.buffer, height, width); + var path = findPath(energy, height, width); + edges = null; // no longer live + return cutPathVerticallyBWPar(image, path); +} + +function reduceImageBWPar(image, + newWidth, newHeight, + intermediateFunc, + finalFunc) { + var height = image.shape[0]; + var width = image.shape[1]; + while (width > newWidth || height > newHeight) { + intermediateFunc(image.buffer, width, height); + + if (width > newWidth) { + image = cutHorizontalSeamBWPar(image); + width -= 1; + } + + if (height > newHeight) { + image = cutVerticalSeamBWPar(image); + height -= 1; + } + } + + finalFunc(image.buffer, width, height); + return image.buffer; +} + +/////////////////////////////////////////////////////////////////////////// +// Benchmarking via run.sh + +var BenchmarkImageWidth = 512; +var BenchmarkImageHeight = 256; +var BenchmarkImage = stripedImage(BenchmarkImageWidth, + BenchmarkImageHeight); +var BenchmarkImagePar = parImage(BenchmarkImage, + BenchmarkImageWidth, + BenchmarkImageHeight); + +var benchmarking = (typeof(MODE) != "undefined"); +if (benchmarking) { + load(libdir + "util.js"); + benchmark("LIQUID-RESIZE", 1, DEFAULT_MEASURE, + function() { + return reduceImageBWSeq( + BenchmarkImage, + BenchmarkImageWidth, BenchmarkImageHeight, + BenchmarkImageWidth/2, BenchmarkImageHeight/2, + function() {}, + function() {}); + }, + + function() { + return reduceImageBWPar( + BenchmarkImagePar, + BenchmarkImageWidth/2, BenchmarkImageHeight/2, + function() {}, + function() {}); + }); +} + +/////////////////////////////////////////////////////////////////////////// +// Running (sanity check) + +if (!benchmarking) { + var seqData = + reduceImageBWSeq(SmallImage, SmallImageWidth, SmallImageHeight, + SmallImageWidth - 15, SmallImageHeight - 10, + function() {}, + printImage); + + var parData = + reduceImageBWPar(SmallImagePar, + SmallImageWidth - 15, SmallImageHeight - 10, + function() {}, + printImage); + + assertEq(seqData, parData); +} diff --git a/js/src/parjs-benchmarks/liquid-resize.js b/js/src/parjs-benchmarks/liquid-resize.js index 55f4b49ac65..5257fe2d43b 100644 --- a/js/src/parjs-benchmarks/liquid-resize.js +++ b/js/src/parjs-benchmarks/liquid-resize.js @@ -10,742 +10,454 @@ // ACM Trans. Graph. 26, 3, Article 10 (July 2007). // DOI=10.1145/1276377.1276390 http://doi.acm.org/10.1145/1276377.1276390 - // Assumption: if MODE defined then running under benchmark script -var benchmarking = (typeof(MODE) != "undefined"); +/////////////////////////////////////////////////////////////////////////// +// Inputs -// Assumption: if libdir undefined then it is current directory (but this one we warn about) -if (typeof(libdir) == "undefined") { - print("Selecting default libdir of './';"); - print("you should override if you are not running from current directory."); - var libdir = "./"; +function buildArray(width, height, func) { + var length = width * height; + var array = new Array(length); + var index = 0; + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + array[index++] = func(y, x); + } + } + return array; } -if (benchmarking) { - // util.js provides interface for benchmark infrastructure. - load(libdir + "util.js"); +function parImage(seqImage, width, height) { + return new ParallelArray([height, width], function (y, x) { + return seqImage[y*width + x]; + }); } -// rectarray.js provides 2D array abstraction for interactive development in REPL. -load(libdir + "rectarray.js"); - -// A RectArray is a 2D array class exported by rectarray.js. -// It has width and height properties, and a get(i,j) method. -// -// A RectArray of Nat is thought of as an image, where the natural -// number contents are the "colors" at that point in the image. -// -// A WrapArray is a drop-in replacement for a RectArray. -// (It uses a different representation.) - -// A ParallelArray is assumed to be 2D throughout this code. - -// A ParMode is an object with properties mode [, expect]; see ParallelArray.js - -// A Path is an Array of indices, related to an origin RectArray R. -// Horizontal paths are of length R.width and have elements in [0,R.height-1]; -// vertical paths are of length R.height and have elements in [0,R.width-1]. - -// A PhaseTimes is an object with various properties naming phases of a -// computation; each property maps to a number. - -// Ideally, one needs only change the below constructions -// to build4 to approximate "real image" input. -// (However, this is untested.) - -// To see the images, try e.g. smallImage.print() - var tinyImage = - WrapArray.build(20, 5, - function(x, y, k) { - var ret; - if (6 <= x && x < 8 && 0 <= y && y < 4) - ret = "."; - else if ((x-15)*(x-15)+(y-1)*(y-1) < 2) - ret = "^"; - else if ((x-20)*(x-20)+(y-3)*(y-3) < 2) - ret = "%"; - else if ((x-1)*(x-1)+(y-3)*(y-3) < 2) - ret = "@"; - else - ret = " "; - return ret.charCodeAt(0) - 32; - }); + buildArray(20, 5, function(y, x) { + var ret; + if (6 <= x && x < 8 && 0 <= y && y < 4) + ret = "."; + else if ((x-15)*(x-15)+(y-1)*(y-1) < 2) + ret = "^"; + else if ((x-20)*(x-20)+(y-3)*(y-3) < 2) + ret = "%"; + else if ((x-1)*(x-1)+(y-3)*(y-3) < 2) + ret = "@"; + else + ret = " "; + return ret.charCodeAt(0) - 32; + }); -var smallImage = - WrapArray.build(60, 15, - function(x, y, k) { - var ret; - if (6 <= x && x < 8 && 0 <= y && y < 7) - ret = "."; - else if ((x-15)*(x-15)+(y-1)*(y-1) < 2) - ret = "^"; - else if ((x-40)*(x-40)+(y-6)*(y-6) < 6) - ret = "%"; - else if ((x-1)*(x-1)+(y-12)*(y-12) < 2) - ret = "@"; - else - ret = " "; - return ret.charCodeAt(0) - 32; - }); +var SmallImageWidth = 60; +var SmallImageHeight = 15; +var SmallImage = + buildArray(SmallImageWidth, SmallImageHeight, function(y, x) { + var ret; + if (6 <= x && x < 8 && 0 <= y && y < 7) + ret = "."; + else if ((x-15)*(x-15)+(y-1)*(y-1) < 2) + ret = "^"; + else if ((x-40)*(x-40)+(y-6)*(y-6) < 6) + ret = "%"; + else if ((x-1)*(x-1)+(y-12)*(y-12) < 2) + ret = "@"; + else + ret = " "; + return ret.charCodeAt(0) - 32; + }); + +var SmallImagePar = parImage(SmallImage, + SmallImageWidth, + SmallImageHeight); var bigImage = - WrapArray.build(200, 70, - function(x, y, k) { - var ret; - if (4 <= x && x < 7 && 10 <= y && y < 40) - ret = "."; - else if ((x-150)*(x-150)+(y-13)*(y-13) < 70) - ret = "^"; - else if ((x-201)*(x-201)+(y-33)*(y-33) < 200) - ret = "%"; - else if ((x-15)*(x-15)+(y-3)*(y-3) < 7) - ret = "@"; - else - ret = " "; - return ret.charCodeAt(0) - 32; - }); + buildArray(200, 70, function(y, x) { + var ret; + if (4 <= x && x < 7 && 10 <= y && y < 40) + ret = "."; + else if ((x-150)*(x-150)+(y-13)*(y-13) < 70) + ret = "^"; + else if ((x-201)*(x-201)+(y-33)*(y-33) < 200) + ret = "%"; + else if ((x-15)*(x-15)+(y-3)*(y-3) < 7) + ret = "@"; + else + ret = " "; + return ret.charCodeAt(0) - 32; + }); // randomImage: Nat Nat Nat Nat -> RectArray function randomImage(w, h, sparsity, variety) { - return WrapArray.build(w, h, function (x,y) { - if (Math.random() > 1/sparsity) - return 0; - else + return buildArray(w, h, function (x,y) { + if (Math.random() > 1/sparsity) + return 0; + else return 1+Math.random()*variety|0; }); } // stripedImage: Nat Nat -> RectArray function stripedImage(w, h) { - return WrapArray.build(w, h, - function (x, y) (Math.abs(x%100-y%100) < 10) ? 32 : 0); + return buildArray(w, h, function (y, x) { + return (Math.abs(x%100-y%100) < 10) ? 32 : 0; + }); } var massiveImage = - WrapArray.build(70, 10000, function(x,y) (Math.abs(x%100-y%100) < 10) ? 32 : 0); + buildArray(70, 10000, function(y, x) (Math.abs(x%100-y%100) < 10) ? 32 : 0); -// asciiart: Self -> RectArray -WrapArray.prototype.asciiart = function asciiart() { - return this.map(function (x) String.fromCharCode(x+32)); -}; - -// row: Self Nat -> Array -WrapArray.prototype.row = function row(i) { - return this.slice(i*this.width*this.payload, (i+1)*this.width*this.payload); -}; - -// render: Self -> String -WrapArray.prototype.render = function render() { - var art = this.asciiart(); - var a = new Array(art.height); - for (var i=0; i < art.height; i++) { - a[i] = art.row(i); - } - return a.map(function(r) r.join("")).join("\n"); -}; - -// print: Self -> void; effect: prints rendered self. -WrapArray.prototype.print = - (function locals() { var pr = print; - return function print() pr(this.render()); })(); - -// toRectArray: Self Nat Nat -> RectArray -Array.prototype.toRectArray = function toRectArray(w,h) { - var p = this; - return WrapArray.build(w, h, function (i, j) p[i+j*w]); -}; - -WrapArray.prototype.toRectArray = function toRectArray(w,h) { - var p = this; - return WrapArray.build(w, h, function (i, j) p.get(i, j)); -}; - -// toRectArray: Self -> RectArray -ParallelArray.prototype.toRectArray = function toRectArray(w, h) { - var p = this; - if (h == undefined) h = p.shape[1]; - if (w == undefined) w = p.shape[0]; - if (p.shape.length == 2) - return WrapArray.build(w, h, function (i,j) p.get(i,j)); - if (p.shape.length == 1) - return WrapArray.build(w, h, function (i,j) p.get(i + j*w)); -}; - -// toParallelArray: Self -> ParallelArray -WrapArray.prototype.toParallelArray = function toParallelArray(mode) { - var r = this; - var w = this.width; - var h = this.height; - - if (false) { - // This path is too slow... - return new ParallelArray([w,h], function (i,j) r.get(i,j), mode); - } else { - // ...so resort to abstraction-breaking path here; yields >10x - // speedup on Felix's Intel Core i7; but can we make above fast? - var b = r.backingArray; - return new ParallelArray([w,h], function (i,j) b[i+w*j], mode); - } -}; - -// transpose: Self -> RectArray -WrapArray.prototype.transpose = - function transpose() { - var r = this; - var b = r.backingArray; - var w = r.width; - return WrapArray.buildN(r.height, r.width, r.payload, - function(x, y, k) - // r.get(y,x,k) - b[y+w*x] - ); - }; - -// transpose: Self -> ParallelArray -WrapArray.prototype.transposeParallelArray = - function transpose(mode) { - var r = this; - var b = r.backingArray; - var w = r.width; - return new ParallelArray([r.height, r.width], function(x, y) b[y+w*x], mode); - }; - -ParallelArray.prototype.transpose = - function transpose(mode) { - var p = this; - var w = this.shape[0]; - var h = this.shape[1]; - return new ParallelArray([h,w], function (i,j) p.get(j,i), mode); - }; - -// The detectEdgesSeq function allows edgesSequentially to be -// implemented w/ sequential code directly rather than using a -// ParMode to enforce sequential execution a la buildSequentially.) - -// detectEdgesSeq_naive: RectArray -> RectArray -// This version is naive because working directly on an array is -// enormously faster. -function detectEdgesSeq_naive(ra) { - var sobelX = [[-1.0, 0.0, 1.0], - [-2.0, 0.0, 2.0], - [-1.0, 0.0, 1.0]]; - var sobelY = [[1.0, 2.0, 1.0], - [0.0, 0.0, 0.0], - [-1.0, -2.0, -1.0]]; - - var width = ra.width; - var height = ra.height; - - var abs = function(x) (x < 0) ? -x : x; - return WrapArray.build(width, height, - // The fill functions here and below are begging for refactoring, but leaving as manual clones until performance issues are resolved. - function (x,y) - { - // process pixel - var totalX = 0; - var totalY = 0; - for (var offY = -1; offY <= 1; offY++) { - var newY = y + offY; - for (var offX = -1; offX <= 1; offX++) { - var newX = x + offX; - if ((newX >= 0) && (newX < width) && (newY >= 0) && (newY < height)) { - var pointIndex = (x + offX + (y + offY) * width); - var e = ra.get(x + offX, y + offY); - totalX += e * sobelX[offY + 1][offX + 1]; - totalY += e * sobelY[offY + 1][offX + 1]; - } - } - } - var total = (abs(totalX) + abs(totalY))/8.0 | 0; - return total; - }); -} - -// detectEdgesSeq_array_wh: Array Nat Nat -> Array -// The input array needs to be carrying width and height properties, like RectArray -function detectEdgesSeq_array_wh(data, width, height) { - var data1 = new Array(width*height); - var sobelX = [[-1.0, 0.0, 1.0], - [-2.0, 0.0, 2.0], - [-1.0, 0.0, 1.0]]; - var sobelY = [[1.0, 2.0, 1.0], - [0.0, 0.0, 0.0], - [-1.0, -2.0, -1.0]]; - - var abs = function(x) (x < 0) ? -x : x; - for (var y = 0; y < height; y++) { - for (var x = 0; x < width; x++) { - // process pixel - var totalX = 0; - var totalY = 0; - for (var offY = -1; offY <= 1; offY++) { - var newY = y + offY; - for (var offX = -1; offX <= 1; offX++) { - var newX = x + offX; - if ((newX >= 0) && (newX < width) && (newY >= 0) && (newY < height)) { - var pointIndex = x + offX + (y + offY) * width; - var e = data[pointIndex]; - totalX += e * sobelX[offY + 1][offX + 1]; - totalY += e * sobelY[offY + 1][offX + 1]; - } - } - } - var total = ((abs(totalX) + abs(totalY))/8.0)|0; - var index = y*width+x; - data1[index] = total | 0; - } +function printImage(array, width, height) { + print("Width", width, "Height", height); + for (var y = 0; y < height; y++) { + var line = ""; + for (var x = 0; x < width; x++) { + var c = array[y*width + x]; + line += String.fromCharCode(c + 32); } - data1.width = width; - data1.height = height; - return data1; + print(line); + } } -function detectEdgesSeq_wraparray(data) { - return detectEdgesSeq_array_wh(data.backingArray, data.width, data.height); -} +/////////////////////////////////////////////////////////////////////////// +// Common -// detectEdges: Self -> RectArray -WrapArray.prototype.detectEdges2D = - (function locals () { var detect = detectEdgesSeq_array_wh; - return function detectEdges() { - return detect(this.backingArray, this.width, this.height).toRectArray(this.width, this.height); - }; - })(); +var SobelX = [[-1.0, 0.0, 1.0], + [-2.0, 0.0, 2.0], + [-1.0, 0.0, 1.0]]; +var SobelY = [[ 1.0, 2.0, 1.0], + [ 0.0, 0.0, 0.0], + [-1.0, -2.0, -1.0]]; -// detectEdgesPar: ParallelArray [ParMode] -> ParallelArray - -function detectEdgesPar_2d(pa, mode) -{ - var sobelX = [[-1.0, 0.0, 1.0], - [-2.0, 0.0, 2.0], - [-1.0, 0.0, 1.0]]; - var sobelY = [[1.0, 2.0, 1.0], - [0.0, 0.0, 0.0], - [-1.0, -2.0, -1.0]]; - - var width = pa.shape[0]; - var height = pa.shape[1]; - - var abs = function(x) (x < 0) ? -x : x; - var ret=new ParallelArray([width, height], - function (x,y) - { - // process pixel - var totalX = 0; - var totalY = 0; - for (var offY = -1; offY <= 1; offY++) { - var newY = y + offY; - for (var offX = -1; offX <= 1; offX++) { - var newX = x + offX; - if ((newX >= 0) && (newX < width) && (newY >= 0) && (newY < height)) { - var pointIndex = (x + offX + (y + offY) * width); - var e = pa.get(x + offX, y + offY); - totalX += e * sobelX[offY + 1][offX + 1]; - totalY += e * sobelY[offY + 1][offX + 1]; - } - } - } - var total = (abs(totalX) + abs(totalY))/8.0 | 0; - return total; - }, mode); - ret.width = width; - ret.height = height; - return ret; -} - -function detectEdgesPar_1d(pa, mode) -{ - var sobelX = [[-1.0, 0.0, 1.0], - [-2.0, 0.0, 2.0], - [-1.0, 0.0, 1.0]]; - var sobelY = [[1.0, 2.0, 1.0], - [0.0, 0.0, 0.0], - [-1.0, -2.0, -1.0]]; - - var width = pa.shape[0]; - var height = pa.shape[1]; - - var abs = function(x) (x < 0) ? -x : x; - - var ret=new ParallelArray(width*height, - function (index) - { - var j = index | 0; - var y = (j / width) | 0; - var x = (j % width); - // process pixel - var totalX = 0; - var totalY = 0; - for (var offY = -1; offY <= 1; offY++) { - var newY = y + offY; - for (var offX = -1; offX <= 1; offX++) { - var newX = x + offX; - if ((newX >= 0) && (newX < width) && (newY >= 0) && (newY < height)) { - var pointIndex = (x + offX + (y + offY) * width); - var e = pa.get(x + offX, y + offY); - totalX += e * sobelX[offY + 1][offX + 1]; - totalY += e * sobelY[offY + 1][offX + 1]; - } - } - } - var total = (abs(totalX) + abs(totalY))/8.0 | 0; - return total; - }, mode); - - ret.width = width; - ret.height = height; - return ret; -} - -// detectEdges: Self [ParMode] -> ParallelArray -ParallelArray.prototype.detectEdges2D = - (function locals () { var detect = detectEdgesPar_2d; - return function detectEdges(mode) detect(this, mode); })(); - -ParallelArray.prototype.detectEdges1D = - (function locals () { var detect = detectEdgesPar_1d; - return function detectEdges(mode) detect(this, mode); })(); - -// computeEnergy: ParallelArray -> RectArray +// computeEnergy: Array -> RectArray // // (The return type is forced upon us, for now at least, until we add // appropriate API to ParallelArray; there's a dependency from each // row upon its predecessor, but the contents of each row could be // computed in parallel.) -function computeEnergy_2d(source, width, height) { - var energy = new WrapArray(width, height); - energy.set(0, 0, 0); +function computeEnergy(source, width, height) { + var energy = new Array(width * height); + energy[0] = 0; for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { - var e = source.get(x, y); + var e = source[y*width + x]; if (y >= 1) { - var p = energy.get(x, y-1); + var p = energy[(y-1)*width + x]; if (x > 0) { - p = Math.min(p, energy.get(x-1, y-1)); + p = Math.min(p, energy[(y-1)*width + x-1]); } if (x < (width - 1)) { - p = Math.min(p, energy.get(x+1, y-1)); + p = Math.min(p, energy[(y-1)*width + x+1]); } e += p; } - energy.set(x, y, e); + energy[y*width + x] = e; } } return energy; } -function computeEnergy_1d(source, width, height) { - var energy = new WrapArray(width, height); - energy.set(0, 0, 0); - for (var y = 0; y < height; y++) { - for (var x = 0; x < width; x++) { - var e = source.get(x + y*width); - if (y >= 1) { - var p = energy.get(x, (y-1)); - if (x > 0) { - p = Math.min(p, energy.get(x-1, (y-1))); - } - if (x < (width - 1)) { - p = Math.min(p, energy.get(x+1, (y-1))); - } - e += p; - } - energy.set(x, y, e); - } - } - return energy; -} - -// computeEnergy: Self -> RectArray -WrapArray.prototype.computeEnergy = - (function locals () { var energy = computeEnergy_2d; - return function computeEnergy() energy(this, this.width, this.height); })(); - -// computeEnergy: Self -> RectArray -ParallelArray.prototype.computeEnergy = - (function locals () { - var energy1d = computeEnergy_1d; - var energy2d = computeEnergy_2d; - return function computeEnergy() - (this.shape.length == 2) - ? energy2d(this, this.width, this.height) - : energy1d(this, this.width, this.height); })(); - // findPath: RectArray -> Array // (This is inherently sequential.) -function findPath(energy) +function findPath(energy, width, height) { - var height = energy.height; - var width = energy.width; var path = new Array(height); var y = height - 1; var minPos = 0; - var minEnergy = energy.get(minPos, y); + var minEnergy = energy[y*width + minPos]; for (var x = 1; x < width; x++) { - if (energy.get(x,y) < minEnergy) { - minEnergy = energy.get(x,y); + if (energy[y+width + x] < minEnergy) { + minEnergy = energy[y*width + x]; minPos = x; } } path[y] = minPos; for (y = height - 2; y >= 0; y--) { - minEnergy = energy.get(minPos, y); + minEnergy = energy[y*width + minPos]; // var line = energy[y] var p = minPos; - if (p >= 1 && energy.get(p-1, y) < minEnergy) { - minPos = p-1; minEnergy = energy.get(p-1, y); + if (p >= 1 && energy[y*width + p-1] < minEnergy) { + minPos = p-1; minEnergy = energy[y*width + p-1]; } - if (p < width - 1 && energy.get(p+1, y) < minEnergy) { - minPos = p+1; minEnergy = energy.get(p+1, y); + if (p < width - 1 && energy[y*width + p+1] < minEnergy) { + minPos = p+1; minEnergy = energy[y*width + p+1]; } path[y] = minPos; } return path; } -// findPath: Self -> Path -WrapArray.prototype.findPath = - (function locals() { var path = findPath; - return function findPath() path(this); })(); +/////////////////////////////////////////////////////////////////////////// +// Sequential -// cutPathHorizontallyBW : RectArray Array -> RectArray -function cutPathHorizontallyBW(ra, path) { - return WrapArray.build(ra.width-1, ra.height, - function (x, y) { - if (x < path[y]-1) - return ra.get(x, y); - if (x == path[y]-1) - return (ra.get(x,y)+ra.get(x+1,y))/2|0; - else - return ra.get(x+1,y); - }); +function transposeSeq(array, width, height) { + return buildArray(height, width, function(y, x) { + return array[x*width + y]; + }); } -// cutPathHorizontallyBW: Self -> RectArray -WrapArray.prototype.cutPathHorizontallyBW = - (function locals() { var cut = cutPathHorizontallyBW; - return function cutPathHorizontallyBW(path) cut(this, path); })(); - -// cutPathVerticallyBW: RectArray Path -> RectArray -function cutPathVerticallyBW(ra, path) { - return WrapArray.build(ra.width, ra.height-1, - function (x, y) { - if (y < path[x]-1) - return ra.get(x, y); - if (y == path[x]-1) - return (ra.get(x,y)+ra.get(x,y+1))/2|0; - else - return ra.get(x,y+1); - }); -} - -// cutPathVerticallyBW: Self Path -> RectArray -WrapArray.prototype.cutPathVerticallyBW = - (function locals() { var cut = cutPathVerticallyBW; - return function cutPathVerticallyBW(path) cut(this, path); })(); - -// cutHorizontalSeamBW: RectArray ParMode -> RectArray - -function cutHorizontalSeamBW_seq(r) -{ - var e = r.detectEdges2D().computeEnergy(); - var p = e.findPath(); e = null; - return r.cutPathHorizontallyBW(p); -} - -function cutHorizontalSeamBW_par(r, mode) -{ - var e = r.toParallelArray(mode).detectEdges1D(mode).computeEnergy(mode); - var p = e.findPath(mode); e = null; - return r.cutPathHorizontallyBW(p, mode); -} - -// cutHorizontalSeamBW: Self ParMode -> RectArray -WrapArray.prototype.cutHorizontalSeamBW = - (function locals() { - var cut_seq = cutHorizontalSeamBW_seq; - var cut_par = cutHorizontalSeamBW_par; - return function cutHorizontalSeamBW(mode) { - return (mode ? cut_par(this, mode) : cut_seq(this)); - };})(); - -// cutVerticalSeamBW: RectArray ParMode -> RectArray -function cutVerticalSeamBW_seq(r) -{ - var e = r.transpose().detectEdges2D().computeEnergy(); - return r.cutPathVerticallyBW(e.findPath()); -} - -function cutVerticalSeamBW_par(r, mode) -{ - var e = r.transposeParallelArray(mode).detectEdges1D(mode).computeEnergy(mode); - return r.cutPathVerticallyBW(e.findPath()); -} - -// cutVerticalSeamBW: Self ParMode -> RectArray -WrapArray.prototype.cutVerticalSeamBW = - (function locals() { - var cut_seq = cutVerticalSeamBW_seq; - var cut_par = cutVerticalSeamBW_par; - return function cutVerticalSeamBW(mode) { - return (mode ? cut_par(this, mode) : cut_seq(this)); - };})(); - -// cutVerticalSeamBW: Self Nat Nat ParMode -> Self -WrapArray.prototype.shrinkBW = function shrinkBW(w, h, mode) { - if (w == undefined) - w = this.width / 2 | 0; - if (h == undefined) - h = this.height / 2 | 0; - var r = this; - var i=0; - while (r.height > h || r.width > w) { - if (i > 0 && i%50 == 0) { print("shrinkBW iteration "+i); } i++; - if (r.width > w) - r = r.cutHorizontalSeamBW(mode); - if (r.height > h) - r = r.cutVerticalSeamBW(mode); - } - return r; -}; - -// timedShrinkBW: Self Nat Nat ParMode -> PhaseTimes -WrapArray.prototype.timedShrinkBW = function timedShrinkBW(w, h, mode) { - var times = { - "topar": 0, "trans": 0, "edges": 0, "energ": 0, "fpath": 0, "cpath": 0 - }; - var r = this; - var lasttime = new Date(); - function elapsed() { - var d = new Date(); var e = d - lasttime; lasttime = d; return e; - } - if (mode) { - while (r.height > h || r.width > w) { - if (r.width > w) { - elapsed(); - var e = r.toParallelArray(mode); - times.topar += elapsed(); - e = e.detectEdges1D(mode); - times.edges += elapsed(); - e = e.computeEnergy(mode); - times.energ += elapsed(); - e = e.findPath(mode); - times.fpath += elapsed(); - r = r.cutPathHorizontallyBW(e, mode); - times.cpath += elapsed(); - e = null; - } - if (r.height > h) { - elapsed(); - var e = r.transposeParallelArray(mode); - times.trans += elapsed(); - e = e.detectEdges1D(mode); - times.edges += elapsed(); - e = e.computeEnergy(mode); - times.energ += elapsed(); - e = e.findPath(mode); - times.fpath += elapsed(); - r = r.cutPathVerticallyBW(e, mode); - times.cpath += elapsed(); - e = null; - } - } - } else { - while (r.height > h || r.width > w) { - if (r.width > w) { - elapsed(); - var e = r.detectEdges2D(); - times.edges += elapsed(); - e = e.computeEnergy(); - times.energ += elapsed(); - var p = e.findPath(); e = null; - times.fpath += elapsed(); - r = r.cutPathHorizontallyBW(p); - times.cpath += elapsed(); - e = null; p = null; - } - if (r.height > h) { - elapsed(); - var e = r.transpose(); - times.trans += elapsed(); - e = e.detectEdges2D(); - times.edges += elapsed(); - e = e.computeEnergy(); - times.energ += elapsed(); - var p = e.findPath(); e = null; - times.fpath += elapsed(); - r = r.cutPathVerticallyBW(p); - times.cpath += elapsed(); - } +// detectEdgesSeq: Array Nat Nat -> Array +function detectEdgesSeq(data, width, height) { + var data1 = new Array(width * height); + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var total = compute(y, x); + data1[y*width + x] = total | 0; } } - return times; -}; + return data1; -function timedDetectEdges2D(mode) { - var d = new Date(); this.detectEdges2D(mode); var e = new Date(); return e - d; -}; + function compute(y, x) { + var totalX = 0; + var totalY = 0; -function timedDetectEdges1D(mode) { - var d = new Date(); this.detectEdges1D(mode); var e = new Date(); return e - d; -}; + var offYMin = (y == 0 ? 0 : -1); + var offYMax = (y == height - 1 ? 0 : 1); + var offXMin = (x == 0 ? 0 : -1); + var offXMax = (x == width - 1 ? 0 : 1); -WrapArray.prototype.timedDetectEdges2D = timedDetectEdges2D; -ParallelArray.prototype.timedDetectEdges2D = timedDetectEdges2D; -ParallelArray.prototype.timedDetectEdges1D = timedDetectEdges1D; + for (var offY = offYMin; offY <= offYMax; offY++) { + for (var offX = offXMin; offX <= offXMax; offX++) { + var e = data[(y + offY) * width + x + offX]; + totalX += e * SobelX[offY + 1][offX + 1]; + totalY += e * SobelY[offY + 1][offX + 1]; + } + } + return (Math.abs(totalX) + Math.abs(totalY))/8.0 | 0; + } +} + +function cutPathHorizontallyBWSeq(array, width, height, path) { + return buildArray(width-1, height, function (y, x) { + if (x < path[y]-1) + return array[y*width + x]; + if (x == path[y]-1) + return (array[y*width + x] + array[y*width + x+1]) / 2 | 0; + else + return array[y*width + x+1]; + }); +} + +function cutPathVerticallyBWSeq(array, width, height, path) { + return buildArray(width, height-1, function (y, x) { + if (y < path[x]-1) + return array[y*width + x]; + if (y == path[x]-1) + return (array[y*width + x] + array[(y+1)*width + x]) / 2 | 0; + else + return array[(y+1)*width + x]; + }); +} + +function cutHorizontalSeamBWSeq(array, width, height) +{ + var edges = detectEdgesSeq(array, width, height); + var energy = computeEnergy(edges, width, height); + var path = findPath(energy, width, height); + edges = null; // no longer live + return cutPathHorizontallyBWSeq(array, width, height, path); +} + +function cutVerticalSeamBWSeq(array, width, height) +{ + var arrayT = transposeSeq(array, width, height); + var edges = detectEdgesSeq(arrayT, height, width); + var energy = computeEnergy(edges, height, width); + var path = findPath(energy, height, width); + edges = null; // no longer live + return cutPathVerticallyBWSeq(array, width, height, path); +} + +function reduceImageBWSeq(image, + width, height, + newWidth, newHeight, + intermediateFunc, + finalFunc) { + while (width > newWidth || height > newHeight) { + intermediateFunc(image, width, height); + + if (width > newWidth) { + image = cutHorizontalSeamBWSeq(image, width, height); + width -= 1; + } + + if (height > newHeight) { + image = cutVerticalSeamBWSeq(image, width, height); + height -= 1; + } + } + + finalFunc(image, width, height); + return image; +} + +/////////////////////////////////////////////////////////////////////////// +// Parallel + +function transposePar(image) { + var height = image.shape[0]; + var width = image.shape[1]; + return new ParallelArray([width, height], function (y, x) { + return image.get(x, y); + }); +} + +// detectEdgesSeq: Array Nat Nat -> Array +function detectEdgesPar(image) { + var height = image.shape[0]; + var width = image.shape[1]; + return new ParallelArray([height, width], function (y, x) { + var totalX = 0; + var totalY = 0; + + var offYMin = (y == 0 ? 0 : -1); + var offYMax = (y == height - 1 ? 0 : 1); + var offXMin = (x == 0 ? 0 : -1); + var offXMax = (x == width - 1 ? 0 : 1); + + for (var offY = offYMin; offY <= offYMax; offY++) { + for (var offX = offXMin; offX <= offXMax; offX++) { + var e = image.get(y + offY, x + offX); + totalX += e * SobelX[offY + 1][offX + 1]; + totalY += e * SobelY[offY + 1][offX + 1]; + } + } + + var result = (Math.abs(totalX) + Math.abs(totalY))/8.0 | 0; + return result; + }); +} + +function cutPathHorizontallyBWPar(image, path) { + var height = image.shape[0]; + var width = image.shape[1]; + return new ParallelArray([height, width-1], function (y, x) { + if (x < path[y]-1) + return image.get(y, x); + if (x == path[y]-1) + return (image.get(y, x) + image.get(y, x+1)) / 2 | 0; + else + return image.get(y, x+1); + }); +} + +function cutPathVerticallyBWPar(image, path) { + var height = image.shape[0]; + var width = image.shape[1]; + return new ParallelArray([height-1, width], function (y, x) { + if (y < path[x]-1) + return image.get(y, x); + if (y == path[x]-1) + return (image.get(y, x) + image.get(y+1, x)) / 2 | 0; + else + return image.get(y+1, x); + }); +} + +function cutHorizontalSeamBWPar(image) +{ + var height = image.shape[0]; + var width = image.shape[1]; + var edges = detectEdgesPar(image); + var energy = computeEnergy(edges.buffer, width, height); + var path = findPath(energy, width, height); + edges = null; // no longer live + return cutPathHorizontallyBWPar(image, path); +} + +function cutVerticalSeamBWPar(image) { + var height = image.shape[0]; + var width = image.shape[1]; + var imageT = transposePar(image); + var edges = detectEdgesPar(imageT); + var energy = computeEnergy(edges.buffer, height, width); + var path = findPath(energy, height, width); + edges = null; // no longer live + return cutPathVerticallyBWPar(image, path); +} + +function reduceImageBWPar(image, + newWidth, newHeight, + intermediateFunc, + finalFunc) { + var height = image.shape[0]; + var width = image.shape[1]; + while (width > newWidth || height > newHeight) { + intermediateFunc(image.buffer, width, height); + + if (width > newWidth) { + image = cutHorizontalSeamBWPar(image); + width -= 1; + } + + if (height > newHeight) { + image = cutVerticalSeamBWPar(image); + height -= 1; + } + } + + finalFunc(image.buffer, width, height); + return image.buffer; +} + +/////////////////////////////////////////////////////////////////////////// +// Benchmarking via run.sh + +var BenchmarkImageWidth = 512; +var BenchmarkImageHeight = 256; +var BenchmarkImage = stripedImage(BenchmarkImageWidth, + BenchmarkImageHeight); +var BenchmarkImagePar = parImage(BenchmarkImage, + BenchmarkImageWidth, + BenchmarkImageHeight); + +var benchmarking = (typeof(MODE) != "undefined"); if (benchmarking) { - // Below functions are to interface with run.sh + load(libdir + "util.js"); + benchmark("LIQUID-RESIZE", 1, DEFAULT_MEASURE, + function() { + return reduceImageBWSeq( + BenchmarkImage, + BenchmarkImageWidth, BenchmarkImageHeight, + BenchmarkImageWidth/2, BenchmarkImageHeight/2, + function() {}, + function() {}); + }, - // For simple, repeatable investigation, use: tinyImage, smallImage, - // bigImage, or massiveImage for expression establishing seqInput - // below. stripedImage() is also useful for repeatable inputs. - // - // For play, use: randomImage(width, height, sparsity, variety) - // or stripedImage(width, height). - // - // The default tower image from the original benchmarks was 800x542. - // (Correspondingly, the shrunken versions were 400x271 and 80x54.) - var seqInput = stripedImage(800/4|0, 542/4|0, 10, 10); - var parInput = seqInput.toParallelArray(); - - function buildSequentially() { - return seqInput.toParallelArray({mode:"seq"}); - } - function buildParallel() { - return seqInput.toParallelArray({mode:"par"}); - } - - function edgesSequentially() { - return detectEdgesSeq_wraparray(seqInput); - } - function edgesParallel() { - return detectEdgesPar_1d(parInput); - } - - function resizSequentially() { - var input = seqInput; - return input.shrinkBW(input.width/2|0, input.height/2|0); - } - function resizParallel() { - var input = seqInput; // Use of "seqInput" here is deliberate, as - // the above code does not add a shrinkBW - // method to ParallelArray.prototype (we - // are going to marshall repeatedly in the - // loop within shrinkBW anyway). - return input.shrinkBW(input.width/2|0, input.height/2|0, {mode:"par"}); - } - - if (benchmarking) { - benchmark("BUILD", 1, DEFAULT_MEASURE, - buildSequentially, buildParallel); - - benchmark("EDGES", 1, DEFAULT_MEASURE, - edgesSequentially, edgesParallel); - - benchmark("RESIZ", 1, DEFAULT_MEASURE, - resizSequentially, resizParallel); - } + function() { + return reduceImageBWPar( + BenchmarkImagePar, + BenchmarkImageWidth/2, BenchmarkImageHeight/2, + function() {}, + function() {}); + }); +} + +/////////////////////////////////////////////////////////////////////////// +// Running (sanity check) + +if (!benchmarking) { + var seqData = + reduceImageBWSeq(SmallImage, SmallImageWidth, SmallImageHeight, + SmallImageWidth - 15, SmallImageHeight - 10, + function() {}, + printImage); + + var parData = + reduceImageBWPar(SmallImagePar, + SmallImageWidth - 15, SmallImageHeight - 10, + function() {}, + printImage); + + var failed = false; + assertEq(seqData.length, parData.length); + for (var i = 0; i < seqData.length; i++) { + if (seqData[i] !== parData[i]) { + print("At index ", i, " sequential has ", seqData[i], " parallel has ", parData[i]); + failed = true; + } + } + if (failed) + throw new Error(); } diff --git a/js/src/parjs-benchmarks/run.sh b/js/src/parjs-benchmarks/run.sh index d78924db228..25ac388b20b 100755 --- a/js/src/parjs-benchmarks/run.sh +++ b/js/src/parjs-benchmarks/run.sh @@ -22,6 +22,6 @@ D="$(dirname $0)" S="$1" shift for T in "$@"; do - echo "$S" -e "'"'var libdir="'$D'/"; var MODE="'$MODE'";'"'" "$T" - "$S" -e 'var libdir="'$D'/"; var MODE="'$MODE'";' "$T" + echo "$S" --ion-parallel-compile=on -e "'"'var libdir="'$D'/"; var MODE="'$MODE'";'"'" "$T" + "$S" --ion-parallel-compile=on -e 'var libdir="'$D'/"; var MODE="'$MODE'";' "$T" done diff --git a/js/src/parjs-benchmarks/util.js b/js/src/parjs-benchmarks/util.js index bb64743255d..56cf4ffeb3f 100644 --- a/js/src/parjs-benchmarks/util.js +++ b/js/src/parjs-benchmarks/util.js @@ -31,6 +31,9 @@ function benchmark(label, w, m, seq, par) { } if (mode(PAR)) { + print("Warming up parallel runs"); + warmup(w, par); + print("Measuring parallel runs"); var [parTimes, parResult] = measureN(m, par); }