2011-08-28 12 views
18

Zrobiłem mały test i okazało się, że array.sort(function(a, b) { return a - b; }); jest dużo szybszy niż array.sort(); w JavaScript.JavaScript, sortowanie z drugim parametrem jest szybsze

Wyniki były dość szokujące, około 1,7 razy szybciej w IE9, 1,6 razy w FF7 i 6,7 razy w Chrome.

Ponadto, implementując quicksort samodzielnie w JS, stwierdziłem, że był on szybszy niż obie wyżej wymienione metody. (Dwie różne implementacje, jedna akceptuje funkcję porównywania jako parametr, druga nie. Oba były szybsze.)

Czy istnieje uzasadnione wytłumaczenie?

EDIT: Moje implementacje:

Nie comparer:

function quickSort(array, from, to) { 
    if(typeof from === 'undefined') { 
     from = 0; 
     to = array.length - 1; 
    } 
    else if(typeof to === 'undefined') { 
     to = array.length - 1; 
    } 

    if(to - from < 1) { 
     return; 
    } 

    var i = from, pivot = to, t; 

    while(i < pivot) { 
     if(array[i] > array[pivot]) { 
      t = array[i]; 
      array[i] = array[pivot - 1]; 
      array[pivot - 1] = array[pivot]; 
      array[pivot] = t; 
      pivot--; 
     } 
     else { 
      i++; 
     } 
    } 

    quickSort(array, from, pivot - 1); 
    quickSort(array, pivot + 1, to); 
} 

Z comparer:

function quickSortFunc(array, sortfunc, from, to) { 
    if(typeof from === 'undefined') { 
     from = 0; 
     to = array.length - 1; 
    } 
    else if(typeof to === 'undefined') { 
     to = array.length - 1; 
    } 

    if(to - from < 1) { 
     return; 
    } 

    var i = from, pivot = to, t; 

    while(i < pivot) { 
     if(sortfunc(array[i], array[pivot]) > 0) { 
      t = array[i]; 
      array[i] = array[pivot - 1]; 
      array[pivot - 1] = array[pivot]; 
      array[pivot] = t; 
      pivot--; 
     } 
     else { 
      i++; 
     } 
    } 

    quickSortFunc(array, sortfunc, from, pivot - 1); 
    quickSortFunc(array, sortfunc, pivot + 1, to); 
} 
+0

jest możliwość, że funkcja sortowania jest prowadzony od średnich. Jak duże były używane tablice? – Matt

+3

Sortowanie "normalne" działa na reprezentację łańcuchów elementów. To może być możliwe narzut. –

+0

Matt, przetestowałem go na tablicach 100, 1000, 10000 i 100000 elementów. Felix, ja o tych dwóch, to wciąż nie wyjaśnia, dlaczego moja implementacja z porównywarką była szybsza niż natywna implementacja z porównywarką. –

Odpowiedz

1

Są dwa czynniki, które wchodzą w grę:

Po pierwsze, jak Felix Króla wymienione w komentarzach, metoda rodzimego sortowania przekształca każdego członka tablicy na ciąg znaków przed porównaniem. Korzystanie z function(a, b) { return a - b; } jest szybsze, jeśli wszyscy (lub większość) członków tablicy to liczby.

Po drugie, algorytm sortowania to implementation dependent. Jak możesz lub nie możesz wiedzieć, quicksort działa bardzo źle, jeśli wstawisz nowy element do już posortowanej tablicy. Może dlatego WebKit zdecydował się zamiast tego zastosować Sortowanie Wyboru.

Ale nie bój się, pomoc jest blisko! Ktoś już forked WebKit to fix this

0

W grę wchodzi wiele powodów. Nie mając sprawdzić typ zmiennej jest jednym z nich, a tylko jeden z nich. A twoja implementacja sprawia, że ​​optymalizator jest szczęśliwy. Współpracuje z gęstej matrycy, to działa tylko z numerami, zmienne są dobrze lunetą i ponownego użycia. Nie, nie, bez, bez ewaluacji, żadnych magicznych zmiennych, właściwości, funkcji ani typów. Optymalizuje się dobrze.

Jednakże, jeśli próbujesz wdrożyć niezależne od rodzaju, niezależne od zamówienia metody, takie jak reverse(), możesz również zauważyć, że twoja własna implementacja jest szybsza. Przynajmniej moje jest.

Dlaczego?

JavaScript jest mocno zoptymalizowany w dzisiejszych czasach, espcially na pętlach i powtarzane operacje na samego rodzaju pasz - numer, sznurka, a nawet obiektów samym kształcie (to skomplikowane). W skrajnych przypadkach środowisko wykonawcze będzie wstawiać twoje funkcje, pomija sprawdzanie typów zmiennych, a w przypadku Chrome nawet twoje numery będą rejestrowane, tak aby pętla mogła być tak szybka, jak C.

Wow.

Ale te optymalizacje tylko wystartował w ostatnich latach. W tej chwili funkcje natywne nie są jeszcze tak optymalizowane, jak kod użytkownika. Nie podlegają tak dynamicznej optymalizacji, jak robi to kod użytkownika.

Tam, powiedziałem to.

Obecnie kod użytkownik może działać szybciej niż natywnej implementacji espcially ponieważ programista wie, co przepływ danych w nim.Ale może to być tymczasowe.

Zatrzymam się tutaj i pozwolę ci zdecydować, czy chcesz utworzyć własną bibliotekę tablic. ;)

+0

To tylko część prawdy. Zauważ, że większość metod Array jest celowo definiowana jako generyczna, więc muszą pracować na obiektach, które nie są tablicami. Jeśli twoja implementacja jest szybsza, prawdopodobnie dlatego, że zapomniałeś jakiegoś szalonego specjalnego przypadku. Na przykład 'Array.prototype.reverse.call ({'2': '2', '3': '3', length: '3.5', apples: 'oranges'})' lub nawet 'var a = [] ; a [1] = 1; a.reverse(). hasOwnProperty (1) // false ". Są to rzeczy, których dostawcy przeglądarek nie mogą optymalizować. W przeciwnym razie jQuery będzie musiał pracować nad innym biletem;) – user123444555621

+0

@ Pumbaa80: Właśnie sprawdziłem specyfikację, ale nie mogłem zobaczyć, co jest ekstremalne w tym przykładzie. Spec mówi: weź długość, ustaw niepodpisany int, a następnie zacznij odwracanie z 0 na długość-1. Czy mogę wiedzieć, jaka jest pułapka? – Sheepy

+0

Nie widziałem twojej implementacji, ale [moja (biorąc pod uwagę specjalne przypadki)] (http://jsperf.com/array-reverse) jest zdecydowanie wolniejsza. – user123444555621

0

To dość dzikie domysły, ale czy może nastąpić trafienie wydajnościowe z powodu rodzimego sortowania, sprawdzanie, czy przekazane atrybuty są puste czy zerowe ... Wyszukując domyślną funkcję każdego wyszukiwania (zamiast raz) ...

to może być mylone kwestia optymalizacji, które mogą zostać rozwiązane, jeśli prawdziwe ... Mam nadzieję, że ktoś w Firefoksie dev może odpowiedzieć na to pytanie :)