最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

字符串和文本

2020-05-22 09:15 作者:unity_某某師_高錦錦  | 我要投稿

字符串和文本的處理不當(dāng)是 Unity 項(xiàng)目中性能問(wèn)題的常見(jiàn)原因。在 C# 中,所有字符串均不可變。對(duì)字符串的任何操作均會(huì)導(dǎo)致分配一個(gè)完整的新字符串。這種操作的代價(jià)相對(duì)比較高,而且在大型字符串上、大型數(shù)據(jù)集上或緊湊循環(huán)中執(zhí)行時(shí),接連不斷的重復(fù)的字符串可能發(fā)展成性能問(wèn)題。

此外,由于 N 個(gè)字符串連接需要分配 N–1 個(gè)中間字符串,串行連接也可能成為托管內(nèi)存壓力的主要原因。

如果必須在緊湊循環(huán)中或每幀期間對(duì)字符串進(jìn)行連接,請(qǐng)使用 StringBuilder 執(zhí)行實(shí)際連接操作。為最大限度減少不必要的內(nèi)存分配,可重復(fù)使用 StringBuilder 實(shí)例。

Microsoft 整理了一份處理 C# 中的字符串的最佳做法清單,可在這里的 MSDN 網(wǎng)站上找到該清單:msdn.microsoft.com。

區(qū)域約束與序數(shù)比對(duì)

在與字符串相關(guān)的代碼中經(jīng)常出現(xiàn)的核心性能問(wèn)題之一是無(wú)意間使用了緩慢的默認(rèn)字符串 API。這些 API 是為商業(yè)應(yīng)用程序構(gòu)建的,可根據(jù)與文本字符有關(guān)的多種不同區(qū)域性和語(yǔ)言規(guī)則來(lái)處理字符串。

例如,在美國(guó)英語(yǔ)區(qū)域設(shè)置下運(yùn)行時(shí),以下示例代碼將返回 true,但在許多歐洲區(qū)域設(shè)置下,將返回 false (1)

注意: 從 Unity 5.3 和 5.4 開始,Unity 的腳本運(yùn)行時(shí)始終在美國(guó)英語(yǔ) (en-US) 區(qū)域設(shè)置下運(yùn)行:

? ?String.Equals("encyclopedia", "encyclop?dia");

對(duì)于大多數(shù) Unity 項(xiàng)目,上述代碼完全沒(méi)有必要。使用序數(shù)比對(duì)可將速度提高大約十倍,這種比較類型以 C 和 C++ 工程師熟悉的方式比較字符串:簡(jiǎn)單地比較字符串的每個(gè)連續(xù)字節(jié),不考慮該字節(jié)所表示的字符。

切換至序數(shù)比對(duì)的方式非常簡(jiǎn)單,只需將?StringComparison.Ordinal?作為最終參數(shù)提供給?String.Equals

myString.Equals(otherString, StringComparison.Ordinal);

低效的內(nèi)置字符串 API

除了切換至序數(shù)比對(duì)以外,目前已知某些 C#?String?API 的效率極低,其中包括?String.Format、String.StartsWith?和?String.EndsWith。盡管?String.Format?難以替換,但低效率字符串比較方法很容易優(yōu)化掉。

盡管 Microsoft 建議將?StringComparison.Ordinal?傳遞給任何不需要為本地化做調(diào)整的字符串比較,但 Unity 基準(zhǔn)測(cè)試表明,相比自定義實(shí)現(xiàn),該方法對(duì)性能的提升效果有限。

方法????????????????????????????????????????????????????????100k 短字符串的時(shí)間(毫秒)

String.StartsWith,默認(rèn)區(qū)域性????????????????????????????????137

String.EndsWith,默認(rèn)區(qū)域性????????????????????????????????????542

String.StartsWith,序數(shù)???????????????????????????????????????????115

String.EndsWith,序數(shù)????????????????????????????????????????????? ? 34

自定義?StartsWith?替換????????????????????????????????????????????????4.5

自定義?EndsWith?替換????????????????????????????????????????????????????4.5

String.StartsWith?和?String.EndsWith?均可以替換為類似于以下示例的簡(jiǎn)單的手工編碼版本。

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);

}

