我有一个函数,我写的是从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。看起来我应该能够减少记忆和/或提高速度,但我不确定如何。
如果有人能帮我弄清楚如何进一步优化这个表格,我会非常感激。
答案 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)。