项目优化之数据集合优化(Unity3D)

简介: 通过详细的理解Array、List、和Dictionaries 让你的游戏运行速度快十倍

通过详细的理解Array、List、和Dictionaries 让你的游戏运行速度快十倍


目标:

  • 这篇文章的主要目标是:过详细的理解Array、List、和Dictionaries,让你的游戏运行速度快十倍。
  • 我应该使用Array吗?我应该使用List吗?噢 ,等等,或者使用Dictionaries?
  • 为什么我的循环花了那么长时间呢? ?
  • 在我代码中看不到任何的错误,那么为什么我的代码执行那么慢?
  • 为什么查找我想要的对象话费那么长时间?
  • 我看不到任何的GC(垃圾回收器)处理,然而我的游戏为什么那么的延迟?
“这些是我们在开发一个游戏的时候经常遇到的一些常见问题。”
但正是这些常见问题最让游戏开发者苦恼了!!
几毫秒的延迟就可以让游戏开发者失去理智!!
最终,我们总是推卸责任喊道:“这个是Unity引擎的错误,我的代码是完美的!!”
那好吧,这并非总是如此,只是有的时候我们不正确使用数据结构中的集合造成的,且心里咒骂着:“集合是如此的慢!”

在应用程序中,我们一般通过以下两种方式去管理相邻对象组:

1. 通过创建对象数组(Array)

2. 通过创建对象的集合

我们应该记得每一种集合的具体用法,以及它的优点和缺点,并且知道在什么情况下使用它是最佳的。


在文章中,我将列举在Unity中所有常用的数组,这样有利于你更好的理解集合。


什么是集合?集合是特殊的类用于数据的存储和检索,集合类通常是用来为元素动态的分配内存,并且通过下标索引来访问列表里的每一个元素等等。

这些类创建Object类的对象的集合,在C#中所有数据类型的基类是Object类。集合可以根据应用程序的请求动态的扩展和缩减,这就是集合的主要优势。

集合使得内存管理和数据管理的过程变得相当简单。


那么,在Unity中常用的集合有那几个呢?在Unity中Dictionary(字典) 和 List(列表)是最常用的集合,让我给初学者对于Dictionary(字典) 和 List(列表)的一些基本概念。如果知道的朋友可以跳过。


1. List

C#List< T >类代表一个强大的List类型(其实就是泛型List类)可以通过索引访问的列表对象,它可以存储没有指定类型的对象集合。

它和其他集合一样都有以下功能:添加(Add),插入(Insert),移除(Remove),查找(Search)等等。

List的索引表示方式和Array一样,然后它的主要优势是动态的指定容器的大小。

例如,我们可以这样定义一个Object的List<>:

List<GameObject> myListOfGameObjects = new List<GameObject>();
复制代码


2. DictionaryDictionary实际上使一个哈希表类型的替代品

Dictionary代表一个键值对

例如,如果5代表Red,10代表Green,我们便在Dictionary中通过5键(Key)找到Red这个值(Value)。

因此,我们如果想要找到Red这个值(Value),只要记住5这个键(Key)即可

那么,Dictionary是怎么查找数据的呢?

例如,我们可以这样定义一个Dictionary对象:

//Dictionary: //在这个例子中,“int”是键,“String”是值

Dictionary<int,String> myDictionary = new Dictionary<int, String>();
复制代码

现在,这篇文章的主要目标是关于优化使用集合,而不是学习集合,因此我们将忽略集合的学习。

但是,如果你想要详细的学习它,你可以下面的链接,这将会帮你更详细的学习集合的概念:

  1. msdn.microsoft.com/en-us/libra…
  2. www.dotnetperls.com/
  3. www.tutorialspoint.com/csharp/csha…


集合是怎么影响游戏的呢?

让我们一起看一个例子,并且理解集合石怎么影响我们的游戏的。

1.在Unity中依照下面方式来设置场景

a) 创建一个空的游戏物体(Empty Game),并且更改名字(你随意,在这里我命名为Test)
复制代码

2.创建一个脚本,并且命名为你喜欢的名字

