Lazy evaluation for subset of class methods
Asked Answered
S

1

5

I'm looking to make a general, lazy evaluation-esque procedure to streamline my code.

Right now, I have the ability to speed up the execution of mathematical functions - provided that I pre-process it by calling another method first. More concretely, given a function of the type:

const Eigen::MatrixXd<double, -1, -1> function_name(const Eigen::MatrixXd<double, -1, -1>& input)

I can pass this into another function, g, which will produce a new version of function_name g_p, which can be executed faster.

I would like to abstract all this busy-work away from the end-user. Ideally, I'd like to make a class such that when any function f matching function_name's method signature is called on any input (say, x), the following happens instead:

  1. The class checks if f has been called before.
  2. If it hasn't, it calls g(f), followed by g_p(x).
  3. If it has, it just calls g_p(x)

This is tricky for two reasons. The first, is I don't know how to get a reference to the current method, or if that's even possible, and pass it to g. There might be a way around this, but passing one function to the other would be simplest/cleanest for me.

The second bigger issue is how to force the calls to g. I have read about the execute around pattern, which almost works for this purpose - except that, unless I'm understanding it wrong, it would be impossible to reference f in the surrounding function calls.

Is there any way to cleanly implement my dream class? I ideally want to eventually generalize beyond the type of function_name (perhaps with templates), but can take this one step at a time. I am also open to other solution to get the same functionality.

Subsellium answered 13/7, 2018 at 1:50 Comment(7)
I don't think I fully understand what you are doing. Is g a function, which takes a function pointer and returns another function pointer? How/where/when do you "produce a new version" of a function?Ulcer
That's right. g is a function which takes a pointer to f, and produces a new optimized version of f, g_p, which is then stored somewhere. Perhaps I should have called g_p f_p for clarity.Subsellium
I think you need to give us an example of what you're talking about (and then ping me). Thanks.Internationalism
As presented the obvious question is 'What benefit does lazy-evaluation give in in this case?' However, my gut-feeling is that you trying to implement something similar to the old C++-library newmat robertnz.net/nm_intro.htm i.e. you want to delay evaluating f so that in some cases g(f(x)) is replaced by an optimized g_and_f(x) instead; as an efficient replacement of g(f(x)) and the lazy-evaluation is intended to solve that. IF that is the case there might be better solutions.Bostwick
How do you want the user to use your class? Is obj->invoke(f, x) acceptable?Eger
@Eger obj->invoke(f, x) is what I have right now, and it's suboptimal, since the entire class would have to be encoded this way.Subsellium
@HansOlsson That is pretty similar to what I want to do, but I'm not really sure how newmat is related to this. Can you explain in detail?Subsellium
U
7

I don't think a "perfect" solution is possible in C++, for the following reasons.

If the calling site says:

result = object->f(x);

as compiled this will call into the unoptimized version. At this point you're pretty much hamstrung, since there's no way in C++ to change where a function call goes, that's determined at compile-time for static linkage, and at runtime via vtable lookup for virtual (dynamic) linkage. Whatever the case, it's not something you can directly alter. Other languages do allow this, e.g. Lua, and rather ironically C++'s great-grandfather BCPL also permits it. However C++ doesn't.

TL;DR to get a workable solution to this, you need to modify either the called function, or every calling site that uses one of these.

Long answer: you'll need to do one of two things. You can either offload the problem to the called class and make all functions look something like this:

const <return_type> myclass:f(x)
{
    static auto unoptimized = [](x) -> <return_type>
    {
        // Do the optimizable heavy lifting here;
        return whatever;
    };
    static auto optimized = g(unoptimized);
    return optimized(x);
}

However I very strongly suspect this is exactly what you don't want to do, because assuming the end-user you're talking about is the author of the class, this fails your requirement to offload this from the end-user.

However, you can also solve it by using a template, but that requires modification to every place you call one of these. In essence you encapsulate the above logic in a template function, replacing unoptimized with the bare class member, and leaving most everything else alone. Then you just call the template function at the calling site, and it should work.

This does have the advantage of a relatively small change at the calling site:

result = object->f(x);

becomes either:

result = optimize(object->f, x);

or:

result = optimize(object->f)(x);

depending on how you set the optimize template up. It also has the advantage of no changes at all to the class.

So I guess it comes down to where you wan't to make the changes.

Yet another choice. Would it be an option to take the class as authored by the end user, and pass the cpp and h files through a custom pre-processor? That could go through the class and automatically make the changes outlined above, which then yields the advantage of no change needed at the calling site.

Unscreened answered 13/7, 2018 at 2:45 Comment(1)
The custom-preprocessor is going to be too painful for my application, and won't scale. The first option is too much user overhead. The second version is pretty similar to what I have right now. The main problem with this is that if my optimized version of f calls another function, there's no way to enforce that it's calling the optimized version of that other function.Subsellium

© 2022 - 2024 — McMap. All rights reserved.