有没有办法提高此查找的速度或效率? (C / C ++)

时间:2009-11-09 21:58:13

标签: c++ optimization lookup modulo

我有一个函数,我写的是从64位整数转换为基本62字符串。最初,我是这样实现的:

char* charset = " 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
int charsetLength = strlen(charset);

std::string integerToKey(unsigned long long input)
{
    unsigned long long num = input;
    string key = "";

    while(num)
    {
        key += charset[num % charsetLength];
        num /= charsetLength;
    }

    return key;
}

然而,这太慢了。

我通过提供生成查找表的选项来提高速度。该表大小约为62个 4 字符串,生成方式如下:

// Create the integer to key conversion lookup table
int lookupChars;

if(lookupDisabled)
    lookupChars = 1;
else
    largeLookup ? lookupChars = 4 : lookupChars = 2;

lookupSize = pow(charsetLength, lookupChars);
integerToKeyLookup = new char*[lookupSize];

for(unsigned long i = 0; i < lookupSize; i++)
{
    unsigned long num = i;
    int j = 0;

    integerToKeyLookup[i] = new char[lookupChars];

    while(num)
    {
        integerToKeyLookup[i][j] = charset[num % charsetLength];
        num /= charsetLength;

        j++;
    }

    // Null terminate the string
    integerToKeyLookup[i][j] = '\0';
}

然后实际转换如下:

std::string integerToKey(unsigned long long input)
{
    unsigned long long num = input;
    string key = "";

    while(num)
    {
        key += integerToKeyLookup[num % lookupSize];
        num /= lookupSize;
    }

    return key;
}

这大大提高了速度,但我仍然相信它可以改进。 32位系统上的内存使用量约为300 MB,64位系统上的内存使用量超过400 MB。看起来我应该能够减少记忆和/或提高速度,但我不确定如何。

如果有人能帮我弄清楚如何进一步优化这个表格,我会非常感激。

8 个答案:

答案 0 :(得分:6)

使用某种字符串构建器而不是重复连接到“密钥”将提供显着的速度提升。

答案 1 :(得分:6)

您可能需要提前为string key预留内存。这可以为您带来不错的性能提升,以及内存利用率的提升。无论何时在std::string上调用追加运算符,如果必须重新分配,它可能会使内部缓冲区的大小加倍。这意味着每个字符串可能占用的内存比存储字符所需的内存大得多。您可以通过提前为字符串保留内存来避免这种情况。

答案 2 :(得分:5)

我同意Rob Walker的意见 - 你专注于改善错误领域的表现。字符串是最慢的部分。

我对代码进行了定时(你的原始版本被破坏了,顺便说一句),你的原始文件(修复后)是44982140个周期,100000次查找,以下代码大约是13113670。

const char* charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
#define CHARSET_LENGTH 62

// maximum size = 11 chars
void integerToKey(char result[13], unsigned long long input)
{
    char* p = result;
    while(input > 0)
    {
        *p++ = charset[input % CHARSET_LENGTH];
        input /= CHARSET_LENGTH;
    }

    // null termination
    *p = '\0';
    // need to reverse the output
    char* o = result;
    while(o + 1 < p)
        swap(*++o, *--p);
}

答案 3 :(得分:2)

这几乎就是如何不这样做的教科书案例。在循环中连接字符串是一个坏主意,因为追加不是特别快,并且因为你经常分配内存。

注意:您的问题表明您正在转换为base-62,但代码似乎有63个符号。你想做什么?

给定64位整数,您可以计算出结果中不需要超过11位数,因此使用静态12字符缓冲区肯定有助于提高速度。另一方面,你的C ++库可能有一个长期等效的ultoa,这将是非常理想的。


编辑:这是我掀起的事情。它允许您指定任何所需的基数:

std::string ullToString(unsigned long long v, int base = 64) {
    assert(base < 65);
    assert(base > 1);
    static const char digits[]="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/";
    const int max_length=65;
    static char buffer[max_length];

    buffer[max_length-1]=0;
    char *d = buffer + max_length-1;
    do {
        d--;
        int remainder = v % base;
        v /= base;
        *d = digits[remainder];
    } while(v>0);

    return d;
}

这只会创建一个std :: string对象,并且不会不必要地移动内存。它目前没有对输出进行零填充,但是将其更改为您需要的多个输出数字是微不足道的。

答案 4 :(得分:1)

您不需要将输入复制到num,因为您按值传递输入。您还可以在编译时计算字符集的长度,每次调用函数时都不需要在运行时计算它。

但这些都是非常小的性能问题。我认为你可以获得的最重要的帮助是避免循环中的字符串连接。构造键字符串时,将字符串构造函数传递给结果字符串的长度,以便只有一个字符串分配。然后在循环中,当你连接到字符串时,你将不会重新分配。

如果将目标字符串作为参考参数,或者甚至像标准算法那样使用两个迭代器,则可以使效率更高效。但这可以说是一个太过分了。

顺便说一句,如果输入的值为零,该怎么办?你甚至不会进入循环;不应该键,然后是“0”?

我看到传入的输入值不能为负数,但我们注意到:C余数运算符不是模运算符。

答案 5 :(得分:1)

为什么不使用base64库?真的重要的是63等于'11'而不是更长的字符串吗?

size_t base64_encode(char* outbuffer, size_t maxoutbuflen, const char* inbuffer, size_t inbuflen);

std::string integerToKey(unsigned long long input) {
    char buffer[14];
    size_t len = base64_encode(buffer, sizeof buffer, (const char*)&input, sizeof input);
    return std::string(buffer, len);
}

是的,每个字符串都以相同的大小结束。如果你不想要它,剥去等号。 (如果需要解码数字,请记得将其添加回来。)

当然,我真正的问题是你为什么要转换固定宽度为8byte的值而不直接使用它作为“key”而不是变长字符串值?

脚注:我很清楚这方面的问题。他没有说明密钥将用于什么,因此我认为它不会用于不同终端机器之间的网络通信。

答案 6 :(得分:1)

如果你可以添加两个符号以便它转换为base-64,你的模数和除法运算将变成一个位掩码和移位。比分裂快得多。

答案 7 :(得分:1)

如果您需要的只是一个短字符串键,转换为base-64数字会加速很多事情,因为div / mod 64非常便宜(shift / mask)。