Bug 872352 - Simplify edges/liquid-resize benchmarks rs=tests

This commit is contained in:
Nicholas D. Matsakis 2013-06-12 22:17:30 -04:00
parent ad77a34995
commit 87ad0fff8c
8 changed files with 880 additions and 798 deletions

View File

@ -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));

View File

@ -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)});

View File

@ -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)});

View File

@ -1,5 +0,0 @@
load(libdir + "edges.js");
benchmark("EDGES-PARALLEL-ARRAY-2D", 1, DEFAULT_MEASURE,
function() {edges1dArraySequentially(ArrInput, Width, Height)},
function() {edges2dParallelArrayInParallel(ParArrInput2D)});

View File

@ -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);
}

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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);
}