C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[23]: DataSetの破棄について


(過去ログ 140 を表示中)

[トピック内 29 記事 (1 - 20 表示)]  << 0 | 1 >>

■81781 / inTopicNo.1)  DataSetの破棄について
  
□投稿者/ sk (1回)-(2016/11/14(Mon) 11:38:37)

分類:[.NET 全般] 

OS:windows server 2012
環境:visual studio 2012 (VB)

以下のような処理を延々とタイマー内で実行すると
5000万回を超えたぐらいで、new実行時に応答が無くなってしまいます。
単純にDataSetのNewを繰り返すと発生してしまうものなのでしょうか?
また、下記処理に何か問題があるのでしょうか?
ご教示願います。


Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

Dim dstData As DataSet
dstData = New DataSet1
dstData.Clear()
'dstData.Disposeでも結果は同じだった。

End Sub
引用返信 編集キー/
■81788 / inTopicNo.2)  Re[1]: DataSetの破棄について
□投稿者/ 魔界の仮面弁士 (942回)-(2016/11/14(Mon) 17:51:34)
No81781 (sk さん) に返信
> 5000万回を超えたぐらいで、new実行時に応答が無くなってしまいます。

5000万回というと、1秒更新で1.6年間、
50ミリ秒更新で一ヶ月弱という回数ですね。

追検証しにくい物だとは思いますが、応答が無くなる回数は
いつも 5000万回 を超えたあたりなのでしょうか。


とりあえずタイマーなしで、空の DataSet1 を生成してみましたが、
特に問題は発生しませんでした。カウントアップの追跡部分など、
何か別の場所で問題が起きている可能性は無いでしょうか。

 For I = 1 To 70000000
  Dim dstData As DataSet
  dstData = New DataSet1()
  dstData.Clear()
 Next
引用返信 編集キー/
■81789 / inTopicNo.3)  Re[2]: DataSetの破棄について
□投稿者/ sk (3回)-(2016/11/14(Mon) 19:13:42)
No81788 (魔界の仮面弁士 さん) に返信
> ■No81781 (sk さん) に返信
>>5000万回を超えたぐらいで、new実行時に応答が無くなってしまいます。
>
> 5000万回というと、1秒更新で1.6年間、
> 50ミリ秒更新で一ヶ月弱という回数ですね。
>
> 追検証しにくい物だとは思いますが、応答が無くなる回数は
> いつも 5000万回 を超えたあたりなのでしょうか。
>
>
> とりあえずタイマーなしで、空の DataSet1 を生成してみましたが、
> 特に問題は発生しませんでした。カウントアップの追跡部分など、
> 何か別の場所で問題が起きている可能性は無いでしょうか。
>
>  For I = 1 To 70000000
>   Dim dstData As DataSet
>   dstData = New DataSet1()
>   dstData.Clear()
>  Next

返信ありがとうございます。
カウントアップ処理も確認しましたが、問題ないと考えています。
また、こちらの環境でも空のDataSetでは現象は発生しませんでした。
ただし、データセットにデータベース(postgres)をロード(テーブル80個)した
状態では5000万回程度で100%発生します。
毎回同じぐらいの回数で発生するので、何か解放漏れがあるのではないか?
と推測しています。
(メモリやハンドル、スレッドの上昇は見られません‥)
Clearではリソースが完全に解放されないということがあるのでしょうか?


引用返信 編集キー/
■81798 / inTopicNo.4)  Re[3]: DataSetの破棄について
□投稿者/ 魔界の仮面弁士 (943回)-(2016/11/15(Tue) 10:02:27)
No81789 (sk さん) に返信
> ただし、データセットに
型付きDataSet を用いず、素の System.Data.DataSet に Tables.Add していった場合も
同様の現象になるのでしょうか。

また、その型付き DataSetは Webサービス等で公開されたものだったりはしないでしょうか。
通常の型付き DataTable は、TypedTableBase(Of ) を継承して作られますが、
Web 参照等を通じて再生成された型付きDataTableは、素のDataTableから継承されるなど、
微妙に動作が異なっていたりします。


> データベース(postgres)をロード(テーブル80個)した
問題が発生しているのは、New の部分なのですよね。

型付きDataSet なら、スキーマの生成(Tables.Add や Columns.Add)は
コンストラクタが呼ばれたタイミングで発生しますが、
データの読みこみは、コンストラクタでは通常起こりません。

New のタイミングではなく Clear のタイミングなら、
データの処分コストがかかる気がしますが、提示されたコードには、
データをロードしている箇所もありませんし。

いずれにせよ、DataSet にとってみれば、元データが
postgres (PostgreSQL?) かどうかは関係無いと思います。
DataAdapter 等にとっては重要な情報ですが。


> 状態では5000万回程度で100%発生します。
(1) その「5000万回」という情報は、どのようにして確認されたものでしょうか。

(2) 発生するのは 5000万を超えた後でしょうか。
それとも 5000万の手前で発生するのでしょうか。
正確な回数が分かれば、参考値としてお聞かせください。

(3) 応答無しというのは、「Tick イベントがそもそも呼ばれない」事象でしょうか。
 それとも「Tick は呼ばれるが New の実行が完了しない」という事象でしょうか。

