質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

454閲覧

PythonでGUIの設計ソフトを作成、設計を複数回行うとどんどん遅くなっていく

mag0123

総合スコア3

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1グッド

0クリップ

投稿2020/10/11 11:22

編集2020/10/12 06:36

前提・実現したいこと

Pythonで電気回路の設計ソフトを作成しています。TkinterでGUI画面を作成し、画面上のボックスに必要な数値を入力して、実行ボタンを押すと設計結果が返ってくるというものです。数値は書き換えて何度も設計を繰り返すことができます。試行回数によらず一定の計算速度を保てるようにしたいです。
Python、tkinterともに扱い初めて1か月の初心者です。

発生している問題・エラーメッセージ

何度も設計を繰り返すと処理速度がどんどん遅くなってしまいます。
ファイルを扱う部分に問題があるのではないかと考えたのですが、原因を見つけることに難航しています。

該当のソースコード

Python

1def inductor_design(specification, line_1, line_2, thickness, result): 2 #設計仕様の値を変数に代入 3 f = float(specification[0]) 4 SC_Rated = float(specification[1]) 5 L_init = float(specification[2]) 6 L_Rated = float(specification[3]) 7 Max_tmp = float(specification[4]) 8 Ku = float(specification[5]) 9 Jmax = float(specification[6]) 10 11 #コアデータベースの値を変数に代入 12 perm = float(line_1[4]) 13 a = float(line_1[5]) 14 b = float(line_1[6]) 15 c = float(line_1[7]) 16 A = float(line_1[8]) 17 B = float(line_1[9]) 18 C = float(line_1[10]) 19 D = float(line_1[11]) 20 Bsat = float(line_1[12]) 21 22 OD = float(line_2[1]) 23 ID = float(line_2[2]) 24 HT = float(line_2[3]) 25 Le = float(line_2[4]) 26 Limit_Wire_Dia = float(line_2[5]) 27 S = float(line_2[6]) 28 Ae = float(line_2[7]) 29 Ve = float(line_2[8]) 30 W = float(line_2[9]) 31 MP = float(line_2[11]) 32 HF = float(line_2[12]) 33 MF = float(line_2[13]) 34 SD = float(line_2[14]) 35 HS = float(line_2[15]) 36 SG = float(line_2[16]) 37 38 N = int(math.ceil(math.sqrt((L_init*Le*pow(10,3))/(4.0*math.pi*perm*(Ae*thickness))))) 39 40 N_11 = 0 41 N_21 = 0 42 N_22 = 0 43 N_31 = 0 44 N_32 = 0 45 N_33 = 0 46 47 48 N_init = N 49 current = int(SC_Rated) 50 diff_max = 1.25 51 diff_min = 0.97 52 flag_1 = 1 53 flag_2 = 1 54 count = 0 55 i = 0 56 57 #定格インダクタンスを得るように初期インダクタンスを動かしている 58 while flag_1 != 0: 59 60 L_at0A = 4.0*math.pi*perm*(Ae*thickness)*math.pow(N,2)/(Le*math.pow(10,3)) 61 H = N*current*math.pow(10,2)/(Le*79.577) 62 per_mu = 100/(a + b*math.pow(H,c)) 63 inductance.insert(current, L_at0A*per_mu/100) 64 if inductance[current] >= L_Rated*diff_min and inductance[current] <= L_Rated*diff_max: 65 Ldc = inductance[current] 66 flag_1 = 0 67 break 68 69 elif flag_2 == 1: 70 N += 1 71 if N > 3*N_init: 72 flag_2 = 2 73 74 elif flag_2 == 2: 75 N -= 1 76 if N < 1: 77 return(3) 78 Ku = Ku/100 79 input_Ku = Ku 80 81 while True: 82 83 84 Aw = (Ku*W*math.pow(10,2))/float(N) 85 if Aw <= 6.733: 86 N_wire = 1 87 Aw = (Ku*W*math.pow(10,2))/float(N*N_wire) 88 elif Aw > 6.733 and Aw <= 13.466: 89 N_wire = 2 90 Aw = (Ku*W*math.pow(10,2))/float(N*N_wire) 91 else: 92 N_wire = 1 93 Aw = Aw 94 95 out_wire = getwire(Aw,wire) 96 97 98 99 #膨らみ率 100 if float(wire[0]) < 1.0: 101 kp = 1.0 102 else: 103 kp = float(wire[0]) 104 105 106 107 Ku_real = (float(wire[10])*N*N_wire*100)/(W*math.pow(10,2))*kp 108 if out_wire == 1 or out_wire == 2: 109 Ku_real = 100 110 111 if Ku_real >= (input_Ku*100-5.0) and Ku_real <= (input_Ku*100+5.0): 112 break 113 else: 114 Ku = Ku - 0.01 115 116 if Ku <= 0: 117 return (5) 118 119 120 ############中略################ 121 122 123 #結果出力 124 result.insert(0, L_at0A) 125 result.insert(1, Ldc) 126 result.insert(2, N) 127 result.insert(3, float(wire[0])) 128 result.insert(4, wire_Weight + core_Weight) 129 result.insert(5, delta_T) 130 result.insert(6, Fin_Ve) 131 result.insert(7, Bm) 132 result.insert(8, Jm) 133 result.insert(9, Pi) 134 result.insert(10, Pwdc) 135 result.insert(11,Rwdc) 136 result.insert(12,Ku_real) 137 result.insert(13,OD) 138 result.insert(14,ID) 139 result.insert(15,HT) 140 result.insert(16,N_wire) 141 result.insert(18,N1) 142 result.insert(19,N2) 143 result.insert(20,N3) 144 result.insert(21,Fin_OD) 145 result.insert(22,Fin_HT) 146 result.insert(23,wire_Weight) 147 result.insert(24,core_Weight) 148 result.insert(25,f) 149 result.insert(26,SC_Rated) 150 result.insert(27,Ae) 151 result.insert(28,Ve) 152 result.insert(29,W) 153 result.insert(30, float(wire[1])) 154 result.insert(31, float(wire[2])) 155 result.insert(32,wire_Length) 156 result.insert(33,kp) 157 result.insert(34,Le) 158 159 160 161 162 163 164 return (0) 165 166

