技术开发 频道

减少.NET应用程序内存占用经验分享

  三、采用非托管代码或者模块编写数据处理逻辑

  上面的两部操作虽然减少了将近50-60%的内存占用,但是仍然达不到领导的要求,于是又尝试并比较了各种 使用不同的数据结构将数据载入到内存中的内存占用大小,包括直接将文件按类型读成字符串、数组、结构及类,内存占用最小的直接将文件读成字符串,10M的数据文件读进内存也会占用20-30M的空间,还不谈对其进行处理过程中产生的一些临时变量对内存的占用。使用dotTrace及CLR Profile等工具检查之后,发现内存的占用也是这些原始数据。然后以” How to reduce the memory usage of .NET applications” 到网上搜了一下减少.NET内存占用的一些方法,在StackOverflow上看到了这一回答:

采用非托管代码

  该同学指出.NET应用程序和其他使用本地代码编写的程序相比会有较大的内存占用,如果对内存开销比较在意,.NET可能不是最好的选择。.NET应用程序的内存一定程度上受垃圾回收的影响。并指出,一些数据结构如List,系统会分配多余的空间。可以使用值类型而不是引用类型,不要创建大对象,以免产生内存碎片等等降低内存占用的建议。

  这些都考虑过之后,内存还是达不到要求,所以开始寻找调用非托管代码的方式来自己更灵活的控制内存的分配与销毁。但是整个程序都是采用.NET编写的,全部切换成C或者C++不现实,所以只有两种方案,一是使用unsafe 代码,二是将数据加载和检索模块采用C或者C++编写,在.NET中采用P/Invoke技术调用。

  刚开始想采用unsafe代码,对数据的加载及检索直接在放在unsafe 代码中。后来觉得代码有些乱,不同风格的代码混杂在一起不太好,而且数据加载和检索的逻辑也比较复杂。所以就直接采用第二种方案,使用C++编写数据加载和检索逻辑。然后在.NET里面调用。

  在开始之前,也做了一些评估,比如将同样的10M的数据加载到内存中,都采用字符串的方式存储,.NET中会占用20-30M的内存,而在C++中只有9-10M的样子,而且变动很小。这正是需要的结果。

  由于对C++不熟,临时抱佛脚,翻了下C++ Primier Plus中关于字符串和STL的相关章节,并请求其他开发小组给予了一定的协助,定义了基本的接口。为了演示,我创建了两个工程,一个是名为SecuData的C++ Win32 DLL工程,一个是测试该类库的名为SecuDataTest的C# WinForm程序。

  我在C++中定义好了4个方法,一个初始化加载数据,一个设置搜索优先级,一个查找匹配方法和一个卸载数据方法,具体的算法由于工作原因不便贴出,这里只是举一个简单的例子,方法名及工程结构如下图:

采用非托管代码

  然后再在.NET中使用P/Invoke技术引入C++ DLL中定义的方法。

采用非托管代码

  这样就可以在.NET中调用这些方法了,需要说明的是,方法的传入值这里是使用String类型的,第二个StringBuilder类型的参数是方法的真正返回值,方法的整体int型返回值表明方法是否执行成功。在调用查找方法时,第二个StringBuilder参数必须初始化一个最大的查询结果的大小,因为在C++中会往这个对象中写入结果,不初始化或者初始化太小都会抛出异常。当然也可以直接返回结构体,这个就需要额外定义,这里返回的都是字符串。完了自己在.NET里面对其进行解析。

  需要注意的是,调试的时候,如果需要调试C++里面的代码,需要指定DLL的生成目录及启动目标,并且将C++项目设置为启动项目,这里我设定生成DLL的目录为SecuDataTest项目生成的SecuDataTest.exe文件所在的目录,调试启动目标设置为SecuDataTest.exe,这样在C++项目中设置断点,启动.NET Winform程序,当P/Invoke触发断点时能够逐步调试C++代码。

0
相关文章