May 24, 2016

std::function - to bind or not to bind?

In my previous post about delegates I wrote how to bind a member function to std::function. There was a lot of template magic involved in order to pass the correct number of placeholders. Shortly after I published I received a lot of feedback and I’m very grateful for this. One advice was not to use std::bind() at all.

I’ll paste directly the code from one of the comments and will make some time measurements:

template <class T, class R, class ... Args>
auto make_function(T t, R(T::*mem_fn)(Args...))
{
  return [t, mem_fn] (Args ... args) mutable -> R { return ((t).*(mem_fn))(args...); };
}

As you can see - it’s usual lambda that can be converted to std::function.

My first thoughts were that lambda should be slower - we have a function call and a lambda call over it. To be honest, I don’t know how std::bind works, maybe it also creates some wrappers? Anyway, let’s benchmark:

struct S
{
	int foo(int a, int b)
	{
		return a + b;
	}
};

int main()
{
	const uint32_t NUM_TESTS{ 10 };
	const uint32_t NUM_ITERATIONS{ 100000000 };

	volatile int res{ 0 };

	S s;
	function<int(int, int)> f{ bind(&S::foo, &s, placeholders::_1, placeholders::_2) }; // passing an object's pointer as I did in previous article

	{
		uint64_t t{ 0 };
		for (int i{ 0 }; i < NUM_TESTS; ++i)
		{
			auto start = chrono::high_resolution_clock::now();
			for (int j{ 0 }; j < NUM_ITERATIONS; ++j)
			{
				res += f(5, 10);
			}
			auto end = chrono::high_resolution_clock::now();
			auto time = chrono::duration_cast<chrono::milliseconds>(end - start);
			t += time.count();
		}

		cout << t / NUM_TESTS << "\n";
	}

	auto* ptr = &s;
	auto fPtr = &S::foo;
	function<int(int, int)> f2 = [ptr, fPtr](int a, int b){ return ((ptr)->*(fPtr))(a, b); }; // capture pointers by copy
	
	res = 0;
	{
		uint64_t t{ 0 };
		for (int i{ 0 }; i < NUM_TESTS; ++i)
		{
			auto start = chrono::high_resolution_clock::now();
			for (int j{ 0 }; j < NUM_ITERATIONS; ++j)
			{
				res += f2(5, 10);
			}
			auto end = chrono::high_resolution_clock::now();
			auto time = chrono::duration_cast<chrono::milliseconds>(end - start);
			t += time.count();
		}

		cout << t / NUM_TESTS << "\n";
	}

	cin.get();

	return 0;
}

On my machine I have around 400ms for both std::bind() and lambda. Not bad. My previous code can be simplified dramatically now - there’s no need in std::bind() and accompanying template tricks.

If you like what I do you can buy me a coffee © nikitablack 2021

Powered by Hugo & Kiss.