網(wǎng)站首頁 編程語言 正文
如果現(xiàn)在有一個需求,我們需要處理一個20G的大文件,我們會怎么處理呢?思考下,我們需要怎么實現(xiàn)這個功能。
我們可能會這么實現(xiàn):
def get_datas():
source_text_path = "路徑"
with open(source_text_path, 'rb') as f:
data = f.readlines()
yield data
if __name__ == '__main__':
for e in get_datas():
deal_data(e) # 處理數(shù)據(jù)
這樣雖然能實現(xiàn),但是我們處理的時候需要消耗的資源和性能不是很友好,所以我們要優(yōu)化,也就是使用mmap模塊。
mmap是一種虛擬內存映射文件的方法,即將一個文件或者其它對象映射到進程的地址空間,實現(xiàn)文件磁盤地址和進程虛擬地址空間中一段虛擬地址的一一映射關系。它省掉了內核態(tài)和用戶態(tài)頁copy這個動作(兩態(tài)間copy),直接將用戶態(tài)的虛擬地址與內核態(tài)空間進行映射,進程直接讀取內核空間,速度提高了,內存占用也少了。
簡單點來說,mmap函數(shù)實現(xiàn)的是內存共享。內存共享是兩個不同的進程共享內存的意思:同一塊物理內存被映射到兩個進程的各自的進程地址空間。這個物理內存已經(jīng)被規(guī)定了大小(大小一定要比實際寫入的東東大)以及名稱。當需要寫入時,找到內存名稱,然后寫入內存,等需要讀取時候, 首先要知道你要讀取多大(因為物理內存比你要讀取的東西大,全部讀取的話會讀到一些“空”的東西),然后尋找對應名稱的物理塊,然后讀取。
mmap 介紹
Windows
mmap.mmap(fileno, length, tagname=None, access=ACCESS_DEFAULT[, offset])
參數(shù)說明:
fileno:文件描述符,可以是file對象的fileno()方法,或者來自os.open(),在調用mmap()之前打開文件,不再需要文件時要關閉。
length:要映射文件部分的大小(以字節(jié)為單位),這個值為0,則映射整個文件,如果大小大于文件當前大小,則擴展這個文件。
tagname:為映射提供標簽名稱的字符串,Windows 允許你對同一文件擁有許多不同的映射。如果指定現(xiàn)有標簽的名稱,則會打開該標簽,否則將創(chuàng)建該名稱的新標簽。如果省略此參數(shù)或設置為None ,則創(chuàng)建的映射不帶名稱。避免使用tag參數(shù)將有助于使代碼在Unix和Windows之間可移植。
access:文件權限
ACCESS_READ:讀訪問;
ACCESS_WRITE:寫訪問,默認;
ACCESS_COPY:拷貝訪問,不會把更改寫入到文件,使用flush把更改寫到文件。
offset:非負整數(shù)偏移量,默認從0開始。
Unix
mmap.mmap(fileno, length, flags=MAP_SHARED, prot=PROT_WRITE|PROT_READ, access=ACCESS_DEFAULT[, offset])
參數(shù)說明:
- flags:映射的性質,默認MAP_SHARED。
- MAP_PRIVATE 會創(chuàng)建私有的寫入時拷貝映射,因此對mmap對象內容的修改將為該進程所私有;
- MAP_SHARED 會創(chuàng)建與其他映射同一文件區(qū)域的進程所共享的映射。
- prot:它將給出所需的內存保護方式;最有用的兩個值是 PROT_READ和 PROT_WRITE,分別指明頁面為可讀或可寫。 prot 默認為PROT_READ | PROT_WRITE。
- access:注意的是可以指定 access 作為替代 flags 和 prot 的可選關鍵字形參。 同時指定 flags,prot 和 access 將導致錯誤。
fileno文件描述符有如下
- os.O_RDONLY:以只讀的方式打開Readonly
- os.O_WRONLY:以只寫的方式打開Write only
- os.O_RDWR:以讀寫的方式打開 Read and write
- os.O_APPEND:以追加的方式打開
- os.O_CREAT:創(chuàng)建并打開一個新文件
- os.O_EXCL:os.O_CREAT| os.O_EXCL 如果指定的文件存在,返回錯誤
- os.O_TRUNC:打開一個文件并截斷它的長度為零(必須有寫權限)
- os.O_BINARY:以二進制模式打開文件(不轉換)
- os.O_NOINHERIT:阻止創(chuàng)建一個共享的文件描述符
- os.O_SHORT_LIVED
- os.O_TEMPORARY:與O_CREAT一起創(chuàng)建臨時文件
- os.O_RANDOM:緩存優(yōu)化,但不限制從磁盤中隨機存取
- os.O_SEQUENTIAL :緩存優(yōu)化,但不限制從磁盤中序列存取
- os.O_TEXT:以文本的模式打開文件(轉換)
支持的方法
- close(): 關閉 mmap。 后續(xù)調用該對象的其他方法將導致引發(fā) ValueError 異常。 此方法將不會關閉打開的文件。
- closed: 如果文件已關閉則返回 True。
- find(str, start, end): 從 start 下標開始,在 m中從左往右尋找子串 str最早出現(xiàn)的下標;沒有找到則返回-1。
- flush([offset, n]):將對文件的內存副本的修改刷新至磁盤。 如果不使用此調用則無法保證在對象被銷毀前將修改寫回存儲。 如果指定了 offset和 size,則只將對指定范圍內字節(jié)的修改刷新至磁盤;在其他情況下,映射的全部范圍都會被刷新。
- windows: 返回的非零值表示成功;否則返回0。 零表示失敗。
- unix: 返回零值以表示成功。 當調用失敗時將引發(fā)異常。
- move(dest, src, count): 將從偏移量 src開始的 count個字節(jié)拷貝到目標索引號 dest。 如果 mmap 創(chuàng)建時設置了 ACCESS_READ,則調用 move將引發(fā)異常。
- read([n]): 返回一個字節(jié),其中包含從當前文件位置開始的至多 n 個字節(jié)。 如果參數(shù)省略,為 None 或負數(shù),則返回從當前文件位置開始直至映射結尾的所有字節(jié)。 文件位置會被更新為返回字節(jié)數(shù)據(jù)之后的位置
- read_byte():返回一個1字節(jié)長的字符串,從 m 對應的文件中讀1個字節(jié),要是已經(jīng)到了EOF還調用 read_byte(),則拋出異常 ValueError。
- readline():返回一個字符串,從 m 對應文件的當前位置到下一個’\n’,當調用 readline() 時文件位于 EOF,則返回空字符串。
- resize(newsize):如果存在的話, 改變映射以及下層文件的大小。 如果 mmap 創(chuàng)建時設置了 ACCESS_READ或 ACCESS_COPY,則改變映射大小將引發(fā)異常。
- rfind(sub[, start[, end]]):返回子序列 sub在對象內被找到的最大索引號,使得 sub 被包含在 [start, end] 范圍中。 可選參數(shù) start和 end 會被解讀為切片表示法。 如果未找到則返回 -1。
- seek(pos[, whence]):設置文件的當前位置。 whence 參數(shù)為可選項并且默認為 os.SEEK_SET 或 0 (絕對文件定位);其他值還有 os.SEEK_CUR 或 1 (相對當前位置查找) 和 os.SEEK_END 或 2 (相對文件末尾查找)。
- size():返回文件的長度,該數(shù)值可以大于內存映射區(qū)域的大小。
- tell():返回文件指針的當前位置。
- write(str):將str寫入文件指針當前位置的內存并返回寫入的字節(jié)總數(shù) (一定不小于 len(str),因為如果寫入失敗,將會引發(fā)錯誤)。 在字節(jié)數(shù)據(jù)被寫入后文件位置將會更新。 如果 mmap 創(chuàng)建時設置了 ACCESS_READ,則向其寫入將引發(fā) 異常
- write_byte(byte):將整數(shù)值 byte 寫入文件指針當前位置的內存;文件位置前進 1。 如果 mmap 創(chuàng)建時設置了 ACCESS_READ,則向其寫入將引發(fā)異常。
對于EOF的處理,write() 和 read_byte() 拋出異常 ValueError,而 write_byte() 和 read() 什么都不做。
使用mmap讀取大文件
from mmap import mmap
def read_data(file_path):
with open(file_path, "r+") as f:
m = mmap(f.fileno(), 0)
g_index = 0
for index, char in enumerate(m):
if char == b"\n":
yield m[g_index:index + 1].decode()
g_index = index + 1
if __name__ == "__main__":
file_path = ""
for content in read_data(file_path):
print(content)
什么時候用mmap?
用mmap來讀取超大文件,不是mmap的主要應用場景,Python官方文件也沒有提到這一點。如果僅僅是讀取超大文件,使用文件對象的read(N),來得更快更好更簡單。
關于標準庫中的mmap模塊。現(xiàn)有一個需求,要對超大文件(接近40G)進行讀寫,notepad++等工具直接拒絕打開此文件。用 r+ 模式打開文件,可以隨意讀寫,但是要特別小心。readline()是否能夠使用,要看這個文件每行都多長,如果沒有換行,就不能用,就算知道每行的大小,也要帶個參數(shù)N來控制最大讀取數(shù)量。readlines()是肯定不能用的,就算帶參數(shù),也可能直接卡死!read(N)沒問題,主要控制是N的大小。
總之,傳統(tǒng)讀寫文件的方式可以用,但是不夠方便。速度也是個問題,傳統(tǒng)的緩存IO方式,涉及到OS內核態(tài)的內存和進程虛擬空間內存的內容交換,對于超大文件而言,這種交換會浪費大量的CPU時間和內存。mmap是另一個方式!它省掉了內核態(tài)和用戶態(tài)頁拷貝這個動作(兩態(tài)間copy),直接將用戶態(tài)的虛擬地址與內核態(tài)空間進行映射,進程直接讀取內核空間,速度提高了,內存占用也少了。
總結來說,常規(guī)文件操作為了提高讀寫效率和保護磁盤,使用了頁緩存機制。這樣造成讀文件時需要先將文件頁從磁盤拷貝到頁緩存中,由于頁緩存處在內核空間,不能被用戶進程直接尋址,所以還需要將頁緩存中數(shù)據(jù)頁再次拷貝到內存對應的用戶空間中。這樣,通過了兩次數(shù)據(jù)拷貝過程,才能完成進程對文件內容的獲取任務。寫操作也是一樣,待寫入的buffer在內核空間不能直接訪問,必須要先拷貝至內核空間對應的主存,再寫回磁盤中(延遲寫回),也是需要兩次數(shù)據(jù)拷貝。
而使用mmap操作文件中,創(chuàng)建新的虛擬內存區(qū)域和建立文件磁盤地址和虛擬內存區(qū)域映射這兩步,沒有任何文件拷貝操作。而之后訪問數(shù)據(jù)時發(fā)現(xiàn)內存中并無數(shù)據(jù)而發(fā)起的缺頁異常過程,可以通過已經(jīng)建立好的映射關系,只使用一次數(shù)據(jù)拷貝,就從磁盤中將數(shù)據(jù)傳入內存的用戶空間中,供進程使用。
總而言之,常規(guī)文件操作需要從磁盤到頁緩存再到用戶主存的兩次數(shù)據(jù)拷貝。而mmap操控文件,只需要從磁盤到用戶主存的一次數(shù)據(jù)拷貝過程。說白了,mmap的關鍵點是實現(xiàn)了用戶空間和內核空間的數(shù)據(jù)直接交互而省去了空間不同數(shù)據(jù)不通的繁瑣過程。因此在某些場景下,mmap效率更高。
從python官網(wǎng)上看mmap的介紹,生成的mmap對象,就像一個bytearray對象,可以直接用index的方式讀寫,可以切片。同時,mmap對象還有一組類似文件操作的接口,read,readline,flush等等。即mmap對象兼具bytearray和file對象的功能。不過還是要注意,對于超大文件的讀(先不考慮寫的問題吧),從磁盤到內核,依然會占用內存,因此絕對不能一口氣全部讀出來。read(N)是必須的,mmap的使用只是可能會提高效率。(如果頻繁的創(chuàng)建和關閉mmap映射,這種創(chuàng)建是為了指向超大文件的不同位置,反而效率更低。一般情況下的read(N)實現(xiàn),不需要使用mmap。)
mmap的另一個應用場景,是進程間的內存共享。多個進程將同一個文件map到同一段內核地址上,即實現(xiàn)了相互之間的共同訪問。
總結:使用mmap的時機
- 將一個普通文件映射到內存中,通常在需要對文件進行頻繁讀寫時使用,這樣用內存映射讀寫取代I/O緩存讀寫,以獲得較高的性能;
- 將特殊文件進行匿名內存映射,可以為關聯(lián)進程提供共享內存空間;
- 為無關聯(lián)的進程提供共享內存空間,一般也是將一個普通文件映射到內存中。
原文鏈接:https://blog.csdn.net/weixin_41951954/article/details/128837722
相關推薦
- 2022-11-09 ORACLE中常用的幾種正則表達式小結_oracle
- 2023-01-12 C語言技巧提升之回調函數(shù)的掌握_C 語言
- 2022-04-25 一篇文章帶你了解C語言的文件操作_C 語言
- 2022-06-02 redis?sentinel監(jiān)控高可用集群實現(xiàn)的配置步驟_Redis
- 2022-09-16 Pandas數(shù)據(jù)形狀df.shape的實現(xiàn)_python
- 2022-11-12 詳解Golang如何實現(xiàn)支持隨機刪除元素的堆_Golang
- 2022-03-22 .NET?6開發(fā)TodoList實現(xiàn)請求日志組件HttpLogging_實用技巧
- 2023-02-07 Pytorch中torch.argmax()函數(shù)使用及說明_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結構-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支