オブジェクトの動的コピーを高速化する方法は?

解決


ぶっち  2014-02-16 02:19:24  No: 46036

フォーム上にエディットボックスを動的に生成するプログラムを作っています。
(ユーザーがコントロール数をテキストで指定し、その数に応じた、
動的にエディットボックスを表示するようなプログラムを作っています。)

そこで、以下の様なTEdit.Createを作成するプログラムを実装しました。
(サンプル用に加工していますのでご了承ください。)

■修正前のコード
//Panel1…複製したコントロールを表示するパネル
procedure TForm1.Button1Click(Sender: TObject);
var
  i, Num : Integer;
begin
  Num := 100; //実験として100個複製するものとする
  for i := 0 to Num do
  begin
    with TEdit.Create(Panel1) do
    begin
      Parent :=Panel1;
      //複製できたか確認するため少しずらして表示する
      Left := TPanel(Parent).ControlCount * 2;
    end;
  end;
end;

しかし、上記のコードですと、
コントロール数が1000を超えるような場合に処理が遅くなってしまうため、
高速化できないか悩んでいます。

そこで、以下の様なTMemoryStreamを使用したコードを
作成したのですが効果がみられませんでした。
(修正前よりも若干遅くなってしまいました)

■修正後のコード
//Panel1…複製したコントロールを表示するパネル
procedure TForm1.Button1Click(Sender: TObject);
var
  MemoryStream : TMemoryStream;
  Edit   : TEdit;
  Writer : TWriter;
  Reader : TReader;
  i, Num : Integer;
begin
  Edit := TEdit.Create(nil); //コピー元コントロールを1つ作成
  Edit.Name:= '';            //Nameプロパティを一時的に空にする(複製時にエラーにならないようにするため)

  //コピー元のエディットコントロールをメモリストリームにコピー
  MemoryStream := TMemoryStream.Create;
  try
    Writer:=TWriter.Create(MemoryStream, SizeOf(Edit));
    try
      Writer.RootAncestor := nil;
      Writer.Ancestor := nil;
      Writer.Root := Control.Owner;
      Writer.WriteSignature;
      Writer.WriteComponent(Edit);
      Writer.WriteListEnd;
    finally
      Writer.Free;
    end;
    MemoryStream.Position:=0;

    Reader:=TReader.Create(MemoryStream, SizeOf(Edit));
    try
      Num := 100;
      for i := 0 to Num do
      begin
        Reader.ReadComponents(Panel1.Owner,Panel1,ReadComponentsProc);
        Reader.Position := 0;
      end;
    finally
      Reader.Free;
    end;

  finally
    MemoryStream.Free;
  end;

  Edit.Free;
end;

procedure TForm1.ReadComponentsProc(Component:TComponent);
begin
  //複製できたか確認するため少しずらして表示する
  TEdit(Component).Left := TPanel(TEdit(Component).Parent).ControlCount * 2;
end;

修正後のコードはメモリレベルでコピーとなりますので、
修正前より早くなることを期待していたのですが、
何か方法が悪いのか?と考えています。。。

■質問内容
  もし、上記「修正後のコード」にて、パフォーマンス的な観点で
  悪い点あればご教授いただいてもよろしいでしょうか?

  また、もし他に良い方法(TMemoryStream以外を使用する方法など)が
  あればお知らせいただいてもよろしいでしょうか?

長文となってしまい申し訳ありませんが、よろしくお願いいたします。


Quest  2014-02-16 03:37:52  No: 46037

エディットボックスはタダの文字列などとは違って
ウィンドウを持つコントロールなので、単純なコピーで
作成ってわけにはいかないと思います。
どのように工夫するにしても、Createを呼んでインスタンスを作成する作業は
コントロールの数分だけ実行しなくてはならないでしょう。

コントロール数が1000を超えると遅くなるそうですが
この1000個のコントロールは1画面に表示されるものですか?
スクロールボックスに置いてスクロール表示するってのも考えられますが
もし画面上に一度に表示する数が数十個に収まるなら
「次へ」「前へ」などのボタンを用意して、コントロールの内容だけを
分割表示するようにした方が良いような気がしますが。

