Martin Ueding
Martin Ueding

Reputation: 8727

Associate a integer (literal) with functions, let caller query number of FLOPs

I work on a high performance code where we have BLAS functions, stencil operators and iterative solvers. For performance measurement, we use our hand-calculated number of “useful” flops and divide by the time the algorithm needed. This gives use the “useful” performance.

The stencils are classes, so I could add a virtual int get_flops() const member function. In the iterative solvers I would just add up the flops from the various stencil calls.

The BLAS routines are functions, however. They are of like void copyVector(V a, const V b). I'd like to associate the integer 0 to this function. Using the return value seems like a quick fix, but I might want to add another annotation later on.

One option would be turning those into singleton classes with an operator(), but this feels like a violation of the open-closed principle (OCP). Is there something like type traits that I can do for functions?

Upvotes: 0

Views: 71

Answers (3)

W.F.
W.F.

Reputation: 13988

I'd probably do something more like:

#include <type_traits>
#include <iostream>

struct dummy { };

template <class T>
struct flop_map {
    static int value;
};

template <class T>
int flop_map<T>::value;

#define FOO_ID(foo) std::integral_constant<decltype(&foo), &foo>

void blas_routine(double *const, double const *const) {}
void other(int, int) {}

int main() {
    flop_map<FOO_ID(blas_routine)>::value = 0;
    flop_map<FOO_ID(other)>::value        = 4;

    std::cout << flop_map<FOO_ID(blas_routine)>::value << std::endl;
    std::cout << flop_map<FOO_ID(other)>::value << std::endl;
}

[live demo]

Upvotes: 1

Martin Ueding
Martin Ueding

Reputation: 8727

Based on Radosław Cybulski's answer, I have expanded this to a full working version:

#include <iostream>
#include <unordered_map>

template <typename Annotation>
class FunctionAnnotation {
  public:
    template <typename R, typename... Args>
    Annotation get(R (*func)(Args...)) {
        return map_.at(strip(func));
    }

    template <typename R, typename... Args>
    void set(R (*func)(Args...), Annotation annotation) {
        map_[strip(func)] = annotation;
    }

  private:
    // Basic function pointer type.
    typedef void (*VoidFunc)();

    // Type stripping function that will take a function pointer and return a
    // function with the wrong but unified type.
    template <typename R, typename... Args>
    VoidFunc strip(R (*func)(Args...)) {
        return reinterpret_cast<VoidFunc>(func);
    }

    // The map with annotation values.
    std::unordered_map<VoidFunc, Annotation> map_;
};

FunctionAnnotation<int> flop_map;

// Some example functions.
void blas_routine(double *const dest, double const *const src) {}
void other(int a, int b) {}

int main() {
    // Populate that map.
    flop_map.set(blas_routine, 0);
    flop_map.set(other, 4);

    // Later on use that.
    std::cout << flop_map.get(blas_routine) << std::endl;
    std::cout << flop_map.get(other) << std::endl;
}

This compiles cleanly with GCC in C++11 and C++14 modes. The output is, as expected:

0
4

Upvotes: 0

Radosław Cybulski
Radosław Cybulski

Reputation: 2992

You can create global unordered_map and associate integer with function pointer. Something like this:

std::unordered_map<void(*)(void), int> tmp;

As far as i remember standard guarantees, that function pointers for all types have the same size and since you just use function pointer to index into map, you can cast any function pointer (not pointer to member tho) to void (*)(void) and set / retrieve from unordered_map.

Upvotes: 0

Related Questions