C#如何快速释放内存的大数组详解

前言

本文告诉大家如何使用 Marshal 做出可以快速释放内存的大数组。最近在做 3D ,需要不断申请一段大内存数组,然后就释放他,但是 C# 对于大内存不是立刻释放,所以就存在一定的性能问题。在博客园看到了一位大神使用 Marshal 做出快速申请的大数组,于是我就学他的方法来弄一个。本文告诉大家这个类是如何使用。

在使用的时候,先来看下原来的 C# 的大数组性能。可以看到在不停gc,性能不好

 static void Main(string[] args)
 {
  for (int i = 0; i < 10000; i++)
  {
  Foo();
  }
  Console.ReadKey();
 }

 private static void Foo()
 {
  var foo = new byte[1000000000];
 }

介绍

在使用 Marshal 之前需要知道这是什么,其实 Marshal 就是一个提供 COM 互操作的方法。

使用

下面使用一个快速申请 int 数组来告诉大家如何使用。

是否还记得 C 的申请数组?其实下面的方法和 C 的相同

  int n = 100000;//长度
  IntPtr buffer = Marshal.AllocHGlobal(sizeof(int) * n);

这时就可以使用 buffer 作为数组

下面对他的第 k 个元素修改

  IntPtr buffer = Marshal.AllocHGlobal(sizeof(int) * n);
  int k = 2;

  IntPtr t = buffer + k * sizeof(int);
  var p = Marshal.PtrToStructure<int>(t);
  Console.WriteLine("p " + p); //196713 这时的值是不确定

  p = 2;
  Marshal.StructureToPtr(p,t,false);

  p = Marshal.PtrToStructure<int>(t);
  Console.WriteLine("p " + p);//2

  //遍历
  Console.WriteLine("遍历");
  for (int i = 0; i < 10; i++)
  {
  t = buffer + i * sizeof(int);
  Console.WriteLine(Marshal.PtrToStructure<int>(t));
  }

遍历:

43909312
            44502144
            2
            0
            0
            24
            1357220181
            196712
            550912
            543686656

可以从上面的代码看到,主要使用的两个方法是 StructureToPtr 和 PtrToStructure ,而 StructureToPtr 就是从指定类型写到指针,希望大家还知道如何使用指针,PtrToStructure 就是从指针指向的地方开始读数据,读指定类型的数据。所以可以从 Marshal 把一个类型使用另一个类型来读取,但是一般需要读取的类型都需要是确定类型大小的,如 char 可以、string 不可以。

反过来,StructureToPtr 是对指定指针写入指定的类型,同样也是需要确定这个类型的大小,如可以写入 char 但是不可以写入 string。这就是对数组读写的方法。

那么遍历的时候什么输出一些诡异的值,实际上因为没有初始化,里面的值是不确定的。我觉得用这个做随机数也不错。

使用 Marshal 是比较安全,因为 ms 做了很多处理,但是也会让程序闪退,如下面的代码

 private static void Foo()
 {
  int n = 100000;//长度
  IntPtr buffer = Marshal.AllocHGlobal(sizeof(int) * n);

  try
  {
  var t = buffer + (n * 10) * sizeof(int);
  var p = Marshal.PtrToStructure<int>(t);
  }
  catch (Exception e)
  {
  Console.WriteLine(e);
  }

  Marshal.FreeHGlobal(buffer);
 }

会出现异常 System.AccessViolationException,这个异常是无法 catch 的,所以使用的时候最好封装一下

“System.AccessViolationException”类型的未经处理的异常在 未知模块

尝试读取或写入受保护的内存。这通常指示其他内存已损坏

如果需要 catch 那么请在 app.config 添加下面的代码

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <runtime>
 <legacyCorruptedStateExceptionsPolicy enabled="true" />
 </runtime>
</configuration>

然后在 Main 函数添加 HandleProcessCorruptedStateExceptions ,请看代码

 [HandleProcessCorruptedStateExceptions]
 static void Main(string[] args)
 {
  AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

  for (int i = 0; i < 100000; i++)
  {
  try
  {
   Foo();
  }
  catch (Exception e)
  {
   Console.WriteLine(e);

  }
  }
  Console.WriteLine("完成");
  Console.ReadKey();
 }