a) 在这里我把命名为GenericCollectionsTest.cs
b) 我使用的是C#作为我的脚本语言,你也可以使用Javascript,如果你愿意的话。
复制代码
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
public class GenericCollectionsTest : MonoBehaviour
{
    #region PUBLIC_DECLARATIONS
    public int numberOfIterations = 10000000;
    #endregion
    #region PRIVATE_DECLARATIONS
    private Stopwatch stopWatch;
    private List<int> intList;
    private Dictionary<int, int> intDictionary;
    private int[] intArray;
    #endregion
    #region UNITY_CALLBACKS
    void Start()
    {
        stopWatch = new Stopwatch();
        intArray = new int[numberOfIterations];
        intList = new List<int>();
        intDictionary = new Dictionary<int, int>();
        AddFakeValuesInArray(numberOfIterations);
        AddFakeValuesInList(numberOfIterations);
        AddFakeValuesInDictionay(numberOfIterations);
    } 
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            PerformTest();
        }
        if (Input.GetKeyDown(KeyCode.S))
        {
            SearchInList(111);
            SearchInDictionary(numberOfIterations - 1);
            UnityEngine.Debug.Log("SearchComplete");
        }
    }
    #endregion 
    #region PRIVATE_METHODS 
    private void AddFakeValuesInArray(int iterations)
    {
        for (int i = 0; i < iterations; i++)
        {
            intArray[i] = Random.Range(0, 100);
        }
    } 
    private void AddFakeValuesInList(int iterations)
    {
        for (int i = 0; i < iterations; i++)
        {
            intList.Add(Random.Range(0, 100));
        }
        intList[iterations - 1] = 111;
    } 
    private void AddFakeValuesInDictionay(int iterations)
    {
        for (int i = 0; i < iterations; i++)
        {
            intDictionary.Add(i, Random.Range(0, 100));
        }
        intDictionary[iterations - 1] = 111;
    } 
    private void SearchInList(int value)
    {
        #region FIND_IN_LIST
        stopWatch.Start();
        int index = intList.FindIndex(item => item == value);
        stopWatch.Stop();
        UnityEngine.Debug.Log("Index " + index);
        UnityEngine.Debug.Log("Time Taken to Find in List  "+stopWatch.ElapsedMilliseconds +" ms");
        stopWatch.Reset();
        #endregion
        #region CHECK_IF_CONTAINS_VALUE_IN_LIST
        stopWatch.Start();
        bool containsValue = intList.Contains(value);
        stopWatch.Stop();
        UnityEngine.Debug.Log(containsValue);
        UnityEngine.Debug.Log("Time Taken To Check in List "+stopWatch.ElapsedMilliseconds +" ms");
        stopWatch.Reset();
        #endregion
    } 
    private void SearchInDictionary(int key)
    {
        #region FIND_IN_DICTIONARY_USING_REQUIRED_KEY
        stopWatch.Start();
        int value = intDictionary[key];
        stopWatch.Stop();
        UnityEngine.Debug.Log(("Time Taken to Find in Dictionary   " + stopWatch.ElapsedMilliseconds + " ms"));
        stopWatch.Reset();
        #endregion 
        #region CHECK_IF_DICTIONARY_CONTAINS_VALUE
        stopWatch.Start();
        bool containsKey = intDictionary.ContainsKey(key);
        stopWatch.Stop();
        UnityEngine.Debug.Log(containsKey);
        UnityEngine.Debug.Log("Time taken to check if it contains key in Dictionary" + stopWatch.ElapsedMilliseconds + " ms");
        stopWatch.Reset();
        #endregion
    } 
    private void PerformTest()
    {
        #region ARRAY_ITERATION
        stopWatch.Start();
        for (int i = 0; i < intArray.Length; i++)
        {
        }
        stopWatch.Stop();
        UnityEngine.Debug.Log("Time Taken By Array "+stopWatch.ElapsedMilliseconds + "ms");
        stopWatch.Reset();
        #endregion 
        #region LIST_ITERATION
        stopWatch.Start();
        for (int i = 0; i < intList.Count; i++)
        { 
        }
        stopWatch.Stop();
        UnityEngine.Debug.Log("Time Taken By List "+stopWatch.ElapsedMilliseconds + "ms");
        stopWatch.Reset();
        #endregion
        #region LIST_ITERATION_BY_FOREACH_LOOP
        stopWatch.Start();
        foreach (var item in intList)
        { 
        }
        stopWatch.Stop();
        UnityEngine.Debug.Log("Time Taken By List Using foreach  "+stopWatch.ElapsedMilliseconds + "ms");
        stopWatch.Reset();
        #endregion 
        #region DICTIONARY_ITERATIOn_LOOP
        stopWatch.Start();
        foreach (var key in intDictionary.Keys)
        {
        }
        stopWatch.Stop();
        UnityEngine.Debug.Log("Time Taken By Dictionary "+stopWatch.ElapsedMilliseconds + "ms");
        stopWatch.Reset();
        #endregion
    }
    #endregion
}
复制代码


