Last active
November 14, 2023 00:28
-
-
Save jamshark70/9e0abd16c7de8c75200f749d91ed77c1 to your computer and use it in GitHub Desktop.
Experimental UGenCache to optimize repeated math ops
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// hjh: cache repeated math ops | |
// todo: `isPure` is incomplete (many deterministic ops are not currently labeled as pure) | |
UGenCache { | |
classvar <>cache; | |
classvar <>optimize = false; // default: use normal SC behavior | |
*clear { | |
cache = MultiLevelIdentityDictionary.new; | |
} | |
*at { |... args| | |
var test; | |
^if(optimize) { | |
test = cache.at(*args); | |
// cache hit only if 'args' points to a legit UGen | |
// if it's a subtree, cache miss | |
if(test.isUGen) { test } // else nil | |
} // else nil | |
} | |
*put { |... args| | |
if(optimize) { cache.put(*args) } | |
} | |
} | |
+ SynthDef { | |
initBuild { | |
UGen.buildSynthDef = this; | |
constants = Dictionary.new; | |
constantSet = Set.new; | |
controls = nil; | |
controlIndex = 0; | |
maxLocalBufs = nil; | |
// experimental | |
UGenCache.clear; | |
} | |
finishBuild { | |
this.addCopiesIfNeeded; | |
this.optimizeGraph; | |
this.collectConstants; | |
this.checkInputs;// will die on error | |
// re-sort graph. reindex. | |
this.topologicalSort; | |
this.indexUGens; | |
UGen.buildSynthDef = nil; | |
// experimental | |
UGenCache.clear; | |
} | |
} | |
+ UGen { | |
isPure { ^false } // deal with other ugens later | |
// notes: | |
// a. Rate is already args[0] | |
// b. Operator is already args[1] for op ugens | |
// therefore the UGen class ++ args should uniquely identify pure ops | |
*multiNewList { arg args; | |
var size = 0, newArgs, results, new, cacheKeys; | |
args = args.asUGenInput(this); | |
args.do({ arg item; | |
(item.class == Array).if({ size = max(size, item.size) }); | |
}); | |
if (size == 0) { | |
^this.new1FromCache(args); | |
}; | |
newArgs = Array.newClear(args.size); | |
results = Array.newClear(size); | |
size.do({ arg i; | |
args.do({ arg item, j; | |
newArgs.put(j, if (item.class == Array, { item.wrapAt(i) },{ item })); | |
}); | |
// needs more testing | |
// in theory this recursive call will eventually check cache | |
// against concrete values or UGens (but not subarrays) | |
results.put(i, this.multiNewList(newArgs)); | |
}); | |
^results | |
} | |
*new1FromCache { |args| | |
var cacheKeys, new, cached; | |
cacheKeys = [this] ++ args; | |
new = this.new1( *args ); | |
// yeah, bummer that I have to make the instance | |
// in order to know if I need or don't need it | |
if(new.isPure) { | |
cached = UGenCache.at(*cacheKeys); | |
if(cached.notNil) { | |
new = cached // pure && in-cache, use cache | |
} { | |
// pure && not in-cache, save in cache | |
UGenCache.put(*(cacheKeys ++ new)); | |
}; | |
}; // not pure, don't cache at all | |
^new | |
} | |
} | |
+ UnaryOpUGen { | |
isPure { | |
^#['rand', 'rand2', 'linrand', 'bilinrand', 'sum3rand'] | |
.includes(operator).not | |
} | |
} | |
+ BinaryOpUGen { | |
isPure { | |
^#['rrand', 'exprand'] | |
.includes(operator).not | |
} | |
} | |
+ Collection { | |
isPure { | |
^this.every(_.isPure) | |
} | |
} | |
+ Number { | |
isPure { ^true } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment