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

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

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

Re[16]: フォルダ内のファイル名を取得する方法 [1]


(過去ログ 35 を表示中)

[トピック内 47 記事 (21 - 40 表示)]  << 0 | 1 | 2 >>

■17571 / inTopicNo.21)  Re[15]: フォルダ内のファイル名を取得する方法
  
□投稿者/ 渋木宏明(ひどり) (725回)-(2008/04/27(Sun) 14:31:47)
渋木宏明(ひどり) さんの Web サイト
> CompareMethod.binaly ならひっかかりませんが
> CompareMethod.Text ならひっかかります。

CompareMethod のヘルプには

>Text システムの LocaleID 値によって決定された、大文字小文字を区別するテキスト並べ替え順に基づいて、テキスト比較を実行します。

とありますが、大文字小文字を同一視して比較するんですか>CompareMethod

引用返信 編集キー/
■17572 / inTopicNo.22)  Re[15]: フォルダ内のファイル名を取得する方法
□投稿者/ す (1回)-(2008/04/27(Sun) 14:57:47)
2008/04/27(Sun) 14:59:47 編集(投稿者)

No17570 (渋木宏明(ひどり) さん) に返信
> 拡張子の数だけ GetFiles() するよりは、1回だけ GetFiles() してから拡張子を見て選別する方が I/O の回数が少なくなるので効率的です。

どうでしょう。
たぶん、拡張子の数だけ GetFiles() するほうが速いのではないかと。
ヒット数 << 母集合 の一般的な場合
引用返信 編集キー/
■17573 / inTopicNo.23)  Re[16]: フォルダ内のファイル名を取得する方法
□投稿者/ 渋木宏明(ひどり) (726回)-(2008/04/27(Sun) 15:37:51)
渋木宏明(ひどり) さんの Web サイト
>>拡張子の数だけ GetFiles() するよりは、1回だけ GetFiles() してから拡張子を見て選別する方が I/O の回数が少なくなるので効率的です。
>
> どうでしょう。
> たぶん、拡張子の数だけ GetFiles() するほうが速いのではないかと。
> ヒット数 << 母集合 の一般的な場合

んー、この場合はどうでしょうね?

GetFiles()(=実際にはその下請けであるだろう FindFirstFile(), FindNextFile() API)は、内部的に全ファイル列挙してみて、フィルタにマッチするやつを順にかえすだけのような気がします。(ワイルドカードのすべてのパターンにマッチするようなインデックスは作れなさそうな気がするので)

なので、GetFiles() を何回も実行するということは、全ファイルの列挙を何回も実行することになる=I/O 量が飛躍的に伸びてしまうのではないかと。
引用返信 編集キー/
■17574 / inTopicNo.24)  Re[17]: フォルダ内のファイル名を取得する方法
□投稿者/ す (2回)-(2008/04/27(Sun) 16:03:23)
No17573 (渋木宏明(ひどり) さん) に返信

>GetFiles()(=実際にはその下請けであるだろう FindFirstFile(), FindNextFile() >API)は、内部的に全ファイル列挙してみて、フィルタにマッチするやつを順にかえすだ>けのような気がします。

FindFirstFile(), FindNextFile() APIで絞り込むと、
そのあとのメモリ処理が少なくなる。

>なので、GetFiles() を何回も実行するということは、全ファイルの列挙を何回も実行す>ることになる=I/O 量が飛躍的に伸びてしまうのではないかと。

IOのほうは、1回目にIOキャッシュに載るので以後の実IOが少なくなる。

の両方の効果がどれくらい顕れるかですね。
引用返信 編集キー/
■17575 / inTopicNo.25)  Re[18]: フォルダ内のファイル名を取得する方法
□投稿者/ 渋木宏明(ひどり) (727回)-(2008/04/27(Sun) 16:49:39)
渋木宏明(ひどり) さんの Web サイト
> FindFirstFile(), FindNextFile() APIで絞り込むと、
> そのあとのメモリ処理が少なくなる。

この時点で目立った差として表れそうなのは

・検出されたファイル名のマーシャリングのコスト
・FindFirstFile(), FindNextFile() API 呼び出しのコスト

あたりですね。

↑に比べれば、他のコストは微々たるものです。

> IOのほうは、1回目にIOキャッシュに載るので以後の実IOが少なくなる。
> の両方の効果がどれくらい顕れるかですね。

確かに。

あとちょっと気になるのは、FindFirstFile(), FindNextFile() がアトミックな操作かどうか、ですね。

FindFirstFile() を実行した時点でファイルカタログのスナップショットが作成されて、FindNextFile() がそのスナップショットの一部を返すような仕様だったりすると

・GetFiles() を1回だけ実行して自前でフィルタ処理
・GetFiles() をフィルタ付きで複数回実行して結果を合成

では結果が異なる場合があるはずです。

引用返信 編集キー/
■17576 / inTopicNo.26)  Re[16]: フォルダ内のファイル名を取得する方法
□投稿者/ す (3回)-(2008/04/27(Sun) 16:55:13)
2008/04/27(Sun) 22:49:27 編集(投稿者)

c:\windows\system32で*.txtを試してみると、1回目/2回目
getfiles( *.txt)が0.07秒/0.01秒
getfiles() + if が0.28秒/0.04秒
なので、21パターンまでは前者ですね。0.07 + 0.01 * 20 < 0.28
(この場合 & この環境では)

※すみません。ツリー位置が変ですね。
引用返信 編集キー/
■17577 / inTopicNo.27)  Re[19]: フォルダ内のファイル名を取得する方法
□投稿者/ す (4回)-(2008/04/27(Sun) 17:06:17)
No17575 (渋木宏明(ひどり) さん) に返信
> あとちょっと気になるのは、FindFirstFile(), FindNextFile() がアトミックな操作かどうか、ですね。

所詮、検索した後で変更されれば同じなので、
もともと気にしなくてもいいなら気にしなくてもいいし、
気にしないといけないものなら気にしないといけないし。

> FindFirstFile() を実行した時点でファイルカタログのスナップショットが作成されて、FindNextFile() がそのスナップショットの一部を返すような仕様だったりすると

そんなことはしてないと思いますが。。。
引用返信 編集キー/
■17578 / inTopicNo.28)  Re[20]: フォルダ内のファイル名を取得する方法
□投稿者/ 渋木宏明(ひどり) (728回)-(2008/04/27(Sun) 18:42:00)
渋木宏明(ひどり) さんの Web サイト
> そんなことはしてないと思いますが。。。

まるっきりやってないですね ;-p

static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);

static void Main(string[] args)
{
	var directoryName = @"C:\Windows\System32";
	var fileNames = new List<string>() { "0.txt", "1.txt", "zzzzzzzz.txt" };

	fileNames.ForEach(delegate(string fileName) { using (var file = File.Create(Path.Combine(directoryName, fileName))) { } });

	WIN32_FIND_DATA fd;
	IntPtr handle = FindFirstFile(Path.Combine(directoryName, "*.txt"), out fd);

	if (handle != INVALID_HANDLE_VALUE)
	{
		fileNames.ForEach(delegate(string fileName) { File.Delete(Path.Combine(directoryName, fileName)); });

		do
		{
			System.Console.Out.WriteLine(fd.cFileName);
		}
		while (Program.FindNextFile(handle, out fd));

		FindClose(handle);
	}
}

引用返信 編集キー/
■17581 / inTopicNo.29)  Re[17]: フォルダ内のファイル名を取得する方法
□投稿者/ す (5回)-(2008/04/27(Sun) 22:13:58)
No17576 (す さん) に返信
> c:\windows\system32で*.txtを試してみると、1回目/2回目
> getfiles( *.txt)が0.07秒/0.01秒
> getfiles() + if が0.28秒/0.04秒
> なので、21パターンまでは前者ですね。0.07 + 0.01 * 20 < 0.28
> (この場合 & この環境では)

ところで、getfiles( *.txt)で取ると、*.txt*なども取れるので、
getfiles( *.txt) + if
で二重にフィルタリングしたほうがいいですね。
引用返信 編集キー/
■17587 / inTopicNo.30)  Re[18]: フォルダ内のファイル名を取得する方法
□投稿者/ ネタ好き (174回)-(2008/04/27(Sun) 23:27:42)
2008/04/27(Sun) 23:28:15 編集(投稿者)