試したこと

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

teamikl👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mag0123

2020/10/12 06:56

プロファイラを実行してみました。まだ、実行結果の見方や分析の仕方について知識が不十分ですので学習する必要がありますが、今後活用できるようになれば、問題解決に役立ちそうです。ありがとうございます。
teamikl

2020/10/12 08:07

プロファイラの利用は、環境構築のハードルがありますが、 line_profiler が解りやすくお勧めです。 Pythonスクリプトのパフォーマンス計測ガイド[和訳] https://yakst.com/ja/posts/42
guest

回答1

0

ベストアンサー

コードが実行できないので見た感じの予想です。


  • whileループがネストして2重になってるのは意図通りでしょうか?

CORE_Database_1 の1行に対して毎回 CORE_Database_2 が読み込まれてます。
例えば、このファイルに書き込みしているとすれば、経過とともに処理に時間が掛かるはずです。

  • ファイルの規模(行数)と処理にかかる時間はどれくらいでしょう?

GUIプログラムでは時間の掛かるループはスレッド等で処理しないと、
GUIの動作に支障が出ます。(ファイルの処理中、一時的に応答なしになる等)


  • txt_51 と連番付けでつけられてるとしたら 50 以上のウィジェットが予想されますが、

ウィジェット数はおおよそどれくらいでしょうか?(詳細に数える必要はありません)

初期化時以外に、例えば関数内で毎回ウィジェット作成し、破棄しないまま増え続けてませんか?
print(txt_51) とすると、実際の連番の値が表示できます。
コードが呼び出される度に数値が増える場合はメモリーリークとなってます。

リソースモニター等で、CPU や メモリの使用率を確認して見て下さい。
また、問題個所の特定にはプロファイリング等も有効です。

このケースの問題を見つけるポイントとしては、
関数内でウィジェットを探して、その関数が呼ばれる頻度を調べて見て下さい。
ウィジェット作成時に name= を与えるだけで解消する場合もあります(関連リンク参照)


  • CSVファイルの読み出し部分のインデントが design 関数の外、

 グローバルになってますが意図通りでしょうか?
※ with open ~ CORE_1 の後ろの行のインデントが2段階になってます。

  • result, specification が範囲内では何か解りませんが、

 リストのコピーであれば output[0:11] = result[0:11] もしくは、
output[:] = result[:]の様に書けます。件数次第ですが速度には大して影響ないはず。


