c ++ isalpha lookup table - PullRequest
       13

c ++ isalpha lookup table

1 голос
/ 20 апреля 2011

Какой самый простой способ реализовать таблицу поиска, которая проверяет, является ли символ альфа или нет, используя таблицу поиска с массивом 256 символов (256 байтов)?Я знаю, что могу использовать функцию isalpha, но таблица поиска может быть более эффективной, требуя одного сравнения вместо нескольких.Я думал о том, чтобы соотнести индекс с десятичным преобразованием символов и напрямую проверить, эквивалентен ли он этому символу.

Ответы [ 6 ]

3 голосов
/ 16 июня 2014

Я всегда использовал этот единственный метод сравнения (я полагаю, он лучше конвейеризует), поскольку он быстрее, чем до четырех сравнений.

unsigned((ch&(~(1<<5))) - 'A') <= 'Z' - 'A' 

Я протестировал несколько разных способов и принял во внимание отсутствие кэша TLB для метода таблицы поиска. Я провел тесты на окнах. Вот времена, когда кодировка была '0' .. 'z':

lookup tbl no tlb miss:                        4.8265 ms
lookup table with tlb miss:                    7.0217 ms
unsigned((ch&(~(1<<5))) - 'A') <= 'Z' - 'A':  10.5075 ms
(ch>='A' && ch<='Z') || (ch>='a' && ch<='z'): 17.2290 ms
isalpha:                                      28.0504 ms

Вы можете четко видеть, что код локали имеет свою стоимость.

Вот времена, когда кодировка была 0..255:

tbl no tlb miss:                               12.6833 ms
unsigned((ch&(~(1<<5))) - 'A') <= 'Z' - 'A':   29.2403 ms
(ch>='A' && ch<='Z') || (ch>='a' && ch<='z'):  34.8818 ms
isalpha:                                       78.0317 ms
tbl with tlb miss:                            143.7135 ms

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

Метод таблицы поиска является лучшим, если сравнивать много символов в строке, но он не намного лучше, чем метод одиночного cmp. Если вы сравниваете символы здесь и там, то промах кэша tlb может сделать метод tbl хуже, чем метод cmp. Одиночный метод cmp работает лучше всего, когда символы с большей вероятностью будут альфа-символами

Вот код:

__forceinline bool isalpha2(BYTE ch) {
    return (ch>='A' && ch<='Z') || (ch>='a' && ch<='z');
}

__forceinline bool isalpha1(BYTE ch) {
    return unsigned((ch&(~(1<<5))) - 'A') <= 'Z' - 'A';
}
bool* aTbl[256];

int main(int argc, char* argv[]) 
{
    __int64 n = 0, cTries = 100000;
    int b=63;
    int ch0=' ', ch1 ='z'+1;
    ch0=0, ch1 = 256;
    // having 255 tables instead of one lets us "flush" the tlb.
    // Intel tlb should have about ~32 entries (depending on model!) in it, 
    // so using more than 32 tables should have a noticable effect.
    for (int i1=0 ; i1<256 ; ++i1) {
        aTbl[i1] = (bool*)malloc(16384);
        for (int i=0 ; i<256 ; ++i)
            aTbl[i1][i] = isalpha(i);
    }

    { CBench Bench("tbl with tlb miss");
        for (int i=1 ; i<cTries ; ++i) {
            for (int ch = ch0 ; ch < ch1 ; ++ ch) 
                n += aTbl[ch][ch];  // tlb buster
        }
    }
    { CBench Bench("tbl no tlb miss");
        for (int i=1 ; i<cTries ; ++i) {
            for (int ch = ch0 ; ch < ch1 ; ++ ch) 
                n += aTbl[0][ch];
        }
    }
    { CBench Bench("isalpha");
        for (int i=1 ; i<cTries ; ++i) {
            for (int ch = ch0 ; ch < ch1 ; ++ ch)
                n += isalpha(ch);
        }
    }
    { CBench Bench("unsigned((ch&(~(1<<5))) - 'A') <= 'Z' - 'A'");
        for (int i=1 ; i<cTries ; ++i) {
            for (int ch = ch0 ; ch < ch1 ; ++ ch)
                n += isalpha1(ch);
        }
    }
    { CBench Bench("(ch>='A' && ch<='Z') || (ch>='a' && ch<='z')");
        for (int i=1 ; i<cTries ; ++i) {
            for (int ch = ch0 ; ch < ch1 ; ++ ch) 
                n += isalpha2(ch);
        }
    }
    return n;
}


