字符串和文本

原文链接:https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity5.html

        在Unity的工程中处理字符串和文本是造成性能问题的常见原因。在C#中,所有的字符串是不可变的。一个字符串的任何操作造成的结果都是一个完全新的字符串的内存分配。这是相对昂贵的,并且当有大量字符串、大量数据集、或是在紧凑的循环中,重复的字符串拼接会发展成为性能问题。

        进一步说,N个字符串的连接需要N-1个中间的字符串,一些列的字符串拼接也是造成托管内存压力的主要原因。

        对于那种每帧都要在紧凑的循环中进行字符串拼接的情况来说,使用StringBuilder来执行实际的拼接操作。StringBuilder实例也可以被重用以来最小化无必要的内存分配。

        微软维护着一个在C#中进行字符串工作的最佳实践列表,可以在MSDN网站上找到:https://docs.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings


语言环境强制和顺序比较

        经常在字符串相关代码中发现的核心性能问题之一是使用了慢速默认的字符串API。这些API是为商业应用程序构建的,并且尝试对文本中字符发现的对许多不同的文化和语言的规则进行处理。

        比如,下面的示例代码当在US-English环境下运行返回true,但是在其他欧洲语言环境下会返回false。

        请注意:在Unity5.3和5.4,Unity的脚本运行时总是在US English (en-US)语言环境下运行:

String.Equals("encyclopedia", “encyclopædia”);

        对于绝大多数Unity项目来说,这完全是不必要的。使用顺序的比较类型大概要快十倍,这种比较字符串的方式类似于C和C++编程者:只是简单的比较字符串的连续字节,二部考虑对应的字节表示的是什么。

        通过简单的使用StringComparison.Ordinal作为String.Equals最后一个参数来切换到顺序的字符串比较:

myString.Equals(otherString, StringComparison.Ordinal);


低效的内置字符串API

        出了切换到顺序比较法外,某些C#的string API已知是非常低效的。其中String.Format, String.StartsWith和String.EndsWith. String.Format是非常难以替换的,但是低效的字符串比较函数被简单的优化掉了。

        虽然微软的建议是传递StringComparison.Ordinal到所有字符串比较中,不需要适应本地化,但是Unity的标准检查程序显示与自定义实现相比较,这个影响是相对最小的。

Method                                                             Time (ms) for 100k short strings

String.StartsWith, default culture                                  137

String.EndsWith, default culture                                    542

String.StartsWith, ordinal                                                115

String.EndsWith, ordinal                                                  34

Custom StartsWith replacement                                    4.5

Custom EndsWith replacement                                      4.5

        String.StartsWith和String.EndsWith可以简单的被手工编写的代码代替,类似于下面的例子:

public static bool CustomEndsWith(string a, string b) {

        int ap = a.Length - 1;

        int bp = b.Length - 1;

        while (ap >= 0 && bp >= 0 && a [ap] == b [bp]) {

            ap--;

            bp--;

        }

        return (bp < 0 && a.Length >= b.Length) ||

                (ap < 0 && b.Length >= a.Length);

        }

    public static bool CustomStartsWith(string a, string b) {

        int aLen = a.Length;

        int bLen = b.Length;

        int ap = 0; int bp = 0;

        while (ap < aLen && bp < bLen && a [ap] == b [bp]) {

        ap++;

        bp++;

        }

        return (bp == bLen && aLen >= bLen) ||

                (ap == aLen && bLen >= aLen);

    }


正则表达式

        虽然正则表达式是一个匹配和操作字符串很强大的方式,但是它是极度性能密集型的。进一步讲,由于C#的库实现了正则表达式,即使简单的IsMatch布尔查询也会在底层造成短暂的大量数据结构分配。除了在初始化时,这种短暂的托管内存流失应该被认为是不可接受的。

        如果正则表达式是必须的,那么强烈的建议不要使用Regex.Match或Regex.Replace这两个静态函数,它们接受正则表达式作为一个字符串参数。这些函数在运行中编译正则表达式并且不会缓存生成的对象。

        下面是一行无伤大雅的代码例子:

Regex.Match(myString, "foo");

        然而每次它被执行,都会生成5KB的垃圾。用一个简单的重构可以消除其大部分的垃圾。

var myRegExp = new Regex("foo");

myRegExp.Match(myString);

        在这个例子中,每次调用myRegExp.Match“只”会产生320B的垃圾。虽然这对于一个简单的比较操作仍然昂贵,但它可观的提高了之前例子的性能。

        因此,如果正则表达式是不变的字符串文字,那么可以通过将这些字符串作为正则表达式构造函数第一个参数传递来可观的提高效率。这些预先编译的正则应该在接下来被复用。


XML, JSON以及其他长格式的文本解析

        解析文本通常是发生在加载时的一项繁重的操作。在一些情况下,解析文本所花费的时间会超过加载和实例化资源的时间。

        这后面的原因是由于特定的解析器使用。C#内置的XML解析器非常的灵活,但是这却造成了对特定数据布局无法优化。

        很多第三方的解析器是基于反射构建的。虽然反射在开发中是一个非常好的选择(因为它允许解析器迅速的适应不断改变的数据布局),但是它是众所周知的慢。

        Unity推荐使用其内置的JSONUtility API来作为一部分的解决方案,它提供了一个Unity的序列化系统读取和输出JSON文件的接口。在大多数标准检查程序中,它都比纯粹的C# JSON解析器快,但是就像Unity序列化系统中的其他接口一样它也有其限制性。没有额外代码的话它不能序列化许多复杂的数据类型,比如说字典。(请注意:查看 ISerializationCallbackReceiver接口在Unity的序列化过程中来找到一个简单的方法增加一个额外必要的执行过程来转化成或是转化一个复杂的数据类型)。

        当在文本数据解析中发生性能问题时,考虑三个可选择的解决方案。


选择1:在构建时解析

        避免文本解析消耗最好的办法是彻底在运行时消除文本解析。总体来说,这意味着将文本的数据通过一些构建的步骤“烘焙”到二进制格式中。

        大多数选择这种方式的开发者移动他们的数据到一些ScriptableObject衍生的类层级中,并且通过AssetBundle分配这些数据。对于使用ScriptableObject非常好的讲解,请去youtube上看Richard Fine’s Unite 2016 talk(https://www.youtube.com/watch?v=VBA1QCoEAX4)。

        这个策略提供了性能的最好可能性,但是只适用于数据并不会动态生成的情况。它最好适用于游戏设计参数和其他内容。


选择2:拆分和延迟加载

        第二个可选择的方法是拆分要解析的数据到更小的块儿中。一旦拆分,解析数据的消耗就将分散到多个帧中。在理想状态下,识别那些指定的需要按需求体验呈现给用户的数据部分,并值加载这些部分。

        在一个简单的例子中,如果项目是一个平台游戏,那么就没有必要将所有关卡的数据序列化到一个巨大的数据团中。如果将数据按每个关卡拆分到单独的资源中,并且也许可以将关卡拆分为区域,当玩家接近它时数据才会被解析。

        虽然这听起来容易,但这实际上大量的投资到工具代码中并且也许会需要重新组织数据结构。


选择3:线程

        对于那些要完全解析成普通C#对象,并且无需与Unity的API交互的的数据来说,可以把解析操作移动到工作线程中。

        这个选项在拥有大量核心的平台上非常强大(请注意:iOS设备最多有两个核心,大多数安卓设备有2-4个。这个技术最适用于构建桌面和控制台目标应用。)但是,它需要仔细的编程来避免造成死锁和竞争条件。

        选择线程实现的项目通常使用C#内置的Thread和ThreadPool类(请参阅msdn.microsoft.com)来管理其工作线程以及标准C#同步类。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容