Mar*_*vič 6 linux multithreading mmap
我有一个程序可以对大量文件(> 10 000)执行一些操作。它产生 N 个工作线程,每个线程映射一些文件,做一些工作并映射它。
我现在面临的问题是,每当我只使用 1 个进程和 N 个工作线程时,它的性能比生成 2 个进程(每个进程有 N/2 个工作线程)要差。我可以看到这一点,iotop
因为 1 个进程+N 个线程仅使用大约 75% 的磁盘带宽,而 2 个进程+N/2 个线程则使用全部带宽。
一些注意事项:
MADV_SEQUENTIAL
但如果我删除它或更改 suggest 参数,它似乎不会改变任何东西(或者只是减慢速度)。htop
似乎也是相同的。所以我的问题是:
编辑:
#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;
}
Run Code Online (Sandbox Code Playgroud)
在多线程环境中使用时有什么
mmap()
我不知道的吗?
是的。 mmap()
需要大量的虚拟内存操作 - 在某些地方有效地单线程处理您的进程。根据Linus Torvalds 的这篇文章:
...使用虚拟内存映射玩游戏本身就非常昂贵。它有许多非常现实的缺点,人们往往会忽略这些缺点,因为内存复制被认为是非常慢的事情,有时优化该副本被视为明显的改进。
mmap 的缺点:
相当明显的安装和拆卸成本。我的意思是引人注目的。就像遵循页表来干净地取消所有内容的映射一样。它是用于维护所有映射列表的簿记。这是取消映射后需要的 TLB 刷新。
页面错误的代价是昂贵的。这就是映射的填充方式,而且速度相当慢。
请注意,上面的大部分内容也必须在整个机器上是单线程的,例如物理内存的实际映射。
因此,映射文件所需的虚拟内存操作不仅成本高昂,而且实际上无法并行完成 - 内核必须跟踪的只有一块实际物理内存,并且多个线程无法并行化对一个内存的更改。进程的虚拟地址空间。
几乎可以肯定,为每个文件重用内存缓冲区会获得更好的性能,其中每个缓冲区创建一次,并且足够大以容纳读入其中的任何文件,然后使用低级 POSIXread()
调用从文件中读取。您可能想尝试使用页面对齐缓冲区并通过使用标志(特定于 Linux)进行调用来使用直接 IOopen()
来O_DIRECT
绕过页面缓存,因为您显然从未重新读取任何数据,并且任何缓存都会浪费内存和 CPU 周期。
重用缓冲区也完全消除了任何munmap()
或delete
/ free()
。
不过,您必须管理缓冲区。也许用 N 个预先创建的缓冲区预填充队列,并在处理完文件后将缓冲区返回到队列?
据,直到...为止
如果是这样,为什么 2 个进程具有更好的性能?
两个进程的使用将由调用引起的特定于进程的虚拟内存操作mmap()
分成两个可以并行运行的可分离的集合。
归档时间: |
|
查看次数: |
3581 次 |
最近记录: |