下面的程序从文件中读取一堆行并解析它们。它可能会更快。另一方面,如果我有几个核心和几个文件要处理,那不是很重要;我可以并行运行工作。
不幸的是,这似乎不适用于我的拱形机器。运行该程序的两个副本仅比运行一个副本(见下文)稍微(如果有的话),并且不到我的驱动器能够的20%。在具有相同硬件的ubuntu机器上,情况会好一些。我获得了3-4个内核的线性扩展,但我的SSD容量大约只有50%。
随着内核数量的增加,哪些障碍会阻止I / O吞吐量的线性扩展,以及可以采取哪些措施来改善软件/操作系统端的I / O并发性?
P.S。 - 对于下面提到的硬件,如果我将解析移动到单独的线程,单个核心足够快,读取将受I / O限制。还有other optimizations用于提高单核性能。但是,对于这个问题,我想关注并发性以及我的编码和操作系统选择如何影响它。
详细信息:
以下是几行iostat -x 1
输出:
使用dd:
将文件复制到/ dev / nullDevice: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.00 0.00 883.00 0.00 113024.00 0.00 256.00 1.80 2.04 2.04 0.00 1.13 100.00
运行我的程序:
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 1.00 1.00 141.00 2.00 18176.00 12.00 254.38 0.17 1.08 0.71 27.00 0.96 13.70
一次运行我的程序的两个实例,读取不同的文件:
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 11.00 0.00 139.00 0.00 19200.00 0.00 276.26 1.16 8.16 8.16 0.00 6.96 96.70
它好多了!添加更多核心并不会增加吞吐量,实际上它会开始降级并变得不那么一致。
这是我的程序的一个实例和dd的一个实例:
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 9.00 0.00 468.00 0.00 61056.00 0.00 260.92 2.07 4.37 4.37 0.00 2.14 100.00
这是我的代码:
#include <string>
#include <boost/filesystem/path.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/fstream.hpp>
typedef boost::filesystem::path path;
typedef boost::filesystem::ifstream ifstream;
int main(int argc, char ** argv) {
path p{std::string(argv[1])};
ifstream f(p);
std::string line;
std::vector<boost::iterator_range<std::string::iterator>> fields;
for (getline(f,line); !f.eof(); getline(f,line)) {
boost::split (fields, line, boost::is_any_of (","));
}
f.close();
return 0;
}
以下是我编辑它的方式:
g++ -std=c++14 -lboost_filesystem -o gah.o -c gah.cxx
g++ -std=c++14 -lboost_filesystem -lboost_system -lboost_iostreams -o gah gah.o
修改:更多细节
在运行上述基准之前我clear memory cache (free page cache, dentries and inodes),以防止linux从缓存中拉入页面。
我的进程似乎受CPU限制;切换到mmap或通过pubsetbuf更改缓冲区大小对记录的吞吐量没有明显影响。
另一方面,缩放是IO绑定的。如果我在运行程序之前将所有文件都带入内存缓存中,那么吞吐量(现在通过iostat
无法看到的执行时间来衡量)与内核数量呈线性关系。
我真正想要了解的是,当我使用多个顺序读取过程从磁盘读取数据时,为什么吞吐量不会随着接近驱动器的过程数量而线性扩展&#39 ; s最大读取速度?为什么我会在没有饱和吞吐量的情况下达到I / O限制,以及在执行此操作时如何依赖于我运行的操作系统/软件堆栈?
答案 0 :(得分:2)
你没有比较类似的东西。
您正在比较
Copying a file to /dev/dull with dd:
(我假设你的意思是/dev/null
...)
与
int main(int argc, char ** argv) {
path p{std::string(argv[1])};
ifstream f(p);
std::string line;
std::vector<boost::iterator_range<std::string::iterator>> fields;
for (getline(f,line); !f.eof(); getline(f,line)) {
boost::split (fields, line, boost::is_any_of (","));
}
f.close();
return 0;
}
第一个只读取原始字节而不关心它们是什么,并将它们转储到位桶中。您的代码按行读取,需要进行标识,然后将它们拆分为矢量。
您阅读数据的方式,您阅读了一行,然后花时间处理它。 dd
命令将您的代码与您的代码进行比较,从不花时间做除读取数据之外的事情 - 它不必读取然后处理然后读取然后处理...
答案 1 :(得分:1)
我相信至少有三个问题在这里发挥作用:
1)我的阅读过于频繁。
我正在阅读的文件具有可预测长度的行,并且具有可预测的分隔符。通过在千分之一中随机引入1微秒延迟,我能够将多个内核之间的吞吐量提高到大约45MB / s。
2)我的pubsetbuf实现实际上没有设置缓冲区大小。
标准仅指定pubsetbuf在指定缓冲区大小为零时关闭缓冲,如this link中所述(感谢@Andrew Henle);所有其他行为都是实现定义的。显然我的实现使用的缓冲区大小为8191(由strace
验证),无论我设置了什么值。
为了测试目的而懒得实现我自己的流缓冲,我重写代码将1000行读入一个向量,然后尝试在第二个循环中解析它们,然后重复整个过程直到文件结尾(有没有随机延迟)。这使我可以扩展到大约50MB / s。
3)我的I / O调度程序和设置不适合我的驱动器和应用程序。
显然,arch linux默认使用我的SSD驱动器的cfq
io调度程序,其参数适用于HDD驱动器。将slice_sync
设置为0,如here所述(参见Mikko Rantalainen的回答和链接文章),或切换到noop
调度程序,如here所述,原始代码获得大约60MB / s的最大吞吐量,运行四个核心。 This link也很有帮助。
通过noop
调度,缩放似乎几乎是线性的,直到我的机器的四个物理内核(我有八个超线程)。