記憶體映射處理大文件 運行環境: linux 實現語言: C++ 文件大小: 大於10G 1、為什麼要用記憶體映射 a、一般讀寫大文件操作會帶來較多的磁碟IO開銷 b、數據流一次性寫入大量數據到記憶體容易達到記憶體限制 c、效率問題 2、基本概念 2.1 記憶體映射 簡單定義: 一個文件到一塊記憶體的映射。 解 ...
記憶體映射處理大文件
- 運行環境:linux
- 實現語言:C++
- 文件大小:大於10G
1、為什麼要用記憶體映射
a、一般讀寫大文件操作會帶來較多的磁碟IO開銷
b、數據流一次性寫入大量數據到記憶體容易達到記憶體限制
c、效率問題
2、基本概念
2.1 記憶體映射
簡單定義:
一個文件到一塊記憶體的映射。
解釋:
1、物理記憶體(Physical memory):相對於虛擬記憶體而言的。物理記憶體指通過物理記憶體條而獲得的記憶體空間。
2、虛擬記憶體(Virtual memory):虛擬記憶體則是指將硬碟的一塊區域劃分來作為記憶體。其主要作用是在電腦運行時為操作系統和各種程式提供臨時儲存。
3、記憶體映射(Memory map):與虛擬記憶體類似。利用進程中與磁碟上的文件大小相同的邏輯記憶體進行映射,併進行定址訪問,其過程就如同對載入了文件的記憶體空間進行訪問。
3、方案
通過nmap(一種系統調用方法)將磁碟文件映射進記憶體。
3.1 實例:利用記憶體映射對爬蟲採集的html頁面數據進行過濾處理
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string.h>
#include <sys/time.h>
#include "header/commonForAll.h"
#include "header/splitHtmlFromNutch.h"
#include "header/memoryMapping.h"
#include "header/cParseIniFile.h"
using namespace std;
int htmlSplitNum = 0;
int viewMapSize = 0;
char* htmloutput;
char* outputFileCommon; //輸出文件公共文件名部分
string::size_type STR_FIND_RETURN; //查找字元串返回值
string DOCTYPE = "<!DOCTYPE html>"; //待查找起始字元串
string _HTML = "</html>"; //待查找結束字元串
bool IS_WRITING = false;
int main(int argc, char **argv) {
/********加入時間測試********/
struct timeval start;
struct timeval end;
unsigned long timer;
gettimeofday(&start,NULL);
/***********************讀取配置文件**********************/
/**************************開始************************/
CParseIniFile parseIniFile;
const string configPath = "/home/chaffee/work/projects/semantic_library/cpp/SplitHtmlFromNutch/splitHtmlFromNutch.ini";
//判斷配置文件是否存在
if(!CommonForAll::isExistFile((char*)configPath.c_str())){
cout << "該文件splitHtmlFromNutch.ini不存在" << endl;
return 0;
}
std::map<string, string> configContent;
std::map<string, string>::iterator iter;
const char* configSection = "main_config";
parseIniFile.ReadConfig(configPath, configContent, configSection);
/**************************結束************************/
htmloutput = (char*)configContent["htmloutput"].c_str();
outputFileCommon = (char*)configContent["outputfilecommon"].c_str();
htmlSplitNum = atoi(configContent["htmlsplitnum"].c_str());
viewMapSize = atoi(configContent["viewmapsize"].c_str());
SplitHtmlFromNutch shfromNuntch(configContent["htmlinput"].c_str(), htmlSplitNum);
if (!shfromNuntch.ISEXIST_INPUTFILE()) {
cout << "輸入文件不存在" << endl;
return 0;
}
//判斷輸出目錄是否存在
if(!CommonForAll::isExistDir(htmloutput)){
cout << "輸出目錄不存在" << endl;
return 0;
}
MemoryMapping memoryMap;
//設置記憶體映射分頁大小
memoryMap.setViewMapSize(viewMapSize);
//獲取有效的文件描述
int fd = CommonForAll::getFd((char*)shfromNuntch.HTMLPATH);
if(fd < 0){
cout << "輸入文件錯誤" << endl;
return 0;
}
//獲取文件大小
long fileLen = CommonForAll::getFileSize((char*)shfromNuntch.HTMLPATH);
if(fileLen <= 0){
cout << "輸入文件大小為0" << endl;
return 0;
}
long count = 0; //分頁計數
long int offset = 0; //分頁偏移量
ofstream ofstrFile;
int outFileCount = 0;
int htmlCount = 0;
/*********輸出文件路徑拼接**********/
char htmlOutputDir[512];
//相容輸出路徑是否帶'/'
if(!(htmloutput[strlen(htmloutput) - 1] == '/')){
sprintf(htmloutput, "%s%c", htmloutput, '/');
}
snprintf(htmlOutputDir, sizeof(htmlOutputDir), "%s%s%d%s", htmloutput, outputFileCommon, outFileCount, ".html");
ofstrFile.open(htmlOutputDir, ios::out);
//====================開始分頁映射操作=======================
while((fileLen - count * memoryMap.VIEWMAPSIZE) > 0){
memoryMap.doPaging(memoryMap.VIEWMAPSIZE, fd, offset);
const char* mmapBuf = memoryMap.memMap;
const char* mmapStart = memoryMap.memMap;
int len = 0;
while(mmapStart != NULL){
mmapStart = CommonForAll::_get_line(mmapBuf,&len);
string strLine(mmapBuf,len);
if(!strLine.empty()){
//按照一百個html頁面進行分割
//------------------start------------------
if (100 == htmlCount) {
outFileCount++;
//用snprintf代替sprintf,標明大小sizeof(htmlOutputDir),防止核心記憶體操作錯誤
snprintf(htmlOutputDir, sizeof(htmlOutputDir), "%s%s%d%s", htmloutput, outputFileCommon, outFileCount, ".html");
ofstrFile.flush();
ofstrFile.clear();
ofstrFile.close();
ofstrFile.open(htmlOutputDir);
htmlCount = 0;
}
if (IS_WRITING) {
STR_FIND_RETURN = strLine.find(_HTML);
if (STR_FIND_RETURN != string::npos) {
if (ofstrFile.is_open()) {
ofstrFile << strLine << endl;
}
IS_WRITING = false;
htmlCount++;
} else {
if (ofstrFile.is_open()) {
ofstrFile << strLine << endl;
}
}
} else {
STR_FIND_RETURN = strLine.find(DOCTYPE);
if (STR_FIND_RETURN != string::npos) {
IS_WRITING = true;
if (ofstrFile.is_open()) {
ofstrFile << strLine << endl;
}
}
}
}
//------------------end------------------
mmapBuf = mmapStart;
}
offset += memoryMap.VIEWMAPSIZE;
count++;
munmap(memoryMap.memMap, memoryMap.VIEWMAPSIZE);
msync(memoryMap.memMap, memoryMap.VIEWMAPSIZE, MS_SYNC);
memoryMap.memMap = NULL;
}
if(ofstrFile){
ofstrFile.flush();
ofstrFile.clear();
ofstrFile.close();
}
close(fd);
/********列印測試時間**********/
gettimeofday(&end,NULL);
timer = 1000000 * (end.tv_sec-start.tv_sec)+ end.tv_usec-start.tv_usec;
printf("程式用時 = %ld us\n",timer);
return 0;
}
核心映射類:
#include "header/memoryMapping.h"
using namespace std;
MemoryMapping::MemoryMapping()
: memMap(NULL)
, VIEWMAPSIZE(0)
{
}
MemoryMapping::~MemoryMapping() {
if (this->memMap)
{
munmap(this->memMap, this->VIEWMAPSIZE);
msync(this->memMap, this->VIEWMAPSIZE, MS_SYNC);
}
//cout << "記憶體映射析構方法" << endl;
}
void MemoryMapping::setViewMapSize(size_t length) {
this->VIEWMAPSIZE = length;
}
bool MemoryMapping::doPaging(size_t length, int fd, off_t offset) {
this->memMap = (char*)mmap(NULL, length, PROT_READ, MAP_SHARED, fd, offset);
if(this->memMap != NULL)
{
return true;
}else{
return false;
}
}
映射函數及參數解釋(引自百度百科):
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
start:映射區的開始地址,設置為0或者null時表示由系統決定映射區的起始地址。
length:映射區的長度。長度單位是以位元組(KB)為單位,不足一個記憶體頁按一個記憶體頁處理。
prot:期望的記憶體保護標誌,不能與文件的打開模式衝突。通過下列的單個或者利用or運算合理地組合(類似於linux的文件許可權系統)。
- PROT_EXEC //頁內容可以被執行。
- PROT_READ //頁內容可以被讀取。
- PROT_WRITE //頁可以被寫入。
- PROT_NONE //頁不可訪問。
flags:指定映射對象的類型,映射選項和映射頁是否可以共用。
- MAP_FIXED //使用指定的映射起始地址,如果由start和length參數指定的記憶體區重疊於現存的映射空間,重疊部分將會被丟棄。如果指定的起始地址不可用,操作將會失敗。並且起始地址必須落在頁的邊界上。
- MAP_SHARED //與其它所有映射這個對象的進程共用映射空間。對共用區的寫入,相當於輸出到文件。直到msync()或者munmap()被調用,文件實際上不會被更新。
- MAP_PRIVATE //建立一個寫入時拷貝的私有映射。記憶體區域的寫入不會影響到原文件。這個標誌和以上標誌是互斥的,只能使用其中一個。
- MAP_DENYWRITE //這個標誌被忽略。
- MAP_EXECUTABLE //同上。
- MAP_NORESERVE //不要為這個映射保留交換空間。當交換空間被保留,對映射區修改的可能會得到保證。當交換空間不被保留,同時記憶體不足,對映射區的修改會引起段違例信號。
- MAP_LOCKED //鎖定映射區的頁面,從而防止頁面被交換出記憶體。
- MAP_GROWSDOWN //用於堆棧,告訴內核VM系統,映射區可以向下擴展。
- MAP_ANONYMOUS //匿名映射,映射區不與任何文件關聯。
- MAP_ANON //MAP_ANONYMOUS的別稱,不再被使用。
- MAP_FILE //相容標誌,被忽略。
- MAP_32BIT //將映射區放在進程地址空間的低2GB,MAP_FIXED指定時會被忽略。當前這個標誌只在x86-64平臺上得到支持。
- MAP_POPULATE //為文件映射通過預讀的方式準備好頁表。隨後對映射區的訪問不會被頁違例阻塞。
- MAP_NONBLOCK //僅和MAP_POPULATE一起使用時才有意義。不執行預讀,只為已存在於記憶體中的頁面建立頁表入口。
fd:有效的文件描述詞。一般是由open()函數返回,其值也可以設置為-1,此時需要指定flags參數中的MAP_ANON,表明進行的是匿名映射。
off_toffset:被映射對象內容的偏移量。