这时可以看到进入 UnhandledException ,但是无法接住,软件还是会崩溃

释放内存

那么如何释放内存?因为这个申请是没有经过管理的,如果没有手动释放,那么就出现内存泄露。

 static void Main(string[] args)
 {
  for (int i = 0; i < 10000; i++)
  {
  Foo();
  }
  Console.ReadKey();
 }

 private static void Foo()
 {
  int n = 100000;//长度
  IntPtr buffer = Marshal.AllocHGlobal(sizeof(int) * n);

 }

上面的代码很快就可以看到内存占用到2G,所以需要手动释放

  Marshal.FreeHGlobal(buffer);

原来的 byte 数组需要使用 1G 内存,而且速度很慢,而现在使用这个方法只需要 7M 内存,速度很快

所以在需要进行大数组申请的时候,需要不停释放,就可以使用这个方法。

如果想使用封装好的,请看下面的大神弄好的类

参见:C#+无unsafe的非托管大数组(large unmanaged array in c# without ‘unsafe' keyword)

实际使用

实际在哪些地方使用?实际上因为很多时候都是使用实例化池,但是实例化池在进入游戏的时候,可以让gc不会让程序暂停,但是会在游戏进入下一关的时候,无法快速清理数据。所以这时就可以使用 Marshal 做实例化池,瞬间就可以清空。

上面的方法暂时不告诉大家如何做,因为涉及到公司的使用。

总结

