如何提高Linux中的SSD I / O吞吐量并发性

时间:2017-05-01 16:44:11

标签: c++ linux concurrency io ssd

下面的程序从文件中读取一堆行并解析它们。它可能会更快。另一方面,如果我有几个核心和几个文件要处理,那不是很重要;我可以并行运行工作。

不幸的是,这似乎不适用于我的拱形机器。运行该程序的两个副本仅比运行一个副本(见下文)稍微(如果有的话),并且不到我的驱动器能够的20%。在具有相同硬件的ubuntu机器上,情况会好一些。我获得了3-4个内核的线性扩展,但我的SSD容量大约只有50%。

随着内核数量的增加,哪些障碍会阻止I / O吞吐量的线性扩展,以及可以采取哪些措施来改善软件/操作系统端的I / O并发性?

P.S。 - 对于下面提到的硬件,如果我将解析移动到单独的线程,单个核心足够快,读取将受I / O限制。还有other optimizations用于提高单核性能。但是,对于这个问题,我想关注并发性以及我的编码和操作系统选择如何影响它。

详细信息:

以下是几行iostat -x 1输出:

使用dd:

将文件复制到/ dev / null
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               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限制,以及在执行此操作时如何依赖于我运行的操作系统/软件堆栈?

2 个答案:

答案 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调度,缩放似乎几乎是线性的,直到我的机器的四个物理内核(我有八个超线程)。