【问题标题】:Cocoa/Objective-C: how much optimization should I do myself?Cocoa/Objective-C:我自己应该做多少优化?
【发布时间】:2009-08-14 19:14:08
【问题描述】:

我最近发现自己编写了一段代码来执行核心数据提取,然后分配两个可变数组,初始容量等于从提取返回的结果数:

// Have some existing context, request, and error objects
NSArray *results = [context executeFetchRequest:request error:&error];

NSMutableArray *firstArray = [[[NSMutableArray alloc] 
                               initWithCapacity:[results count]] autorelease];
NSMutableArray *secondArray = [[[NSMutableArray alloc] 
                                initWithCapacity:[results count]] autorelease];

写完后我又看了一遍,有些奇怪:我给[results count] 打了两次电话。结果集可能非常大(数百甚至一千个对象)。

我的第一个要求是将[results count] 分解为一个单独的 NSUInteger,然后将该整数用于每个数组的容量。我的问题:这种手动优化是否必要?编译器是否会识别出它正在运行 [results count] 两次并且只保留该值而无需我明确指定该行为?或者它会运行该方法两次 - 一个潜在的昂贵操作?

与此类似,程序员(尤其是 iPhone 程序员,可用的内存/处理能力有限)应该手动执行哪些其他优化,而不是信任编译器?

【问题讨论】:

    标签: objective-c cocoa performance optimization


    【解决方案1】:

    回答实际问题:不,Objective-C 编译器永远无法优化方法发送。 (嗯,实际上,有一种可能的情况:如果它确定接收者是 nil。)

    一个类没有办法保证方法的行为(特别是你不能使用 gcc 的__attribute__((const)) 和方法),编译器也没有办法告诉什么方法实现会由于 Objective-C 的动态特性而被调用。例如,results 实际上可以是一个代理对象,它在每次调用时将count 方法转发给一个随机对象。 Core Data 没有特别的理由这样做,但编译器并不知道。

    除此之外,调用-[NSArray count] 的成本是微不足道的,除了极其人为的代码之外,特定方法绝对不可能成为任何事情的瓶颈。避免双重调用的习惯可以合理地被认为是值得的,但实际上担心成本或出于性能原因返回“纠正”它会浪费时间,很可能比你的程序花费更多的时间调用 @987654325 @ 在其整个有用生命周期内。

    【讨论】:

    • 这听起来像是这样做的理由;进行更改所花费的时间将少于担心是否应该进行更改所花费的时间:-)
    • 啊,但是花在辩论上的时间将大大超过首先提出问题的成本或(不)进行优化的成本......
    • 感谢有关 Objective-C 编译器和方法发送的花絮 - 以前不知道。 +1
    【解决方案2】:

    优化的第一条规则:永远不要在没有基准验证需要优化的情况下进行优化。否则,您将遭受“过早的优化”以及大量(可能)浪费的努力。

    现在,如果您知道某件事需要 2 秒才能运行,而您运行了 4 次,而您可以运行一次 - 那将是合理的。但听起来你在这种情况下不确定,所以答案是“基准测试”

    【讨论】:

    • 虽然这是绝对正确的(也是我最喜欢的规则之一),但纠正错误的编程并不是过早的优化。调用不必要的操作、不必要的内部循环、链表中的随机查找或数组列表中的插入排序都是错误代码的示例,而不是未优化的代码。
    • 它可能是错误的代码,但不是错误的代码,因此我认为你提到的所有内容都是一种优化。
    【解决方案3】:

    简短的回答:。至少现在不会。

    我认为编译器不会识别出您正在运行 [results count] 两次,但这并不重要。关于这个主题的通常引用是“过早的优化是万恶之源”。随心所欲地编写代码;首先考虑可读性。然后,你完成之后,你实际上已经注意到你的代码很慢,然后返回并在必要时寻找优化。

    所以规则是:编写合理、可读的代码,如果发现问题,事后优化

    【讨论】:

      【解决方案4】:

      编译器不会发现你已经运行了它。

      解决这个问题不是优化,它只是正确地编程。除了优化之外,您还需要注意一些事项。其中大多数都涉及一种或另一种不必要的循环。

      将其拉出到一个单独的变量中真的很难吗?

      NSArray *results = [context executeFetchRequest:request error:&error];
      NSUInteger count=[results count];
      NSMutableArray *firstArray  = [[[NSMutableArray alloc] initWithCapacity:count] autorelease];
      NSMutableArray *secondArray = [[[NSMutableArray alloc] initWithCapacity:count] autorelease];
      

      这绝对不是那么清楚(可能更清楚)。

      无论如何,需要避免执行无限制的不必要循环,这不是优化,这是错误的。

      请不要以为我不了解过早优化的弊端,我在回答其他问题时反复咆哮...

      至于您的另一个问题,如果没有测试或规范表明您的代码运行速度太慢,请不要“优化”,但不要编写糟糕的代码。始终注意内部循环和可能导致内部循环的事情——这些是最关键的。

      对数组列表的插入排序会导致内部循环,对链表的插入排序不会...

      遍历链表会导致内部循环(假设您使用循环索引来检索“Next”值而不是保留节点并引用 .next 会很好),遍历数组列表不会.

      比较两个列表通常需要一个内部循环,并且通常值得查看您的数据以尝试找出不需要内部循环的方法(例如,如果它们都已排序,则您可以编写这样的代码没有内部循环。

      【讨论】:

      • 有些人(包括我自己)会争辩说它的可读性较差,而且您获得的速度优势可能可以忽略不计。就个人而言,我更愿意看到 [结果计数] 而不是必须跟踪一个新变量 i,只是为了删除单个“计数”消息。但那是个人风格;有人说我的方法嵌套了太多方括号而无法阅读。然而,我的观点是,基于偏好的一种或另一种方式的可读性可能仍然胜过这种“正确的编程”。
      • 至于可读性——它很接近。您没有跟踪一个新变量,因为它只在那个地方使用。如果你愿意,你甚至可以限定它,但如果你保持你的函数很小,它应该已经被合理地限定了。执行多项操作的行通常写起来很有趣,并且在您编写它时易于理解,因为它们可以封装一个概念,但是当您第一次看到多行等效项时,总是更容易阅读。无论如何,“正确编程”部分是关于不重复调用不必要的代码,而不是我的特殊解决方案。
      • int 已签名并且可能太短。正确的类型是NSUInteger
      • 我想这是有道理的。我想通过宏映射到 unsigned int (然后应该可以工作)。不太了解 O-c 让我想知道幕后可能是什么。我不知道他们为所有变量制作了宏,我以为他们只会做你必须分配和释放的那些……我会更新的。
      • 与其假设,不如找出答案!命令双击 Xcode 中的单词 NSUInteger,你会发现它的类型定义为 unsigned int 或 unsigned long,具体取决于目标架构。 (请记住,在 64 位 Mac 环境中,int 是 32 位,所以 int 真的可能太短了。)纯粹从实际意义上讲,它在定义它的所有目标上与 unsigned long 的大小相同,但对于 32 位使用 long 会已更改现有类型和方法的 @encode() 字符串,这可能会导致兼容性问题。
      猜你喜欢
      • 2012-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-06
      • 1970-01-01
      • 2013-03-25
      • 1970-01-01
      相关资源
      最近更新 更多