(4) 応答が無くなってしまうというのは、具体的にはどの程度の時間だったのでしょうか。
たとえば約90秒後に回復する現象だとか、あるいは回復後もパフォーマンスが低下するとか
それとも、15分経っても完全に何も動きが無い状態になってしまっていただとか。
(数十ミリ秒程度の停止なら、GC の稼動タイミングという可能性もありそうですが)


> 毎回同じぐらいの回数で発生するので、何か解放漏れがあるのではないか?
> と推測しています。
そもそも事象を正確に掴めていないため、問題点を掴めていません。

一応 DataSet の内部実装を追ってみましたが、現時点では特に思い当たりませんでした。
https://referencesource.microsoft.com/#System.Data/System/Data/DataSet.cs

気になるとすれば、型付きDataSet のコンストラクタで、スキーマの
変更通知のイベントハンドラを割り当ていますが、それを剥がすコードが
無いようでした。(これが問題になる事象かどうかは別として)


ただ、NewRow メソッドについての、こんな投稿は見つかりました。
http://d.hatena.ne.jp/genja/20090911/1252623184

また、Clear しないとメモリが解放されないという投稿もありました。
http://anton0825.hatenablog.com/entry/20110131/1296708900


いずれも根拠が書かれていないので、根本理由までは不明ですが、
仮に Clear すれば改善されるというのであれば、たとえば駄目元で
 dstData.Clear()
 dstData.Relations.Clear()
 dstData.Tables.Clear()
のように、行だけではなくスキーマ情報も抹消してみては如何でしょう。
引用返信 編集キー/
■81799 / inTopicNo.5)  Re[4]: DataSetの破棄について
□投稿者/ sk (4回)-(2016/11/15(Tue) 11:45:46)
ご返信ありがとうございます。
以下インラインで回答させて頂きます。

No81798 (魔界の仮面弁士 さん) に返信
> ■No81789 (sk さん) に返信
>>ただし、データセットに
> 型付きDataSet を用いず、素の System.Data.DataSet に Tables.Add していった場合も
> 同様の現象になるのでしょうか。

確認できていないので、確認させていただきます。


> また、その型付き DataSetは Webサービス等で公開されたものだったりはしないでしょうか。
> 通常の型付き DataTable は、TypedTableBase(Of ) を継承して作られますが、
> Web 参照等を通じて再生成された型付きDataTableは、素のDataTableから継承されるなど、
> 微妙に動作が異なっていたりします。

データセットはサーバエクスプローラと関連させたあとD&Dで作成しています。
特に特殊なものではないと思っています。


>>データベース(postgres)をロード(テーブル80個)した
> 問題が発生しているのは、New の部分なのですよね。
>
> 型付きDataSet なら、スキーマの生成(Tables.Add や Columns.Add)は
> コンストラクタが呼ばれたタイミングで発生しますが、
> データの読みこみは、コンストラクタでは通常起こりません。
>
> New のタイミングではなく Clear のタイミングなら、
> データの処分コストがかかる気がしますが、提示されたコードには、
> データをロードしている箇所もありませんし。
>
> いずれにせよ、DataSet にとってみれば、元データが
> postgres (PostgreSQL?) かどうかは関係無いと思います。
> DataAdapter 等にとっては重要な情報ですが。

DataSetのNew内のMe.InitClass()で止まってしまっています。
(現象発生時、Me.InitClass()直後のログ出力がされていない。)


>
>>状態では5000万回程度で100%発生します。
> (1) その「5000万回」という情報は、どのようにして確認されたものでしょうか。

カウンタを用意して加算、ログ出力しています。


> (2) 発生するのは 5000万を超えた後でしょうか。
> それとも 5000万の手前で発生するのでしょうか。
> 正確な回数が分かれば、参考値としてお聞かせください。

以下の通りです。
実際に業務で作成したプログラムでの回数
1回目:54364159
2回目:54364144
3回目:54363946
4回目:54366666

投稿させていただいたサンプルプログラムでの回数
1回目:53024288
2回目:53024288


> (3) 応答無しというのは、「Tick イベントがそもそも呼ばれない」事象でしょうか。
>  それとも「Tick は呼ばれるが New の実行が完了しない」という事象でしょうか。

Tickイベント内でNewが実行され、Newが完了せずそのまま応答なしになっている状態です。


>
> (4) 応答が無くなってしまうというのは、具体的にはどの程度の時間だったのでしょうか。
> たとえば約90秒後に回復する現象だとか、あるいは回復後もパフォーマンスが低下するとか
> それとも、15分経っても完全に何も動きが無い状態になってしまっていただとか。
> (数十ミリ秒程度の停止なら、GC の稼動タイミングという可能性もありそうですが)

1〜2日放置しても回復しなかったので、そのまま放置しても回復はしないと思います。


>>毎回同じぐらいの回数で発生するので、何か解放漏れがあるのではないか?
>>と推測しています。
> そもそも事象を正確に掴めていないため、問題点を掴めていません。
>
> 一応 DataSet の内部実装を追ってみましたが、現時点では特に思い当たりませんでした。
> https://referencesource.microsoft.com/#System.Data/System/Data/DataSet.cs
>
> 気になるとすれば、型付きDataSet のコンストラクタで、スキーマの
> 変更通知のイベントハンドラを割り当ていますが、それを剥がすコードが
> 無いようでした。(これが問題になる事象かどうかは別として)
>
>
> ただ、NewRow メソッドについての、こんな投稿は見つかりました。
> http://d.hatena.ne.jp/genja/20090911/1252623184
>
> また、Clear しないとメモリが解放されないという投稿もありました。
> http://anton0825.hatenablog.com/entry/20110131/1296708900
>
>
> いずれも根拠が書かれていないので、根本理由までは不明ですが、
> 仮に Clear すれば改善されるというのであれば、たとえば駄目元で
>  dstData.Clear()
>  dstData.Relations.Clear()
>  dstData.Tables.Clear()
> のように、行だけではなくスキーマ情報も抹消してみては如何でしょう。

ありがとうございます。
上記の削除処理を入れて再度確認してみます。

引用返信 編集キー/
■81801 / inTopicNo.6)  Re[5]: DataSetの破棄について
□投稿者/ 魔界の仮面弁士 (944回)-(2016/11/15(Tue) 14:45:37)
No81799 (sk さん) に返信
> DataSetのNew内のMe.InitClass()で止まってしまっています。
> (現象発生時、Me.InitClass()直後のログ出力がされていない。)

それはつまり、そもそも InitClass が呼び出されていない、ということでしょうか。
それとも、InitClass 内のいずれかで停止している、ということでしょうか。

仮に InitClass 内で時間がかかっているとしても、停止してしまうような
処理には心当たりがありません。コンストラクタからの呼び出しなら尚の事。



> 実際に業務で作成したプログラムでの回数
> 投稿させていただいたサンプルプログラムでの回数

検証時の参考にさせていただきます。

ひとまず、下記条件の DataSet1 を作成して追検証中ですが、
タイマー依存のテストなので、非常に時間がかかりますね…。

・DataSet 内に、テーブルを60個作成(DataTable1〜DataTable60)
・それぞれ、40個のテキスト列を実装(Column1〜Column40)
・各テーブルの Column40 は PrimaryKey として割り当て
・CaseSensitive = True
・EnforceConstraints = True
・RelationShip なし
・TableAdapter なし
・Timer1.Inerval = 1
・X64 ビルド
・VB2012 + .NET 4.6.1
・タスク優先度 = AboveNormal
引用返信 編集キー/
■81807 / inTopicNo.7)  Re[6]: DataSetの破棄について
□投稿者/ sk (5回)-(2016/11/15(Tue) 16:38:45)
No81801 (魔界の仮面弁士 さん) に返信
> ■No81799 (sk さん) に返信
>>DataSetのNew内のMe.InitClass()で止まってしまっています。
>>(現象発生時、Me.InitClass()直後のログ出力がされていない。)
>
> それはつまり、そもそも InitClass が呼び出されていない、ということでしょうか。
> それとも、InitClass 内のいずれかで停止している、ということでしょうか。
>
> 仮に InitClass 内で時間がかかっているとしても、停止してしまうような
> 処理には心当たりがありません。コンストラクタからの呼び出しなら尚の事。

InitClass()をログで挟んだところ、直前のログが出力されていましたので、
恐らくInitClass内のどこかで停止していると思われます。
仮面弁士さんが心当たりがないということは、何か他に原因があるのでしょうか‥。


>>実際に業務で作成したプログラムでの回数
>>投稿させていただいたサンプルプログラムでの回数
>
> 検証時の参考にさせていただきます。
>
> ひとまず、下記条件の DataSet1 を作成して追検証中ですが、
> タイマー依存のテストなので、非常に時間がかかりますね…。
>
> ・DataSet 内に、テーブルを60個作成(DataTable1〜DataTable60)
> ・それぞれ、40個のテキスト列を実装(Column1〜Column40)
> ・各テーブルの Column40 は PrimaryKey として割り当て
> ・CaseSensitive = True
> ・EnforceConstraints = True
> ・RelationShip なし
> ・TableAdapter なし
> ・Timer1.Inerval = 1
> ・X64 ビルド
> ・VB2012 + .NET 4.6.1
> ・タスク優先度 = AboveNormal

わざわざありがとうございます。
こちらも引き続き検証を行い、結果をご報告させていただきます。

引用返信 編集キー/
■81810 / inTopicNo.8)  Re[6]: DataSetの破棄について
□投稿者/ 魔界の仮面弁士 (949回)-(2016/11/16(Wed) 10:04:20)
No81801 (魔界の仮面弁士) に追記
> ひとまず、下記条件の DataSet1 を作成して追検証中ですが、
> タイマー依存のテストなので、非常に時間がかかりますね…。

ここまで21時間回して450万回。5436万回の壁は遠いです。

少なくとも現在の当方環境では、System.Windows.Forms.Timer の分解能が
秒間64回(15.6ミリ秒間隔)程度しか無い様なので、結果が出るまでには
10日はかかる見込みです。

# timeBeginPeriod(1) は TIMERR_NOERROR を返したようだけど、効いていなかったかな。
引用返信 編集キー/
■81812 / inTopicNo.9)  Re[7]: DataSetの破棄について
□投稿者/ sk (6回)-(2016/11/16(Wed) 10:55:11)
No81810 (魔界の仮面弁士 さん) に返信
> ■No81801 (魔界の仮面弁士) に追記
>>ひとまず、下記条件の DataSet1 を作成して追検証中ですが、
>>タイマー依存のテストなので、非常に時間がかかりますね…。
>
> ここまで21時間回して450万回。5436万回の壁は遠いです。
>
> 少なくとも現在の当方環境では、System.Windows.Forms.Timer の分解能が
> 秒間64回(15.6ミリ秒間隔)程度しか無い様なので、結果が出るまでには
> 10日はかかる見込みです。
>
> # timeBeginPeriod(1) は TIMERR_NOERROR を返したようだけど、効いていなかったかな。

申し訳ありません。
こちらの検証環境では、以下のような処理で約2日強で発生しています。
正確に記載せずに申し訳ありません。

Timer1.Inerval = 10

Private _intCount As Integer = 0
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

for intLoop as integer = 0 to 100

_intCount = _intCount + 1
'※ログ出力処理

Dim dstData As DataSet
dstData = New DataSet1
dstData.Clear()
'dstData.Disposeでも結果は同じだった。

Next

End Sub

引用返信 編集キー/
■81821 / inTopicNo.10)  Re[8]: DataSetの破棄について
□投稿者/ 魔界の仮面弁士 (950回)-(2016/11/16(Wed) 15:38:05)
No81812 (sk さん) に返信
> 申し訳ありません。
> こちらの検証環境では、以下のような処理で約2日強で発生しています。

そうでしたか。途中経過を報告して良かったです。

だとすると、タイマー処理(WM_TIMER メッセージ)は関係なさそうなので、
現在の計測は中止して、ワーカースレッドで無限ループさせる
テスト手法に切り替えてみます。
それでも 25 時間ほどかかりそうですが…。(^_^;)


> for intLoop as integer = 0 to 100

101 回のループですね。

あまり元のコードを変えない方がよいのかも知れませんが、
とりあえずこんな感じにしてみました。


'---------------------
Imports System.Threading
Partial Public Class DataSet1
    Private Shared Log As String            'コンストラクター内の呼び出し履歴
    Private Shared InitClassLine As Integer 'InitClass 内の何行目まで進んだかをカウントアップ

    Friend Shared Function GetLog() As String
        Return "InitClass 内の行位置: " & InitClassLine.ToString("#,0") & vbCrLf & Log
    End Function

    ' <DebuggerNonUserCodeAttribute(), GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")> _
    Public Sub New()
        ' DataSet1.Designer.vb から移植
        MyBase.New()
        InitClassLine = 0
        Log = " DataSet1 : before [BeginInit()]" & vbCrLf
        Me.BeginInit()
        Log &= " DataSet1 : before [InitClass()]" & vbCrLf
        Me.InitClass()
        Log &= " DataSet1 : before [AddHandler()]" & vbCrLf
        Dim schemaChangedHandler As Global.System.ComponentModel.CollectionChangeEventHandler = AddressOf Me.SchemaChanged
        AddHandler MyBase.Tables.CollectionChanged, schemaChangedHandler
        AddHandler MyBase.Relations.CollectionChanged, schemaChangedHandler
        Log &= " DataSet1 : before [EndInit()]" & vbCrLf
        Me.EndInit()
        Log &= " DataSet1 : end of constructor" & vbCrLf
    End Sub

#Region "Private Sub InitClass()"
    ' <DebuggerNonUserCodeAttribute(), GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")>
    Private Sub InitClass()
        InitClassLine = 1
        Me.DataSetName = "DataSet1"
        InitClassLine = 2
        Me.Prefix = ""
        InitClassLine = 3
        Me.Namespace = "http://tempuri.org/DataSet1.xsd"
        InitClassLine = 4
        Me.CaseSensitive = True
        InitClassLine = 5
        Me.EnforceConstraints = True
        InitClassLine = 6
        Me.SchemaSerializationMode = Global.System.Data.SchemaSerializationMode.IncludeSchema
        InitClassLine = 1010
        Me.tableSampleTable1 = New SampleTable1DataTable()
        InitClassLine = 1011
        MyBase.Tables.Add(Me.tableSampleTable1)
        InitClassLine = 1020
        Me.tableSampleTable2 = New SampleTable2DataTable()
        InitClassLine = 1021
        '--- 中略 ---
        InitClassLine = 1600
        Me.tableSampleTable60 = New SampleTable60DataTable()
        InitClassLine = 1601
        MyBase.Tables.Add(Me.tableSampleTable60)
        InitClassLine = 9999
    End Sub
#End Region
End Class

'---------------------

Imports System.Threading
Imports System.ComponentModel

Public Class Form1
    Private intCount As Integer = 0
    Private isBusy As Boolean = False
    Private cancel As Boolean = False

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        BackgroundWorker1.WorkerReportsProgress = True
        BackgroundWorker1.WorkerSupportsCancellation = True
        Button1.Text = "計測開始"
        Button2.Text = "計測中止"
        Button3.Text = "処理状況"
        Button1.Enabled = True
        Button2.Enabled = False
        Button3.Enabled = False
        TextBox1.ScrollBars = ScrollBars.Both
        TextBox1.Clear()
        Label1.Text = "開始待ち"
    End Sub

    Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
        If isBusy Then
            e.Cancel = True
            MsgBox("計測中です", MsgBoxStyle.Exclamation)
        End If
    End Sub

    Private Function Test() As Integer
        Thread.CurrentThread.Priority = ThreadPriority.Highest
        Dim currentCount As Integer = intCount
        '101匹の大行進
        For dalmatians = 0 To 100
            currentCount = Interlocked.Increment(intCount)

            Dim dstData As DataSet
            dstData = New DataSet1
            dstData.Clear() 'とりあえず Using 無しでテスト
        Next
        Return currentCount
    End Function

    Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        isBusy = True
        Label1.Text = "計測中"
        Button1.Enabled = False
        Button2.Enabled = True
        Button3.Enabled = True
        Button1.Text = Now.ToString("HH:mm:ss.fff yyyy/MM/dd")

        Dim sw = Stopwatch.StartNew()
        Do
            Dim currentCount = Await Task.Run(AddressOf Test)
            Me.Text = currentCount.ToString("#,0") & "回経過 " & sw.Elapsed.ToString("c")
        Loop Until cancel
        sw.Stop()
        isBusy = False
        Label1.Text = "測定完了" & vbCrLf & sw.Elapsed.ToString("G")
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        Button2.Enabled = False
        Label1.Text = "停止中"
        cancel = True
    End Sub

    Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
        Dim count = intCount
        TextBox1.Text = String.Format("{0:#,0}回目", count) & vbCrLf & DataSet1.GetLog()
    End Sub
End Class

引用返信 編集キー/
■81832 / inTopicNo.11)  Re[9]: DataSetの破棄について
□投稿者/ sk (7回)-(2016/11/16(Wed) 18:00:01)
No81821 (魔界の仮面弁士 さん) に返信
> ■No81812 (sk さん) に返信

> あまり元のコードを変えない方がよいのかも知れませんが、
> とりあえずこんな感じにしてみました。

ありがとうございます。
こちらの環境でも同様の仕組みで動作を開始してみます。
(そちらで現象が発生しなかった場合の切り分け材料の一つとして)


> 'とりあえず Using 無しでテスト

ちなみに、Usingを使用してもこちらの環境では結果は同じでした。

引用返信 編集キー/
■81875 / inTopicNo.12)  Re[10]: DataSetの破棄について
□投稿者/ sk (8回)-(2016/11/18(Fri) 16:38:42)
No81798 (魔界の仮面弁士 さん) に返信
> ■No81789 (sk さん) に返信
>>ただし、データセットに
> 型付きDataSet を用いず、素の System.Data.DataSet に Tables.Add していった場合も
> 同様の現象になるのでしょうか。

只今、検証中です。


> いずれも根拠が書かれていないので、根本理由までは不明ですが、
> 仮に Clear すれば改善されるというのであれば、たとえば駄目元で
>  dstData.Clear()
>  dstData.Relations.Clear()
>  dstData.Tables.Clear()
> のように、行だけではなくスキーマ情報も抹消してみては如何でしょう。

スキーマ情報を削除するようにしましたが、53,024,288回で停止しました。


> あまり元のコードを変えない方がよいのかも知れませんが、
> とりあえずこんな感じにしてみました。

仮面弁士さんから頂いたソースを組み込んだところやはり停止してしまいました。
結果は以下の通りです。

53,024,288回目
InitClass 内の行位置: 1,460
DataSet1 : before [BeginInit()]
DataSet1 : before [InitClass()]

コード抜粋
    ‥
MyBase.Tables.Add(テーブル1)
InitClassLine = 1460
Me.tabled_テーブル2 = New テーブル2DataTable
    ‥


以上、経過報告まで。
引用返信 編集キー/
■81879 / inTopicNo.13)  Re[11]: DataSetの破棄について
□投稿者/ 魔界の仮面弁士 (967回)-(2016/11/18(Fri) 17:06:04)
No81875 (sk さん) に返信
>>あまり元のコードを変えない方がよいのかも知れませんが、
>>とりあえずこんな感じにしてみました。
> 仮面弁士さんから頂いたソースを組み込んだところやはり停止してしまいました。

こちらは現状、動き続けてますね…。

25時間25分ほど経過した段階の途中経過がこんな感じ。

58,556,557回目
InitClass 内の行位置: 9,999
DataSet1 : before [BeginInit()]
DataSet1 : before [InitClass()]
DataSet1 : before [AddHandler()]
DataSet1 : before [EndInit()]
DataSet1 : end of constructor
引用返信 編集キー/
■81883 / inTopicNo.14)  Re[12]: DataSetの破棄について
□投稿者/ sk (9回)-(2016/11/18(Fri) 17:40:21)
No81879 (魔界の仮面弁士 さん) に返信
> ■No81875 (sk さん) に返信
> >>あまり元のコードを変えない方がよいのかも知れませんが、
> >>とりあえずこんな感じにしてみました。
>>仮面弁士さんから頂いたソースを組み込んだところやはり停止してしまいました。
>
> こちらは現状、動き続けてますね…。

そうですか‥‥

