内存映射分块,处理大文件效率并没有提高,为何????

s182838ss 2016-10-25 09:25:29
加精
本来想用内存映射分块技术处理海量数据的,都说这个有优势

问题描述:
先用内存映射技术处理200M和800M的数据,速度和普通I/O比确实提高了很多,但是处理3G的文件时,效率直线下降.
我的思路是把3G的文件映射分块,内次MapViewOfFile800M,读完后UnmapViewOfFile,然后继续下一块,直到处理完整个文件。

在网上查了资料说是UnmapViewOfFile内存并没有被真实回收,导致内存被占满了,效率下降.
代码和下面这个帖子代码类似,问题也相似,去过我描述的不清楚,可以参考下面这个帖子
http://bbs.csdn.net/topics/390317410?locationNum=13
...全文
6010 点赞 收藏 49
写回复
49 条回复
切换为时间正序
当前发帖距今超过3年,不再开放新的回复
发表回复
中级伴读 2018-10-18
在你的while循环之前,用VirtualFree申请一块你想用的内存,再用MapViewOfFileEx函数,把之前申请到的内存作为第5个参数传进去,这样每次映射都会映射到固定的内存起始位置,就不会存在你说的内存占用越来越多的情况了
回复
BeanJoy 2016-11-07
引用 47 楼 WindsonZhL 的回复:
以前做过一段时间的数据备份,当时实验过硬盘读写4GB大文件一次,耗时约1分钟(只多不少)。 是直接ReadFile加WriteFile。CreateFile倒数第二个参数(flag)除了可指定文件属性之外,不要加任何附加flag。 用了一个总大小32MB的cluster链表,每个cluster大小为16kB,共2048个。写是单独的一个线程。 当时也是调查效率问题,发现是大文件时cluster很快用光,因写慢于读,读会等待写完后回收cluster。 但这里代码有bug,演变成了回收一个cluster从文件中读一个cluster大小的数据,读写次数大幅增加, 所以就慢了。 32MB是一个比较合适的值,在读写次数和单次读写长度之间需要找好均衡点。另外文件在磁盘上是按簇存储的, 实际存储大小一定是的簇大小的整数倍,这也是设定缓存大小时需要考虑的。
你这程序可以改下,每次读写16KB数据量太小了,要知道,程序中大部分的耗时都在硬盘IO上,要读取32MB的数据,得IO中断2018次,这效率实在是慢。你不妨试试把cluster的大小改为4MB再试试,当然cluster的个数也要减少,用不着2048个,10个足矣。 链表的用意在于读写线程分离,写线程写完当前链表的的时候,可以从直接从其他有数据的链表获取继续写,而不用等读数据完毕才继续写。同样,读线程只需负责从空链表获取然后读数据过时去,不必等写完才读取。两个线程同步工作,保证写完可以继续取到有数据的链表就行。当然,如果读慢于写,那么就要保证读完有空的链表继续读就行。主要目的就是保证慢的操作要连续进行。
回复
以前做过一段时间的数据备份,当时实验过硬盘读写4GB大文件一次,耗时约1分钟(只多不少)。 是直接ReadFile加WriteFile。CreateFile倒数第二个参数(flag)除了可指定文件属性之外,不要加任何附加flag。 用了一个总大小32MB的cluster链表,每个cluster大小为16kB,共2048个。写是单独的一个线程。 当时也是调查效率问题,发现是大文件时cluster很快用光,因写慢于读,读会等待写完后回收cluster。 但这里代码有bug,演变成了回收一个cluster从文件中读一个cluster大小的数据,读写次数大幅增加, 所以就慢了。 32MB是一个比较合适的值,在读写次数和单次读写长度之间需要找好均衡点。另外文件在磁盘上是按簇存储的, 实际存储大小一定是的簇大小的整数倍,这也是设定缓存大小时需要考虑的。
回复
赵4老师 2016-11-04
37楼正解!
回复
s182838ss 2016-11-04
引用 40 楼 china_jeffery的回复:
mark一下,晚点回答
期待你的回答
回复
s182838ss 2016-11-03
引用 43 楼 BeanJoy的回复:
[quote=引用 42 楼 s182838ss 的回复:] [quote=引用 39 楼 BeanJoy的回复:][quote=引用 30 楼 s182838ss 的回复:] [quote=引用 26 楼 BeanJoy 的回复:] 所以楼主的问题,非要让处理3GB的文件效率也快,那么就增加物理内存,让内存可用空间大于3GB,那么效率也是飞快。 如果楼主细心的话,可以发现windows里拷贝文件开始时,速度也是飞快,后面就会越来越慢,也是因为开始拷贝时是将数据写入到内存里的,然后从内存拷贝到目标位置。
首先非常感谢你这么详细的解答 楼上几位都说要加大物理内存,但是《windows核心编程》第17章提到采用内存映射可以处理几十G以及上百G的文件,用以提高效率,难道这本书的意思是默认有足够大的物理内存?不知道是否有了解此书的? [/quote] 我特地下载了《windows核心编程》,看了17章,没有提到处理超大文件的地方,能截个图来看看?[/quote] 记错了,不是在这本书里看到的[/quote] 是哪本书,把相关的地方发上来看看呢,看看是怎么说的。[/quote] 忘记在哪看到的了
回复
BeanJoy 2016-11-02
引用 42 楼 s182838ss 的回复:
[quote=引用 39 楼 BeanJoy的回复:][quote=引用 30 楼 s182838ss 的回复:] [quote=引用 26 楼 BeanJoy 的回复:] 所以楼主的问题,非要让处理3GB的文件效率也快,那么就增加物理内存,让内存可用空间大于3GB,那么效率也是飞快。 如果楼主细心的话,可以发现windows里拷贝文件开始时,速度也是飞快,后面就会越来越慢,也是因为开始拷贝时是将数据写入到内存里的,然后从内存拷贝到目标位置。
首先非常感谢你这么详细的解答 楼上几位都说要加大物理内存,但是《windows核心编程》第17章提到采用内存映射可以处理几十G以及上百G的文件,用以提高效率,难道这本书的意思是默认有足够大的物理内存?不知道是否有了解此书的? [/quote] 我特地下载了《windows核心编程》,看了17章,没有提到处理超大文件的地方,能截个图来看看?[/quote] 记错了,不是在这本书里看到的[/quote] 是哪本书,把相关的地方发上来看看呢,看看是怎么说的。
回复
赵4老师 2016-11-01
您是否希望迅速对您频繁使用的文件进行碎片整理?使用 Contig 优化单个的文件,或者创建连续的新文件。http://technet.microsoft.com/zh-cn/sysinternals/bb897428
回复
china_jeffery 2016-11-01
mark一下,晚点回答
回复
s182838ss 2016-11-01
引用 39 楼 BeanJoy的回复:
[quote=引用 30 楼 s182838ss 的回复:] [quote=引用 26 楼 BeanJoy 的回复:] 所以楼主的问题,非要让处理3GB的文件效率也快,那么就增加物理内存,让内存可用空间大于3GB,那么效率也是飞快。 如果楼主细心的话,可以发现windows里拷贝文件开始时,速度也是飞快,后面就会越来越慢,也是因为开始拷贝时是将数据写入到内存里的,然后从内存拷贝到目标位置。
首先非常感谢你这么详细的解答 楼上几位都说要加大物理内存,但是《windows核心编程》第17章提到采用内存映射可以处理几十G以及上百G的文件,用以提高效率,难道这本书的意思是默认有足够大的物理内存?不知道是否有了解此书的? [/quote] 我特地下载了《windows核心编程》,看了17章,没有提到处理超大文件的地方,能截个图来看看?[/quote] 记错了,不是在这本书里看到的
回复
BeanJoy 2016-10-31
引用 30 楼 s182838ss 的回复:
[quote=引用 26 楼 BeanJoy 的回复:] 所以楼主的问题,非要让处理3GB的文件效率也快,那么就增加物理内存,让内存可用空间大于3GB,那么效率也是飞快。 如果楼主细心的话,可以发现windows里拷贝文件开始时,速度也是飞快,后面就会越来越慢,也是因为开始拷贝时是将数据写入到内存里的,然后从内存拷贝到目标位置。
首先非常感谢你这么详细的解答 楼上几位都说要加大物理内存,但是《windows核心编程》第17章提到采用内存映射可以处理几十G以及上百G的文件,用以提高效率,难道这本书的意思是默认有足够大的物理内存?不知道是否有了解此书的? [/quote] 我特地下载了《windows核心编程》,看了17章,没有提到处理超大文件的地方,能截个图来看看?
回复
BeanJoy 2016-10-31
引用 36 楼 justwannafuckyou 的回复:
[quote=引用 25 楼 BeanJoy 的回复:] 先上代码,然后上实际测试结果,再根据现象猜测操作系统的工作原理:
难得这么认真的回复 还搞了个gif。。。666[/quote] 主要是太闲了
回复
赵4老师 2016-10-31
当程序需要使用比如2GB~1TB左右的存储时,最简单的办法恐怕得是用文件读写模拟内存读写了吧。windows参考_fseeki64函数,linux参考fseeko64函数。
回复
justwannafuckyou 2016-10-31
引用 25 楼 BeanJoy 的回复:
先上代码,然后上实际测试结果,再根据现象猜测操作系统的工作原理:

