0

編集

元の編集されていない質問が誤解を招く場合はお詫び申し上げます。

stringこの質問は、無効な XML 文字を から削除する方法を尋ねているのではありませ

私のコードをレビューするように頼んでいるわけではありません。

私が答えで探しているのは、署名付きの関数です

string <YourName>(string input, Func<char, bool> check);

と同等またはそれ以上のパフォーマンスが得られますRemoveCharsBufferCopyBlackList。理想的には、この関数はより一般的で、可能であれば読みやすいものにする必要がありますが、これらの要件は二次的なものです。


最近、文字列から無効な XML 文字を削除する関数を作成しました。私のアプリケーションでは、文字列が適度に長くなる可能性があり、無効な文字が発生することはめったにありません。この演習は私に考えさせました。セーフ マネージド c# でこれを行う方法と、私のシナリオで最高のパフォーマンスを提供する方法を教えてください。

これが私のテストプログラムです。 char を省略したものを「有効な XML 述語」に置き換えました'X'

class Program
{
    static void Main()
    {
        var attempts = new List<Func<string, Func<char, bool>, string>>
            {
                RemoveCharsLinqWhiteList,
                RemoveCharsFindAllWhiteList,
                RemoveCharsBufferCopyBlackList
            }

        const string GoodString = "1234567890abcdefgabcedefg";
        const string BadString = "1234567890abcdefgXabcedefg";
        const int Iterations = 100000;
        var timer = new StopWatch();

        var testSet = new List<string>(Iterations);
        for (var i = 0; i < Iterations; i++)
        {
            if (i % 1000 == 0)
            {
                testSet.Add(BadString);
            }
            else
            {
                testSet.Add(GoodString);
            }
        }

        foreach (var attempt in attempts)
        {
            //Check function works and JIT
            if (attempt.Invoke(BadString, IsNotUpperX) != GoodString)
            {
                throw new ApplicationException("Broken Function");       
            }

            if (attempt.Invoke(GoodString, IsNotUpperX) != GoodString)
            {
                throw new ApplicationException("Broken Function");       
            }

            timer.Reset();
            timer.Start();
            foreach (var t in testSet)
            {
                attempt.Invoke(t, IsNotUpperX);
            }

            timer.Stop();
            Console.WriteLine(
                "{0} iterations of function \"{1}\" performed in {2}ms",
                Iterations,
                attempt.Method,
                timer.ElapsedMilliseconds);
            Console.WriteLine();
        }

        Console.Readkey();
    }

    private static bool IsNotUpperX(char value)
    {
        return value != 'X';
    }

    private static string RemoveCharsLinqWhiteList(string input,
                                                      Func<char, bool> check);
    {
        return new string(input.Where(check).ToArray());
    }

    private static string RemoveCharsFindAllWhiteList(string input,
                                                      Func<char, bool> check);
    {
        return new string(Array.FindAll(input.ToCharArray(), check.Invoke));
    }

    private static string RemoveCharsBufferCopyBlackList(string input,
                                                      Func<char, bool> check);
    {
        char[] inputArray = null;
        char[] outputBuffer = null;

        var blackCount = 0;
        var lastb = -1;
        var whitePos = 0;

        for (var b = 0; b , input.Length; b++)
        {
            if (!check.invoke(input[b]))
            {
                var whites = b - lastb - 1;
                if (whites > 0)
                {
                    if (outputBuffer == null)
                    {
                        outputBuffer = new char[input.Length - blackCount];
                    }

                    if (inputArray == null)
                    {
                        inputArray = input.ToCharArray();
                    }

                    Buffer.BlockCopy(
                                      inputArray,
                                      (lastb + 1) * 2,
                                      outputBuffer,
                                      whitePos * 2,
                                      whites * 2);
                    whitePos += whites; 
                }

                lastb = b;
                blackCount++;
            }
        }

        if (blackCount == 0)
        {
            return input;
        }

        var remaining = inputArray.Length - 1 - lastb;
        if (remaining > 0)
        {
            Buffer.BlockCopy(
                              inputArray,
                              (lastb + 1) * 2,
                              outputBuffer,
                              whitePos * 2,
                              remaining * 2);

        }

        return new string(outputBuffer, 0, inputArray.Length - blackCount);
    }        
}

試行を実行すると、関数がより専門化されるにつれてパフォーマンスが向上することがわかります。この操作を実行するためのより高速で一般的な方法はありますか? または、一般的なオプションがない場合、より高速な方法はありますか?

私は実際には 'X' を削除することに興味がなく、実際には述語はより複雑であることに注意してください。

4

1 に答える 1

2

高いパフォーマンスが必要な場合は、これを行うために LINQ to Objects 別名列挙子を使用したくないことは確かです。また、文字ごとにデリゲートを呼び出さないでください。デリゲートの呼び出しは、実行している実際の操作に比べてコストがかかります。

RemoveCharsBufferCopyBlackList は良さそうです (文字ごとのデリゲート呼び出しを除く)。

ハードコーディングされたデリゲートの内容をインライン化することをお勧めします。条件を記述するさまざまな方法を試してみてください。最初に現在の文字を既知の適切な文字の範囲 (例: 0x20-0xFF) と照合し、一致する場合は通過させることにより、パフォーマンスが向上する場合があります。このテストはほとんどの場合合格するため、XML では無効な個々の文字に対する高価なチェックを省くことができます。

編集:少し前にこの問題を解決したことを思い出しました:

    static readonly string invalidXmlChars =
        Enumerable.Range(0, 0x20)
        .Where(i => !(i == '\u000A' || i == '\u000D' || i == '\u0009'))
        .Select(i => (char)i)
        .ConcatToString()
        + "\uFFFE\uFFFF";
    public static string RemoveInvalidXmlChars(string str)
    {
        return RemoveInvalidXmlChars(str, false);
    }
    internal static string RemoveInvalidXmlChars(string str, bool forceRemoveSurrogates)
    {
        if (str == null) throw new ArgumentNullException("str");
        if (!ContainsInvalidXmlChars(str, forceRemoveSurrogates))
            return str;

        str = str.RemoveCharset(invalidXmlChars);
        if (forceRemoveSurrogates)
        {
            for (int i = 0; i < str.Length; i++)
            {
                if (IsSurrogate(str[i]))
                {
                    str = str.Where(c => !IsSurrogate(c)).ConcatToString();
                    break;
                }
            }
        }

        return str;
    }
    static bool IsSurrogate(char c)
    {
        return c >= 0xD800 && c < 0xE000;
    }
    internal static bool ContainsInvalidXmlChars(string str)
    {
        return ContainsInvalidXmlChars(str, false);
    }
    public static bool ContainsInvalidXmlChars(string str, bool forceRemoveSurrogates)
    {
        if (str == null) throw new ArgumentNullException("str");
        for (int i = 0; i < str.Length; i++)
        {
            if (str[i] < 0x20 && !(str[i] == '\u000A' || str[i] == '\u000D' || str[i] == '\u0009'))
                return true;
            if (str[i] >= 0xD800)
            {
                if (forceRemoveSurrogates && str[i] < 0xE000)
                    return true;
                if ((str[i] == '\uFFFE' || str[i] == '\uFFFF'))
                    return true;
            }
        }
        return false;
    }

RemoveInvalidXmlChars は最初に ContainsInvalidXmlChars を呼び出して、文字列の割り当てを保存することに注意してください。ほとんどの文字列には無効な XML 文字が含まれていないため、楽観的になることができます。

于 2012-04-20T17:37:53.230 に答える