Каков самый быстрый способ преобразования hex в integer в C++?

Я пытаюсь преобразовать hex char в integer как можно быстрее.

Это только одна линия: int x = atoi(hex.c_str);

есть ли более быстрый способ?

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

int hextoint(char number) {
    if (number == '0') {
        return 0;
    }
    if (number == '1') {
        return 1;
    }
    if (number == '2') {
        return 2;
    }
    /*
     *  3 through 8
     */
    if (number == '9') {
        return 9;
    }
    if (number == 'a') {
        return 10;
    }
    if (number == 'b') {
        return 11;
    }
    if (number == 'c') {
        return 12;
    }
    if (number == 'd') {
        return 13;
    }
    if (number == 'e') {
        return 14;
    }
    if (number == 'f') {
        return 15;
    }
    return -1;
}

6 ответов


предлагаемые решения, которые отображаются быстрее, чем OP if-else:

  • Неупорядоченная Таблица Поиска Карты

при условии, что ваши входные строки всегда являются шестнадцатеричными числами, вы можете определить таблицу поиска как unordered_map:

std::unordered_map<char, int> table {
{'0', 0}, {'1', 1}, {'2', 2},
{'3', 3}, {'4', 4}, {'5', 5},
{'6', 6}, {'7', 7}, {'8', 8},
{'9', 9}, {'a', 10}, {'A', 10},
{'b', 11}, {'B', 11}, {'c', 12},
{'C', 12}, {'d', 13}, {'D', 13},
{'e', 14}, {'E', 14}, {'f', 15},
{'F', 15}, {'x', 0}, {'X', 0}};

int hextoint(char number) {
  return table[(std::size_t)number];
}
  • таблица поиска как пользователь constexpr литерал (C++14)

или если вы хотите что-то более быстрее, вместо unordered_map вы можете использовать новый C++14 объектов с литеральными типами пользователей и определите таблицу как литеральный тип во время компиляции:

struct Table {
  long long tab[128];
  constexpr Table() : tab {} {
    tab['1'] = 1;
    tab['2'] = 2;
    tab['3'] = 3;
    tab['4'] = 4;
    tab['5'] = 5;
    tab['6'] = 6;
    tab['7'] = 7;
    tab['8'] = 8;
    tab['9'] = 9;
    tab['a'] = 10;
    tab['A'] = 10;
    tab['b'] = 11;
    tab['B'] = 11;
    tab['c'] = 12;
    tab['C'] = 12;
    tab['d'] = 13;
    tab['D'] = 13;
    tab['e'] = 14;
    tab['E'] = 14;
    tab['f'] = 15;
    tab['F'] = 15;
  }
  constexpr long long operator[](char const idx) const { return tab[(std::size_t) idx]; } 
} constexpr table;

constexpr int hextoint(char number) {
  return table[(std::size_t)number];
}

Live Demo

критерии:

я запустил тесты с кодом, написанным Никосом Афанасиу, который был опубликован недавно на isocpp.org в качестве предлагаемого метода микро-бенчмаркинга C++.

сравниваемые алгоритмы:

1. Оригинал OP if-else:

long long hextoint3(char number) {
  if(number == '0') return 0;
  if(number == '1') return 1;
  if(number == '2') return 2;
  if(number == '3') return 3;
  if(number == '4') return 4;
  if(number == '5') return 5;
  if(number == '6') return 6;
  if(number == '7') return 7;
  if(number == '8') return 8;
  if(number == '9') return 9;
  if(number == 'a' || number == 'A') return 10;
  if(number == 'b' || number == 'B') return 11;
  if(number == 'c' || number == 'C') return 12;
  if(number == 'd' || number == 'D') return 13;
  if(number == 'e' || number == 'E') return 14;
  if(number == 'f' || number == 'F') return 15;
  return 0;
}

2. Компактный if-else, предложенный Кристофом:

