之前寫過文章談Javascript字串的特殊字元處理,今天在研究如何自己DIY處理簡單的JSON轉換(網站非.NET 3.5,沒法用內建的JavaScriptSerializer;也不想引用JSON.NET增加部署複雜性),無意中找到完整的特殊字元轉換規格,就再寫了一次更完整的轉換函數。

由於要換的字元數高達10個,想到是否該用Dictionary<string, string>來維護置換對照表? 用Dictionary改寫後的程式碼看起來雖然比較高級,但動用物件又衍生額外迴圈的代價,是多少會拖累效能。會慢多少? 值不值得? 要試試才知!

順便寫了一個300萬次迴圈的測試,結果是8,181ms vs 6,145ms,Replace Hard-Coding串接法明顯勝出,快了1/3以上,最終版本就決定用直接Replace串接法。

【2009-09-03更新】Ammon大提示了比String.Replace更有效率的StringBuilder.Replace, 但可能本案例中只Replace10次,次數不算多,被建立StringBuilder物件的額外耗損抵消,實測時StringBuilder.Replace並未比String.Replace快。但若Replace次數更大時,有可能就足以產生明顯優勢。

【2009-09-06更新】關於StringBuilder與String的字串相接效能比較,可以參考這篇文章

class TestEscapeJSString
{
    /// <summary>
    /// Replace characters for Javscript string literals
    /// </summary>
    /// <param name="text">raw string</param>
    /// <returns>escaped string</returns>
    public static string EscapeStringForJS(string s)
    {
        //REF: http://www.javascriptkit.com/jsref/escapesequence.shtml
        return s.Replace(@"\", @"\\")
                .Replace("\b", @"\b")
                .Replace("\f", @"\f")
                .Replace("\n", @"\n")
                .Replace("\0", @"\0")
                .Replace("\r", @"\r")
                .Replace("\t", @"\t")
                .Replace("\v", @"\v")
                .Replace("'", @"\'")
                .Replace(@"""", @"\""");
    }
 
    public static void Test()
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        string s = "I'm PC!\n\"I\tam\tPC!\"\n";
        for (int i = 0; i < 3000000; i++)
        {
            string t = EscapeStringForJS(s);
        }
        sw.Stop();
        Console.WriteLine("Duration {0}ms", sw.ElapsedMilliseconds);
    }
 
    //Method 2, 
    //Using Dictionary to store the escape characters mapping
    //But it's slower than hard-coding style
    //3,000,000 times: 6,145ms vs 8,181ms
    static Dictionary<string, string> escMap =
         new Dictionary<string, string>()
    { 
        {"\b", @"\b"}, {"\f", @"\f"}, {"\n", @"\n"}, 
        {"\0", @"\0"}, {"\r", @"\r"}, {"\t", @"\t"}, 
        {"\v", @"\v"}, {"'", @"\'"}, {@"""", @"\"""} 
    };
    public static string EscapeStringForJSX(string s)
    {
        s = s.Replace(@"\", @"\\");
        foreach (string k in escMap.Keys)
            s = s.Replace(k, escMap[k]);
        return s;
    }
}

Comments

# by Ammon

多次的 Replace 應使用 StringBuilder 的 Replace 方法 return new StringBuilder(s).Replace(@"\", @"\\").Replace("\b", @"\b").Replace("\f", @"\f").ToString();

# by Jeffrey

to Ammon, 謝謝你的提示,又學到新東西。很興奮地動手做了測試,可能是只Replace 10次次數不夠多,Replace提升的效率被新建StringBuilder物件的額外損耗抵消(亦或是Compiler對String.Replace做了最佳化),實測下來,StringBuilder法以7,668ms vs 6,391ms輸給String.Replace。不過,這個提示很寶貴,我加註到本文中,感謝!

# by laneser

我也很興奮地測了一下我愛用的 Regex... 大輸! 然後把測試的字串 duplicate , StringBuilder.Replace 依然輸給 String.Replace (這很怪, 也許我應該追究其原因) 唯一有一點貢獻的是, 用 Dictionary 做 replace 時可以用 foreach (var k in escMap) s = s.Replace(k.Key, k.Value); 這樣會比去查 Dictionary 快一些.

# by SlimeMeteor

據我我知道的部份 StringBuilder 會比 String 快的地方是 StringBuilder.Append("String Temp"); StringBuilder.AppendLine("String Temp") 這種 會比 StringA += "String Temp"; 要來得快速,至於 Replace 只是針對物件內部的元素作調整,跟串接無關的狀況下,或許沒機會提升效能吧。 總之 StringBuilder 用在 "串接字串" 方面會比較快 至於 Replace() 的部份花費的資源應該是一樣的 取得原始字串 -> 逐一比對 -> 放置到新的陣列 以上,若有誤歡迎指教

# by SlimeMeteor

對了,我另外想到的東西是關於參考技術。 假設 public static string EscapeStringForJS(string s) 改為 public static void EscapeStringForJS(ref string s) 的狀況下,函式內不使用 return 而是直接對傳入的物件參考進行操作,這樣大量作業時,是否可以有更佳的效能呢? 有興趣的可以 try try see XD

# by SlimeMeteor

實測結果: 實驗了很多其他的方法都沒有原來的快 ... orz 另外發現 .Replace() 的先後順序有可能會影響速度 ... 奇怪,用了 ref 之後反而變慢了 XD 是因為 Loop 的關係嗎?

# by Ammon

StringBuilder 和 String 對字串操作最大的差異在於效能 當使用量大的時候 StringBuilder 很明顯吃CPU%較少 單機用迴圈跑不準,請放到 Web 上用壓力測試的方式做,當使用者量多的時候就有非常明顯的差異,我於正式環境實際體驗過兩次。 養成好習慣,不論是 += 或 Replace,都改用StringBuilder (但還是要視狀況使用,而非濫用)。 附上微軟提供的參考資料 String concatenation is notoriously inefficient. It involves character copies on the order of "l * n * n," where "l" is the average string length, and "n" is the number of substrings that are concatenated together. When "n" is a large value, your application can dramatically slow, and the concatenation can exhaust both the CPU and the heap. StringBuilder efficiency is on the order of "n * l," if you can reasonably estimate the buffer size in advance. StringBuilder is somewhat less efficient if it needs to grow the internal buffer but is still much better than concatenation. http://blogs.msdn.com/tess/archive/2006/06/22/643309.aspx PRB: High CPU Utilization in Web Service or Web Form http://support.microsoft.com/kb/307340/en-us How to improve string concatenation performance in Visual Basic .NET or in Visual Basic 2005 http://support.microsoft.com/kb/306821/EN-US/ ASP.NET Case Study: High CPU in GC - Large objects and high allocation rates http://blogs.msdn.com/tess/archive/2006/06/22/643309.aspx

# by Ark

2個想法牽拖 StringBuilder 的 Methods 較String少所以可能呼叫時比較快對到, 但是要new Regex Methods 可能多載的關係 可能也會比較慢對到

# by Jeffrey

"養成好習慣,不論是 += 或 Replace,都改用StringBuilder (但還是要視狀況使用,而非濫用)。" 幫忙補充一個不適用StringBuilder的例子: 靜態字串的串接用Literal String就好了。http://blog.darkthread.net/blogs/darkthreadtw/archive/2007/12/15/stringbuilder-for-static-string-concate.aspx 我覺得RegEx.Replace強在彈性而非效能,因此比對的程式邏輯會比String或StringBuilder的Replace複雜很多,用在這種簡單的罝換上速度會吃虧。 我有幫StringBuilder寫過文章(就是上述連結說的那篇"平反文"),探討其效能強處及記憶體耗用狀況,晚點再整理一下貼出來。

Post a comment