代码之家  ›  专栏  ›  技术社区  ›  t.g.

使用内存映射文件花费的不可理解的时间

  •  10
  • t.g.  · 技术社区  · 15 年前

    我正在编写一个例程,使用内存映射文件比较两个文件。以防文件太大而无法一次性映射。我把文件分开,一部分一部分地映射。例如,要映射1049MB文件,我将其拆分为512MB+512MB+25MB。

    除了一件事之外,其他事情都很好:比较剩余的(本例中为25mb)总是要花费很长的时间,尽管代码逻辑完全相同。3观察结果:

    1. 先比较哪个并不重要,不管是主部分(512mb*n)还是剩余部分(本例中是25mb)先比较,结果都是一样的
    2. 剩下的额外时间似乎花在 用户模式
    3. VS2010 Beta 1中的分析显示,时间花费在 std::_Equal() ,但这个函数主要(profiler说是100%)在等待I/O和其他线程。

    我试过

    • 将视图大小因子更改为另一个值
    • 用成员函数替换lambda函子
    • 更改测试中的文件大小
    • 将余数的执行顺序更改为循环之前/之后

    结果是相当一致的:在剩下的部分和在 用户模式 .

    我怀疑这与映射的大小不是映射对齐的倍数(在我的系统上是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.892s,CPU 56.201s(48.1%),用户54.548s,系统1.652s

    段大小=536870912字节,舍入0

    实际72.258s,CPU 2.273s(3.1%),用户0.320s,系统1.953s

    段大小=536870912字节,舍入1

    实际75.304s,CPU 1.943s(2.6%),用户0.240s,系统1.702s

    段大小=536870912字节,四舍五入

    实际84.328s,CPU 1.783s(2.1%),用户0.320s,系统1.462s

    段大小=536870912字节,舍入3

    实际73.901s,CPU 1.702s(2.3%),用户0.330s,系统1.372s


    响应者建议后的更多观察

    将剩余部分进一步拆分为主体和尾部(剩余部分=主体+尾部),其中

    • body=n*alignment()和tail<1*alignment()
    • body=m*alignment(),tail<1*alignment()+n*alignment(),其中m为偶数。
    • body=m*alignment(),tail<1*alignment()+n*alignment(),其中m是2的指数。
    • body=n*alignment(),tail=余数-body。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; 
    }                        
    
    }
    

    观察:

    在另外两台与我公司相同H/W配置的PC上,结果一致如下:

    ----vs2010beta1enu\u vsts.iso[1319909376字节]-----

    剩余大小=44840960字节

    余数尾部大小=14336字节

    实际0.060s,CPU 0.040s(66.7%),用户0.000s,系统0.040s

    余数体大小=44826624字节

    实际13.601s, CPU 7.731(56.8%),用户7.481 ,系统0.250s

    段大小=67108864字节,总整数=19

    实际172.476s,CPU 4.356s(2.5%),用户0.731s,系统3.625s

    但是,在具有不同H/W配置的PC上运行相同的代码会产生:

    ----vs2010beta1enu\u vsts.iso[1319909376字节]----- 剩余大小=44840960字节

    余数尾部大小=14336字节

    实际0.013s,CPU 0.000s(0.0%),用户0.000s,系统0.000s

    余数体大小=44826624字节

    真实2.468秒, CPU 0.188s(7.6%),用户0.047s ,系统0.141s

    段大小=67108864字节,总整数=19

    实际65.587s,CPU 4.578s(7.0%),用户0.844s,系统3.734s

    系统信息

    我的工作站无法理解的时间:

    操作系统名称:Microsoft Windows XP Professional

    操作系统版本:5.1.2600 Service Pack 3 Build 2600

    操作系统制造商:微软公司

    操作系统配置:成员工作站

    操作系统生成类型:无单处理器

    原始安装日期:2004-01-27,23:08

    系统启动时间:3天2小时15分46秒

    系统制造商:戴尔公司

    系统型号: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

    启动设备:\设备\硬盘卷2

    系统所在地:zh cn;中文(中国)

    输入语言环境:zh cn;中文(中国)

    时区:(GMT+08:00)北京、重庆、香港、乌鲁木齐

    总物理内存:3574 MB

    可用物理内存:1986 MB

    虚拟内存:最大大小:2048MB

    虚拟内存:可用:1916MB

    虚拟内存:正在使用:132 MB

    页面文件位置:C:\ page file.sys

    网卡:已安装3个网卡。

           [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
    

    另一个产生“正确”时间的工作站: 操作系统名称:Microsoft Windows XP Professional

    操作系统版本:5.1.2600 Service Pack 3 Build 2600

    操作系统制造商:微软公司

    操作系统配置:成员工作站

    OS构建类型:无多处理器

    原始安装日期:2009年5月18日下午2:28:18

    系统启动时间:21天5小时0分49秒

    系统制造商:戴尔公司

    系统型号: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

    启动设备:\设备\硬盘卷1

    系统所在地:zh cn;中文(中国)

    输入语言环境:en-us;英语(美国)

    时区:(GMT+08:00)北京、重庆、香港、乌鲁木齐

    总物理内存:3317 MB

    可用物理内存:1682 MB

    虚拟内存:最大大小:2048MB

    虚拟内存:可用:2007 MB

    虚拟内存:正在使用:41 MB

    页面文件位置:C:\ page file.sys

    网卡:已安装3个网卡。

           [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
    

    有什么解释理论吗?谢谢。

    6 回复  |  直到 13 年前
        1
  •  4
  •   kriss    15 年前

    这种行为看起来很不合逻辑。我想知道如果我们尝试一些愚蠢的事情会发生什么。如果整个文件大于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
  •  2
  •   MSN    15 年前

    你比较的文件有多零碎?你可以使用 FSCTL_GET_RETRIEVAL_POINTERS 获取文件在磁盘上映射到的范围。我怀疑最后的25MB会有很多小范围来解释您所测量的性能。

        3
  •  2
  •   Omnifarious    15 年前

    我想知道当一个段的大小不是偶数页时,mmap是否表现得异常?也许你可以尝试通过逐步缩小你的段大小来处理文件的最后部分,直到你的大小小于MaffddFielySoel::CalthMeNe(),并专门处理最后一点。

    另外,您说您正在执行512MB块,但您的代码将大小设置为8<<10。然后将其乘以mapped_file_source::alignment()。mapped_file_source::alignment()真的是65536吗?

    为了更便于移植和减少混淆,我建议您只需使用template参数中给定的大小,并简单地要求它是代码中映射的_file_source::alignment()的偶数倍。或者让人们以2的力量从块大小开始。将块大小作为模板参数传入,然后乘以某个奇怪的实现定义的常量,这似乎有点奇怪。

        4
  •  2
  •   DaveR    15 年前

    我知道这不是你问题的确切答案,但你有没有试着回避整个问题,即一次性映射整个文件?

    我对Win32内存管理了解甚少,但在Linux上可以使用 MAP_NORESERVE 旗带 mmap() ,因此不需要为整个文件大小保留RAM。考虑到你只是从两个文件中读取,操作系统应该能够在任何时候扔掉页面,如果它缺少内存…

        5
  •  1
  •   Zan Lynx    15 年前

    出于好奇,我会在linux或bsd上尝试一下,看看它是如何工作的。

    我有一个 真的很粗糙 猜猜这个问题: 我敢打赌windows正在做很多额外的检查,以确保它不会映射到文件的末尾。在过去,一些操作系统中存在安全问题,允许mmap用户查看文件系统私有数据或刚过映射末尾的区域中其他文件中的数据,因此对于操作系统设计者来说,这里要小心。因此,windows可能会使用一种更为谨慎的“将数据从磁盘复制到内核,将未映射的数据归零,将数据复制到用户”,而不是更快的“将数据从磁盘复制到用户”。

    尝试映射到文件末尾的下方,排除不适合64K块的最后字节。

        6
  •  0
  •   Sebastiaan M    15 年前

    是不是病毒扫描器导致了这些奇怪的结果?你试过不用病毒扫描器吗?

    当做,

    塞巴斯蒂安