Directoryオブジェクトのソースコード見たけど、GetFilesを1回だけ呼び出して、
後でチェックする方がパフォーマンス的にいいと思います。
一度ソースコードを見てみると分かると思いますが、
複数回あのメソッドを呼び出すとパフォーマンスが劣化すると思います。
無論ハードウェアでのキャッシュ効果はあると思いますが、
出来るだけハードウェアにアクセスする回数は減らした方がいいと思います。
これはOSを作った時の経験から言えることです。
Windowsは私の想像以上に最適化しているかもしれませんが、
それならば直の事、1回アクセスしてオブジェクトを使いまわすほうが、より最適化されると思います。
引用返信 編集キー/
■17589 / inTopicNo.31)  Re[18]: フォルダ内のファイル名を取得する方法
□投稿者/ 渋木宏明(ひどり) (729回)-(2008/04/27(Sun) 23:59:43)
渋木宏明(ひどり) さんの Web サイト
> ところで、getfiles( *.txt)で取ると、*.txt*なども取れるので、

うわ。ヘルプに書いてありますね。
8.3 の呪いか。
引用返信 編集キー/
■17591 / inTopicNo.32)  Re[19]: フォルダ内のファイル名を取得する方法
□投稿者/ れい (510回)-(2008/04/28(Mon) 01:25:50)
2008/04/28(Mon) 17:05:03 編集(投稿者)
2008/04/28(Mon) 03:13:31 編集(投稿者)

No17587 (ネタ好き さん) に返信
> 無論ハードウェアでのキャッシュ効果はあると思いますが、
> 出来るだけハードウェアにアクセスする回数は減らした方がいいと思います。

むー。その方針は、Windowsでは間違っていると私は思います。

ファイルの検索は、パフォーマンス、互換性、信頼性、
どれを優先的に考えても、「GetFiles()やGetDirectories()でマッチングを行うべき」です。

以下、長くなりますが、論拠といろいろ私の知っていることの列挙です。
情報のソースはWDK、およびそれに含まれるHelpとFastFatのコード、
書籍「Windows NT File System Internals」などです。

まず。
・Directory.GetFilesの実装について。

これは引数に適当な前処理を加えた後、FindXXXFileを呼び出すだけです。
いろいろチェックをかけていますのでその分は重いですが、
後述するように、コンテキスト切替に比べて微々たる物です。

・FindXXXFileのアトミック性の件。

> あとちょっと気になるのは、FindFirstFile(), FindNextFile() がアトミックな操作かどうか、ですね。

FindXXXFileは、
ディレクトリハンドルを開いて、
IRP_MJ_DIRECTORY_CONTROLを複数回送信することで
ファイル列挙を行います。

初回のIRP_MJ_DIRECTORY_CONTROL時点でスナップショットを作るような
アトミックな操作であることが「推奨」されていますが、
通常はアトミックではありません。
NTFS、FATともにアトミックにはなっていません。
途中でファイルが削除・生成された際には、
「列挙されないファイル」や「二重に列挙されるファイル」が出てしまいます。
(二重に列挙される場合があることはあまり知られていないと思います。)

アトミックに取得したい場合はNtCreateFileでディレクトリを「排他的に開い」て
自分でIRP_MJ_DIRECTORY_CONTROLを送信します。

・FindFirstFileのマッチングの取り方について。

> GetFiles()(=実際にはその下請けであるだろう FindFirstFile(), FindNextFile() API)は、内部的に全ファイル列挙してみて、フィルタにマッチするやつを順にかえすだけのような気がします。(ワイルドカードのすべてのパターンにマッチするようなインデックスは作れなさそうな気がするので)

Windowsのファイルシステムでは、
ディレクトリ内のファイル列挙を行うときにはかならず
「カーネルモードでパターンマッチングを行う」ことになっています。

FindXXXFileはユーザーモードで文字列マッチングを行っていません。
FindFirstFileに渡した検索文字列はほぼそのままファイルシステムドライバに渡され、
そこでマッチングにかけられます。
#全ファイルを取得したいときには、わざわざ「*」を渡さなければいけません。

このマッチング処理はFsRtlIsNameInExpressionを使っています。
これはかなり高速です。
#.Netで同じ処理を作ってみましたが、およそ10〜1000倍ほど違います。

・ファイル名の文字に関して。

>Windows のファイル名では、ABC と abc は同一視されますが、
>ABCとabcは別物として扱われる事になっていますし。

これは一番大きい問題だと思います。
大文字小文字の判断、文字列のマッチングなどは「ファイルシステム依存」です。
例えば、「あいう」と「アイウ」を同一視するファイルシステムもWindowsはサポートできます。
#シェルはサポートできない場合もありますが。

日本語だと皆気にしていませんが、
修飾文字があるような言語体系では重要です。
ウムラウトとかエスツェットを考えてください。

バージョンやリージョンが変われば、変わる可能性もあります。

なので、ファイル名の一部が与えられたときに、
その文字列とファイルシステム上で一致するファイルはどれなのか、
「ファイルシステムにしか判断できません」
また、それは
「ファイルシステムが判断すべき事項」
でもあります。

・キャッシュについて。

> 無論ハードウェアでのキャッシュ効果はあると思いますが

NTFS/FAT共に、ディレクトリエントリのキャッシュは
キャッシュマネージャではなく、ファイルシステム自身が管理します。
これはキャッシュマネージャが管理する他のキャッシュよりはるかに優先的に確保されます。
ファイルシステムによってはNon-Paged(ページファイルに落とされない)な領域から確保され、
一度アクセスしたあとはキャッシュから消えません。

・速度に関して。

> Windowsは私の想像以上に最適化しているかもしれませんが、
> それならば直の事、1回アクセスしてオブジェクトを使いまわすほうが、より最適化されると思います。

FindXXXFileの実装のところでで述べたように、
ディレクトリ内のファイル列挙では、
返されるファイル数に比例したカーネル/ユーザーモード切替、IRP生成が起きます。
この切替やIRP生成は文字列連結やパス文字列の検査などより「遥かにコストの高い処理」です。

無論ハードウェアアクセスよりは遅いですが、
前述のようにディレクトリエントリはかなりの確率で
キャッシュされていますので、あまり問題になりません。

膨大な個数のファイルがあるディレクトリを検索してみると差が顕著に現れます。
100000個とかファイルがあるディレクトリからファイルを一つだけ検索してみてください。
LinuxやSolarisなどでは下手をするとシェルが落ちます。
Windowsではマッチするファイル数が小さければ、問題になりません。

以上のように。

Windowsのファイルシステムの設計思想は、「多機能」「高性能」で、
「なるべくファイルシステムに問い合わせる」ように設計されています。

readdir()ではなくFindXXXFile()が実装されているのはそのせいです。

これにより、
ファイルシステムドライバのコードは増大し、実装が難しくなっていますが、
反面、キャッシュヒット率の向上、アクセス権・排他処理の高性能化、多言語への容易な対応、
ディレクトリ検索の高速化につながっています。

ディレクトリ内のファイル数が多少増えても重くならないようにするためにも、
ファイル名のマッチングを正しく行うためにも、
ディレクトリ内のファイル検索はなるべく「カーネルモードにやらせるべき」です。

つまり、「.NetならGetFiles()やGetDirectories()でマッチングを行う」です。
複数のパターンでファイルを検索したい場合は、
「パターンごとにGetFilesを呼び出すべき」です。

全ファイルを取得して、自分でマッチングさせて探すのは、
多種多様なファイルシステムをサポートできず、
将来の拡張性も失い、
且つ、場合によって使えないほど重くなる可能性があります。

#ミスリーディングだったのでいくつか修正。
引用返信 編集キー/
■17592 / inTopicNo.33)  Re[19]: フォルダ内のファイル名を取得する方法
□投稿者/ す (6回)-(2008/04/28(Mon) 01:32:13)
No17587 (ネタ好き さん) に返信
> 2008/04/27(Sun) 23:28:15 編集(投稿者)
>
> Directoryオブジェクトのソースコード見たけど、GetFilesを1回だけ呼び出して、
> 後でチェックする方がパフォーマンス的にいいと思います。
> 一度ソースコードを見てみると分かると思いますが、

ソースコードは見てませんが、実際に試してみると分かると思います。

> 複数回あのメソッドを呼び出すとパフォーマンスが劣化すると思います。
> 無論ハードウェアでのキャッシュ効果はあると思いますが、
> 出来るだけハードウェアにアクセスする回数は減らした方がいいと思います。
> これはOSを作った時の経験から言えることです。
> Windowsは私の想像以上に最適化しているかもしれませんが、
> それならば直の事、1回アクセスしてオブジェクトを使いまわすほうが、より最適化されると思います。

いえ、OSのIOキャッシュのことです。
引用返信 編集キー/
■17593 / inTopicNo.34)  Re[20]: フォルダ内のファイル名を取得する方法
□投稿者/ れい (511回)-(2008/04/28(Mon) 01:46:10)
No17592 (す さん) に返信
> いえ、OSのIOキャッシュのことです。

ストレージのキャッシュにも載るでしょうが。

それ以前に、ディレクトリエントリリストは
ファイルシステム内部で独自に保持されます。
キャッシュといっていいのかわかりませんが…。

内部にディレクトリエントリのリストを保持していないと、
ファイルシステムが事実上作れないので。

なので、初回以降は
ほぼ「遅延書き込み」しか起きません。

ちなみに、ディレクトリエントリのリストは
ファイルRead/Writeに使われる、
いわゆる「OSのキャッシュ」には載りません。
引用返信 編集キー/
■17594 / inTopicNo.35)  Re[21]: フォルダ内のファイル名を取得する方法
□投稿者/ す (7回)-(2008/04/28(Mon) 02:07:21)
No17593 (れい さん) に返信
>>いえ、OSのIOキャッシュのことです。

一般概念的に使っているので。。。

ところで、

> なので、初回以降は
> ほぼ「遅延書き込み」しか起きません。

> ファイルシステムによってはNon-Paged(ページファイルに落とされない)な領域から確
> 保され、一度アクセスしたあとはキャッシュから消えません。

ですが、暫く時間を置くと、また遅くなるような。。。

それに、そういう作りはまずいと思いますけど。。。
引用返信 編集キー/
■17595 / inTopicNo.36)  Re[22]: フォルダ内のファイル名を取得する方法
□投稿者/ れい (512回)-(2008/04/28(Mon) 02:18:49)
2008/04/28(Mon) 03:24:47 編集(投稿者)

#より正確な表現に変更。

No17594 (す さん) に返信
>>ファイルシステムによってはNon-Paged(ページファイルに落とされない)な領域から確
>>保され、一度アクセスしたあとはキャッシュから消えません。
>
> ですが、暫く時間を置くと、また遅くなるような。。。
> それに、そういう作りはまずいと思いますけど。。。

FATやNTFSの場合、
全ファイルハンドルがクローズされても
メモリが十分にあれば解放しません。
メモリが減ると、しばらく保持した後解放します。

ルートディレクトリの場合や
Floppyなど一部のリムーバブルメディアの場合は
メディアがなくなるまで解放しません。
引用返信 編集キー/
■17596 / inTopicNo.37)  Re[19]: フォルダ内のファイル名を取得する方法
□投稿者/ す (8回)-(2008/04/28(Mon) 02:42:18)
No17589 (渋木宏明(ひどり) さん) に返信
>>ところで、getfiles( *.txt)で取ると、*.txt*なども取れるので、
>
> うわ。ヘルプに書いてありますね。
> 8.3 の呪いか。

ところで、この呪いに対するアプリ側のスマートな対策はないもんでしょうか?
二重に自分でフィルタリングするのはどうも不細工で。。。
ヘルプに模範コードを書いてほしいけれど無理なのかも。
引用返信 編集キー/
■17597 / inTopicNo.38)  Re[19]: フォルダ内のファイル名を取得する方法
□投稿者/ ネタ好き (175回)-(2008/04/28(Mon) 02:44:18)
れいさん情報有難うございます。
れいさんはファイルドライバを今まさに探求しているだけにすごく的確です。
成る程、カーネルモードとユーザモードの切り替えコストを考えると、
(モード切替は確かに遅い)
カーネルモードでの処理時間を長くするために、Windowsに任せると良いというのは納得出来る判断です。
と言う事は一番パフォーマンスが望める方法は、Win32Native.FindFirstFileとかを、自分で呼び出して、
Directoryクラスがやっている余分なユーザーモードの.NET処理を削り落とすと言う事ですね。
引用返信 編集キー/
■17598 / inTopicNo.39)  Re[20]: フォルダ内のファイル名を取得する方法
□投稿者/ 渋木宏明(ひどり) (730回)-(2008/04/28(Mon) 02:59:05)
渋木宏明(ひどり) さんの Web サイト
> つまり、「.NetならGetFiles()やGetDirectories()を使うべき」です。
> 複数のパターンでファイルを検索したい場合は、
> 「パターンごとにGetFilesを呼び出すべき」です。

それでも

・フィルタに *.txt を指定すると *.txtZ みたいなやつまで列挙される
・同名ファイルが重複して列挙される可能性がある

ということは、後処理がかかせなくて残念。

> と言う事は一番パフォーマンスが望める方法は、Win32Native.FindFirstFileとかを、自分で呼び出して、
> Directoryクラスがやっている余分なユーザーモードの.NET処理を削り落とすと言う事ですね。

そこまでこだわるなら、マーシャリングのコストも馬鹿にならないですね。

引用返信 編集キー/
■17599 / inTopicNo.40)  Re[20]: フォルダ内のファイル名を取得する方法
 
□投稿者/ れい (513回)-(2008/04/28(Mon) 03:09:51)
2008/04/28(Mon) 03:18:26 編集(投稿者)

No17597 (ネタ好き さん) に返信
> れいさんはファイルドライバを今まさに探求しているだけにすごく的確です。

実際作ってみて、
Win32APIを眺めてるだけでは理解できなかった部分のなぞがだいぶ解けました。

Windowsのファイルシステムは
ディレクトリエントリのリスティングと「リネーム」が非常に特徴的で、
設計思想がよく出ていると思います。

> 成る程、カーネルモードとユーザモードの切り替えコストを考えると、
> (モード切替は確かに遅い)
> カーネルモードでの処理時間を長くするために、Windowsに任せると良いというのは納得出来る判断です。

・問合せ/返信という形で要求する。問合せ側がバッファを用意する。
・バッファの自動伸長はない
・ディレクトリ構造はFS依存
・ファイル名は可変長。
・問合せ中にディレクトリを排他ロックしたくない
・何個エントリがあるのか分からない。個数の問合せもしたくない。(変更があるから)

などを考えると、
どうしても数エントリごとに1回はモードを切り替えないとうまくいかないので、
エントリをたくさん返すのは重い処理になります。

FSは「全部任せろ!」という設計思想なのに、
.NetのGetFiles/GetDirectoriesはいろいろやってくれちゃっていて、
ネタ好きさんと同様に好きではありません。
矛盾を感じます。

No17596 (す さん) に返信
> ところで、この呪いに対するアプリ側のスマートな対策はないもんでしょうか?
> 二重に自分でフィルタリングするのはどうも不細工で。。。
> ヘルプに模範コードを書いてほしいけれど無理なのかも。

FindFirstFileなら、問題はありませんから、
これも同じ問題ですよね。

DirectorySeparatorなど、OSがサポートしない文字が含まれていないことを確認したら、
そのままFindFirstFileに渡してくれるだけでいいのに、と私は思います。

違うプラットフォームとかを考えてるのかもしれませんが。

> と言う事は一番パフォーマンスが望める方法は、Win32Native.FindFirstFileとかを、自分で呼び出して、
> Directoryクラスがやっている余分なユーザーモードの.NET処理を削り落とすと言う事ですね。

パフォーマンスがいいのはもちろんですが、
上記問題や、シンプルさからもそっちのほうがよい気がします。

ですが、なぜいろいろ処理せねばならなかったのか、詳細を私はまだよく検討していません。
FindFirstFileでほとんど足りるはずなんですが。

無駄なものは作らないはずで、きっと何か事情があったはずだと思います。
その事情がわからない以上、私はGetFilesを使うという方針です。

#FSDを作った身としては
#「せっかくカーネルモードで文字列マッチングまでやってるんだから使ってくれ」
#とか
#「ここまでやらせておいてまだユーザーモードでいろいろ処理しなきゃいけないなんておかしい」
#という気分ですが。
引用返信 編集キー/

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

管理者用

- Child Tree -