long long hextoint(char number) {
  if (number >= '0' && number <= '9') return number - '0';
  else if (number >= 'a' && number <= 'f') return number - 'a' + 0x0a;
  else if (number >= 'A' && number <= 'F') return number - 'A' + 0X0a;
  else return 0;
}

3. Исправлена версия троичного оператора, которая обрабатывает также вводы заглавной буквы, предложенные g24l:

long long hextoint(char in) {
  int const x = in;
  return (x <= 57)? x - 48 : (x <= 70)? (x - 65) + 0x0a : (x - 97) + 0x0a;
}

4. Таблица Поиска (unordered_map):

long long hextoint(char number) {
  return table[(std::size_t)number];
}

здесь table показана неупорядоченная карта ранее.

5. Таблица поиска (user constexpr дословный):

long long hextoint(char number) {
  return table[(std::size_t)number];
}

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

Экспериментальные Параметры

я определил функцию, которая преобразует входную шестнадцатеричную строку в целое число:

long long hexstrtoint(std::string const &str, long long(*f)(char)) {
  long long ret = 0;
  for(int j(1), i(str.size() - 1); i >= 0; --i, j *= 16) {
    ret += (j * f(str[i]));
  }
  return ret;
}

я также определил функцию, которая заполняет вектор строк случайным шестнадцатеричным строки:

std::vector<std::string>
populate_vec(int const N) {
  random_device rd;
  mt19937 eng{ rd() };
  uniform_int_distribution<long long> distr(0, std::numeric_limits<long long>::max() - 1);
  std::vector<std::string> out(N);
  for(int i(0); i < N; ++i) {
    out[i] = int_to_hex(distr(eng));
  }
  return out;
}

я создал векторы, заполненные 50000, 100000, 150000, 200000 и 250000 случайными шестнадцатеричными строками соответственно. Затем для каждого алгоритма я запускаю 100 экспериментов и усредняю результаты времени.

компилятор был GCC версии 5.2 с опцией оптимизации -O3.

результаты:

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

Обсуждение

из результатов можно сделать вывод, что для этих экспериментальных параметров предлагаемый метод table out-выполняет все остальные методы. Метод if-else намного хуже, где как unordered_map хотя он выигрывает метод if-else, он значительно медленнее, чем другие предлагаемые методы.

код

Edit:

результаты по методу, предложенному стгатиловым, с побитовым операции:

long long hextoint(char x) {
    int b = uint8_t(x);
    int maskLetter = (('9' - b) >> 31);
    int maskSmall = (('Z' - b) >> 31);
    int offset = '0' + (maskLetter & int('A' - '0' - 10)) + (maskSmall & int('a' - 'A'));
    return b - offset;
}

enter image description here

Edit:

я также протестировал исходный код из g24l против метода таблицы:

long long hextoint(char in) {
  long long const x = in;
  return x < 58? x - 48 : x - 87;
}

обратите внимание, что этот метод не обрабатывает заглавные буквы A, B, C, D, E и F.

результаты:

enter image description here

тем не менее метод таблицы отображается быстрее.


правильным вопросом было бы:"какой самый быстрый способ преобразовать один char hex to dec in X система , например, i686 " .

среди подходов здесь ответ на это на самом деле то же самое или очень очень почти то же самое в системе с многоступенчатым трубопроводом. Любая система без конвейера будет склоняться к методу таблицы поиска (LUT), но если доступ к памяти медленный, условный метод (CEV) или метод побитовой оценки (BEV) могут получить прибыль в зависимости от скорости xor против нагрузки для данного процессора.

(CEV) разлагает на 2 эффективных адреса нагрузки сравнение и условное перемещение из регистров который не склонен к МИС-предсказание. Все эти команды pairable в конвейере pentium. Таким образом, они фактически идут в 1-цикле.

  8d 57 d0                lea    -0x30(%rdi),%edx
  83 ff 39                cmp    x39,%edi
  8d 47 a9                lea    -0x57(%rdi),%eax
  0f 4e c2                cmovle %edx,%eax

