使用随机访问C ++和Linux的Linux内存映射文件性能不佳蟒蛇

时间:2014-10-13 21:19:55

标签: python c++ linux mmap

在尝试使用内存映射文件创建一个千兆字节的文件(大约13GB)时,我遇到了mmap()似乎有问题的问题。初始实现是在Windows上使用boost :: iostreams :: mapped_file_sink在c ++中完成的,一切都很顺利。然后代码在Linux上运行,在Windows上花费几分钟的时间在Linux上运行了几个小时。

这两台机器是同一硬件的克隆:戴尔R510 2.4GHz 8M缓存16GB Ram 1TB Disk PERC H200控制器。

Linux是使用3.8内核和g ++ 4.83的Oracle Enterprise Linux 6.5。

有人担心升级库可能存在问题,因此使用boost :: interprocess :: file_mapping并使用native mmap()再次完成实现。这三个都表现出相同的行为。当Linux性能严重下降时,Windows和Linux的性能与某一点相当。

完整的源代码和性能数字链接如下。

// C++ code using boost::iostreams
void IostreamsMapping(size_t rowCount)
{
   std::string outputFileName = "IoStreamsMapping.out";
   boost::iostreams::mapped_file_params params(outputFileName);
   params.new_file_size = static_cast<boost::iostreams::stream_offset>(sizeof(uint64_t) * rowCount);
   boost::iostreams::mapped_file_sink fileSink(params); // NOTE: using this form of the constructor will take care of creating and sizing the file.
   uint64_t* dest = reinterpret_cast<uint64_t*>(fileSink.data());
   DoMapping(dest, rowCount);
}

void DoMapping(uint64_t* dest, size_t rowCount)
{
   inputStream->seekg(0, std::ios::beg);
   uint32_t index, value;
   for (size_t i = 0; i<rowCount; ++i)
   {
      inputStream->read(reinterpret_cast<char*>(&index), static_cast<std::streamsize>(sizeof(uint32_t)));
      inputStream->read(reinterpret_cast<char*>(&value), static_cast<std::streamsize>(sizeof(uint32_t)));
      dest[index] = value;
   }
}

最后一次测试是在Python中用另一种语言重现的。跌倒发生在同一个地方,所以看起来像是同样的问题。

# Python code using numpy
import numpy as np
fpr = np.memmap(inputFile, dtype='uint32', mode='r', shape=(count*2))
out = np.memmap(outputFile, dtype='uint64', mode='w+', shape=(count))
print("writing output")
out[fpr[::2]]=fpr[::2]

对于c ++测试,Windows和Linux具有类似的性能,高达约3亿int64(Linux看起来稍快)。对于C ++和Python来说,看起来Linux上的性能会下降到3Gb(每个int64 = 3.2Gb 4亿* 8字节)。

我知道在32位Linux上,3Gb是一个神奇的界限,但我并不知道64位Linux的类似行为。

结果的要点是1.4分钟,Windows在Linux上变为1.7小时,4亿int64。我实际上试图映射接近13亿int64。

有谁可以解释为什么Windows和Linux之间的性能会出现这种差异?

非常感谢任何帮助或建议!

LoadTest.cpp

Makefile

LoadTest.vcxproj

updated mmap_test.py

original mmap_test.py

Updated Results使用更新的Python代码...... Python速度现在可与C ++相媲美

Original Results注意:Python结果陈旧

1 个答案:

答案 0 :(得分:7)

编辑:升级到&#34;正确答案&#34;。问题在于&#34;脏页&#34;由Linux处理。我仍然希望我的系统一次又一次地刷新脏页面,所以我不允许它有很多未完成的页面。但与此同时,我可以证明这是正在发生的事情。

