使用 array.resize 而不是 redim 的原因

Reasons to use array.resize instead of redim

提问人:genespos 提问时间:11/11/2015 最后编辑:ElektroStudiosgenespos 更新时间:11/12/2015 访问量:5316

问:

我知道“redim”比“Array.Resize”更古老,但不明白为什么更好地使用后者而不是前者。

.net 数组 vb.net

评论

7赞 Hans Passant 11/11/2015
从来没有任何理由。一旦你开始考虑 Redim/Resize,你应该立即用 List(Of T) 替换。

答:

0赞 Mych 11/11/2015 #1

我不是 100% 确定,但我似乎记得 resize 将直接在数组上工作,而 redim 将以新大小创建一个新数组,然后将值从旧数组复制到新数组中,然后处理旧的并重命名新的数组。即相当多的处理。

评论

0赞 genespos 11/11/2015
所以array.redim更快?我看到array.redim的工作原理是ref
0赞 Mych 11/11/2015
我不这么认为。。。他们为什么要引入劣等的东西。
1赞 ElektroStudios 11/11/2015
@Mych,Array.Resize 和 ReDim 都将分配一个新数组来复制整个旧数组。msdn.microsoft.com/en-us/library/bb348051%28v=vs.110%29.aspx
3赞 ElektroStudios 11/11/2015 #2

正如 MSDN 中所解释的,ReDimArray.Resize 似乎通过分配一个具有固定大小的新 Array 来复制旧 awwat 的内容来工作,但是有一些内部差异(我忽略了)使速度更快,并且在比较时是一个性能杀手。ReDimReDim Preserve

通过下一个测试的结果,该测试执行一个简单的迭代以重新调整数组的维度 100.000 次,您可以证明它比 快得多,但是,两者都比使用方法的自定义泛型解决方案慢。ReDimArray.ResizeArray.Copy


结果

ReDim 运行时间:00:00:07.3918219

Array.Resize 运行时间:00:00:28.9773317

自定义调整大小运行时间:00:00:03.2659880

如您所见,和 之间相差 +20 秒ReDimArray.Resize


测试源代码

Dim myArray As String()
Dim sw As New Stopwatch

myArray = New String() {"Q", "W", "E", "R", "T", "Y"}
sw.Start()
For x As Integer = 1 To 100000 : ReDim myArray(x) : Next
sw.Stop()
Console.WriteLine("ReDim Elapsed Time: " & sw.Elapsed.ToString)

myArray = New String() {"Q", "W", "E", "R", "T", "Y"}
sw.Restart()
For x As Integer = 1 To 100000 : Array.Resize(myArray, x) : Next
sw.Stop()
Console.WriteLine("Array.Resize Elapsed Time: " & sw.Elapsed.ToString)

myArray = New String() {"Q", "W", "E", "R", "T", "Y"}
sw.Restart()
For x As Integer = 1 To 100000 : ResizeArray(myArray, x) : Next
sw.Stop()
Console.WriteLine("Custom Resize Elapsed Time: " & sw.Elapsed.ToString)

自定义阵列大小调整解决方案:

将其添加为扩展方法非常有用。

' <Extension>
<DebuggerStepThrough>
Public Function ResizeArray(Of T)(ByVal sender As T(),
                                  ByVal newSize As Integer) As T()

    If (newSize <= 0) Then
        Throw New ArgumentOutOfRangeException(paramName:="newSize",
                                              message:="Value bigger than 0 is required.")
    End If

    Dim preserveLength As Integer = Math.Min(sender.Length, newSize)

    If (preserveLength > 0) Then
        Dim newArray As Array = Array.CreateInstance(sender.GetType.GetElementType, newSize)
        Array.Copy(sender, newArray, preserveLength)
        Return DirectCast(newArray, T())

    Else
        Return sender

    End If

End Function

免责声明:我不是该函数的作者,它是我前段时间在关于 Array.Resize 性能的随机 StackOverflow 答案中发现的 C# 代码的自定义 Vb.Net 改编。

