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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

5回答

3678閲覧

C++ 出力を行うとメモリ使用量が増加する

beginner101

総合スコア18

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

1グッド

2クリップ

投稿2019/01/04 06:14

編集2019/01/18 08:40

前提・実現したいこと

初心者です。
Excel出力を行う際のメモリ使用量が大きいので調べてほしいと頼まれ、
どこで発生しているのかを調査したところ、
GetCellData関数の
if(formula) data = range.GetFormula();
else data = range.GetValue(vtMissing);
で、メモリの使用量が大きく上がるのが分かりました。
しかし、どこに問題があるのかがまだ分からないので、教えてもらえないでしょうか。

該当のソースコード

void CExcelCtrl::SetCellData(short col, short row,LPCSTR str) { try{ Range range = m_excel.GetCells(); range.SetItem(COleVariant(row),COleVariant(col),COleVariant(str)); }catch(COleDispatchException *e){ AfxMessageBox(e->m_strDescription,MB_ICONEXCLAMATION); } } void CExcelCtrl::GetCellData(int cols,int rows,int cole,int rowe,CStringArray &dt,bool formula) { CString sc,ec; sc.Format("%s%d",Num2Col(cols),rows); ec.Format("%s%d",Num2Col(cole),rowe); GetCellData(sc,ec,dt,formula); } void CExcelCtrl::GetCellData(LPCSTR sc,LPCSTR ec,CStringArray &dt,bool formula) { try{ _Worksheet ws = m_excel.GetActiveSheet(); Range range = ws.GetRange(COleVariant(sc),COleVariant(ec)); COleVariant data; if(formula) data = range.GetFormula(); else data = range.GetValue(vtMissing); COleSafeArray sa; sa.Attach(data); long rowmax,colmax; sa.GetUBound(1,&rowmax); sa.GetUBound(2,&colmax); CString str; long idx[2]; dt.RemoveAll(); for(long row = 1;row <= rowmax;row++){ idx[0] = row; for(long col = 1;col <= colmax;col++){ idx[1] = col; COleVariant val; sa.GetElement(idx,&val); switch(val.vt){ case VT_R8: str.Format("%1.2f", val.dblVal); break; case VT_BSTR: str.Format("%s",(CString)val.bstrVal); break; case VT_EMPTY: str.Empty(); break; } dt.Add(str); } } }catch(COleDispatchException *e){ AfxMessageBox(e->m_strDescription,MB_ICONEXCLAMATION); dt.RemoveAll(); } }

RangeのGetCells関数

LPDISPATCH Range::GetCells() { LPDISPATCH result; InvokeHelper(0xee, DISPATCH_PROPERTYGET, VT_DISPATCH, (void*)&result, NULL); return result; }

試したこと

PageComvert関数のみ除外して、Excel出力を実行
PageComvert関数内のGetCellDataのみ除外して、Excel出力を実行

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

Windows10/64bit
VisualStudio 2017

yohhoy👍を押しています

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

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

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

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

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

y_waiwai

2019/01/04 06:46

なにをどうしたときに、何がどうなるのかを詳しく説明しましょう あまりにも説明不足です
beginner101

2019/01/04 06:50

申し訳ありません。 説明を追加致しました。
yumetodo

2019/01/04 07:21

うーん、ご提示の箇所が問題とは思えないんですよね・・・。ChangeDataで呼んでいる関数群とかそっちなんだろうか・・・
beginner101

2019/01/04 07:50 編集

PageComvert関数だけ除外してみると、メモリの使用率がガクッと下がり、表紙の名前や日付、各ページの番号だけが未出力になるだけで、中身の件名や数値には問題ありませんでした。 出力後のメモリが200,000k→20,000kぐらいに違うので、どこかが問題だとはわかるのですが・・・ ChangeDataで呼んでいる関数も追加したほうがいいでしょうか?
KoichiSugiyama

2019/01/06 07:57

COMオブジェクトの詳細部分のソース(怪しいのはRangeクラス?のソース)が無いので想像ですが、デストラクタでリリースするためのメソッドなどがちゃんと呼ばれていないのではないでしょうか。Rangeクラスのソースを可能であれば提示してもらえると解決につながるかもしれません。
beginner101

2019/01/07 00:12 編集

ご回答ありがとうございます。 詳細部分のソースというのは、SetやGet,ChangeData関数で呼ばれている関数のことでしょうか。
KoichiSugiyama

2019/01/07 02:01 編集

ソースを見た限りではCExcelCtrl::SetCellData()で呼ばれている Range range = m_excel.GetCells(); が怪しく見えますので、Rangeクラスがわかれば問題点が見えるのではないかと思っています。 GetCells()関数やCExcelCtrl::GetCellData()で呼ばれているGetCellData()関数も開示可能であれば、お願いします。
beginner101

2019/01/07 02:15

ご回答ありがとうございます。 関連するソースコードを追加致しました。
KoichiSugiyama

2019/01/07 02:31

