当 T 是值类型时,从字符串转换为泛型类型 T 的更快方法? [英] Faster way to convert from a String to generic type T when T is a valuetype?

查看:9
本文介绍了当 T 是值类型时,从字符串转换为泛型类型 T 的更快方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有谁知道在 VB 中从字符串到泛型类型的快速方法 T 约束为值类型(Of T as Structure),当我知道时那 T 将永远是某种数字类型?

这对我来说太慢了:

返回 DirectCast(Convert.ChangeType(myStr, GetType(T)), T)

但它似乎是从 String --> T 获取的唯一合理方法.我尝试使用 Reflector 来查看 Convert.ChangeType 是如何工作的,虽然我可以通过该代码的破解版本从字符串转换为给定的数字类型,但我不知道如何将该类型插入 T 以便可以返回.

我将添加我看到的部分速度损失(在计时循环中)是因为返回值被分配给 Nullable(Of T) 值.如果我为特定的数字类型(即UInt16)强类型化我的类,那么我可以极大地提高性能,但是对于我使用的每种数字类型都需要复制该类.

如果在通用方法/类中处理 T 时有转换器,那就太好了.也许有,但我忘记了它的存在?


结论:
测试下面提供的三个实现和我最初的 DirectCast/ChangeType 表单,@peenut 使用准备好的委托从基本类型中获取 Parse 方法的方法有效.但是,没有进行错误检查,因此实现者需要记住仅将其与具有 Parse 方法可用的值类型一起使用.或者扩展下面的代码来做错误检查.

所有运行均在运行 Windows Server 2003 R2 和 4GB RAM 的 32 位系统上完成.每个运行"是要测试的方法的 1,000,000 次执行 (ops),使用 StopWatch 计时并以毫秒为单位报告.

DirectCast(Convert.ChangeType(myStr, GetType(T)), T):

1000000 次操作:597 毫秒10 次运行的平均 1000000 次操作:472 毫秒10 次运行的平均 1000000 次操作:458 毫秒10 次运行的平均 1000000 次操作:453 毫秒10 次运行的平均 1000000 次操作:466 毫秒10 次运行的平均 1000000 次操作:462 毫秒


使用System.Reflection并调用InvokeMethod获取Parse方法:

1000000 操作:12213ms10 次运行的平均 1000000 次操作:11468 毫秒10 次运行的平均 1000000 次操作:11509 毫秒10 次运行的平均 1000000 次操作:11524 毫秒10 次运行的平均 1000000 次操作:11509 毫秒10 次运行的平均 1000000 次操作:11490 毫秒


Konrad 生成 IL 代码以访问 Parse 方法并将调用存储到委托中的方法:

1000000 次操作:352 毫秒10 次运行的平均 1000000 次操作:316 毫秒10 次运行的平均 1000000 次操作:315 毫秒10 次运行的平均 1000000 次操作:314 毫秒10 次运行的平均 1000000 次操作:314 毫秒10 次运行的平均 1000000 次操作:314 毫秒


peenut使用delegate直接访问Parse方法的做法:

1000000 次操作:272ms10 次运行的平均 1000000 次操作:272 毫秒10 次运行的平均 1000000 次操作:275 毫秒10 次运行的平均 1000000 次操作:274 毫秒10 次运行的平均 1000000 次操作:272 毫秒10 次运行的平均 1000000 次操作:273 毫秒



相比之下,peenut 的方法在紧密循环中执行 1,000,000 次时快了近 200 毫秒,因此他的方法胜出.尽管如此,康拉德的研究也不甘落后,它本身就是对 ILGenerator 之类的有趣研究.向所有做出贡献的人致敬!

解决方案

是的,我知道更快的解决方案 :-)

更快的解决方案是为给定(通用)类型 T 使用准备好的委托.如果您只对 String->(内置数字类型)感兴趣,您可以简单地使用一个参数(String)获取 Parse 方法.

程序来测试可能性的速度.请注意,只有前两种方法是通用的,第三种和第四种方法仅供比较.

