对比fwrite、mmap、DirectIO 的内存、性能开销,剖析 Page Cache

news2024/12/25 12:52:49

背景

如上图所示:应用程序写文件有三种形式。

  1. fwrite : 应用程序 -> fwrite(Buffered IO) -> File System -> Page Cache -> Block IO Layer -> Device & Disk etc.
  2. mmap : 应用程序 -> mmap -> Page Cache -> Block IO Layer -> Device & Disk etc.
  3. Direct IO : 应用程序 -> Block IO Layer -> Device & Disk etc.

Direct IO优点:使用Direct I/O时,数据直接在应用程序和存储设备之间传输,绕过了操作系统的缓存机制。这样可以提高数据传输的效率

Direct IO缺点:其中一个限制是,Direct I/O要求数据缓冲区必须对齐到存储设备的块大小,否则会导致性能下降或者出现错误。

为了解决这个问题,通常需要使用一块内存作为中转缓冲区,将应用程序的数据先复制到中转缓冲区中,然后再使用Direct I/O将数据从中转缓冲区传输到存储设备。这样可以保证数据缓冲区对齐,并且可以避免在应用程序和存储设备之间频繁地复制数据。

这块中转缓冲区通常被称为“heap”,因为它是从堆内存中分配的。在使用Direct I/O时,heap的大小通常需要根据存储设备的块大小和应用程序的需求来确定。

测试用例

#include <iostream>
#include <fstream>
#include <chrono>
#include <cstring>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>

using namespace std;

const int FILE_SIZE = 1024 * 1024 * 1024; // 1GB
const int BLOCK_SIZE = 4096; // 4KB

void test_fwrite() {
    ofstream ofs("test_fwrite.bin", ios::binary);
    char* buffer = new char[BLOCK_SIZE];
    auto start = chrono::high_resolution_clock::now();
    for (int i = 0; i < FILE_SIZE / BLOCK_SIZE; i++) {
        ofs.write(buffer, BLOCK_SIZE);
    }
    auto end = chrono::high_resolution_clock::now();
    cout << "fwrite time: " << chrono::duration_cast<chrono::milliseconds>(end - start).count() << "ms" << endl;
    delete[] buffer;
}

void test_mmap() {
    int fd = open("test_mmap.bin", O_RDWR | O_CREAT, 0666);
    ftruncate(fd, FILE_SIZE);
    char* buffer = (char*) mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    auto start = chrono::high_resolution_clock::now();
    memset(buffer, 0, FILE_SIZE);
    auto end = chrono::high_resolution_clock::now();
    cout << "mmap time: " << chrono::duration_cast<chrono::milliseconds>(end - start).count() << "ms" << endl;
    munmap(buffer, FILE_SIZE);
    close(fd);
}

void test_directio() {
    int fd = open("test_directio.bin", O_RDWR | O_CREAT | O_DIRECT, 0666);
    char* buffer = new char[BLOCK_SIZE];
    char* heap_buffer = new char[FILE_SIZE];
    auto start = chrono::high_resolution_clock::now();
    for (int i = 0; i < FILE_SIZE / BLOCK_SIZE; i++) {
        write(fd, buffer, BLOCK_SIZE);
    }
    auto end = chrono::high_resolution_clock::now();
    cout << "directIO time: " << chrono::duration_cast<chrono::milliseconds>(end - start).count() << "ms" << endl;
    delete[] buffer;
    delete[] heap_buffer;
    close(fd);
}

int main() {

    test_fwrite();
    test_mmap();
    test_directio();
    
    return 0;
}
  1. 这个 demo 分别测试了 fwrite、mmap 和 DirectIO 的性能,其中 fwrite 和 mmap 都是直接写入文件,而 DirectIO 则需要使用 heap_buffer 作为内存中转。
  2. 在测试 DirectIO 时,我们使用了 O_DIRECT 标志来打开文件,这会禁用文件系统缓存,从而确保数据直接从内存写入磁盘。但是,由于 DirectIO 要求内存对齐,因此我们需要使用 heap_buffer 来确保内存对齐。这个 heap_buffer 的大小与文件大小相同。

运行这个 demo,我们可以得到以下输出:

wj@wj:~/WORK/Learning/Learning/DirectIO$ ./test.out 
fwrite time: 649ms
mmap time: 267ms
directIO time: 1191ms
wj@wj:~/WORK/Learning/Learning/DirectIO$

实验结论

从输出可以看出,mmap 的性能远远优于 fwrite,而 DirectIO 的性能比 fwrite 差一些。

这表明,mmap 可以映射文件到内存中,从而避免了数据的复制,因此性能更高。

而 DirectIO 需要使用 heap_buffer 作为内存中转,因此存在一定的内存开销,目前来看这个开销还是非常大的,不能忽略不计。

DirectIO的内存开销

