12

我找不到关于 ARC 在现实项目中的性能影响的客观研究。 官方文档

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

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

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

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

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

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

有没有人有关于这种或其他技术的任何信息来避免性能损失?(除了停用 ARC)

谢谢。

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

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

\开始{讽刺}

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

ARC慢吗?

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

这显然应该理解为

\开始{模仿}

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

\结束{模仿}

\结束{讽刺}

4

5 回答 5

6

这是我的ARC 与 MRC 性能测量。性能测试项目在 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 是一个让程序员的工作变得更轻松的工具。由您来决定您是否喜欢浪费时间尝试查找泄漏的对象和悬空指针崩溃,以至于您宁愿坚持使用 MRC。

于 2013-03-20T20:47:33.253 回答
2

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

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

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

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

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

于 2012-09-21T09:01:28.130 回答
1

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

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

于 2013-04-08T12:50:08.520 回答
1

我害怕谈论一些无关紧要的事情,而且还基于猜测......


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

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

所以,

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

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

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

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

于 2013-08-10T01:19:36.517 回答
0

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

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

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

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

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

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

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

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

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

于 2013-03-20T21:41:50.527 回答