• 准备

    • 要求

    • 机器配置

    • 测试磁盘 IO 性能

  • 实验一: Buffer IO 写入

  • 实验二: 4K 单次 Direct IO 写入

  • 实验三: mmap 写入

  • 实验四: 改进的 mmap 写入

  • 结论

准备

要求

在 限制内存 的情况下,假定我们每次写入 4k 的数据,如何保证 kill -9 不丢数据的情况下,仍然稳定的跑满磁盘的 IO?因为需要保证 kill -9 不丢数据,所以 fwrite() 就不在我们的考虑范围之内了. 又因为限制内存,所以直观的想法是直接 Direct IO, 但 Direct IO 能否跑满磁盘 IO 呢?

机器配置

CPU: 64 核 Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz

磁盘 : Intel Optane SSD

测试磁盘 IO 性能

官方称读 / 写带宽是 2400/2000 MB/s, 我们利用 fio 来进行实测:

顺序读性能:

sudo fio --filename=test -iodepth=64 -ioengine=libaio --direct=1 --rw=read --bs=2m --size=2g --numjobs=4 --runtime=10 --group_reporting --name=test-read

结果:

READ: bw=2566MiB/s (2691MB/s), 2566MiB/s-2566MiB/s (2691MB/s-2691MB/s), io=8192MiB (8590MB), run=3192-3192msec

顺序写性能:

sudo fio --filename=test -iodepth=64 -ioengine=libaio -direct=1 -rw=write -bs=1m -size=2g -numjobs=4 -runtime=20 -group_reporting -name=test-write

结果:

WRITE: bw=2181MiB/s (2287MB/s), 2181MiB/s-2181MiB/s (2287MB/s-2287MB/s), io=8192MiB (8590MB), run=3756-3756msec

实测读写带宽: 2566/2181 MB/s

实验一: Buffer IO 写入

因为是限制内存,所以 Buffer IO 不在我们的考虑范围内,但是我们先来测试一下 Buffer IO 的具体性能到底如何? 我们使用最简单的方法,因为我们的 CPU 核数是 64,所以直接 64 线程单次 4K 字节 Buffer IO 写入, 即通过操作系统的 Page Cache 的策略来缓存,刷盘:

代码片段 : 完整代码

static char data[4096] attribute((aligned(4096))) = {'a'};

void writer(int index) {std::string fname = "data" + std::to_string(index);int data_fd = ::open(fname.c_str(), O_RDWR | O_CREAT | O_APPEND, 0645);for (int32_t i = 0; i < 1000000; i++) {        ::write(data_fd, data, 4096);    }    close(data_fd);}

int main() {std::vectorstd::thread threads;for(int i = 0; i < 64; i++) {std::thread worker(writer, i);        threads.push_back(std::move(worker));    }for (int i = 0; i < 64; i++) {        threads[i].join();    }return 0;}

我们通过 O_APPEND 单次 4k 追加写入,之后通过 vmstat 来保留 120s 的写入带宽:

vmstat 1 120 > buffer_io

经过最后的测试数据整理,我们发现 Buffer IO 的性能基本能稳定跑满带宽, 其中只有一次 I/O 抖动:

实验二: 4K 单次 Direct IO 写入

Buffer IO 利用 Page Cache 帮助我们缓存了大量的数据,其实必然提高了写入带宽,但假如在限制内存的情况下,Buffer IO 就不是正确的解决方案了,这次我们绕过 Page Cache, 直接 Direct IO 单次 4K 写入:

代码片段 : 完整代码

唯一需要修改的地方就是在 open() 中加入 O_DIRECT 标志:

int data_fd = ::open(fname.c_str(), O_RDWR | O_CREAT | O_APPEND | O_DIRECT, 0645);

通过 vmstat 获取写入带宽数据, 整理如下:

通过数据我们发现,单次 4k 的 Direct IO 写入无法跑满磁盘的 I/O 带宽,仅仅只有 800MB/S

实验三: mmap 写入

通过前面这两个实验我们发现,Buffer IO 是可以跑满磁盘 I/O 的,那我们可以尝试模拟 Buffer IO 的写入方式,使用较少的内存来达到 Buffer IO 的写入效果.

我们使用 mmap 来实现 Buffer IO 写入,通过限定的 Buffer Block 来模拟 Page Cache 的聚合效果, 实验中我们使用 memcpy 来完成数据拷贝,Buffer Block 我们设定为 4K * 4, 与 Direct IO 的不同,我们这次限定即 16KB 的单次写入:

代码片段: 完整代码

main() 函数不变,修改线程的 writer() 函数:

static char data[4096] attribute((aligned(4096))) = {'a'};static int32_t map_size = 4096 * 4;