DirectIO 的内存开销主要取决于两个因素:每个请求的大小和并发请求数量。由于 DirectIO 使用 heap_buffer 作为内存中转,因此每个请求都需要分配一定大小的内存。并且,由于 DirectIO 是异步的,因此在高并发情况下,可能会有大量的请求同时进行,从而导致内存开销增加。

具体来说,假设每个请求的大小为 `request_size`,并发请求数量为 `concurrency`,则 DirectIO 的内存开销可以估算为: ``` memory_overhead = request_size * concurrency ```

例如,如果每个请求的大小为 4KB,同时有 1000 个请求在进行,则 DirectIO 的内存开销大约为 4MB。

需要注意的是,这只是一个粗略的估算,实际的内存开销可能会受到其他因素的影响,例如操作系统的内存管理策略、硬件配置等。

实验demo

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

#define BUF_SIZE 4096

int main(int argc, char *argv[]) {
    int fd;
    char *buf;
    struct stat st;
    off_t offset = 0;
    ssize_t nread;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s <file>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    //使用 `open` 函数打开一个文件,并指定 `O_DIRECT` 标志以启用 DirectIO
    fd = open(argv[1], O_RDONLY | O_DIRECT);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    //使用 `fstat` 函数获取文件的信息,包括块大小
    if (fstat(fd, &st) == -1) {
        perror("fstat");
        exit(EXIT_FAILURE);
    }

    //使用 `aligned_alloc` 函数分配一个对齐到块大小的缓冲区
    buf = (char*)aligned_alloc(st.st_blksize, BUF_SIZE);
    if (buf == NULL) {
        perror("aligned_alloc");
        exit(EXIT_FAILURE);
    }

    //使用 `pread` 函数读取文件,并在读取过程中计算内存开销
    while ((nread = pread(fd, buf, BUF_SIZE, offset)) > 0) {
        offset += nread;
    }

    if (nread == -1) {
        perror("pread");
        exit(EXIT_FAILURE);
    }

    printf("offset : %ld\n",offset);

    free(buf);
    close(fd);

    return 0;
}

wj@wj:~/WORK/Learning/Learning/DirectIO$ g++ directIO.cpp -o directIO.out
wj@wj:~/WORK/Learning/Learning/DirectIO$ ./directIO.out test_mmap.bin
offset : 1073741824
wj@wj:~/WORK/Learning/Learning/DirectIO$ 

注意,该程序只是一个简单的示例,实际的内存开销可能会更复杂,需要根据具体情况进行测试和分析。

fwrite 和 mmap 都需要经过 Page Cache,再到 Block IO Layer,那么Linux系统为什么要这样设计呢?

什么是Page Cache?

思考一个问题:Page Cache 到底是属于内核空间还是属于用户空间呢?

参考:如何用数据观测Page Cache?_如何查看page cache-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1369997.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NNDL总结

第四章 前馈神经网络 4.1 神经元 人工神经元&#xff0c;简称神经元&#xff0c;是构成神经网络的基本单元。 当>0时&#xff0c;为1&#xff0c;兴奋&#xff1b; 当<0时&#xff0c;为0&#xff0c;抑制。 激活函数的性质 1、连续可导的非线性函数。 2、激活函数及其导…

Java中的输入输出处理(一)

文件 文件&#xff1a;文件是放在一起的数据的集合。比如1.TXT。 存储地方&#xff1a;文件一般存储在硬盘&#xff0c;CD里比如D盘 如何访问文件属性&#xff1a;我们可以通过java.io.File类对其处理 File类 常用方法&#xff1a; 方法名称说明boolean exists()判断文件或目…

mysql原理--事务

1.事务的起源 对于大部分程序员来说&#xff0c;他们的任务就是把现实世界的业务场景映射到数据库世界。比如银行为了存储人们的账户信息会建立一个 account 表&#xff1a; CREATE TABLE account (id INT NOT NULL AUTO_INCREMENT COMMENT 自增id,name VARCHAR(100) COMMENT …

这些开源自动化测试框架,会用等于白嫖一个w

作者&#xff1a;黑马测试 链接&#xff1a;https://www.zhihu.com/question/19923336/answer/2585952461 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 随着计算机技术人员的大量增加&#xff0c;通过编写代码来…

C#上位机快速开发笔记

1.下载vs2012版本&#xff0c;安装密钥解除 #微信文章教程有详细描述&#xff0c;公众号“软件安装管家目录” 2.新建工程 3.界面模块介绍 3.资源管理器 3.1 图标修改位置 3.2 软件版本信息文件介绍 4.常用工具箱控件 1. Button按钮 #按键函数代码&#xff0c;按下按…

【Python排序算法系列】—— 希尔排序

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 &#x1f4ab;个人格言:"没有罗马,那就自己创造罗马~" 目录 希尔排序 &#xff08;ShellSort&#xff09; 由来和特点 理解 过程演示 Step1&#xff1a;…