3.分解代码和理解代码

a) 让我们来分解这个代码,并且一步一步的理解代码
b) 在这里显示了一些数据,如下表所示:
复制代码
intArray 一个int类型的数组(Array)
intList 一个int类型的列表(List)
intDictionary 一个键值都是int类型的字典(Dictionary)
c) 现在,让我们来看看Start()方法
复制代码
void Start()
    {
        stopWatch = new Stopwatch();
        intArray = new int[numberOfIterations];
        intList = new List<int>();
        intDictionary = new Dictionary<int, int>();
        AddFakeValuesInArray(numberOfIterations);
        AddFakeValuesInList(numberOfIterations);
        AddFakeValuesInDictionay(numberOfIterations);
    }
复制代码
d) 在这里,我初始化了数组(Array),列表(List),字典(Dictionary),并且为他们添加了一些随机值。
e) 正如你在代码中看到的,我创建了一个Private(私有的)方法为那些集合添加一些随机数。
f) 在这里我也使用了Stopwatch对象用来时间和性能测试,并且也在Start()里进行了初始化
g) 如果你还不知道Stopwatch是怎么运作的,在往后学习之前,先去了解它,这样有利于你接下来的理解。
h) 请参考以下链接:
复制代码

www.dotnetperls.com/stopwatch

i) 现在让我们看一看PerformTest()方法
j) 我把该方法分为了4块,这样更有利于理解:
复制代码
ARRAY_ITERATION 这里我们只遍历该数组
LIST_ITERATION 这里我们只使用简单的for循环遍历列表
LIST_ITERATION_BY_FOREACH_LOOP 这里我们使用foreach循环遍历列表
DICTIONARY_ITERATION_LOOP 这里我们遍历字典
k)  正如你在Update()中看到,我们在按下“Space”键(空格键)时调用PerformTest()。
  l)  现在,我们将执行项目去测试一番。
复制代码

Note为了获得性能的确切数据,我们让每一个种类型的数据集合迭代10万次。 你的输出应该跟下面的图片一样:

网络异常,图片无法展示
|

n)  那么,这就意味着Array(数组)是最好的吗?我们应该只使用Array(数组)就行了嘛?不,不是这样的。正如我们之前说的那样,我们要有计划的使用集合。
o)  只是我们要明确我们的需要,按自己的需求指定需用的集合类型。
P)  让我们一起去了解一些,我们应该在什么情况下使用什么样的集合类型。
复制代码


情况1:在整个游戏中,对象的数量保持不变

·  在这种情况下使用List(列表)和Dictionary(字典)是不合适的,很显然对象的数量没有改变。然而使用一个集合为什么会给内存和CPU造成额外的消耗呢?

·在这里,Array(数组)的效率是List(列表)的两倍。


情况2:在游戏中对象的数量在不断的变化

·我们从上面中了解到Array(数组)不是动态分配的,显然,我在这种情况下应该使用List(列表)。因为对象在持续的改变,所以List(列表)比Dictionary要快很多。

·List(列表)常用来管理对象池

·List(列表)比Dictionary(字典)快将近8倍左右

·使用foreach循环来遍历List比使用for循环多消耗将近    3倍的时间(这个在《关于Foreach你不知道的事儿》    中有详细说明)。


所以这意味着我们应该完全停止使用字典吗?不是的,让我们通过下面的例子更好的理解它。在代码中有两个方法SearchInList() 和 SearchInDictionary()。


通过下面表格进行了解:

SearchInList() 方法的第一部分是传递一个值给列表,然后在列表中去查找这个值,第二部分是判断这个列表是否存在该值,最后根据判断条件返回相应的布尔值
SeatchInDictionary() 方法的第一部分是根据这个传入的键去找到这个键对应的值,第二部分通过使用ContainsKey()方法判断这个方法里是否有指定的键

让我们在一次运行项目进行测试,且在运行中按下“S”键后看输出日志的显示。


输出将是这样的:

网络异常,图片无法展示
|

从上图中便能得知,使用Dictionary(字典)进行搜索几乎不消耗任何时间

因此,如果在整个游戏运行的时候需要不断的寻找一些对象时,明智的选择就是选择使用Dictionary(字典)。

接受它吧,你的游戏不能没有集合!

是的,这是正确的。我们只需要知道在什么情况下使用什么类型的集合。

结论很简单,有三个基本原则:

1.当一个对象的数量保持不变时和需要频繁的查找对象时不要使用List(列表)。

2.如果是动态的对象,且不需要频繁查找对象时,使用List(列表)是最佳的选择。

3.需要快速查找,并且对象的改变很小时,使用Dictionary(字典)是最佳的选择。

4.当一个对象的数量保持不变时,使用Array(数组)是最佳的选择(自己添加的)

如果你想更深入的理解,请点击下面的链接进行学习:

www.dotnetperls.com/dictionary-…

answers.unity3d.com/questions/3…



相关文章
|
6月前
|
大数据 API 图形学
Unity优化——批处理的优势
Unity优化——批处理的优势
177 0
|
6月前
|
存储 人工智能 Java
Unity优化——脚本优化策略4
Unity优化——脚本优化策略4
113 0
|
6月前
|
安全 Java 图形学
Unity3D 导出的apk进行混淆加固、保护与优化原理(防止反编译)
Unity3D 导出的apk进行混淆加固、保护与优化原理(防止反编译)
82 0
|
4月前
|
存储 设计模式 监控
运用Unity Profiler定位内存泄漏并实施对象池管理优化内存使用
【7月更文第10天】在Unity游戏开发中,内存管理是至关重要的一个环节。内存泄漏不仅会导致游戏运行缓慢、卡顿,严重时甚至会引发崩溃。Unity Profiler作为一个强大的性能分析工具,能够帮助开发者深入理解应用程序的内存使用情况,从而定位并解决内存泄漏问题。同时,通过实施对象池管理策略,可以显著优化内存使用,提高游戏性能。本文将结合代码示例,详细介绍如何利用Unity Profiler定位内存泄漏,并实施对象池来优化内存使用。
293 0
|
3月前
|
开发者 图形学 iOS开发
掌握Unity的跨平台部署与发布秘籍,让你的游戏作品在多个平台上大放异彩——从基础设置到高级优化,深入解析一站式游戏开发解决方案的每一个细节,带你领略高效发布流程的魅力所在
【8月更文挑战第31天】跨平台游戏开发是当今游戏产业的热点,尤其在移动设备普及的背景下更为重要。作为领先的游戏开发引擎,Unity以其卓越的跨平台支持能力脱颖而出,能够将游戏轻松部署至iOS、Android、PC、Mac、Web及游戏主机等多个平台。本文通过杂文形式探讨Unity在各平台的部署与发布策略,并提供具体实例,涵盖项目设置、性能优化、打包流程及发布前准备等关键环节,助力开发者充分利用Unity的强大功能,实现多平台游戏开发。
104 0
|
3月前
|
开发者 图形学 UED
深度解析Unity游戏开发中的性能瓶颈与优化方案:从资源管理到代码执行,全方位提升你的游戏流畅度,让玩家体验飞跃性的顺滑——不止是技巧,更是艺术的追求
【8月更文挑战第31天】《Unity性能优化实战:让你的游戏流畅如飞》详细介绍了Unity游戏性能优化的关键技巧,涵盖资源管理、代码优化、场景管理和内存管理等方面。通过具体示例,如纹理打包、异步加载、协程使用及LOD技术,帮助开发者打造高效流畅的游戏体验。文中提供了实用代码片段,助力减少内存消耗、提升渲染效率,确保游戏运行丝滑顺畅。性能优化是一个持续过程,需不断测试调整以达最佳效果。
95 0
|
6月前
|
人工智能 安全 API
Unity优化——加速物理引擎1
Unity优化——加速物理引擎1
149 0
|
6月前
|
存储 人工智能 缓存
Unity优化——脚本优化策略3
Unity优化——脚本优化策略3
|
6月前
|
存储 缓存 Java
Unity优化——脚本优化策略2
Unity优化——脚本优化策略2
|
6月前
|
存储 XML 缓存
Unity优化——脚本优化策略1
Unity优化——脚本优化策略1
下一篇
无影云桌面