(LUT) разлагается на mov между регистрами и mov из зависимого от данных местоположения памяти плюс некоторые nops для выравнивания и должен принимать минимум 1-цикл. Как и предыдущие, существуют только ЗАВИСИМОСТИ данных.

  48 63 ff                movslq %edi,%rdi
  8b 04 bd 00 1d 40 00    mov    0x401d00(,%rdi,4),%eax

(BEV) - это другой зверь, поскольку он фактически требует 2 movs + 2 xors + 1 и условного mov. Они могут также красиво конвейера.

  89 fa                   mov    %edi,%edx
  89 f8                   mov    %edi,%eax
  83 f2 57                xor    x57,%edx
  83 f0 30                xor    x30,%eax
  83 e7 40                and    x40,%edi
  0f 45 c2                cmovne %edx,%eax

конечно, это очень редкий случай, когда это критическое приложение (возможно, Mars Pathfinder является кандидатом) для преобразования просто знак char. Вместо этого можно было бы ожидать преобразования большей строки, фактически сделав цикл и вызвав эту функцию.

таким образом, на такой сценарий код, который лучше vectorizable является победителем. LUT не векторизуется, а BEV и CEV имеют лучшее поведение. In вообще такая микро-оптимизация никуда вас не приведет, напишите свой код и дайте жить (т. е. пусть компилятор работает).

поэтому я фактически построил некоторые тесты в этом смысле, которые являются легко воспроизводимые в любой системе с компилятором c++11 и источником случайных устройств, таких как любая система *nix. Если не разрешить векторизацию -O2 CEV / LUT почти равны, но один раз -O3 установлено преимущество написания кода, который более разложим показывает разница.

чтобы резюмировать, если у вас есть старый компилятор, используйте LUT, если ваша система является низкоуровневой или старой, рассмотрите BEV, иначе компилятор перехитрит вас, и вы должны использовать CEV.


: речь идет о преобразовании из множества символов {0,1,2,3,4,5,6,7,8,9, a,b,c,d,e, f} в множество {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}. Там нет заглавных букв рассмотрение.

идея состоит в том, чтобы воспользоваться линейностью таблицы ascii в сегментах.

[простой и легкий]: условная оценка - > CEV

int decfromhex(int const x)
{
return x<58?x-48:x-87;
}

[грязный и сложный]: побитовая оценка -> BEV

int decfromhex(int const x)
{
return 9*(x&16)+( x & 0xf  );
}

[время компиляции]: условная оценка шаблона -> TCV

template<char n> int decfromhex()
{
  int constexpr x = n;
  return x<58 ? x-48 : x -87;
}

[таблица поиска]: таблица поиска -> Лут

int decfromhex(char n)
{
static int constexpr x[255]={
           // fill everything with invalid, e.g. -1 except places\
           // 48-57 and 97-102 where you place 0..15 
           };
return x[n];
}

среди всех , последний, кажется, самый быстрый на первый взгляд. Второй-только во время компиляции и constant expression.

[результат] (пожалуйста, проверьте): *BEV является самым быстрым среди всех и обрабатывает нижний и верхний регистр буквы, но только маргинальный до CEV, который не обрабатывает заглавными буквами. LUT становится медленнее, чем CEV и BEV как размер из строки увеличивается.

примерный результат для str-размеров 16-12384 можно найти ниже ( чем ниже, тем лучше )

enter image description here

среднее время (100 работает ), это шоу. Размер пузыря-это нормальная ошибка.

скрипт для запуска тестов доступны.


тесты были выполнены для conditional CEV, в bitwise Бев и lookup table ЛТУ на множестве случайно сгенерированных строк. Тесты довольно просты и от:

проверить исходный код

