C# 补完计划(二):C# 的 GC
Reference:
C#-垃圾回收机制(GC)
垃圾回收的基本知识
浅谈C#的GC机制
C# GC
官网中有这么一句话:
The garbage collector is a common language runtime component that controls the allocation and release of managed memory。
垃圾回收机制(Garbage Collection)简称GC,是CLR的一个组件,它控制内存的分配与释放。
概括:就是GC会帮你自动管理内存,分配内存,回收内存,采用的就是对应的GC的算法。
由于 C# 的编译流程为先由 C# 编译器编译为中间语言代码(IL),再由公共语言运行时(CLR)执行。因此 C# 中的 GC 功能实际上由 CLR 提供。
C# GC 工作原理
垃圾收集器的本质,就是跟踪所有被引用到的对象,整理不再被引用的对象,回收相应的内存。
以应用程序的root为基础,遍历应用程序在Heap上动态分配的所有对象,通过识别它们是否被引用来确定哪些对象是已经死亡的、哪些仍需要被使用。已经不再被应用程序的root ...
C++ 补完计划(六):C++ 编译过程
C/C++ 文件编译过程
上图展示了一个C/C++文件通过编译器生成一个可执行文件的大致过程。包含源文件的预处理,编译,汇编和最终链接目标文件生成可执行文件的四个步骤。
预处理 ( 得到 .i or .ii )
预处理的本质是进行内容的插入和替换,主要包含以下几项工作。
将所有的 #define 删除,并且展开所有的宏定义。说白了就是字符替换
处理所有的条件编译指令,#ifdef #ifndef #endif 等,就是带#的那些
处理 #include,将 #include 指向的文件插入到该行处
删除所有注释
添加行号和文件标示,这样的在调试和编译出错的时候才知道是是哪个文件的哪一行
保留 #pragma 编译器指令,因为编译器需要使用它们。
编译 ( 得到 .s 文件 )
编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析,并且将代码优化后产生相应的汇编代码文件(ASCLL文件),即 .s 文件。这个过程是整个程序构建的核心部分,也是最复杂的部分之一。对于不同架构的CPU汇编代码不相同(X86_32和X64的汇编指令就不同相同),同样在编译时选择的优化等级也 ...
多线程杂谈
进程与线程
进程
我们编写的代码只是一个存储在硬盘的静态文件,通过编译后就会生成二进制可执行文件,当我们运行这个可执行文件后,它会被装载到内存中,接着 CPU 会执行程序中的每一条指令,那么这个运行中的程序,就被称为「进程」(Process)。
多个程序、交替执行的思想,就是 CPU 管理多个进程的初步想法。例如当进程要从硬盘读取数据时,CPU 不需要阻塞等待数据的返回,而是去执行另外的进程。当硬盘数据返回时,CPU 会收到个中断,于是 CPU 再继续运行这个进程。
虽然单核的 CPU 在某一个瞬间,只能运行一个进程。但在 1 秒钟期间,它可能会运行多个进程,这样就产生并行的错觉,实际上这是并发。
并发与并行
并发(Concurrency):
并发是指在同一时间段内,多个任务交替执行。并发并不要求多个任务同时执行,而是任务之间可以交替进行。在单核 CPU 上,通过快速切换任务,使得多个任务看起来像是同时进行的,这就是并发。并发的关键在于任务的交替执行和管理。
如在一个 Web 服务器上,多个客户端请求可以并发处理,即使服务器只有一个 CPU 核心,它也可以通过快速切换处理不同的请 ...
C# 补完计划(一):值类型与引用类型
值类型与引用类型
在C#中值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中。
值类型(value type):byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型。值类型变量声明后,不管是否已经赋值,编译器为其分配内存。
引用类型(reference type):string 和 class统称为引用类型。当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。
值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。引用类型的对象总是在进程堆中分配(动态分配)。
从概念上看,值类型直接存储其值,而引用类型存储对其值的引用。这两种类型存储在内存的不同地方。在C#中,我们必须在设计类型的时候就决定类型实例的行为。这种决定非常重要,用《CLR via C#》作者Jeffrey Richter的话来说,“不理解引用类型和值 ...
Unity 补完计划(三):Unity的热更新与xLua
热更新
热更新是指在不需要重新编译打包游戏的情况下,在线更新游戏中的一些非核心代码和资源,比如活动运营和打补丁。热更新分为资源热更新和代码热更新两种,代码热更新实际上也是把代码当成资源的一种热更新,但通常所说的热更新一般是指代码热更新。
资源热更新主要通过AssetBundle来实现,在Unity编辑器内为游戏中所用到的资源指定AB包的名称和后缀,然后进行打包并上传服务器,待游戏运行时动态加载服务器上的AB资源包。
代码热更新主要包括Lua热更新、ILRuntime热更新和C#直接反射热更新等。由于ILRuntime热更新还不成熟可能存在一些坑,而C#直接反射热更新又不支持IOS平台,因此目前大多采用更成熟的、没有平台限制的Lua热更新方案。
三种热更新方案
Lua热更新
Lua热更新解决方案是通过一个Lua热更新插件(如ulua、slua、tolua、xlua等)来提供一个Lua的运行环境以及和C#进行交互。目前用的人最多,性能最好的当属 xlua 热更新插件对应的热更新解决方案。xLua是腾讯开源的热更新插件,有大厂背书和专职人员维护,插件的稳定性和可持续性较强。
由于 Lua ...
Unity 补完计划(二):AssetBundle
AB 包的概念
Unity 资源管理
在Unity中,一般来说,资源加载方式主要分为Resources加载和AssetBundle加载。
Unity有个特殊文件夹Resources,放在这个文件夹下的资源可以通过Resources.Load()来直接加载。即Resources加载资源方式。
当获得AssetBundle之后,也可以调用AssetBundle对应的API来加载资源。
什么是 AB 包
AB包全名AssetBundle(资源包)。是一种Unity提供的用于存放资源的包。通过将资源分布在不同的AB包中可以最大程度地减少运行时的内存压力,并且可以有选择地加载内容。
为什么要用AB包
热更新。(要热更新需要确保AB包打出来的资源具有唯一性,且相同资源的AB包检验码相同。)
资源热更新主要通过AssetBundle来实现,在Unity编辑器内为游戏中所用到的资源指定AB包的名称和后缀,然后进行打包并上传服务器,待游戏运行时动态加载服务器上的AB资源包。
Resources加载虽然简单方便,但是也有很多问题:
对内存管理造成一定的负担。
在打开应用时加载时间很长。
Reso ...
Lua 入门
Lua 数据类型
Lua 是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。
Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。
数据类型
描述
nil
这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。
boolean
包含两个值:false和true。
number
表示双精度类型的实浮点数
string
字符串由一对双引号或单引号来表示
function
由 C 或 Lua 编写的函数
userdata
表示任意存储在变量中的C数据结构
thread
表示执行的独立线路,用于执行协同程序
table
Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来 ...
Unity 补完计划(一):Unity 与 Mono、IL2CPP
Unity 是一个基于组件式编程的游戏开发引擎,其为开发者提供了很多功能强大的组件。尽管在通过脚本操作Unity 组件的过程中,Unity 有自己的实现和技术,但归根结底,Unity 还是利用了 Mono 运行时来实现其脚本模块的基础。
Mono
Mono 是一个由 Xamarin 公司所主持的自由开放源代码项目。它基于通用语言架构(Common Language infrastructure ,缩写 CLI)和 C#的 ECMA标准(Ecma-334和Ecma-335),提供了微软 .NET 的另一种实现方式。与微软的 .NET Framework(共通语言运行平台)不同,Mono 项目不仅可以运行于 Windows 系统上,还可以运行于Linux,FreeBSD,Unix,OS X 和 Solaris,甚至一些游戏平台,例如:Playstation 3,Wii 或 XBox 360。看到这里,大伙应该知道为什么 Unity 能够实现跨平台了,没错,这归功于 Mono。不过,在 Unity2018版本后,Unity 不再使用 Mono,而采用 .NET Core 来实现跨平台。
. ...
聊一聊 Sora、SD 都偏爱的 VAE 算法
2022年8月22日那天起,多了一个行业,叫 AIGC。
Stable Diffusion 的发布的确颠覆了很多人的认知:“用一段文字生成图片”。这是多么反人类的操作。
但我看到的,其实不是文生图的技术,这东西早就有了,Google 的 Imagen 已经达到了真实照片级别、DALL·E 都出 2 了… 让我感到与之前 AI 不同的地方是,这东西的效果已经跨越了 Demo 的阶段。不知道大家是不是都有这种体验:AI 主要是做 Demo 牛逼,实际跑起来还是堆人或者堆代码。而 Stable Diffusion 让我第一次有了种感觉,这次,兴许真能用。
而让我再一次有这个感觉的,当然就是 ChatGPT 和 Llama 2 了。而最近出的 Sora 看 Demo 的话确实也很厉害,只是除了让互联网多出更多垃圾以外,我个人觉得这个场景也没什么太多意义了。但本文只讨论技术。如果只看技术的话,我是指,只看 OpenAI 说了又好像没说但不得不说确实还是说了点的技术细节的话,似乎可以理解为一个 Stable Diffusion 加了几层 Temporal Attention、UNet 也换成 At ...
C++ 补完计划(五):C++ 设计模式
单例模式
理解单例模式
什么是单例模式?为什么要使用单例模式?
单例模式是保证类只有一个实例,并提供一个访问该实例的全局节点。该实例被所有程序模块共享。
单例模式是为了解决如下两个问题:
控制类实例的个数,保证类只有一个实例。例如对于数据库或者文件这种共享资源,保证资源类只有一个实例实际上就是控制了访问权限,同时时间只能有一个应用程序去访问;
有时候我们会使用全局变量去存储一些信息,但是很不安全,而且不方便管理,因为任何代码、任何地点都有可能改变全局变量的内容。可以利用单例模式为代替全局变量提供一个全局访问的节点,而不是将解决同一个问题的代码分散在程序各处。
所有单例的实现都包含以下两个相同的步骤:
构造函数和析构函数为私有类型,目的是禁止外部构造和析构。拷贝构造函数和赋值构造函数是私有类型,目的是禁止外部拷贝和赋值,确保实例的唯一性。
新建一个静态构建方法作为“构建”函数 (作为获取实例的唯一接口)。该函数会“偷偷”调用私有构造函数来创建对象,并将其保存在一个静态成员变量中。此后所有对于该函数的调用都将返回这一缓存对象。
如果你的代码能够访问单例类,那它就能调 ...
UE 补完计划(五) UE中的STL-2
引言
这是个系列文章,会通过分析源码,从数据结构层面来分析UE4/UE5中的几种基本容器:TArray、TSparseArray、TBitArray、TSet、TMap、TMultiMap,并与传统实现方案进行比较。
本文基于的源码版本是UE5 release 5.0.0。不过源码与UE4对比过基本一致,可通用。
本文作为系列文章的第二篇,会分析TSet、TMap、TMultiMap的实现。
数据结构是程序员的基本功,因此本文不会赘述教科书上人尽皆知的概念,只会从UE4/UE5的基本容器的特色机制出发来分析其具体实现。
TSet
上一篇文章中,详细分析了TSparseArray的机制。TSparseArray在UE5的哈希结构(TSet、TMap)中,有着重要的应用,也是TSet的底层容器。
TSet的功能与std::set类似,用于查询某个元素是否已经位于set容器中。std::set使用红黑树来进行查询,并且由于红黑树的性质可以天然保证遍历的有序性。std::unordered_set则使用哈希表来实现,无法保证有序性。
而UE的TSet容器,不仅仅使用哈希表来进行查询来保证的查询 ...
UE 补完计划(五) UE中的STL-1
引言
这是个系列文章,会通过分析源码,从数据结构层面来分析UE4/UE5中的几种数据结构基本容器:TArray、TSparseArray、TBitArray、TSet、TMap、TMultiMap,并与传统实现方案进行比较。
本文基于的源码版本是UE5 release 5.0.0。不过源码与UE4对比过基本一致,可通用。
本文作为系列文章的第一篇,会分析TArray、TSparseArray、TBitArray的实现。
数据结构是程序员的基本功,因此本文不会赘述教科书上人尽皆知的概念,只会从UE4/UE5的数据结构基本容器的特色机制出发来分析其具体实现。
TArray
TArray就是动态数组,类似stl中的std::vector。其功能也与传统的动态数组类似,支持元素增删查改、遍历等常见操作。
TArray的结构
这是一个模板类,需要外部指定使用的内存分配器。我们也可以在ContainerFwd.h里面找到其默认分配器是TSizedHeapAllocator:
123456789// ContainerAllocationPolicies.htemplate <int Inde ...
UE 补完计划(四) 导航系统-5
前言:A* 寻路算法实现
UE4的导航使用的是RecastDetour组件,这是一个开源组件,主要支持3D场景的导航网格导出和寻路,或者有一个更流行的名字叫做NavMesh。不管是Unity还是UE都使用了这一套组件。
Github上有更为详细的源码、Demo和说明:https://github.com/recastnavigation/recastnavigation
本文使用的UE4源码版本为4.26.1,2021年2月2日的版本。
RecastDetour其实是两个模块,Recast负责生成导航网格,而Detour负责利用导航网格来寻路。
在前几篇文章中,阐述了UE4如何构建导航数据,并如何将Recast数据组织成寻路用的Detour数据的。
这一篇将会介绍UE4如何利用这些导航数据,来实际进行寻路,以及是如何支持垂直方向的3D寻路的。
A* 算法简介
A* 算法有非常多的非常好的文章详细介绍。这里我自己非常推荐这两篇,详细列举了 A* 算法的原理、常见问题和优化方案。
堪称最好最全的A*算法详解(译文)
径规划之 A* 算法
这里我还是简单介绍一下A*算法对比其它寻路算 ...
UE 补完计划(四) 导航系统-4
前言:区域划分算法–Watershed与Monotone
UE4的导航使用的是RecastDetour组件,这是一个开源组件,主要支持3D场景的导航网格导出和寻路,或者有一个更流行的名字叫做NavMesh。不管是Unity还是UE都使用了这一套组件。
Github上有更为详细的源码、Demo和说明:https://github.com/recastnavigation/recastnavigation
本文使用的UE4源码版本为4.24.3,2020年2月25日的版本。
Recast采用了体素化的方式,来生成导航网格。大致分为三个步骤:
将场景体素化。形成一个多层的体素模型。
将不同层的体素模型划分为可重叠的2D区域。不同层的2D区域不同
沿着边界区域剥离出导航凸多边形。
在上一篇文章,讲述了整个区域生成的过程,并简单介绍了Monotone区域划分算法。这一篇会从理论说起,并深入源码层面,详细解读Watershed算法的实现。
WaterShed 算法
Watershed算法中文名称叫做分水岭算法,是一种利用已有的深度图划分区域的算法。在图像处理上也经常用于利用灰度值划分一张图片上的不 ...
UE 补完计划(四) 导航系统-3
前言:区域生成
UE4的导航使用的是RecastDetour组件,这是一个开源组件,主要支持3D场景的导航网格导出和寻路,或者有一个更流行的名字叫做NavMesh。不管是Unity还是UE都使用了这一套组件。
Github上有更为详细的源码、Demo和说明:https://github.com/recastnavigation/recastnavigation
本文使用的UE4源码版本为4.24.3,2020年2月25日的版本。
Recast采用了体素化的方式,来生成导航网格。大致分为三个步骤:
将场景体素化。形成一个多层的体素模型。
将不同层的体素模型划分为可重叠的2D区域。不同层的2D区域不同
沿着边界区域剥离出导航凸多边形。
在上一篇文章,讲述了场景体素化的过程。现在我们已经有了rcHeightField数据,标记了场景中所有的span以及每个span的可行走标记。接下来会根据这些数据来生成实际使用的导航网格区域。
区域生成流程
这一部分会使用之前生成的rcHeightField数据。大致分为两个步骤:
初始化CompactHeightfield数据。记录所有 ...