Uma foto minha

Measuring a function execution time using a higher-order function in C++


These days I have being reading some contents about functional programming style using modern C++. In order to exercise it, I thought about creating a higher-order function that receives a function with no parameters (I'll dive deeper at this decision later), and measures how much time it takes to execute this function.

But first, what is a higher-order function?

In mathematics and computer science, a higher-order function (HOF) is a function that does at least one of the following:

  • takes one or more functions as arguments (i.e. a procedural parameter, which is a parameter of a procedure that is itself a procedure),
  • returns a function or value as its result.

ā€” From Wikipedia link.

This Wikipedia article mentioned before also presents examples of higher-order functions using multiple languages, including C++. Check it out if you're interested to compare other languages syntax.

In order to make this function generic, and work with any function that receives no parameter, I decided to use a template. Check the code below:

#include <chrono>
#include <functional>
#include <iostream>
#include <vector>

using std::chrono::duration;
using std::chrono::high_resolution_clock;
using nanoseconds = duration<double, std::nano>;

// ...

template <typename T> std::vector<float> run_and_measure_time(T func) {
  const auto t_begin = high_resolution_clock::now();
  const auto exec_result = func();
  const auto t_end = high_resolution_clock::now();

  const nanoseconds t_diff = t_end - t_begin;
  std::cout << "Time: " << t_diff.count() << "ns\n";

  return exec_result;
}

*Something that could be improved later is the return type, which is statically defined as std::vector, that works for my example scenario, but would be better to be more generic.

Now you must be wondering, how would you use this helper higher-order function in real life? Most useful (and pure) functions receive arguments to work.

Well, you can encapsulate this target function (func in the example) in a closure that stores the function arguments, although defers its execution to the future. This is something that we do a lot in functional programming using either closures, as mentioned before, or leveraging partial application if the language supports it.

For C++, you can create closures using lambda expressions, or you can use partial application using std::bind, or just creating a curied version of the target function (I have an example at the repository code that I mention later). For example:

// Most code was removed to reduce confunsion
// ...

int main() {
  // ...


  // Closure with lambda expression
  auto res1 = run_and_measure_time([&]() {
    return NumpyCpp::linspace(start[i], end[i], points[i], endpoint[i]);
  });

  // Partial application with curied function
  auto res2 = run_and_measure_time([&]() {
    return NumpyCpp::linspace_curied(start[i])(end[i])(points[i])(endpoint[i]);
  });

  // Partial application with std::bind
  auto p3 = std::bind(NumpyCpp::linspace, start[i], end[i], points[i],
                        std::placeholders::_1);
  auto res3 = run_and_measure_time([&]() { return p3(endpoint[i]); });

  return 0;
}

The complete code for this project can be found at this repository on GitHub:

The curied version of the NumpyCpp::linspace mentioned before, which is called NumpyCpp::linspace_curied is presented there, so check it out if you're interested.