это проверяемо:

  1. A локальная копия входных данных строка помещается в локальный буфер каждый раз.
  2. A локальная копия результатов сохраняется, который затем копируется в кучу для каждой строки тест
  3. продолжительность извлекается только на время работы со строкой
  4. единый подход нет никаких сложных машин и обернуть вокруг кода, который установлен для других случаев.
  5. нет выборки используется вся последовательность синхронизации
  6. выполняется предварительный нагрев процессора
  7. сон между тестами происходят, чтобы разрешить маршал код такой, что один тест не использует преимущества предыдущего теста.
  8. сборник осуществляется с g++ -std=c++11 -O3 -march=native dectohex.cpp -o d2h
  9. запуск С taskset -c 0 d2h
  10. нет зависимостей потоков или многопоточности
  11. результаты фактически используются, чтобы избежать любого типа оптимизации цикла

в качестве примечания я видел на практике версию 3, чтобы быть намного быстрее со старыми компиляторами c++98.

[ИТОГ] используйте CEV без страха, если вы не знаете свои переменные во время компиляции, где вы могли бы использовать version TCV. The ЛТУ следует использовать только после значительной производительности в случае использования оценка, и, вероятно, с более старыми компиляторами. Другой случай-когда ваш набор больше, т. е. {0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,A,B,C,D,E, F} . Этого также можно достичь. Наконец, если вы permormance голодного использования Бев .

С unordered_map были удалены, так как они были слишком медленными для сравнения, или в лучшем случае могут быть такими же быстрыми, как решение LUT.

результаты с моего личного ПК на строках размера 12384/256 и для 100 строк:

 g++ -DS=2 -DSTR_SIZE=256 -DSET_SIZE=100 -DUNITS=nanoseconds -O3 -std=c++11 -march=native dectohex.cpp -o d2h && taskset -c 0 ./d2h
sign: -2709
-------------------------------------------------------------------
(CEV) Total: 185568 nanoseconds - mean: 323.98 nanoseconds  error: 88.2699 nanoseconds
(BEV) Total: 185568 nanoseconds - mean: 337.68 nanoseconds  error: 113.784 nanoseconds
(LUT) Total: 229612 nanoseconds - mean: 667.89 nanoseconds  error: 441.824 nanoseconds
-------------------------------------------------------------------


g++ -DS=2 -DSTR_SIZE=12384 -DSET_SIZE=100 -DUNITS=nanoseconds -O3 -std=c++11 -march=native hextodec.cpp -o d2h && taskset -c 0 ./h2d

-------------------------------------------------------------------
(CEV) Total: 5539902 nanoseconds - mean: 6229.1 nanoseconds error: 1052.45 nanoseconds
(BEV) Total: 5539902 nanoseconds - mean: 5911.64 nanoseconds    error: 1547.27 nanoseconds
(LUT) Total: 6346209 nanoseconds - mean: 14384.6 nanoseconds    error: 1795.71 nanoseconds
-------------------------------------------------------------------
Precision: 1 ns

результаты работы системы с GCC 4.9.3, скомпилированной в металл без загрузки системы на строки размером 256/12384 и для 100 струны

g++ -DS=2 -DSTR_SIZE=256 -DSET_SIZE=100 -DUNITS=nanoseconds -O3 -std=c++11 -march=native dectohex.cpp -o d2h && taskset -c 0 ./d2h
sign: -2882
-------------------------------------------------------------------
(CEV) Total: 237449 nanoseconds - mean: 444.17 nanoseconds  error: 117.337 nanoseconds
(BEV) Total: 237449 nanoseconds - mean: 413.59 nanoseconds  error: 109.973 nanoseconds
(LUT) Total: 262469 nanoseconds - mean: 731.61 nanoseconds  error: 11.7507 nanoseconds
-------------------------------------------------------------------
Precision: 1 ns