评论

1赞 genespos 11/11/2015
感谢您的回答和您的代码:我会尽快测试它;)
1赞 Bjørn-Roger Kringsjå 11/12/2015
你的 bechmark 有一个非常大的缺陷。您没有设置返回值,因此您始终使用初始数组(6 个元素)。中的参数是通过引用的,因此它应按要求工作。应用更改,您将获得不同(但正确)的结果。ResizeArray(myArray, x)Array.ResizemyArray = ResizeArray(myArray, x)
0赞 johnnyontheweb 6/7/2021
你是对的 @Bj ørn-RogerKringsjå,这就是结果!“ReDim 运行时间:00:00:02.3281857 ReDim 保留运行时间:00:00:09.0978561 Array.Resize 运行时间:00:00:17.3862156 自定义调整大小运行时间:00:00:17.5181313”
2赞 Bjørn-Roger Kringsjå 11/12/2015 #3

除非添加修饰符,否则无法比较 ReDimArray.Resize<T>Preserve

Array.Resize不仅会分配一个新数组,还会将源数组中的所有项目复制到目标数组。

ReDim如果没有修饰符,将只分配一个新数组。源数组中的所有项都将丢失。Preserve

所以基本上是这样:

Dim a As String() = {"item1", "item2"}
ReDim a(4 - 1) 'Double the size
'At this point, a contains 4 null references.

...等于:

Dim a As String() = {"item1", "item2"}
a = New String(4 - 1) {} 'Double the size
'At this point, a contains 4 null references.

您可以通过检查发布配置中生成的 CIL 代码并查找 0x8D - newarr <etype> 指令来验证这一点。通过了解这一点,很明显为什么它比 .Array.Resize<T>

因此,让我们比较一下 和 .ReDim PreserveArray<T>

那么使用哪一个呢?

让我们创建两个方法并查看代码。CIL

VB.NET

Private Sub ResizeArray1(Of T)(ByRef a As T(), size As Int32)
    ReDim Preserve a(size - 1)
End Sub

Private Sub ResizeArray2(Of T)(ByRef a As T(), size As Int32)
    Array.Resize(a, size)
End Sub

CIL公司

