我正在寻找将字符串转换为各种数据类型的最快(通用方法)。
我正在解析由某物生成的大型文本数据文件(文件大小为几兆字节)。此特定函数读取文本文件中的行,根据分隔符将每一行解析为列,并将解析后的值放入 .NET 数据表中。稍后将其插入数据库。FAR 的瓶颈是字符串转换(Convert 和 TypeConverter)。
我必须采用动态方式(即远离“Convert.ToInt32”等...),因为我永远不知道文件中将包含哪些类型。类型由运行时的早期配置确定。
到目前为止,我已经尝试了以下方法,并且都需要几分钟来解析文件。请注意,如果我注释掉这一行,它只会在几百毫秒内运行。
row[i] = Convert.ChangeType(columnString, dataType);
和
TypeConverter typeConverter = TypeDescriptor.GetConverter(type);
row[i] = typeConverter.ConvertFromString(null, cultureInfo, columnString);
如果有人知道像这样通用的更快方法,我想知道它。或者,如果我的整个方法由于某种原因很糟糕,我愿意接受建议。但是请不要向我指出使用硬编码类型的非泛型方法;这根本不是一个选择。
UPDATE - 多线程提高性能测试
为了提高性能,我考虑将解析任务拆分为多个线程。我发现速度有所提高,但仍然没有我希望的那么快。但是,对于那些感兴趣的人,这是我的结果。
系统:
Intel Xenon 3.3GHz 四核 E3-1245
内存:12.0 GB
Windows 7 企业版 x64
测试:
测试功能是这样的:
(1) 接收一个字符串数组。(2) 用分隔符分割字符串。(3) 将字符串解析为数据类型,并将它们存储在一行中。(4) 向数据表中添加行。(5) 重复(2)-(4)直到完成。
该测试包括 1000 个字符串,每个字符串被解析为 16 列,因此总共有 16000 个字符串转换。我测试了单线程、4 线程(因为四核)和 8 线程(因为超线程)。因为我只是在这里处理数据,所以我怀疑添加更多的线程会不会有任何好处。因此,对于单个线程,它解析 1000 个字符串,4 个线程每个解析 250 个字符串,8 个线程每个解析 125 个字符串。我还测试了几种使用线程的不同方式:线程创建、线程池、任务和函数对象。
结果: 结果时间以毫秒为单位。
单线程:
- 方法调用:17720
4 线程
- 参数化线程开始:13836
- ThreadPool.QueueUserWorkItem:14075
- Task.Factory.StartNew:16798
- 函数 BeginInvoke EndInvoke:16733
8 个线程
- 参数化线程开始:12591
- ThreadPool.QueueUserWorkItem:13832
- Task.Factory.StartNew:15877
- 函数 BeginInvoke EndInvoke:16395
如您所见,最快的是使用 8 个线程(我的逻辑核心数)的参数化线程开始。然而,它并没有比使用 4 个线程好太多,并且仅比使用单核快 29%。当然结果会因机器而异。我也坚持了
Dictionary<Type, TypeConverter>
用于字符串解析的缓存,因为使用类型转换器数组并没有提供显着的性能提升,并且拥有一个共享的缓存类型转换器更易于维护,而不是在需要时到处创建数组。
另一个更新:
好的,所以我进行了更多测试,看看是否可以挤出更多性能,我发现了一些有趣的东西。我决定坚持使用 8 个线程,全部从 Parameterized Thread Start 方法开始(这是我之前测试中最快的)。运行与上述相同的测试,只是使用不同的解析算法。我注意到
Convert.ChangeType and TypeConverter
花费大约相同的时间。类型特定的转换器,例如
int.TryParse
稍微快一点,但对我来说不是一个选择,因为我的类型是动态的。ricovox 有一些关于异常处理的好建议。我的数据确实有无效数据,一些整数列会为空数字加上破折号“-”,所以类型转换器会爆炸:这意味着我解析的每一行至少有一个异常,那就是 1000 个异常!非常耗时。
顺便说一句,这就是我使用 TypeConverter 进行转换的方式。Extensions 只是一个静态类,GetTypeConverter 只返回一个cahced TypeConverter。如果在转换过程中抛出异常,则使用默认值。
public static Object ConvertTo(this String arg, CultureInfo cultureInfo, Type type, Object defaultValue)
{
Object value;
TypeConverter typeConverter = Extensions.GetTypeConverter(type);
try
{
// Try converting the string.
value = typeConverter.ConvertFromString(null, cultureInfo, arg);
}
catch
{
// If the conversion fails then use the default value.
value = defaultValue;
}
return value;
}
结果:
在 8 个线程上进行相同的测试 - 解析 1000 行,每列 16 列,每个线程 250 行。
所以我做了3件新的事情。
1 - 运行测试:在解析之前检查已知的无效类型以最小化异常。即 if(!Char.IsDigit(c)) value = 0; 或 columnString.Contains('-') 等...
运行时间:29 毫秒
2 - 运行测试:使用具有 try catch 块的自定义解析算法。
运行时间:12424ms
3 - 运行测试:在解析之前使用自定义解析算法检查无效类型以最小化异常。
运行时间 15 毫秒
哇!正如您所看到的,消除异常带来了天壤之别。我从来没有意识到异常的代价有多大!因此,如果我将例外情况最小化为真正未知的情况,那么解析算法的运行速度会快三个数量级。我正在考虑这绝对解决了。我相信我会用 TypeConverter 保持动态类型转换,它只会慢几毫秒。在转换之前检查已知的无效类型可以避免异常,这会大大加快速度!感谢 ricovox 指出这让我进一步测试。