#include "stdafx.h"
#include <Windows.h>
#include <conio.h>

int main()
{

	printf("input:");
	char c = getchar();

	if (c == '1')
	{

		HANDLE hFile = CreateFile(_T("D:\\aa.iso"), GENERIC_READ|GENERIC_WRITE,  //打开文件,这里aa是我硬盘上一个2.5G的东东
			FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

		DWORD fileSize = GetFileSize(hFile,NULL);

		printf("process1...\r\n");

		DWORD dwStart = ::GetTickCount();

		HANDLE hFileMapping = CreateFileMapping(hFile,NULL,PAGE_READWRITE,0,0,NULL); //创建映射文件

		INT64 i = 0;
		PBYTE pbFile;
		DWORD errorNo;
		DWORD dwBlockSize = 200 * 1024 * 1024;
		DWORD fileBlock = dwBlockSize;   //我机器上物理内存有2G,本来使用512M作为块大小,结果一会内存就光了。还以为是块太大,所以改成512K,还是耗光了。
		while(i<fileSize)            //循环,直到写完整个文件
		{
			if ((fileSize - i) < dwBlockSize)
			{
				fileBlock = fileSize - i;
			}

			pbFile = (PBYTE)MapViewOfFile(hFileMapping, FILE_MAP_WRITE, 0,i,fileBlock);
			memset(pbFile,(BYTE)dwStart,fileBlock); //把文件内存块填1
			UnmapViewOfFile(pbFile);    //卸载映射视图
			errorNo = GetLastError();
			i+=fileBlock;
		}

		DWORD dwEnd = ::GetTickCount();
		printf("finished1:%d ms\r\n", dwEnd - dwStart);

		CloseHandle(hFileMapping);        //关闭句柄
		
		dwEnd = ::GetTickCount();
		printf("finished2:%d ms\r\n", dwEnd - dwStart);

		CloseHandle(hFile);

		dwEnd = ::GetTickCount();
		printf("finished:%d ms\r\n", dwEnd - dwStart);

		getch();
	}
	else
	{
	}

	return 0;
}
仔细看gif和代码,整个iso文件大小为2,588,262,400 字节,781ms内就完成了所有操作,内存的可用大小也从4.3GB瞬间降到了1.9GB,而且在此过种也可以看到没有哪个程序占用的内存有太大变化,所以可以猜测memset时提交的数据全在内存当中,当把所有数据都提交到内存中后,我们的代码也就立即执行完了,整个过程才781ms,但是在一开始memset时,操作系统就会同时把内存中的数据写入到硬盘文件中,这个过程相对内存写入来说是很慢的,所以从gif里可以看到可用内存就从1.9G慢慢恢复到4.3GB,这个过程就是操作系统把我们提交到内存里的数据写入到硬盘文件中。 而且注意的是,我写的这个测试程序是32位的,而文件大小远远超过了2GB,程序里memset提交到内存里的数据并不受32位程序可用最大内存为2GB的限制,实际这些都是操作系统干的事,所以受限制的只是内存的总大小而已。如果有更大的文件的话,memset会把可用的所有内存都用完,然后等待操作系统把一部分数据写入到硬盘中,空闲出一块内存来,然后memset再把数据提交到这块内存中,直到所有数据都提交到内存中,我们的程序就算执行完了,但是操作系统还是在慢慢的将内存中的数据写入到硬盘中,将内存一点点释放出来。 实际上想一想,无论是读还是写数据,始终要将数据写入到硬盘或者从硬盘上读取出来,硬盘IO这个操作肯定是少不了,这速度也肯定快不了,而使用内存映射,无非就是将内存当作缓存来用,先把数据写入到内存中,由操作系统来将内存中的数据写入到文件中(这个过程对我们来说透明的),或者从硬盘读数据到内存中,我们程序直接从内存上读取数据,这样当然快了。 如果内存足够大,当然可以将整个文件写入到内存中,然后让操作系统后台慢慢将内存里的数据写入到硬盘文件中,这样我们就感觉效率飞快。但是要注意,虽然我们程序写完后,还不能立即操作硬盘上的文件,因为操作系统后台还在写文件,楼主可以测试下,这个时候鼠标右键点击文件,会发现右键菜单不会弹出来,鼠标而是呈现繁忙的图标。 而且,更严重的问题是,如果这时候操作系统崩溃了,那么就哭去吧,程序以为写完了,其实数据都在内存中,操作系统又崩溃了,数据就丢失了。
引用 25 楼 BeanJoy 的回复:
先上代码,然后上实际测试结果,再根据现象猜测操作系统的工作原理:

#include "stdafx.h"
#include <Windows.h>
#include <conio.h>

int main()
{

	printf("input:");
	char c = getchar();

	if (c == '1')
	{

		HANDLE hFile = CreateFile(_T("D:\\aa.iso"), GENERIC_READ|GENERIC_WRITE,  //打开文件,这里aa是我硬盘上一个2.5G的东东
			FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

		DWORD fileSize = GetFileSize(hFile,NULL);

		printf("process1...\r\n");

		DWORD dwStart = ::GetTickCount();

		HANDLE hFileMapping = CreateFileMapping(hFile,NULL,PAGE_READWRITE,0,0,NULL); //创建映射文件

		INT64 i = 0;
		PBYTE pbFile;
		DWORD errorNo;
		DWORD dwBlockSize = 200 * 1024 * 1024;
		DWORD fileBlock = dwBlockSize;   //我机器上物理内存有2G,本来使用512M作为块大小,结果一会内存就光了。还以为是块太大,所以改成512K,还是耗光了。
		while(i<fileSize)            //循环,直到写完整个文件
		{
			if ((fileSize - i) < dwBlockSize)
			{
				fileBlock = fileSize - i;
			}

			pbFile = (PBYTE)MapViewOfFile(hFileMapping, FILE_MAP_WRITE, 0,i,fileBlock);
			memset(pbFile,(BYTE)dwStart,fileBlock); //把文件内存块填1
			UnmapViewOfFile(pbFile);    //卸载映射视图
			errorNo = GetLastError();
			i+=fileBlock;
		}

		DWORD dwEnd = ::GetTickCount();
		printf("finished1:%d ms\r\n", dwEnd - dwStart);

		CloseHandle(hFileMapping);        //关闭句柄
		
		dwEnd = ::GetTickCount();
		printf("finished2:%d ms\r\n", dwEnd - dwStart);

		CloseHandle(hFile);

		dwEnd = ::GetTickCount();
		printf("finished:%d ms\r\n", dwEnd - dwStart);

		getch();
	}
	else
	{
	}

	return 0;
}
仔细看gif和代码,整个iso文件大小为2,588,262,400 字节,781ms内就完成了所有操作,内存的可用大小也从4.3GB瞬间降到了1.9GB,而且在此过种也可以看到没有哪个程序占用的内存有太大变化,所以可以猜测memset时提交的数据全在内存当中,当把所有数据都提交到内存中后,我们的代码也就立即执行完了,整个过程才781ms,但是在一开始memset时,操作系统就会同时把内存中的数据写入到硬盘文件中,这个过程相对内存写入来说是很慢的,所以从gif里可以看到可用内存就从1.9G慢慢恢复到4.3GB,这个过程就是操作系统把我们提交到内存里的数据写入到硬盘文件中。 而且注意的是,我写的这个测试程序是32位的,而文件大小远远超过了2GB,程序里memset提交到内存里的数据并不受32位程序可用最大内存为2GB的限制,实际这些都是操作系统干的事,所以受限制的只是内存的总大小而已。如果有更大的文件的话,memset会把可用的所有内存都用完,然后等待操作系统把一部分数据写入到硬盘中,空闲出一块内存来,然后memset再把数据提交到这块内存中,直到所有数据都提交到内存中,我们的程序就算执行完了,但是操作系统还是在慢慢的将内存中的数据写入到硬盘中,将内存一点点释放出来。 实际上想一想,无论是读还是写数据,始终要将数据写入到硬盘或者从硬盘上读取出来,硬盘IO这个操作肯定是少不了,这速度也肯定快不了,而使用内存映射,无非就是将内存当作缓存来用,先把数据写入到内存中,由操作系统来将内存中的数据写入到文件中(这个过程对我们来说透明的),或者从硬盘读数据到内存中,我们程序直接从内存上读取数据,这样当然快了。 如果内存足够大,当然可以将整个文件写入到内存中,然后让操作系统后台慢慢将内存里的数据写入到硬盘文件中,这样我们就感觉效率飞快。但是要注意,虽然我们程序写完后,还不能立即操作硬盘上的文件,因为操作系统后台还在写文件,楼主可以测试下,这个时候鼠标右键点击文件,会发现右键菜单不会弹出来,鼠标而是呈现繁忙的图标。 而且,更严重的问题是,如果这时候操作系统崩溃了,那么就哭去吧,程序以为写完了,其实数据都在内存中,操作系统又崩溃了,数据就丢失了。
难得这么认真的回复 还搞了个gif。。。666
回复
BeanJoy 2016-10-31
引用 30 楼 s182838ss 的回复:
[quote=引用 26 楼 BeanJoy 的回复:] 所以楼主的问题,非要让处理3GB的文件效率也快,那么就增加物理内存,让内存可用空间大于3GB,那么效率也是飞快。 如果楼主细心的话,可以发现windows里拷贝文件开始时,速度也是飞快,后面就会越来越慢,也是因为开始拷贝时是将数据写入到内存里的,然后从内存拷贝到目标位置。
首先非常感谢你这么详细的解答 楼上几位都说要加大物理内存,但是《windows核心编程》第17章提到采用内存映射可以处理几十G以及上百G的文件,用以提高效率,难道这本书的意思是默认有足够大的物理内存?不知道是否有了解此书的? [/quote] 这个问题,还是像我说的那样,从宏观上来看,无论使用什么方式,数据总是要写入硬盘或者从硬盘读取出来的,这个操作效率高不了的。现在64位的操作系统,内存上百G也是很平常的事情。
回复
up
回复
nettman 2016-10-31
关注下
回复
worldy 2016-10-30
引用 6 楼 s182838ss 的回复:
[quote=引用 5 楼 oyljerry 的回复:] 这个等于是你的物理内存限制导致内存映射性能下降了,所以几百兆性能很快,但3G就不行了, 你应该增大物理内存来提升内存映射性能
非常感谢你的回答, 但增大物理内存怎么个增大法?[/quote] 使用64位系统,多买几块内存
回复
worldy 2016-10-30
现在内存很便宜,你一天的工资就可以增加好多
回复
s182838ss 2016-10-30
引用 29 楼 worldy 的回复:
[quote=引用 6 楼 s182838ss 的回复:] [quote=引用 5 楼 oyljerry 的回复:] 这个等于是你的物理内存限制导致内存映射性能下降了,所以几百兆性能很快,但3G就不行了, 你应该增大物理内存来提升内存映射性能
非常感谢你的回答, 但增大物理内存怎么个增大法?[/quote] 使用64位系统,多买几块内存[/quote] 机子是64位的系统,如果只有增大内存才能解决这个问题,那只有放弃用内存映射了,对我来说得不偿失
回复
加载更多回复
相关推荐
发帖
进程/线程/DLL
创建于2007-09-28

1.5w+

社区成员

VC/MFC 进程/线程/DLL
申请成为版主
帖子事件
创建了帖子
2016-10-25 09:25
社区公告
暂无公告