Hiro与其他IoC容器的比较

12
这篇文章(2009年4月11日),作者声称Hiro是:

“世界上速度最快的IOC容器……是一个静态预编译的IOC容器,其性能与没有IOC容器的应用程序一样快”。

它是否仍然是当前最快的IOC容器?它是否已经可以用于生产环境?还有其他容器可以在编译时进行IOC吗?相对于其他IOC容器,它的主要优缺点是什么?

谢谢


10
你是否真的需要在IoC容器中获得绝对的最高速度?在99%的应用程序中,它从来不是瓶颈,是否值得这样做? - Mauricio Scheffer
简单服务定位器声称也非常快:http://simpleservicelocator.codeplex.com/discussions/236029。我敢打赌它会胜过Hiro。 - Steven
1
@caveman:我会重新考虑这样做,因为我认为你的应用程序会花费一些非常重要的时间在垃圾回收上。 - Mauricio Scheffer
@ Mauricio Scheffer,@ Steven:感谢您的建议。 @ Steven:我刚刚进行了一次简单的测试,SimpleServiceLocator比Autofac快36倍。但我认为它可能比Hiro慢几倍(基于此基准)。 - user593358
5
@caveman,你每秒钟创建的组件,如果它是如此重要的路径,请为什么不回退到使用“new”?通常优化意味着对99%的代码使用最清晰的工具/技术/模式,然后对于那热门的1%,使用最快速的方式... - Nicholas Blumhardt
显示剩余5条评论
1个回答

20
Hiro声称是最快的容器。这种说法基于作者给出的基准测试(请参见here,以获取多个容器之间的客观比较)。无论这个基准测试是否现实取决于您的应用程序大小。基准测试似乎巧妙地设置了一个非常小的注册类型集合。当向基准测试添加更多注册时,性能开始下降(请参见下面的示例基准测试)。仔细观察可以发现,Hiro具有O(n)的性能特征,而普通的DI框架具有O(1)的特征,因此其他框架的性能随着注册类型数量的增加保持不变。
Hiro的好处在于它会即时生成新的程序集,并且解析新类型只需要进行单个接口调用。这非常快。另一方面,大多数常见的DI框架在调用GetInstance时总是需要进行字典查找。Hiro的GetInstance方法在内部基本上是一个大的switch case语句,实现为一堆if语句。基于if的switch case语句在某个点之前是非常快的。C#编译器使用18个case语句作为转折点的启发式方法。低于该数字,编译器将生成一堆if语句。超过该数字,它将创建并存储一个静态字典,并进行字典查找,因为在18个以上的查找中,字典查找的性能更快。
很抱歉,由于您的内容中包含代码术语和专业词汇,我需要更多上下文信息才能进行翻译。请提供更详细的信息或说明您需要什么帮助。
请看这篇文章(以及后续文章),它对IOC框架进行了很好的(功能)比较。作者有自己认为重要的东西。你需要为自己列出这样的清单。性能似乎是你的首选。但请注意,还有其他提高性能的方法。例如,通过将类型注册为单例来防止创建多个类型。

以下是与其他容器的小型比较。请注意,我没有编写Hiro,因此可能会遗漏一些内容,尤其是因为似乎没有任何文档可供参考,但是我们来看看:

  1. 与大多数IOC框架只需要1或2个程序集不同,Hiro需要4个程序集才能运行。
  2. 当解决递归依赖关系时,它会抛出堆栈溢出异常(顺便说一下,大多数框架都会这样做)。因此,它没有循环依赖关系检测。
  3. 似乎不支持任何除瞬态之外的生命周期(作者说它支持,但我目前看不到对此的支持)。实际上,这对性能来说是不好的,因为大多数服务通常会被注册为单例。根据作者的说法,它支持多种生命周期。
  4. 不支持解析开放式泛型类型。
  5. 不支持未注册类型的解析。
  6. 除XML(智能感知)文档外,它没有其他文档。

还有其他可以在编译时进行IOC的容器吗?

请定义“编译时”。Hiro在运行时动态生成一个新的程序集。其他容器(如AutofacWindsorSimple Injector)在内部发出IL或编译委托。这与一次性编译完整程序集的速度并没有差别(但是,调用委托的开销比进行接口调用稍微慢一些)。

顺便说一句,我改变了基准测试以看到上述描述的行为。注册50个额外类型后,您将看到Hiro的性能已经慢了三倍,与初始基准测试相比。这是我使用的代码:

public interface IHandler<T> { }

public class Handler<T> : IHandler<T> { }

public class HiroUseCase : UseCase
{
    IMicroContainer container;

    private static void RegisterHandler<T>(DependencyMap map)
    {
        map.AddService(typeof(IHandler<T>), typeof(Handler<T>));
    }       

