Existe-t-il un moyen plus rapide que x >= start && x <= end
en C ou C++ de tester si un entier est compris entre deux entiers?
UPDATE: Ma plate-forme spécifique est iOS. Cela fait partie d'une fonction de flou de boîte qui limite les pixels à un cercle dans un carré donné.
UPDATE: Après avoir essayé le réponse acceptée , j'ai obtenu une accélération d'un ordre de grandeur sur la seule ligne de code par rapport à l'exécution normale x >= start && x <= end
façon.
UPDATE: Voici le code après et avant avec l'assembleur de XCode:
NEW WAY
// diff = (end - start) + 1
#define POINT_IN_RANGE_AND_INCREMENT(p, range) ((p++ - range.start) < range.diff)
Ltmp1313:
ldr r0, [sp, #176] @ 4-byte Reload
ldr r1, [sp, #164] @ 4-byte Reload
ldr r0, [r0]
ldr r1, [r1]
sub.w r0, r9, r0
cmp r0, r1
blo LBB44_30
OLD WAY
#define POINT_IN_RANGE_AND_INCREMENT(p, range) (p <= range.end && p++ >= range.start)
Ltmp1301:
ldr r1, [sp, #172] @ 4-byte Reload
ldr r1, [r1]
cmp r0, r1
bls LBB44_32
mov r6, r0
b LBB44_33
LBB44_32:
ldr r1, [sp, #188] @ 4-byte Reload
adds r6, r0, #1
Ltmp1302:
ldr r1, [r1]
cmp r0, r1
bhs LBB44_36
Assez étonnant de voir à quel point la réduction ou l’élimination des ramifications peut donner une telle accélération.
Il y a un vieux truc pour faire cela avec seulement une comparaison/branche. On peut se demander si cela va vraiment améliorer la vitesse, et même si c'est le cas, c'est probablement trop peu pour être remarqué ou préoccupé, mais lorsque vous ne commencez qu'avec deux comparaisons, les chances d'une amélioration considérable sont plutôt faibles. Le code ressemble à:
// use a < for an inclusive lower bound and exclusive upper bound
// use <= for an inclusive lower bound and inclusive upper bound
// alternatively, if the upper bound is inclusive and you can pre-calculate
// upper-lower, simply add + 1 to upper-lower and use the < operator.
if ((unsigned)(number-lower) <= (upper-lower))
in_range(number);
Avec un ordinateur classique typique (tout ce qui utilise un complément à deux), la conversion en non signé est vraiment un non-seulement - il s’agit simplement de changer la façon dont les mêmes bits sont visualisés.
Notez que dans un cas typique, vous pouvez pré-calculer upper-lower
en dehors d'une boucle (présumée), de sorte que cela ne prend normalement pas beaucoup de temps. En plus de réduire le nombre d'instructions de branche, cela améliore également (généralement) la prédiction de branche. Dans ce cas, la même branche est prise que le nombre soit inférieur à l'extrémité inférieure ou supérieure à l'extrémité supérieure de la plage.
Pour ce qui est de savoir comment cela fonctionne, l’idée de base est assez simple: un nombre négatif, considéré comme un nombre non signé, sera plus grand que tout ce qui a commencé par un nombre positif.
En pratique, cette méthode traduit number
et l'intervalle en point d'origine et vérifie si number
est dans l'intervalle [0, D]
, où D = upper - lower
. Si number
au-dessous de la borne inférieure: négatif, et si au-dessus de la limite supérieure: supérieur à D
.
Il est rare de pouvoir effectuer des optimisations significatives pour coder à si petite échelle. Le gain de performances provient de l'observation et de la modification du code à un niveau supérieur. Vous pourrez peut-être éliminer complètement la nécessité du test de portée ou ne faire que O(n) au lieu de O (n ^ 2). Vous pourrez peut-être réorganiser les tests de sorte qu'un côté de l'inégalité soit toujours impliqué. Même si l'algorithme est idéal, les gains sont plus probables lorsque vous voyez comment ce code effectue 10 millions de tests sur la plage, que vous trouvez le moyen de les regrouper et que vous utilisez SSE pour effectuer de nombreux tests en parallèle. .
Cela dépend du nombre de fois que vous souhaitez effectuer le test sur les mêmes données.
Si vous effectuez le test une seule fois, il n’ya probablement pas de moyen significatif d’accélérer l’algorithme.
Si vous faites cela pour un ensemble de valeurs très fini, vous pouvez créer une table de correspondance. L'exécution de l'indexation peut être plus coûteuse, mais si vous pouvez placer la table entière dans le cache, vous pouvez alors supprimer toutes les branches du code, ce qui devrait accélérer les choses.
Pour vos données, la table de consultation serait 128 ^ 3 = 2 097 152. Si vous pouvez contrôler l'une des trois variables de manière à prendre en compte toutes les occurrences où start = N
à la fois, la taille de l'ensemble de travail redescend à 128^2 = 16432
octets, ce qui devrait convenir à la plupart des caches modernes.
Vous devez toujours analyser le code actuel pour voir si une table de recherche sans branche est suffisamment rapide par rapport aux comparaisons évidentes.
Cette réponse consiste à signaler un test effectué avec la réponse acceptée. J'ai effectué un test de plage fermée sur un grand vecteur d'entier aléatoire trié et, à ma grande surprise, la méthode de base de (faible <= num && num <= élevé) est en fait plus rapide que la réponse acceptée ci-dessus! Les tests ont été réalisés sur le HP Pavilion g6 (AMD A6-3400APU avec 6 Go de RAM. Voici le code principal utilisé pour les tests:
int num = Rand(); // num to compare in consecutive ranges.
chrono::time_point<chrono::system_clock> start, end;
auto start = chrono::system_clock::now();
int inBetween1{ 0 };
for (int i = 1; i < MaxNum; ++i)
{
if (randVec[i - 1] <= num && num <= randVec[i])
++inBetween1;
}
auto end = chrono::system_clock::now();
chrono::duration<double> elapsed_s1 = end - start;
par rapport à ce qui suit est la réponse acceptée ci-dessus:
int inBetween2{ 0 };
for (int i = 1; i < MaxNum; ++i)
{
if (static_cast<unsigned>(num - randVec[i - 1]) <= (randVec[i] - randVec[i - 1]))
++inBetween2;
}
Faites attention que randVec est un vecteur trié. Quelle que soit la taille de MaxNum, la première méthode bat la seconde sur ma machine!