【问题标题】:Improving mmap/munmap performance on MacOS X提高 MacOS X 上的 mmap/munmap 性能
【发布时间】:2010-11-07 06:52:47
【问题描述】:

在 MacOS X 机器上运行以下 C 代码(2GB 文件上的一堆 mmap 和 munmap)似乎比在 Linux 机器上慢得多。

#define BUFSZ 2000000000
static u_char buf[BUFSZ];
....

// Time 10000 mmaps and munmaps from random offsets for various 
// sizes of mapped chunk.
for (msize = 4096; msize <= 1048576; msize *= 16) {
  fd = open("io_benchmark.dat", O_RDONLY);
  if (fd  < 0 ) die("can't open io_benchmark.dat for reading");
  for (i = 0; i < 10000; i++) {
    // Make sure the block to be mapped doesn't start in the
    // last meg.
    offset = (size_t) random() % (BUFSZ - 1048576);
    mblock = femmap(fd, (off_t)offset, (size_t) msize, PROT_READ, 
                    "test block");
    total = 0;
    for (j = 0; j < msize; j++) {
      total += mblock[j];
    }
    femunmap(mblock, (size_t) msize, "test block");
  }
  printf("Elapsed time to mmap and munmap 10000 blocks of %d kB: %.4f sec\n", 
         msize/1024, (time = time_since_last_call()));

  rslt = close(fd);
  if (fd  < 0 ) die("can't close io_benchmark.dat after reading");
}

具体来说,比较两台机器

CPU     Xeon E3113 dual core @ 3.00GHz           Core 2 Duo @ 2.4GHz dual core
RAM     8GB                                      4GB
Kernel  2.6.18-92.el5PAE SMP i686                MacOS 10.6.4 Snow Leopard
Disk    WD 250GB SATA 16MB cache 7200 RPM EXT3   Hitachi 250GB SATA 5400 RPM, journaled HFS+

给出以下结果

                            Linux    MacOS X
Time for 10000 4kB mmaps    0.0165   682.87
Time for 10000 64kB mmap    0.0170   657.79
Time for 10000 1MB mmaps    0.0217   633.38

即使考虑到内存量的减少,考虑到文件只有物理内存的一半,这似乎也很不寻常。谁能指出可能会提高性能的代码更改或配置更改?

我们尝试使用读取而不是 mmap,它确实产生了很大的不同,但这样做需要对现有代码库进行重大更改(并且 mmap 比 linux 上的读取快得多)。

【问题讨论】:

  • 您的total 变量在赋值后没有使用?它可能只是被优化了,所以你可能没有衡量正确的东西。
  • 您的大 buf 数组从未使用过 - 也许您需要显示您的 femmap()femunmap() 函数的定义?一个可编译的例子总是有帮助的。
  • 在 Mac OS X 上通过 Instruments 运行活动监视器可能会显示显着的页入页出负载。 MacOS X 测试系统的 RAM 太低,无法进行公平比较。

标签: c linux performance macos mmap


【解决方案1】:

我认为你只是没有衡量正确的东西。我检查了你测试的内部部分,我的 gcc 版本能够完全优化循环。

这会发生变化,例如当我将 mblock 指针声明为指向 volatile 数据的指针时。然后编译器有义务对循环中的语句执行所有副作用,特别是从内存中充电。

因此,您可以从测试中得出的唯一结论是:

  • 您在 MacOS X 上的编译器不是很好 聪明
  • 始终检查汇编程序 基准生产

因此,如果您可以真正重做测试,我很想看看这两个系统在该功能方面的真正区别。

【讨论】:

    【解决方案2】:

    看起来这是“设计使然”。据此https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemAdvancedPT/MappingFilesIntoMemory/MappingFilesIntoMemory.html#//apple_ref/doc/uid/TP40010765-CH2-SW1:

    以下情况不应使用文件映射:

    • 您想按顺序读取文件 从头到尾只有一次。

    • 文件有几百兆 或更大的尺寸。 (映射大文件 快速填充虚拟内存空间。 此外,您的程序可能不会 如果有可用空间 已经运行了一段时间或其 内存空间是碎片化的。)

    对于大型顺序读取操作,最好禁用磁盘缓存并将文件读入一个小的内存缓冲区。有关详细信息,请参阅“有选择地缓存文件”。


    这是一个演示问题的代码sn-p:

    off_t       file_target = off_t(3) << 29; //  1.5GB
    const char* path = "B9361194.data";
    
    //  Touch the output file
    {
        FILE*       fp = fopen( path, "a");
        fclose(fp);
    }
    
    //  Open the output file
    FILE*       fp = fopen( path, "rb+");
    int         fd = fileno(fp);
    off_t       file_physical = 0;
    off_t       file_logical = 0;
    ftruncate( fd, file_physical );
    
    //  Declare the mapping
    off_t       map_start = 0;
    off_t       map_count = 0;
    char*       map_address = 0;
    
    //  Set up the input buffer.
    //  We are just going to write this out until we have written total bytes
    size_t      requested = 1024;
    char    input[requested];
    for ( size_t i = 0; i < requested; ++i ) input[i] = 1;
    
    //  Write the buffer, resizing and mapping as we go.
    while ( file_logical < file_target ) {
        //  Figure out how much to write.
        size_t limit = requested;
        if ( ( file_target - file_logical ) < (off_t)limit ) 
            limit = size_t( file_target - file_logical );
    
        //  If we can't fit the buffer inside the allocation
        //  unmap and grow everything
        if ( file_logical + (off_t)limit > file_physical ) {
            //  Unmap
            if ( map_address ) munmap( map_address, map_count );
    
            //  Grow the file by 64K
            off_t   new_physical = off_t(1) << 16;  //  64K allocation
            if ( new_physical < (off_t)limit ) new_physical = limit;
            file_physical += new_physical;
            ftruncate( fd, file_physical );
    
            //  Map the end
            map_count = off_t(1) << 23;    //  8MB
            if ( map_count > file_physical ) map_count = file_physical;
            map_start = file_physical - map_count;
            void* address = mmap( 0, map_count, ( PROT_WRITE | PROT_READ ), MAP_SHARED, fd, map_start );
            // int err = errno;
            // if ( address == MAP_FAILED ) CPPUNIT_ASSERT_EQUAL_MESSAGE( strerror(err), 0, errno );
            map_address = reinterpret_cast<char*>( address );
        }
    
        //  Copy the buffer in
        size_t  offset = size_t(file_logical - map_start);
        memcpy( map_address + offset, input, limit );
        file_logical += limit;
    }
    
    //  Clean up
    if ( map_address ) munmap( map_address, map_count );
    ftruncate( fd, file_logical );
    fclose( fp );
    

    不知道他们是否以及何时修复它。

    【讨论】:

      【解决方案3】:

      我对 OS X mmap 问题的结论是映射整个文件并保持映射。如果你需要扩展文件,映射比你需要的更多的字节,这样你只需要偶尔重新映射。

      您可能需要使用 64 位 OS X 才能完成这项工作。

      【讨论】:

        猜你喜欢
        • 2017-10-11
        • 2017-05-19
        • 2012-04-06
        • 1970-01-01
        • 2014-11-23
        • 2019-03-21
        • 2017-12-14
        • 2021-10-20
        • 2011-03-02
        相关资源
        最近更新 更多