Curl Global Community
RecordGridColumnの配列は、フィールドで定義するな - Printable Version

+- Curl Global Community (https://communities.curl.com)
+-- Forum: Blogs (https://communities.curl.com/forumdisplay.php?fid=17)
+--- Forum: Tech blog (https://communities.curl.com/forumdisplay.php?fid=18)
+---- Forum: Baison log (https://communities.curl.com/forumdisplay.php?fid=27)
+---- Thread: RecordGridColumnの配列は、フィールドで定義するな (/showthread.php?tid=1083)



RecordGridColumnの配列は、フィールドで定義するな - umemura - 05-16-2014

★point
 RecordGrid.columns に設定するRecordGridColumnの配列は、フィールドで定義するな!


下記の数値は、RecordGridUI.get-cell-at で、
1回につき、100レコード×100カラム のセルを取得した場合の、
メモリの使用量の推移です。
※Curlのメモリ使用量は、「タスクマネージャ」の「プロセス」タブで表示される
 「surge,.exe」の「メモリ(プライベートワーキングセット)」を参照しました

■セル取得時のメモリ使用量
①「field定義のカラムを利用」した場合のメモリ消費量(単位は100KB)
1回目: 508 差分
2回目: 791 283
3回目:1074 283
4回目:1347 273
5回目:1629 282
6回目:1894 265
7回目:2195 301

②「一時的なカラムを利用」した場合のメモリ消費量(単位は100KB)
1回目: 549 差分
2回目: 791 242
3回目:1075 284
4回目:1347 272
5回目:1367 20
6回目:1383 16
7回目:1407 24



①と②の違いは、下記のアプレットのコードを見てもらうとわかりますが、
簡単に言うと、RecordGrid.columns に設定する{Array-of RecordGridColumn} を、
クラスのフィールド(field)に定義して利用するか、そうでないか、というものです。

Code:
{define-class public Screen {inherits Dialog}

  ||レコードとカラムの数
  field rs-size:int = 100
  field rf-size:int = 100


  ||フィールドとして定義するレコードグリッドカラムの配列
  field ary-column:{Array-of RecordGridColumn}

  ||一時的なレコードグリッドカラムの配列を返却するゲッター
  {getter {tmp-ary-column}:{Array-of RecordGridColumn}
    {return {self.make-columms}}
  }

  {constructor public {default}
    set self.ary-column = {self.make-columms}

    def ary-rf = {{Array-of RecordField}}
    {for i:int = 0 below self.rf-size do
        {ary-rf.append
            {RecordField {String i}, nullable? = true}
        }
    }

    def rs =
        {RecordSet
            {RecordFields {splice ary-rf} }
        }


    {for i:int = 0 below self.rs-size do
        def new-r = {rs.new-record}
        {rs.append new-r}
    }

    def cb-permanent =
        {CommandButton
            label = "field定義のカラムを利用する",
            {on Action do
                {self.get-cell rs, self.ary-column}
            }
        }

    def cb-tmp =
        {CommandButton
            label = "一時的なカラムを利用する",
            {on Action do
                {self.get-cell rs, self.tmp-ary-column}
            }
        }

    def cb-gc =
        {CommandButton
            label = "ガベージコレクト",
            {on Action do
                {garbage-collect}
            }
        }

    {self.add
        {VBox
            cb-permanent,
            cb-tmp,
            cb-gc
        }
    }

  }

  ||セルを取得すrう
  {method {get-cell
              record-set:RecordSet,
              cols:{Array-of RecordGridColumn}
          }:void

    let cnt:int = 0
    def grid = {RecordGrid }
    set grid.columns = cols
    set grid.record-source = record-set


    {for r in grid.records do
        {for col in grid.columns do

            def cell = {grid.ui.get-cell-at r, col}
            {if-non-null cell then
                set cnt = cnt + 1
             else
                {output "cell が見つかりません"}
            }
        }
    }
    {output cnt & "個のセルを生成しました"}
  }


  ||大量のカラムを生成する
  {method {make-columms}:{Array-of RecordGridColumn}

    def cols = {{Array-of RecordGridColumn}}
    {for i:int = 0 below self.rf-size do
        {cols.append
            {RecordGridColumn {String i}}
        }
    }
    {return cols}
  }

}

{def s = {Screen}}
{value s}

数値の推移をみてもわかるように、フィールドで定義して利用した①には、
単純増加傾向がみられるのに比べ、
そうではない②は、4回目以降、メモリの使用増加量が低下しています。
①については、私が試した限りでは20回以上、単純増加傾向か続きました。

また、各処理の間で、明示的に「ガベージコレクト(garbage-collect)」を呼び出した場合も、
①ではその効果がみられませんでした。
プログラムでは、グリッドを毎回作り直しているので、
以前のグリッド内で生成されたセルは、ガベージとして認識され、破棄されてほしいところですが、
私の試した限りでは、そのように期待した結果にはなりませんでした。

どうも、フィールドで定義したカラムを利用して生成されたセルは、
そのフィールドの親クラスが破棄されるまで、
ずっとガベージとして認識されないのではないかと推測されるのです。


静的に、グリッドとカラムを保持するような画面であればよいのですが、
グリッドに対して何度もカラムの再設定をする処理のある画面では、
上記のように、メモリの使用量がどんどん増えてしまう、
という問題が起こる可能性があります。

今回私が試した環境は、Windows7+Curl8.0.4 だけですので、
この現象が、ほかの環境でも発生するかは不明ですが、
できるだけ、RecordGridColumn の配列は、フィールドで定義せず、
記載したアプレットのサンプルソースのようにgetterで一時的に作成するなどの方式にしたほうがよさそうです。





RE: RecordGridColumnの配列は、フィールドで定義するな - umemura - 05-27-2014

フィールド定義より、ゲッターで返したほうが増加傾向は抑えられているのですが、
1万件くらいの大量レコードを読み込んでから、RecordGridUI.get-cell-at で全セルを取得すると、
やはりかなりのメモリを消費し、かつ、ガベージコレクトでもメモリが解放されません。

こまった・・・。



RE: RecordGridColumnの配列は、フィールドで定義するな - umemura - 05-29-2014

私の考え違いでした。メモリ解放されました。

下記のサンプルで試していたのですが、
ポイントは、グリッドに渡したカラムの配列をクリアする、ということのようです。

前のサンプルで、カラムの配列を明示的にクリアしていなくても、
getterの場合にはメモリが解放されていたのは、
RecordGrid.columns に新しいカラムの配列が設定された時点で、
古いカラムの配列がガベージとして判断されたから、ということかもしれません。

field で定義してしまうと、うかつにクリアできないですし、
やはり、RecordGridColumn の配列は、
getter を利用して一時変数で作成する、というのが、
セルのインスタンスをガベージとして認識させるには正しいようです。


Code:
{def columns-tf = {TextField value = "30"}}
{def records-tf = {TextField value = "1000"}}

{HBox "カラム数", columns-tf}
{HBox "レコード数", records-tf}

{let grid:#RecordGrid = null}
{def col-ary = {{Array-of RecordGridColumn}}}


{let rs:#RecordSet = null}
{def make-grid =
    {CommandButton
        label = "レコードセットの作成",
        {on Action do
            def rf-ary = {{Array-of RecordField}}
            {for i:int = 0 below {columns-tf.value.to-int} do
                {rf-ary.append
                    {RecordField {String i}, nullable? = true}
                }
            }
            set rs =
                {RecordSet
                    {RecordFields
                        {splice rf-ary}
                    }
                }
            {for i:int = 0 below {records-tf.value.to-int} do
                def new-r = {rs.new-record}
                {rs.append new-r}
            }

            set grid =
                {RecordGrid
                    width = 7in
                    ,height = 5in
                }

            {col-ary.clear}
            ||対象レコードセットのフィールド数だけ、
            ||独自のレコードグリッドを作成する
            {for rf in rs.fields do
                {col-ary.append
                    {RecordGridColumn
                        rf.name
                    }
                }
            }
            set grid.columns = col-ary

            set grid.record-source = rs
        }
    }
}

{def get-cell-cb =
    {CommandButton
        label = "セルの取得",
        {on Action do


            def dt = {DateTime}

            def cell-ary = {{Array-of RecordGridCell}}
            let cnt:int = 0
            {for r in grid.records do
                {for col in grid.columns do
                    {if-non-null cell = {grid.ui.get-cell-at r, col} then
                        {if {cell-ary.find cell} == -1 then
                            {cell-ary.append cell}
                            set cnt = cnt + 1
                        }
                    }
                }
            }

            {popup-message  cnt & "個のセルを取得(" &   {dt.elapsed} & ")"}
        }
    }
}


{def gc-cb =
    {CommandButton
        label = "ガベージコレクト",
        {on Action do
            {garbage-collect}
        }
    }
}

{def reset-grid-cb =
    {CommandButton
        label = "グリッドのリセット",
        {on Action do

            ||★point
            ||セルをガベージ対象にするには、
            ||RecordGrid.columns に渡した元の配列をクリアする!
            {col-ary.clear}

            set grid.record-source = null
            set grid = null
        }
    }
}

{HBox make-grid, get-cell-cb, reset-grid-cb, gc-cb}