使用随机访问C++和Python的糟糕的Linux内存映射文件性能

sha*_*.lo 11 c++ python linux mmap

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

这两台机器是相同硬件的克隆:戴尔R510 2.4GHz 8M高速缓存16GB Ram 1TB磁盘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;
   }
}
Run Code Online (Sandbox Code Playgroud)

最后一次测试是在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]
Run Code Online (Sandbox Code Playgroud)

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

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

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

任何人都可以解释为什么Windows和Linux之间存在性能上的这种脱节?

任何帮助或建议将不胜感激!

LoadTest.cpp

Makefile文件

LoadTest.vcxproj

更新了mmap_test.py

原始的mmap_test.py

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

原始结果注意:Python结果是陈旧的

Mat*_*son 7

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

我做了这个(用"sudo -i"):

# echo 80 > /proc/sys/vm/dirty_ratio
# echo 60 > /proc/sys/vm/dirty_background_ratio
Run Code Online (Sandbox Code Playgroud)

这给了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
Run Code Online (Sandbox Code Playgroud)

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

$ ./a.out m64 200000000
Setup Duration 33.1042 seconds
Linux: mmap64
size=1525 MB
Mapping Duration 30.6785 seconds
Overall Duration 91.7038 seconds
Run Code Online (Sandbox Code Playgroud)

与"之前"比较:

$ ./a.out m64 200000000
Setup Duration 33.7436 seconds
Linux: mmap64
size=1525
Mapping Duration 1467.49 seconds
Overall Duration 1501.89 seconds
Run Code Online (Sandbox Code Playgroud)

有这些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
Run Code Online (Sandbox Code Playgroud)

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

对于历史:这是我最初写的"非答案" - 这里的一些评论仍然适用...

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

这是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();
}
Run Code Online (Sandbox Code Playgroud)

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

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

我的系统中有足够的RAM,所以我认为问题在于Linux不喜欢太多"脏"的映射页面.

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

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