正則表達(dá)式

盡管正則表達(dá)式是匹配和操作字符串的強(qiáng)大方法,但它們可能對(duì)性能的影響極大。此外,由于 C# 庫(kù)的正則表達(dá)式實(shí)現(xiàn)方式,即使簡(jiǎn)單的布爾值?IsMatch?查詢也需要在底層分配大型瞬態(tài)數(shù)據(jù)結(jié)構(gòu)。除非在初始化期間,否則這種瞬態(tài)托管內(nèi)存波動(dòng)都是不可接受的。

如果必須使用正則表達(dá)式,強(qiáng)烈建議不要使用靜態(tài)?Regex.Match?或?Regex.Replace?方法,這些方法會(huì)將正則表達(dá)式視為字符串參數(shù)。這些方法即時(shí)編譯正則表達(dá)式,并且不緩存生成的對(duì)象。

以下示例代碼為無(wú)害的單行代碼。

Regex.Match(myString, "foo");

但是,該代碼每次執(zhí)行時(shí)會(huì)產(chǎn)生 5 KB 的垃圾。通過(guò)簡(jiǎn)單的重構(gòu)即可消除其中的大部分垃圾:

var myRegExp = new Regex("foo");?

myRegExp.Match(myString);

在本示例中,每次調(diào)用?myRegExp.Match“只”產(chǎn)生 320 字節(jié)的垃圾。盡管這對(duì)于簡(jiǎn)單的匹配操作仍然代價(jià)高昂,但比前面的示例有了相當(dāng)大的改進(jìn)。

因此,如果正則表達(dá)式是不變的字符串字面值,通過(guò)將正則表達(dá)式傳遞為正則表達(dá)式對(duì)象構(gòu)造函數(shù)的第一個(gè)參數(shù)來(lái)預(yù)編譯它們,可顯著提高效率。這些預(yù)編譯的正則表達(dá)式之后會(huì)被重用。

XML、JSON 和其他長(zhǎng)格式文本解析

解析文本通常是加載期間所發(fā)生的最繁重的操作之一。在某些情況下,解析文本所花費(fèi)的時(shí)間可能超過(guò)加載和實(shí)例化資源所花費(fèi)的時(shí)間。

此問(wèn)題背后的原因取決于所使用的具體解析器。C# 的內(nèi)置 XML 解析器極為靈活,但因此無(wú)法針對(duì)具體數(shù)據(jù)布局進(jìn)行優(yōu)化。

許多第三方解析器都是基于反射構(gòu)建的。盡管反射在開發(fā)過(guò)程中是絕佳選擇(因?yàn)樗茏尳馕銎骺焖龠m應(yīng)不斷變化的數(shù)據(jù)布局),但眾所周知,它的速度非常慢。

Unity 引入了采用其內(nèi)置?JSONUtility?API 的部分解決方案,該解決方案提供了讀取/發(fā)出 JSON 的 Unity 序列化系統(tǒng)接口。在大多數(shù)基準(zhǔn)測(cè)試中,它比純 C# JSON 解析器快,但它與 Unity 序列化系統(tǒng)的其他接口具有相同的限制:沒(méi)有額外代碼的情況下,無(wú)法對(duì)許多復(fù)雜的數(shù)據(jù)類型(如字典)進(jìn)行序列化(2)(注意: 請(qǐng)參閱?ISerializationCallbackReceiver?接口,了解如何通過(guò)一種方法輕松添加必要的額外處理以便在 Unity 序列化過(guò)程中來(lái)回轉(zhuǎn)換復(fù)雜數(shù)據(jù)類型)。

當(dāng)遇到文本數(shù)據(jù)解析所引起的性能問(wèn)題時(shí),請(qǐng)考慮三種替代解決方案。

方案 1:在構(gòu)建時(shí)解析

避免文本解析成本的最佳方法是完全取消運(yùn)行時(shí)文本解析。通常,這意味著通過(guò)某種構(gòu)建步驟將文本數(shù)據(jù)“烘焙”成二進(jìn)制格式。

