65.9K
CodeProject 正在变化。 阅读更多。
Home

Fluentx:一个特殊的 .NET 库

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.64/5 (7投票s)

2014年9月29日

CPOL

4分钟阅读

viewsIcon

14445

Fx.Switch 的替代方案 "Fluentx:一个特殊的 .NET 库"

引言

我评论过 Fluentx 库由于过度使用委托而速度较慢,现在我被要求提供事实依据。

Fx.Switch 原始代码

原始 Fx.Switch 示例

        static int FxSwitch(object obj)
        {
            int result = -1;

            Type typ = obj.GetType();

            var fx = Fx.Switch(typ);

                fx.Case<int>().Execute(() =>
                {
                    result = 1;
                })
                .Case<string>().Execute(() =>
                {
                    result = 2;
                })
                .Default(() =>
                {
                    result = 0;
                });

            return result;
        }

简单替代方案

        static int SimpleSwitch(object obj)
        {
            int result = -1;

            Type typ = obj.GetType();

            switch (Type.GetTypeCode(typ))
            {
                case TypeCode.Int32:
                    result = 1;
                    break;
           
                case TypeCode.String:
                    result = 2;
                    break;

                default:
                    result = 0;
                    break;
            }
           
            return result;
        }

速度比较

方法:经过适当的预热以消除 JIT 编译成本后,平均 100 万次迭代。

Fx.Switch     0.7497169 us
SimpleSwitch  0.0748549 us

因此,两者之间的速度差异为 10 倍。

内存分配比较

Fx.Switch 每次调用分配大约 240 字节。以下是 100 万次调用的详细分解

allocation

委托是内存分配中最昂贵的,其次是 Fluentx.Fx 对象。 CaseInfo[] 和 CaseInfo 对象是作为 List<CaseInfo> 的一部分分配的,List<CaseInfo> 由 Fluentx.Fx 对象持有。

SimpleSwitch 在第一次运行后不需要分配任何内存。

IL 大小和 JIT 编译比较

这是 IL 大小和方法 JIT 编译的比较

 97 230 Fluentx.Program.FxSwitch(class System.Object) JIT FluentxTest.exe
 26  58 Fluentx.Fx.Switch(class System.Type) JIT Fluentx.dll
 30  60 Fluentx.Fx.Fluentx.ISwitchTypeBuilder.Case() JIT Fluentx.dll
 19  32 Fluentx.Fx.Fluentx.ISwitchTypeCaseBuilder.Execute(class System.Action) JIT Fluentx.dll
 30  93 Fluentx.Fx.Fluentx.ISwitchTypeBuilder.Case() JIT Fluentx.dll
 94 223 Fluentx.Fx.Fluentx.ISwitchTypeBuilder.Default(class System.Action) JIT Fluentx.dll
 8    6 Fluentx.Program+<>c__DisplayClass3.b__0() JIT FluentxTest.exe

 40  47 Fluentx.Program.SimpleSwitch(class System.Object) JIT FluentxTest.exe

FxSwitch 解决方案涉及 7 个方法,主程序中有 2 个,Fluentx.dll 中有 5 个。仅主方法 FxSwitch 就有 97 字节的 IL 指令,编译成 230 字节的机器码。

SimpleSwitch 实现要简单得多:单个方法,具有 40 字节 IL 和 47 字节机器码。

指令跟踪比较

如果您想知道为什么 FxSwitch 如此昂贵,或者想确切地了解它的实现方式,可以使用 windbg 和 "wt" 命令获取指令级别的跟踪。然后,您可以使用 PerfView 显示此类 "wt" 命令输出。

这是 FxSwitch 解决方案的指令跟踪摘要

fxSwitchInst

这表明 FxSwitch 解决方案需要 1,216 条指令,而没有考虑垃圾回收的额外成本。最昂贵的部分是在内部列表上调用 Linq.Enumerable.Last 方法,然后在其上进行枚举。

这是 FxSimple 解决方案的相同内容

FxSimpleInst

只有 113 条指令,主要在 GetType 和 GetTypeCode 方法中。

算法复杂度分析

对 switch 语句的期望是其时间复杂度应为常数。在 .Net 中,即使有足够的 case 标签,切换字符串的实现也会尝试实现这一期望。这是通过在第一次使用时构造一个 Dictionary<string, int> 查找表来实现的。

FxSwitch 解决方案的时间复杂度为:O(N * Ln(N)) + O(N) = O(N * Ln(N)),其中 N 是 case 标签的数量。因此,当用于更大数量的 case 标签时,其性能会更差。

原因是 Fx.Switch 的实现方式是首先构造一个内部 List,然后在该列表上执行线性搜索。通过不断附加来构造 List 的成本是 O(N * Ln(N)),因为该列表需要不时调整大小,分配更多内存并复制更多内存。由于该列表只构造一次,使用一次,然后丢弃,我认为可能根本不需要构造该列表。在这种情况下,时间复杂度将是线性的 O(N),远低于常数时间预期。

MultiDictionary(多重字典)

Fluentx 提供了一个名为 Multititionary(原文如此)的多重字典。这是一个使用示例

Multitionary<int, string> muldic = new Multitionary<int, string>();

muldic.Add(new Kuple<int, string>(1, "1"));
muldic.Add(new Kuple<int, string>(1, "one"));
muldic.Add(new Kuple<int, string>(2, "2"));
muldic.Add(new Kuple<int, string>(2, "two"));

Kuple<int, string> v = muldic[1];

构造函数看起来有点奇怪,因为它不允许用户指定比较器和初始大小,就像 Dictionary<K,V> 那样。

Add 调用看起来很奇怪,因为需要分配一个 Kuple 对象。

索引运算符甚至更奇怪,因为它返回单个值,而不是您期望的集合。要获取与键关联的所有值,您必须遍历整个字典。

如果深入了解,Multititionary 是使用单个 Kuple 列表实现的,因此无法像 Dictionary 中那样获得常数查找时间。

CLR 团队已经提供了一个具有正确 AP 表面和实现的 MultiValueDictionary。

结论

我希望通过这次讨论提出几点

  1. C# 和 CLR 对委托和 LINQ Enumerable 的使用进行了最少的优化。 当在小型主体上使用时,相对成本可能非常高。
  2. 可以编写性能相当不错的 C# 代码,但您需要知道要避免什么。 因此,一个好的策略是经常且尽早地衡量代码的性能。
  3. 编写高质量的代码很难,编写高质量的库代码更加困难。 因此,请在作为基本库代码共享您的代码时格外小心,并且不要在没有更深入地了解其工作原理的情况下使用其他人的库代码,尤其是在它没有源代码的情况下。

谢谢

 

© . All rights reserved.