パフォーマンス改善の時に考えること(プログラムレベル)
パフォーマンス改善について
最近作成しているシステムはデータを扱うケースが多く、処理を書くたびにパフォーマンスが問題になっています。扱うデータが10万を超えて、全てインメモリという条件がついているのでなかなか厳しいです。
今回はこのパフォーマンス改善についてとり組んできたことについて書きます。
パフォーマンス改善には、レスポンス改善とメモリ改善の両方があります。今回はどちらも取り組んだのでその成果について書きます。
ただし、今回はプログラムレベルできる改善にとどめます。今回は既存DBも扱うシステムで、DB周りはいじれませんでした。
両方に共通すること
- 目標を決める
- 計測する
- テストをする
目標を決める
一番重要なことはここまでのパフォーマンスは必要というラインを決めることだと思います。それによって取れる対策が変わってきます。
本当に一瞬で返すことが必須要件といわれるとアーキテクチャをある程度壊さないといけないですが、数秒程度でOKならそこまでしたくはないです。
あとは、画面によって求められる速度は違います。よく使う画面ほど速度が大事なので、そこらへんの見極めもありますね。
なんにせよ、目標がなければパフォーマンス改善はできません。
また、環境条件もきちんと決めておいたほうがいいと思いますね。
使えるメモリの量、ネットワークの速度を考えなかった場合、結果が異なることがあります。
そして、環境のほうを改善してもらえるのなら、できる限りプログラムは触らないほうがいいと思います。技術的負債よりも環境を変えるほうが安いです。
計測する
ボトルネックとなっている箇所を見極めることは大事です。
アーキテクチャの大きな変更を伴う場合はある程度決め打ちすることもありますが、大体は計測してボトルネックだけを治して最小限の変更でとどめたいです。
print文で計測することもありますが、最近はVS2015に「パフォーマンスプロファイラー」という機能があるのでそちらを優先的に使っています。
やはり、本気で作業する場合は手動よりも機械的な方法に頼るほうがいいですね。
テストをする
当たり前ですが、パフォーマンス改善で機能を変えてはだめです。
といっても大きなアーキテクチャの変更を伴う場合はこれが一番難しいです。
テストスイートでもあればいいのですが、今回はなかったのでDBの最終結果をSQLで比較していました。
とにかく小さな修正をするたびにテストしたほうがいいと思います。
レスポンス改善
- ループ回数を減らす。
- インメモリにする
- 非同期にする。
ループ回数を減らす。
10万件のループになると、それだけで遅いです。仮に処理が簡単であっても一気にボトルネックになります。
SQLで何とかする
ではどうするのかというと、まずはSQLでなんとかできないかを考えます。
ビジネスロジックをSQLに持ちこむべきではないとは思いますが、パフォーマンスの観点からいえばできる限りDBサーバーに働いてもらったほうが効率がいいです。
特に取得・集約処理を1回のSQLでできるようにすれば、大分早くなるのでこれをまず検討します。特に階層系のSQLをまとめると大きな改善になります。
以前、Join禁止というプロジェクトがありましたが、検索のレスポンスが1分とか5分とかでした。やはり、SQLを最大限に使わずパフォーマンスを出すのは難しい面があります。
また、自分たちの使っているDBはOracleですが、一括でSQLを発行するBulk機能があります。これを使うと何万件の命令をすぐにこなせたのでなかなか良かったです。
参照関係をきちんともつ
今回のシステムではあるコレクションとあるコレクションをキーでぶつけて、処理をするということを何度もやっていました。もちろん、ループで。
こういうキーでぶつけれるときは、データ構造に参照関係をもつとループが減ります。
どうしても、データ構造を変えれないときはLinqのJoinなどで乗り切ってもいいと思います。
インメモリにする
特にIOがネックになっている時は、インメモリで処理します。
データベースはうまく仕事を任せると早いですが、回数が多いと逆に最大のボトルネックになります。
見極めは難しいですが、難しいビジネスロジックが入ってくる場合はインメモリで処理すると早いです。
うちでは些細なことですが更新日付やマスタ値をとってくるところを数1000回ループして、遅くなることがありますね。
こういうDBアクセスはとりあえずメモリに入れてしまうという手があります。
メモリ使用量との兼ね合いもありますけどね。
非同期にする
最近はLinqのAsParallelやTaskがあるので非同期のハードルは低いです。
処理の順番に制約がなく、メイン処理に関係ないところでは非同期でやってしまうという手があります。
ただ、個人的にはバグになりやすい部分もあるのでできる限り最終手段にしています。
最初からそういうデータ構造にしておくのならいいのですが。
メモリ改善
- 都度処理を行う
- データ型を変える
都度処理を行う
例えば普段かくプログラムはいったんDBから読んだレコードをリストに入れて、リストに対して処理するように書きます。
ただ、あまりにもレコードが多いときは1件ずつ読みながら、その都度処理をしてクラスに持たないようにします。
実際問題、これができるケースは多くはないです。
またデータを極力DBに入れて、必要な時に必要な分だけとってくるようにします。
とくに表示するためのデータなどはインメモリで持たないようにします。レスポンス的には遅くなるので難しいですが、兼ね合いで決めます。
データ型を変える
最終手段的なところはありますが、数字を扱う型をdecimalからdoubleやintに変えると何10%かは軽くなったと思います。
規約とシステムにもよりますが、数値の型はメモリに影響が大きいようです。
まとめ
今回行った対策はこのような感じです。10万件扱えるアーキテクチャと数千件のデータを扱うアーキテクチャの違いが分からず、結構苦戦しています。
.NETのネイティブ周りや独自仕様(Formaアプリケーション)に詳しくないも原因かなと思います。
そこらへんにもいずれ踏み込んで勉強したいです。
作業と順番
背景
業務であっても、趣味であってもプログラミングをしていると、当然コーディングをして終わりということはありません。
大抵、コードをリファクタリングする作業が必要ですし、場合によってはパフォーマンス改善(メモリの使用量削減、レスポンス改善等)に取り組むことがあります。
今回考えたいのは、どの作業にどのタイミングでとり組むべきなのかというところです。
自分はとりあえず動くコードを作る>リファクタリングですべての機能を作りこむことが多いのですが、最近はそうしないほうがいいのかなと思い始めてきました。
機能を作りこむ>リファクタリング>パフォーマンス改善
自分はまず動くコードを作ることが多いです。
多分、テスト駆動開発(TDD:Test Driven Development)に影響を受けていて、まずは動くコードを作って徐々に作りこんでいくのが好きです。
こうすると純粋にロジックに集中できるので効率的に開発できる気がします。そこからリファクタリングをしてとりあえず作業は終了して、全部の機能を作りこみます。
そして、ボトルネックになっている箇所があれば、パフォーマンス改善にとり組むという具合です。
機能が一番重要で、パフォーマンスは後回しという考え方が背景にあります。
パフォーマンス改善>機能を作りこむ>リファクタリング
逆にチームのメンバーは最初からパフォーマンス改善を考慮して設計します。
そのうえで、機能を作りこんでいって、最後にリファクタリングをします。
この順番で行くと納期が押していくと最終的にリファクタリングを切り捨てることになります。
逆にメリットとしてはアーキテクチャの変更が最小限で済むということがあります。
パフォーマンスを改善するときは大きなアーキテクチャの変更が必要なことが多いので、先に配慮しておくと修正が最小限で済みます。
個人的な結論
これまで先にパフォーマンス改善をするやり方は「過剰な最適化」「早まった最適化」の類だと思って避けていました。
ただ、現在の業務ではほぼすべてのケースでパフォーマンス改善の要求があって、逆にリファクタリングの効果が下がっていきます。
それに会社全体としてリファクタリング自体が評価されない傾向にあるので(そもそもコードレビューもない)、他のメンバーに合わせるのがいいのかなと思うようになりました。
もちろん、データ構造を大きく変えるとか、データ型を変えるとかそういった最適化は最終手段にしたいですが、アーキテクチャレベルの設計判断は早めのほうが自分の状況ではいいのかなという感じです。
趣味
では、趣味はどうすべきなのかなと考えると、やはり同じなのかなと思います。
趣味で何かを作っているとどうしても動くコードを作ることに腐心しがちなので、開発環境、パフォーマンス改善、リファクタリングその他もろもろが後回しになります。
だから、コードを書くということの優先順位を少し下げて、学びに集中するくらいが自分にとってちょうどいいのかなと思います。
はじめまして
あいさつ
初めまして。dexia2と申します。地方でプログラマーをやっています。
今日からブログを始めようと思います。よろしくお願いします。
自己紹介
技術スタック
IT業界に入ってからは3年目くらいです。
- .NETを使ったデスクトップアプリケーション開発
- .NETを使ったWeb開発(ASP.NET+Single Page Application)
あたりが専門です。
今入社した会社はプログラム言語(VB.NET、C#)、OS、サーバー、グループウェアの全部がWindowsなので技術スタックは基本的にそっち方向に偏っています。
その反動でLAMPとか、Macとか、Ruby、Pythonとかに憧れています。
浮世離れしてないといいなぁ。
趣味
- ヨーガ
- バドミントン
- クラフト集め(全部人にあげてしまいますが……)
多分システム化できないことが好きなんだと思います。直観的にいい悪いとか感じられるところとかすごくいいです。
でも、ここらへんの分野とITをつないでいけたらいいなと思っています。
ちなみに、ヨーガに関してはこんなものもあるみたいですが。
あと、バドミントンもブログを持っています。しばらく更新しないですが、バドミントン好きな方はどうぞ。
ブログについて
始めようと思った理由
普段冷静に自分を見つめる機会というのがなくて、それで学習機会を逸しているような気がしています。
それでブログを書くことで自分のことを考える機会を増やせたらいいかなと思っています。
書きたいこと
技術的にはあまり新しいことをしていないので、あまり書けないんじゃないかなと思います。
どちらかというと人間的なところ、集中力の出し方とか思考の使い方とかハック的なところについて興味があるので、そこらへんについて書きたいです。
まぁ、どうなるかはわかりません。趣味については仕事に関係ありそうなら書きます。
更新頻度
自省の意味もあってできる限り書いていきたいですが、プログラミングや趣味との兼ね合いもあるので、不定期ということにします。
目標としては週に1回くらい簡単にかけたらいいなというところを目指します。
まとめ
とりあえず、技術者として発信をしたことがないので、何か一つでも有益な情報を書けたらいいなと思います。