void MapRegion(int fd, uint64_t file_offset, char** base) {void* ptr = mmap(nullptr, map_size, PROT_READ | PROT_WRITE,                    MAP_SHARED,                    fd,                    file_offset);if (unlikely(ptr == MAP_FAILED)) {        *base = nullptr;return;    }    base = reinterpret_cast<char>(ptr);}

void UnMapRegion(char* base) {    munmap(base, map_size);}

void writer(int index) {std::string fname = "data" + std::to_string(index);char* base = nullptr;char* cursor = nullptr;uint64_t mmap_offset = 0, file_offset = 0;int data_fd = ::open(fname.c_str(), O_RDWR | O_CREAT, 0645);    posix_fallocate(data_fd, 0, (4096UL * 1000000));    MapRegion(data_fd, 0, &base);if (unlikely(base == nullptr)) {return;    }    cursor = base;    file_offset += map_size;for (int32_t i = 0; i < 1000000; i++) {if (unlikely(mmap_offset >= map_size)) {            UnMapRegion(base);            MapRegion(data_fd, file_offset, &base);if (unlikely(base == nullptr)) {return;            }            cursor = base;            file_offset += map_size;            mmap_offset = 0;        }memcpy(cursor, data, 4096);        cursor += 4096;        mmap_offset += 4096;    }    UnMapRegion(base);    close(data_fd);}

我们通过 vmstat 来获取写入带宽数据,我们发现 mmap 的 16K 写入可以跑满磁盘带宽,但 I/O 抖动较大,无法类似于 Buffer IO 稳定的写入.

我们通过 perf 生成火焰图分析:

通过 pref 生成分析瓶颈时发现,写入 writer() 时触发了大量的 Page Fault, 即缺页中断,而 mmap() 本身的调用也有一定的消耗 (关于 mmap() 的源码分析,我们在后面的文章会详细分析 ),我们实验三的思路是: 首先 fallocate 一个大文件,然后 mmap() 内存映射 16k 的 Block, memcpy() 写满之后,游标右移重新 mmap(),以此循环.

实验四: 改进的 mmap 写入

为了避免 mmap() 的开销,我们使用临时文件在写入之前 mmap() 映射,之后循环利用这 16K 的 Block, 避免 mmap() 的巨大开销:

代码片段: 完整代码

void MapRegion(int fd, uint64_t file_offset, char** base) {void* ptr = mmap(nullptr, map_size, PROT_READ | PROT_WRITE,           MAP_SHARED,           fd,           file_offset);if (unlikely(ptr == MAP_FAILED)) {    *base = nullptr;return;  }  *base = reinterpret_cast<char*>(ptr);}

void UnMapRegion(char* base) {  munmap(base, map_size);}

void writer(int index) {std::string fname = "data" + std::to_string(index);std::string batch = "batch" + std::to_string(index);

char* base = nullptr;char* cursor = nullptr;uint64_t mmap_offset = 0, file_offset = 0;

int data_fd = ::open(fname.c_str(), O_RDWR | O_CREAT | O_DIRECT, 0645);int batch_fd = ::open(batch.c_str(), O_RDWR | O_CREAT | O_DIRECT, 0645);  posix_fallocate(data_fd, 0, (4096UL * 1000000));  posix_fallocate(batch_fd, 0, map_size);

  MapRegion(batch_fd, 0, &base);if (unlikely(base == nullptr)) {return;  }  cursor = base;  file_offset += map_size;

for (int32_t i = 0; i < 1000000; i++) {if (unlikely(mmap_offset >= map_size)) {      pwrite64(data_fd, base, map_size, file_offset);      cursor = base;

      file_offset += map_size;      mmap_offset = 0;    }memcpy(cursor, data, 4096);    cursor += 4096;    mmap_offset += 4096;  }  UnMapRegion(base);

  close(data_fd);  close(batch_fd);}

使用 vmstat 来获取写入速度的数据, 整理如下:

这次避免了 mmap() 的开销,写入速度可以稳定保持在 2180 MB/S 左右,且没有 I/O 抖动.

内存使用也仅仅只有 18000KB, 大约 18M:

结论

下面是四种方式的写入速度对比:

在限制内存,且需要 kill -9 不丢数据的情况下,我们可以使用 mmap() 来模拟 Buffer IO,但为了避免频繁 mmap() 的开销,我们需要临时文件来做我们的内存映射. 这种方法可以保证我们的写入速度稳定且 kill -9 不至于丢失数据.

转载自:http://www.leviathan.vip

END -

「技术分享」某种程度上,是让作者和读者,不那么孤独的东西。欢迎关注我的微信公众号:「Kirito的技术分享」

