J'ai récemment posé une question sur la Révision du Code de revoir un algorithme de tri nommé QuickMergeSort. Je ne rentrerai pas dans les détails, mais à un certain point, l'algorithme effectue un interne mergesort: au lieu d'utiliser de la mémoire supplémentaire pour stocker les données à fusionner, il permute les éléments à fusionner avec les éléments d'une autre partie de la séquence d'origine, ce qui n'est normalement pas concernées par la fusion. Ici est la partie de l'algorithme que je suis concerné, avec: la fonction qui effectue la fusion:
template<
typename InputIterator1,
typename InputIterator2,
typename OutputIterator,
typename Compare = std::less<>
>
auto half_inplace_merge(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2,
OutputIterator result, Compare compare={})
-> void
{
for (; first1 != last1; ++result) {
if (first2 == last2) {
std::swap_ranges(first1, last1, result);
return;
}
if (compare(*first2, *first1)) {
std::iter_swap(result, first2);
++first2;
} else {
std::iter_swap(result, first1);
++first1;
}
}
// first2 through last2 are already in the right spot
}
Cette fonction a été adapté à partir de l'éponyme en fonction de la libc++ mise en œuvre de l' std::inplace_merge
; cette nouvelle version swaps éléments avec une autre partie du tableau d'origine au lieu de déplacer des éléments à partir de l'auxiliaire de tableau.
Depuis la fusion est interne, j'ai réalisé que je n'avais pas réellement besoin d'avoir deux types d'entrée: InputIterator1
et InputIterator2
sont toujours les mêmes. Puis je suis venu à réaliser que, puisque les opérations sur first1
et first2
étaient toujours les mêmes, j'ai pu stocker dans un tableau à deux éléments et d'utiliser le résultat de la comparaison à l'indice de la matrice de savoir qui itérateur pour échanger et pour incrémenter. Avec cette petite astuce, je me débarrasser de la direction générale et obtenir une surtout sans branches algorithme de fusion:
template<
typename InputIterator,
typename OutputIterator,
typename Compare = std::less<>
>
auto half_inplace_merge(InputIterator first1, InputIterator last1,
InputIterator first2, InputIterator last2,
OutputIterator result, Compare compare={})
-> void
{
InputIterator store[] = { first1, first2 };
for (; store[0] != last1; ++result) {
if (store[1] == last2) {
std::swap_ranges(store[0], last1, result);
return;
}
bool cmp = compare(*store[1], *store[0]);
std::iter_swap(result, store[cmp]);
++store[cmp];
}
// first2 through last2 are already in the right spot
}
Maintenant, le truc, c'est: avec ce nouveau half_inplace_merge
de la fonction, l'ensemble de l'algorithme de tri est de 1,5 fois plus lent que l'original à l' half_inplace_merge
, et j'ai aucune idée de pourquoi. J'ai essayé plusieurs compilateur niveaux d'optimisation, plusieurs astuces pour éviter d'éventuels problèmes d'aliasing, mais il semble que le problème vient de l'dépourvu de branches tromper lui-même.
Donc, est ce que quelqu'un en mesure d'expliquer pourquoi le sans branches code est plus lent?
Addendum: pour ceux qui veulent courir la même référence que j'ai fait... eh bien, il sera un peu difficile: j'ai utilisé les points de référence à partir d'une bibliothèque personnelle, qui comprennent beaucoup de choses, vous aurez besoin de télécharger la bibliothèque, pour ajouter ce fichier quelque part, et pour exécuter ce test après avoir ajouté la ligne nécessaires pour invoquer quick_merge_sort
près de la section en surbrillance (vous aurez besoin de rediriger la sortie standard du programme vers un fichier dans un profiles
sous-répertoire). Ensuite, vous aurez besoin pour exécuter ce script Python pour voir les résultats, l'ajout d' quick_merge_sort
de la ligne en surbrillance. Notez que NumPy et matplotlib besoin d'être installé.