gpt4 book ai didi

linux - mmap:使用多线程时的性能

转载 作者:太空狗 更新时间:2023-10-29 11:35:10 26 4
gpt4 key购买 nike

我有一个程序可以对很多文件(> 10 000)执行一些操作。它产生 N 个工作线程,每个线程映射一些文件,做一些工作并 munmaps 它。

我现在面临的问题是,每当我只使用 1 个进程和 N 个工作线程时,它的性能比生成 2 个进程(每个进程都有 N/2 个工作线程)的性能更差。我可以在 iotop 中看到这一点,因为 1 个进程 + N 个线程仅使用大约 75% 的磁盘带宽,而 2 个进程 + N/2 个线程使用全部带宽。

一些注意事项:

  • 只有在我使用 mmap()/munmap() 时才会发生这种情况。我试图用 fopen()/fread() 替换它并且它工作得很好。但由于 mmap()/munmap() 带有第 3 方库,我想以其原始形式使用它。
  • madvise() 是用 MADV_SEQUENTIAL 调用的,但如果我删除它或更改 advise 参数,它似乎没有任何改变(或者只是减慢速度)。
  • 线程亲和性似乎并不重要。我试图将每个线程限制为特定的核心。我还尝试将线程限制为核心对(超线程)。到目前为止没有结果。
  • 即使在这两种情况下,htop 报告的负载似乎也相同。

所以我的问题是:

  • 在多线程环境中使用 mmap() 时,有什么我不知道的吗?
  • 如果是这样,为什么 2 个进程的性能更好?

编辑:

  • 正如评论中所指出的,它在具有 2 个 CPU 的服务器上运行。我可能应该尝试设置线程亲和性,使其始终在同一个 CPU 上运行,但我想我已经尝试过了,但没有奏效。
  • 这是一段代码,我可以用它重现与我的生产软件相同的问题。
#include <condition_variable>
#include <deque>
#include <filesystem>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

#ifndef WORKERS
#define WORKERS 16
#endif

bool stop = false;
std::mutex queue_mutex;
std::condition_variable queue_cv;

std::pair<const std::uint8_t*, std::size_t> map_file(const std::string& file_path)
{
int fd = open(file_path.data(), O_RDONLY);
if (fd != -1)
{
auto dir_ent = std::filesystem::directory_entry{file_path.data()};
if (dir_ent.is_regular_file())
{
auto size = dir_ent.file_size();
auto data = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
madvise(data, size, MADV_SEQUENTIAL);
close(fd);
return { reinterpret_cast<const std::uint8_t*>(data), size };
}

close(fd);
}

return { nullptr, 0 };
}

void unmap_file(const std::uint8_t* data, std::size_t size)
{
munmap((void*)data, size);
}

int main(int argc, char* argv[])
{
std::deque<std::string> queue;

std::vector<std::thread> threads;
for (std::size_t i = 0; i < WORKERS; ++i)
{
threads.emplace_back(
[&]() {
std::string path;

while (true)
{
{
std::unique_lock<std::mutex> lock(queue_mutex);
while (!stop && queue.empty())
queue_cv.wait(lock);
if (stop && queue.empty())
return;
path = queue.front();
queue.pop_front();
}

auto [data, size] = map_file(path);
std::uint8_t b = 0;
for (auto itr = data; itr < data + size; ++itr)
b ^= *itr;
unmap_file(data, size);

std::cout << (int)b << std::endl;
}
}
);
}

for (auto& p : std::filesystem::recursive_directory_iterator{argv[1]})
{
std::unique_lock<std::mutex> lock(queue_mutex);
if (p.is_regular_file())
{
queue.push_back(p.path().native());
queue_cv.notify_one();
}
}

stop = true;
queue_cv.notify_all();

for (auto& t : threads)
t.join();

return 0;
}

最佳答案

Is there anything about mmap() I am not aware of when used in multithreaded environment?

是的。 mmap() 需要大量的虚拟内存操作 - 在某些地方有效地单线程处理您的进程。每this post from one Linus Torvalds:

... playing games with the virtual memory mapping is very expensive in itself. It has a number of quite real disadvantages that people tend to ignore because memory copying is seen as something very slow, and sometimes optimizing that copy away is seen as an obvious improvment.

Downsides to mmap:

  • quite noticeable setup and teardown costs. And I mean noticeable. It's things like following the page tables to unmap everything cleanly. It's the book-keeping for maintaining a list of all the mappings. It's The TLB flush needed after unmapping stuff.

  • page faulting is expensive. That's how the mapping gets populated, and it's quite slow.

请注意,上面的大部分内容也必须在整个机器上是单线程的,例如物理内存的实际映射。

因此映射文件所需的虚拟内存操作不仅代价高昂,而且实际上无法并行完成 - 内核必须跟踪的只有一大块实际物理内存,并且多个线程无法并行化更改进程的虚拟地址空间。

您几乎肯定会获得更好的性能,为每个文件重用内存缓冲区,其中每个缓冲区创建一次并且足够大以容纳读入其中的任何文件,然后使用低级 POSIX read() 调用。您可能想尝试使用页面对齐缓冲区并通过使用 O_DIRECT 标志(特定于 Linux)调用 open() 来使用直接 IO 来绕过页面缓存,因为您显然永远不要重新读取任何数据,任何缓存都是对内存和 CPU 周期的浪费。

重用缓冲区还完全消除了任何 munmap()delete/free()

不过,您必须管理缓冲区。也许用 N 个预先创建的缓冲区预填充队列,并在完成文件后将缓冲区返回到队列?

至于

If so, why do 2 processes have better performance?

使用两个进程将 mmap() 调用引起的进程特定的虚拟内存操作拆分为两个可并行运行的可分离集合。

关于linux - mmap:使用多线程时的性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55965430/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com