65.9K
CodeProject 正在变化。 阅读更多。
Home

比较速度测试

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.57/5 (9投票s)

2008年5月29日

CPOL

6分钟阅读

viewsIcon

31033

downloadIcon

599

一个简单易用的类,用于在优化代码执行速度时执行比较性的、非基准速度测试。

摘要

本文介绍了一个不复杂的类,用于比较两种编程设计方案的原始速度。这不是一项科学的基准测试,而是一种非常简单的方法,可以实现两个编码过程,以衡量哪个可能更快以及快多少。该类允许以最少的基础设施编码进行比较,它不是什么高深的技术。

目录

介绍

在编写一些图像滤镜方法的过程中,我不断发现自己需要优化代码以提高原始速度。虽然我采用的方法从根本上说相当高效,但定期处理超过六百万像素的需求使得速度至关重要,有时甚至牺牲了透明性和良好的编程实践。将24位图像转换为8位索引灰度,然后查找边缘的简单过程意味着2000万像素操作。每个像素节省一微秒,就可以节省20秒的处理时间。

日常实践很快就变成了关键决策,例如:我是否保护一个类成员/字段(如像素数组)并通过读写属性访问它,还是将其公开并允许直接读写数据?在我了解通过属性访问的时间成本之前,我只是强烈怀疑直接读写会稍微快一些,更不清楚会快多少。

最终,我有如此多的未完成比较,以至于我编写了一个简单的抽象类,使我能够尽快完成这些比较,并获得足够科学的结果,以便我能够选择使用哪些选项。

一些令人惊讶的结果

对我来说是这样的……

我并不关心能够经受严格学术审查的严谨科学比较,我只是想做出一些设计选择,从而缩短某些图像滤镜的执行等待时间。我进行的一些测试结果差异比我预期的要明显一些,有些则相当令人惊讶。

示例 - 属性与直接成员访问

举个例子;直接、无保护地**读取**类成员的效率大约是通过属性访问相同成员的五倍。我没有测试写入访问;这个测试结果足以让我将几个类成员公开。

后来我惊讶地发现,如果运行可执行文件的“Release”版本,结果是不确定的——一次传递属性访问占优势,另一次直接访问。我的结论是两者都可以,我的假设是优化器在Release二进制文件中为两者构建了相似的代码。我在下载中包含了两个可执行文件,_SpeedTests_Debug.exe_ 和 _SpeedTests_Release.exe_,这样你就可以亲自看到这种差异。

示例 - 递归与堆栈和循环

另一个例子:一个简单的递归方法出人意料地似乎比将值推入 .NET `Stack` 实例并循环的效率高出十倍。我没有测试手动制作的堆栈,这可能会导致不同的结果;我只是使用了递归方法。

类 SpeedTestsAB

速度测试运行一个基本的控制方法 (`SpeedTestControl`),它只是一个空方法,用于提供一个开销度量,可以从每个测试的总时间中减去,以提供净运行时间。

抽象类 `SpeedTestsAB` 定义了四个 `abstract`/`MustOverride` 方法

方法 描述

SetUpTestA

SpeedTestA 的初始化,例如

C#:`base.f_DescribeA = "直接访问公共字段。";`

SetUpTestB

SpeedTestB 的初始化,例如

VB: `me.f_DescribeB = "通过属性访问字段。"`

SpeedTestA

要执行的速度测试 A 代码。

SpeedTestB

要执行的速度测试 B 代码。

此外,还有一些方法和属性可以简单地报告结果。

属性 描述
TotalTimeA
TotalTimeB
TotalTimeControl

每个测试所花费的总时间。

NetTimeA
NetTimeB

每个测试花费的净时间 - `TotalTime - TotalTimeControl`。

Repetitions

重复次数。

方法

描述

结果

返回一个非常简单的结果报告字符串。

AppendResults

将结果字符串追加到文件中。

ShowResults

在 `MessageBox` 中显示结果字符串。

WriteResults

将结果字符串写入文件或流。

最终输出看起来像

测试结果
10,000,000 repetitions.
Test A: Access a public field directly.
Test B: Access a field via a property.
00:00:00.0937500 hh:mm:ss.ff Equivalent Elapsed Time Control Process.
00:00:00.1406250 hh:mm:ss.ff Total Elapsed Time Process A.
00:00:00.1718750 hh:mm:ss.ff Total Elapsed Time Process B. 00:00:00.0468750 hh:mm:ss.ff
Net Elapsed Time Process A.
00:00:00.0625000 hh:mm:ss.ff Net Elapsed Time Process B.
Net Unit Processing Time A: 4.688 nanosecs
Net Unit Processing Time B: 6.250 nanosecs
75.000% Percentage: Process A divided by Process B.

使用 SpeedTestsAB

创建一个继承 `clsBase` `SpeedTestsAB` 的类,例如

Public Class clsSpeedTestAB_Properties _
  Inherits clsBaseSpeedTestAB

