PoC: Cached function calls
Chris Nicholson-Sauls
ibisbasenji at gmail.com
Fri Dec 15 22:11:09 PST 2006
A discussion on this group caused me to add a new feature to a hypothetical scripting
language I've been toying around with for a couple months or so. It looks, basically,
like this:
# cache Int fibidx (Int n) {
# if (n < 2) return n;
# else return fibidx(n - 1) + fibidx(n - 2);
# }
The nifty feature is the keyword 'cache' up there, which causes the function 'fibidx' to
be evaluated /exactly once/ for a given parameter list. (Granted in the case of Fibonacci
one can easily accomplish what I'm talking about with a class, but I thought it'd make a
simple demonstration case.)
So I got to thinking... wouldn't it be nifty to have this, or something like it, in D!
With that in mind, I tried to think up a way it might be accomplished -- I hadn't yet
actually tried to do anything with any of D's nifty new templating features, namely
tuples. Well, I've learned a few things anyhow. :) The following actually works!
# import std .traits ;
#
# struct TCachedFunc (alias Func) { static:
#
# alias ReturnType !(Func) Ret ;
# alias ParameterTypeTuple !(Func) Params ;
#
# static if (is(typeof(Params[1]))) {
# // multiple parameters
#
# private struct Node {
# Params params ;
# }
#
# private Ret[Node] p_cache ;
#
# Ret opCall (Params args) {
# Node node ;
# Ret* result ;
#
# foreach (i, x; args) {
# node.params[i] = x;
# }
# result = node in p_cache;
#
# if (!result) {
# p_cache[node] = Func(args);
# result = node in p_cache;
# }
# return *result;
# }
# }
# else {
# // single parameter
#
# alias Params[0] Param ;
#
# private Ret[Param] p_cache ;
#
# Ret opCall (Param arg) {
# Ret* result = arg in p_cache;
#
# if (!result) {
# p_cache[arg] = Func(arg);
# result = arg in p_cache;
# }
# return *result;
# }
# }
# }
Given a 'fibidx' D function like the one above, one may use this template to create a
cached version by simply aliasing the template. For example:
# alias TCachedFunc!(fibidx) fibidx_cached;
Then just call it like normal. I timed such a function as a means of testing the
template. The results follow (in order: normal function call, initial use of cache, a
second use of the cache):
<Benchmark TCachedFunc> Baseline 17.520000
<Benchmark TCachedFunc> Time 16.810000 & 1.042237 versus baseline
<Benchmark TCachedFunc> Time 0.000000 & inf versus baseline
Just, wow. That said, it really only exhibits any benefits for functions of some
reasonable complexity, or with deep recursion that eats up cycles (like a fib function
;)). Anything that would normally be inlined by the compiler will definitely perform
better with a normal call.
Anyhow, I just thought someone might find it interesting. Maybe even useful. I'm
considering it for inclusion in Cashew, once I figure out how to properly do this with a
delegate as well.
-- Chris Nicholson-Sauls
More information about the Digitalmars-d
mailing list