Самый быстрый способ определить, существует ли целое число между двумя целыми числами (включительно) с известными наборами значений

Есть ли более быстрый способ, чем x >= start && x <= end в C или С++, проверить, существует ли целое число из двух целых чисел?

UPDATE: Моя конкретная платформа - iOS. Это часть функции размытия ящика, которая ограничивает пиксели по кругу в заданном квадрате.

UPDATE: после ответа принятого ответа я получил ускорение порядка одной строки кода, выполнив обычный способ x >= start && x <= end.

UPDATE: вот код после и после с ассемблером из 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

Довольно удивительно, как уменьшение или устранение ветвления может обеспечить такую ​​резкую скорость.

Ответ 1

Есть старый трюк, чтобы сделать это только с одним сравнением/ветвью. Будет ли это действительно улучшать скорость, может быть открытым вопрос, и даже если это произойдет, это, вероятно, слишком мало, чтобы заметить или позаботиться, но когда вы только начинаете с двух сравнений, шансы на значительное улучшение довольно удалены. Код выглядит так:

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

С типичным современным компьютером (т.е. чем-либо, использующим два дополнения) преобразование в unsigned действительно является nop - просто изменение в том, как просматриваются одни и те же биты.

Обратите внимание, что в типичном случае вы можете предварительно вычислить upper-lower вне цикла (предполагаемого), так что обычно не вносят сколько-нибудь значительного времени. Наряду с уменьшением количества команд ветвления, это также (как правило) улучшает предсказание ветвей. В этом случае выполняется одна и та же ветвь, находится ли это число ниже нижнего конца или над верхним концом диапазона.

Что касается этого, основная идея довольно проста: отрицательное число, если смотреть как беззнаковое число, будет больше, чем все, что начиналось как положительное число.

На практике этот метод переводит number и интервал в точку начала и проверяет, находится ли number в интервале [0, D], где D = upper - lower. Если number ниже нижней границы: отрицательный, а если выше верхней границы: больше, чем D.

Ответ 2

Это зависит от того, сколько раз вы хотите выполнить тест по тем же данным.

Если вы выполняете тест за один раз, вероятно, нет значимого способа ускорить алгоритм.

Если вы делаете это для очень конечного набора значений, вы можете создать таблицу поиска. Выполнение индексации может быть более дорогостоящим, но если вы можете поместить всю таблицу в кеш, вы можете удалить все ветвления из кода, что должно ускорить процесс.

Для ваших данных таблица поиска будет 128 ^ 3 = 2,097,152. Если вы можете управлять одной из трех переменных, поэтому учитывайте все экземпляры, где start = N за один раз, размер рабочего набора падает до 128^2 = 16432 байтов, что должно хорошо вписываться в большинство современных кешей.

Вам все равно нужно будет сравнить фактический код, чтобы увидеть, является ли таблица нерассмотренного поиска достаточно быстрой, чем очевидные сравнения.

Ответ 3

Редко можно сделать значительную оптимизацию для кода в таком маленьком масштабе. Большой прирост производительности достигается благодаря наблюдению и изменению кода с более высокого уровня. Возможно, вам удастся полностью исключить необходимость теста диапазона или использовать только O (n) вместо O (n ^ 2). Вы можете повторно заказать тесты, чтобы одна сторона неравенства всегда подразумевалась. Даже если алгоритм идеален, выигрыши с большей вероятностью появятся, когда вы увидите, как этот код проверяет диапазон 10 миллионов раз, и вы найдете способ их пакетного запуска и использовать SSE для параллельного выполнения многочисленных тестов.

Ответ 4

Этот ответ должен сообщить о тестировании, выполненном с принятым ответом. Я выполнил закрытый тест диапазона на большом векторе отсортированного случайного целого числа, и, к моему удивлению, основной метод (low <= num & num <= high) на самом деле быстрее, чем принятый ответ выше! Тест проводился на HP Pavilion g6 (AMD A6-3400APU с емкостью 6 ГБ. Здесь основной код, используемый для тестирования:

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;

по сравнению со следующим, который является принятым ответом выше:

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

Обратите внимание, что randVec - отсортированный вектор. Для любого размера MaxNum первый метод превосходит второй на моей машине!

Ответ 5

Невозможно просто выполнить побитовое действие над целым?

Так как он должен быть между 0 и 128, если установлен 8-й бит (2 ^ 7), то он равен 128 или больше. Случай с краем будет больно, однако, поскольку вы хотите инклюзивное сравнение.