创建一个代码段,定义运行测试所需的属性、方法和数据,例如

#Region "[=== SPEED TEST COMPONENTS ===]"

  Protected f_SomeInteger As Int32 = 123456

  Public Property SomeInteger() As Int32
    Get
      Return Me.f_SomeInteger
    End Get
    Set(ByVal value As Int32)
      Me.f_SomeInteger = value
    End Set
  End Property

#End Region

重写方法 `SetUpTestA` 和 `SetUpTestB`,至少包含测试描述,例如

''' <summary>
''' Overriden speed test A setup.
''' </summary>
''' <remarks></remarks>
Protected Overrides Sub SetUpTestB()
    Me.f_DescribeB = "Access a field via a property."
End Sub

用要测试的代码覆盖方法 `SpeedTestA` 和 `SpeedTestB`,例如

''' <summary>
''' Overridden Speed Test A.
''' </summary>
''' <remarks></remarks>
Protected Overrides Sub SpeedTestA()
    Dim xInt As Int32
    xInt = Me.f_SomeInteger
End Sub

''' <summary>
''' Overridden Speed Test B.
''' </summary>
''' <remarks></remarks>
Protected Overrides Sub SpeedTestB()
    Dim xInt As Int32
    xInt = Me.SomeInteger
End Sub

在某个地方定义类并调用 `RunTest` 方法,例如

Private Sub Button1_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles Button3.Click
    Dim xTest As New clsSpeedTestAB_Properties(10000000)
    xTest.RunTest()
    xTest.ShowResults
End Sub

下载文件

下载分为四个包

目录

SpeedTests_Src_CS.zip

C# 演示源代码,包括

  • clsBaseSpeedTestAB.cs
  • clsSpeedTestAB_Delegates.cs
  • clsSpeedTestAB_Properties.cs
  • clsSpeedTestAB_Recursion.cs
  • Form1.cs

请注意,C# 源代码是 Lutz Roeder 的 .NET Reflector v5.1 的反汇编输出,而不是我移植源代码的结果。因此,代码中可能存在一些错误,因为反汇编创建了几个错误,我已经尝试修复。

SpeedTests_Src_VB.zip

VB.NET 演示源代码,包括

  • clsBaseSpeedTestAB.vb
  • clsSpeedTestAB_Delegates.vb
  • clsSpeedTestAB_Properties.vb
  • clsSpeedTestAB_Recursion.vb
  • Form1.vb

SpeedTests_EXE.zip

两个速度测试编译,一个在调试模式下(_SpeedTests_Debug.exe_),一个在发布模式下(_SpeedTests_Release.exe_)。

SpeedTests_Article.zip

本文。

注意事项

这不是一个进行正确科学速度基准测试的例子,而只是一个在原始速度至关重要时选择代码设计的机制。

在调试模式下编译的代码与在发布模式下编译的相同代码在效率/速度方面通常存在相当大的差异。假设您的软件最终版本将在发布模式下编译,我建议在该模式下进行速度测试。然而,比较两种模式下的执行速度是很有趣的。

示例测试结果

(在调试模式下。)

这些结果来自该类的一个早期版本,并被积极用于制定图像编码设计决策。使用发布编译版本进行测试时,这些差异可能会消失。

速度测试:If, ElseIf 与 Select Case 比较

Public Overrides Sub SpeedTestA()
  If Me.f_Int32 = 0 Then
    Me.f_Int32 = 1
  ElseIf Me.f_Int32 = 1 Then
    Me.f_Int32 = 1
  ElseIf Me.f_Int32 = 2 Then
    Me.f_Int32 = 2
  ElseIf Me.f_Int32 = 3 Then
    Me.f_Int32 = 3
  ElseIf Me.f_Int32 = 4 Then
    Me.f_Int32 = 4
  ElseIf Me.f_Int32 = 5 Then
    Me.f_Int32 = 5
  ElseIf Me.f_Int32 = 6 Then
    Me.f_Int32 = 6
  ElseIf Me.f_Int32 = 7 Then
    Me.f_Int32 = 7
  ElseIf Me.f_Int32 = 8 Then
    Me.f_Int32 = 8
  ElseIf Me.f_Int32 = 9 Then
    Me.f_Int32 = 9
  End If
End Sub

Public Overrides Sub SpeedTestB()
  Select Case Me.f_Int32
    Case 0
      Me.f_Int32 = 0
    Case 1
      Me.f_Int32 = 1
    Case 2
      Me.f_Int32 = 2
    Case 3
      Me.f_Int32 = 3
    Case 4
      Me.f_Int32 = 4
    Case 5
      Me.f_Int32 = 5
    Case 6
      Me.f_Int32 = 6
    Case 7
      Me.f_Int32 = 7
    Case 8
      Me.f_Int32 = 8
    Case 9
      Me.f_Int32 = 9
  End Select
End Sub

