In the previous post I have compared two alternative ways of transforming a string to upper case, both using std::transform : one that modifies an existing string and one that generates a new one by inserting at the end using std::back_inserter . For the second alternative I have presented two implementations, one that does an initial reservation for the newly created string and one that does not.

inline std::string to_upper_v2(std::string const & text) { auto uppertext = std::string{}; std::transform(std::begin(text), std::end(text), std::back_inserter(uppertext), toupper); return uppertext; } inline std::string to_upper_v3(std::string const & text) { auto uppertext = std::string{}; uppertext.reserve(text.size()); std::transform(std::begin(text), std::end(text), std::back_inserter(uppertext), toupper); return uppertext; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 inline std : : string to_upper_v2 ( std : : string const & text) { auto uppertext = std::string{}; std : : transform ( std : : begin ( text ) , std : : end ( text ) , std : : back_inserter ( uppertext ) , toupper ) ; return uppertext ; } inline std : : string to_upper_v3 ( std : : string const & text) { auto uppertext = std::string{}; uppertext . reserve ( text . size ( ) ) ; std : : transform ( std : : begin ( text ) , std : : end ( text ) , std : : back_inserter ( uppertext ) , toupper ) ; return uppertext ; }

The curious conclusion of the tests was that the version with reserve was actually slower than the one that did not perform an initial reservation.

The solution was built with Visual Studio 2015 Update 2. As it was later noticed in the comments, the actual cause of that is a Microsoft optimization for std::string by using an array of 16 characters for strings that do not exceed this size and only dynamically allocate memory for larger strings. Since all the strings had a length between 3 and 12 characters, this optimization was used for all strings. Therefore, reserve() dynamically allocated memory that was never used and its execution time only added to the overall time.

To actually be able to test the performance of these two implementations with VC++, the strings should be larger than 16 characters. So I changed the code to generate strings between 17 and 25 characters long.

auto dist_len = std::uniform_int_distribution<>{ 3, 12 }; 1 auto dist_len = std : : uniform_int_distribution < > { 3 , 12 } ;

The results this time were totally different. The 3rd version with initial reserving was more performant than the one that did not do that. It can also be noticed that the more strings need to be transformed the more similar times it takes for all the versions.

No of strings time v1 time v2 time v3 Percentage of slowdown with v2 Percentage of slowdown with v3 1000 122 219 205 79.5 68.0 10000 1202 2178 2055 81.2 71.0 100000 13563 22758 21431 67.8 58.0 1000000 136160 225669 214149 65.7 57.3 10000000 1368034 2268991 2155969 65.9 57.6 100000000 23090172 27997658 27322888 21.3 18.3

In the chart below with blue it is represented the results for version 2 and with orange the results for version 3 (with initial reservation).



Note: Generating 100 milion strings between 17 and 25 characters require a lot of memory. In my tests it peaked to 13GB. So if you want to run the code you should be aware of this.

Share this: Facebook

Twitter

Print

More

Email

Reddit



