现在我正在开发一个项目,需要将整数转换为基本62字符串,每秒多次。转换完成得越快越好。
问题是我很难让自己的基本转换方法快速和可靠。如果我使用字符串,它通常可靠且运行良好,但速度很慢。如果我使用char数组,它通常要快得多,但它也非常混乱,并且不可靠。 (它会产生堆损坏,比较应匹配的字符串返回负数等)
那么从一个非常大的整数转换为一个基本62键的最快,最可靠的方法是什么?将来,我计划在我的应用程序中使用SIMD模型代码,这个操作是否可以并行化?
编辑:此操作每秒执行数百万次;一旦操作完成,它就会再次作为循环的一部分开始,因此运行得越快越好。被转换的整数具有任意大小,并且可以很容易地与128位整数(或更大)一样大。
编辑:这是我目前正在使用的功能。
char* charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
int charsetLength = (int)(strlen(charset));
//maxChars is an integer specifying the maximum length of the key
char* currentKey = new char[maxChars];
void integerToKey(unsigned long long location)
{
unsigned long long num = location;
int i = 0;
for(; num > 0; i++)
{
currentKey[i] = charset[num % (charsetLength)];
num /= charsetLength + 1;
}
currentKey[i + 1] = '\0';
}
我从一个属于我的应用程序的类中删除了这个,并且修改了一些代码,以便它没有理由没有它的拥有类。
答案 0 :(得分:5)
在我的头脑中,我希望实现看起来很像这样。
const char lookUpTable[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
std::string ConvertToBase62( int integer )
{
char res[MAX_BASE62_LENGTH];
char* pWritePos = res;
int leftOver = integer;
while( leftOver )
{
int value62 = leftOver % 62;
*pWritePos = lookUpTable[value62];
pWritePos++;
leftOver /= value62;
}
*pWritePos = 0;
return std::string( res );
}
目前这不是SIMD可以优化的。没有SIMD模数。
如果我们自己做Modulo,我们可以按如下方式重写循环。
while( leftOver )
{
const int newLeftOver = leftOver / 62;
int digit62 = leftOver - (62 * newLeftOver);
*pWritePos = lookUpTable[digit62];
pWritePos++;
leftOver = newLeftOver;
}
现在我们有一些很容易SIMD的东西,如果不是那个查找......
虽然您可以通过同时为多个值执行模数来获得良好的速度提升。它甚至可能值得第二次展开循环,因此您可以在前一组计算时处理接下来的4个模数(由于指令延迟)。你应该能够以这种方式非常有效地隐藏延迟。 #
如果我能想出一种消除表格查找的方法,我会回来的......
编辑:也就是说,从32位整数中可以得到的最大base62位数是6,你应该能够完全展开循环并同时处理所有6位数。我不完全确定SIMD会在这里给你带来多少胜利。这是一个有趣的实验,但我确实怀疑你会在上面的循环中获得更多的加速。如果有人没有在我的开发机器的键盘上倒茶,那么尝试它会很有趣:(
编辑2:我想到了。编译器使用可怕的魔术数字可以对常量/ 62进行精心优化...所以我甚至不认为上面的循环会产生分歧。
答案 1 :(得分:5)
可能你想要的是itoa的某个版本。这是一个链接,显示了性能测试的各种版本的itoa: http://www.jb.man.ac.uk/~slowe/cpp/itoa.html
总的来说,我知道有两种方法可以做到这一点。一种方法是执行连续分割以一次剥离一个数字。另一种方法是预先计算“块”中的转换。因此,您可以预先计算一个大小为62 ^ 3的int到文本转换块,然后一次执行数字3。如果你有效地进行内存布局和查找,那么在运行时可能会稍快一些,但会导致启动损失。
答案 2 :(得分:4)
我感觉很糟糕,因为我无法记住我最初发现这个的地方,但我一直在我的代码中使用它,并且发现它非常快。你可以修改一下,以确保在某些地方更有效率。
哦,我感觉更糟,因为这是用Java编写的,但快速的c& p和重构可以让它在c ++中运行
public class BaseConverterUtil {
private static final String baseDigits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
public static String toBase62( int decimalNumber ) {
return fromDecimalToOtherBase( 62, decimalNumber );
}
public static String toBase36( int decimalNumber ) {
return fromDecimalToOtherBase( 36, decimalNumber );
}
public static String toBase16( int decimalNumber ) {
return fromDecimalToOtherBase( 16, decimalNumber );
}
public static String toBase8( int decimalNumber ) {
return fromDecimalToOtherBase( 8, decimalNumber );
}
public static String toBase2( int decimalNumber ) {
return fromDecimalToOtherBase( 2, decimalNumber );
}
public static int fromBase62( String base62Number ) {
return fromOtherBaseToDecimal( 62, base62Number );
}
public static int fromBase36( String base36Number ) {
return fromOtherBaseToDecimal( 36, base36Number );
}
public static int fromBase16( String base16Number ) {
return fromOtherBaseToDecimal( 16, base16Number );
}
public static int fromBase8( String base8Number ) {
return fromOtherBaseToDecimal( 8, base8Number );
}
public static int fromBase2( String base2Number ) {
return fromOtherBaseToDecimal( 2, base2Number );
}
private static String fromDecimalToOtherBase ( int base, int decimalNumber ) {
String tempVal = decimalNumber == 0 ? "0" : "";
int mod = 0;
while( decimalNumber != 0 ) {
mod = decimalNumber % base;
tempVal = baseDigits.substring( mod, mod + 1 ) + tempVal;
decimalNumber = decimalNumber / base;
}
return tempVal;
}
private static int fromOtherBaseToDecimal( int base, String number ) {
int iterator = number.length();
int returnValue = 0;
int multiplier = 1;
while( iterator > 0 ) {
returnValue = returnValue + ( baseDigits.indexOf( number.substring( iterator - 1, iterator ) ) * multiplier );
multiplier = multiplier * base;
--iterator;
}
return returnValue;
}
}
答案 3 :(得分:2)
上面有逆转问题 - 低顺序在生成的字符串中排在第一位 - 我不知道这是否真的是一个问题,因为它取决于生成的字符串的后续使用。
通常可以通过radix * radix块来加速这种基数转换 在您的情况下,需要char [2] [62 * 62]。该数组可以在初始化时构建(它是const)。
这必须是基准测试。划分成本曾经是巨大的,所以节省一半的分歧是肯定的胜利。这取决于缓存这个7000+字节表的能力和除法的成本。
答案 4 :(得分:1)
如果您遇到堆损坏,那么您遇到的问题超出了此处显示的代码。
您可以通过在开始之前为字符串保留字符串空间来使字符串类更快,使用string :: reserve。
您的字符串以相反的顺序出现,低位字符-62位是字符串中的第一个字符。这可以解释您的比较问题。
答案 5 :(得分:1)
您的实施速度非常快。我会建议做一些改变:
void integerToKey(unsigned long long location)
{
unsigned long long num = location;
int i = 0;
for(; num > 0; i++)
{
currentKey[i] = charset[num % (charsetLength)];
num /= charsetLength; // use charsetLength
}
currentKey[i] = '\0'; // put the null after the last written char
}
第一个更改(除以charsetLength
)可能导致您的字符串比较问题。使用原始代码(除以charsetLength + 1
),可能会有不同的整数值错误地转换为相同的字符串。对于基数62,则0和62都将编码为"0"
。
很难说上述任何一项更改都会导致报告的堆损坏问题,而且没有更多的上下文(例如maxChars
的值)。
此外,您应该知道上面的代码将以相反的顺序写入字符串表示的数字(尝试使用基数10并转换一个数字,如12345,看看我的意思)。不过,这对您的申请可能无关紧要。
答案 6 :(得分:0)
这是我在php中用于Base 10到N(本例中为62)的解决方案 我的整个帖子都在这里:http://ken-soft.com/?p=544
public class BNID {
// Alphabet of Base N (This is a Base 62 Implementation)
var $bN = array(
'0','1','2','3','4','5','6','7','8','9',
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'
);
var $baseN;
function __construct() {
$this->baseN = count($this->bN);
}
// convert base 10 to base N
function base10ToN($b10num=0) {
$bNnum = "";
do {
$bNnum = $this->bN[$b10num % $this->baseN] . $bNnum;
$b10num /= $this->baseN;
} while($b10num >= 1);
return $bNnum;
}
// convert base N to base 10
function baseNTo10($bNnum = "") {
$b10num = 0;
$len = strlen($bNnum);
for($i = 0; $i < $len; $i++) {
$val = array_keys($this->bN, substr($bNnum, $i, 1));
$b10num += $val[0] * pow($this->baseN, $len - $i - 1);
}
return $b10num;
}
}
答案 7 :(得分:0)
我正在寻找另一个答案,因为我尝试的几个答案并没有产生我预期的输出。尽管如此,这是为了可读性而非速度而优化的。
string toStr62(unsigned long long num) {
string charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
int base = charset.length();
string str = num ? "" : "0";
while (num) {
str = charset.substr(num % base, 1) + str;
num /= base;
}
return str;
}