今回こちらでは、今までと違うOS(7)で動作させたにも関わらず「53,024,288回」という
ぴったり回数で停止したので、環境に依存はしないと想定します。(希望)
あと違いを考えると、Tableの個数が違いますので、Table数に比例している(?)とすると、
仮面弁士さんの環境では「66,280,240回」ぐらいで発生する(?)かもしれません。

引用返信 編集キー/
■81887 / inTopicNo.15)  Re[13]: DataSetの破棄について
□投稿者/ 魔界の仮面弁士 (970回)-(2016/11/18(Fri) 20:01:01)
2016/11/18(Fri) 21:04:04 編集(投稿者)

No81883 (sk さん) に返信
>>こちらは現状、動き続けてますね…。
> 今回こちらでは、今までと違うOS(7)で動作させたにも関わらず「53,024,288回」という
> ぴったり回数で停止したので、環境に依存はしないと想定します。(希望)


古い情報ですが、こんな投稿がありました。


[継承したコントロールのメモリの解放]
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=47943&forum=7

》 投稿日時: 2009-01-21 10:06
》 これ、Release モードだと発生しませんね。


当方では Release ビルドの EXE で実行させていますので、
再現しないかも知れません。


》 投稿日時: 2009-02-18 01:13
》 どうも最適化を有効にしない場合は、VBコンパイラが
》 作成したクラスのコンストラクタに以下のようなコードを挿入していますね。

》 <DebuggerNonUserCode> _
》 Public Sub New()
》   Dim list As List(Of WeakReference) = CustomDataSet.__ENCList
》   SyncLock list
》     CustomDataSet.__ENCList.Add(New WeakReference(Me))
》   End SyncLock
》 End Sub


__ENCList というと、Edit & Continue のヘルパーですね。

その方向で検索してみたら、こんな記事が見つかりました。
https://support.microsoft.com/ja-jp/kb/919481

このあたりは VS2010 で改善されているそうなので、
関係ないかも知れませんが、一応参考までに。


---
追記:DataSet の弱参照に関して、こんな記事もありました。
内容的には上記と同じかな。

https://social.msdn.microsoft.com/Forums/vstudio/en-US/b83106bd-8afe-4291-8620-f173e214cd23/weakreferences-in-a-debug-build?forum=clr
引用返信 編集キー/
■81889 / inTopicNo.16)  Re[14]: DataSetの破棄について
□投稿者/ 魔界の仮面弁士 (971回)-(2016/11/18(Fri) 21:45:18)
2016/11/18(Fri) 21:49:56 編集(投稿者)

No81887 (魔界の仮面弁士) に追記
> 当方では Release ビルドの EXE で実行させていますので、
> 再現しないかも知れません。

No81821 で作った DataSet1 を逆コンパイルしてみました。
IL のままだと読み難いので、VB コードに置き換えています。


<Releaseビルド>
Public Sub New()
 Me._schemaSerializationMode = SchemaSerializationMode.IncludeSchema
 DataSet1.InitClassLine = 0
 DataSet1.Log = " DataSet1 : before [BeginInit()]" & vbCrLf
 Me.BeginInit()
 以下略


<Debugビルド>
Private Shared __ENCList As New List<WeakReference>();
Public Sub New()
 DataSet1.__ENCAddToList(Me) '★
 Me._schemaSerializationMode = SchemaSerializationMode.IncludeSchema
 DataSet1.InitClassLine = 0
 DataSet1.Log = " DataSet1 : before [BeginInit()]" & vbCrLf
 Me.BeginInit()
 以下略


VB2012 でも起こりえるようです。
https://social.msdn.microsoft.com/Forums/en-US/a304a4d5-660d-4df2-a40d-1d6ca76b8e62/contractclassforattribute-and-encaddtolist?forum=codecontracts


――ということで、現段階の私の見解としては:

Event を持つクラスを、VB.NET で Debug ビルドした場合、
Edit & Continue の仕組みのために、WeakReference なリストに
そのインスタンスが保持されるようになっているため、超高頻度で
大量にインスタンス化した場合には、GC 回収が間に合わずに
メモリを圧迫する結果になりえる。それが今回の「停止」の要因となった。

――と予想してみました。

まぁ、当方環境にて Debug ビルドな動作テストを行ったわけでも無いですし、
Event の無いクラスでどうなるのかも調べていないので、確証は無いですが。


# でもこのパターンなら、動作が停止するのではなく、例外で通達されるか、
# プロセス自体が落とされる事態になっても良いような気がする…。
引用返信 編集キー/
■81892 / inTopicNo.17)  Re[15]: DataSetの破棄について
□投稿者/ sk (10回)-(2016/11/18(Fri) 23:11:03)
No81889 (魔界の仮面弁士 さん) に返信
> 2016/11/18(Fri) 21:49:56 編集(投稿者)

> Event を持つクラスを、VB.NET で Debug ビルドした場合、
> Edit & Continue の仕組みのために、WeakReference なリストに
> そのインスタンスが保持されるようになっているため、超高頻度で
> 大量にインスタンス化した場合には、GC 回収が間に合わずに
> メモリを圧迫する結果になりえる。それが今回の「停止」の要因となった。
>
> ――と予想してみました。

DebugビルドとReleaseビルドでそのような違いがあるとは知りませんでした。
今回確認用として作成したサンプルプログラムは
Debugビルドで作成しているので、上記要因に当たる可能性は十分あると思います。
しかし、実際業務で作成しているものはReleaseでビルドしていますので
要因としては・・と思います。

