Как минимизировать затраты на выделение и инициализацию NSDateFormatter?

Я заметил, что с помощью NSDateFormatter может быть довольно дорогостоящим. Я понял, что выделение и инициализация объекта уже занимают много времени.
Кроме того, кажется, что с помощью NSDateFormatter в несколько потоков увеличивает расходы. Может ли быть блокировка, когда потоки должны ждать друг друга?

Я создал небольшое тестовое приложение, чтобы проиллюстрировать проблему. Пожалуйста, проверьте это.

В чем причина таких затрат и как я могу улучшить использование?


17,12. - Чтобы обновить мои наблюдения: я не понимаю, почему потоки работают дольше при параллельной обработке по сравнению с последовательным порядком. Разница во времени возникает только при использовании NSDateFormatter.

6 ответов

Решение

Примечание. Ваша программа-пример очень похожа на микро-эталон и очень эффективно максимально увеличивает стоимость форматера даты. Вы сравниваете абсолютно ничего не делая с чем-то. Таким образом, чем бы это ни было, оно будет в разы медленнее, чем ничего.

Такие тесты чрезвычайно ценны и вводят в заблуждение. Микро-тесты обычно полезны только тогда, когда у вас есть реальный случай Teh Slow. Если бы вам пришлось сделать этот тест в 10 раз быстрее (что, на самом деле, вы могли бы сделать с тем, что я предлагаю ниже), но реальный случай составляет всего 1% от общего процессорного времени, используемого в вашем приложении, конечный результат не будет резкое улучшение скорости - это будет едва заметно.

В чем причина таких затрат?

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

Скорее всего, стоимость связана как с необходимостью разбора / проверки строки формата даты, так и с необходимостью выполнить какой-либо конкретный языковой цикл, который NSDateFormatter делает. Какао имеет чрезвычайно полную поддержку для локализации, но эта поддержка идет за счет сложности.

Видя, как вы написали довольно классный пример программы, вы можете запустить свое приложение в Instruments и попробовать различные инструменты выборки CPU, чтобы понять, как потребляются циклы CPU и как работают Instruments (если вы найдете что-нибудь интересное, обновите свой вопрос!).

Может ли быть блокировка, когда потоки должны ждать друг друга?

Я удивлен, что это не просто сбой, когда вы используете один форматер из нескольких потоков. NSDateFormatter конкретно не упоминает, что это потокобезопасный. Таким образом, вы должны предположить, что это не потокобезопасно.

Как я могу улучшить использование?

Не создавайте так много форматеров даты!

Либо оставьте один для пакета операций, а затем избавьтесь от него, или, если вы используете их все время, создайте его в начале работы вашего приложения и держите его до изменения формата.

Для многопоточности сохраняйте по одному на каждый поток, если вам действительно это нужно (держу пари, что это чрезмерно - архитектура вашего приложения такова, что создание одной операции будет более разумным).

Мне нравится использовать последовательную очередь 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;
}

С помощью -initWithDateFormat:allowNaturalLanguage: вместо -init с последующим -setDateFormat: должно быть намного быстрее (вероятно, ~2x).

Хотя в общем то, что сказал bbum: кешируйте ваши средства форматирования даты для горячего кода.

(Изменить: это больше не так в iOS 6/OSX 10.8, теперь они должны быть одинаково быстрыми)

Используйте 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 И изменения формата и локали стоят дорого. Я создал "фабричный" класс для повторного использования моего NSDateFormatters,

у меня есть NSCache экземпляр, где я храню до 15 NSDateFormatter экземпляры, основанные на формате и информации о локали, в момент, когда я создал тогда. Итак, когда-нибудь позже, когда они мне снова понадобятся, я задам некоторые вопросы своему классу. NSDateFormatter формата "dd/MM/yyyy" с использованием локали "pt-BR" и мой класс дает корреспондент уже загружен NSDateFormatter пример.

Вы должны согласиться с тем, что в большинстве стандартных приложений крайне важно иметь более 15 форматов даты на одну среду выполнения, поэтому я предполагаю, что это отличный предел для их кэширования. Если вы используете только 1 или 2 разных формата даты, у вас будет загружено только это количество NSDateFormatter экземпляров. Звучит хорошо для моих нужд.

Если вы хотите попробовать это, я опубликовал это на GitHub.

Я думаю, что лучшая реализация, как показано ниже:

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;
         }
    }
}
Другие вопросы по тегам