c ++ isalpha查找表

时间:2011-04-20 08:05:45

标签: c++ char lookup-tables

实现查找表的最简单方法是使用具有256个字符(256字节)数组的查找表来检查字符是否为alpha?我知道我可以使用isalpha函数,但是查找表可以更高效,需要一次比较而不是多次比较。我正在考虑使用char十进制转换来对应索引,并直接检查它是否与char等效。

6 个答案:

答案 0 :(得分:3)

实际上,根据Plauger的说法,“标准C库”[91] isalpha通常使用查找表来实现。这本书真的过时了,但今天可能仍然如此。这是他提出的isalpha的定义

<强>功能

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

<强>宏

#define isalpha(c) (_Ctype[(int)(c)] & (_LO|_UP|_XA))

答案 1 :(得分:3)

您的编译器库的实现可能非常有效,并且可能已经在大多数情况下使用查找表,但是如果您要自己执行{{{}那么可能会处理某些可能有点棘手的情况。 1}}:

  • 正确处理签名字符(在查找表上使用否定索引)
  • 处理非ASCII语言环境

您可能不需要处理非ASCII语言环境,在这种情况下,您可能(可能)可以略微改进库。

事实上,如果一个宏或函数只返回结果:

,我不会感到惊讶
isalpha()

可能比表查找更快,因为它不必打击内存。但我怀疑它会以任何有意义的方式更快,并且很难衡量差异,除非在基准测试中除了((('a' <= (c)) && ((c) <= 'z')) || (('A' <= (c)) && ((c) <= 'Z'))) 调用之外什么都没做(这可能也会改善表查找结果,因为表可能会在许多测试的缓存中。)

isalpha()真的是瓶颈吗?对任何人?

只需使用编译器库中的那个。

答案 2 :(得分:3)

请记住优化的第一条规则:不要这样做。

然后记住第二条优化规则,很少应用:不要这样做。

然后,如果您确实遇到了瓶颈并且您已将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 :(得分:3)

我一直使用这种单一的比较方法(我认为它管道更好),因为它比最多四次比较更快。

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

我对几种不同的方法进行了基准测试,并考虑了查找表方法的TLB缓存未命中。我在Windows上运行基准测试。以下是charset为'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

您可以清楚地看到区域设置代码有成本。

以下是charset为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未命中更多。您还可以看到,当角色为alpha时,单个cmp方法效果更好。

如果比较一行中的许多字符,查找表方法是最好的,但它并不比单个cmp方法好多少。如果你在这里和那里比较字符,那么tlb缓存未命中可能会使tbl方法比单个cmp方法更糟糕。当字符更可能是alpha时,单个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;
};

答案 4 :(得分:0)

如果您正在寻找字母字符a-Z,那么字符数比255数组少得多。您可以从相关的ASCII字符(最低的字母字符)中减去“A”,这将是您的数组的索引。例如'B' - 'A'是1.测试,如果是否定的,则不是alpha。如果大于你的最大alpha('z'),那么它不是alpha。

如果您正在使用unicode,则此方法将无效。

答案 5 :(得分:0)

我认为你可以比查找表更简单地实现isalpha。使用字符'a' - 'z'和'A' - 'Z'在ASCII中连续的事实,这样的简单测试就足够了:

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

请注意,这不会考虑不同的区域设置。