【问题标题】:How to minimize the costs for allocating and initializing an NSDateFormatter?如何最小化分配和初始化 NSDateFormatter 的成本?
【发布时间】:2016-04-18 22:04:09
【问题描述】:

我注意到使用NSDateFormatter 可能会非常昂贵。我发现分配和初始化对象已经消耗了很多时间。
此外,似乎在多个线程中使用NSDateFormatter 会增加成本。是否存在线程必须相互等待的阻塞?

我创建了一个小型测试应用程序来说明问题。请检查一下。

产生这些费用的原因是什么?如何提高使用率?


17.12。 - 更新我的观察:我不明白为什么线程在并行处理时比在串行顺序运行时运行时间更长。只有在使用 NSDateFormatter 时才会出现时间差。

【问题讨论】:

  • 如果可以的话,我会为那个基准应用程序给你三分。好吧,2 因为 iVar 都以 m_ 为前缀,但是......仍然......这是深入了解仪器、采样、线程等的一个很好的起点......

标签: multithreading cocoa memory-management nsdateformatter microbenchmark


【解决方案1】:

注意:您的示例程序在很大程度上是一个微型基准,并且非常有效地最大程度地放大了日期格式化程序的成本。您将什么都不做做某事进行比较。因此,无论 something 是什么,它似乎都比 nothingsomething 倍

这样的测试非常有价值,而且极具误导性。微基准通常仅在您拥有 Teh Slow 的真实案例时才有用。如果你要让这个基准测试速度提高 10 倍(事实上,你可能可以按照我在下面的建议),但现实世界的情况只是你的应用程序中使用的总 CPU 时间的 1%,最终结果不会是显着的速度提升——几乎不会引起注意。

产生这些费用的原因是什么?

NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyyMMdd HH:mm:ss.SSS"];

最有可能的是,成本与必须解析/验证日期格式字符串以及必须执行 NSDateFormatter 所做的任何类型的特定于区域设置的 goop 有关。 Cocoa 对本地化提供了极其彻底的支持,但这种支持是以复杂性为代价的。

看到您如何编写了一个非常棒的示例程序,您可以在 Instruments 中启动您的应用程序并尝试各种 CPU 采样工具,以了解消耗 CPU 周期的内容以及 Instruments 的工作原理(如果您发现任何有趣的东西,更新你的问题!)。

是否存在线程必须相互等待的阻塞?

令我惊讶的是,当您从多个线程中使用单个格式化程序时,它不会简单地崩溃。 NSDateFormatter 没有特别提到它是线程安全的。因此,您必须假设它不是线程安全的。

如何提高使用率?

不要创建这么多日期格式化程序!

要么为一批操作保留一个,然后将其删除,或者如果您一直使用它们,则在应用运行开始时创建一个并保留直到格式更改。

对于线程,如果确实需要,请为每个线程保留一个(我敢打赌,这太过分了——您的应用程序架构使得每批操作创建一个会更明智)。

【讨论】:

  • 感谢您的回复。我试图让测试应用程序保持简单。这就是为什么它“完全无所作为”。当然有一个真实的世界背景。它是一个文件解析器,用于检索时间戳等。然而 NSDateFormatter 的简单分配使例程非常慢。 - 我知道将格式化程序作为成员变量处理以在一个线程中重用它会加快速度。但是我仍然无法解释为什么当多个线程使用格式化程序时它的成本很高。不使用会员时,时差更明显。
  • 线程情况下的行为是未定义的,因为NSDateFormatter 没有明确声明线程安全。因此,在线程情况下它很慢也就不足为奇了(它完全可以工作,这很令人惊讶)。您是否尝试使用 Instruments 中的 CPU 采样器在运行基准测试时对其进行采样?这会告诉你周期已经过去了。
  • 我不确定我是否理解。如果 CPU 采样器可以显示哪个 CPU 处理了哪个线程,我在 Instruments 中找不到它。
  • 有几种不同的配置很有用; Time Profiler 为所有(或一个 IIRC)进程提供基于时间的采样,CPU Sampler 是一种略有不同的基于时间的采样器,最后,多核可以提供基于线程状态的 CPU 采样。
  • Hrm -- 也许我应该以你为例,启动 Instruments,然后写一篇博客文章,展示可以从一个非常简单的基准测试中收集到的所有不同信息。
