The hidden cost of std::ranges


At work, we have recently adopted some commercial static checker to hunt bugs in our codebase. The tool does not only identify bugs and security vulnerabilities. It is also advising you on opportunities to modernize your code to use the latest and greatest of the C++ language and of its standard library.

One of the suggestion the tool made was to replace std::find_if() calls with the more modern std::ranges::find_if(). And so we did. And then some of our unit tests would suddenly become slower for no apparent reason. After all, we were only changing something like:

auto pos = std::find_if(objects.begin(), objects.end(),
        [key](const auto& obj) { return obj.id == key; });
Langage du code : PHP (php)

with:

auto pos = std::ranges::find_if(objects,
        [key](const auto& obj) { return obj.id == key; });Langage du code : PHP (php)

It is undoubtedly easier to read. But what is not immediately apparent is that range algorithms have an extra parameter called projection. This is a function that allows to transform each of the visited object before applying the predicate. The default projection is std::identity<T>. But still, I means that every object passes through that projection.

When the optimizer is used, this call to std::identity is eliminated. But in our debug builds, used for the unit tests, we don’t optimize the code and so the cost of that call became apparent.

One can take benefit of the projection and replace the calls above with:

auto pos = std::ranges::find(objects, key, &Object::id);Langage du code : PHP (php)

The projection can also be a lambda (eg. if the key is not directly in the objects that are visited).

Although godbolt.org is probably not the best choice to run a benchmark, here is a comparison of the 3 methods (searching 10.000 elements in a vector of 100.000 objects).

Share

,

  1. Pas encore de commentaire.
(ne sera pas publié)


Les commentaires sont fermés.