    public HiroUseCase()
    {
        var map = new DependencyMap();

        // *** My added registrations
        RegisterHandler<byte>(map);
        RegisterHandler<byte?>(map);
        RegisterHandler<short>(map);
        RegisterHandler<short?>(map);
        RegisterHandler<ushort>(map);
        RegisterHandler<ushort?>(map);
        RegisterHandler<int>(map);
        RegisterHandler<int?>(map);
        RegisterHandler<uint>(map);
        RegisterHandler<uint?>(map);

        RegisterHandler<long>(map);
        RegisterHandler<long?>(map);
        RegisterHandler<ulong>(map);
        RegisterHandler<ulong?>(map);
        RegisterHandler<float>(map);
        RegisterHandler<float?>(map);
        RegisterHandler<double>(map);
        RegisterHandler<double?>(map);
        RegisterHandler<decimal>(map);
        RegisterHandler<decimal?>(map);

        RegisterHandler<DateTime>(map);
        RegisterHandler<DateTime?>(map);
        RegisterHandler<char>(map);
        RegisterHandler<char?>(map);
        RegisterHandler<object>(map);
        RegisterHandler<string>(map);
        RegisterHandler<bool>(map);
        RegisterHandler<bool?>(map);
        RegisterHandler<Enum>(map);
        RegisterHandler<DateTimeKind>(map);

        RegisterHandler<DateTimeKind?>(map);
        RegisterHandler<DateTimeOffset>(map);
        RegisterHandler<DateTimeOffset?>(map);
        RegisterHandler<DayOfWeek>(map);
        RegisterHandler<DayOfWeek?>(map);
        RegisterHandler<DBNull>(map);
        RegisterHandler<Delegate>(map);
        RegisterHandler<DivideByZeroException>(map);
        RegisterHandler<DllNotFoundException>(map);
        RegisterHandler<Exception>(map);

        RegisterHandler<KeyNotFoundException>(map);
        RegisterHandler<InvalidOperationException>(map);
        RegisterHandler<InvalidCastException>(map);
        RegisterHandler<InvalidProgramException>(map);
        RegisterHandler<InvalidTimeZoneException>(map);
        RegisterHandler<IDisposable>(map);
        RegisterHandler<IComparable>(map);
        RegisterHandler<IEquatable<int>>(map);
        RegisterHandler<IEnumerable>(map);
        RegisterHandler<IEqualityComparer>(map);

        // *** Original benchmark setup
        map.AddService(typeof(IWebApp), typeof(WebApp));
        map.AddService(typeof(IAuthenticator), typeof(Authenticator));
        map.AddService(typeof(IStockQuote), typeof(StockQuote));
        map.AddService(typeof(IDatabase), typeof(Database));
        map.AddService(typeof(IErrorHandler), typeof(ErrorHandler));
        map.AddService(typeof(ILogger), typeof(Logger));

        IContainerCompiler compiler = new ContainerCompiler();
        var assembly = compiler.Compile(map);;

        var loadedAssembly = assembly.ToAssembly();
        var containerType = loadedAssembly.GetTypes()[0];
        container = (IMicroContainer)Activator
            .CreateInstance(containerType);
    }

    public override void Run()
    {
        var webApp = 
            (IWebApp)container.GetInstance(typeof(IWebApp), null);
        webApp.Run();
    }
}

1
@Steven,非常有趣的分析...我本来以为Hiro的代码生成会更像new A(new B(new C()), new D())...看起来错过了一个机会。不过,许多容器(例如Windsor)确实会发出几乎等效的IL。 - Nicholas Blumhardt
1
分析得很好,但你错过了一些要点,Steven:1)Hiro使用ILMerge,使发布版本只有一个程序集,2)如果您使依赖项变为惰性并在运行时解决它,则存在循环DI,3)Hiro确实支持其他生命周期(包括Singleton、Transient和Once-per-web session),4和5)是的,你抓住我了——我故意将Open Generics从Hiro中删除,因为那已经在LinFu中了,6)它有相当多的XML文档,使用起来非常简单。所见即所得 :) - plaureano
1
已修复:我修改了Hiro,使用OpCodes.Switch指令来在O(1)时间内解决服务问题:http://is.gd/62qV7T --再次感谢Steve的反馈! - plaureano
1
@plaureano:我正在尽我最大的努力获取对您的库的客观看法,并根据此更改我的答案,但这开始变得有点尴尬。即使使用1.01版本,它仍然无法编译。我仍然收到与之前相同的错误消息。您发布“可供生产使用”的软件,但实际上无法编译。您真的测试过您的软件吗? - Steven
2
@Steve:顺便说一下,我确实会测试我的软件,但如果你试图使用一个与Hiro alpha版本相比完全不同的生产版本进行基准测试,我无法帮助你。在生产版本中,IContainerCompiler类是公共的,因此显然存在版本问题,而不是测试问题。 - plaureano
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接