到此这篇关于C#如何快速释放内存的大数组的文章就介绍到这了,更多相关C#快速释放内存的大数组内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C# 字符串string和内存流MemoryStream及比特数组byte[]之间相互转换

    定义string变量为str,内存流变量为ms,比特数组为bt 1.字符串转比特数组 复制代码 代码如下: (1)byte[] bt=System.Text.Encoding.Default.GetBytes("字符串"); (2)byte[] bt=Convert.FromBase64String("字符串"); 2.字符串转流 复制代码 代码如下: (1)MemoryStream ms=new MemoryStream(System.Text.Encoding.

  • C#如何快速释放内存的大数组详解

    前言 本文告诉大家如何使用 Marshal 做出可以快速释放内存的大数组.最近在做 3D ,需要不断申请一段大内存数组,然后就释放他,但是 C# 对于大内存不是立刻释放,所以就存在一定的性能问题.在博客园看到了一位大神使用 Marshal 做出快速申请的大数组,于是我就学他的方法来弄一个.本文告诉大家这个类是如何使用. 在使用的时候,先来看下原来的 C# 的大数组性能.可以看到在不停gc,性能不好 static void Main(string[] args) { for (int i = 0;

  • Java核心技术巩固篇之控制流程与大数值及数组详解

    目录 1. 控制流程 1.1 块作用域 1.2 条件语句 2. 大数值 3. 数组 3.1 命令行参数 前言: 个人已经学习了一遍 Java,为了巩固基础查漏补缺,自己再看一遍核心卷.该文章主要补充了一些自己没有了解的.重要的.易忘记的知识点,所以并不是用来专门学习 Java 的文章,而是主要用来帮助大家巩固自己的内功的.如果大家想要看 Java 学习的文章,可以去我的 JavaSE 专栏,里面都是关于 Java 基础语法的知识. 1. 控制流程 1.1 块作用域 [易错点]: 不能在嵌套的两个

  • PHP内存溢出优化代码详解

    相信很多人做大批量数据导出和数据导入的时候,经常会遇到PHP内存溢出的问题,在解决了问题之后,总结了一些经验,整理成文章记录下. 优化点 1.优化SQL语句,避免慢查询,合理的建立索引,查询指定的字段,sql优化这块在此就不展开了. 2.查询的结果集为大对象时转数组处理,框架中一般有方法可以转,如Laravel中有toArray(),Yii2中有asArray(). 3.对于大数组进行数据切割处理,PHP函数有array_chunk().array_slice(). 4.对于大型的字符串和对象,

  • Java基础之内存泄漏与溢出详解

    一.浅析 内存泄露( memory leak):是指程序在申请内存后,无法释放已申请的内存空间,多次内存泄露堆积后果很严重,内存迟早会被占光.内存泄漏最终会造成内存溢出. 内存溢出(out of memory) :是指程序在申请内存时,没有足够的内存空间供其使用 JVM中有一下几种内存空间: 栈内存(Stack):每个线程私有的. 堆内存(Heap):所有线程公用的. 方法区(Method Area):有点像以前常说的"进程代码段",这里面存放了每个加载类的反射信息.类函数的代码.编译

  • GoLang内存泄漏原因排查详解

    目录 背景 临时性内存泄漏 通道理解 背景 Go 语言中有对应的Go 内存回收机制,在Go采用 并发三色标记清除  算法, 但是由于实际的过程中 发现会有一些内存泄漏的常见,内存泄漏 分为: 临时性 和 永久性内存泄漏. 初步排查过程中: 发现Linux使用top 发现内存随着时间会持续的增加没有稳定在一个合理值中. 在使用 pprof ,BBC 等 Go的内存泄漏工具进行排查 临时性内存泄漏 指的释放内存 不及时,对应的内存在更晚时候释放,这类问题主要是 string,slice 和底层的Bu

  • C++中静态初始化数组与动态初始化数组详解

    静态初始化的数组的长度必须是在程序中确定的常数,不能是由用户输入的变量 例子: int a[10];//正确 Student stud[10];//正确:Student是一个学生类 int n;cin>>n;int a[n];//错误 int n;cin>>n;Student stud[n];//错误:Student是一个学生类 动态初始化数组可以使用用户输入的变量作为数组的长度. 例子: int n; cin>>n; int *a=new int[n];//这样整数数

  • Java开发必备知识之数组详解

    一.ASCII码 二.为什么需要数组 案例: 160班 现在 77人 统计 全班的Java成绩 用程序进行存储 变量 统计 全班不及格的同学 要 补考 补考过的同学 修改成绩 定义 77 个变量 int 帅 = 59: int 洋 = 100: int cto = 60: int ceo = 58: 三.什么是数组 概念:数组就是内存中一块 连续的 内存空间,用于存放 相同类型 的多个数据 四.定义数组 声明一个数组:确定数组中存放的数据类型 语法: 数据类型[] 数组名://建议 数据类型 [

  • Java基础之Unsafe内存操作不安全类详解

    简介 Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,直接操作内存就意味着 1.不受jvm管理,也就意味着无法被GC,需要我们手动GC,稍有不慎就会出现内存泄漏. 2.Unsafe的不少方法中必须提供原始地址(内存地址)和被替换对象的地址,偏移量要自己计算,一旦出现问题就是JVM崩溃级别的异常,会导致整个JVM实例崩溃,表现为应用程序直接crash掉. 3.直接操作内存,也意味着其速度更快,在高并发的条件之下能够很好地提高效率. Unsafe 类 public final c

  • YGC前后新生代是否变大分析详解

    问题描述 我们都知道gc是为了释放内存,但是你是否碰到过ygc前后新生代反增不减的情况呢?gc日志效果类似下面的: 2016-05-18T15:06:13.011+0800: [GC [ParNew (promotion failed): 636088K->690555K(707840K), 0.2958900 secs][CMS: 1019739K->1019733K(1310720K), 2.6208600 secs] 1655820K->1655820K(2018560K), [C

  • C++中整形与浮点型如何在内存中的存储详解

    目录 1 数据类型 1.1 类型的基本归类 2 整形在内存中的存储 2.1 二进制的三种形式 2.2 大小端字的介绍 3 浮点数在内存中的存储 3.1 浮点数存储规则 1 数据类型 前面我们已经知道了基本的内置类型: 类型的意义: 1. 使用这个类型开辟内存空间的大小(大小决定了使用范围). 2. 如何看待内存空间的视角. 1.1 类型的基本归类 整形家族: char unsigned char signed char short unsigned short [int] signed shor

随机推荐