最快的基本转换方法?

时间:2009-08-05 19:50:45

标签: c++ base

现在我正在开发一个项目,需要将整数转换为基本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';
}

我从一个属于我的应用程序的类中删除了这个,并且修改了一些代码,以便它没有理由没有它的拥有类。

8 个答案:

答案 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;
}
相关问题