Protected f_Int32 As Int32
测试结果
600,000,000 repetitions.
Test A: Use if, elseif.
Test B: Use select case.
00:00:07.4270833 hh:mm:ss.ff Equivalent Elapsed Time Control Process.
00:00:10.0312500 hh:mm:ss.ff Total Elapsed Time Process A.
00:00:09.2968750 hh:mm:ss.ff Total Elapsed Time Process B.
00:00:02.6041667 hh:mm:ss.ff Net Elapsed Time Process A.
00:00:01.8697917 hh:mm:ss.ff Net Elapsed Time Process B.
Net Unit Processing Time A: 2.604 secs
Net Unit Processing Time B: 1.870 secs
139.276% Percentage: Process A divided by Process B.

结论

Select Case 可能比 If, ElseIf 大约快 40%。

速度测试:If, ElseIf 与嵌套 IIF 比较

Public Overrides Sub SpeedTestA()
  If Me.f_Int32 = 0 Then
    Me.f_Int32 = 1
  ElseIf Me.f_Int32 = 1 Then
    Me.f_Int32 = 1
  ElseIf Me.f_Int32 = 2 Then
    Me.f_Int32 = 2
  ElseIf Me.f_Int32 = 3 Then
    Me.f_Int32 = 3
  ElseIf Me.f_Int32 = 4 Then
    Me.f_Int32 = 4
  ElseIf Me.f_Int32 = 5 Then
    Me.f_Int32 = 5
  ElseIf Me.f_Int32 = 6 Then
    Me.f_Int32 = 6
  ElseIf Me.f_Int32 = 7 Then
    Me.f_Int32 = 7
  ElseIf Me.f_Int32 = 8 Then
    Me.f_Int32 = 8
  ElseIf Me.f_Int32 = 9 Then
    Me.f_Int32 = 9
  End If
End Sub

Public Overrides Sub SpeedTestB()
  IIf(Me.f_Int32 = 0, Me.f_Int32 = 0 _
        , IIf(Me.f_Int32 = 1, Me.f_Int32 = 1 _
        , IIf(Me.f_Int32 = 2, Me.f_Int32 = 2 _
        , IIf(Me.f_Int32 = 3, Me.f_Int32 = 3 _
        , IIf(Me.f_Int32 = 4, Me.f_Int32 = 4 _
        , IIf(Me.f_Int32 = 5, Me.f_Int32 = 5 _
        , IIf(Me.f_Int32 = 6, Me.f_Int32 = 6 _
        , IIf(Me.f_Int32 = 7, Me.f_Int32 = 7 _
        , IIf(Me.f_Int32 = 8, Me.f_Int32 = 8 _
        , IIf(Me.f_Int32 = 9, Me.f_Int32 = 9 _
        , Me.f_Int32 = 9))))))))))
End Sub

Protected f_Int32 As Int32 = -1
测试结果
100,000,000 repetitions.
Test A: Use if, elseif.
Test B: Use nested IIF.
00:00:01.2343750 hh:mm:ss.ff Equivalent Elapsed Time Control Process.
00:00:03.2812500 hh:mm:ss.ff Total Elapsed Time Process A.
00:00:39.7500000 hh:mm:ss.ff Total Elapsed Time Process B.
00:00:02.0468750 hh:mm:ss.ff Net Elapsed Time Process A.
00:00:38.5156250 hh:mm:ss.ff Net Elapsed Time Process B.
Net Unit Processing Time A: 2.047 secs
Net Unit Processing Time B: 38.516 secs
5.314% Percentage: Process A divided by Process B.

结论

不要使用 IIF

速度测试:比较右移 4 位 (X >> 4) 与除以 16

Public Overrides Sub SpeedTestA()
  Dim xInt As Int32 = _
    CInt((((((((Me.f_Int >> 4) >> 4) >> 4) >> 4) >> 4) >> 4) >> 4) >> 4)
End Sub

Public Overrides Sub SpeedTestB()
  Dim xInt As Int32 = _
    CInt((((((((Me.f_Int / 16 / 16 / 16 / 16 / 16 / 16 / 16 / 16)
End Sub

Protected f_Int As Int32 = 123456
测试结果
100,000,000 repetitions.
Test A: Shift-Right 4.
Test B: Divide by 16.
00:00:01.2500000 hh:mm:ss.ff Equivalent Elapsed Time Control Process.
00:00:01.3593750 hh:mm:ss.ff Total Elapsed Time Process A.
00:00:20.9843750 hh:mm:ss.ff Total Elapsed Time Process B.
00:00:00.1093750 hh:mm:ss.ff Net Elapsed Time Process A.
00:00:19.7343750 hh:mm:ss.ff Net Elapsed Time Process B.
Net Unit Processing Time A: 109.375 millisecs
Net Unit Processing Time B: 19,734.375 millisecs
0.554% Percentage: Process A divided by Process B.

结论

位移操作比除法快近 200 倍。

历史

  • 2008-05-26:创建。
© . All rights reserved.