Skip to content

Instantly share code, notes, and snippets.

@jamshark70
Last active November 14, 2023 00:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamshark70/9e0abd16c7de8c75200f749d91ed77c1 to your computer and use it in GitHub Desktop.
Save jamshark70/9e0abd16c7de8c75200f749d91ed77c1 to your computer and use it in GitHub Desktop.
Experimental UGenCache to optimize repeated math ops
// 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