g++ -DS=2 -DSTR_SIZE=12384 -DSET_SIZE=100 -DUNITS=nanoseconds -O3 -std=c++11 -march=native dectohex.cpp -o d2h && taskset -c 0 ./d2h
sign: -137532
-------------------------------------------------------------------
(CEV) Total: 6834796 nanoseconds - mean: 9138.93 nanoseconds    error: 144.134 nanoseconds
(BEV) Total: 6834796 nanoseconds - mean: 8588.37 nanoseconds    error: 4479.47 nanoseconds
(LUT) Total: 8395700 nanoseconds - mean: 24171.1 nanoseconds    error: 1600.46 nanoseconds
-------------------------------------------------------------------
Precision: 1 ns

[КАК ЧИТАТЬ РЕЗУЛЬТАТЫ]

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

общее время для каждого теста дается. Среднее значение вычисляется как сумма / сумма таймингов для вычисления одной строки (никакого другого кода в этой области, но может быть векторизовано, и это нормально) . Ошибка-стандартное отклонение таймингов.

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


код имеет уникальный макрос, определенный для запуска тестов, позволяет определить переменные времени компиляции для установки вверх по тестам и печатает полную информацию, такую как:

g++ -DS=2 -DSTR_SIZE=64 -DSET_SIZE=1000 -DUNITS=nanoseconds -O3 -std=c++11 -march=native dectohex.cpp -o d2h && taskset -c 0 ./d2h
sign: -6935
-------------------------------------------------------------------
(CEV) Total: 947378 nanoseconds - mean: 300.871 nanoseconds error: 442.644 nanoseconds
(BEV) Total: 947378 nanoseconds - mean: 277.866 nanoseconds error: 43.7235 nanoseconds
(LUT) Total: 1040307 nanoseconds - mean: 375.877 nanoseconds    error: 14.5706 nanoseconds
-------------------------------------------------------------------

например, чтобы запустить тест с помощью 2sec пауза на str размера 256 в общей сложности 10000 различные строки, выходные тайминги в double precision, и граф в nanoseconds следующая команда компилирует и запускает тест.

g++ -DS=2 -DSTR_SIZE=256 -DSET_SIZE=10000 -DUTYPE=double -DUNITS=nanoseconds -O3 -std=c++11 -march=native dectohex.cpp -o d2h && taskset -c 0 ./d2h

Ну, это странный вопрос. Преобразование одного шестнадцатеричного символа в целое число происходит так быстро, что очень трудно сказать, какой из них быстрее, потому что все методы, скорее всего, быстрее, чем код, который вы пишете, чтобы использовать их =)

Я предполагаю следующее:

  1. у нас есть современный процессор x86(64).
  2. код ASCII входного символа хранится в регистре общего назначения, например в eax.
  3. выходной число должен быть получен в регистре общего назначения.
  4. входной символ гарантированно будет действительной шестнадцатеричной цифрой (один из 16 случаев).

решение

теперь вот несколько методов решения проблемы: первый на основе поиска, два на основе тернарного оператора, Последний на основе битовых операций:

int hextoint_lut(char x) {
    static char lut[256] = {???};
    return lut[uint8_t(x)];
}

int hextoint_cond(char x) {
    uint32_t dig = x - '0';
    uint32_t alp = dig + ('0' - 'a' + 10);
    return dig <= 9U ? dig : alp;
}
int hextoint_cond2(char x) {
    uint32_t offset = (uint8_t(x) <= uint8_t('9') ? '0' : 'a' - 10);
    return uint8_t(x) - offset;
}

int hextoint_bit(char x) {
    int b = uint8_t(x);
    int mask = (('9' - b) >> 31);
    int offset = '0' + (mask & int('a' - '0' - 10));
    return b - offset;
}

здесь генерируются соответствующие списки сборок (только соответствующие части показано):

;hextoint_lut;
movsx   eax, BYTE PTR [rax+rcx]   ; just load the byte =)

;hextoint_cond;
sub edx, 48                       ; subtract '0'
cmp edx, 9                        ; compare to '9'
lea eax, DWORD PTR [rdx-39]       ; add ('0' - 'a' + 10)
cmovbe  eax, edx                  ; choose between two cases in branchless way

