From 3b84dbce08ab15db2d68b2c3fd7559d849076340 Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 10 Aug 2021 17:16:20 -0400 Subject: [PATCH 01/11] ignore lock file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ddc9ab3..c7b283a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,6 @@ logs results npm-debug.log +package-lock.json node_modules/* *.DS_Store \ No newline at end of file From d24605721f9dfd6ee718a4bceaae3a5eb3c25328 Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 10 Aug 2021 17:17:25 -0400 Subject: [PATCH 02/11] trim spaces --- fdg.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/fdg.js b/fdg.js index 8db7e43..60d1f13 100644 --- a/fdg.js +++ b/fdg.js @@ -21,17 +21,17 @@ var centralDiff = cwiseCompiler({ post: EmptyProc, body: { args: [ { - name: 'out', + name: 'out', lvalue: true, rvalue: false, count: 1 }, { - name: 'left', + name: 'left', lvalue: false, rvalue: true, count: 1 }, { - name: 'right', + name: 'right', lvalue: false, rvalue: true, count: 1 @@ -49,7 +49,7 @@ var zeroOut = cwiseCompiler({ post: EmptyProc, body: { args: [ { - name: 'out', + name: 'out', lvalue: true, rvalue: false, count: 1 @@ -117,7 +117,7 @@ function generateGradient(boundaryConditions) { var d = boundaryConditions.length var code = ['function gradient(dst,src){var s=src.shape.slice();' ] - + function handleBoundary(facet) { var cod = d - facet.length @@ -139,9 +139,9 @@ function generateGradient(boundaryConditions) { if(loStr.length === 0) { boundStr = '' } - + if(cod > 0) { - code.push('if(1') + code.push('if(1') for(var i=0; i= 0 || facet.indexOf(-(i+1)) >= 0) { continue @@ -178,9 +178,9 @@ function generateGradient(boundaryConditions) { dPickStr.join(), ')))}else{dst.set(', pickStr.join(), ',', bnd, ',0)};') } else { - code.push('if(s[', bnd, ']>1){diff(', outStr, - ',src.pick(', cPickStr.join(), ')', boundStr, - ',src.pick(', dPickStr.join(), ')', boundStr, + code.push('if(s[', bnd, ']>1){diff(', outStr, + ',src.pick(', cPickStr.join(), ')', boundStr, + ',src.pick(', dPickStr.join(), ')', boundStr, ');}else{zero(', outStr, ');};') } break @@ -199,7 +199,7 @@ function generateGradient(boundaryConditions) { if(facet[i] < 0) { aPickStr[bnd] = 's[' + bnd + ']-2' bPickStr[bnd] = '0' - + } else { aPickStr[bnd] = 's[' + bnd + ']-1' bPickStr[bnd] = '1' @@ -211,9 +211,9 @@ function generateGradient(boundaryConditions) { bPickStr.join(), ')))}else{dst.set(', pickStr.join(), ',', bnd, ',0)};') } else { - code.push('if(s[', bnd, ']>2){diff(', outStr, - ',src.pick(', aPickStr.join(), ')', boundStr, - ',src.pick(', bPickStr.join(), ')', boundStr, + code.push('if(s[', bnd, ']>2){diff(', outStr, + ',src.pick(', aPickStr.join(), ')', boundStr, + ',src.pick(', bPickStr.join(), ')', boundStr, ');}else{zero(', outStr, ');};') } break From dbd50af92b1d7f0cdc21052288222fbfd90b4a05 Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 10 Aug 2021 18:26:59 -0400 Subject: [PATCH 03/11] use gradinet cache --- fdg.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fdg.js b/fdg.js index 60d1f13..1f4d85f 100644 --- a/fdg.js +++ b/fdg.js @@ -260,7 +260,7 @@ function generateGradient(boundaryConditions) { var link = Function.apply(void 0, linkNames) var proc = link.apply(void 0, linkArgs) - TEMPLATE_CACHE[token] = proc + GRADIENT_CACHE[token] = proc return proc } From b12281eae3fe8cd21b8cd5dc6b1c96aa8b4a2f60 Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 11 Aug 2021 10:54:45 -0400 Subject: [PATCH 04/11] temp inline cwise-compiler --- fdg.js | 546 ++++++++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 - 2 files changed, 545 insertions(+), 2 deletions(-) diff --git a/fdg.js b/fdg.js index 1f4d85f..4de489b 100644 --- a/fdg.js +++ b/fdg.js @@ -1,9 +1,553 @@ 'use strict' +var uniq = require("uniq") + +// This function generates very simple loops analogous to how you typically traverse arrays (the outermost loop corresponds to the slowest changing index, the innermost loop to the fastest changing index) +// TODO: If two arrays have the same strides (and offsets) there is potential for decreasing the number of "pointers" and related variables. The drawback is that the type signature would become more specific and that there would thus be less potential for caching, but it might still be worth it, especially when dealing with large numbers of arguments. +function innerFill(order, proc, body) { + var dimension = order.length + , nargs = proc.arrayArgs.length + , has_index = proc.indexArgs.length>0 + , code = [] + , vars = [] + , idx=0, pidx=0, i, j + for(i=0; i 0) { + code.push("var " + vars.join(",")) + } + //Scan loop + for(i=dimension-1; i>=0; --i) { // Start at largest stride and work your way inwards + idx = order[i] + code.push(["for(i",i,"=0;i",i," 0) { + code.push(["index[",pidx,"]-=s",pidx].join("")) + } + code.push(["++index[",idx,"]"].join("")) + } + code.push("}") + } + return code.join("\n") +} + +// Generate "outer" loops that loop over blocks of data, applying "inner" loops to the blocks by manipulating the local variables in such a way that the inner loop only "sees" the current block. +// TODO: If this is used, then the previous declaration (done by generateCwiseOp) of s* is essentially unnecessary. +// I believe the s* are not used elsewhere (in particular, I don't think they're used in the pre/post parts and "shape" is defined independently), so it would be possible to make defining the s* dependent on what loop method is being used. +function outerFill(matched, order, proc, body) { + var dimension = order.length + , nargs = proc.arrayArgs.length + , blockSize = proc.blockSize + , has_index = proc.indexArgs.length > 0 + , code = [] + for(var i=0; i0;){"].join("")) // Iterate back to front + code.push(["if(j",i,"<",blockSize,"){"].join("")) // Either decrease j by blockSize (s = blockSize), or set it to zero (after setting s = j). + code.push(["s",order[i],"=j",i].join("")) + code.push(["j",i,"=0"].join("")) + code.push(["}else{s",order[i],"=",blockSize].join("")) + code.push(["j",i,"-=",blockSize,"}"].join("")) + if(has_index) { + code.push(["index[",order[i],"]=j",i].join("")) + } + } + for(var i=0; i 0) { + allEqual = allEqual && summary[i] === summary[i-1] + } + } + if(allEqual) { + return summary[0] + } + return summary.join("") +} + +//Generates a cwise operator +function generateCWiseOp(proc, typesig) { + + //Compute dimension + // Arrays get put first in typesig, and there are two entries per array (dtype and order), so this gets the number of dimensions in the first array arg. + var dimension = (typesig[1].length - Math.abs(proc.arrayBlockIndices[0]))|0 + var orders = new Array(proc.arrayArgs.length) + var dtypes = new Array(proc.arrayArgs.length) + for(var i=0; i 0) { + vars.push("shape=SS.slice(0)") // Makes the shape over which we iterate available to the user defined functions (so you can use width/height for example) + } + if(proc.indexArgs.length > 0) { + // Prepare an array to keep track of the (logical) indices, initialized to dimension zeroes. + var zeros = new Array(dimension) + for(var i=0; i 0) { + code.push("var " + vars.join(",")) + } + for(var i=0; i 3) { + code.push(processBlock(proc.pre, proc, dtypes)) + } + + //Process body + var body = processBlock(proc.body, proc, dtypes) + var matched = countMatches(loopOrders) + if(matched < dimension) { + code.push(outerFill(matched, loopOrders[0], proc, body)) // TODO: Rather than passing loopOrders[0], it might be interesting to look at passing an order that represents the majority of the arguments for example. + } else { + code.push(innerFill(loopOrders[0], proc, body)) + } + + //Inline epilog + if(proc.post.body.length > 3) { + code.push(processBlock(proc.post, proc, dtypes)) + } + + if(proc.debug) { + console.log("-----Generated cwise routine for ", typesig, ":\n" + code.join("\n") + "\n----------") + } + + var loopName = [(proc.funcName||"unnamed"), "_cwise_loop_", orders[0].join("s"),"m",matched,typeSummary(dtypes)].join("") + var f = new Function(["function ",loopName,"(", arglist.join(","),"){", code.join("\n"),"} return ", loopName].join("")) + return f() +} + +// The function below is called when constructing a cwise function object, and does the following: +// A function object is constructed which accepts as argument a compilation function and returns another function. +// It is this other function that is eventually returned by createThunk, and this function is the one that actually +// checks whether a certain pattern of arguments has already been used before and compiles new loops as needed. +// The compilation passed to the first function object is used for compiling new functions. +// Once this function object is created, it is called with compile as argument, where the first argument of compile +// is bound to "proc" (essentially containing a preprocessed version of the user arguments to cwise). +// So createThunk roughly works like this: +// function createThunk(proc) { +// var thunk = function(compileBound) { +// var CACHED = {} +// return function(arrays and scalars) { +// if (dtype and order of arrays in CACHED) { +// var func = CACHED[dtype and order of arrays] +// } else { +// var func = CACHED[dtype and order of arrays] = compileBound(dtype and order of arrays) +// } +// return func(arrays and scalars) +// } +// } +// return thunk(compile.bind1(proc)) +// } + +var compile = generateCWiseOp + +function createThunk(proc) { + var code = ["'use strict'", "var CACHED={}"] + var vars = [] + var thunkName = proc.funcName + "_cwise_thunk" + + //Build thunk + code.push(["return function ", thunkName, "(", proc.shimArgs.join(","), "){"].join("")) + var typesig = [] + var string_typesig = [] + var proc_args = [["array",proc.arrayArgs[0],".shape.slice(", // Slice shape so that we only retain the shape over which we iterate (which gets passed to the cwise operator as SS). + Math.max(0,proc.arrayBlockIndices[0]),proc.arrayBlockIndices[0]<0?(","+proc.arrayBlockIndices[0]+")"):")"].join("")] + var shapeLengthConditions = [], shapeConditions = [] + // Process array arguments + for(var i=0; i0) { // Gather conditions to check for shape equality (ignoring block indices) + shapeLengthConditions.push("array" + proc.arrayArgs[0] + ".shape.length===array" + j + ".shape.length+" + (Math.abs(proc.arrayBlockIndices[0])-Math.abs(proc.arrayBlockIndices[i]))) + shapeConditions.push("array" + proc.arrayArgs[0] + ".shape[shapeIndex+" + Math.max(0,proc.arrayBlockIndices[0]) + "]===array" + j + ".shape[shapeIndex+" + Math.max(0,proc.arrayBlockIndices[i]) + "]") + } + } + // Check for shape equality + if (proc.arrayArgs.length > 1) { + code.push("if (!(" + shapeLengthConditions.join(" && ") + ")) throw new Error('cwise: Arrays do not all have the same dimensionality!')") + code.push("for(var shapeIndex=array" + proc.arrayArgs[0] + ".shape.length-" + Math.abs(proc.arrayBlockIndices[0]) + "; shapeIndex-->0;) {") + code.push("if (!(" + shapeConditions.join(" && ") + ")) throw new Error('cwise: Arrays do not all have the same shape!')") + code.push("}") + } + // Process scalar arguments + for(var i=0; i0) { + throw new Error("cwise: pre() block may not reference array args") + } + if(i < proc.post.args.length && proc.post.args[i].count>0) { + throw new Error("cwise: post() block may not reference array args") + } + } else if(arg_type === "scalar") { + proc.scalarArgs.push(i) + proc.shimArgs.push("scalar" + i) + } else if(arg_type === "index") { + proc.indexArgs.push(i) + if(i < proc.pre.args.length && proc.pre.args[i].count > 0) { + throw new Error("cwise: pre() block may not reference array index") + } + if(i < proc.body.args.length && proc.body.args[i].lvalue) { + throw new Error("cwise: body() block may not write to array index") + } + if(i < proc.post.args.length && proc.post.args[i].count > 0) { + throw new Error("cwise: post() block may not reference array index") + } + } else if(arg_type === "shape") { + proc.shapeArgs.push(i) + if(i < proc.pre.args.length && proc.pre.args[i].lvalue) { + throw new Error("cwise: pre() block may not write to array shape") + } + if(i < proc.body.args.length && proc.body.args[i].lvalue) { + throw new Error("cwise: body() block may not write to array shape") + } + if(i < proc.post.args.length && proc.post.args[i].lvalue) { + throw new Error("cwise: post() block may not write to array shape") + } + } else if(typeof arg_type === "object" && arg_type.offset) { + proc.argTypes[i] = "offset" + proc.offsetArgs.push({ array: arg_type.array, offset:arg_type.offset }) + proc.offsetArgIndex.push(i) + } else { + throw new Error("cwise: Unknown argument type " + proc_args[i]) + } + } + + //Make sure at least one array argument was specified + if(proc.arrayArgs.length <= 0) { + throw new Error("cwise: No array arguments specified") + } + + //Make sure arguments are correct + if(proc.pre.args.length > proc_args.length) { + throw new Error("cwise: Too many arguments in pre() block") + } + if(proc.body.args.length > proc_args.length) { + throw new Error("cwise: Too many arguments in body() block") + } + if(proc.post.args.length > proc_args.length) { + throw new Error("cwise: Too many arguments in post() block") + } + + //Check debug flag + proc.debug = !!user_args.printCode || !!user_args.debug + + //Retrieve name + proc.funcName = user_args.funcName || "cwise" + + //Read in block size + proc.blockSize = user_args.blockSize || 64 + + return createThunk(proc) +} + + module.exports = gradient var dup = require('dup') -var cwiseCompiler = require('cwise-compiler') +var cwiseCompiler = compileCwise var TEMPLATE_CACHE = {} var GRADIENT_CACHE = {} diff --git a/package.json b/package.json index f65387e..5332167 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "test": "test" }, "dependencies": { - "cwise-compiler": "^1.0.0", "dup": "^1.0.0" }, "devDependencies": { From 1274e5102675e1b1b87c8f115b2cafb9849989b9 Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 11 Aug 2021 11:39:08 -0400 Subject: [PATCH 05/11] use cached functions for CWiseOp --- fdg.js | 380 ++++++--------------------------------------------------- 1 file changed, 36 insertions(+), 344 deletions(-) diff --git a/fdg.js b/fdg.js index 4de489b..6c5ae48 100644 --- a/fdg.js +++ b/fdg.js @@ -1,359 +1,51 @@ 'use strict' -var uniq = require("uniq") - -// This function generates very simple loops analogous to how you typically traverse arrays (the outermost loop corresponds to the slowest changing index, the innermost loop to the fastest changing index) -// TODO: If two arrays have the same strides (and offsets) there is potential for decreasing the number of "pointers" and related variables. The drawback is that the type signature would become more specific and that there would thus be less potential for caching, but it might still be worth it, especially when dealing with large numbers of arguments. -function innerFill(order, proc, body) { - var dimension = order.length - , nargs = proc.arrayArgs.length - , has_index = proc.indexArgs.length>0 - , code = [] - , vars = [] - , idx=0, pidx=0, i, j - for(i=0; i 0) { - code.push("var " + vars.join(",")) - } - //Scan loop - for(i=dimension-1; i>=0; --i) { // Start at largest stride and work your way inwards - idx = order[i] - code.push(["for(i",i,"=0;i",i," 0) { - code.push(["index[",pidx,"]-=s",pidx].join("")) - } - code.push(["++index[",idx,"]"].join("")) - } - code.push("}") - } - return code.join("\n") -} + }, -// Generate "outer" loops that loop over blocks of data, applying "inner" loops to the blocks by manipulating the local variables in such a way that the inner loop only "sees" the current block. -// TODO: If this is used, then the previous declaration (done by generateCwiseOp) of s* is essentially unnecessary. -// I believe the s* are not used elsewhere (in particular, I don't think they're used in the pre/post parts and "shape" is defined independently), so it would be possible to make defining the s* dependent on what loop method is being used. -function outerFill(matched, order, proc, body) { - var dimension = order.length - , nargs = proc.arrayArgs.length - , blockSize = proc.blockSize - , has_index = proc.indexArgs.length > 0 - , code = [] - for(var i=0; i0;){"].join("")) // Iterate back to front - code.push(["if(j",i,"<",blockSize,"){"].join("")) // Either decrease j by blockSize (s = blockSize), or set it to zero (after setting s = j). - code.push(["s",order[i],"=j",i].join("")) - code.push(["j",i,"=0"].join("")) - code.push(["}else{s",order[i],"=",blockSize].join("")) - code.push(["j",i,"-=",blockSize,"}"].join("")) - if(has_index) { - code.push(["index[",order[i],"]=j",i].join("")) - } - } - for(var i=0; i 0) { - allEqual = allEqual && summary[i] === summary[i-1] - } - } - if(allEqual) { - return summary[0] - } - return summary.join("") } //Generates a cwise operator -function generateCWiseOp(proc, typesig) { - - //Compute dimension - // Arrays get put first in typesig, and there are two entries per array (dtype and order), so this gets the number of dimensions in the first array arg. - var dimension = (typesig[1].length - Math.abs(proc.arrayBlockIndices[0]))|0 - var orders = new Array(proc.arrayArgs.length) - var dtypes = new Array(proc.arrayArgs.length) - for(var i=0; i 0) { - vars.push("shape=SS.slice(0)") // Makes the shape over which we iterate available to the user defined functions (so you can use width/height for example) - } - if(proc.indexArgs.length > 0) { - // Prepare an array to keep track of the (logical) indices, initialized to dimension zeroes. - var zeros = new Array(dimension) - for(var i=0; i 0) { - code.push("var " + vars.join(",")) - } - for(var i=0; i 3) { - code.push(processBlock(proc.pre, proc, dtypes)) - } - - //Process body - var body = processBlock(proc.body, proc, dtypes) - var matched = countMatches(loopOrders) - if(matched < dimension) { - code.push(outerFill(matched, loopOrders[0], proc, body)) // TODO: Rather than passing loopOrders[0], it might be interesting to look at passing an order that represents the majority of the arguments for example. - } else { - code.push(innerFill(loopOrders[0], proc, body)) - } - - //Inline epilog - if(proc.post.body.length > 3) { - code.push(processBlock(proc.post, proc, dtypes)) - } - - if(proc.debug) { - console.log("-----Generated cwise routine for ", typesig, ":\n" + code.join("\n") + "\n----------") - } - - var loopName = [(proc.funcName||"unnamed"), "_cwise_loop_", orders[0].join("s"),"m",matched,typeSummary(dtypes)].join("") - var f = new Function(["function ",loopName,"(", arglist.join(","),"){", code.join("\n"),"} return ", loopName].join("")) - return f() +function generateCWiseOp(proc) { + return CACHED_CWiseOp[proc.funcName] } // The function below is called when constructing a cwise function object, and does the following: From 64dc53c48e0fb07c6d5506b2f4a04ab5acda69aa Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 11 Aug 2021 11:52:21 -0400 Subject: [PATCH 06/11] use cached functions for thunk --- fdg.js | 96 +++++++++++++++++++++++++++------------------------------- 1 file changed, 45 insertions(+), 51 deletions(-) diff --git a/fdg.js b/fdg.js index 6c5ae48..0d97d94 100644 --- a/fdg.js +++ b/fdg.js @@ -73,61 +73,55 @@ function generateCWiseOp(proc) { var compile = generateCWiseOp -function createThunk(proc) { - var code = ["'use strict'", "var CACHED={}"] - var vars = [] - var thunkName = proc.funcName + "_cwise_thunk" - - //Build thunk - code.push(["return function ", thunkName, "(", proc.shimArgs.join(","), "){"].join("")) - var typesig = [] - var string_typesig = [] - var proc_args = [["array",proc.arrayArgs[0],".shape.slice(", // Slice shape so that we only retain the shape over which we iterate (which gets passed to the cwise operator as SS). - Math.max(0,proc.arrayBlockIndices[0]),proc.arrayBlockIndices[0]<0?(","+proc.arrayBlockIndices[0]+")"):")"].join("")] - var shapeLengthConditions = [], shapeConditions = [] - // Process array arguments - for(var i=0; i0) { // Gather conditions to check for shape equality (ignoring block indices) - shapeLengthConditions.push("array" + proc.arrayArgs[0] + ".shape.length===array" + j + ".shape.length+" + (Math.abs(proc.arrayBlockIndices[0])-Math.abs(proc.arrayBlockIndices[i]))) - shapeConditions.push("array" + proc.arrayArgs[0] + ".shape[shapeIndex+" + Math.max(0,proc.arrayBlockIndices[0]) + "]===array" + j + ".shape[shapeIndex+" + Math.max(0,proc.arrayBlockIndices[i]) + "]") + +var CACHED_thunk = { + cdiff: function(compile) { + var CACHED = {} + return function cdiff_cwise_thunk(array0, array1, array2) { + if (!(array0.shape.length === array1.shape.length + 0 && array0.shape.length === array2.shape.length + 0)) throw new Error('cwise: Arrays do not all have the same dimensionality!') + for (var shapeIndex = array0.shape.length - 0; shapeIndex-- > 0;) { + if (!(array0.shape[shapeIndex + 0] === array1.shape[shapeIndex + 0] && array0.shape[shapeIndex + 0] === array2.shape[shapeIndex + 0])) throw new Error('cwise: Arrays do not all have the same shape!') + } + var t0 = array0.dtype, r0 = array0.order, t1 = array1.dtype, r1 = array1.order, t2 = array2.dtype, r2 = array2.order, type = [t0, r0.join(), t1, r1.join(), t2, r2.join()].join(), proc = CACHED[type] + if (!proc) { CACHED[type] = proc = compile([t0, r0, t1, r1, t2, r2]) } return proc(array0.shape.slice(0), array0.data, array0.stride, array0.offset | 0, array1.data, array1.stride, array1.offset | 0, array2.data, array2.stride, array2.offset | 0) } - } - // Check for shape equality - if (proc.arrayArgs.length > 1) { - code.push("if (!(" + shapeLengthConditions.join(" && ") + ")) throw new Error('cwise: Arrays do not all have the same dimensionality!')") - code.push("for(var shapeIndex=array" + proc.arrayArgs[0] + ".shape.length-" + Math.abs(proc.arrayBlockIndices[0]) + "; shapeIndex-->0;) {") - code.push("if (!(" + shapeConditions.join(" && ") + ")) throw new Error('cwise: Arrays do not all have the same shape!')") - code.push("}") - } - // Process scalar arguments - for(var i=0; i 0;) { + if (!(array0.shape[shapeIndex + 0] === array1.shape[shapeIndex + 0])) throw new Error('cwise: Arrays do not all have the same shape!') + } + var t0 = array0.dtype, r0 = array0.order, t1 = array1.dtype, r1 = array1.order, type = [t0, r0.join(), t1, r1.join()].join(), proc = CACHED[type] + if (!proc) { CACHED[type] = proc = compile([t0, r0, t1, r1]) } return proc(array0.shape.slice(0), array0.data, array0.stride, array0.offset | 0, array1.data, array1.stride, array1.offset | 0) + } + }, - //Compile thunk - var thunk = new Function("compile", code.join("\n")) + fdTemplate2: function(compile) { + var CACHED = {} + return function fdTemplate2_cwise_thunk(array0, array1, array4) { + if (!(array0.shape.length === array1.shape.length + 0 && array0.shape.length === array4.shape.length + 0)) throw new Error('cwise: Arrays do not all have the same dimensionality!') + for (var shapeIndex = array0.shape.length - 0; shapeIndex-- > 0;) { + if (!(array0.shape[shapeIndex + 0] === array1.shape[shapeIndex + 0] && array0.shape[shapeIndex + 0] === array4.shape[shapeIndex + 0])) throw new Error('cwise: Arrays do not all have the same shape!') + } + var t0 = array0.dtype, r0 = array0.order, t1 = array1.dtype, r1 = array1.order, t4 = array4.dtype, r4 = array4.order, type = [t0, r0.join(), t1, r1.join(), t4, r4.join()].join(), proc = CACHED[type] + if (!proc) { CACHED[type] = proc = compile([t0, r0, t1, r1, t4, r4]) } return proc(array0.shape.slice(0), array0.data, array0.stride, array0.offset | 0, array1.data, array1.stride, array1.offset | 0, array4.data, array4.stride, array4.offset | 0) + } + }, +} + +function createThunk(proc) { + var thunk = CACHED_thunk[proc.funcName] return thunk(compile.bind(undefined, proc)) } From 706bbd218aa6841e32c87fdab47c841d296500ee Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 11 Aug 2021 13:15:32 -0400 Subject: [PATCH 07/11] use cached gradient --- fdg.js | 242 +++++++++++++++++++++++++-------------------------------- 1 file changed, 106 insertions(+), 136 deletions(-) diff --git a/fdg.js b/fdg.js index 0d97d94..3340c2e 100644 --- a/fdg.js +++ b/fdg.js @@ -338,158 +338,128 @@ function generateTemplate(d) { }) } -function generateGradient(boundaryConditions) { - var token = boundaryConditions.join() - var proc = GRADIENT_CACHE[token] - if(proc) { - return proc - } - - var d = boundaryConditions.length - var code = ['function gradient(dst,src){var s=src.shape.slice();' ] - - function handleBoundary(facet) { - var cod = d - facet.length - - var loStr = [] - var hiStr = [] - var pickStr = [] - for(var i=0; i= 0) { - pickStr.push('0') - } else if(facet.indexOf(-(i+1)) >= 0) { - pickStr.push('s['+i+']-1') - } else { - pickStr.push('-1') - loStr.push('1') - hiStr.push('s['+i+']-2') - } +function CACHED_link(diff, zero, grad1, grad2) { + return function(dst, src) { + var s = src.shape.slice() + if (1 && s[0] > 2 && s[1] > 2) { + grad2( + src + .pick(-1, -1) + .lo(1, 1) + .hi(s[0] - 2, s[1] - 2), + dst + .pick(-1, -1, 0) + .lo(1, 1) + .hi(s[0] - 2, s[1] - 2), + dst + .pick(-1, -1, 1) + .lo(1, 1) + .hi(s[0] - 2, s[1] - 2) + ) } - var boundStr = '.lo(' + loStr.join() + ').hi(' + hiStr.join() + ')' - if(loStr.length === 0) { - boundStr = '' + if (1 && s[1] > 2) { + grad1( + src + .pick(0, -1) + .lo(1) + .hi(s[1] - 2), + dst + .pick(0, -1, 1) + .lo(1) + .hi(s[1] - 2) + ) + zero( + dst + .pick(0, -1, 0) + .lo(1) + .hi(s[1] - 2) + ) } - - if(cod > 0) { - code.push('if(1') - for(var i=0; i= 0 || facet.indexOf(-(i+1)) >= 0) { - continue - } - code.push('&&s[', i, ']>2') - } - code.push('){grad', cod, '(src.pick(', pickStr.join(), ')', boundStr) - for(var i=0; i= 0 || facet.indexOf(-(i+1)) >= 0) { - continue - } - code.push(',dst.pick(', pickStr.join(), ',', i, ')', boundStr) - } - code.push(');') + if (1 && s[1] > 2) { + grad1( + src + .pick(s[0] - 1, -1) + .lo(1) + .hi(s[1] - 2), + dst + .pick(s[0] - 1, -1, 1) + .lo(1) + .hi(s[1] - 2) + ) + zero( + dst + .pick(s[0] - 1, -1, 0) + .lo(1) + .hi(s[1] - 2) + ) } - - for(var i=0; i1){dst.set(', - pickStr.join(), ',', bnd, ',0.5*(src.get(', - cPickStr.join(), ')-src.get(', - dPickStr.join(), ')))}else{dst.set(', - pickStr.join(), ',', bnd, ',0)};') - } else { - code.push('if(s[', bnd, ']>1){diff(', outStr, - ',src.pick(', cPickStr.join(), ')', boundStr, - ',src.pick(', dPickStr.join(), ')', boundStr, - ');}else{zero(', outStr, ');};') - } - break - - case 'mirror': - if(cod === 0) { - code.push('dst.set(', pickStr.join(), ',', bnd, ',0);') - } else { - code.push('zero(', outStr, ');') - } - break - - case 'wrap': - var aPickStr = pickStr.slice() - var bPickStr = pickStr.slice() - if(facet[i] < 0) { - aPickStr[bnd] = 's[' + bnd + ']-2' - bPickStr[bnd] = '0' - - } else { - aPickStr[bnd] = 's[' + bnd + ']-1' - bPickStr[bnd] = '1' - } - if(cod === 0) { - code.push('if(s[', bnd, ']>2){dst.set(', - pickStr.join(), ',', bnd, ',0.5*(src.get(', - aPickStr.join(), ')-src.get(', - bPickStr.join(), ')))}else{dst.set(', - pickStr.join(), ',', bnd, ',0)};') - } else { - code.push('if(s[', bnd, ']>2){diff(', outStr, - ',src.pick(', aPickStr.join(), ')', boundStr, - ',src.pick(', bPickStr.join(), ')', boundStr, - ');}else{zero(', outStr, ');};') - } - break - - default: - throw new Error('ndarray-gradient: Invalid boundary condition') - } + if (1 && s[0] > 2) { + grad1( + src + .pick(-1, 0) + .lo(1) + .hi(s[0] - 2), + dst + .pick(-1, 0, 0) + .lo(1) + .hi(s[0] - 2) + ) + zero( + dst + .pick(-1, 0, 1) + .lo(1) + .hi(s[0] - 2) + ) } - - if(cod > 0) { - code.push('};') + if (1 && s[0] > 2) { + grad1( + src + .pick(-1, s[1] - 1) + .lo(1) + .hi(s[0] - 2), + dst + .pick(-1, s[1] - 1, 0) + .lo(1) + .hi(s[0] - 2) + ) + zero( + dst + .pick(-1, s[1] - 1, 1) + .lo(1) + .hi(s[0] - 2) + ) } + dst.set(0, 0, 0, 0) + dst.set(0, 0, 1, 0) + dst.set(s[0] - 1, 0, 0, 0) + dst.set(s[0] - 1, 0, 1, 0) + dst.set(0, s[1] - 1, 0, 0) + dst.set(0, s[1] - 1, 1, 0) + dst.set(s[0] - 1, s[1] - 1, 0, 0) + dst.set(s[0] - 1, s[1] - 1, 1, 0) + return dst } +} - //Enumerate ridges, facets, etc. of hypercube - for(var i=0; i<(1< Date: Wed, 11 Aug 2021 13:24:26 -0400 Subject: [PATCH 08/11] clear extra interface checks --- fdg.js | 97 +++------------------------------------------------------- 1 file changed, 5 insertions(+), 92 deletions(-) diff --git a/fdg.js b/fdg.js index 3340c2e..b716ee4 100644 --- a/fdg.js +++ b/fdg.js @@ -43,37 +43,12 @@ var CACHED_CWiseOp = { } } -//Generates a cwise operator function generateCWiseOp(proc) { return CACHED_CWiseOp[proc.funcName] } -// The function below is called when constructing a cwise function object, and does the following: -// A function object is constructed which accepts as argument a compilation function and returns another function. -// It is this other function that is eventually returned by createThunk, and this function is the one that actually -// checks whether a certain pattern of arguments has already been used before and compiles new loops as needed. -// The compilation passed to the first function object is used for compiling new functions. -// Once this function object is created, it is called with compile as argument, where the first argument of compile -// is bound to "proc" (essentially containing a preprocessed version of the user arguments to cwise). -// So createThunk roughly works like this: -// function createThunk(proc) { -// var thunk = function(compileBound) { -// var CACHED = {} -// return function(arrays and scalars) { -// if (dtype and order of arrays in CACHED) { -// var func = CACHED[dtype and order of arrays] -// } else { -// var func = CACHED[dtype and order of arrays] = compileBound(dtype and order of arrays) -// } -// return func(arrays and scalars) -// } -// } -// return thunk(compile.bind1(proc)) -// } - var compile = generateCWiseOp - var CACHED_thunk = { cdiff: function(compile) { var CACHED = {} @@ -139,7 +114,6 @@ function Procedure() { this.pre = null this.body = null this.post = null - this.debug = false } function compileCwise(user_args) { @@ -161,71 +135,23 @@ function compileCwise(user_args) { proc.arrayArgs.push(i) proc.arrayBlockIndices.push(arg_type.blockIndices ? arg_type.blockIndices : 0) proc.shimArgs.push("array" + i) - if(i < proc.pre.args.length && proc.pre.args[i].count>0) { - throw new Error("cwise: pre() block may not reference array args") - } - if(i < proc.post.args.length && proc.post.args[i].count>0) { - throw new Error("cwise: post() block may not reference array args") - } } else if(arg_type === "scalar") { proc.scalarArgs.push(i) proc.shimArgs.push("scalar" + i) } else if(arg_type === "index") { proc.indexArgs.push(i) - if(i < proc.pre.args.length && proc.pre.args[i].count > 0) { - throw new Error("cwise: pre() block may not reference array index") - } - if(i < proc.body.args.length && proc.body.args[i].lvalue) { - throw new Error("cwise: body() block may not write to array index") - } - if(i < proc.post.args.length && proc.post.args[i].count > 0) { - throw new Error("cwise: post() block may not reference array index") - } } else if(arg_type === "shape") { proc.shapeArgs.push(i) - if(i < proc.pre.args.length && proc.pre.args[i].lvalue) { - throw new Error("cwise: pre() block may not write to array shape") - } - if(i < proc.body.args.length && proc.body.args[i].lvalue) { - throw new Error("cwise: body() block may not write to array shape") - } - if(i < proc.post.args.length && proc.post.args[i].lvalue) { - throw new Error("cwise: post() block may not write to array shape") - } } else if(typeof arg_type === "object" && arg_type.offset) { proc.argTypes[i] = "offset" proc.offsetArgs.push({ array: arg_type.array, offset:arg_type.offset }) proc.offsetArgIndex.push(i) - } else { - throw new Error("cwise: Unknown argument type " + proc_args[i]) } } - //Make sure at least one array argument was specified - if(proc.arrayArgs.length <= 0) { - throw new Error("cwise: No array arguments specified") - } - - //Make sure arguments are correct - if(proc.pre.args.length > proc_args.length) { - throw new Error("cwise: Too many arguments in pre() block") - } - if(proc.body.args.length > proc_args.length) { - throw new Error("cwise: Too many arguments in body() block") - } - if(proc.post.args.length > proc_args.length) { - throw new Error("cwise: Too many arguments in post() block") - } - - //Check debug flag - proc.debug = !!user_args.printCode || !!user_args.debug - //Retrieve name proc.funcName = user_args.funcName || "cwise" - //Read in block size - proc.blockSize = user_args.blockSize || 64 - return createThunk(proc) } @@ -465,24 +391,11 @@ function generateGradient(boundaryConditions) { } function gradient(out, inp, bc) { - if(Array.isArray(bc)) { - if(bc.length !== inp.dimension) { - throw new Error('ndarray-gradient: invalid boundary conditions') - } - } else if(typeof bc === 'string') { - bc = dup(inp.dimension, bc) - } else { - bc = dup(inp.dimension, 'clamp') - } - if(out.dimension !== inp.dimension + 1) { - throw new Error('ndarray-gradient: output dimension must be +1 input dimension') - } - if(out.shape[inp.dimension] !== inp.dimension) { - throw new Error('ndarray-gradient: output shape must match input shape') - } - for(var i=0; i Date: Wed, 11 Aug 2021 13:32:07 -0400 Subject: [PATCH 09/11] reorganize code --- fdg.js | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/fdg.js b/fdg.js index b716ee4..a5596e6 100644 --- a/fdg.js +++ b/fdg.js @@ -1,5 +1,7 @@ 'use strict' +var dup = require('dup') + var CACHED_CWiseOp = { zero: function(SS, a0, t0, p0) { var s0 = SS[0], t0p0 = t0[0] @@ -43,12 +45,6 @@ var CACHED_CWiseOp = { } } -function generateCWiseOp(proc) { - return CACHED_CWiseOp[proc.funcName] -} - -var compile = generateCWiseOp - var CACHED_thunk = { cdiff: function(compile) { var CACHED = {} @@ -100,6 +96,10 @@ function createThunk(proc) { return thunk(compile.bind(undefined, proc)) } +function compile(proc) { + return CACHED_CWiseOp[proc.funcName] +} + function Procedure() { this.argTypes = [] this.shimArgs = [] @@ -116,7 +116,7 @@ function Procedure() { this.post = null } -function compileCwise(user_args) { +function cwiseCompiler(user_args) { //Create procedure var proc = new Procedure() @@ -156,11 +156,6 @@ function compileCwise(user_args) { } -module.exports = gradient - -var dup = require('dup') -var cwiseCompiler = compileCwise - var TEMPLATE_CACHE = {} var GRADIENT_CACHE = {} @@ -390,7 +385,7 @@ function generateGradient(boundaryConditions) { return proc } -function gradient(out, inp, bc) { +module.exports = function gradient(out, inp, bc) { if(!Array.isArray(bc)) { if(typeof bc === 'string') { bc = dup(inp.dimension, bc) From 19b7f9509de3049b9a1c16c821a1e094ccf4701d Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 11 Aug 2021 13:35:08 -0400 Subject: [PATCH 10/11] clear extra condition checks from cached functions --- fdg.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/fdg.js b/fdg.js index a5596e6..6fb1e83 100644 --- a/fdg.js +++ b/fdg.js @@ -49,10 +49,6 @@ var CACHED_thunk = { cdiff: function(compile) { var CACHED = {} return function cdiff_cwise_thunk(array0, array1, array2) { - if (!(array0.shape.length === array1.shape.length + 0 && array0.shape.length === array2.shape.length + 0)) throw new Error('cwise: Arrays do not all have the same dimensionality!') - for (var shapeIndex = array0.shape.length - 0; shapeIndex-- > 0;) { - if (!(array0.shape[shapeIndex + 0] === array1.shape[shapeIndex + 0] && array0.shape[shapeIndex + 0] === array2.shape[shapeIndex + 0])) throw new Error('cwise: Arrays do not all have the same shape!') - } var t0 = array0.dtype, r0 = array0.order, t1 = array1.dtype, r1 = array1.order, t2 = array2.dtype, r2 = array2.order, type = [t0, r0.join(), t1, r1.join(), t2, r2.join()].join(), proc = CACHED[type] if (!proc) { CACHED[type] = proc = compile([t0, r0, t1, r1, t2, r2]) } return proc(array0.shape.slice(0), array0.data, array0.stride, array0.offset | 0, array1.data, array1.stride, array1.offset | 0, array2.data, array2.stride, array2.offset | 0) } @@ -69,10 +65,6 @@ var CACHED_thunk = { fdTemplate1: function(compile) { var CACHED = {} return function fdTemplate1_cwise_thunk(array0, array1) { - if (!(array0.shape.length === array1.shape.length + 0)) throw new Error('cwise: Arrays do not all have the same dimensionality!') - for (var shapeIndex = array0.shape.length - 0; shapeIndex-- > 0;) { - if (!(array0.shape[shapeIndex + 0] === array1.shape[shapeIndex + 0])) throw new Error('cwise: Arrays do not all have the same shape!') - } var t0 = array0.dtype, r0 = array0.order, t1 = array1.dtype, r1 = array1.order, type = [t0, r0.join(), t1, r1.join()].join(), proc = CACHED[type] if (!proc) { CACHED[type] = proc = compile([t0, r0, t1, r1]) } return proc(array0.shape.slice(0), array0.data, array0.stride, array0.offset | 0, array1.data, array1.stride, array1.offset | 0) } @@ -81,10 +73,6 @@ var CACHED_thunk = { fdTemplate2: function(compile) { var CACHED = {} return function fdTemplate2_cwise_thunk(array0, array1, array4) { - if (!(array0.shape.length === array1.shape.length + 0 && array0.shape.length === array4.shape.length + 0)) throw new Error('cwise: Arrays do not all have the same dimensionality!') - for (var shapeIndex = array0.shape.length - 0; shapeIndex-- > 0;) { - if (!(array0.shape[shapeIndex + 0] === array1.shape[shapeIndex + 0] && array0.shape[shapeIndex + 0] === array4.shape[shapeIndex + 0])) throw new Error('cwise: Arrays do not all have the same shape!') - } var t0 = array0.dtype, r0 = array0.order, t1 = array1.dtype, r1 = array1.order, t4 = array4.dtype, r4 = array4.order, type = [t0, r0.join(), t1, r1.join(), t4, r4.join()].join(), proc = CACHED[type] if (!proc) { CACHED[type] = proc = compile([t0, r0, t1, r1, t4, r4]) } return proc(array0.shape.slice(0), array0.data, array0.stride, array0.offset | 0, array1.data, array1.stride, array1.offset | 0, array4.data, array4.stride, array4.offset | 0) } From 28f575c2719eed56c48e29df8bf318e1111ef082 Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 12 Aug 2021 14:17:00 -0400 Subject: [PATCH 11/11] simplify more --- fdg.js | 131 ++------------------------------------------------------- 1 file changed, 3 insertions(+), 128 deletions(-) diff --git a/fdg.js b/fdg.js index 6fb1e83..69e2ce5 100644 --- a/fdg.js +++ b/fdg.js @@ -88,59 +88,10 @@ function compile(proc) { return CACHED_CWiseOp[proc.funcName] } -function Procedure() { - this.argTypes = [] - this.shimArgs = [] - this.arrayArgs = [] - this.arrayBlockIndices = [] - this.scalarArgs = [] - this.offsetArgs = [] - this.offsetArgIndex = [] - this.indexArgs = [] - this.shapeArgs = [] - this.funcName = "" - this.pre = null - this.body = null - this.post = null -} - function cwiseCompiler(user_args) { - //Create procedure - var proc = new Procedure() - - //Parse blocks - proc.pre = user_args.pre - proc.body = user_args.body - proc.post = user_args.post - - //Parse arguments - var proc_args = user_args.args.slice(0) - proc.argTypes = proc_args - for(var i=0; i