10

メモリ マップト ファイルを使用して 2 つのファイルを比較するルーチンを作成しています。ファイルが大きすぎて一度にマップできない場合。ファイルを分割し、パーツごとにマップします。たとえば、1049MB のファイルをマップするには、512MB + 512MB + 25MB に分割します。

コード ロジックはまったく同じですが、残りの部分 (この例では 25MB) を比較するのに常に非常に長い時間がかかります。3 つの観察:

  1. 主要部分 (512MB * N) と残り (この例では 25MB) のどちらが先に比較されても、結果は同じままです。
  2. 残りの余分な時間はユーザーモードで費やされているようです
  3. VS2010 ベータ 1 でのプロファイリングでは、時間は t 内で費やされstd::_Equal()ますが、この関数はほとんど (プロファイラーは 100% と言います) I/O および他のスレッドを待機しています。

私は試した

  • VIEW_SIZE_FACTOR を別の値に変更する
  • ラムダファンクターをメンバー関数に置き換える
  • テスト中のファイルサイズの変更
  • 残りの実行順序をループの前後に変更する

結果は非常に一貫していました。残りの部分とユーザーモードでより多くの時間がかかりました。

マップされたサイズがマッピングの配置 (私のシステムでは 64K) の倍数ではないという事実と関係があると思われますが、その方法はわかりません。

以下は、ルーチンの完全なコードと 3G ファイルで測定されたタイミングです。

誰か説明してくれませんか、ありがとう?

// using memory-mapped file
template <size_t VIEW_SIZE_FACTOR>
struct is_equal_by_mmapT
{
public:
    bool operator()(const path_type& p1, const path_type& p2)
    {
        using boost::filesystem::exists;
        using boost::filesystem::file_size;

        try
        {
            if(!(exists(p1) && exists(p2))) return false;

            const size_t segment_size = mapped_file_source::alignment() * VIEW_SIZE_FACTOR;  

            // lanmbda 
            boost::function<bool(size_t, size_t)> segment_compare = 
            [&](size_t seg_size, size_t offset)->bool 
            {
                using boost::iostreams::mapped_file_source;
                boost::chrono::run_timer t;     

                mapped_file_source mf1, mf2;  

                mf1.open(p1, seg_size, offset);
                mf2.open(p2, seg_size, offset);

                if(! (mf1.is_open() && mf2.is_open())) return false;

                if(!equal (mf1.begin(), mf1.end(), mf2.begin())) return false;  

                return true;
            };

            boost::uintmax_t size = file_size(p1);
            size_t round     = size / segment_size;
            size_t remainder = size & ( segment_size - 1 );

            // compare the remainder
            if(remainder > 0)
            {
                cout << "segment size = " 
                     << remainder 
                     << " bytes for the remaining round";
                if(!segment_compare(remainder, segment_size * round)) return false;    
            }   

            //compare the main part.  take much less time, even 
            for(size_t i = 0; i < round; ++i)
            {
                cout << "segment size = " 
                     << segment_size 
                     << " bytes, round #" << i;
                if(!segment_compare(segment_size, segment_size * i))  return false;
            }
        }
        catch(std::exception& e)
        {
            cout << e.what();
            return false;
        }

        return true;                                      
    }
};

typedef is_equal_by_mmapT<(8<<10)> is_equal_by_mmap;  // 512MB  

出力:

残りのラウンドのセグメント サイズ = 354410496 バイト

実際の 116.892 秒、CPU 56.201 秒 (48.1%)、ユーザー 54.548 秒、システム 1.652 秒

セグメント サイズ = 536870912 バイト、ラウンド #0

実際の 72.258 秒、CPU 2.273 秒 (3.1%)、ユーザー 0.320 秒、システム 1.953 秒

セグメント サイズ = 536870912 バイト、ラウンド #1

実際の 75.304 秒、CPU 1.943 秒 (2.6%)、ユーザー 0.240 秒、システム 1.702 秒

セグメント サイズ = 536870912 バイト、ラウンド #2

