Precalculate the size of the shortest string
In the inner loop you are checking str.size() < i
. Consider that the longest common prefix cannot be longer than the shortest string. I would first try to calculate the size of the shortest string, as this avoids the size check in the inner loop of the actual algorithm:
std::string f(std::vector<std::string>& strs){
std::size_t min_size = SIZE_MAX;
for (auto& str: strs)
min_size = std::min(min_size, str.size());
for(std::size_t i = 0; i < min_size; ++i)
for (auto& str: strs)
if(str[i] != strs[0][i])
return str.substr(0, i);
return strs[0];
}
Memory access pattern
The algorithm is quite simple, and I strongly suspect the bottleneck is how fast it can read the strings from memory.
If you have a large number of strings which don't fit into the CPU cache, then the order in which you check things against each other might matter a lot.
RAM access latency can be quite high, but this latency can be hidden by the CPU by looking at your memory access patterns, and prefetching memory based on that pattern. Contemporary CPUs can handle code reading and writing to a few areas in RAM simultaneously, but if for example you are comparing 100 strings, it will not be able to track that, and thus will either not prefetch (bad) or prefetch the wrong things (even worse).
So it might be interesting to check only two strings against each other at a time, as that is much more likely to keep the prefetcher happy:
std::string f(std::vector<std::string>& strs){
std::size_t min_size = SIZE_MAX;
for (auto& str: strs) {
min_size = std::min(min_size, str.size());
for (std::size_t i = 0; i < min_size; ++i)
if(str[i] != strs[0][i]) {
min_size = i;
break;
}
}
if (min_size == 0)
break;
}
return strs[0].substr(0, min_size);
}
However, this strategy might work slower than your solution if everything already fit into the cache.
You could probably find an even better solution that does something in-between the two strategies: consider checking the first cache line worth of bytes from each string against each other, then the next cache line worth of bytes if necessary, and so on.
Avoid checking a given string against itself
Both in your code and in my code above, the inner loop will check all strings against the first string. But that includes checking the first string against itself, which is unnecessary.
Check multiple characters in one go
You are checking individual characters against each other, but on contemporary computers, the CPU typically has registers that are 64 bits wide, and can thus hold 8 characters. There are even vector registers that are larger; with AVX512 you can have 64 characters in one register! For long strings, this might allow you to speed up the algorithm substantially.
Say you compare 8 characters at a time, stored in uint64_t
s. Then if two of those uint64_t
s are equal, you know that all 8 characters are the same, and you can skip to the next set of 8 characters. But if they are not equal, you can still quickly find which of the 8 characters was the first that is not equal by XORing both uint64_t
s together, and using std::countl_zero()
(since C++20) or ffs()
on the result.