;hextoint_cond2;                  ; (modified slightly)
mov eax, 48                       
mov edx, 87                       ; set two offsets to registers
cmp ecx, 57                       ; compare with '9'
cmovbe  edx, eax                  ; choose one offset
sub ecx, edx                      ; subtract the offset

;hextoint_bit;
mov ecx, 57                       ; load '9'
sub ecx, eax                      ; get '9' - x
sar ecx, 31                       ; convert to mask if negative
and ecx, 39                       ; set to 39 (for x > '9')
sub eax, ecx                      ; subtract 39 or 0
sub eax, 48                       ; subtract '0'

анализ

я попытаюсь оценить количество циклов, принятых каждым подходом в смысле пропускной способности, что по существу является временем, затраченным на одно входное число, когда много чисел обрабатываются одновременно. Рассмотрим архитектуре Sandy Bridge как пример.

на hextoint_lut функция состоит из одиночной нагрузки памяти, которая принимает 1 uop на порте 2 или 3. Оба этих порта предназначены для загрузки памяти, а также имеют адрес расчет внутри, которые способны делать rax+rcx без дополнительной платы. Есть два таких порта, каждый может сделать один uop в цикле. Поэтому, предположительно, эта версия займет 0,5 часа. Если нам нужно загрузить входной номер из памяти, это потребует еще одной загрузки памяти на значение, поэтому общая стоимость будет 1 Часы.

на hextoint_cond версия имеет 4 инструкции, но cmov разбивается на два отдельных uops. Так 5 uops в итоге, каждое можно обрабатывать на любом из три арифметических порта 0, 1 и 5. Так что, предположительно, это займет 5/3 цикла времени. Обратите внимание, что порты загрузки памяти свободны, поэтому время не будет увеличиваться, даже если вам нужно загрузить входное значение из памяти.

на hextoint_cond2 версия 5 инструкции. Но в плотном цикле константы могут быть предварительно загружены в регистры, поэтому будут только сравнение, cmov и вычитание. Они 4 uops в итоге, давая 4/3 циклов в значение (даже с прочитанной памятью).

на hextoint_bit версия-это решение, которое гарантированно не имеет ветвей и поиска, что удобно, если вы не хотите всегда проверять, сгенерировал ли ваш компилятор инструкцию cmov. Первый mov свободен, так как константа может быть предварительно загружена в плотный цикл. Остальные 5 арифметических инструкций, которые 5 uops в портах 0, 1, 5. Таким образом, это должно занять 5/3 циклов (даже при чтении памяти).

Benchmark

я выполнил тест для функций C++, описанных выше. В бенчмарке генерируется 64 КБ случайных данных, затем каждая функция запускается много раз на этих данных. Все результаты добавляются в контрольную сумму, чтобы убедиться, что компилятор не удаляет код. Используется ручная 8X раскатка. Я тестировал на ядре Ivy Bridge 3.4 Ghz, которое очень похоже на Sandy Bridge. Каждая строка вывода содержит: имя функции, общее время, затраченное бенчмарком, количество циклов на входное значение, сумму всех выходов.

Эталон код

MSVC2013 x64 /O2:
hextoint_lut: 0.741 sec, 1.2 cycles  (check: -1022918656)
hextoint_cond: 1.925 sec, 3.0 cycles  (check: -1022918656)
hextoint_cond2: 1.660 sec, 2.6 cycles  (check: -1022918656)
hextoint_bit: 1.400 sec, 2.2 cycles  (check: -1022918656)

GCC 4.8.3 x64 -O3 -fno-tree-vectorize
hextoint_lut: 0.702 sec, 1.1 cycles  (check: -1114112000)
hextoint_cond: 1.513 sec, 2.4 cycles  (check: -1114112000)
hextoint_cond2: 2.543 sec, 4.0 cycles  (check: -1114112000)
hextoint_bit: 1.544 sec, 2.4 cycles  (check: -1114112000)

