Why Is Allocating or Initializing Nsdateformatter Considered "Expensive"

NSDateFormatter expense

func testPerformanceExampleUpdateFormat() {
self.measure {
(0...1_000).forEach { _ in
let df = DateFormatter()
df.dateFormat = "MMM yyyy"
let formattedDate1 = df.string(from: Date())
df.dateFormat = "MM/dd/yyyy"
let formattedDate2 = df.string(from: Date())
}
}
}

func testPerformanceExampleNewInstance() {
self.measure {
(0...1_000).forEach { _ in
let df1 = DateFormatter()
df1.dateFormat = "MMM yyyy"
let df2 = DateFormatter()
df2.dateFormat = "MM/dd/yyyy"
let formattedDate1 = df1.string(from: Date())
let formattedDate2 = df2.string(from: Date())
}
}
}
  • Result time when updating a format 1 000 times: 0.0896 s.

  • Result time when creating a new instance 1 000 times: 0.138 s.

How expensive is a type?

func testPerformanceExample() {
self.measure {
(0...1_000).forEach { _ in
let int = DateFormatter()
}
}
}

The result time is 0.0017 s. for 1 000 instances of DateFormat

Let's compare it to other types:

0.000883 s. for 1 000 instances of Int; // let int = 2

0.000814 s. for 1 000 instances of String; // let str = String("Hello")

0.00118 s. for 1 000 instances of Date; // let date = Date()

0.0784 s. for 1 000 instances of UITableViewController; let vc = UITableViewController()

0.246 s. for 1 000 instances of UITableView; // let tv = UITableView()

Is it expensive?

How to minimize the costs for allocating and initializing an NSDateFormatter?

Note: Your example program is very much a micro-benchmark and very effectively maximally amplifies that cost of a date formatter. You are comparing doing absolutely nothing with doing something. Thus, whatever that something is, it will appear to be something times slower than nothing.

Such tests are extremely valuable and extremely misleading. Micro-benchmarks are generally only useful when you have a real world case of Teh Slow. If you were to make this benchmark 10x faster (which, in fact, you probably could with what I suggest below) but the real world case is only 1% of overall CPU time used in your app, the end result is not going to be a dramatic speed improvement -- it will be barely noticeable.

What is the reason for such costs?

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

Most likely, the cost is associated with both having to parse/validate the date format string and having to do any kind of locale specific goop that NSDateFormatter does. Cocoa has extremely thorough support for localization, but that support comes at a cost of complexity.

Seeing as how you wrote a rather awesome example program, you could fire up your app in Instruments and try the various CPU sampling instruments to both understand what is consuming CPU cycles and how Instruments works (if you find anything interesting, please update your question!).

Can there be a blocking where the threads have to wait for each other?

I'm surprised it doesn't simply crash when you use a single formatter from multiple threads. NSDateFormatter doesn't specifically mention that it is thread safe. Thus, you must assume that it is not thread safe.

How can I improve the usage?

Don't create so many date formatters!

Either keep one around for a batch of operations and then get rid of it or, if you use 'em all the time, create one at the beginning of your app's run and keep around until the format changes.

For threading, keep one per thread around, if you really really have to (I'd bet that is excessive -- that the architecture of your app is such that creating one per batch of operations will be more sensible).

Is caching a NSDateformatter application-wide good idea?

I'll chime in here with an answer based on experience. The answer is yes, caching NSDateFormatter app-wide is a good idea, however, for added safety there is a step you want to take for this.

Why is it good? Performance. It turns out that creating NSDateFormatters is actually slow. I worked on an app that was highly localized and used a lot of NSDateFormatters as well as NSNumberFormatters. There were times that we dynamically created them greedily within methods as well as having classes that had their own copy of the formatters they needed. In addition, we had the added burden that there were cases where we could also display strings localized for different locales on the same screen. We were noticing that our app was running slow in certain cases, and after running Instruments, we realized it was formatter creation. For example we saw performance hit when scrolling table views with a large number of cells. So we ended up caching them by creating a singleton object which vended the appropriate formatter.

A call would look something like:

NSDateFormatter *dateFormatter = [[FormatterVender sharedInstance] shortDate];

Note, this is Obj-C, but the equivalent can be made in Swift. It just happened ours was in Obj-C.

As of iOS 7, NSDateFormatters and NSNumberFormatters are "thread safe", however as Hot Licks mentioned, you probably don't want to go around modifying the format if another thread is utilizing it. Another +1 for caching them.

And another benefit I just thought of was code maintainability. Especially if you have a large team like we have. Because all devs know there is a centralized object that vends the formatters, they can simply see if the formatter they need already exists. If it doesn't, it gets added. This is typically feature related, and hence usually means that new formatter will be needed elsewhere as well. This also helps reduce bugs, because if there happens to be a bug in the formatter, you fix it one spot. But we usually catch that during the unit tests for the new formatter.

There is one more element you can add for safety, if you want. Which is you can use the NSThread's threadDictionary to store the formatter. In other words, when you call the singleton which will vend the formatter, that class checks the current thread's threadDictionary to see if that formatter exists or not. If it exists, then it simply returns it. If not, it creates it and then returns it. This adds a level of safety so if, for some reason you wanted to modify your formatter, you can do it and not have to worry about that the formatter is being modified by another thread.

What we used at the end of the day was singleton which vended specific formatters (both NSDateFormatter and NSNumberFormatter), ensuring that each thread itself had it's own copy of that specific formatter (note the app was created prior to iOS 7, which made that an essential thing to do). Doing that improved our app performance as well as got rid of some nasty side effects we experienced due to thread safety and formatters. Since we have the threadDictionary part in place, I never tested it to see if we had any issues on iOS7+ without it (ie. they have truly become thread-safe). Thus why I added the "if you want" above.

Formatting NSDate is lagging my UITableVIew

There is word that creating date formatters is expensive. Use an instance variable and set up a single date formatter in viewDidLoad, then reuse that same formatter each time you need one. Or lazy load one as needed.

If it's not the creation that's expensive, but the parsing of the date string, pre-save a parsed copy of your date and use that instead. You'll have to worry about things like location and locale changes with that though, so it gets more complicated.

NSDateFormatter milliseconds bug

It seems that NSDateFormatter works only with millisecond resolution, for the
following reasons:

  • By setting a breakpoint in CFDateFormatterCreateDateFromString, one can
    see that this function is called from dateFromString::

    (lldb) bt
    * thread #1: tid = 0x26d03f, 0x018f47d0 CoreFoundation`CFDateFormatterCreateDateFromString, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
    frame #0: 0x018f47d0 CoreFoundation`CFDateFormatterCreateDateFromString
    frame #1: 0x0116e0ea Foundation`getObjectValue + 248
    frame #2: 0x0116dfc7 Foundation`-[NSDateFormatter getObjectValue:forString:errorDescription:] + 206
    frame #3: 0x0116879f Foundation`-[NSDateFormatter dateFromString:] + 71
    * frame #4: 0x00002d56 foo`main(argc=1, argv=0xbfffee54) + 182 at main.mm:25
  • CFDateFormatterCreateDateFromString() is from
    CFDateFormatter.c
    which is open source. One can see that all calendrical calculations are made using the
    ICU Calendar Classes.

  • It is stated in calendar.h that Calendar uses UDate which has a millisecond resolution:

    /**
    * <code>Calendar</code> is an abstract base class for converting between
    * a <code>UDate</code> object and a set of integer fields such as
    * <code>YEAR</code>, <code>MONTH</code>, <code>DAY</code>, <code>HOUR</code>,
    * and so on. (A <code>UDate</code> object represents a specific instant in
    * time with millisecond precision. See UDate
    * for information about the <code>UDate</code> class.)
    * ...

Swift: Fastest/Easiest Way to format NSDate()

Make use of the NSDateFormatterStyle.LongStyle as value for the NSDateFormatter.dateStyle property:

let myDate = NSDate()

let formatter = NSDateFormatter()
formatter.dateStyle = .LongStyle

let dateAsString = formatter.stringFromDate(myDate)

print(dateAsString)
// January 10, 2016


Related Topics



Leave a reply



Submit