当我知道T
永远是Of T as Structure
时,有没有人知道VB中从字符串转到泛型T
约束为值类型(Return DirectCast(Convert.ChangeType(myStr, GetType(T)), T)
)的快速方法一些数字类型?
这对我来说太慢了:
String
但它似乎是从T
获得的唯一理智的方法 - > Convert.ChangeType
。我已经尝试使用Reflector来查看T
是如何工作的,虽然我可以通过该代码的黑客版本从String转换为给定的数字类型,但我不知道如何将该类型转换回Nullable(Of T)
所以可以退回。
我将添加我所看到的速度惩罚的一部分(在时序循环中)是因为返回值被分配给UInt16
值。如果我强烈键入我的类以获取特定数字类型(即T
),那么我可以大大提高性能,但是对于我使用的每种数字类型,都需要复制该类。
如果在通用方法/类中使用转换器来自/ Parse
,则几乎是好的。也许有而且我忘记了它的存在?
结论:
测试下面提供的三个实现和我原来的DirectCast / ChangeType表单,@ peenut使用准备好的委托从基本类型中获取Parse
方法的方法有效。但是,没有进行错误检查,因此实现者需要记住仅将其用于具有DirectCast(Convert.ChangeType(myStr, GetType(T)), T)
方法的valuetype。或者扩展下面的内容来进行错误检查。
所有运行都在运行Windows Server 2003 R2并具有4GB RAM的32位系统上完成。每次“运行”是要测试的方法的1,000,000次执行(操作),使用StopWatch计时并以毫秒为单位报告。
原始1000000 ops: 597ms
Average of 1000000 ops over 10 runs: 472ms
Average of 1000000 ops over 10 runs: 458ms
Average of 1000000 ops over 10 runs: 453ms
Average of 1000000 ops over 10 runs: 466ms
Average of 1000000 ops over 10 runs: 462ms
:
System.Reflection
使用InvokeMethod
并致电Parse
以获取1000000 ops: 12213ms
Average of 1000000 ops over 10 runs: 11468ms
Average of 1000000 ops over 10 runs: 11509ms
Average of 1000000 ops over 10 runs: 11524ms
Average of 1000000 ops over 10 runs: 11509ms
Average of 1000000 ops over 10 runs: 11490ms
方法:
Parse
Konrad生成IL代码以访问1000000 ops: 352ms
Average of 1000000 ops over 10 runs: 316ms
Average of 1000000 ops over 10 runs: 315ms
Average of 1000000 ops over 10 runs: 314ms
Average of 1000000 ops over 10 runs: 314ms
Average of 1000000 ops over 10 runs: 314ms
方法并将调用存储到委托中的方法:
Parse
peenut使用委托直接访问1000000 ops: 272ms
Average of 1000000 ops over 10 runs: 272ms
Average of 1000000 ops over 10 runs: 275ms
Average of 1000000 ops over 10 runs: 274ms
Average of 1000000 ops over 10 runs: 272ms
Average of 1000000 ops over 10 runs: 273ms
方法的方法:
{{1}}
相比较而言,peenut的方法在紧密循环中执行1,000,000次时快了近200ms,因此他的方法胜出了。尽管如此,康拉德并不落后,而且本身就是对ILGenerator等事物的迷人研究。所有贡献者的道具!
答案 0 :(得分:11)
是的,我知道更快的解决方案: - )
更快的解决方案是使用为给定(通用)类型T准备的委托。如果您只对String->(内置数字类型)感兴趣,您可以简单地使用一个参数(String)获取Parse方法。 / p>
计划测试可能性的速度。请注意,前两种方法只是通用的,第三种和第四种方法仅供比较。
Imports System.Reflection
Module Module1
Public Class Parser(Of T As Structure)
Delegate Function ParserFunction(ByVal value As String) As T
Public Shared ReadOnly Parse2 As ParserFunction = GetFunction()
Private Shared Function GetFunction() As ParserFunction
Dim t As Type = GetType(T)
Dim m As MethodInfo = t.GetMethod("Parse", New Type() {GetType(String)})
Dim d As ParserFunction = DirectCast( _
ParserFunction.CreateDelegate(GetType(ParserFunction), m), _
ParserFunction)
Return d
End Function
Public Shared Function Parse1(ByVal value As String) As T
Return DirectCast(Convert.ChangeType(value, GetType(T)), T)
End Function
End Class
Sub Main()
Dim w As New Stopwatch()
'test data:
Dim arrStr() As String = New String(12345678 - 1) {}
Dim r As New Random
For i As Integer = 0 To arrStr.Length - 1
arrStr(i) = r.Next().ToString()
Next
Dim arrInt1() As Integer = New Integer(arrStr.Length - 1) {}
Dim arrInt2() As Integer = New Integer(arrStr.Length - 1) {}
Console.WriteLine("1. method - Convert.ChangeType:")
w.Reset()
w.Start()
For i As Integer = 0 To arrStr.Length - 1
arrInt1(i) = Parser(Of Integer).Parse1(arrStr(i))
Next
w.Stop()
Console.WriteLine(w.Elapsed)
Console.WriteLine()
Console.WriteLine("2. method - prepared delegate:")
w.Reset()
w.Start()
For i As Integer = 0 To arrStr.Length - 1
arrInt2(i) = Parser(Of Integer).Parse2(arrStr(i))
Next
w.Stop()
Console.WriteLine(w.Elapsed)
Console.WriteLine()
Console.WriteLine("3. method - Integer.Parse:")
w.Reset()
w.Start()
For i As Integer = 0 To arrStr.Length - 1
arrInt2(i) = Integer.Parse(arrStr(i))
Next
w.Stop()
Console.WriteLine(w.Elapsed)
Console.WriteLine()
Console.WriteLine("4. method - CType:")
w.Reset()
w.Start()
For i As Integer = 0 To arrStr.Length - 1
arrInt2(i) = CType(arrStr(i), Integer)
Next
w.Stop()
Console.WriteLine(w.Elapsed)
Console.WriteLine()
End Sub
End Module
如果需要,您可以更改已测试元素的数量。我使用了12345678个随机整数。我的程序输出:
1. method - Convert.ChangeType: 00:00:03.5176071 2. method - prepared delegate: 00:00:02.9348792 3. method - Integer.Parse: 00:00:02.8427987 4. method - CType: 00:00:05.0542241
时间比率:3.5176071 / 2.9348792 = 1.20
答案 1 :(得分:2)
以下是使用前面提到的DynamicMethod
的不同方法。
同样,我无法测试VB代码(测试调用中的Mono编译器阻塞,但不是代码本身),但我认为这是正确的。它的C#等效作品和下面的代码是1:1的翻译:
public class Parser(of T as Structure)
delegate function ParserFunction(value as String) as T
private shared readonly m_parse as ParserFunction
shared sub new()
dim tt as Type = gettype(T)
dim argumentTypes as Type() = new Type() { gettype(String) }
dim typeDotParse as MethodInfo = tt.GetMethod("Parse", argumentTypes)
dim method as new DynamicMethod("Parse", tt, argumentTypes)
dim il as ILGenerator = method.GetILGenerator()
il.Emit(OpCodes.Ldarg_0)
il.Emit(OpCodes.Call, typeDotParse)
il.Emit(OpCodes.Ret)
m_parse = directcast( _
method.CreateDelegate(gettype(ParserFunction)), _
ParserFunction)
end sub
public shared function Parse(byval value As String) As T
return m_parse(value)
end function
end class
如果您有最新版本的VB,则可以清除此代码。同样,Mono编译器还不知道Option Infer
等等。
这段代码的作用是为每个被调用的代码编译一个单独的解析方法。此解析方法仅将实际解析委托给该类型的共享T.Parse
方法(例如Integer.Parse
)。编译完成后,此代码不需要任何额外的转换,也不需要装箱,也不需要Nullable
s。
代码调用如下:
Dim i As Integer = Parser(Of Integer).Parse("42")
除了编译的一次性开销之外,这个方法应该是最快的,因为没有其他开销:只是对实际解析例程的函数调用。它不会比那更快。
答案 2 :(得分:0)
可能不是答案,而是另一个问题。通过这种方法你会实现什么?让我们假设你以某种方式实现了这样的方法(对于Vb.Net帖子中的C#抱歉,但希望你能得到这个想法):
T Convert<T>(string strInput) { ... }
并且您将仅在有限范围的类型中使用此方法:double,int,Int16等。因此您将使用它:
double x = Convert<double>(myStr);
我认为这种方法没有任何好处,因为出于同样的原因,没有那种方法你会写:
double x = double.Parse(myStr);
所以我想说的是,如果没有你的魔法,你会编写相同数量的代码来使用它。我没有看到该方法的任何好处。我错过了一些用例吗?
答案 3 :(得分:0)
我没有VB编译器所以我无法测试它,但以下工作在C#中。我怀疑它更快,因为它使用反射:
Public Shared Function Parse(Of T As Strcture)(ByVal value As String) As T
Dim type = GetType(T)
Dim result = type.InvokeMember( _
"Parse", _
BindingFlags.Public Or BindingFlags.Static Or BindingFlags.InvokeMethod, _
Nothing, Nothing, new Object() { value })
Return DirectCast(result, T)
End Function
加快这一过程的一种方法是从T.Parse
成员而不是InvokeMember
创建动态方法,并为每种类型缓存该动态方法。这意味着第一次调用(编译动态方法)的开销更大,但后续运行会更快。