我正在编写一个高性能解析器,在我看来Int32.Parse
可能太慢了。我写了一个假设正确输入的简单版本,它表现得更好。那么我应该创建自己的版本吗?或者还有另一种更快的方法吗?
我的方法是这样的:
// parse simple int, assuming relatively correct input (i.e. all digits)
public static int ParseInt32Simply(string str) {
if (str == null) throw new ArgumentNullException("str");
if (str.Length == 0) throw new ArgumentException("str is empty");
int sign = 1, index = 0;
if (str[0] == '-') { sign = -1; index = 1; }
else if (str[0] == '+') { index = 1; }
int result = 0;
for (; index < str.Length; ++index) {
result = 10 * result + (str[index] - '0');
}
if (result < 0) throw new OverflowException(str + " is too large for Int32");
return result * sign;
}
我的结果与内置的等价物非常不同:
Int32.Parse took 8.2775453 seconds
ParseInt32Simply took 0.6511523 seconds
Int32.Parse took 6.7625807 seconds
ParseInt32Simply took 0.4677390 seconds
(在我的机器上运行2500万次迭代;运行VS 2008 SP1的P4 3 GHz)
那么,我应该使用我的版本吗?或者我可以使用另一种方法吗?
答案 0 :(得分:6)
您是否已分析过您的代码以确定ParseInt32实际上是瓶颈?除非你肯定知道你会看到一个好处,否则我不会替换你正在编码的环境的“标准库”的一部分。
答案 1 :(得分:5)
.net Int32.Parse
非常非常快速成功。
当它失败时会引发异常 - 然后它非常慢,因为异常很慢。
你需要扩展你的测试 - 你需要检查一下好的和坏的字符串模式的时间,这类似于你需要它做的。
如果您非常确定所有字符串都是有效的整数,则Int32.Parse
是可行的方法。如果您怀疑只有极少数可以忽略不计,那么在循环中使用Int32.TryParse
而不是try-catch
要快得多。
通常情况下,如果您的try-catch
位于循环之外,请使用Int32.Parse
- 您将获得异常并在第一次获得无效值时停止。
如果您的try-catch
位于循环内,请使用Int32.TryParse
。
Int32.Parse
和Int32.TryParse
都经过高度优化并且相对成熟 - 除非您有专业情况,否则我认为它们很难改进。
答案 2 :(得分:4)
我的观点是,如果节省的时间对您的应用程序而言意义重大并且有益,那就去吧。
我们在XML解析方面存在一个模糊的类似问题,并且出于性能原因选择手动执行,但它基于已知的环境 - 我们正在提供XML,因此我们可以相当安全地在解析中使用快捷方式。
显然风险在于它不太可能像标准库版本那样完整,因此需要让团队的新开发人员意识到这一点,以免他们做些什么来打破它。
答案 3 :(得分:4)
是的 - 您可以使用自己的解析int版本,只要您100%确定源数据是您可以控制的(因此始终符合您的Int32格式)。此外,您应该使用自己的代码来隔离世界其他地方,因为如果您在某个库中发布了这个代码,那么人们可能希望拥有Int32.Parse的标准行为。如果你不能提供,那对他们没有好处。然而,正如这里的许多人所暗示的那样,如果你试图挤出你的大部分表现,你应该确定这是真正需要做的事情。但是,您可能比这里的任何人都更了解自己的代码。
我个人会尝试避免更改解析。如果还有其他瓶颈,那么首先可能需要进行调查。
答案 4 :(得分:4)
如果您的测试是可验证的,并且您确实需要性能提升(例如,您将该函数调用为每秒数万次),那么就不需要了。
我只是更改名称...因为 ParseInt32Simply 没有告诉维护程序员什么。我认为像 TrustedSourceInt32Parse 或 GuaranteedInt32Parse 之类的名称或其他类似的名称是更好的名称。
答案 5 :(得分:3)
我认为这里的主要问题是你的句子假定正确的输入。从阅读代码开始,它似乎无法正确处理“12x”。
Int32.Parse有很多东西可以验证输入,甚至可以记录你的文化来处理一些文化差异,虽然我不能想到任何专门用于Int32的东西。
您确定代码中的瓶颈是Int32吗?
答案 6 :(得分:1)
你如何衡量速度?我试过这个:
Stopwatch sw = new Stopwatch();
Random rand = new Random();
for (int n = 0; n < 10; n++)
{
sw.Start();
for (int i = 0; i < 1000000; i++)
{
ParseInt32Simply(rand.Next().ToString());
}
sw.Stop();
Console.WriteLine(sw.Elapsed.Ticks + " - ParseInt32Simply");
sw.Reset();
sw.Start();
for (int i = 0; i < 1000000; i++)
{
int.Parse(rand.Next().ToString());
}
sw.Stop();
Console.WriteLine(sw.Elapsed.Ticks + " - int.Parse");
sw.Reset();
Console.WriteLine();
}
结果完全不同:
2932852 - ParseInt32Simply
4684522 - int.Parse
3003988 - ParseInt32Simply
4666928 - int.Parse
2892545 - ParseInt32Simply
4660209 - int.Parse
2888998 - ParseInt32Simply
4636007 - int.Parse
2955727 - ParseInt32Simply
4668501 - int.Parse
2929210 - ParseInt32Simply
4653799 - int.Parse
2893706 - ParseInt32Simply
4671503 - int.Parse
2899547 - ParseInt32Simply
4633957 - int.Parse
你的简单方法仍然更快,但不到2倍(实际上这是非常好的表现!)。
答案 7 :(得分:1)
如果您解析的格式是您知道的有效数字,那么您确实可以编写更快的自定义解析器。我曾为同一目的写过一个Double.Parse函数。从最低有效数字开始更快。这样你就可以增加解析数字的功效。
我已经创建了这个的快速实现,
public static Int32 ParseValidNumberAsInt32(string str)
{
if (str == null)
throw new ArgumentNullException("str");
if (str.Length == 0)
throw new ArgumentException("str is empty");
Int32 result = 0;
Int32 currentPower = 1;
Boolean isNegative = str[0] == '-';
for (int currentCharIndex = str.Length - 1; currentCharIndex > 0; currentCharIndex--)
{
result += (str[currentCharIndex] - '0') * currentPower;
currentPower *= 10;
}
return isNegative ? -1 * result : result + ((str[0] - '0') * currentPower);
}
如果你真的想要速度,你可以写一个不安全的实现..
如果您解析一个大文件,您可以将文件作为原始字节读取并使用它们。这将使它快得多(不转换为unicode字符串,不在行中拆分字符串,不在子字符串中拆分行,不解析子字符串),但是你将失去可维护性。
答案 8 :(得分:1)
请查看此博客文章:Karl Seguin撰写的Fast string to integer conversion。
答案 9 :(得分:0)
对null和空字符串的验证是不够的,你应该检查参数是否是有效的整数。
答案 10 :(得分:0)
您的测试结果如何? 看来你的测试还不行。
当我循环50000次时,我只有一点点差别 然后我有大约30K的差异,有利于你的自定义方法, 但这对于普通方法的优点是可以忽略的