実際の 84.328 秒、CPU 1.783 秒 (2.1%)、ユーザー 0.320 秒、システム 1.462 秒

セグメント サイズ = 536870912 バイト、ラウンド #3

実際の 73.901 秒、CPU 1.702 秒 (2.3%)、ユーザー 0.330 秒、システム 1.372 秒


レスポンダーによる提案後のさらなる観察

残りをボディとテールにさらに分割します (残り = ボディ + テール)。

  • ボディ = N * アライメント()、テール < 1 * アライメント()
  • 本体 = m * 配置 ()、および末尾 < 1 * 配置 () + n * 配置 ()、ここで m は偶数です。
  • ボディ = m * アライメント()、テール < 1 * アライメント() + n * アライメント()、m は 2 の指数です。
  • 本体 = N * 配置 ()、テール = 残り - 本体。N はランダムです。

合計時間は変化しませんが、時間は必ずしも尻尾に関係しているのではなく、体と尻尾のサイズに関係していることがわかります。大きい部分ほど時間がかかります。時間は、私には最も理解できないユーザータイムです。

また、Procexp.exe を介してページ フォールトも確認します。残りは、メイン ループより多くのフォールトを取りません。


アップデート 2

他のワークステーションでいくつかのテストを実行しましたが、問題はハードウェア構成に関連しているようです。

テストコード

// compare the remainder, alternative way
if(remainder > 0)
{
    //boost::chrono::run_timer t;       
    cout << "Remainder size = " 
         << remainder 
         << " bytes \n";

    size_t tail = (alignment_size - 1) & remainder;
    size_t body = remainder - tail;

{
    boost::chrono::run_timer t;                               
    cout << "Remainder_tail size = " << tail << " bytes";
    if(!segment_compare(tail, segment_size * round + body)) return false;
}                        
{
    boost::chrono::run_timer t;                               
    cout << "Remainder_body size = " << body << " bytes";
    if(!segment_compare(body, segment_size * round)) return false; 
}                        

}

観察:

私と同じハードウェア構成を持つ別の 2 台の PC では、結果は次のように一貫しています。

------VS2010Beta1ENU_VSTS.iso [1319909376 バイト] ------

残りのサイズ = 44840960 バイト

Remainder_tail サイズ = 14336 バイト

実際の 0.060 秒、CPU 0.040 秒 (66.7%)、ユーザー 0.000 秒、システム 0.040 秒

Remainder_body サイズ = 44826624 バイト

実際の 13.601 秒、CPU 7.731 秒 (56.8%)、ユーザー 7.481 秒、システム 0.250 秒

セグメント サイズ = 67108864 バイト、合計ラウンド数 = 19

実際の 172.476 秒、CPU 4.356 秒 (2.5%)、ユーザー 0.731 秒、システム 3.625 秒

ただし、ハードウェア構成が異なる PC で同じコードを実行すると、次の結果が得られました。

------VS2010Beta1ENU_VSTS.iso [1319909376 バイト] ------ 残りのサイズ = 44840960 バイト

Remainder_tail サイズ = 14336 バイト

実際の 0.013 秒、CPU 0.000 秒 (0.0%)、ユーザー 0.000 秒、システム 0.000 秒

Remainder_body サイズ = 44826624 バイト

実際の 2.468 秒、CPU 0.188 秒 (7.6%)、ユーザー 0.047 秒、システム 0.141 秒

セグメント サイズ = 67108864 バイト、合計ラウンド数 = 19

実際の 65.587 秒、CPU 4.578 秒 (7.0%)、ユーザー 0.844 秒、システム 3.734 秒

システム情報

私のワークステーションは理解できないタイミングをもたらします:

OS名:Microsoft Windows XP Professional

OS バージョン: 5.1.2600 サービス パック 3 ビルド 2600

OSメーカー:マイクロソフトコーポレーション

OS 構成: メンバー ワークステーション

OS ビルド タイプ: ユニプロセッサ フリー

元のインストール日: 2004-01-27, 23:08

システム稼働時間: 3 日 2 時間 15 分 46 秒

システム メーカー: Dell Inc.