引用返信 編集キー/
■81900 / inTopicNo.18)  Re[12]: DataSetの破棄について
□投稿者/ 魔界の仮面弁士 (974回)-(2016/11/21(Mon) 10:36:22)
No81879 (魔界の仮面弁士 さん) に返信
> こちらは現状、動き続けてますね…。
> 25時間25分ほど経過した段階の途中経過がこんな感じ。
> 58,556,557回目

再現しました。
Release ビルドにおいて、60時間22分58秒後 に停止しています。

71,582,789回目
InitClass 内の行位置: 1,120
 DataSet1 : before [BeginInit()]
 DataSet1 : before [InitClass()]


[処理状況]ボタンは反応するので、UI スレッドは生きているようですが、
ワーカースレッドは、DataSet1 の初期化プロセスの
 Me.tableSampleTable12 = New DataSet1.SampleTable12DataTable()
を完了できない状態です。
# おかげで、[計測中止]ボタンを押しても停止処理が完了しない…。


一応、メモリ使用状況を記載しておきます。
計測中、継続的に監視していたわけではないので、分かるのは
停止時点の最終値のログだけです。(最小値=最大値な状態)


===== Virtual Memory =====
     31,872 KB          Private Bytes
     38,380 KB          Peak Private Bytes
    670,208 KB          Virtual Size
253,837,243 回          Page Faults
          0 回          Page Fault Delta

===== Handles =====
        187 個          Handles
        211 個          Peak Handles
         62 個          GDI Handles
         36 個          USER Handles

===== .NET CLR Memory =====
    586,768 バイト      # Bytes in all Heaps
      1,370 個          # GC Handles
 20,519,820 回          # Gen 0 Collections
 10,076,826 回          # Gen 1 Collections
    337,649 回          # Gen 2 Collections
          0 回          # Induced GC
          1 個          # of Pinned Objects
         18 個          # of Sink Blocks in use
  3,731,456 バイト      # Total committed Bytes
402,644,992 バイト      # Total reserved Bytes
          0 %          % Time in GC
          0 バイト/秒  Allocated Bytes/Second
          0 個          Finalization Survivors
  3,145,727 バイト      Gen 0 heap size
          0 バイト/秒  Gen 0 Promoted Bytes/Sec
         24 バイト      Gen 1 heap size
          0 バイト/秒  Gen 1 Promoted Bytes/Sec
    516,800 バイト      Gen 2 heap size
     69,944 バイト      Large Object Heap size
          0 バイト      Promoted Finalization-Memory from Gen 0
          0 バイト      Promoted Memory from Gen 0
         56 バイト      Promoted Memory from Gen 1

===== .NET CLR Loading =====
        169 個          Total Classes Loaded
  1,499,136 バイト      Bytes in Loader Heap

引用返信 編集キー/
■81901 / inTopicNo.19)  Re[13]: DataSetの破棄について
□投稿者/ sk (11回)-(2016/11/21(Mon) 13:06:57)
No81900 (魔界の仮面弁士 さん) に返信
> ■No81879 (魔界の仮面弁士 さん) に返信

> 再現しました。
> Release ビルドにおいて、60時間22分58秒後 に停止しています。

再現してしまいましたか・・。


> 型付きDataSet を用いず、素の System.Data.DataSet に Tables.Add していった場合も
> 同様の現象になるのでしょうか。

素のdatasetというわけではありませんが、テーブルデザインからadapterを削除した
もので確認してみましたが、結果は変わらず53,024,288回で停止していました。
引き続きdatasetにaddした素のdatasetで確認してみます。


引用返信 編集キー/
■81921 / inTopicNo.20)  Re[14]: DataSetの破棄について
 
□投稿者/ 魔界の仮面弁士 (979回)-(2016/11/24(Thu) 09:39:27)
No81901 (sk さん) に返信
>>Release ビルドにおいて、60時間22分58秒後 に停止しています。
> 再現してしまいましたか・・。

タイマーを使わずに無限ループさせてみましたが、やはり 57時間ともたずに、
型付きな DataSet1 生成が停止してしまいました。

今回はコンソールアプリとしての実行です。

-----------------
71,582,789 回目
InitClass 内の行位置: 1,150 …… DataSet1 内の15個目のテーブルをNewしている最中
DataSet1 : before [BeginInit()]
DataSet1 : before [InitClass()]

-----------------

※実行回数 71,582,720 時点の累積時間は 2日と8時間56分59秒4931635

コンストラクタが完了待ちのままになっているというだけであり、プロセス自体は
停止していません。Console.CancelKeyPress イベントによる割り込みは可能でした。


また、今回の検証時、GC.GetTotalMemory も計測していましたが、
常時、数 MB というサイズを維持して上下動しており、
GC が間に合っていないというわけでもなさそうに思えます。

GC.GetTotalMemory の最小値は 70,796
最大値でも 7,96,084 という結果です。

処理停止後の GC.GetTotalMemory は 3,524,268 バイトでしたが、
停止後に GC を強制発動してみたところ、242,412 バイトまで減少しました。
それでもプログラムが再開する様子はありません。


