2015-11-27 8 views
9

私はMS Accessファイルからテーブル全体を読みたいので、できるだけ早くそれをやろうとしています。大きなサンプルをテストすると、テーブルの最後のレコードと比較してトップレコードを読み込んだときにループカウンタが高速に増加することがわかりました。ADOTableをスクロールするのがなぜ遅くなり、遅くなるのですか?

procedure TForm1.Button1Click(Sender: TObject); 
const 
    MaxRecords = 40000; 
    Step = 5000; 
var 
    I, J: Integer; 
    Table: TADOTable; 
    T: Cardinal; 
    Ts: TCardinalDynArray; 
begin 
    Table := TADOTable.Create(nil); 
    Table.ConnectionString := 
    'Provider=Microsoft.ACE.OLEDB.12.0;'+ 
    'Data Source=BigMDB.accdb;'+ 
    'Mode=Read|Share Deny Read|Share Deny Write;'+ 
    'Persist Security Info=False'; 
    Table.TableName := 'Table1'; 
    Table.Open; 

    J := 0; 
    SetLength(Ts, MaxRecords div Step); 
    T := GetTickCount; 
    for I := 1 to MaxRecords do 
    begin 
    Table.Next; 
    if ((I mod Step) = 0) then 
    begin 
     T := GetTickCount - T; 
     Ts[J] := T; 
     Inc(J); 
     T := GetTickCount; 
    end; 
    end; 
    Table.Free; 

// Chart1.SeriesList[0].Clear; 
// for I := 0 to Length(Ts) - 1 do 
// begin 
// Chart1.SeriesList[0].Add(Ts[I]/1000, Format(
//  'Records: %s %d-%d %s Duration:%f s', 
//  [#13, I * Step, (I + 1)*Step, #13, Ts[I]/1000])); 
// end; 
end; 

そして、私のPC上の結果:ここではこれを示すサンプルコードです enter image description here

テーブルが2つの文字列フィールド、ダブル1と1つの整数があります。主キーも索引フィールドもありません。なぜそれが起こり、どのように私はそれを防ぐことができますか?

+0

いいえ私はコントロールをプログラムで作成していますが、サンプルコードには何も表示されません。 – saastn

+0

Forループが1つだけオフになっていませんか?とにかく、たくさんのレコードを読めば、多くのメモリ割り当てが必要になり、メモリの割り当てが増えるほど時間がかかることに驚いていますか? – MartynA

+0

@MartynAループについては正しいですね。しかし、私はそれがそれを遅くするメモリ割り当てだとは言えません。 'Table.Open'のすべてのレコードをフェッチするように見えますが、タスクマネージャはその行を実行した後にメモリ割り当てを表示しません。 – saastn

答えて

17

あなたのサイズと同じサイズのMS Sql Serverデータセットを使用してAdoQueryを使用して結果を再現できます。

しかし、ラインプロファイリングの後、私はこれに対する答えを見つけたと思いますが、やや直感的です。 DelphiのDBプログラミングは、Disable/EnableControlsの呼び出しでループを囲むと、データセットをループするほうがはるかに速くなるという考えに慣れています。しかし、データセットにDB認識コントロールが付いていない場合、誰がそれをするのが気になりますか?

DB認識コントロールが存在しないにもかかわらず、状況に応じてDisable/EnableControlsを使用しても速度が大幅に向上することがわかりました。

理由はAdoDB.PasでTCustomADODataSet.InternalGetRecordこの含まれていることである。

 if ControlsDisabled then 
     RecordNumber := -2 else 
     RecordNumber := Recordset.AbsolutePosition; 

をし、私のラインプロファイラーによると、しばらくはAdoQuery1.Eof AdoQuery1.Nextループはその時実行の98.8パーセントを費やしているんではありません割当:

 RecordNumber := Recordset.AbsolutePosition; 

! Recordset.AbsolutePositionの計算はもちろん、Recordsetインターフェイスの「間違った側」には隠されていますが、レコードセットに入るほど明らかに呼び出し時間が長くなるという事実により、計算されたと推測するのは合理的ですレコードセットのデータの先頭から数えて

ControlsDisabledは、DisableControlsが呼び出され、EnableControlsの呼び出しによって元に戻されない場合はtrueを返します。ですから、Disable/EnableControlsで囲まれたループを再テストしてください。うまくいけば、私と同様の結果が得られます。スローダウンがメモリ割り当てに関連していないことが正しいと思われます。

次のコードを使用する:

procedure TForm1.btnLoopClick(Sender: TObject); 
var 
    I: Integer; 
    T: Integer; 
    Step : Integer; 
begin 
    Memo1.Lines.BeginUpdate; 
    I := 0; 
    Step := 4000; 
    if cbDisableControls.Checked then 
    AdoQuery1.DisableControls; 
    T := GetTickCount; 
{.$define UseRecordSet} 
{$ifdef UseRecordSet} 
    while not AdoQuery1.Recordset.Eof do begin 
    AdoQuery1.Recordset.MoveNext; 
    Inc(I); 
    if I mod Step = 0 then begin 
     T := GetTickCount - T; 
     Memo1.Lines.Add(IntToStr(I) + ':' + IntToStr(T)); 
     T := GetTickCount; 
    end; 
    end; 
{$else} 
    while not AdoQuery1.Eof do begin 
    AdoQuery1.Next; 
    Inc(I); 
    if I mod Step = 0 then begin 
     T := GetTickCount - T; 
     Memo1.Lines.Add(IntToStr(I) + ':' + IntToStr(T)); 
     T := GetTickCount; 
    end; 
    end; 
{$endif} 
    if cbDisableControls.Checked then 
    AdoQuery1.EnableControls; 
    Memo1.Lines.EndUpdate; 
