字符串 (const char*, size_t) 到 int?
Posted
技术标签:
【中文标题】字符串 (const char*, size_t) 到 int?【英文标题】:String (const char*, size_t) to 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
对象(或以零结尾的字符串)。
【参考方案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] >= '0' && s[i] <= '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_string
family。
【讨论】:
请注意,对于二进制补码系统上的最大负数,这将失败... @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 的解决方案,这与他们来的一样快。
【讨论】:
以上是关于字符串 (const char*, size_t) 到 int?的主要内容,如果未能解决你的问题,请参考以下文章