エディットボックスが同時に1000個以上必要なシチュエーションを
想像できないので、的をはずしているかもしれません。
差し支えなければ、何をしたいのかを具体的に提示してもらえば
別の方法もあるかもしれません。


KHE00221  2014-02-16 18:33:19  No: 46038

WriteComponentResFile と  ReadComponentResFile


ぶっち  2014-02-18 03:48:26  No: 46039

Quest様、KHE00221様、

ご回答、情報提供いただきありがとうございます。
また、質問の背景などをご説明をしておらず大変申し訳ありません。

背景といたしまして、
ある特定の顧客様向けに開発している、業務アプリケーションの開発を行っているのですが、ある画面の入力項目だけは可変的に増やせるようにしたいという要件をいただいています。

数に関しては、制限は設けない仕様としていますが、
実際は「数1000」そこまではいくことはないと思います。
ただし、場合によっては、数100規模になる場合もあること、
また、実際はデータベースとの連携処理も入っている関係で、
1分ぐらいフリーズしてしまう状況です。
(ボトルネックがコントロールの生成処理となっています。)

ご指摘いただいた通り、数100を超えるような場合も、
画面上に一度に表示せず、スクロールで表示しますので、
下の方のコントロールは、スクロールされるまで、生成しないようにする
などの代案の方法も検討しております。
(ただ、できれば代案を使用せずにパフォーマンスを改善したいと思っています。)

また、WriteCompnentsRes、ReadComponentsResを使用する方法に
変更したのですが、処理速度が改善が変わりませんでした。
変更後のコードを以下に記載いたします。

■WriteCompnentsRes/RaedComponentsResを使用するコード

procedure TForm1.Button1Click(Sender: TObject);
var
  MemoryStream : TMemoryStream;
  Edit, Edit2   : TEdit;
  i, Num : Integer;
begin
  Edit := TEdit.Create(nil); //コピー元コントロールを1つ作成
  Edit := Edit1; 

  Edit.Name:= ''; //Nameプロパティを一時的に空にする(複製時にエラーにならないようにするため

  //コピー元のエディットコントロールをメモリストリームにコピー
  MemoryStream := TMemoryStream.Create;
  try
    MemoryStream.WriteComponentRes('Edit', Edit);

    Num := 100; //実験として100個複製するものとする
    for i := 0 to Num do
    begin
      MemoryStream.Position := 0;
      Edit2 := TEdit.Create(nil);
      MemoryStream.ReadComponentRes(Edit2);

      with Edit2 do
      begin
        Parent :=Panel1;
        //複製できたか確認するため少しずらして表示する
        Left := TPanel(Parent).ControlCount * 2;
      end;
    end;
  finally
   MemoryStream.Free;
  end;

  Edit.Free;
end;

修正前コードと処理時間が変わらない理由ですが、
ReadComponentsResの対象コントロール(Edit2)に
あらかじめCreateの命令が入れているためと思います。
(あらかじめTEdit.Createをしないと
ReadComponentsResの処理でアドレス違反のエラーが発生してしまうため、
直前にCreateするように記述しています。)

ReadComponentsResを使用する場合も、やはりCreateを使用するしかないのでしょうか?
(それとも、使い方に誤りがあるのでしょうか?)

インターネットにてReadComponentsResのサンプルコードを探したのですが、
見つけることができず、使い方も不安な状況でございます…

なお、Delphiのバージョンは2007となっています。(いまさらで申し訳ありません。。。)

もしわかればで構いませんのでご教授いただければ幸いです。


Mr.XRAY  2014-02-18 05:00:08  No: 46040

>エディットボックスが同時に1000個以上必要なシチュエーションを
>想像できないので、的をはずしているかもしれません。

私も,そのような状況が想像できなかったし,たとえ数100個としても,
それを選択する,つまり利用する方の状況が何とも不思議な感じがします.