class  CBench {
public:
    __declspec(noinline) CBench(CBench* p) : m_fAccumulate(false), m_nTicks(0),
     m_cCalls(0), m_pBench(p), m_desc(NULL), m_nStart(GetBenchMark()) { }
    __declspec(noinline) CBench(const char *desc_in, bool fAccumulate=false) : 
     m_fAccumulate(fAccumulate), m_nTicks(0), m_cCalls(0), m_pBench(NULL), 
     m_desc(desc_in), m_nStart(GetBenchMark()) {    }
    __declspec(noinline) ~CBench() {
        __int64 n = (m_fAccumulate) ? m_nTicks : GetBenchMark() - m_nStart;
        if (m_pBench) {
            m_pBench->m_nTicks += n;
            m_pBench->m_cCalls++;
            return;
        } else if (!m_fAccumulate) {
            m_cCalls++;
        }
        __int64 nFreq;
        QueryPerformanceFrequency((LARGE_INTEGER*)&nFreq);
        double ms = ((double)n * 1000)/nFreq;
        printf("%s took: %.4f ms, calls: %d, avg:%f\n", m_desc, ms, m_cCalls,
             ms/m_cCalls);
    }
    __declspec(noinline) __int64 GetBenchMark(void) {
        __int64 nBenchMark;
        QueryPerformanceCounter((LARGE_INTEGER*)&nBenchMark);
        return nBenchMark;
    }
    LPCSTR     m_desc;
    __int64    m_nStart, m_nTicks;
    DWORD      m_cCalls;
    bool       m_fAccumulate;
    CBench*    m_pBench;
};
3 голосов
/ 20 апреля 2011

Помните первое правило оптимизации: не делайте этого.

Затем запомните второе правило оптимизации, которое применяется очень редко: пока не делайте этого.

Тогда, если вы действительно столкнулись с узким местом и вы определили isalpha как причину, тогда что-то вроде этого может быть быстрее, в зависимости от того, как ваша библиотека реализует функцию. Вам нужно будет измерить производительность в вашей среде и использовать ее только в том случае, если действительно есть ощутимое улучшение. Это предполагает, что вам не нужно тестировать значения вне диапазона unsigned char (обычно 0 ... 255); для этого вам понадобится дополнительная работа.

#include <cctype>
#include <climits>

class IsAlpha
{
public:
    IsAlpha()
    {
        for (int i = 0; i <= UCHAR_MAX; ++i)
            table[i] = std::isalpha(i);
    }

    bool operator()(unsigned char i) const {return table[i];}

private:
    bool table[UCHAR_MAX+1];
};

Использование:

IsAlpha isalpha;

for (int i = 0; i <= UCHAR_MAX; ++i)
    assert(isalpha(i) == bool(std::isalpha(i)));
3 голосов
/ 20 апреля 2011

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

  • правильная работа со знаковыми символами (с использованием отрицательной индексации в таблице поиска)
  • работа с локалями не-ASCII

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

На самом деле, я бы не удивился, если бы макрос или функция, которая просто возвращала результат:

((('a' <= (c)) && ((c) <= 'z')) || (('A' <= (c)) && ((c) <= 'Z')))

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

И является ли isalpha() узким местом?Для кого-нибудь?

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

3 голосов
/ 20 апреля 2011

На самом деле, согласно Plauger в «Стандартной библиотеке C» [91] isalpha часто реализуется с использованием справочной таблицы.Эта книга действительно устарела, но сегодня она все еще может иметь место.Вот его предлагаемое определение для исальфы

Функция

int isalpha(int c)
{
    return (_Ctype[c] & (_LO|_UP|_XA));
}

Макро

#define isalpha(c) (_Ctype[(int)(c)] & (_LO|_UP|_XA))
0 голосов
/ 20 апреля 2011

Я думаю, вы можете реализовать isalpha гораздо более просто, чем с помощью таблицы поиска.Используя тот факт, что символы 'a' - 'z' и 'A' - 'Z' являются последовательными в ASCII , достаточно простого теста, подобного этому:

char c ;
// c gets some value
if(('A'<=c && 'Z'>=c) || ('a'<=c && 'z'>=c)) // c is alpha

Обратите внимание, что этоне учитывает разные локали.

0 голосов
/ 20 апреля 2011

Если вы ищете буквенные символы, aZ, это намного меньше символов, чем ваш массив 255.Вы можете вычесть «A» из рассматриваемого символа ASCII (самый низкий буквенный символ), который будет индексом в вашем массиве.например, «B» - «A» равно 1. Тест, если отрицательный, не является альфа.Если значение больше, чем ваша максимальная альфа ('z'), то это не альфа.

Если вы вообще используете юникод, этот метод не будет работать.

...