GCC 4.8.3 x64 -O3
hextoint_lut: 0.702 sec, 1.1 cycles  (check: -1114112000)
hextoint_cond: 0.717 sec, 1.1 cycles  (check: -1114112000)
hextoint_cond2: 0.468 sec, 0.7 cycles  (check: -1114112000)
hextoint_bit: 0.577 sec, 0.9 cycles  (check: -1114112000)

очевидно, что подход LUT занимает один цикл на значение (как и прогнозировалось). Другие подходы обычно занимают от 2,2 до 2,6 циклов на величину. В случае GCC,hextoint_cond2 медленно, потому что компилятор использует cmp+sbb+и magic вместо желаемых инструкций cmov. Также обратите внимание, что по умолчанию GCC векторизирует большинство подходов (последний абзац), что обеспечивает ожидаемо более быстрые результаты, чем невекторизуемый подход LUT. Обратите внимание, что ручная векторизация даст значительно больший импульс.

Обсуждение

отметим, что hextoint_cond С обычным условным прыжком вместо cmov будет иметь филиал. Предполагая случайный ввод шестнадцатеричных цифр, он будет mispredicted почти всегда. Так что представление будет ужасным, я думаю.

Я проанализировал производительность. Но если нам нужно обработать тонны входных значений, то мы должны определенно векторизовать преобразование, чтобы получить лучшую скорость. hextoint_cond можно векторизовать с SSE довольно простым способом. Он позволяет обрабатывать 16 байтов до 16 байтов, используя только 4 Инструкции, занимая около 2 циклов, я полагаю.

обратите внимание, что для того, чтобы увидеть разницу в производительности, вы должны убедиться, что все входные значения вписываются в кэш (L1-лучший случай). Если Вы читаете входные данные из основной памяти, даже std::atoi одинаково быстро с рассмотренными методами =)

кроме того, вы должны развернуть свой основной цикл 4x или даже 8x для максимальной производительности (чтобы удалить накладные расходы). Как вы уже могли заметить, скорость обоих методов сильно зависит от того, какие операции окружают код. Е. Г. добавление нагрузкой на память удваивает время, затраченное на первый подход, но не влияет на другие подходы.

П. С. скорее всего, вам не нужно оптимизировать это.


предполагая, что ваша функция вызывается для действительной шестнадцатеричной цифры, это будет стоить в среднем не менее 8 операций сравнения (и, возможно, 7 прыжков). Довольно дорогой.

альтернатива была бы более компактной:

if (number >= '0' && number<='9') 
    return number-'0';
else if (number >= 'a' && number <='f') 
    return number-'a'+0x0a; 
else return -1; 

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

if (number>=0) 
   return mytable[number]; 
else return -1;   

Если вы хотите преобразовать больше чем одну цифру на время, вы могли бы взглянуть на этот вопрос)

Edit: benchmark

после ИКЭнаблюдения, я написал небольшой неофициальный ориентир (доступно онлайн здесь), который вы можете запустить на своем любимом компиляторе.

выводы:

  • таблица поиска всегда является победителем
  • переключатель лучше, чем if-цепь.
  • С msvc2015 (release) вторым лучшим является моя компактная версия, за которой на удивление близко следует версия карты 101010.
  • С gcc на ideone второй является версией коммутатора, за которой следует компактная версия. enter image description here

Это мой любимый код hex-to-int:

inline int htoi(int x) {
    return 9 * (x >> 6) + (x & 017);
}

он нечувствителен к регистру для буквы i.e вернет правильный результат для "a"и " A".


Если вы (или кто-то другой) фактически преобразуете массив значений, я сделал кодировщик и декодер AVX2 SIMD, который проверяет ~12x быстрее, чем самая быстрая скалярная реализация:https://github.com/zbjornson/fast-hex

16 шестнадцатеричных значений удобно помещаются (дважды) в регистр YMM, поэтому вы можете использовать PSHUFB для выполнения параллельного поиска. Декодирование немного сложнее и основано на бит-мудрых ops.