【问题标题】:String (const char*, size_t) to int?字符串 (const char*, size_t) 到 int?
【发布时间】:2012-03-08 15:43:29
【问题描述】:

将 (const char*, size_t) 表示的字符串转换为 int 的最快方法是什么?

字符串不是以null结尾的。 这两种方式都涉及我想避免的字符串副本(以及更多)。

是的,这个函数每秒被调用几百万次。 :p

int to_int0(const char* c, size_t sz)
{
    return atoi(std::string(c, sz).c_str());
}

int to_int1(const char* c, size_t sz)
{
    return boost::lexical_cast<int>(std::string(c, sz));
}

【问题讨论】:

  • 如果您使用的是 C++11,则不会有副本,因为将在传递给 atoi 的临时字符串上调用移动构造函数
  • @TonyTheLion:您仍然需要复制一次数据以创建string 对象(或以零结尾的字符串)。

标签: c++


【解决方案1】:

给定一个这样的计数字符串,您可以通过自己进行转换来获得一点速度。根据代码需要的健壮程度,这可能相当困难。目前,让我们假设最简单的情况——我们确定字符串是有效的,只包含数字(现在没有负数)并且它表示的数字总是在 int 的范围内。对于这种情况:

int to_int2(char const *c, size_t sz) { 
    int retval = 0;
    for (size_t i=0; i<sz; i++)
        retval *= 10;
        retval += c[i] -'0';
    }
    return retval;
}

从那里,您可以随心所欲地处理复杂的事情——处理前导/尾随空格,'-'(但正确处理 2 的补码中的最大负数并不总是微不足道的 [编辑:见 Nawaz 的回答对此的一种解决方案]),数字分组等。