大多數(shù)選擇使用該方法的開發(fā)者會(huì)將其數(shù)據(jù)移動(dòng)到某種 ScriptableObject 衍生的類層級(jí)視圖中,然后通過(guò) AssetBundle 分配數(shù)據(jù)。有關(guān)使用 ScriptableObjects 的精彩討論,請(qǐng)參閱 youtube 上?Richard Fine 的 Unite 2016 講座。

該策略可實(shí)現(xiàn)盡可能高的性能,但只適用于不需要?jiǎng)討B(tài)生成的數(shù)據(jù)。它適用于游戲設(shè)計(jì)參數(shù)和其他內(nèi)容。

方案 2:拆分和延遲加載

第二種可行的方法是將必須解析的數(shù)據(jù)拆分為較小的數(shù)據(jù)塊。拆分后,解析數(shù)據(jù)的成本可分?jǐn)偟蕉鄠€(gè)幀。在理想的情況下,可識(shí)別出為用戶提供所需體驗(yàn)而需要的特定數(shù)據(jù)部分,然后只加載這些部分。

舉一個(gè)簡(jiǎn)單的例子:如果項(xiàng)目為平臺(tái)游戲,則沒(méi)必要將所有關(guān)卡的數(shù)據(jù)一起序列。如果將數(shù)據(jù)拆分為每個(gè)關(guān)卡的獨(dú)立資源,并且將關(guān)卡劃分到區(qū)域中,則可以在玩家闖關(guān)到相應(yīng)位置時(shí)再解析數(shù)據(jù)。

雖然這聽起來(lái)不難,但實(shí)際上需要在工具編碼方面投入大量精力,并可能需要重組數(shù)據(jù)結(jié)構(gòu)。

方案 3:線程

如果數(shù)據(jù)完全解析成純 C# 對(duì)象,并且不需要與 Unity API 進(jìn)行任何交互,則可以將解析操作移至工作線程。

該方案在具有大量核心的平臺(tái)上非常強(qiáng)大(3)(注意: iOS 設(shè)備最多有 2 個(gè)核心。大多數(shù) Android 設(shè)備具有 2–4 個(gè)核心。該技術(shù)適用于針對(duì)電腦平臺(tái)和游戲主機(jī)發(fā)布的項(xiàng)目。)但是,該方案需要仔細(xì)編程,以免產(chǎn)生死鎖和競(jìng)態(tài)條件。

選擇實(shí)現(xiàn)線程的項(xiàng)目通常使用內(nèi)置的 C#?Thread?和?ThreadPool?類(請(qǐng)參閱?msdn.microsoft.com)來(lái)管理其工作線程以及標(biāo)準(zhǔn) C# 同步類。

腳注

  • (1) 請(qǐng)注意,從 Unity 5.3 和 5.4 開始,Unity 的腳本運(yùn)行時(shí)始終在美國(guó)英語(yǔ) (en-US) 區(qū)域設(shè)置下運(yùn)行。

  • (2) 請(qǐng)參閱?ISerializationCallbackReceiver?接口,了解如何通過(guò)一種方法輕松添加必要的額外處理以便在 Unity 序列化過(guò)程中來(lái)回轉(zhuǎn)換復(fù)雜數(shù)據(jù)類型。

  • (3) 請(qǐng)注意,iOS 設(shè)備最多有 2 個(gè)核心。大多數(shù) Android 設(shè)備具有 2–4 個(gè)核心。該技術(shù)適用于針對(duì)電腦平臺(tái)和游戲主機(jī)發(fā)布的項(xiàng)目。

字符串和文本的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
平原县| 玉林市| 应城市| 台安县| 胶南市| 英山县| 英德市| 内江市| 邻水| 宾阳县| 南溪县| 金沙县| 留坝县| 当雄县| 蒙山县| 醴陵市| 杭锦旗| 日喀则市| 青川县| 广昌县| 晋城| 富民县| 菏泽市| 邢台市| 新丰县| 河曲县| 临沧市| 广德县| 白玉县| 石泉县| 荥阳市| 延庆县| 潢川县| 洛南县| 赣榆县| 虹口区| 黔江区| 嘉定区| 鄂托克旗| 班戈县| 广平县|