end; 

は、以下の結果(DisableControlsでは、特に指定のない限り呼ばれない)私が手:

Using CursorLocation = clUseClient 

AdoQuery.Next AdoQuery.RecordSet AdoQuery.Next 
       .MoveNext    + DisableControls 

4000:157   4000:16    4000:15 
8000:453   8000:16    8000:15 
12000:687   12000:0    12000:32 
16000:969   16000:15   16000:31 
20000:1250   20000:16   20000:31 
24000:1500   24000:0    24000:16 
28000:1703   28000:15   28000:31 
32000:1891   32000:16   32000:31 
36000:2187   36000:16   36000:16 
40000:2438   40000:0    40000:15 
44000:2703   44000:15   44000:31 
48000:3203   48000:16   48000:32 

======================================= 

Using CursorLocation = clUseServer 

AdoQuery.Next AdoQuery.RecordSet AdoQuery.Next 
       .MoveNext    + DisableControls 

4000:1031   4000:454   4000:563 
8000:1016   8000:468   8000:562 
12000:1047   12000:469   12000:500 
16000:1234   16000:484   16000:532 
20000:1047   20000:454   20000:546 
24000:1063   24000:484   24000:547 
28000:984   28000:531   28000:563 
32000:906   32000:485   32000:500 
36000:1016   36000:531   36000:578 
40000:1000   40000:547   40000:500 
44000:968   44000:406   44000:562 
48000:1016   48000:375   48000:547 

は、MDAC/ADO層に直接AdoQuery1.Recordset.MoveNext電話を呼び出します、 コースですが、AdoQuery1.Nextには標準のTDataSet モデルのすべてのオーバーヘッドが含まれます。 Serge Kraikov氏によると、CursorLocationを変更することで確かに差が生じ、clUseClientを使用してDisableControlsを呼び出すよりもかなり遅くなっていますが、われわれが気づいた減速は見られません。私は、あなたがRecordSet.MoveNextでclUseClientを使用する余分な速度を利用できるかどうかにかかわらず、あなたが何をしようとしているのかに依存していると思います。

+0

ありがとう、 'DisableControls'が私のために働いてくれました。しかし、あなたの結果とは異なり、 'clUseServer'は' clUseClient'より遅くはありません。しかし、 'LockType'を' ltReadOnly'に設定しない限り、 'CursorLocation'を' clUseServer'に設定した後、データセットはレコードを返しません。 – saastn

+0

@MartynA好奇心から、どのプロファイラを使用しましたか? –

+0

@ChristianHolmJørgensen:Nexus Quality Suite(www.nexusdb.com)のラインプロファイラを使用しました。これは、同じ名前の古いTurbopower製品の元気です。 – MartynA

1

テーブルを開くと、ADOデータセットは内部的に特殊なデータ構造を作成し、データセットの前方/後方 - 「データセットCURSOR」をナビゲートします。ナビゲーション中、ADOは既に訪問されたレコードのリストを格納して双方向ナビゲーションを提供します。
ADOカーソルコードは、このリストを格納するために2次時間O(n2)アルゴリズムを使用しているようです。
しかし、そこには、この問題を回避している - サーバー側カーソルを使用します。

Table.CursorLocation := clUseServer; 

を、私はこの修正プログラムを使用してコードをテストし、線形時間をフェッチ取得 - レコードのすべての次のチャンクを取得することは、以前と同じ時間がかかります。

PS一部の他のデータアクセスライブラリでは、特別な「単方向」データセットが提供されています。このデータセットは、転送されたレコードだけをトラバースし、トラバースされていないレコードも格納しません。

1

DAOはAccessのネイティブであり、(IMHO)は通常より高速です。 切り替えるかどうかにかかわらず、GetRowsメソッドを使用します。 DAOとADOの両方がそれをサポートしています。 ループがありません。いくつかの行のコードを使用して、レコードセット全体を配列にダンプすることができます。コード: yourrecordset.MoveLast yourrecordset.MoveFirst yourarray = yourrecordset.GetRows(yourrecordset.RecordCount)

+0

多分、OPはDelphiのコードを要求していますが、Delphiでは、通常はdbレコードの配列を処理しません。 – MartynA

+0

MartynAに感謝します。私はDelphiについて何も知らないが、他の言語と同様の構造を持っていると思っただけです。 – AVG

+0

さて、適切な型の配列を宣言するだけで*できますが、Delphiのやり方ではありません。ポイントは、Delphiでは、サポートされているすべてのデータセットタイプは、移動可能な論理カーソルを持つデータセットの一般化されたモデルを含む1つの祖先(TDataset)の子孫です。また、すべてのdb対応コントロールは、配列ではなく、このモデルとやりとりするように設計されています。その結果、すべてのdb対応コントロールは、サポートされているすべてのTDataset子孫で動作します。 – MartynA

関連する問題