出来れば、問題と思う箇所を単体で実行できる形にして
その部分のみで問題が発生するかどうかを試してみてください。

  • 単体で実行可能なソースコード (コピー&ペーストのみで実行可能なもの)
  • 動作に必要な外部ファイル (この場合 csv ファイル)

 ※ csvはテスト用に小規模なもの。小規模なものでは問題が発生しない場合は、
最低限 カラムだけでもわかれば、適当にデータを増やして実行できるので先頭の数行でもよいです。

問題が他の環境で再現可能な形で提示して頂ければ、より詳細な原因がわかると思います。


関連 Tkinter での同様のパフォーマンス問題

投稿2020/10/11 17:37

編集2020/10/11 18:41
teamikl

総合スコア8760

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mag0123

2020/10/12 04:09

Q1:whileループがネストして2重になってるのは意図通りでしょうか? A1:意図通りです。データベース1が材質(28種)、2がサイズ(40種)という形で、28×40通りの設計を行い、その中から最適解を1つ出力しています。 Q2:ファイルの規模(行数)と処理にかかる時間はどれくらいでしょう? A2:行数は約1900行です。処理時間は1回目の試行で32秒、2回目68秒、3回目106秒、4回目151秒という風にだんだん遅くなっていきます。 Q3:ウィジェット数はおおよそどれくらいでしょうか? A3:Entry約30個、label約50個、button5個です。 print(txt_51)を試してみました、結果は「.!Entry28」で試行回数を増やしても変化しませんでした。 Q4:CSVファイルの読み出し部分のインデントが design 関数の外、グローバルになってますが意図通りでしょうか? A4:こちらの質問に記載する際にインデントがずれてしまいました、実際はdesignの中です。 経験不足でコード全体から問題箇所を抽出してという作業がまだ思うようにできず、質問内容も不十分なもので申し訳ありません。 問題と思う箇所を単体で実行できる形にして試すということをまず実践できるようにしていきたいです。
teamikl

2020/10/12 05:17

>行数は約1900行 すいません、ここはソースコードの行数ではなくて ループに掛かる時間を知りたかったので、CSVファイルの行数でした。 28x40 通りということなので、 意図通りの回数になっているか、実際にログを取って調べて見ましょう。 Q5. 1回目と2回目の実行時に、ループ回数が増えたりはしてませんか? もし、count でループの回数を数えているなら、design 関数の末尾で print(count) として見て下さい。 ちなみに、range(1,2): #コア数1~3でループ は [1] なので、ループしてません。(末尾の数は含まない。コメントの数とも不一致) もし試行回数が増えてループ回数に変化が無ければ、 「design() 関数の呼び出し元」、もしくは 「inductor_design() 関数側」に問題がある可能性が高くなります。
mag0123

2020/10/12 06:47

Q5. 1回目と2回目の実行時に、ループ回数が増えたりはしてませんか? A5.2回の試行で確認したところ、1回目、2回目ともに1120(28x40)回でした。 inductor_design()関数が設計計算を担っており、一番計算量の多い関数になっています。そのなかで処理に時間がかかりそうなwhileループが2カ所あります。その箇所を該当ソースコードに掲載いたしました。そのほかの部分を一部省略して掲載してあります。 このwhileループで時間がかかっていると想像していますが、なぜ実行回数が増えると時間も増えていくのかの原因がつかめません。
teamikl

2020/10/12 08:33

- ロジックのミスにより、試行回数により計算量が増えている - 大量のオブジェクトが生成されていて GC (ガベージコレクション) の影響で遅くなる 等が考えられますが、まだ情報不足です。 省略された箇所が内容次第では原因になることも有りえますし、 問題の再現が出来ない事には、はっきりとした原因は解りません。 問題を切り分ける為に、GUIを利用せず、演算のみを数回繰り返すプログラムを作り timeit モジュールを使い所要時間を計測して見て下さい。 (GUIで入力が必要な部分は、テスト用に適当な固定値を用いてテストします。出力は不要です) ---- >A3:Entry約30個、label約50個、button5個です。 >print(txt_51)を試してみました、結果は「.!Entry28」で試行回数を増やしても変化しませんでした。 確認ですが、Label も同様に変わりませんか? (レイアウト配置に place() を使っている場合、 新しくLabelオブジェクトが作成されてても、見た目では解らない場合があります。)
mag0123

2020/10/13 04:28

