ПРИМЕЧАНИЕ. Заголовок вопроса был отредактирован - исходный заголовок был посвящен оптимизации «Пожалуйста, критикуйте - оптимальная функция для преобразования строковых регистров в C», что объясняет, почему мой ответ касается только оптимизации, а не общего характера. "улучшение" функций.
Если вы действительно ищете самый быстрый способ сделать это, то в долгосрочной перспективе вам подойдет версия без веток, поскольку она может использовать SIMD. Кроме того, он позволяет избежать таблиц (которые могут быть слишком большими во встроенной системе, если память действительно ограничена).
Вот простой пример без ветвей без SIMD, и ToLower - тривиальное изменение.
char BranchFree_AsciiToUpper(char inchar)
{
// Branch-Free / No-Lookup
// toupper() for ASCII-only
const int ConvertVal = 'A' - 'a';
// Bits to Shift Arithmetic to Right : 9 == (char-bits + 1)
const int AsrBits = 9;
int c=(int)inchar;
//if( (('a'-1)<c) && (c<('z'+1)) ) { c += 'A'-'a'; }
int LowerBound = ('a'-1) - c;
int UpperBound = c - ('z' + 1);
int BranchFreeMask = (LowerBound & UpperBound)>>AsrBits;
c = c + (BranchFreeMask & ConvertVal);
return((char)c);
}
Моя функция расширена для ясности и использует не жестко заданные константы. Вы можете сделать то же самое в одной строке с жестко запрограммированными значениями, но мне нравится читаемый код; однако вот «сжатая» версия моего алгоритма. Это не намного быстрее, так как оно ТОЧНО то же самое "сжимает" до одной строки.
c+=(((96-(int)c)&((int)c-123))>>9)&(-32);
Здесь вы можете сделать ряд оптимизаций, чтобы сделать его еще быстрее. Вы можете жестко закодировать более оптимальные числа для ASCII, потому что в примере не предполагается, что какое-либо отображение кодировки, кроме a-z, и A-Z являются смежными диапазонами. Например, с ASCII, если у вас нет переключателя, вы можете фактически изменить AsrBits на 4 (9-5), поскольку ConvertVal будет +/- 32 в зависимости от операции toupper или tolower.
Если у вас есть рабочие версии без ветвей, вы можете использовать методы SIMD или SWAR (SIMD в регистре) для преобразования 4–16 байтов за раз (или даже, возможно, больше в зависимости от того, насколько широки ваши регистры, и если вы развернете, чтобы скрыть задержку). Это будет намного быстрее, чем любой метод поиска, который в значительной степени ограничен однобайтовым преобразованием, если только у вас нет очень больших таблиц, которые растут экспоненциально на байт, обрабатываемый одновременно.
Кроме того, вы можете сгенерировать предикат без ветвей, не используя повышающее преобразование int, но тогда вам нужно будет выполнить еще пару операций (с повышающим преобразованием это всего лишь одно вычитание на диапазон). Возможно, вам потребуется выполнить расширенные операции для SWAR, но в большинстве реализаций SIMD есть операция сравнения, которая бесплатно генерирует для вас маску.
Операции SWAR / SIMD также могут выиграть от меньшего количества операций чтения / записи в память, а выполняемые записи могут быть выровнены. Это намного быстрее на процессорах, которые имеют штраф за попадание при загрузке (например, на процессор ячеек PS3). Совместите это с простой предварительной выборкой в развернутой версии, и вы почти полностью сможете избежать остановки памяти.
Я знаю, что в моем примере много кода, но есть ZERO веток (неявных или явных), и в результате нет неверных предсказаний ветвлений. Если вы работаете на платформе со значительными штрафами за неправильное прогнозирование ветвления (что верно для многих конвейерных встроенных процессоров), то даже без SIMD ваша оптимизированная сборка выпуска приведенного выше кода должна работать быстрее, чем что-то, что кажется гораздо менее сложным, но создает неявные ветви.
Даже без SIMD / SWAR интеллектуальный компилятор может развернуть и чередовать вышеуказанную реализацию, чтобы скрыть задержки и получить очень быструю версию - особенно на современных суперскалярных процессорах, которые могут выдавать более одной независимой инструкции за цикл. Обычно это невозможно ни с одной из версий ветвления.
Если вы вручную развернете, я бы сгруппировал загрузки и собрал хранилища, чтобы компилятору было проще чередовать неответвляющиеся независимые инструкции между ними. Пример:
// Unrolled inner loop where 'char *c' is the string we're converting
char c0=c[0],c1=c[1],c2=c[2],c3=c[3]; // Grouped-Loads
c[0]=BranchFree_AsciiToUpper(c0);
c[1]=BranchFree_AsciiToUpper(c1);
c[2]=BranchFree_AsciiToUpper(c2);
c[3]=BranchFree_AsciiToUpper(c3);
c+=4;
Приличный компилятор должен иметь возможность встроить ToUpper и полностью чередовать приведенный выше код, поскольку между ними нет ветвей, псевдонимов памяти и кодезависимых инструкций. Просто ради удовольствия я решил скомпилировать это, и компилятор, нацеленный на PowerPC, сгенерировал идеальное чередование для суперскалярного ядра с двумя задачами, которое легко превзойдет любой код с ветвями.
mr r31,r3
mr r13,r13
lbz r11,0(r31)
lbz r10,1(r31)
extsb r11,r11
lbz r9,2(r31)
extsb r10,r10
lbz r8,3(r31)
subfic r7,r11,96
addi r6,r11,-123
srawi r5,r7,9
srawi r4,r6,9
subfic r3,r10,96
addi r7,r10,-123
extsb r9,r9
srawi r6,r3,9
srawi r3,r7,9
subfic r7,r9,96
addi r30,r9,-123
extsb r8,r8
srawi r7,r7,9
srawi r30,r30,9
subfic r29,r8,96
addi r28,r8,-123
srawi r29,r29,9
srawi r28,r28,9
and r5,r5,r4
and r3,r6,r3
and r7,r7,r30
and r30,r29,r28
clrrwi r4,r5,5
clrrwi r6,r7,5
clrrwi r5,r3,5
clrrwi r7,r30,5
add r4,r4,r11
add r3,r5,r10
add r11,r6,r9
stb r4,0(r31)
add r10,r7,r8
stb r3,1(r31)
stb r11,2(r31)
stb r10,3(r31)
Доказательство тому - пудинг, и приведенный выше скомпилированный код будет очень быстрым по сравнению с версиями ветвления даже до перехода на SWAR или SIMD.
Итак, причины, по которым это должен быть самый быстрый способ:
- Отсутствие штрафов за неверное предсказание ветвления
- Возможность SIMD-алгоритма для 4-16 (и более) байтов за раз
- Компилятор (или программист) может разворачивать и чередовать, чтобы устранить задержки и воспользоваться преимуществами суперскалярных (многозадачных) процессоров.
- Нет задержек памяти (например, поиск по таблице)
person
Adisak
schedule
07.10.2010
for (int i = strlen(s); i; i--)
будет быстрее, чемwhile (*c)
(по крайней мере, в тех случаях, когдаstrlen
оптимизирован). - person ruslik   schedule 07.10.2010strlen()
от CRT оптимизирован, и это быстрее, чем простая реализация. Он проверяет больше символов за один шаг. - person ruslik   schedule 08.10.2010strlen()
в микс каким-то образом ускорит его - кроме того, что я слышал, он оптимизирован - я дам вам золотую звезду. - person Anon   schedule 08.10.2010