GO语言笔记3-指针

指针的概念 先看一段代码的输出 package main import "fmt" func main(){ var age int 18fmt.Println("age的内存地址值是:",&age)//age的内存地址值是: 0xc000012090// 定义一个指针变量// *int 是一个指针类型&#xff0c;可以理解为指向int类型的…

Native组件Widget

demo下载路径 gitgithub.com:haijun-suyan/ReminderWidget.git 注意&#xff1a; 组件开发 SwiftUI 添加链接描述

鼠标随动指定区域高亮显示(Excel聚光灯)

实例需求&#xff1a;工作表中数据表实现跟随鼠标选中高亮效果&#xff0c;需要注意如下几个细节需求 数据表为连续区域&#xff0c;但是不一定从A1单元格开始数据表的前两行&#xff08;标题行&#xff09;不使用高亮效果数据表中已经应用了条件格式&#xff0c;高亮显示取消…

0109作业

1> 思维导图 2> 使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin&quo…

静态网页设计——多彩贵州(HTML+CSS+JavaScript)(dw、sublime Text、webstorm、HBuilder X)

前言 声明&#xff1a;该文章只是做技术分享&#xff0c;若侵权请联系我删除。&#xff01;&#xff01; 感谢大佬的视频&#xff1a;https://www.bilibili.com/video/BV1cK411v7R2/?vd_source5f425e0074a7f92921f53ab87712357b 源码&#xff1a;https://space.bilibili.com…

【漏洞复现】锐捷RG-UAC统一上网行为管理系统信息泄露漏洞

Nx01 产品简介 锐捷网络成立于2000年1月&#xff0c;原名实达网络&#xff0c;2003年更名&#xff0c;自成立以来&#xff0c;一直扎根行业&#xff0c;深入场景进行解决方案设计和创新&#xff0c;并利用云计算、SDN、移动互联、大数据、物联网、AI等新技术为各行业用户提供场…

ROS2 Humble学习笔记

本文发表与个人的github pages。部分内容未同步到这里。 想查看完整内容&#xff0c;请移步到ROS2 Humble学习笔记。 一、前言 2013年的时候已经接触ROS了&#xff0c;当时断断续续学习了一些ROS的基础知识。16年搬到深圳之后&#xff0c;也有幸参加过星火的一次关于ROS的一些…

EI级 | Matlab实现VMD-TCN-GRU变分模态分解结合时间卷积门控循环单元多变量光伏功率时间序列预测

EI级 | Matlab实现VMD-TCN-GRU变分模态分解结合时间卷积门控循环单元多变量光伏功率时间序列预测 目录 EI级 | Matlab实现VMD-TCN-GRU变分模态分解结合时间卷积门控循环单元多变量光伏功率时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.【EI级】Matlab实现…

三甲医院ADR智能监测系统源码,药品不良反应智能监测系统全套源码,java语言,自主研发

ADR智能监测系统源码&#xff0c;药品不良反应智能监测系统全套商业项目源码&#xff0c;自主版权 ADR监测上报系统是基于医院临床数据中心而建立&#xff0c;运用信息技术实现药品不良反应的智能监测、报告管理、知识库查询、统计分析等功能。 系统自动提取不良反应报告数据&…

灵活轻巧的java接口自动化测试实战

前言 无论是自动化测试还是自动化部署&#xff0c;撸码肯定少不了&#xff0c;所以下面的基于java语言的接口自动化测试&#xff0c;要想在业务上实现接口自动化&#xff0c;前提是要有一定的java基础。 如果没有java基础&#xff0c;也没关系。这里小编也为大家提供了一套jav…

手撕 PCA

PCA&#xff08;Principal Component Analysis&#xff09;&#xff0c;中文名称&#xff1a;主成分分析。迄今为止最流行的降维算法。 假设 n 维空间中的一个单位立方体&#xff0c;易知&#xff1a;一维空间中该立方体中任意两点的距离不超过 1 1 1&#xff0c;二维空间中该…

【MySQL函数】掌握这些常用函数,让你的数据库操作如虎添翼!

目录 强制走索引 字符串函数 通配符 CONCAT&#xff1a;连接两个或多个字符串 LENGTH&#xff1a;返回字符串的长度 LOWER&#xff1a;将字符串转换为小写 UPPER&#xff1a;将字符串转换为大写 TRIM&#xff1a;删除字符串开头和结尾的空格 字符串转化为number 替换…

24/01/09 qt work

1. 使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;密码是…

doris部署

doris-2.0.1.1部署安装 一、下载doris安装包二、解压到/data下&#xff0c;修改名称三、修改fe配置文件四、启动doris-fe五、验证doris-fe六、修改be配置文件七、启动doris-be八、mysql中连接be&#xff0c;在Doris中添加后端节点九、设置密码 一、下载doris安装包 wget https…