システム モデル: OptiPlex GX520

システムの種類: X86 ベースの PC

プロセッサ: 1 プロセッサがインストールされています。

                       [01]: x86 Family 15 Model 4 Stepping 3 GenuineIntel ~2992 Mhz

BIOS バージョン: DELL-7

Windows ディレクトリ: C:\WINDOWS

システム ディレクトリ: C:\WINDOWS\system32

起動デバイス: \Device\HarddiskVolume2

システム ロケール: zh-cn;中国語 (中国)

入力ロケール: zh-cn;中国語 (中国)

時間帯: (GMT+08:00) 北京、重慶、香港、ウルムチ

合計物理メモリ: 3,574 MB

利用可能な物理メモリ: 1,986 MB

仮想メモリ: 最大サイズ: 2,048 MB

仮想メモリ: 利用可能: 1,916 MB

仮想メモリ: 使用中: 132 MB

ページ ファイルの場所: C:\pagefile.sys

ネットワーク カード: 3 NIC がインストールされています。

       [01]: VMware Virtual Ethernet Adapter for VMnet1

             Connection Name: VMware Network Adapter VMnet1

             DHCP Enabled:    No

             IP address(es)

             [01]: 192.168.75.1

       [02]: VMware Virtual Ethernet Adapter for VMnet8

             Connection Name: VMware Network Adapter VMnet8

             DHCP Enabled:    No

             IP address(es)

             [01]: 192.168.230.1

       [03]: Broadcom NetXtreme Gigabit Ethernet

             Connection Name: Local Area Connection 4

             DHCP Enabled:    Yes

             DHCP Server:     10.8.0.31

             IP address(es)

             [01]: 10.8.8.154

「正しい」タイミングをもたらす別のワークステーション: OS 名: Microsoft Windows XP Professional

OS バージョン: 5.1.2600 サービス パック 3 ビルド 2600

OSメーカー:マイクロソフトコーポレーション

OS 構成: メンバー ワークステーション

OS ビルド タイプ: マルチプロセッサ フリー

元のインストール日: 2009 年 5 月 18 日、午後 2:28:18

システム稼働時間: 21 日 5 時間 0 分 49 秒

システム メーカー: Dell Inc.

システム モデル: OptiPlex 755

システムの種類: X86 ベースの PC

プロセッサ: 1 プロセッサがインストールされています。

        [01]: x86 Family 6 Model 15 Stepping 13 GenuineIntel ~2194 Mhz

BIOS バージョン: DELL-15

Windows ディレクトリ: C:\WINDOWS

システム ディレクトリ: C:\WINDOWS\system32

起動デバイス: \Device\HarddiskVolume1

システム ロケール: zh-cn;中国語 (中国)

入力ロケール: en-us;英語 (米国)

時間帯: (GMT+08:00) 北京、重慶、香港、ウルムチ

合計物理メモリ: 3,317 MB

利用可能な物理メモリ: 1,682 MB

仮想メモリ: 最大サイズ: 2,048 MB

仮想メモリ: 利用可能: 2,007 MB

仮想メモリ: 使用中: 41 MB

ページ ファイルの場所: C:\pagefile.sys

ネットワーク カード: 3 NIC がインストールされています。

       [01]: Intel(R) 82566DM-2 Gigabit Network Connection

             Connection Name: Local Area Connection

             DHCP Enabled:    Yes

             DHCP Server:     10.8.0.31

             IP address(es)

             [01]: 10.8.0.137

       [02]: VMware Virtual Ethernet Adapter for VMnet1

             Connection Name: VMware Network Adapter VMnet1

             DHCP Enabled:    Yes

             DHCP Server:     192.168.154.254

             IP address(es)

             [01]: 192.168.154.1

       [03]: VMware Virtual Ethernet Adapter for VMnet8

             Connection Name: VMware Network Adapter VMnet8

             DHCP Enabled:    Yes

             DHCP Server:     192.168.2.254

             IP address(es)

             [01]: 192.168.2.1

説明理論はありますか?ありがとう。

4

