Performance QTP数据表操作*非常*慢(在MMDRV批处理执行器下更好)?
可能是一个扣人心弦的故事——QTP似乎无缘无故地浪费了我们的工作时间: 考虑这个脚本,它的datatable正好包含一个全局行,其中26列名为“a”到“Z”,其中填充了任意值:Performance QTP数据表操作*非常*慢(在MMDRV批处理执行器下更好)?,performance,datatable,qtp,Performance,Datatable,Qtp,可能是一个扣人心弦的故事——QTP似乎无缘无故地浪费了我们的工作时间: 考虑这个脚本,它的datatable正好包含一个全局行,其中26列名为“a”到“Z”,其中填充了任意值: Print "Started" Services.StartTransaction "Simpletest" Set G=DataTable.GetSheet ("Global") For J=1 to 26 For I=1 to 100 Set P=G.GetParameter (Chr
Print "Started"
Services.StartTransaction "Simpletest"
Set G=DataTable.GetSheet ("Global")
For J=1 to 26
For I=1 to 100
Set P=G.GetParameter (Chr (J+64))
If P.Value = "Hi" Then
End If
Next
Next
Services.EndTransaction "Simpletest"
Print "Ended"
在QTP 10下执行此操作需要在我的爆炸机上花费15.1秒。(当然,动画跑步已关闭。)
现在,我从QTP的bin文件夹中使用mmdrv.exe执行此操作,并为其指定参数“-usr”“”,该参数是包括QTP test.usr文件路径在内的全名
这需要0.07秒
你好?这是性能提升的215倍,但功能相同。怎么回事?
我在这里挖掘,因为我们对QTP数据表做了一些异国情调的工作,并且在QTP下面临严重的性能问题。我相信已经找到了DataTable.GetSheet和DTSheet.GetParameter属性/方法的原因
现在,我看到用于从LoadRunner场景中执行QTP测试的MMDRV没有性能损失,我想知道以下几点:
- 访问xls文件是否有1:1的替代方案
- Ex Mercury/HP的人员是否应该注意到,正如MMDRV.EXE所演示的那样,QTP下的数据表访问效率非常低,并对此采取一些措施
- 就我所见,所有其他QTP功能在MMDRV和QTP下的速度相当。有人能承认吗? *还有谁知道这件事吗
*更新*使用QTP隐形执行需要1.54秒。这是一个10倍的改进,只需隐藏QTP,如其中一个答案所述。唉。在全GUI开发环境下运行会导致性能下降。您也可以在LoadRunner中观察到VUGEN中的这种差异,在使用复杂代码的情况下,在MDRV上运行可以显著提高性能。您还会看到人们经常抱怨VUGEN“比实际应用程序慢”
那么,如果这让我感到惊讶?不是真的。有趣的是,我没有考虑在QTP安装上是否存在MDRV,但这使得QTP与Web QUICKTEST中的TULIP技术有着共同的传统。tulip base在功能方面是QuicktestPro的基础,在负载方面是一些较新的web HTTP技术的基础。数据表操作造成了200倍的性能损失。QTP下的其他操作仍然比MMDRV下的慢,但没有这样的恐怖因素 我通过在自定义结构(实际上是一个对象集合)中“缓存”所有DataTable调用来解决这个问题。因为我查询了大量的图纸和参数属性,所以构建这个需要5秒钟。处理我的结构而不是调用DTSheet和DTParameter属性要快得多,确实足够快了 我怀疑在QTP下,所有数据表访问都是通过他们(HP)从第三方获得许可的自定义Excel控件完成的,而在MMDRV下,他们使用的代码集成得更紧密,从而减少了每次调用的开销 公爵会说:“哈哈哈,真是一团糟。” **更新**根据请求,这里概述了我所说的“缓存”数据表调用的含义。这是相当多的代码,所以要做好准备 这是一团乱麻(抱歉,现在没有时间翻译德语内联评论)(对于格式,我显然无能为力,也许你想在QTP的编辑器中剪切并粘贴此内容): 这一切都从一个通用容器类开始,我可以在其中存储(并通过索引访问)N个对象引用:
' Container-Klasse, die N Objektreferenzen aufnehmen kann.
Class TContainer
Public iItems() ' Array, das die Objektreferenzen aufnimmt
Private iItemsHaveUBound ' True, wenn das Array mindestens ein Element hat
' Konstruktor
Private Sub Class_Initialize
iItemsHaveUBound=false ' Kein Element in iItems vorhanden
End Sub
' Anzahl der enthaltenen Objektreferenzen?
Public Property Get Count
If iItemsHaveUBound Then ' Nur wenn > 0 Elemente enthalten sind (also mindestens einmal ReDim Preserve für iItems gelaufen ist),
' können wir UBound aufrufen. Macht keinen Sinn, ist aber so, ein UBound (E) liefert für ein frisches Private E() einen Subscript error...
Count=UBound (iItems)+1 ' Grösstmöglicher Index+1, da Zählung bei 0 beginnt, und 0-basierender Index+1 = Abzahl
else
Count=0 ' Jungfräuliches iItems(), direkt 0 liefern
End If
End Property
' Getter für indizierte Referenz (Index ist 1-basierend!)
Public Default Property Get Item (ByVal Index)
Set Item=iItems(Index-1)
End Property
' Setter für indizierte Zuweisung (Index ist 1-basierend!)
Public Property Set Item (ByVal Index, ByVal Val)
' MBLogDebugComment "SetItem","Index=" & Index
If Count <= (Index-1) Then
ReDim Preserve iItems (Index-1)
iItemsHaveUBound=true
End If
Set iItems(Index-1)=Val
End Property
Public Property Get AddItem (ByVal Val)
Item(Count+1)=Val
Set AddItem=Val
End Property
End Class
现在,真正的东西来了:
容纳N张纸的容器。我收集每张纸的属性并将它们存储在该容器中
' Klassen, die die Tabellenbkattstrukturen repräsentieren. Hintergrund ist ein ganz abgefahrener: Der Kollektor muss sich die Spaltenstrukturen aller
' intensiv anschauen, um seinen Job zu machen (Verweise verstehen, Klassencode generieren, Zuweisungscode generieren). Dafür greift er wieder und wieder
' auf DTSheet- und DTParameter-Instanzen zu. Das ist performancemässig aber sehr, sehr teuer (warum auch immer!). Um erträgliche Laufzeiten zu erhalten,
' enumeriert der Kollektor im helper BuildTestDataDescr die Sheets und deren Spalten und merkt sich in eigenen Datenstrukturen alles, was er später
' über die Spalten so wissen muss. Anschliessend macht der Kollektor seinen Job anhand dieser Repräsentationen, nicht mehr anhand der
' DataTable-Eigenschaften. Das ergibt funktional das gleiche, macht aber performancemässig einen Riesen-Unterschied.
' Klasse, die eine Tabellenblattspalte repräsentiert
Class TestDataColumnDescr
Public Column ' as DTParameter; Referenz auf die Original-Spalte
Public ColumnName ' as String; der Name der Spalte
Public ColumnKind ' fertig ausgerechnete Spaltenart in Sachen Kollektor
Public ColumnRefdSheet ' as DTSheet; bei Verweisspalte: das verwiesene Sheet
Public ColumnRefdSheetName ' as String; bei Verweisspalte: der Name des verwiesenen Sheets
Public ColumnRefdSheetDescr ' as TestDataSheetDescr; bei Verweisspalte: Referenz auf den TestDataSheetDescr-Descriptor des verwiesenen Sheets
Public ColumnRefdSheetIDColumn ' as DTParameter; bei Verweisspalte: Referenz auf die Original-ID-Spalte des verwiesenen Sheets
Public ColumnRefdSheetPosColumn ' as DTParameter; bei Verweisspalte: Referenz auf die Original-Pos-Spalte des verwiesenen Sheets (Nothing, wenn 1:1)
' Konstruktor
Private Sub Class_Initialize
End Sub
End Class
' Klasse, die ein Tabellenblatt repräsentiert
Class TestDataSheetDescr
Public Sheet ' as DTSheet; Referenz auf das Original-Sheet
Public SheetName ' as String; Name des Sheets
Public SheetRowCount ' as Integer; Anzahl Zeilen im Original-Sheet
Public SheetColumnCount ' as Integer; Anzahl Spalten im Original-Sheet
Public SheetColumn ' as TContainer; Container aller Spaltendescriptoren (TestDataColumnDescr)
' Konstruktor
Private Sub Class_Initialize
Set SheetColumn=New TContainer
End Sub
End Class
以下是用于构建容器内容物的材料:
' Container aller Tabellenblattrepräsentationen
Dim TestDataDescr ' wird in BuildTestDataDescr instanziiert
' Aufbau von Tabellenblattrepräsentationen, damit Kollektor nicht dauernd DataSheet-Funktionen aufrufen muss. TestDataDescr instanziieren, aufbauen.
Public Sub BuildTestDataDescr
' Build N Sheet Descriptors
Dim SheetIndex
Dim ColumnIndex
Dim S
Dim S1
Dim S2
Dim Index
dim SheetDescr, ColumnDescr
' Zunächst die N Sheet-Descriptoren mit ihren Spaltendescriptoren anlegen
'Services.StartTransaction "BuildTestDataDescr"
Set TestDataDescr = New TContainer
For SheetIndex=1 to DataTable.GetSheetCount
set SheetDescr = New TestDataSheetDescr
With TestDataDescr.AddItem (SheetDescr)
Set .Sheet=DataTable.GetSheet (SheetIndex)
.SheetName=.Sheet.Name
.SheetRowCount=.Sheet.GetRowCount
.SheetColumnCount=.Sheet.GetParameterCount
Set S=.Sheet ' .Sheet ist im folgenden With nicht erreichbar, keine Ahnung, warum (nested Withes funken nicht anscheinend)
For ColumnIndex=1 to .SheetColumnCount
set ColumnDescr = New TestDataColumnDescr
With .SheetColumn.AddItem (ColumnDescr)
Set .Column=S.GetParameter (ColumnIndex)
.ColumnName=.Column.Name
End With
Next
End With
Next
' Jetzt etwaige Verweisspalten mit zugehöriger Info anreichern (wir machen das in einem zweiten Schritt, damit wir garantiert zu allen
' verwiesenen Blättern einen Descriptor finden -- ohne Rekursion und komplizierten Abbruchbedingungen bei zyklischen Verweisen...); ferner
' müssen die Namen von auswahltabellenbasierten Spalten angepasst werden:
For SheetIndex=1 to TestDataDescr.Count
With TestDataDescr(SheetIndex)
For ColumnIndex=1 to .SheetColumnCount
Set S=.Sheet ' .Sheet ist im folgenden With nicht erreichbar, keine Ahnung, warum (nested Withes funken nicht anscheinend)
With .SheetColumn(ColumnIndex)
.ColumnKind=DetectColumnKind (.ColumnName,S1,S2)
Select Case .ColumnKind
Case ckComment
' Nuttin', weil: Ist ja eine Gruppier- oder Kommentarspalte -- ignorieren
Case ckData
' Datenspalte -- hier nichts weiter zu tun
.ColumnName=S1 ' ausser: Namen bereinigen (hat nur Folgen für auswahllistenbasierte Spalten)
Case ckReference
' Verweisspalte -- merken, was später immer wieder an info benötigt wird
.ColumnName=S1
Set .ColumnRefdSheet=MBFindSheet (S2)
If .ColumnRefdSheet is Nothing Then
MBErrorAbort "MBUtil.MBCollectAllTestData", _
"Fehler beim Definieren von Klassen;" & vbNewline _
& "Spalte '" & .ColumnName & "' definiert einen Verweis auf Datentabellenblatt '" & S2 & "', welches nicht existiert." & vbNewline _
& "Bitte überprüfen Sie die entsprechenden Datentabellenblätter"
End If
.ColumnRefdSheetName=.ColumnRefdSheet.Name
Set .ColumnRefdSheetIDColumn=.ColumnRefdSheet.GetParameter ("ID")
Set .ColumnRefdSheetPosColumn=MBFindColumn (.ColumnRefdSheet,"Pos")
For Index=1 to TestDataDescr.Count
If TestDataDescr(Index).SheetName = .ColumnRefdSheetName then
Exit For
End If
Next
Set .ColumnRefdSheetDescr=TestDataDescr(Index)
End Select
End With
Next
End With
Next
'Services.EndTransaction "BuildTestDataDescr"
End Sub
基于容器中的信息,我使用TestDataDescr中的结构来迭代列等
如本例中所示,它接收一个容器元素(SourceSheetDescr),查看每一列,并根据列类型执行“操作”,列类型是容器元素中内置信息的一部分:
For ParamIndex=1 to SourceSheetDescr.SheetColumnCount
With SourceSheetDescr.SheetColumn(ParamIndex)
Select Case .ColumnKind
Case ckComment
' Do something
Case ckData
' Do something else Case ckReference
' Do other stuff End Select
End With
Next
这样我就不必查询DTSheet.GetParameter()和acoid来调用任何其他DataTable方法。
当然,这只是使用容器所保存信息的一个示例
在我们的典型用例中,与调用DataTable方法相比,这将性能提高了三倍,而且即使我们已经避免了所有冗余调用,并且在传统的数据表访问代码中进行了所有其他明显的优化。我们在QTP中也遇到了同样的性能问题。经过调查,我们在两个方面解决了问题
- 数据表(糟糕的性能)
- QTP可见/不可见
Dim qtApp
Set qtApp = CreateObject("QuickTest.Application")
qtApp.Launch ' Start QuickTest
If qtApp.Visible = False Then ' Make the QuickTest application invisible/visible
qtApp.Visible = True
Else
qtApp.Visible = False
End If
在我们考虑开发相同的机制时,您是否愿意分享缓存DataTable的想法,并且看到这样一个示例会很有好处
亲切问候,,
Achraf在QTP中的数据表操作非常缓慢。因此,在数据表中使用Excel公式可以加快运算速度 那又怎样?在霍法尔,看到200倍以上的性能损失难道不奇怪吗?它来自哪里?(你知道2.6 GHz PC上的系数200意味着什么吗?)我在VUGEN中观察到了类似的加速,复杂代码使用一些相当复杂的规则动态创建和销毁数据,这就是我缺乏震惊的原因,通常与
Dim qtApp
Set qtApp = CreateObject("QuickTest.Application")
qtApp.Launch ' Start QuickTest
If qtApp.Visible = False Then ' Make the QuickTest application invisible/visible
qtApp.Visible = True
Else
qtApp.Visible = False
End If