Skip to content

Instantly share code, notes, and snippets.

@lefticus
Last active July 13, 2021 15:01
Show Gist options
  • Save lefticus/21a5b3bcfc3e95e16f89 to your computer and use it in GitHub Desktop.
Save lefticus/21a5b3bcfc3e95e16f89 to your computer and use it in GitHub Desktop.
Immediately-invoked Function Expressions in C++
#include <chrono>
#include <string>
#include <sstream>
#include <vector>
#include <iostream>
std::string to_string(const int i)
{
std::stringstream ss;
ss << i;
return ss.str();
}
std::vector<std::vector<std::string>> classic(const int num_vecs, const int vec_size)
{
std::vector<std::vector<std::string>> retval;
for (int i = 0; i < num_vecs; ++i)
{
std::vector<std::string> nextvec(vec_size);
for (int j = 0; j < vec_size; ++j)
{
nextvec[j] = "Some string that's a little bit longer than a short string ";
// plus whatever else needs to happen
}
retval.push_back(nextvec);
// do some other house keeping here
}
return retval;
}
std::vector<std::vector<std::string>> moved(const int num_vecs, const int vec_size)
{
std::vector<std::vector<std::string>> retval;
for (int i = 0; i < num_vecs; ++i)
{
std::vector<std::string> nextvec(vec_size);
for (int j = 0; j < vec_size; ++j)
{
nextvec[j] = "Some string that's a little bit longer than a short string ";
// plus whatever else needs to happen
}
retval.push_back(std::move(nextvec)); // this version requires extra bookkeeping to get the performance
// do some other house keeping here
// but we might be tempted to use nextvec, which is now is some unknown state
}
return retval;
}
std::vector<std::string> build_vector(const int vec_size)
{
std::vector<std::string> nextvec(vec_size);
for (int j = 0; j < vec_size; ++j)
{
nextvec[j] = "Some string that's a little bit longer than a short string ";
// plus whatever else needs to happen
}
return nextvec;
}
std::vector<std::vector<std::string>> function_call(const int num_vecs, const int vec_size)
{
std::vector<std::vector<std::string>> retval;
for (int i = 0; i < num_vecs; ++i)
{
retval.push_back(build_vector(vec_size));
}
return retval;
}
std::vector<std::vector<std::string>> iife(const int num_vecs, const int vec_size)
{
std::vector<std::vector<std::string>> retval;
for (int i = 0; i < num_vecs; ++i)
{
retval.push_back([vec_size](){
std::vector<std::string> nextvec(vec_size);
for (int j = 0; j < vec_size; ++j)
{
nextvec[j] = "Some string that's a little bit longer than a short string ";
// plus whatever else needs to happen
}
return nextvec;
}());
// no extra bookkeeping
// no temptation to use the moved value
// no pollution of the local namespace with a 'bad' variable
}
return retval;
}
std::vector<std::vector<std::string>> smarter(const int num_vecs, const int vec_size)
{
return std::vector<std::vector<std::string>>(num_vecs, std::vector<std::string>(vec_size, "Some string that's a little bit longer than a short string "));
}
int main()
{
const int num_vecs = 1;
const int vec_size = 10000000;
auto start_time = std::chrono::steady_clock::now();
classic(num_vecs, vec_size);
auto classic_time = std::chrono::steady_clock::now();
moved(num_vecs, vec_size);
auto moved_time = std::chrono::steady_clock::now();
function_call(num_vecs, vec_size);
auto function_time = std::chrono::steady_clock::now();
iife(num_vecs, vec_size);
auto iife_time = std::chrono::steady_clock::now();
smarter(num_vecs, vec_size);
auto smarter_time = std::chrono::steady_clock::now();
std::cout << "Classic: " << std::chrono::duration_cast<std::chrono::microseconds>(classic_time - start_time).count() << "us\n";
std::cout << "Moved: " << std::chrono::duration_cast<std::chrono::microseconds>(moved_time - classic_time).count() << "us\n";
std::cout << "Function: " << std::chrono::duration_cast<std::chrono::microseconds>(function_time - moved_time).count() << "us\n";
std::cout << "IIFE: " << std::chrono::duration_cast<std::chrono::microseconds>(iife_time - function_time).count() << "us\n";
std::cout << "Smarter: " << std::chrono::duration_cast<std::chrono::microseconds>(smarter_time - iife_time).count() << "us\n";
}
@lefticus
Copy link
Author

Output:

jason@jason-VirtualBox:~$ clang++-3.5 ./iife.cpp -std=c++11 -O3
jason@jason-VirtualBox:~$ ./a.out 
Classic:  3106445ns
Moved:    1363530ns
Function: 1015524ns
IIFE:     1009592ns
Smarter:  258654ns

@mdprice
Copy link

mdprice commented Mar 2, 2017

So I was watching your CppCon2016 talk and you mentioned the IIFE idiom there that I'd never seen before. So I tried it and it's pretty cool. Then I googled it and I ran accross this gist and I noticed 2 quick things that I always default to when working with vectors. First off I would do a reserve of the main retval vector to make sure you avoid any copying since you know the size before you push anything. Secondly, inside the function you fill the vector with default constructed strings. Then you copy asign the const char* to that string. Wouldn't that be better to create an empty vector, reserve and then emplace_back on it. That way you directly construct each element with what you want. And of course like you said in your talk: "Don't do more work then you half to". I would try the following code:

std::vector<std::vector<std::string>> iife_with_reserve(const int num_vecs, const int vec_size)
{
    std::vector<std::vector<std::string>> retval;
    retval.reserve(num_vecs);

    for (int i = 0; i < num_vecs; ++i)
    {
        retval.push_back([vec_size]() {
            std::vector<std::string> nextvec;
            nextvec.reserve(vec_size);
            for (int j = 0; j < vec_size; ++j)
            {
                nextvec.emplace_back("Some string that's a little bit longer than a short string ");
                // plus whatever else needs to happen
            }
            return nextvec;
        }());
    }

    return retval;
}

Another thing to try instead of emplace_back with a const char* is to create one std::string and then copy that. The issue is that each time you insert a string into the vector it has to count the size of it. I wonder how much of the speed gains the last one gets comes from not doing that the whole time. My point is you are needlessly doing a conversion from const char* to string that has a non-zero cost, mainly the strlen calculation that you could eliminate by doing the conversion once and then copying that.

I like the IIFE technique and will certainly be using that in my coding going forward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment