【问题标题】:Are there any concrete study of the performance impact of using ARC?是否有任何关于使用 ARC 对性能影响的具体研究?
【发布时间】:2012-09-13 16:26:06
【问题描述】:

我找不到有关 ARC 对现实项目的性能影响的客观研究。 The official doc

编译器有效地消除了许多无关的保留/释放调用,并且已投入大量精力来加速 Objective-C 运行时。特别是,当方法的调用者是 ARC 代码时,常见的“返回一个保留/自动释放的对象”模式要快得多,并且实际上并没有将对象放入自动释放池中。

已被技术迷转述/变形为“ARC 更快”。

我确定的就是我所测量的。我们最近将我们的 iOS 项目迁移到 ARC,我在代码的一些 CPU 密集型区域之前/之后进行了一些性能测量(生产代码,当然是使用 -Os 标志编译的)。

我观察到 70%(是 70%)的性能回归。使用 Instruments 跟踪保留/释放事件,我意识到编译器在您不会执行此操作的区域(在 ARC 之前的环境中)引入了很多保留/释放对。基本上,任何暂时的都会变强。我相信这就是性能回归的根源。

迁移之前的原始代码已经非常优化。几乎没有完成任何自动释放。因此,切换到 ARC 几乎没有改进空间。

幸运的是,Instruments 能够向我展示 ARC 引入的最昂贵的保留/释放,并且我能够使用 __unsafe_unretained 停用它们。这略微缓解了性能回归。

是否有人知道有关此技术或其他技术以避免性能损失的任何信息? (除了停用 ARC)

谢谢。

编辑: 我并不是说 ARC 因为性能影响而不好。使用 ARC 的优势在很大程度上优于性能回归(在我们的代码中,回归没有任何可见的效果,所以我放弃了)。我认为 ARC 是一项非常好的技术。我永远不会回到MRC。我更多是出于好奇而问这个问题。

我只是对有关该主题的绝大多数博客(例如hereand there)有点恼火,它们或多或少给人的印象是 ARC 代码将比 MRC 代码更快(我以前相信我把手放在它上面)。我真的觉得在一些微基准测试之外情况并非如此。充其量你可以希望与 MRC 相提并论,而不是更快。我做了一些其他涉及对象操作的简单测试(比如计算文档中的单词)。每次 ARC 都变慢(认为不如我最初所说的 70% 性能回归那么糟糕)

\begin{讽刺}

事实上,上述文档确实回答了这个问题:

ARC 慢吗?

这取决于您要测量的内容,但通常“不”。 ...

显然应该理解为

\begin{模仿}

嗯......嗯......我们不能说它慢,因为这是一种新的酷技术,我们希望你采用它。因此,我们用双引号回答“否”只是为了避免集体诉讼。别再问愚蠢的问题了。

\end{模仿}

\end{讽刺}

【问题讨论】:

  • @H2CO3 我不认为 ARC 是垃圾......这只是你的意见,我宁愿认为这是以更简单/更好的方式管理内存的巨大努力,它是一个前沿可以替代 GC 的编译器技术 ...
  • ARC 确实为我节省了很多打字时间。怎么样?
  • @H2CO3 你真的不是 ARC 的粉丝,是吗 ;-) 你有博客文章或任何地方说明你的理由吗?我很想见他们。我经常在 ARC 和 MRC 代码库之间切换,并且 far 更喜欢 ARC。
  • 你写博客了吗?如果是这样,我们可以正面交锋。我将一劳永逸地进行 ARC/MRC 性能比较。我已经揭穿了 ARC 神话learn-cocos2d.com/2012/06/mythbusting-8-reasons-arc 以及您需要了解的内容learn-cocos2d.com/2011/11/everything-know-about-arc 在任何应用程序中可能只有不到 1% 的代码可以从手动调整的 MRC 代码中受益,而对于这些区域,您可以禁用 ARC一个文件。由于对性能损失的模糊概念,没有理由对剩下的 99% 进行自虐。
  • 保存打字让我觉得 ARC 的一个肤浅的理由。我对使用它的强烈偏好是由于我认识到我自己的人性错误。在过去的十年中,我在许多项目中编写了大量的 objc 代码,尽管如此,我发现我仍然无法可靠地编写无错误的代码。 ARC 以对 MRC 的理解和性能分析工具(这可能会导致偶尔出现 ARC 禁用文件)为后盾,让我离理想更近了一点。

标签: objective-c performance automatic-ref-counting


【解决方案1】:

我不敢说离题,而且还基于猜测......


我认为 ARC 的大部分性能提升是通过内联保留/释放调用而不是省略它们。

此外,据我所知,ARC 通常会引入额外的保留/释放调用。因为 ARC 非常严格和保守,所以它大多不执行保留/释放省略。许多新插入的保留/释放调用在语义上是必需的,但在 MRC 下被程序员省略了。 (例如所有传入的函数参数和临时变量)

所以,

  1. 为了严格满足语义完整性,保留/释放的调用次数实际上增加了很多

  2. 其中一些会被非常保守的优化忽略掉。

  3. 对保留/释放的实际调用将通过优化内联 - 通过从动态 Objective-C 方法调用变为静态 C 函数调用 - 因此调用成本本身将大大降低。

因此,我们通常会降低性能。我意识到在使用 ARC 之前我省略了很多保留/释放调用。但正如您所指出的,无论我们得到什么,它在语义上都是完整,并且仍然可以手动 - 如此确定 - 使用__unsafe_unretained 省略。

【讨论】:

    【解决方案2】:

    在发送消息或调用具有对象参数的 C/C++ 函数以及每个此类函数的指令数量相对较少的情况下,可能会出现明显的性能下降。编译器将为每个参数插入(以后不再优化)一个保留/释放对。

    考虑到上下文,编译器可能会认识到某些保留/释放对是不必要的。但是我注意到,即使调用了一个声明为静态内联并且与调用者位于同一翻译单元中的函数,编译器也无法优化掉不必要的对参数的保留/释放调用.

    【讨论】:

      【解决方案3】:

      当然临时变量是强默认的。这是明确且清楚地记录在案的。而且,如果您考虑周全,这就是人们通常想要的。

       MyWidget *widget=[[MyWidget alloc]init];  // 1
       [myWidget mill]; // 2
      

      如果小部件不强大,新的 MyWidget 将在第 1 行创建,并且可以在第 2 行之前释放并归零!

      现在,如果您在紧密循环的中间使用大量临时变量(例如,如果您严格遵守得墨忒耳定律),并且假设这些临时变量完全没有性能成本,因为世界上有很多寄存器,那么您会感到惊讶。

      这可能就是你现在居住的角落。

      但那是一个充满异国情调的特殊地方!大多数代码不在紧密循环的中间。大多数紧密循环都不是性能瓶颈。而且大多数紧密循环不需要很多中间变量。

      相反,ARC 可以以您无法手动执行的方式进行自动释放优化(尽管优化器可能可以)。所以,如果有一个函数在你的紧密循环中返回一个自动释放的变量,你可能会更好地使用 ARC。

      过早的优化是个坏主意。您可能处于不可避免的性能困境,但大多数人并非如此。可以肯定的是,我大部分时间都在 OS X 上度过,但多年来我一直没有遇到性能问题,答案不是更好的算法。

      (最后,如果 ARC 对您的应用程序造成了 70% 的性能损失,那么您在关键路径上进行了大量的内存管理!请考虑一下:您将 70% 的时间用于分配和释放对象。这听起来像是享元或对象缓存或回收池之类的教科书案例!)

      【讨论】:

      • 更正,这是 70% 的时间保留和释放,而不是分配。所以池或对象缓存不会改变任何东西(实际上,已经有一个池。这是在池管理中,编译器插入了这么多无用的保留/释放对!)
      • 我指的不是自动释放池——我在想熟悉的习惯用法,不是分配对象并在完成后释放它们,而是保留“未使用”的对象以供回收利用。例如,参见 NSTableView。
      • 很难想象你会花费这么多时间只是保留和释放而没有分配或释放。你在你的关键部分还做什么?这里有并发问题吗?我很难理解为什么一个不分配或取消分配的保留/释放对比几个调度和一个增量/减量对需要更多的开销。
      • @MarkBernstein:递增和递减是引用计数如此缓慢的主要原因。它们通常会导致缓存未命中。
      【解决方案4】:

      这是我的ARC vs MRC performance measurements。性能测试项目是available on github,因此您可以添加自己的测试。只要确保在设备上运行它。模拟器中的结果有偏差,通常有利于 MRC。

      总结一下:

      ARC 和 MRC 的速度通常相同。一般来说,ARC 下的代码应该更快,但紧密循环可能会更慢,而且明显如此。

      在低级测试中,由于优化(自动释放返回,@autoreleasepool),ARC 在速度方面优于 MRC。

      在某些代码中,只要应用是单线程的,ARC 会插入额外的保留/释放,这在 MRC 下并非绝对必要。这样的代码在 ARC 下可能会更慢,尽管它只在紧密循环中有所不同,并且很大程度上取决于相关代码。

      例如,即使在 MRC 下,接收对象的方法也应该保留它,因为它可能在方法运行时在多线程应用程序中被释放。您可以在 MRC 中省略该代码这一事实使其速度更快,但本质上更不安全(尽管您很少会遇到这样的问题,OTOH,如果您愿意的话)。示例:

      -(void) someMethod:(id)object
      {
          [object retain]; // inserted by ARC, good practice under MRC
          [object doSomething];
          [object doAnotherThing];
          [object release]; // inserted by ARC, good practice under MRC
      }
      

      因为这个原因,我在测试项目中使用的遗传算法使用 ARC 会慢 40%。这是一个糟糕(极端)的例子,因为对于这种算法,由于 NSMutableArray 上的大量插入/删除操作以及正在创建的 NSNumber 对象,您应该通过在 C 中重写关键代码部分来看到更大的性能改进。

      完全忽略 ARC 是完全疏忽的,因为它在某些情况下可能变慢。如果您发现这些情况对性能很重要,那么-fno-objc-arc 该代码或用 C 重写它。

      出于性能方面的考虑,不应考虑支持或反对 ARC。 ARC is a tool that make's a programmer's job a lot easier。您是否愿意浪费时间尝试查找泄漏的对象和悬空指针崩溃,以致于您宁愿坚持使用 MRC。

      【讨论】:

      • 是的,它还没有上线。没想到有人这么快就来了。 :) 再等 10 分钟。
      • 感谢您的回答。我阅读了您的帖子并快速浏览了您的资源。除非我遗漏了什么,否则您的测试主要包括微基准测试(测量低级语言功能)。您是否有任何测试可以真正计算一些有趣的东西并且使用 ARC 更快(除了说明您论文的逆向的遗传算法(我同意 C 对这些东西会更好))?换个说法,你拿一个真实的源代码,用 XCode 的脚本把它转换成 ARC,然后更快地得到一些东西。
      • @LearnCocos2D,我不同意您的示例-someMethod: 要求这些保留和释放在存在线程的情况下是安全的。调用者用一个它没有责任在持续时间内保持活动的对象调用方法是错误的。此外,该方法中所谓的保留和释放补救措施并不能解决任何线程问题。如果对象可以在-doSomething-doAnotherThing 之间释放,那么它可以在方法顶部的-retain 之前释放。
      【解决方案5】:

      我认为,如果您得到类似的性能回归,唯一可能的解释是您的手动托管代码“不安全”,我的意思是存在潜在的内存泄漏和较少的保留/释放调用,从而以某种方式进行程序内存管理不安全。

      如果手动管理的代码写得好且安全的话,我认为 ARC 代码不会比手动管理的慢...

      当然,我也认为编写良好的手动管理代码可能比 ARC 代码稍快,但代价是什么?更多的工作需要手工完成...... 在大多数情况下,麻烦多于值得!

      此外,我认为 ARC 应该与垃圾收集器环境进行比较,而不是与 完美编写的 MRC人脑永远比程序更聪明(或至少我希望如此... :-) ) ...

      但是,如果您有一个编写良好的 MRC 代码库,并且您确实确定它是安全且快速的,为什么将它放在 ARC 下?手动管理内存,使用-fno-objc-arc 标志...使用 ARC 不是强制性的,尤其是出于这些原因。

      【讨论】:

      • 好吧,我怀疑这就是原因。 OP 似乎比编写糟糕的生产代码更聪明......
      • @H2CO3 查看我编辑的答案...您不应将 ARC 与完美的 MRC 进行比较,而应将其与 GC 环境进行比较...
      • pre-ARK 代码没问题。它没有泄漏也没有崩溃。当然,与 ARC 编译器生成的相比,它包含的保留/释放对要少得多。但我知道,相应的价值观无法从我的脚下释放出来。此信息来自对象图的全局知识(ARC 编译器无法访问)。所以这不会使我的旧的 pre-ARC 代码不安全。我同意你关于 ARC 的优点,我不打算恢复开关。我很遗憾,关于性能影响的信息很少(我可以支付)
      • @aleroot:最初的问题是“有没有人有关于这种或其他技术的任何信息来避免性能损失?(除了停用 ARC)”。这个问题有真正的答案吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-11
      • 1970-01-01
      • 1970-01-01
      • 2011-05-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多