【发布时间】:2019-05-03 07:51:16
【问题描述】:
我有一个程序可以对很多文件(> 10 000)执行一些操作。它产生 N 个工作线程,每个线程映射一些文件,执行一些工作并对其进行映射。
我现在面临的问题是,每当我只使用 1 个进程和 N 个工作线程时,它的性能都比生成 2 个进程更差,每个进程都有 N/2 个工作线程。我可以在iotop 中看到这一点,因为 1 个进程+N 个线程仅使用大约 75% 的磁盘带宽,而 2 个进程+N/2 个线程使用全部带宽。
一些注意事项:
- 仅当我使用 mmap()/munmap() 时才会发生这种情况。我试图用 fopen()/fread() 替换它,它工作得很好。但是由于 mmap()/munmap() 带有 3rd 方库,我想以原始形式使用它。
- madvise() 使用
MADV_SEQUENTIAL调用,但如果我删除它或更改建议参数,它似乎并没有改变任何东西(或者它只是减慢它的速度)。 - 线程亲和性似乎无关紧要。我试图将每个线程限制为特定的核心。我还尝试将线程限制为核心对(超线程)。到目前为止没有结果。
-
htop报告的负载似乎在两种情况下都相同。
所以我的问题是:
- mmap() 在多线程环境中使用时有什么我不知道的吗?
- 如果是这样,为什么 2 个进程的性能更好?
编辑:
- 正如 cmets 中所指出的,它在具有 2xCPU 的服务器上运行。我可能应该尝试设置线程关联,使其始终在同一个 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;
}
【问题讨论】:
-
为什么 2 个进程的性能更好 - 可能是线程之间的意外交互或错误共享。贴出代码。
-
我会试着想出一些最小的例子。我只是想我错过了一些关于 mmap() 和线程的东西,但如果确定原因不是那么简单,我会想出一些东西。
-
在多插槽机器上,内存也可能分配在远程 NUMA 节点而不是本地节点上。查看
numactl --hardware的输出。 -
如果有任何改变,它确实是具有 2xCPU 的服务器。我会尽快发布最小示例。
-
我已经添加了一个可以重现问题的代码。
标签: linux multithreading mmap