我这样做了(&#34; sudo -i&#34;):

# echo 80 > /proc/sys/vm/dirty_ratio
# echo 60 > /proc/sys/vm/dirty_background_ratio

这给出了这些设置VM脏设置:

grep ^ /proc/sys/vm/dirty*
/proc/sys/vm/dirty_background_bytes:0
/proc/sys/vm/dirty_background_ratio:60
/proc/sys/vm/dirty_bytes:0
/proc/sys/vm/dirty_expire_centisecs:3000
/proc/sys/vm/dirty_ratio:80
/proc/sys/vm/dirty_writeback_centisecs:500

这使我的基准测试运行如下:

$ ./a.out m64 200000000
Setup Duration 33.1042 seconds
Linux: mmap64
size=1525 MB
Mapping Duration 30.6785 seconds
Overall Duration 91.7038 seconds

在&#34;之前与&#34;比较:

$ ./a.out m64 200000000
Setup Duration 33.7436 seconds
Linux: mmap64
size=1525
Mapping Duration 1467.49 seconds
Overall Duration 1501.89 seconds

有这些VM脏设置:

grep ^ /proc/sys/vm/dirty*
/proc/sys/vm/dirty_background_bytes:0
/proc/sys/vm/dirty_background_ratio:10
/proc/sys/vm/dirty_bytes:0
/proc/sys/vm/dirty_expire_centisecs:3000
/proc/sys/vm/dirty_ratio:20
/proc/sys/vm/dirty_writeback_centisecs:500

我不确定我应该使用什么设置来获得IDEAL性能,同时仍然不会让所有脏页永远留在内存中(这意味着如果系统崩溃,则需要更长时间才能写入磁盘) 。

历史:这是我最初写的“非答案”#34; - 这里的一些评论仍然适用......

不是一个真正的答案,但我发现相当有趣的是,如果我将代码更改为首先读取整个数组并将其写出来,那么它比在同一个循环中执行两者的速度要快得多。我很欣赏,如果你需要处理非常庞大的数据集(大于内存),这完全没用。将原始代码发布后,100M uint64值的时间为134秒。当我分开读取和写入周期时,它是43s。

这是修改后的DoMapping函数[仅代码我已更改]:

struct VI
{
    uint32_t value;
    uint32_t index;
};


void DoMapping(uint64_t* dest, size_t rowCount)
{
   inputStream->seekg(0, std::ios::beg);
   std::chrono::system_clock::time_point startTime = std::chrono::system_clock::now();
   uint32_t index, value;
   std::vector<VI> data;
   for(size_t i = 0; i < rowCount; i++)
   {
       inputStream->read(reinterpret_cast<char*>(&index), static_cast<std::streamsize>(sizeof(uint32_t)));
       inputStream->read(reinterpret_cast<char*>(&value), static_cast<std::streamsize>(sizeof(uint32_t)));
       VI d = {index, value};
       data.push_back(d);
   }
   for (size_t i = 0; i<rowCount; ++i)
   {
       value = data[i].value;
       index = data[i].index;
       dest[index] = value;
   }
   std::chrono::duration<double> mappingTime = std::chrono::system_clock::now() - startTime;
   std::cout << "Mapping Duration " << mappingTime.count() << " seconds" << std::endl;
   inputStream.reset();
}

我目前正在运行200M记录的测试,这在我的计算机上花费了大量时间(2000+秒没有代码更改)。很明显,所花费的时间来自磁盘I / O,我看到IO速率为50-70MB / s,这是相当不错的,因为我真的不希望我相当简单设置提供更多。对于更大的尺寸,改进并不是那么好,但仍然是一个不错的改进:总时间为1502秒,而在同一循环中读取和写入的时间为2021秒&#34;。

另外,我想指出这对任何系统都是一个相当糟糕的测试 - 事实上Linux明显比Windows更糟糕 - 你不是真的想要映射一个大文件和每个页面随机写入8个字节[意味着必须读入4KB页面]。如果这反映了您的REAL应用程序,那么您应该认真地重新考虑您的方法。当你有足够的可用内存使整个内存映射区域适合RAM时,它会正常运行。

我的系统中有足够的内存,所以我认为问题在于Linux并不像太多的映射页面那么脏&#34;。

我觉得这可能与它有关: https://serverfault.com/questions/126413/limit-linux-background-flush-dirty-pages 更多解释: http://www.westnet.com/~gsmith/content/linux-pdflush.htm

不幸的是,我也很累,需要睡觉。我明天是否可以尝试这些 - 但不要屏住呼吸。就像我说的那样,这不是一个真正的答案,而是一个长期的评论,它并不适合评论(并且包含代码,这在评论中完全是垃圾)