La forma más rápida de determinar si un entero está entre dos enteros (inclusive) con conjuntos de valores conocidos


¿Hay una forma más rápida que x >= start && x <= end en C o C++ para probar si un entero está entre dos enteros?

ACTUALIZAR : Mi plataforma específica es iOS. Esto es parte de una función de desenfoque de caja que restringe los píxeles a un círculo en un cuadrado dado.

ACTUALIZACIÓN : Después de probar la respuesta aceptada, obtuve un orden de aceleración de magnitud en una línea de código sobre hacerlo de la manera normal x >= start && x <= end.

UPDATE: Aquí está el código después y antes con ensamblador de XCode:

NUEVO CAMINO

// 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

Bastante sorprendente cómo reducir o eliminar la ramificación puede proporcionar una velocidad tan dramática.

Author: jjxtra, 2013-06-13

5 answers

Hay un viejo truco para hacer esto con una sola comparación/rama. Si realmente mejorará la velocidad puede ser cuestionable, e incluso si lo hace, probablemente sea demasiado poco para darse cuenta o preocuparse, pero cuando solo comienza con dos comparaciones, las posibilidades de una gran mejora son bastante remotas. El código se ve así:

// 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);

Con una computadora típica y moderna (es decir, cualquier cosa que use complemento de dos), la conversión a sin signo es realmente un nop just solo un cambio en cómo el mismo los bits se ven.

Tenga en cuenta que en un caso típico, puede pre-calcular upper-lower fuera de un bucle (supuesto), por lo que normalmente no contribuye ningún tiempo significativo. Junto con la reducción del número de instrucciones de rama, esto también (generalmente) mejora la predicción de rama. En este caso, se toma la misma rama si el número está por debajo del extremo inferior o por encima del extremo superior del rango.

En cuanto a cómo funciona esto, la idea básica es bastante simple: un número negativo, cuando se ve como un número sin signo, será más grande que cualquier cosa que comenzó como un número positivo.

En la práctica este método traduce number y el intervalo al punto de origen y comprueba si number está en el intervalo [0, D], donde D = upper - lower. Si number por debajo del límite inferior: negativo , y si está por encima del límite superior: mayor que D.

 502
Author: Jerry Coffin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-27 11:01:22

Depende de cuántas veces desee realizar la prueba sobre los mismos datos.

Si está realizando la prueba una sola vez, probablemente no haya una forma significativa de acelerar el algoritmo.

Si está haciendo esto para un conjunto muy finito de valores, entonces podría crear una tabla de búsqueda. Realizar la indexación puede ser más costoso, pero si puede caber toda la tabla en caché, entonces puede eliminar todas las ramificaciones del código, lo que debería acelerar las cosas hasta.

Para sus datos la tabla de búsqueda sería 128^3 = 2,097,152. Si puede controlar una de las tres variables para considerar todas las instancias donde start = N a la vez, entonces el tamaño del conjunto de trabajo se reduce a 128^2 = 16432 bytes, lo que debería encajar bien en la mayoría de las cachés modernas.

Todavía tendría que comparar el código real para ver si una tabla de búsqueda sin ramas es lo suficientemente rápida que las comparaciones obvias.

 17
Author: Andrew Prock,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-18 20:28:38

Es raro poder hacer optimizaciones significativas al código a una escala tan pequeña. Las grandes ganancias de rendimiento provienen de observar y modificar el código desde un nivel superior. Puede ser capaz de eliminar la necesidad de la prueba de rango por completo, o solo hacer O(n) de ellos en lugar de O(n^2). Es posible que pueda reordenar las pruebas para que siempre se implique un lado de la desigualdad. Incluso si el algoritmo es ideal, las ganancias son más probables cuando vea cómo este código hace la prueba de rango 10 millones de veces y encuentras una manera de agruparlos y usar SSE para hacer muchas pruebas en paralelo.

 16
Author: Ben Jackson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-13 19:34:26

Esta respuesta es para informar sobre una prueba realizada con la respuesta aceptada. Realicé una prueba de rango cerrado en un vector grande de entero aleatorio ordenado y para mi sorpresa el método básico de ( low

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;

Comparado con lo siguiente que es la respuesta aceptada arriba:

int inBetween2{ 0 };
for (int i = 1; i < MaxNum; ++i)
{
    if (static_cast<unsigned>(num - randVec[i - 1]) <= (randVec[i] - randVec[i - 1]))
        ++inBetween2;
}

Presta atención a que randVec es un vector ordenado. Para cualquier tamaño de MaxNum el primer método supera al segundo en mi máquina!

 2
Author: rezeli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-03 07:54:23

¿No es posible simplemente realizar una operación bitwise en el entero?

Puesto que tiene que estar entre 0 y 128, si el bit 8 está establecido (2^7) es 128 o más. El caso edge será un dolor, sin embargo, ya que desea una comparación inclusiva.

 -3
Author: icedwater,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-14 03:36:33