【讨论】:

    【解决方案2】:

    另一个 slow 版本,用于 uint32:

    void str2uint_aux(unsigned& number, unsigned& overflowCtrl, const char*& ch)
    {
        unsigned digit = *ch - '0';
        ++ch;
    
        number = number * 10 + digit;
    
        unsigned overflow = (digit + (256 - 10)) >> 8;
        // if digit < 10 then overflow == 0
        overflowCtrl += overflow;
    }
    
    unsigned str2uint(const char* s, size_t n)
    {
        unsigned number = 0;
        unsigned overflowCtrl = 0;
    
        // for VC++10 the Duff's device is faster than loop
        switch (n)
        {
        default:
            throw std::invalid_argument(__FUNCTION__ " : `n' too big");
    
        case 10: str2uint_aux(number, overflowCtrl, s);
        case  9: str2uint_aux(number, overflowCtrl, s);
        case  8: str2uint_aux(number, overflowCtrl, s);
        case  7: str2uint_aux(number, overflowCtrl, s);
        case  6: str2uint_aux(number, overflowCtrl, s);
        case  5: str2uint_aux(number, overflowCtrl, s);
        case  4: str2uint_aux(number, overflowCtrl, s);
        case  3: str2uint_aux(number, overflowCtrl, s);
        case  2: str2uint_aux(number, overflowCtrl, s);
        case  1: str2uint_aux(number, overflowCtrl, s);
        }
    
        // here we can check that all chars were digits
        if (overflowCtrl != 0)
            throw std::invalid_argument(__FUNCTION__ " : `s' is not a number");
    
        return number;
    }
    

    为什么这么慢?因为它一个接一个地处理字符。如果我们保证可以访问高达s+16 的字节,我们可以对*ch - '0'digit + 246 使用矢量化。
    就像在这段代码中一样:

        uint32_t digitsPack = *(uint32_t*)s - '0000';
        overflowCtrl |= digitsPack | (digitsPack + 0x06060606); // if one byte is not in range [0;10), high nibble will be non-zero
        number = number * 10 + (digitsPack >> 24) & 0xFF;
        number = number * 10 + (digitsPack >> 16) & 0xFF;
        number = number * 10 + (digitsPack >> 8) & 0xFF;
        number = number * 10 + digitsPack & 0xFF;
        s += 4;
    

    范围检查的小更新:
    第一个 sn-p 在每次迭代时都有冗余移位(或mov),所以它应该是

    unsigned digit = *s - '0';
    overflowCtrl |= (digit + 256 - 10);
    ...
    if (overflowCtrl >> 8 != 0) throw ...
    

    【讨论】:

    • 即使这也可能有每次迭代分支(如果没有内联,则调用 str2uint_aux)。它还对可以处理的数字大小施加了任意限制(例如,它不适用于 64 位类型)。这可以扩大,但只能以将已经很大的例程增加大约一倍为代价。处理带符号的数字会增加更多。这可能是值得的,但几乎可以很容易地使用足够的额外缓存来降低整体速度。
    • @JerryCoffin:这段代码的重点是不用s[i] &gt;= '0' &amp;&amp; s[i] &lt;= '9'。完整或部分循环展开、C 宏与内联函数 - 是此代码的次要方面。
    • 我明白你在追求什么,是的,将溢出合并在一起而不是单独处理它们是一个不错的主意——但除非你预计会有相当多的坏数据,否则分支预测在这里会很好用,因此您的额外工作可能收效甚微(如果有的话)。
    • @Nawaz:对于非数字字符,overflowCtrl 变量将非零。无需使用 comparisons 和分支 - 只需减去 '0',添加 246,如果不是数字,结果将大于 255。所以,你可以收集溢出并立即检查它们。
    • @Nawaz: digit 是无符号的,所以'*' - '0'0xf...fffffffa,它大于0xff
    【解决方案3】:

    最快:

    int to_int(char const *s, size_t count)
    {
         int result = 0;
         size_t i = 0 ;
         if ( s[0] == '+' || s[0] == '-' ) 
              ++i;
         while(i < count)
         {
              if ( s[i] >= '0' && s[i] <= '9' )
              {
                  //see Jerry's comments for explanation why I do this
                  int value = (s[0] == '-') ? ('0' - s[i] ) : (s[i]-'0');
                  result = result * 10 + value;
              }
              else
                  throw std::invalid_argument("invalid input string");
              i++;
         }
         return result;
    } 
    

    由于在上面的代码中,每次迭代都会比较(s[0] == '-'),我们可以通过在循环中将result计算为负数来避免这种情况,如果s[0]确实是@987654328,则返回result @,否则返回-result(这使它成为一个正数,应该是):

    int to_int(char const *s, size_t count)
    {
         size_t i = 0 ;
         if ( s[0] == '+' || s[0] == '-' ) 
              ++i;
         int result = 0;
         while(i < count)
         {
              if ( s[i] >= '0' && s[i] <= '9' )
              {
                  result = result * 10  - (s[i] - '0');  //assume negative number
              }
              else
                  throw std::invalid_argument("invalid input string");
              i++;
         }
         return s[0] == '-' ? result : -result; //-result is positive!
    } 
    

    这是一种进步!


    在 C++11 中,您可以使用 std::stoi 系列中的任何函数。还有std::to_stringfamily。

    【讨论】:

    • 请注意,对于二进制补码系统上的最大负数,这将失败...
    • @Abyx:就它的作用而言,它的速度与你能得到的一样快。我发布的内容可能会快一点,但只是以不那么健壮为代价(即不尝试处理负数或包含除数字以外的任何内容的字符串)。
    • 当然:让我们暂时假设 16 位整数和一个包含 "-32768" 的字符串。这会尝试将 32768 转换为 int,但这会溢出,因为最大的 16 位有符号 int 是 32767
    • 是的,如果字符串中的数字表示的数字太大而无法放入 int 中,溢出基本上是不可避免的。然而,在这种情况下,字符串表示 可以 适合 int 的数字,但该函数仍然不会产生正确的结果(至少不可靠)。
    • 是的,我想就是这样。希望@Abyx(或投票反对的人)现在将其删除。还有一个小问题:为了获得最大速度,可能最好有两个单独的循环,一个用于正数,另一个用于负数,因此您只需执行一次测试而不是每次迭代。
    【解决方案4】:
    llvm::StringRef s(c,sz);
    int n;
    s.getAsInteger(10,n);
    return n;
    

    http://llvm.org/docs/doxygen/html/classllvm_1_1StringRef.html

    【讨论】:

    • 因为它需要真正快,最好不要使用它,因为在引用的子函数GetAsUnsignedInteger()中有基数自动检查的调用和分支。
    【解决方案5】:

    如果您不想避免字符串复制,则必须编写自定义例程或使用 3rd 方库。

    您可能不想从头开始编写 atoi(仍然可能在此处产生错误),因此我建议从公共领域或 BSD 许可的代码中获取现有的 atoi 并对其进行修改。例如,您可以从FreeBSD cvs tree 获取现有的 atoi。

    【讨论】:

      【解决方案6】:

      如果你经常运行这个函数,我敢打赌你会多次解析同一个数字。我的建议是将字符串 BCD 编码为静态字符缓冲区(你知道它不会很长,因为 atoi 只能处理 +-2G)当少于 X 位时(X=8 用于 32 位查找, X=16 用于 64 位查找)然后将缓存放在哈希映射中。

      当您完成第一个版本时,您可能会发现一些不错的优化,例如完全跳过 BCD 编码并仅使用字符串中的 X 字符(当字符串长度 atoi。

      编辑:...或替代 atoi 以替代 Jerry Coffin 的解决方案,这与他们来的一样快。

      【讨论】:

        猜你喜欢
        • 2010-09-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多