普通のMFCのラッパークラスっぽいですね。 PageConvert関数のループ処理内部で呼ばれているものをちょっとずつコメントして、どの処理がメモリを食っているか調べてみてはどうでしょうか。
beginner101

2019/01/07 02:37

ご回答ありがとうございます。 コメントアウトでの区切り方としては、どのように区切ればよろしいでしょうか。
beginner101

2019/01/10 02:31 編集

どうやらGetCellData()関数のif(formula) data = range.GetFormula(); else data = range.GetValue(vtMissing);の時点でメモリ使用率が増加しています。 あまりメモリが増加しないようにするためにはどう修正を行えばいいのでしょうか。
Bull

2019/01/10 03:01

Excel を COM で接続して操作するプログラムは何度か作成しました。#import を使ってタイプライブラリーを読み込む方法で作りましたが、MFC を使ったものも作った事があります。 私の経験からは、注意深く作ればメモリーリークはなかったと思っています。以前手がけたプロジェクトでは、プログラムが 24 時間ずっと稼働するもので、メモリーリークがあると困るので、慎重にテストした覚えがあります。そのシステムは現在も稼働しているはずです。 制作段階でいろいろなサンプルを作って確認しましたが、エクセルからデータを取り込む際に、データ量によってはかなりのメモリーを使用しました。特にある程度大きなセル範囲を一度に取り込むと、一時的にですが、300MB を超える事もありました。 メモリー使用量を抑えるためには、セルの範囲をできるだけ小さくすることが効果的だと思います。大きな範囲を取り込む必要があるなら複数回に分けるのがよいのではないかと思います。
beginner101

2019/01/10 05:08 編集

ご回答ありがとうございます。 こちらで起きている症状も恐らく同じようなものであると思います。 セルの範囲を小さくする、または、大きな範囲を取り込む際には複数回分けるには、どういったやり方があるのでしょうか。
Bull

2019/01/10 11:12

今さらながらソースを拝見しました。見間違いでなければ、一度に255列を一行ずつ取り込んでいるようです。私がテストしたときは20列×500行(セルの内容は2000文字ほど文字列で)を一度に取り込んでいましたので、状況が違うかも知れません。 とはいえ、分割して取り込むのは有効かもしれないので、一度に20列ほどにしてはいかがでしょうか? ソースを読む限りCStringArray にセルの内容を文字列にして追加しているようなので、分割はたやすいように思えます。 前のコメントでも書きましたが、私のプログラムは #import を使用しています。ご質問のプログラムを読む限り、そうではないようですので前提が違うのかもしれません。
beginner101

2019/01/10 23:49

ご回答ありがとうございます。 取り込む範囲を変更して、様子を見てみます。
beginner101

2019/01/11 06:44 編集

一旦、取り込む範囲を50行に変更したらメモリの使用量も70Mに小さくなりました。 20行にしたところ、50Mに減っていました。 出力結果にも問題はありません。 しかし、問題となっていた箇所を COleSafeArray sa; if(fomula) sa.Attach(range.GetFormula()); else sa.Attach(range.GetValue(vtMissing)); のように変更して出力したところ、メモリ使用量が25Mほどになっていました。 つまりdataへの代入が悪かったということなのでしょうか。 それでもCOleVariantなら解放されるはずなのですが・・・
Bull

2019/01/11 05:57

こちらでも同様な現象を確認しました。どうもメモリーリークしているようです。こちらの環境でテストしたプログラムは、MFCのウィザードが作成したヘッダーを使用しています。 こちらの環境ではメモリーリークは COleVariant ではなく get_Value(質問のソースでは GetValue)で起きているようでした。 get_Value と GetValue が同じものかどうかはわかりませんが、get_Value は VARIANT を返しているのですが、それを COleVariant で受けるとメモリーリークしてしまうようです。 とりあえず、 VARIANT data; VariantInit(&data); data = range.get_Value(covOptional); として、最後に VariantClear(&data); とするとメモリーリークは収まりました。
beginner101

2019/01/11 06:24 編集

ご回答ありがとうございます。 そちらでも同じようなことが・・・ 修正前では、GetFomulaの方でメモリが増加し、GetVauleの方では使用量がGetFomulaの半分でした。 直接入れてもメモリリークは起こりませんし、VARIANTで宣言しても起こらないとなると、COleVariantがメモリ増加の原因ということでしょうか。 それともrangeの代入処理が悪いのでしょうか。
beginner101

2019/01/11 07:14

COleVariant::Attach()を使用すると、COleVariantでも大丈夫なようです。 if(formula) data.Attach(range.GetFormula()); else data.Attach(range.GetValue(vtMissing));
Bull

2019/01/11 07:23

先のコメントでは、GetValue がメモリーリークを起こしていような事を書きましたが、正確ではなかったです。 もし、GetValue と GetFomula が VARIANT を返しているとするとどうなるか、少し説明します。 COleVariant はクラスですのでコンストラクターや代入演算子が定義されています。それを見てみると、VariantCopy を呼んでいます。 VARIANT が VT_BSTR の場合は文字列がコピーされます。しかし、VARIANT は単純な構造体なので、デストラクターは定義されていません。コピー先の COleVariant の BSTR はデストラクターで後始末されますが、コピー元の VARIANT の BSTR はそのままです。 一方、VARIANT を VARIANT にコピーした場合は、単純なコピーになるので、ポインターをコピーするだけです。最後に VariantClear で後始末すれば、メモリーリークは起こりません。 余談ですが、#import を使用した場合は、GetValue が variant_t を返すので、これもメモリーリークは起こりません。 variant_t も COleVariant と同じようなクラスですが、個人的にはこっちの方が使いやすいです。
beginner101

2019/01/11 09:03 編集

ご回答ありがとうございます。 なるほど、それが原因で解放されずにメモリリークを引き起こしていたのですね。 ようやく納得がいきました。 VARIANTもinitやClearを忘れるとえらいことになりますが、こちらの方がまだどこで使用しているかがわかりやすく判断しやすいと思いました。 頭を悩ませていたこの問題に一区切りできそうで、皆様のご協力、本当に感謝しております。
guest

回答5

0

そもそも論、COM使わなくてもECMAでOffice Open XML Spreadsheetの規格が決まっているのだからそれに準拠したライブラリを使えばいいのでは・・・?

.NET向けにはMSが公式で
https://github.com/OfficeDev/Open-XML-SDK
を出していますが、C++にこだわるなら(C++/CLIは闇が深いので)
https://github.com/tfussell/xlnt
のような有志のライブラリもある模様。

投稿2019/01/07 05:28

yumetodo

総合スコア5850

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

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

beginner101

2019/01/07 06:06

ご回答ありがとうございます。
guest

0

CExcelCtrl::GetCellData()内で

C++

1_Worksheet ws = m_excel.GetActiveSheet();

とアクティブシートを取得しているところで、ラッパークラスではなく、インターフェースで戻り値を受けているみたいですので、使い終わった後に

C++

1ws.delete();

を呼んでやればいいと思います(メソッドうろ覚えなので外しているかもしれません)。

投稿2019/01/07 05:07

KoichiSugiyama

総合スコア3041

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

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

beginner101

2019/01/07 05:48 編集

ご回答ありがとうございます。 実際に試してみます。
beginner101

2019/01/07 06:00

うーん、置く場所が悪いのか、 アプリケーションの内部エラーと表示されました。
guest

0

PageConvert 関数の中から GetCellData, SetCellData を呼び出しているから、これが多分 Excel の COM を呼び出してるような感じに思います。

COM の呼び出し= Excel アプリケーションの起動、に近いことをやってますから、そりゃメモリは増大するかと思います。

※別の環境ですが、PHP で Excel を扱う PHPSpreadSheet を使う場合、Cell 一個で概ね 1KB メモリを食う、というのがありますので、おそらくそれより消費量が大きい可能性があります

投稿2019/01/04 08:41

tacsheaven

総合スコア13703

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

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

beginner101

2019/01/07 00:08 編集

ご回答ありがとうございます。 つまり呼び出しを少ないように改良したほうが良いということでしょうか。 それとも仕様なので変更は難しいということでしょうか。
tacsheaven

2019/01/07 00:11

詳しい作りが不明ですが、COMを経由している以上はメモリ使用量は上がります。COMを使わない(独自に処理する)ライブラリもありますが、それでもセル当たり1KBのメモリ使用量は避けられないと思います。
beginner101

2019/01/07 00:32

ご回答ありがとうございます。 1セル1KBですか。 出力したExcelを見比べてどれほどセルが使用されているか調べてみます
beginner101

2019/01/09 02:16

すみません、GetCellDataのみでメモリが上がることがわかったのですが、 この部分にどのような変更を行えば、メモリの消費が少なくなりそうでしょうか
guest

0

自己解決

問題となっていた部分を、
COleVariant data;
if(siki) data.Attach(range.GetFormula());
else data.Attach(range.GetValue(vtMissing));

COleSafeArray sa;
sa.Attach(data);

のように変更しましたところ、メモリの使用量増加が起こらなくなりました。

投稿2019/01/15 00:45

beginner101

総合スコア18

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

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

0

昔と変わっていなければ、EXCELのセルはウィンドウ1個分のリソースを食います。なので、コピーするだけでかなりメモリを消費するのでは?
・・・残念ながら環境がないので憶測になってしまいますが・・・

投稿2019/01/04 07:11

cateye

総合スコア6851

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

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

beginner101

2019/01/04 07:26

ここのコンバート処理を行うソースコードを除外した場合、出力後のメモリ(プライベートワーキングセット)で見てみると20,000kなのですが ソースコードを元に戻した場合、200,000k近くメモリが上昇します 表紙にある名前や日付、番号を出力するだけなのにこんなに上がるのかなと思いましたので
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問