2015-07-15 19 views
10

Poniższy kod, który odwzorowuje proste obiekty wartościowe na obiekt, działa w Javie ponad 15 razy szybciej niż w Objective-C przy użyciu XCode 7 beta3, "Najszybsze, agresywne optymalizacje [-Ofast]". Mogę uzyskać ponad 280M odsyłaczy/sekundę w Javie, ale tylko około 19M w przykładzie objc. (Opublikowalam odpowiedni kod Javy tutaj, poniewaz rozpoczal to jako porównanie Swift: Swift Dictionary slow even with optimizations: doing uncessary retain/release?).NSMutableDictionary znacznie wolniejszy niż Java Map ... dlaczego?

To jest uproszczona wersja mojego prawdziwego kodu, który jest zdecydowanie związany z czasem szukania hash i wykazuje również tę ogólną różnicę wydajności. W poniższym teście testuję wartość null tylko po to, aby kompilator nie optymalizował wyszukiwania, ale w prawdziwej aplikacji używałbym tej wartości w większości przypadków.

Kiedy patrzę na instrumenty, widzę dużo czasu spędzonego na zachowaniu/zwolnieniu, msgSend i niektórych blokujących połączeniach, których nie rozumiem.

Wszelkie sugestie na temat tego, co może być przyczyną 10-15 razy wolniej niż Java lub wszelkie obejścia byłyby mile widziane. Mogę faktycznie zaimplementować idealny skrót taki jak ten poniżej, więc gdybym mógł go znaleźć, mógłbym użyć szybkiego słownika int-object dla systemu 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]); 
    } 
+0

Użycie 'NSNumber' w miejsce' MyKey' podwaja wydajność, wskazując, że 'MyKey' odpowiada za około 1/2 wydajności. Uzgodniono, że 'NSNumber' może nie być najlepszym testem wydajności. Istnieje jednak problem z synchronizacją metod hash i równości, a także tworzenie kluczowych obiektów do testu, które są częścią taktowania. – zaph

+0

Nie próbuję argumentować, że Objective-C NSMutableDictionary jest nawet bliski prędkości podanej dla implementacji Java. Ale jestem zaskoczony różnicą. – zaph

+0

Masz rację, że używanie NSNumber jest szybsze i nie wiem dlaczego. FYI, użyłem klawisza "mutable", aby uniknąć przydzielania pamięci w pętli odczytu. –

Odpowiedz

2

Można reimplement metodę -isEqual: tak aby uniknąć udostępniające właściwości:

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

To nie byłoby dopuszczalne, jeśli klasa MyKey może być podklasy, ale widzę z kodu Java, że ​​klasa jest final.

1

Złożoność obliczeniowa w NSMutableDictionary jest następny (z pliku 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. 

Znaczy, prawie cały czas trzeba mieć O (1) złożoność na dostęp/wstawiania/usuwania. W przypadku Javy HashMap powinieneś uzyskać prawie to samo.

Według badań this nie ma żadnych korzyści przy korzystaniu z inicjatora wygodności dictionaryWithCapacity:.

W przypadku użycia liczby całkowitej jako klucza prawdopodobnie możliwe byłoby zastąpienie słownika tablicą.

W tym WWDC session wyjaśnił objc_msgSend problemy z wydajnością i jak sobie z nimi poradzić. Pierwsze rozwiązanie to użycie kontenerów C++ i STL. Drugi to użycie Swift, ponieważ w przeciwieństwie do Objective-C jest dynamiczny tylko wtedy, gdy się go zauważa.

+0

Nie wyjaśnia to 1) dlaczego Java jest 10x15x szybsza niż ObjC w tym benchmarku, lub 2) dlaczego Swift jest prawie dwukrotnie wolniejszy. –

+0

1) W Javie masz kod bajtowy i JVM. Nie ma sensu porównywać wydajności Java z Objective C/Swift. 2) Testy zależą od wielu czynników, takich jak implementacja, kompilator, poziom optymalizacji itp. Zgodnie z tymi [wzorcami] (https://github.com/vsco/swift-benchmarks) wszystko jest w porządku z wydajnością Swift. – sgl0v

+0

Oczywiście warto porównywać te rzeczy. Java, pomimo swojego JIT, jest uważana za jeden z najwolniejszych języków głównego nurtu. Zarówno Objective C, jak i Swift są statycznie kompilowane i dostępne są silne optymalizacje w czasie kompilacji. Oczekuje się, że zniszczą Javę w tak zwanym mikropocisku, ale nie, o rząd wielkości. Dlaczego nie i dlaczego optymalizator tak nas drastycznie obniża, są interesujące pytania godne dyskusji. Twoja odpowiedź nawet nie zarysuje powierzchni. –

Powiązane problemy