参考までに、今回の計測プログラムを掲載しておきます。

Imports System
Imports System.Data
Imports System.Diagnostics
Imports System.Linq
Imports System.Threading

'1 = 型付 DataSet で検証
'2 = 素の DataSet で検証
#Const TYPED_DATASET = 1

Module Module1
 Private Cancel As Boolean = False
 Private Const LoopMax As Integer = 256 '連続生成数

 Private _i As Integer '処理回数

 ''' <summary>ログ出力</summary>
#If TYPED_DATASET = 1 Then
 Private Function GetLog() As String
  Return _i.ToString("#,0") & " 回目" & vbCrLf &
    DataSet1.GetLog()
 End Function
#Else
 Private Function GetLog() As String
  Return _i.ToString("#,0") & " 回目" & vbCrLf &
    "Table:" & _table.ToString("#,0") & vbCrLf & "Column:" & _column.ToString("#,0")
 End Function
 Private Const TableCount As Integer = 60
 Private Const ColumnCount As Integer = 80
 Private _Log As String
 Private _table As Integer
 Private _column As Integer
#End If

 Sub Main()
  '[Break] もしくは [Ctrl]+[C] で割りこみ処理
  AddHandler Console.CancelKeyPress, _
   Sub(sender, e)
    Dim msg = "中止   => 計測停止" & vbCrLf &
       "再試行  => GC.Collect を発動して継続" & vbCrLf &
       "キャンセル => (何もしない)" & vbCrLf
    Dim style = vbAbortRetryIgnore Or vbQuestion Or vbDefaultButton3 Or vbMsgBoxSetForeground
    Select Case MsgBox(msg & vbCrLf & GetLog(), style)
     Case vbAbort
      Cancel = True
     Case vbRetry
      Dim before = GC.GetTotalMemory(False)
      GC.Collect()
      GC.WaitForPendingFinalizers()
      GC.Collect()
      Dim after = GC.GetTotalMemory(True)
      MsgBox("GC結果" & vbCrLf & String.Format(
        "発動前: {0,15:#,0}" & vbCrLf & "発動後: {1,15:#,0}",
        before, after), vbInformation)
     Case vbIgnore
      REM DoNothing
    End Select
    e.Cancel = True
   End Sub

  '負荷テスト
  Do
   Module1._i = 0
   Dim curMem As Long = 0
   Dim minMem As Long = Long.MaxValue
   Dim maxMem As Long = Long.MinValue
   Dim sw As Stopwatch = Stopwatch.StartNew()
   Cancel = False
   While Module1._i < (Int32.MaxValue - LoopMax)
    Dim counter = String.Format("実行回数 {0,-16:#,0}" & vbTab, Module1._i)

    Console.SetCursorPosition(0, 0)
    Console.WriteLine(counter)
    Console.WriteLine("現在時刻 " & Now.ToString("yyyy/MM/dd HH:mm:ss.fffffff"))
    Console.WriteLine("累積時間 " & sw.Elapsed.ToString("G"))
    Dim oldMem = GC.GetTotalMemory(False)
    minMem = Math.Min(oldMem, minMem)
    maxMem = Math.Max(oldMem, maxMem)
    Console.WriteLine()
    For j = 1 To LoopMax
     Module1._i += 1
     Dim ds As DataSet
#If TYPED_DATASET = 1 Then
     ds = New DataSet1()
     If Cancel Then Exit While
#Else
     _Log = " before New DataSet()" & vbCrLf
     ds = New DataSet()
     ds.EnforceConstraints = False
     For Module1._table = 1 To TableCount
      Dim tblName = "Table" & Module1._table.ToString("0000")
      Dim tbl As New DataTable(tblName)
      For Module1._column = 1 To ColumnCount
       Dim colName = "Column" & Module1._column.ToString("000")
       Dim col As New DataColumn(colName)
       tbl.Columns.Add(col)
      Next
      ds.Tables.Add(tbl)
      If Cancel Then Exit While
     Next
     ds.EnforceConstraints = True
     _Log &= " before Clear()" & vbCrLf
#End If
     ds.Clear()
    Next
    Dim newMem = GC.GetTotalMemory(False)
    minMem = Math.Min(newMem, minMem)
    maxMem = Math.Max(newMem, maxMem)
    Console.WriteLine("使用容量 {0,-40}", String.Format("{0:#,0} => {1:#,0}", oldMem, newMem))
    Console.WriteLine()
    Console.WriteLine("最低 {0,-15:#,0}", minMem)
    Console.WriteLine("最高 {0,-15:#,0}", maxMem)
   End While
   Console.SetCursorPosition(0, 10)
   Console.WriteLine(If(Cancel, "キャンセル", "ループ終了"))
   Console.WriteLine(GetLog())
   Console.WriteLine("現在時刻 " & Now.ToString("yyyy/MM/dd HH:mm:ss.fffffff"))
  Loop While MsgBoxResult.Yes = MsgBox("もう一度最初から?", vbYesNo Or vbQuestion)

  Console.SetCursorPosition(0, 21)
  Console.WriteLine("Hit Any Key...")
  Console.ReadKey()
 End Sub

End Module
引用返信 編集キー/

次の20件>
トピック内ページ移動 / << 0 | 1 >>

管理者用

- Child Tree -