欢迎访问 生活随笔!

尊龙游戏旗舰厅官网

当前位置: 尊龙游戏旗舰厅官网 > 运维知识 > linux >内容正文

linux

byte数组转file不写入磁盘-尊龙游戏旗舰厅官网

发布时间:2024/10/8 linux 0 豆豆
尊龙游戏旗舰厅官网 收集整理的这篇文章主要介绍了 byte数组转file不写入磁盘_linux 环境写文件如何稳定跑满磁盘 i/o 带宽? 小编觉得挺不错的,现在分享给大家,帮大家做个参考.
  • 准备

    • 要求

    • 机器配置

    • 测试磁盘 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(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(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 带宽?的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得尊龙游戏旗舰厅官网网站内容还不错,欢迎将尊龙游戏旗舰厅官网推荐给好友。

网站地图