通过解析库探究函数式抽象代价 ( ini 解析示例补充)

上一篇 用 HexColor 作为示例,可能过于简单

这里再补充一个 ini 解析的示例

由于实在写不动用其他库解析 ini 了, 春节都要过完了,累了,写不动了,

所以随意找了一份解析ini的库, 仅供参考,对比不准确,毕竟完整库包含了更多功能

先看看结果


BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3085/23H2/2023Update/SunValley3)
Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
.NET SDK 8.0.200
  [Host]     : .NET 8.0.2 (8.0.224.6711), X64 RyuJIT AVX2
  DefaultJob : .NET 8.0.2 (8.0.224.6711), X64 RyuJIT AVX2


Method Mean Error StdDev Gen0 Gen1 Allocated
Hande_Ini 567.9 ns 11.24 ns 21.66 ns 0.2851 - 1.75 KB
RuQu_Ini 1,691.4 ns 33.48 ns 64.51 ns 0.4177 - 2.56 KB
IniDataParser 4,836.3 ns 94.44 ns 167.87 ns 1.1215 0.0076 6.91 KB
// * Legends *
  Mean      : Arithmetic mean of all measurements
  Error     : Half of 99.9% confidence interval
  StdDev    : Standard deviation of all measurements
  Gen0      : GC Generation 0 collects per 1000 operations
  Gen1      : GC Generation 1 collects per 1000 operations
  Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
  1 ns      : 1 Nanosecond (0.000000001 sec)

总结

  • delegate 肯定会有调用消耗,ini 场景使用的函数远多于 HexColor ,可以看到消耗大了很多

先看来自 dotnet Microsoft.Extensions.Configuration 中解析 ini 的代码

public static IDictionary<string, string?> Read(string content)
{
    var data = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
    using (var reader = new StringReader(content))
    {
        string sectionPrefix = string.Empty;

        while (reader.Peek() != -1)
        {
            string rawLine = reader.ReadLine()!; // Since Peak didn't return -1, stream hasn't ended.
            string line = rawLine.Trim();

            // Ignore blank lines
            if (string.IsNullOrWhiteSpace(line))
            {
                continue;
            }
            // Ignore comments
            if (line[0] is ';' or '#' or '/')
            {
                continue;
            }
            // [Section:header]
            if (line[0] == '[' && line[line.Length - 1] == ']')
            {
                // remove the brackets
                sectionPrefix = string.Concat(line.AsSpan(1, line.Length - 2).Trim(), ConfigurationPath.KeyDelimiter);
                continue;
            }

            // key = value OR "value"
            int separator = line.IndexOf('=');
            if (separator < 0)
            {
                throw new FormatException(rawLine);
            }

            string key = sectionPrefix + line.Substring(0, separator).Trim();
            string value = line.Substring(separator + 1).Trim();

            // Remove quotes
            if (value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"')
            {
                value = value.Substring(1, value.Length - 2);
            }

            if (data.ContainsKey(key))
            {
                throw new FormatException(key);
            }

            data[key] = value;
        }
    }
    return data;
}

再来看看部分函数语义优化的代码

public class Ini
{
    private static Ini instance = new Ini();

    public Func<IPeeker<char>, bool> WhiteSpace = Chars.IngoreWhiteSpace.Map(i => i > 0);

    public Func<IPeeker<char>, bool> Comment = Chars.In(";#/").Delimited(Chars.NotCRLF.ToSlice().Opt(), Chars.IsCRLF, "Comment not right").Map((c, x, y, z) => c);

    public Func<IPeeker<char>, string> SectionName = Chars.Is('[').Delimited(Chars.Not(']').ToSlice().Once("Section name is required."), Chars.Is(']'), "Section name must end with ']'").Map((c, x, y, z) => y?.ToString());

    public Func<IPeeker<char>, string> Key = Chars.Not('=').ToSlice().Once("key is required.").Map(i => i.ToString());
    public Func<IPeeker<char>, char> Separater = Chars.Is('=').Once("Section name is required.");

    public Func<IPeeker<char>, string> Value = Chars.NotCRLF.ToSlice().Once("value is required.").Map(i =>
    {
        var v = i.ToString().Trim();
        return v.StartsWith('"') ? v[1..^1] : v;
    });

    public IDictionary<string, string> ParseString(string content)
    {
        var input = Input.From(content);
        var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        while (input.TryPeek(out var v))
        {
            if (!(WhiteSpace(input) || Comment(input) || Section(input, dict)))
            {
                throw new NotSupportedException(v.ToString());
            }
        }
        return dict;
    }

    public bool SectionContentEnd(IPeeker<char> input)
    {
        return !input.TryPeek(out var v) || v is '[';
    }

    public bool Section(StringPeeker input, Dictionary<string, string> dict)
    {
        var name = SectionName(input);
        if (name == null) return false;
        while (!SectionContentEnd(input))
        {
            if (WhiteSpace(input) || Comment(input))
            {
                continue;
            }
            SectionKV(input, dict, name);
        }
        return true;
    }

    public void SectionKV(StringPeeker input, Dictionary<string, string> dict, string name)
    {
        var k = Key(input);
        Separater(input);
        var v = Value(input);
        k = $"{name}:{k.Trim()}";
        dict.Add(k, v.Trim());
    }

    public static IDictionary<string, string> Parse(string content)
    {
        return instance.ParseString(content);
    }
}

最后截取 部分 ini 解析库的代码 仅供参考

public IniData Parse(string iniDataString)
{
    IniData iniData = (Configuration.CaseInsensitive ? new IniDataCaseInsensitive() : new IniData());
    iniData.Configuration = Configuration.Clone();
    if (string.IsNullOrEmpty(iniDataString))
    {
        return iniData;
    }

    _errorExceptions.Clear();
    _currentCommentListTemp.Clear();
    _currentSectionNameTemp = null;
    try
    {
        string[] array = iniDataString.Split(new string[2] { "\n", "\r\n" }, StringSplitOptions.None);
        for (int i = 0; i < array.Length; i++)
        {
            string text = array[i];
            if (text.Trim() == string.Empty)
            {
                continue;
            }

            try
            {
                ProcessLine(text, iniData);
            }
            catch (Exception ex)
            {
                ParsingException ex2 = new ParsingException(ex.Message, i + 1, text, ex);
                if (Configuration.ThrowExceptionsOnError)
                {
                    throw ex2;
                }

                _errorExceptions.Add(ex2);
            }
        }

        if (_currentCommentListTemp.Count > 0)
        {
            if (iniData.Sections.Count > 0)
            {
                iniData.Sections.GetSectionData(_currentSectionNameTemp).TrailingComments.AddRange(_currentCommentListTemp);
            }
            else if (iniData.Global.Count > 0)
            {
                iniData.Global.GetLast().Comments.AddRange(_currentCommentListTemp);
            }

            _currentCommentListTemp.Clear();
        }
    }
    catch (Exception item)
    {
        _errorExceptions.Add(item);
        if (Configuration.ThrowExceptionsOnError)
        {
            throw;
        }
    }

    if (HasError)
    {
        return null;
    }

    return (IniData)iniData.Clone();
}

完整代码参考 https://github.com/fs7744/ruqu

posted @ 2024-02-15 17:06  victor.x.qu  阅读(215)  评论(0编辑  收藏  举报