ところで,一般に,コントロールの描画には時間がかかります.
描画の更新を停止しておけば,気持ちですが,速くなります.
以下のコードは Windows 7 U64(SP1) + Delphi XE で確認していますが,
Delphi 2 以降,Windows 95 以降であれば動作するハズです.

描画の「更新を停止」すると速くなるという参考です.
今回の用途に応用できるかどうかは分かりません.
速いとか遅いとかを問題にしているので,当然,時間を計測しています.

uses MMSystem;

const
  Num = 1000;

//--------------------------------------------------------------
//  オブジェクトの生成だけなら速い
//--------------------------------------------------------------
procedure TForm1.Button1Click(Sender: TObject);
var
  i         : Integer;
  StartTime : DWORD;
begin
  for i := Panel1.ControlCount - 1 downto 0 do begin
    Panel1.Controls[i].Free;
  end;

  StartTime := TimeGetTime;

  for i := 0 to Num do begin
    with TEdit.Create(Panel1) do begin
      Visible := False;
      Parent  :=Panel1;
      Left := 20;
      top  := 50;
    end;
    Application.ProcessMessages;
  end;

  ShowMessage(IntToStr(timeGetTime - StartTime) + ' ms');
end;

//--------------------------------------------------------------
//  描画の更新を停止してオブジェクトを生成すれば少しは速くなる
//--------------------------------------------------------------
procedure TForm1.Button2Click(Sender: TObject);
var
  i         : Integer;
  StartTime : DWORD;
begin
  for i := Panel1.ControlCount - 1 downto 0 do begin
    Panel1.Controls[i].Free;
  end;

  StartTime := TimeGetTime;
  LockWindowUpdate(Panel1.Handle);

  for i := 0 to Num do begin
    with TEdit.Create(Panel1) do begin
      Visible := True;
      Parent  :=Panel1;
      Left := 20;
      top  := 50;
    end;
    Application.ProcessMessages;
  end;
  LockWindowUpdate(0);

  ShowMessage(IntToStr(timeGetTime - StartTime) + ' ms');
end;

//--------------------------------------------------------------
//  描画の更新を停止しないと遅くなる
//--------------------------------------------------------------
procedure TForm1.Button3Click(Sender: TObject);
var
  i         : Integer;
  StartTime : DWORD;
begin
  for i := Panel1.ControlCount - 1 downto 0 do begin
    Panel1.Controls[i].Free;
  end;

  StartTime := TimeGetTime;

  for i := 0 to Num do begin
    with TEdit.Create(Panel1) do begin
      Visible := True;
      Parent  :=Panel1;
      Left := 20;
      top  := 50;
    end;
    Application.ProcessMessages;
  end;

  ShowMessage(IntToStr(timeGetTime - StartTime) + ' ms');
end;

# 私は,環境を提示しない質問は,ほとんど冗談だと思っています(笑)
# 後から提示するというのは,「自分の書き込みをチェックしろ」というのと同じだと思っています.


tor  2014-02-18 07:55:35  No: 46041

うーん、そのたくさんのエディットボックスって別に不規則に並べるわけじゃなくて、たぶん升目状になるんですよね? そういう用途ならStringGridを使うものなんじゃないでしょうか。
仮にエディットたくさん並べたとしても、どうせ編集するのは一度に一箇所だけですし。
何かしらStringGridではダメな事情があるとしても、実際に編集を行うエディットボックスは一つだけにして
残りはLabelなどもっと軽量なモノで代用する方向で考えた方がいいのではないかと思います。


KHE00221  2014-02-18 20:26:49  No: 46042

Parent :=Panel1;
 Left := TPanel(Parent).ControlCount * 2;
を     
 Left := TPanel(Parent).ControlCount * 2;
 Parent :=Panel1;
にする


ぶっち  2014-02-27 22:04:04  No: 46043

レスが遅くなってしまい申し訳ありません。

あれから試行錯誤しておりましたが、
エディット複数ですと高速化が難しかったため、

皆様からいただいたアドバイスをもとに
顧客と交渉の上、StringGrid化することで解決いたしました。

ありがとうございました!


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加