当T是值类型时,更快的方式从String转换为泛型类型?

时间:2011-01-06 04:58:54

标签: vb.net

当我知道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等事物的迷人研究。所有贡献者的道具!

4 个答案:

答案 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创建动态方法,并为每种类型缓存该动态方法。这意味着第一次调用(编译动态方法)的开销更大,但后续运行会更快。