GUIを利用せず演算のみのプログラムにして所要時間を計測しました。 その結果、実行回数が増えるとともに所要時間も増加していきました。演算結果は常に同じです。 設計数を28x40通りから9x40通りに減らしてみた場合も、同様の結果でした。 該当ソースコードに記載している演算プログラムのwhileループ部分の所要時間も確認してみました。 その結果、実行回数1回目でも20回目でも合計のループ回数は同じなのですが、所要時間は増えていました。各ループを比較して毎回増加しているとは限らなかったですが、平均でみると、実行回数1回目の時は0.00026秒/ループ、20回目の時は0.0053秒/ループといった値でした。
teamikl

2020/10/13 05:38 編集

確認されたループ回数は以前に掲載されたコードでの count の回数ですよね? その中で呼ばれている今回の2つのwhileループの回数の変化はどうでしょう。 気になる点: 変数 inductance の定義は、掲載のコードに見当たりませんが、使い方から見てリストでしょうか? 2回目実行時に初期化されてなければ、実行の度に増え続け、実行速度に影響が出る事が考えられます。 他は、詳細まで見てませんが、デバッグの方法として他に出来る事 各引数の値を表示して確認して見ましょう (期待通りの値かどうか、2回目で変化していないか) 引数の値が毎回同じだとすると、外的要因に成り得るのは(上記のコードの範囲では) 変数 inductance と get_wire inductance の変化はループ回数には影響ないはずですが、リストが肥大化した場合 メモリを再構築するので、その時に時間が掛かるみたいなことが予想できます。 解決策: 2回目が実行前されるに inductance を初期化
mag0123

2020/10/13 09:02

ご回答ありがとうございます。 inductor_design()関数の中の2つのwhileループの回数は1回目も2回目以降も全く同じでした。 ちなにみこの2つ以外には、この関数内にループはなく、そのループのない箇所も所要時間を計測したのですが、実行回数とともに少しずつ増加していることがわかりました。 --- inductanceの初期化を関数内で行っておりませんでした。inductor_design()関数内に初期化を追加したのですが所要時間は改善しませんでした。 この時プログラム全体を見直していたら、この部分をリストにする必要がないことがわかり、inductanceというリストを使用しない形に書き換えました、しかし実行回数とともに時間が増加するという現象は改善しませんでした。 --- get_wire()関数の呼び出し回数も確認しましたが、実行回数による変化はありませんでした。 同じ演算内容・回数・結果ですが、時間だけ増えていっているように見えます。
teamikl

2020/10/13 14:04 編集

inductanceではなかったようですね。 前提として、掲載のコードに原因があるかどうかがわからない以上、これ以上の回答は困難なので デバッグやり方の方針としてコメントしますね。 その前に、上のコメントで説明した リストが累積されて、実行時間が増える例です) https://ideone.com/PMXaXA 空のリストに1000回insert, 要素数1000のリストに1000回insert ~を計測してます。 処理内容はどちらも同じ1000回のinsertですが、 2回目は1回目の結果1000の要素に対して行って、所要時間が増えることを確認。 これ自体は、バグではなくPythonのリストの性質によるもので、正常な挙動です。 パフォーマンスの問題に繋がる場合は、リストの想定する用途を超える場合なので、 適切な代替モジュールを使う等の解決策があります。 (もし今回の問題が)このケースの問題であれば、メモリ使用量が増え続けるので、 メモリに関するプロファイリングで、どのリストのデータが増え続けているか調べることができます。 ---- > inductor_design()関数の中の2つのwhileループの回数は1回目も2回目以降も全く同じでした。 ということは、inductor_design() のループの問題ではないと仮定するなら inductor_design() を呼ばないコードを作って試して見てください。 もしくは、nductor_design() の中で直ぐに適当な値を return するでもよいです。 時間のかかる二つのループは実行しないようにします。 デバッグのためなので正確な計算ができなくても構いません、問題の再現のみを目的とします。 もし問題が再現した場合 → 呼び出し側の問題です 問題が再現しない場合 → inductor_design() 側の問題が考えられます 外部要因があるため、単独で問題になるかは、現時点の情報ではまだ不明です。 inductor_design() のみで問題が再現できるかどうかを試してください。 このように、問題範囲を狭めていき、 問題と関係ない部分は簡単なコードに置き換える等して、 問題を再現できる状況を保ったまま、コードを短くしていってください。 ---- コード短縮Tips を幾つか紹介 # float に変換して変数に代入 line_1 = ["1.0", "1.1", "1.2", "1.3"] a, b, c = map(float, line_1[1:4]) # a=1.1 b=1.2 c=1.3 # 変数をまとめて初期化 (※数値の場合のみ) a = b = c = 0
mag0123