6 に答える 6

4

この振る舞いは非常に非論理的に見えます。ばかげたことをやったらどうなるのだろうか。ファイル全体が 512MB より大きい場合は、残りのサイズではなく、最後の部分の完全な 512MB を再度比較できます。

何かのようなもの:

        if(remainder > 0)
        {
            cout << "segment size = " 
                 << remainder 
                 << " bytes for the remaining round";
                if (size > segment_size){
                    block_size = segment_size;
                    offset = size - segment_size;
                }
                else{
                    block_size = remainder;
                    offset = segment_size * i
                }
            if(!segment_compare(block_size, offset)) return false;    
        }   

ファイルの一部を2 回比較することになるため、これは非常に愚かなことのように思えますが、プロファイリングの数値が正確であれば、より高速になるはずです。

(まだ) 答えは得られませんが、実際に高速である場合、探している応答は、プログラムがデータの小さなブロックに対して行う処理にあることを意味します。

于 2009-09-04T22:05:43.343 に答える
2

私はこれがあなたの質問に対する正確な答えではないことを知っています。しかし、問題全体を回避しようとしましたか?つまり、ファイル全体を一度にマップするだけですか?

Win32のメモリ管理についてはほとんど知りません。MAP_NORESERVEただし、Linuxではフラグをで使用できるためmmap()、ファイルサイズ全体にRAMを予約する必要はありません。両方のファイルから読み取っているだけであることを考えると、RAMが不足した場合、OSはいつでもページを破棄できるはずです...

于 2009-09-05T12:02:33.847 に答える
2

セグメントのサイズが偶数ページでない場合、mmap の動作がおかしいのではないでしょうか? 場合によっては、ファイルの最後の部分の処理を試すことができます。それには、セグメント サイズを徐々に半分にして、mapped_file_source::alignment() よりも小さいサイズにし、その最後の部分を特別に処理します。

また、512MB のブロックを実行していると言いますが、コードはサイズを 8<<10 に設定します。次に、mapped_file_source::alignment() を掛けます。maps_file_source::alignment() は本当に 65536 ですか?

移植性を高め、混乱を少なくするために、テンプレート パラメーターで指定されたサイズを使用し、コード内でそれが maps_file_source::alignment() の偶数倍であることを単純に要求することをお勧めします。または、2 のべき乗で渡して、ブロック サイズなどを開始します。ブロック サイズをテンプレート パラメーターとして渡してから、奇妙な実装定義の定数を乗算するのは少し奇妙に思えます。

于 2009-09-03T21:18:21.403 に答える
2

比較しているファイルはどの程度断片化されていますか? FSCTL_GET_RETRIEVAL_POINTERSファイルがディスク上でマップする範囲を取得するために使用できます。最後の 25 MB には、測定したパフォーマンスを説明するために多くの小さな範囲があると思います。

于 2009-09-03T16:28:19.000 に答える
1

好奇心から、Linux または BSD で試して、どのように動作するかを確認します。

私はこの問題について非常に大まかな推測を持っています: Windows は、ファイルの末尾を超えてマップされないようにするために、多くの余分なチェックを行っているに違いありません。過去に、一部の OS では、mmap ユーザーがファイルシステムのプライベート データや、マップの終わりを過ぎた領域の他のファイルからのデータを表示できるというセキュリティ上の問題がありました。そのため、ここで注意することは、OS 設計者にとって良い考えです。 . そのため、Windows は、はるかに高速な「ディスクからユーザーへのデータのコピー」ではなく、より慎重な「ディスクからカーネルへのデータのコピー、マップされていないデータの消去、ユーザーへのデータのコピー」を使用している可能性があります。

64K ブロックに収まらない最後のバイトを除いて、ファイルの末尾のすぐ下にマッピングしてみてください。

于 2009-09-05T05:09:26.383 に答える
0

ウイルス スキャナがこれらの奇妙な結果を引き起こしているのでしょうか? ウイルススキャナなしで試しましたか?

よろしく、

セバスチャン

于 2009-09-07T06:47:26.110 に答える