MimeKitと呼ばれる C# の MIME & mbox パーサーに取り組んでいます。
これは、私が作成した以前の MIME および mbox パーサー ( GMimeなど) に基づいていますが、これは非常に高速でした (1.2 GB の mbox ファイル内のすべてのメッセージを約 1 秒で解析できました)。
MimeKit のパフォーマンスはまだテストしていませんが、C で使用したのと同じ手法を C# で多数使用しています。C の実装よりも遅くなると思いますが、ボトルネックは I/O であり、MimeKit はGMime のように最適な (4k) 読み取りを行うように書かれているため、かなり近いはずです。
現在のアプローチが遅いと感じている理由 (StreamReader.ReadLine()、テキストを結合してから SharpMimeTools に渡す) は、次の理由によるものです。
StreamReader.ReadLine() は、ファイルからデータを読み取る最適な方法ではありません。StreamReader() が内部バッファリングを行うことは確かですが、次の手順を実行する必要があります。
A) ファイルから読み取ったバイトのブロックを Unicode に変換します (これには、ストリームから読み取ったバイトを Unicode char[] に変換するために、ディスクから読み取った byte[] 内のバイトを反復処理する必要があります)。
B) 次に、内部の char[] を繰り返し処理し、「\n」が見つかるまで各文字を StringBuilder にコピーする必要があります。
したがって、行を読み取るだけで、mbox 入力ストリームに少なくとも 2 つのパスがあります。進行中のすべてのメモリ割り当ては言うまでもありません...
次に、読み取ったすべての行を単一のメガストリングに結合します。これには、入力に対する別のパスが必要です (おそらく ReadLine() から読み取った各文字列からすべての文字を StringBuilder にコピーしますか?)。
現在、入力テキストに対して最大 3 回の反復が行われていますが、解析はまだ行われていません。
これで、SharpMimeMessageStream を使用する SharpMimeTools にメガ文字列を渡します... (/facepalm) は、文字セット変換を行う別の StreamReader の上にある ReadLine() ベースのパーサーです。これにより、何かが解析される前に 5 回の反復が行われます。SharpMimeMessageStream には、読みすぎた場合に ReadLine() を「元に戻す」方法もあります。したがって、彼がそれらの行のいくつかを少なくとも 2 回スキャンしていると想定するのは合理的です。進行中のすべての文字列割り当ては言うまでもありません...うーん。
各ヘッダーについて、SharpMimeTools がライン バッファーを取得すると、フィールドと値に分割されます。それは別のパスです。これまでのところ、パスは最大 6 つです。
次に、SharpMimeTools は string.Split() (これは、この MIME パーサーが標準に準拠していないことを示すかなり良い指標です) を使用して、「,」で分割することによってアドレス ヘッダーをトークン化し、分割することによってパラメーター化されたヘッダー (Content-Type や Content-Disposition など) をトークン化します。の上 ';'。それは別のパスです。(現在、最大 7 パスです。)
それらを分割すると、string.Split() から返された各文字列に対して正規表現一致が実行され、rfc2047 エンコードされた単語トークンごとにさらに正規表現が渡されてから、エンコードされた単語の文字セットとペイロード コンポーネントに対して別のパスが最終的に作成されます。この時点までに、入力の大部分で少なくとも 9 回または 10 回のパスについて話しています。
GMime と MimeKit が必要とするパスの数がすでに 2 倍以上になっており、パーサーを最適化してパスを少なくとも 1 回少なくすることができることを知っているため、これ以上の調査は断念しました。
また、補足として、byte[] (または sbyte[]) の代わりに文字列を解析する MIME パーサーは、決して優れたものにはなりません。電子メールの問題は、非常に多くのメール クライアント/スクリプト/その他が、ヘッダーとメッセージ本文で宣言されていない 8 ビット テキストを送信することです。ユニコード文字列パーサーはどうすればそれを処理できるでしょうか? ヒント: できません。
using (var stream = File.OpenRead ("Inbox.mbox")) {
var parser = new MimeParser (stream, MimeFormat.Mbox);
while (!parser.IsEndOfStream) {
var message = parser.ParseMessage ();
// At this point, you can do whatever you want with the message.
// As an example, you could save it to a separate file based on
// the message subject:
message.WriteTo (message.Subject + ".eml");
// You also have the ability to get access to the mbox marker:
var marker = parser.MboxMarker;
// You can also get the exact byte offset in the stream where the
// mbox marker was found:
var offset = parser.MboxMarkerOffset;
}
}
2013-09-18 更新: MimeKit を mbox ファイルの解析に使用できるようになり、ねじれをうまく解決することができましたが、私の C ライブラリほど高速ではありません。これは iMac でテストされたので、I/O パフォーマンスは私の古い Linux マシンほど良くありません (GMime が同様のサイズの mbox ファイルを ~1 秒で解析できる場所です):
[fejj@localhost MimeKit]$ mono ./mbox-parser.exe larger.mbox
Parsed 14896 messages in 6.16 seconds.
[fejj@localhost MimeKit]$ ./gmime-mbox-parser larger.mbox
Parsed 14896 messages in 3.78 seconds.
[fejj@localhost MimeKit]$ ls -l larger.mbox
-rw-r--r-- 1 fejj staff 1032555628 Sep 18 12:43 larger.mbox
ご覧のとおり、GMime はまだかなり高速ですが、MimeKit のパーサーのパフォーマンスを改善する方法についていくつかのアイデアがあります。fixed
C# のステートメントは非常に高価であることが判明したため、使用方法を変更する必要があります。たとえば、昨日行った単純な最適化により、全体の時間が約 2 ~ 3 秒短縮されました (記憶が正しければ)。
最適化の更新:以下を置き換えることで、パフォーマンスがさらに 20% 向上しました。
while (*inptr != (byte) '\n')
inptr++;
と:
do {
mask = *dword++ ^ 0x0A0A0A0A;
mask = ((mask - 0x01010101) & (~mask & 0x80808080));
} while (mask == 0);
inptr = (byte*) (dword - 1);
while (*inptr != (byte) '\n')
inptr++;
最適化の更新: Enum.HasFlag() の使用をやめ、代わりにダイレクト ビット マスキングを使用することで、最終的に MimeKit を GMime と同じくらい高速にすることができました。
MimeKit は、3.78 秒で同じ mbox ストリームを解析できるようになりました。
比較のために、SharpMimeTools は 20分以上かかります(これをテストするには、SharpMimeTools は mbox ファイルを解析できないため、メールを個別のファイルに分割する必要がありました)。
別の更新:コード全体にさまざまな調整を加えることで、3.00 秒フラットまで下げました。