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

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

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

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


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


person JJD    schedule 14.12.2010    source источник
comment
Если бы мог, я бы дал вам три балла за это тестовое приложение. Ну, 2, потому что все iVars имеют префикс m_, но ... тем не менее ... это отличная отправная точка для глубокого погружения с инструментами, сэмплированием, потоковой передачей и т. Д.   -  person bbum    schedule 14.12.2010


Ответы (6)


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

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

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

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

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

Увидев, как вы написали довольно замечательный пример программы, вы можете запустить свое приложение в инструментах и ​​попробовать различные инструменты выборки ЦП, чтобы понять, что потребляет циклы ЦП и как работают инструменты (если вы найдете что-нибудь интересное, пожалуйста < / em> обновите свой вопрос!).

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

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

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

Не создавайте столько средств форматирования даты!

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

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

person bbum    schedule 14.12.2010
comment
Спасибо за ваш ответ. Я старался сделать тестовое приложение простым. Вот почему он абсолютно ничего не делает. Конечно, есть контекст реального мира. Это анализатор файлов, извлекающий метку времени и многое другое. Тем не менее простое выделение NSDateFormatter делает процедуру очень медленной. - Я знаю, что использование средства форматирования как переменной-члена для повторного использования в одном потоке ускоряет работу. Но все же я не могу объяснить, почему форматтер такой дорогостоящий, когда его используют несколько потоков. Разница во времени становится более очевидной, когда я не использую член. - person JJD; 15.12.2010
comment
Поведение в многопоточном случае не определено, поскольку NSDateFormatter явно не заявляет о безопасности потоков. Таким образом, неудивительно, что в многопоточном случае он медленный (большой сюрприз, что он вообще работает). Пробовали ли вы использовать сэмплер ЦП в инструментах для тестирования результатов теста во время его выполнения? Это скажет вам, как прошли циклы. - person bbum; 15.12.2010
comment
Я не уверен, что понимаю. Если сэмплер ЦП может показать, какой ЦП какой поток обработал, я не могу найти его в инструментах. - person JJD; 17.12.2010
comment
Есть несколько полезных конфигураций; Time Profiler обеспечивает основанную на времени выборку всех (или одного, IIRC) процессов, CPU Sampler - это немного другой, основанный на времени сэмплер, и, наконец, Multicore может обеспечивать выборку CPU на основе состояния потока. - person bbum; 17.12.2010
comment
Хм - может быть, мне стоит взять ваш пример, запустить Instruments и написать сообщение в блоге, показывающее все различные биты информации, которые можно почерпнуть из очень простого теста. - person bbum; 17.12.2010

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

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

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

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

person Catfish_Man    schedule 14.12.2010
comment
Спасибо. Вызов функции setDateFormat не требует затрат. Фактически, вы можете не использовать это в тестовом приложении. Я вставил его на тот случай, если компиляция попытается оптимизировать и удалит dateFormatter, потому что он вообще не используется. - person JJD; 15.12.2010
comment
Это действительно быстрее. Как ты узнал? Тем не менее, я не уверен, разумно ли использовать этот инициализатор, поскольку неясно, устарел он или нет. См. Здесь: stackoverflow.com/questions/3182314/ < / а> - person JJD; 17.12.2010
comment
Я запустил Shark (или Instruments, сейчас не помню) и посмотрел, где были затраты. Оба вызова -init и -set в конечном итоге вызывают код настройки форматтера ICU, что является дорогостоящим битом. (отредактировано) О, хм. Я вижу проблему. Это создает средство форматирования в стиле 10.0. Это очень странно ... ожидаемый способ использования форматтера не должен дублировать работу. Я займусь расследованием. - person Catfish_Man; 19.12.2010
comment
Разведан. Видимо нет хорошего способа сделать это. Как прискорбно :( Однако кеширование должно делать это неактуальным. - person Catfish_Man; 29.12.2010

Используйте 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;
}
person leviathan    schedule 18.03.2014
comment
Этот онк великолепен. Создание NSDateFormatter только один раз, что решило мою проблему. - person Jassi; 31.03.2015

Поскольку создание / инициализация NSDateFormatter И изменение формата и локали обходятся дорого. Я создал "фабричный" класс для обработки повторного использования моего NSDateFormatters.

У меня есть NSCache экземпляр, в котором я храню до 15 NSDateFormatter экземпляров, в зависимости от формата и информации о локали, в тот момент, когда я его создал. Итак, когда-нибудь позже, когда они мне снова понадобятся, я прошу свой класс с помощью некоторого NSDateFormatter формата «дд / ММ / гггг», используя локаль «pt-BR», и мой класс предоставит корреспонденту уже загруженный экземпляр NSDateFormatter.

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

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

person Douglas Fischer    schedule 27.04.2013
comment
Есть ли особая причина, по которой вы расширяете NSObject в своей реализации? Не могли бы вы написать расширение для NSDateFormatter? - person JJD; 27.04.2013
comment
Мне нужно запустить экземпляр NSCache, я не знаю, можно ли использовать категории, но я думаю, что можно было бы расширить NSDateFormatter. Я пропустил его. - person Douglas Fischer; 27.04.2013
comment
Это должно быть возможно. Ищите ассоциативные ссылки в Objective-C. - person JJD; 29.04.2013
comment
@DouglasFischer, если вы когда-нибудь захотите переделать настройку кеша Date Formatter, в этой статье описывается прекрасное использование синглтона в качестве кеша NSDateFormatter - krakendev.io/blog/antipatterns-singletons. - person Natalia; 15.05.2018

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

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;
         }
    }
}
person Community    schedule 18.04.2016