2020/10/14 02:14

ご回答ありがとうございます。 以下の2点で問題が解決しました。 ・現在の掲載コードのWhile True:の中のget_wire()関数を呼び出す際、毎回リストwireの初期化を追記しました。 ・同様に、以前掲載したコード(inductor_design()関数の呼び出し元)の方でも、inductor_design()関数を呼び出す際に毎回リストresultの初期化を追記しました。 この結果、速度が低下していく問題が解決しました。 前回のリストの初期化というヒント、そして今回のデバッグやり方の方針についてのアドバイスがうまく結びつきました。ありがとうございました。 デバッグの方法がわからずプログラム全体を眺めるだけで一向に解決することができなかったので、ご回答をいただいた内容は1つ1つがとても勉強になりました。 こちらの示す情報が不十分であるにも関わらず、毎回ご丁寧で的確なアドバイスをいただき、本当にありがとうございました。 今回学んだことを今後の学習に活かして、ステップアップできるよう精進して参ります。
teamikl

2020/10/14 03:36

時間が遅くなる原因について、 説明が正確ではなかったので訂正します。 自分が想定したメモリ再割当てというコメントは、 list.append を使った場合で、 Pythonのリストは予め大きめのメモリを確保し、 足りなければ再度余裕を持って確保するという挙動をします。 (その為、大規模なデータを確保するには不向き) list.insert では、それに加えて*** 値の移動 ***が発生し、 これの動作の計算量が O(n) となるので、今回はここが原因だったようです。 (リストの長さに比例して大きくなるという意味です。) リストinsert時の挙動 - resize + 1 - 既存の値を移動 (指定場所から **末尾まで**) - 新しい値をいれる このなかで、既存の値の移動がinsertの度に「末尾までの値を全て移動する」ため リストの長さに比例して時間がかかる → 初期化していなければ 累積して時間がかかる という状況につながっていたようです。 ---- なので、結果出力のinsert は、 append や extend を使ったほうが良いです。 list.append は O(1) リストの長さに関わらず一定です。 予め確保されたメモリ領域に値をひとつ格納するだけです。 但し、大きくなりすぎるとメモリ再割当てに時間を要しますが、 この場合は、何度も呼ばれるわけではないので、 呼び出し回数に比例して時間がかかるとは考えにくかったです。 上記のURLに提示したコードは insert で計測した例なので、 コード例としては誤りではないのですが、 説明として insert の際の「値の移動」を説明し忘れていました。
teamikl

2020/10/14 04:03

問題が解決したようで良かったです。 総括すると 「リストの初期化忘れ」に加え 「リストのinsert 」も問題を助長する要因を担っていたように思います。 初期化さえしていれば、規模次第では 毎回insertするのは、少し効率が悪い程度で済みます。 ---- もう少しだけ、オフトピになりますが。 問題の再発防止について、 今回の問題では、恐らく初めてのデバッグという事や 元々のコードの規模が大きい事も有り、手探りな状況もあったと思いますが、 比較的わかりやすく、問題を特定するのをやりやすくする手段として 「グローバル変数を極力使わない」関数設計を心がけてみてください。 グローバル変数があると コードを掲載する際、未定義の値になってしまったり、 テストをする場合、グローバル変数の状態次第で、 結果が変わり問題を再現できたり出来なかったりすることが起こるので、 できるだけ、「**変化する値は** 関数の引数にするように設計」します。 今回のようなケースあれば、大抵の場合、 関数の引数と戻り値をチェックするだけで 一回目実行時と二回目実行時の相違点に気が付けるようになります。 ※ どこまでがグローバル変数なのか、という判断も難しかったりするのですが、 モジュールや定数(プログラム起動中に変化しない値)は、 グローバルのものを参照しても許容されます。 その他利点として、関数を単体実行してテストがしやすくなったり、 質問にコードを掲載する際も、関数単体で完結していれば、 問題の再現がやりやすくなります。
mag0123

2020/10/14 04:28

ご回答ありがとうございました。 この度はとても丁寧にわかりやすく書いて頂き、大変勉強になりました。 これからプログラムを書く際に、今回教えて戴いた内容を生かせるようにしていきたいと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問