【解决方案2】:

我喜欢使用 GCD 顺序队列来确保线程安全,它方便、有效、高效。比如:

dispatch_queue_t formatterQueue = dispatch_queue_create("formatter queue", NULL);
NSDateFormatter *dateFormatter;
// ...
- (NSDate *)dateFromString:(NSString *)string
{
    __block NSDate *date = nil;
    dispatch_sync(formatterQueue, ^{
        date = [dateFormatter dateFromString:string];
    });
    return date;
}

【讨论】:

    【解决方案3】:

    使用-initWithDateFormat:allowNaturalLanguage: 而不是-init 后跟-setDateFormat: 应该快得多(可能~2x)。

    不过,总的来说,bbum 所说的:缓存您的日期格式化程序以获取热代码。

    (编辑:这在 iOS 6/OSX 10.8 中不再适用,它们现在应该都一样快)

    【讨论】:

    • 谢谢。函数调用setDateFormat 不会产生成本。实际上,您可以将其从测试应用程序中删除。我把它放在以防万一编译尝试优化并删除 dateFormatter 因为它根本没有使用。
    • 确实更快。你怎么知道?尽管如此,我不确定使用这个初始化器是否聪明,因为不清楚它是否被弃用。见这里:stackoverflow.com/questions/3182314/…
    • 我启动了 Shark(或 Instruments,我现在不记得了)并查看了成本在哪里。 -init 和 -set 调用最终都会调用 ICU 格式化程序设置代码,这是昂贵的部分。 (编辑)哦,嗯。我看到了这个问题。这将创建一个 10.0 样式的格式化程序。这很奇怪......使用格式化程序的预期方式不应该做重复的工作。我会调查的。
    • 已调查。显然没有什么好的方法可以做到这一点。多么不幸:(缓存仍然应该使它无关紧要。
    【解决方案4】:

    使用 GDC dispath_once 就可以了。这将确保多个线程之间的同步,并确保日期格式化程序只创建一次。

    + (NSDateFormatter *)ISO8601DateFormatter {
        static NSDateFormatter *formatter;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            formatter = [[NSDateFormatter alloc] init];
            formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
        });
        return formatter;
    }
    

    【讨论】:

    • 这首歌很棒。只创建一次 NSDateFormatter 解决了我的问题。
    【解决方案5】:

    由于NSDateFormatter 的创建/初始化以及格式和语言环境的更改成本很高。我创建了一个“工厂”类来处理我的NSDateFormatters 的重用。

    我有一个NSCache 实例,根据格式和语言环境信息,在我创建的那一刻,我最多可以存储15 个NSDateFormatter 实例。因此,稍后当我再次需要它们时,我使用语言环境“pt-BR”通过一些格式为“dd/MM/yyyy”的NSDateFormatter 向我的班级提问,我的班级给了已经加载的NSDateFormatter 实例。

    您应该同意,在大多数标准应用程序中,每个运行时拥有超过 15 种日期格式是一种极端情况,因此我认为这是缓存它们的一个很大的限制。如果您只使用 1 或 2 种不同的日期格式,您将只有这个数量的已加载 NSDateFormatter 实例。听起来很适合我的需求。

    如果你想试试,I made it public on GitHub

    【讨论】:

    • implementation 中扩展NSObject 是否有特殊原因?你不能为NSDateFormatter写一个扩展吗?
    • 我需要初始化 de NSCache 实例,我不知道是否可以使用类别,但我认为,可以从 NSDateFormatter 扩展。我错过了。
    • 应该是可以的。在 Objective-C 中搜索“关联引用”。
    • @DouglasFischer,如果你想重新设置 Date Formatter 缓存,这篇文章描述了一个很好的使用单例作为 NSDateFormatter 缓存 - krakendev.io/blog/antipatterns-singletons
    【解决方案6】:

    我认为最好的实现如下:

    NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
    NSDateFormatter *dateFormatter = threadDictionary[@”mydateformatter”];
    if(!dateFormatter){
        @synchronized(self){
            if(!dateFormatter){
                dateFormatter = [[NSDateFormatter alloc] init];
               [dateFormatter setDateFormat:@”yyyy-MM-dd HH:mm:ss”];
               [dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@”Asia/Shanghai”]];
              threadDictionary[@”mydateformatter”] = dateFormatter;
             }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多