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

背景

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

  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; // 4KBvoid 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 4096int 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` 标志以启用 DirectIOfd = 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.mzph.cn/news/608498.shtml

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

相关文章

【LLM】vLLM部署与int8量化

Acceleration & Quantization vLLM vLLM是一个开源的大型语言模型&#xff08;LLM&#xff09;推理和服务库&#xff0c;它通过一个名为PagedAttention的新型注意力算法来解决传统LLM在生产环境中部署时所遇到的高内存消耗和计算成本的挑战。PagedAttention算法能有效管理…

虾皮商品标题:如何创建有效的虾皮商品标题

虾皮&#xff08;Shopee&#xff09;平台是一个非常受欢迎的电商平台&#xff0c;为卖家提供了一个广阔的销售渠道。在虾皮上&#xff0c;一个有效的商品标题是吸引潜在买家注意力的关键元素之一。一个好的商品标题能够吸引更多的点击和浏览量&#xff0c;从而提高销售机会。下…

QT学习_20_一机一码加密授权

为保护自己辛苦写出的程序&#xff0c;规避白嫖。通常需要对可执行程序进行加密授权。网上主流的方法是给主程序套壳&#xff0c;但是破解软件网站都快要翻烂了&#xff0c;还是没有找到满足自己需求的套壳软件。索性还是自己写这个加密授权软件。 0、中心思想 让软件获取此电脑…

什么是API网关代理?

带有API网关的代理服务显着增强了用户体验和性能。特别是对于那些使用需要频繁创建和轮换代理的工具的人来说&#xff0c;使用 API 可以节省大量时间并提高效率。 了解API API&#xff08;即应用程序编程接口&#xff09;充当服务提供商和用户之间的连接网关。通过 API 连接&a…

【PostgreSQL】在DBeaver中实现序列、函数、视图、触发器设计

【PostgreSQL】在DBeaver中实现序列、函数、触发器、视图设计 基本配置一、序列1.1、序列使用1.1.1、设置字段为主键&#xff0c;数据类型默认整型1.1.2、自定义序列&#xff0c;数据类型自定义 1.2、序列延申1.2.1、理论1.2.2、测试1.2.3、小结 二、函数2.1、SQL直接创建2.1.1…

L1-007 念数字(Java)

题目 输入一个整数&#xff0c;输出每个数字对应的拼音。当整数为负数时&#xff0c;先输出fu字。十个数字对应的拼音如下&#xff1a; 0: ling 1: yi 2: er 3: san 4: si 5: wu 6: liu 7: qi 8: ba 9: jiu输入格式&#xff1a; 输入在一行中给出一个整数&#xff0c;如&…

React-路由进阶

一、路由的使用 1.声明式导航 在src/index.js文件中定义一个路由模式&#xff08;可选&#xff0c;也可以在具体的某个组件中使用Router&#xff09; import React from "react"; import ReactDOM from "react-dom";// 设置路由模式 import {HashRouter…

Python教程37:使用turtle画一个戴帽子的皮卡丘

---------------turtle源码集合--------------- Python教程36&#xff1a;海龟画图turtle写春联 Python源码35&#xff1a;海龟画图turtle画中国结 Python源码31&#xff1a;海龟画图turtle画七道彩虹 Python源码30&#xff1a;海龟画图turtle画紫色的小熊 Python源码29&a…

基于宝塔搭建Discuz!论坛

一、安装宝塔 我是在我的虚拟机上安装图的宝塔 虚拟机版本&#xff1a;Ubuntu 18.04 wget -O install.sh https://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh 6dca892c安装完成之后在浏览器输入你的地址 https://你的域名&#xff08;或…

【Java的导入导出Excel操作详细介绍】

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

etcd集群搭建

etcd构建自身高可用集群主要有三种形式&#xff1a; 静态发现: 预先已知etcd集群中有哪些节点&#xff0c;在启动时通过--initial-cluster参数直接指定好etcd的各个节点地址etcd动态发现: 静态配置前提是在搭建集群之前已经提前知道各节点的信息&#xff0c;而实际应用中可能存…

抽奖小程序

import random import keyboard import timedef make_choice():while True:choice_selected random.choices([乾, 坤, 坎, 离, 兑, 震, 巽, 艮])[0]print(choice_selected, end\r)# time.sleep(0.1)if keyboard.is_pressed(enter):return choice_selectedif __name__ __main_…

基于JavaWeb+BS架构+SpringBoot+Vue校园一卡通系统的设计和实现

基于JavaWebBS架构SpringBootVue校园一卡通系统的设计和实现 文末获取源码Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 文末获取源码 Lun文目录 第一章 概述 4 1.1 研究背景 4 1.2研究目的及意义 4 1.3国内外发展现状 4 1…

视觉SALM与激光SLAM的区别

前言&#xff1a; 这里比较一下视觉SLAM和激光SLAM的区别&#xff0c;仅比较其在算法层面上的一些不同&#xff0c;这里拿视觉SLAM算法&#xff1a;ORB-SLAM系列和激光SLAM算法&#xff1a;LOAM系列对比。 一&#xff1a;特征提取 1.ORB-SLAM&#xff08;视觉SLAM&#xff0…

什么是跨境电商独立站?

你是否有过这样的经历&#xff1a;当你在网上浏览一些商品时&#xff0c;发现有些网站的域名很奇怪&#xff0c;比如 .com、.net、.co等&#xff0c;而且网站的界面和设计也和国内的电商平台不太一样。 你可能会好奇&#xff0c;这些网站是什么&#xff1f;它们是怎么做的&…

网络安全之你的浏览器记录真的安全吗?

密码是每个人最私密的东西&#xff0c;轻易是不会展示给他人的&#xff0c;那么我如何能知道你电脑上浏览器里保存的密码呢&#xff1f;浏览器是大家在网上冲浪最常用的软件&#xff0c;在登录一些网站填写账号密码后&#xff0c;浏览器为了方便大家使用&#xff0c;会提示是否…

2.4G SOC无线收发芯片:XL2401D 产品介绍

XL2401D芯片是工作在2.400~2.483GHz世界通用ISM频段,集成微控制器的的SOC无线收发芯片。芯片集成射频收发机、频率收生器、晶体振荡器、调制解调器等功能模块&#xff0c;并且支持对多组网和带 ACK的通信模式。发射输出功率、工作频道以及通信数据率均可配置。芯片内含以EPROM作…

unity小程序websocket:nginx配置https (wss)转http (ws)及其他问题解决

目录 前言 实际运用场景 处理流程如下 nginx配置ssl和wss 配置过程中遇到的问题 1、无法连接服务器 2、通过IP可以访问&#xff0c;域名却不行 问题描述 解决 3、如何判断该域名是否备案了 前言 为了服务器网络的通用性&#xff0c;我们在实现移动端的游戏转微信小程序…

Python教程38:使用turtle画动态粒子爱心+文字爱心

Turtle库是Python语言中的一个标准库&#xff0c;它提供了一种有趣的方式来介绍编程和图形绘制的基本概念。Turtle库使用一个虚拟的“海龟”来绘制图形。你可以控制海龟的方向、速度和位置&#xff0c;通过向前移动、向左转或向右转等命令来绘制线条、圆弧多边形等图形。 -----…

Java 反射(一)

反射 1.反射的介绍 1.反射机制允话程序在执行期间借助于Refelction API取得任何类的信息&#xff08;比如成员变量&#xff0c;构造器&#xff0c;成员方法等&#xff09;并能操作对象的属性及方法&#xff0c;反射在设计模式和框架底层都会用到 2.加载完类之后&#xff0c;在…