.method private static void ResizeArray1<T>(!!T[]& a, int32 size) cil managed
{
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: ldarg.0 
    L_0002: ldind.ref 
    L_0003: ldarg.1 
    L_0004: ldc.i4.1 
    L_0005: sub.ovf 
    L_0006: ldc.i4.1 
    L_0007: add.ovf 
    L_0008: newarr !!T
    L_000d: call class [mscorlib]System.Array [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Utils::CopyArray(class [mscorlib]System.Array, class [mscorlib]System.Array)
    L_0012: castclass !!T[]
    L_0017: stind.ref 
    L_0018: ret 
}

.method private static void ResizeArray2<T>(!!T[]& a, int32 size) cil managed
{
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: ldarg.1 
    L_0002: call void [mscorlib]System.Array::Resize<!!T>(!!0[]&, int32)
    L_0007: ret 
}

如您所见,最终成为对 Microsoft.VisualBasic.CompilerServices.Utils.CopyArray 的调用指令。现在,如果检查(源代码在 RS 上不可用)和 Array.Resize<T>,您将看到这两个方法最终都是对 Array.Copy<T> 的调用指令,这是一个 CLR 方法。ReDim PreserveCopyArray

因此,人们可以争辩说它们本质上都是“相同的”,而快速的 benckmark(可在本文末尾找到)似乎证实了这一点。

但是,正如 Hans Passant 正确指出的那样,每当需要操作数组时>都应该使用 List<T。


基准

迭代次数:10
MaxSize:100000

空源数组:

{ 方法 = ResizeArray1, Time = 00:00:05.6533126 }
{ 方法 = ResizeArray2, Time = 00:00:05.6973607 }

不断增长的源阵列:

{ 方法 = ResizeArray1, Time = 00:01:42.6964858 }
{ 方法 = ResizeArray2, Time = 00:01:42.1891668 }

Option Strict On

Public Module Program

    Friend Sub Main()
        Console.WriteLine("Warming up...")
        Program.Benchmark(iterations:=10, maxSize:=1000, warmUp:=True)
        Console.WriteLine("Warmup completed. Measurement started...")
        Program.Benchmark(iterations:=10, maxSize:=100000, warmUp:=False)
        Console.WriteLine()
        Console.WriteLine("Measurement completed. Press any key to exit.")
        Console.ReadKey()
    End Sub

    Private Sub Benchmark(iterations As Int32, maxSize As Int32, warmUp As Boolean)

        Dim watch As Stopwatch
        Dim a As String()

        'BY: EMPTY SOURCE ARRAY --------------------------------- 

        'Resize array #1

        watch = Stopwatch.StartNew()

        For i As Int32 = 1 To iterations
            For n As Int32 = 1 To maxSize
                a = Program.CreateArray(Of String)(0)
                Program.ResizeArray1(a, n)
            Next
        Next

        watch.Stop()

        If (Not warmUp) Then
            Console.WriteLine()
            Console.WriteLine(String.Format("R E S U L T"))
            Console.WriteLine()
            Console.WriteLine(String.Format("Iterations: {0}", iterations))
            Console.WriteLine(String.Format("   MaxSize: {0}", maxSize))
            Console.WriteLine()
            Console.WriteLine("Empty source array:")
            Console.WriteLine()
            Console.WriteLine(New With {.Method = "ResizeArray1", .Time = watch.Elapsed.ToString()})
        End If

        'Resize array #2

        watch = Stopwatch.StartNew()

        For i As Int32 = 1 To iterations
            For n As Int32 = 1 To maxSize
                a = CreateArray(Of String)(0)
                ResizeArray2(a, n)
            Next
        Next

        watch.Stop()

        If (Not warmUp) Then
            Console.WriteLine(New With {.Method = "ResizeArray2", .Time = watch.Elapsed.ToString()})
        End If

        'BY: GROWING SOURCE ARRAY -------------------------------

        'Resize array #1

        watch = Stopwatch.StartNew()
        a = Program.CreateArray(Of String)(0)

        For i As Int32 = 1 To iterations
            For n As Int32 = 1 To maxSize
                Program.ResizeArray1(a, n)
            Next
        Next

        watch.Stop()

        If (Not warmUp) Then
            Console.WriteLine()
            Console.WriteLine("Growing source array:")
            Console.WriteLine()
            Console.WriteLine(New With {.Method = "ResizeArray1", .Time = watch.Elapsed.ToString()})
        End If

        'Resize array #2

        watch = Stopwatch.StartNew()
        a = Program.CreateArray(Of String)(0)

        For i As Int32 = 1 To iterations
            For n As Int32 = 1 To maxSize
                Program.ResizeArray2(a, n)
            Next
        Next

        watch.Stop()

        If (Not warmUp) Then
            Console.WriteLine(New With {.Method = "ResizeArray2", .Time = watch.Elapsed.ToString()})
        End If

    End Sub

    Private Function CreateArray(Of T)(size As Int32) As T()
        Return New T(size - 1) {}
    End Function

    Private Sub ResizeArray1(Of T)(ByRef a As T(), size As Int32)
        ReDim Preserve a(size - 1)
    End Sub

    Private Sub ResizeArray2(Of T)(ByRef a As T(), size As Int32)
        Array.Resize(a, size)
    End Sub

End Module

评论

0赞 genespos 11/12/2015
我读了你的帖子(以及你对第一个答案的评论),但你如何看待“自定义数组调整大小解决方案”,它真的有用吗?
1赞 Bjørn-Roger Kringsjå 11/12/2015
@genespos 我对此表示高度怀疑。最快的方法可能是创建一个新的数组实例并直接调用。快速基准测试(发布配置)似乎证实了这一点。事实上,它是最快的,而自定义方法是最慢的。Array.Copy