导入 System.Reflection模块模块1公共类解析器(T作为结构)委托函数 ParserFunction(ByVal value As String) As T公共共享只读 Parse2 作为 ParserFunction = GetFunction()私有共享函数 GetFunction() As ParserFunctionDim 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), _解析器函数)返回 d结束功能公共共享函数 Parse1(ByVal 值作为字符串)作为 T返回 DirectCast(Convert.ChangeType(value, GetType(T)), T)结束功能结束类子主()暗淡为新的秒表()'测试数据:Dim arrStr() As String = New String(12345678 - 1) {}暗淡为新随机For i As Integer = 0 To arrStr.Length - 1arrStr(i) = r.Next().ToString()下一个Dim arrInt1() As Integer = New Integer(arrStr.Length - 1) {}Dim arrInt2() As Integer = New Integer(arrStr.Length - 1) {}Console.WriteLine("1. 方法 - Convert.ChangeType:")w.Reset()w.Start()For i As Integer = 0 To arrStr.Length - 1arrInt1(i) = Parser(Of Integer).Parse1(arrStr(i))下一个w.Stop()Console.WriteLine(w.Elapsed)Console.WriteLine()Console.WriteLine("2. 方法 - 准备好的委托:")w.Reset()w.Start()For i As Integer = 0 To arrStr.Length - 1arrInt2(i) = Parser(Of Integer).Parse2(arrStr(i))下一个w.Stop()Console.WriteLine(w.Elapsed)Console.WriteLine()Console.WriteLine("3. 方法 - Integer.Parse:")w.Reset()w.Start()For i As Integer = 0 To arrStr.Length - 1arrInt2(i) = Integer.Parse(arrStr(i))下一个w.Stop()Console.WriteLine(w.Elapsed)Console.WriteLine()Console.WriteLine("4. 方法 - CType:")w.Reset()w.Start()For i As Integer = 0 To arrStr.Length - 1arrInt2(i) = CType(arrStr(i), 整数)下一个w.Stop()Console.WriteLine(w.Elapsed)Console.WriteLine()结束子端模块

如果需要,您可以更改测试元素的数量.我使用了 12345678 个随机整数.我的程序输出:

<上一页> 1.方法 - Convert.ChangeType:00:00:03.51760712.方法 - 准备好的委托:00:00:02.93487923.方法——Integer.Parse:00:00:02.84279874.方法-CType:00:00:05.0542241

倍率:3.5176071/2.9348792 = 1.20

Does anyone know of a fast way in VB to go from a string to a generic type T constrained to a valuetype (Of T as Structure), when I know that T will always be some number type?

This is too slow for my taste:

Return DirectCast(Convert.ChangeType(myStr, GetType(T)), T)

But it seems to be the only sane method of getting from a String --> T. I've tried using Reflector to see how Convert.ChangeType works, and while I can convert from the String to a given number type via a hacked-up version of that code, I have no idea how to jam that type back into T so it can be returned.

I'll add that part of the speed penalty I'm seeing (in a timing loop) is because the return value is getting assigned to a Nullable(Of T) value. If I strongly-type my class for a specific number type (i.e., UInt16), then I can vastly increase the performance, but then the class would need to be duplicated for each numeric type that I use.

It'd almost be nice if there was converter to/from T while working on it in a generic method/class. Maybe there is and I'm oblivious to its existence?


Conclusion:
Testing the three provided implementations below and my original DirectCast/ChangeType form, @peenut's approach of using a prepared delegate to fetch the Parse method from a basic type works. No error checking is done, however, so implementors need to remember to only use this with valuetypes that have a Parse method available. Or extend the below to do error checking.

All runs were done on a 32bit system running Windows Server 2003 R2 with 4GB of RAM. Each "run" is 1,000,000 executions (ops) of the method to be tested, timed with StopWatch and reported back in milliseconds.

Original DirectCast(Convert.ChangeType(myStr, GetType(T)), T):

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


Using System.Reflection and calling InvokeMethod to get at the Parse method:

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


Konrad's approach to generate IL code to access the Parse method and store the call into a delegate:

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


peenut's approach of using a delegate to access the Parse method directly:

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



Comparatively, peenut's approach is almost 200ms faster when executed 1,000,000 times in a tight loop, so his approach wins out. Although, Konrad's wasn't far behind and is itself a fascinating study of things like ILGenerator. Props to all who contributed!

解决方案

Yes, I know about faster solution :-)

Faster solution is to use prepared delegate for given (generic) Type T. If you are only interested in String->(built-in numeric type), you can simply get Parse method with one argument (String).

Program to test speed of possibilities. Note that only first two methods are generic, 3rd and 4th methods are for comparison only.

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

You can change number of tested elements, if you want. I used 12345678 random integers. Program outputs for me:

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

Ratio of times: 3.5176071 / 2.9348792 = 1.20

这篇关于当 T 是值类型时,从字符串转换为泛型类型 T 的更快方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