byte数组转file不写入磁盘_Linux 环境写文件如何稳定跑满磁盘 I/O 带宽?相关推荐

  1. 压缩命令_Linux环境下文件压缩打包命令详解

    你好,我是goldsunC 让我们一起进步吧! 前言 我们知道,在面向对象的程序设计中,一切皆对象.而在Linux操作系统中,一切皆文件,因此我们总会跟文件打交道. Linux文件系统很庞大复杂,不过 ...

  2. java byte数组转化为流_java中字节数组和字符串、IO流和字节数组之间的转换

    在实际开发中经常会碰到乱码问题: 首先查看系统支持的编码方式,默认的编码方式 //检查java支持的编码 System.out.println(Charset.availableCharsets(). ...

  3. byte数组转byte数组 java_Java中文件与字节数组转换

    Java文件与字节数组转换 /** * 返回一个byte数组 * @param file * @return * @throws IOException */ private byte[] getBy ...

  4. java创建byte数组_java byte[]生成

    1. ByteArrayOutputStream extends OutputStream 提供了一个byte数组,和记录写入数组值个数的类. a.实现了write(int)这个抽象函数,这里默认只写 ...

  5. Java数字类型转byte数组

    文章目录 方法1 自己写 int转byte数组 byte数组转int 参考:https://blog.csdn.net/qq_41054313/article/details/88424454 方法2 ...

  6. linux扩展磁盘空间命令_在Linux上监视磁盘空间– SQL Server DBA的有用命令

    linux扩展磁盘空间命令 In this article, we will explore the different Linux commands related to the disk spac ...

  7. java创建byte数组_java.创建一个byte数组,long length = file.length(); byte[] bytes =

    导航:网站首页 > java.创建一个byte数组,long length = file.length(); byte[] bytes = 时间:2017-12-17 java.创建一个byte ...

  8. .dat文件写入byte类型数组_Go语言学习基础-读文件、写文件、行过滤器

    Reading File 读文件 读写文件是许多Go程序所需的基本任务.首先,我们将看一些读取文件的示例.读取文件需要检查是否出现调用错误. 最基本的文件读取任务是将文件的全部内容读到内存中iouti ...

  9. 读取Java文件到byte数组的三种方式及Java文件操作大全(包括文件加密,String加密)

    读取Java文件到byte数组的三种方式 package zs;import java.io.BufferedInputStream; import java.io.ByteArrayOutputSt ...

最新文章

  1. 2003:个人知识管理实施
  2. 2D简单图形相关算法罗列
  3. Kerberos策略的配置
  4. 计算TPCC值的例子
  5. matlab lpfilter.m,histroi/statmoments/lpfilter/dftuv的Matlab程序
  6. [mmdetection] - win10配置mmdetection(1.1和2.0) + 训练网络(faster-rcnn、mask-rcnn)
  7. React开发(134):ant design学习指南之form中getFieldValue
  8. Java 学习总结(187)—— 轻量级开源日志框架 tinylog 简介
  9. python程序员面试自我介绍_如何拿到面试公司一半Offer——我的Python求职之路
  10. linux环境安装jdk啊
  11. R中双表操作学习[转载]
  12. vim的学习笔记(3)
  13. Spring教程笔记8 基于SHH的员工信息管理系统
  14. Atitit btree 搜索原理 目录 第一节 左边小右边大 的有序树 1 第二节 平衡算法 1 第三节 层次高度一般3--4层 3 第四节 类似索引 3 第二章 Ref 5 第一节 左边小右
  15. excel熵值法计算权重_SPSSAU一众新功能上线:高级公式、综合得分一键计算
  16. 夜间环境人脸识别_古蔺县小区人脸识别门禁系统方案_点击了解
  17. 解析Google地图的Search接口返回的f.txt文件数据(其他类似)
  18. 《精进:如何成为一个很厉害的人》 采铜
  19. 变中求生 ——频繁变化的团队如何打造团队文化
  20. Day 112/200 Macbook Pro 电脑按键脱落怎么办?

热门文章

  1. Java线程池--拒绝策略RejectedExecutionHandler
  2. 百度html在线编辑器插件,百度编辑器UEditor插件DjangoUeditor v1.8.143
  3. mysql5.6最好的备份方案_Mysql 5.6迁移至PostgreSQL 9.6的实践小结
  4. percona+mysql插件_使用percona mysql插件来监控mysql5.7
  5. jq封装post请求数据_GitHub - xiaohange/JQHttpRequest: GET/POST / PUT / DELETE 网络请求的封装...
  6. C++ : 返回两个字符串的最长公共字符串
  7. IO-4(BufferedInputStream、BufferedInputStream、BufferedReader、BufferedWriter)
  8. 当前操作系统缺少黑体等字体_操作系统开发之——中断
  9. Cpp / Hash 所得字符串转成 Hex 字符串。
  10. matlab基数排序,c语言 数据结构 利用随机函数产生N个随机整数,对这些数进行多种方法进行排序...