NSMutableDictionary намного медленнее, чем Java Map... почему?

Следующий код, который сопоставляет простые носители значений объекту, работает быстрее на Java быстрее, чем Objective-C, используя XCode 7 beta3, "Быстрая, агрессивная оптимизация [-Ofast]". Я могу получить более 280 М поисков/сек в Java, но только около 19 М в примере objc. (Я отправил соответствующий Java-код здесь, поскольку это началось как сравнение Swift: Swift Dictionary медленный даже с оптимизацией: выполнение незавершенного сохранения/выпуска?).

Это упрощенная версия моего реального кода, который определенно связан с временем поиска хэша и демонстрирует эту общую разницу в производительности. В тесте ниже я тестирую значение null, чтобы убедиться, что компилятор не оптимизирует поиск, но в реальном приложении я бы использовал значение в большинстве случаев.

Когда я смотрю на инструменты, я вижу много времени, потраченного на сохранение/освобождение, msgSend и некоторые блокирующие вызовы, которые я не понимаю.

Любые идеи о том, что может объяснить это, на 10-15 раз медленнее, чем Java, или любые обходные пути, будут оценены. Я могу на самом деле реализовать идеальный хэш, как тот, который приведен ниже, поэтому я мог бы использовать быстрый словарь int-object для iOS, если бы мог найти его.

@interface MyKey : NSObject <NSCopying>
    @property int xi;
@end

@implementation MyKey
    - (NSUInteger)hash { return self.xi; }
    - (BOOL)isEqual:(id)object    { return ((MyKey *)object).xi == self.xi; }
    - (id)copyWithZone:(NSZone *)zone { return self; }

@end

    NSMutableDictionary *map = [NSMutableDictionary dictionaryWithCapacity:2501];
    NSObject *obj = [[NSObject alloc] init];

    int range = 2500;
    for (int x=0; x<range; x++) {
        MyKey *key = [[MyKey alloc] init];
        key.xi=x;
        [map setObject:obj forKey:key];
    }

    MyKey *key = [[MyKey alloc] init];
    int runs = 50;
    for (int run=0; run<runs; run++)
    {
        NSDate *start = [NSDate date];

        int reps = 10000;
        for(int rep=0; rep<reps; rep++)
        {
            for (int x=0; x<range; x++) {
                key.xi=x;
                if ( [map objectForKey:key] == nil ) { NSLog(@"missing key"); }
            }
        }

        NSLog(@"rate = %f", reps*range/[[NSDate date] timeIntervalSinceDate:start]);
    }

Ответ 1

Вы можете переопределить свой метод -isEqual:, как это, чтобы избежать доступа к свойствам:

- (BOOL) isEqual:(id)other
{
    return _xi == ((MyKey*)other)->_xi;
}

Это не приемлемо, если ваш класс MyKey может быть подклассом, но из кода Java я вижу, что класс есть final.

Ответ 2

Вычислительная сложность NSMutableDictionary является следующей (из файла CFDictionary.h):

The access time for a value in the dictionary is guaranteed to be at
worst O(N) for any implementation, current and future, but will
often be O(1) (constant time). Insertion or deletion operations
will typically be constant time as well, but are O(N*N) in the
worst case in some implementations. Access of values through a key
is faster than accessing values directly (if there are any such
operations). Dictionaries will tend to use significantly more memory
than a array with the same number of values.

Значит, почти все время у вас должна быть сложность O (1) для доступа/вставки/удаления. Для Java HashMap вы должны получить почти то же самое.

В соответствии с этим исследованием нет преимуществ при использовании инициализатора удобства dictionaryWithCapacity:.

Если вы используете целое число как ключ, возможно, можно будет заменить словарь массивом.

В этом сеансе WWDC они объяснили проблемы производительности objc_msgSend и способы их устранения. Первое решение - использовать контейнеры С++ и STL. Второй - использовать Swift, потому что в отличие от Objective-C он только динамический, если он отмечает.