I have a vector of integer. I apply a filter and transform on this vector and returns it. And user call process function on each element of returned vector.
I tried to use std::ranges to optimize away creation of this vector and instead return a view.
Original code:
auto getData() {
std::vector<T2> v;
for (T1 p: myData) {
if (filter(p))
v.push_back(transform(p));
}
return v;
}
Modified to return std::ranges
auto dataView() {
return myData | ranges::views::filter(filter) | ranges::views::transform([this](auto t) { return transform(t); });
}
I expected this to perform much better than original code but benchmarks doesnt show any benefit.
Then I modified to use return a lambda which taken a callable and use it. This outperforms original code by 7X.
auto getDataCallback() {
return [this](auto func) {
for (T1 p: myData) {
if (filter(p))
func(transform(p));
}
};
}
Here is benchmark link: https://quick-bench.com/q/paTAkYuj-Ec9jp7Yi_9egR3pHt8
I expect almost same runtime for 2nd and 3rd solution but not sure if i am doing anything wrong here.
Here is complete code:
#include <ranges>
#include <algorithm>
static inline constexpr size_t million = 1000; //1000000;
static inline size_t sum = 0;
using T1 = int;
using T2 = std::string;
bool filter(T1 t) { return 0 == t % 2; }
void process(const T2 &p) { sum += p.size(); };
T2 transform(T1 x) { return std::to_string(x); }
static inline std::vector<T1> data;
namespace Conventional {
class MyClass {
std::vector<T1>& myData = data;
T2 transform(T1 x) { return std::to_string(x); }
public:
auto getData() {
std::vector<T2> v;
for (T1 p: myData) {
if (filter(p))
v.push_back(transform(p));
}
return v;
}
};
void myFunc() {
sum = 0;
MyClass obj;
auto data = obj.getData();
std::for_each(data.begin(), data.end(), process);
}
};
namespace LazyCpp17 {
class MyClass {
std::vector<T1>& myData = data;
T2 transform(T1 x) { return std::to_string(x); }
public:
auto getDataCallback() {
return [this](auto func) {
for (T1 p: myData) {
if (filter(p))
func(transform(p));
}
};
}
};
void myFunc() {
sum = 0;
MyClass obj;
obj.getDataCallback()(process);
}
};
namespace LazyCpp20 {
class MyClass {
std::vector<T1>& myData = data;
T2 transform(T1 x) { return std::to_string(x); }
public:
auto dataView() {
return myData | std::views::filter(filter) | std::views::transform([this](auto t) { return transform(t); });
}
};
void myFunc() {
sum = 0;
MyClass obj;
std::ranges::for_each(obj.dataView(), process);
}
};
static void ConventionalB(benchmark::State& state) {
// Code inside this loop is measured repeatedly
for (auto _ : state) {
Conventional::myFunc();
// Make sure the variable is not optimized away by compiler
}
}
// Register the function as a benchmark
BENCHMARK(ConventionalB);
static void LazyCpp17B(benchmark::State& state) {
// Code before the loop is not measured
std::string x = "hello";
for (auto _ : state) {
LazyCpp17::myFunc();
}
}
BENCHMARK(LazyCpp17B);
static void LazyCpp20B(benchmark::State& state) {
// Code before the loop is not measured
std::string x = "hello";
for (auto _ : state) {
LazyCpp20::myFunc();
}
}
BENCHMARK(LazyCpp20B);