10 개의 숫자를 정렬하는 가장 빠른 방법? (숫자는 32 비트입니다)


211

문제를 해결하고 있으며 10 개의 숫자 (int32)를 매우 빠르게 정렬합니다. 내 응용 프로그램은 가능한 한 빨리 10 백만 번을 정렬해야합니다. 수십억 개의 요소로 구성된 데이터 세트를 샘플링하고 있으며 10 개의 숫자를 골라서 단순화해야 할 때마다 정렬합니다 (정렬 된 10 개의 요소 목록에서 결론을 내립니다).

현재 삽입 정렬을 사용하고 있지만 삽입 정렬을 이길 10 개의 특정 문제에 대해 매우 빠른 사용자 지정 정렬 알고리즘을 구현할 수 있다고 생각합니다.

누구 든지이 문제에 접근하는 방법에 대해 알고 있습니까?


14
조잡하게 들리면 일련의 중첩 된 if진술이 가장 효과적입니다. 루프를 피하십시오.
John Alexiou

8
순열 집합에 편향이있는 숫자가 주어 지거나 균일하게 분포 될 것으로 기대하십니까? 하나의 목록과 다음 목록의 순서 사이에 어떤 관계가 있습니까?
Douglas Zare

4
Benford의 법칙에 따라 전체 데이터 세트 (수십억 개의 숫자)가 배포되지만이 세트에서 무작위로 요소를 선택할 때 더 이상 존재하지 않습니다.
bodacydo


11
수십억 개의 요소 중에서 무작위로 선택하는 경우 전체 데이터 세트가 RAM에 있더라도 해당 데이터를 가져 오는 대기 시간이 선택한 요소를 정렬하는 데 필요한 시간보다 더 큰 영향을 줄 수 있습니다. 데이터를 순차적으로 또는 무작위로 선택하여 성능을 벤치마킹하여 영향을 테스트 할 수 있습니다.
Steve S.

답변:


213

(HelloWorld의 제안에 따라 정렬 네트워크를 조사합니다.)

29 비교 / 스왑 네트워크가 10 입력 정렬을 수행하는 가장 빠른 방법 인 것 같습니다. 나는 1969 년 Waksman이 발견 한 네트워크를 Javascript 에서이 예제에 사용했습니다.이 스크립트는 if명령문, 비교 및 ​​스왑 목록 일뿐이므로 C로 직접 변환해야합니다 .

function sortNet10(data) {	// ten-input sorting network by Waksman, 1969
    var swap;
    if (data[0] > data[5]) { swap = data[0]; data[0] = data[5]; data[5] = swap; }
    if (data[1] > data[6]) { swap = data[1]; data[1] = data[6]; data[6] = swap; }
    if (data[2] > data[7]) { swap = data[2]; data[2] = data[7]; data[7] = swap; }
    if (data[3] > data[8]) { swap = data[3]; data[3] = data[8]; data[8] = swap; }
    if (data[4] > data[9]) { swap = data[4]; data[4] = data[9]; data[9] = swap; }
    if (data[0] > data[3]) { swap = data[0]; data[0] = data[3]; data[3] = swap; }
    if (data[5] > data[8]) { swap = data[5]; data[5] = data[8]; data[8] = swap; }
    if (data[1] > data[4]) { swap = data[1]; data[1] = data[4]; data[4] = swap; }
    if (data[6] > data[9]) { swap = data[6]; data[6] = data[9]; data[9] = swap; }
    if (data[0] > data[2]) { swap = data[0]; data[0] = data[2]; data[2] = swap; }
    if (data[3] > data[6]) { swap = data[3]; data[3] = data[6]; data[6] = swap; }
    if (data[7] > data[9]) { swap = data[7]; data[7] = data[9]; data[9] = swap; }
    if (data[0] > data[1]) { swap = data[0]; data[0] = data[1]; data[1] = swap; }
    if (data[2] > data[4]) { swap = data[2]; data[2] = data[4]; data[4] = swap; }
    if (data[5] > data[7]) { swap = data[5]; data[5] = data[7]; data[7] = swap; }
    if (data[8] > data[9]) { swap = data[8]; data[8] = data[9]; data[9] = swap; }
    if (data[1] > data[2]) { swap = data[1]; data[1] = data[2]; data[2] = swap; }
    if (data[3] > data[5]) { swap = data[3]; data[3] = data[5]; data[5] = swap; }
    if (data[4] > data[6]) { swap = data[4]; data[4] = data[6]; data[6] = swap; }
    if (data[7] > data[8]) { swap = data[7]; data[7] = data[8]; data[8] = swap; }
    if (data[1] > data[3]) { swap = data[1]; data[1] = data[3]; data[3] = swap; }
    if (data[4] > data[7]) { swap = data[4]; data[4] = data[7]; data[7] = swap; }
    if (data[2] > data[5]) { swap = data[2]; data[2] = data[5]; data[5] = swap; }
    if (data[6] > data[8]) { swap = data[6]; data[6] = data[8]; data[8] = swap; }
    if (data[2] > data[3]) { swap = data[2]; data[2] = data[3]; data[3] = swap; }
    if (data[4] > data[5]) { swap = data[4]; data[4] = data[5]; data[5] = swap; }
    if (data[6] > data[7]) { swap = data[6]; data[6] = data[7]; data[7] = swap; }
    if (data[3] > data[4]) { swap = data[3]; data[3] = data[4]; data[4] = swap; }
    if (data[5] > data[6]) { swap = data[5]; data[5] = data[6]; data[6] = swap; }
    return(data);
}

alert(sortNet10([5,7,1,8,4,3,6,9,2,0]));

다음은 네트워크를 그래픽으로 표현한 것으로 독립 단계로 나뉩니다. 병렬 처리를 이용하기 위해 5-4-3-4-4-4-3-2 그룹을 4-4-4-4-4-4-3-2 그룹으로 변경할 수 있습니다.
10 입력 분류 ​​네트워크 (1969 년 왓츠 맨)

10 개 입력 정렬 네트워크 (1969 년 Waksman) 재 그룹화


69
암시; 스왑 매크로를 사용하십시오. 같은#define SORTPAIR(data, i1, i2) if (data[i1] > data[i2]) { int swap = data[i1]... }
피터 코르

9
이것이 최소임을 논리적으로 보여줄 수 있습니까?
corsiKa

8
@corsiKa 그렇습니다. 분류 네트워크는 컴퓨터 과학 초기부터 연구 분야였습니다. 대부분의 경우 최적의 솔루션은 수십 년 동안 알려져 왔습니다. 참조 en.wikipedia.org/wiki/Sorting_network
M69 ''snarky와 unwelcoming ''

8
테스트 할 Jsperf를 만들었으며 네트워크 정렬이 브라우저의 기본 정렬보다 20 배 이상 빠르다는 것을 확인할 수 있습니다. jsperf.com/fastest-10-number-sort
다니엘

9
@Katai 이것은 컴파일러가 생성 할 수있는 최적화를 파괴합니다. 나쁜 생각. 더 많은 정보를 원하시면 이것을 읽으십시오 en.wikipedia.org/wiki/…
Antzi

88

이 고정 크기를 다룰 때는 Sorting Networks를 살펴보십시오 . 이 알고리즘은 런타임이 고정되어 있으며 입력과 독립적입니다. 사용 사례의 경우 일부 정렬 알고리즘과 같은 오버 헤드가 없습니다.

Bitonic sort 는 그러한 네트워크의 구현입니다. 이것은 CPU에서 len (n) <= 32에서 가장 잘 작동합니다. 더 큰 입력에서는 GPU 로의 전환을 생각할 수 있습니다. https://ko.wikipedia.org/wiki/Sorting_network

Btw, 정렬 알고리즘을 비교하기에 좋은 페이지는 여기 bitonic sort입니다.

http://www.sorting-algorithms.com


3
@ ErickG.Hagstrom 많은 솔루션이 있습니다. 29 개의 비교를 사용하는 한 동일하게 효율적입니다. 나는 1969 년부터 Waksman의 솔루션을 사용했습니다. 그는 29 비교 버전을 처음 발견 한 것 같습니다.
m69 ''으르렁 거리고 환영받지 않는 ''

1
예, @ m69 백만 이상이 있습니다. Waksman의 솔루션은 길이가 29이고 깊이가 9입니다. 링크 된 솔루션은 깊이 차원보다 길이 = 29, 깊이 = 8에 비해 개선되었습니다. 물론 C로 구현 될 때 깊이는 중요하지 않습니다.
Erick G. Hagstrom 1

4
@ ErickG.Hagstrom 분명히 1973 년 Knuth가 발견 한 깊이 7의 87 가지 솔루션이 있지만 빠른 Google로는 그중 하나를 찾을 수 없었습니다. larc.unt.edu/ian/pubs/9-input.pdf (결론, p.14 참조)
m69 ''뱀파이어와 환영받지 않음 ''

4
@ ErickG.Hagstrom : 깊이는 "C 수준에서"차이를 만들지 않지만, 아마도 컴파일러와 CPU가이를 마치면 CPU 내에서 부분적으로 병렬화 될 수 있으므로 깊이가 더 작을 수 있습니다. 물론 CPU에 따라 : 일부 CPU는 상대적으로 단순하고 순차적으로 수행되는 반면, 일부 CPU는 여러 작업을 수행 할 수 있습니다. 특히로드 및 저장에 필요한 스택의 스택에 대해 매우 다른 성능을 얻을 수 있습니다. 수행 방법에 따라 10 개의 변수를 조작합니다.
Steve Jessop

1
@ ErickG.Hagstrom 이안 파 베리 (Ian Parberry)의 논문에서 즉시 명확하지는 않았지만 깊이 7 네트워크의 길이는 29보다 큽니다. Knuth, "The Art Of Computer Programming Vol.III", §5.3.4, fig . 49와 51.
m69 ''뱀파이어와 환영받지 못한 ''

33

4 개 그룹으로 비교되는 정렬 네트워크를 사용하면 SIMD 레지스터에서 비교할 수 있습니다. 패킹 된 최소 / 최대 명령어 쌍은 패킹 된 비교기 기능을 구현합니다. 죄송합니다. 지금보고있는 페이지를 찾을 시간이 없지만 SIMD 또는 SSE 정렬 네트워크에서 검색하면 문제가 발생합니다.

x86 SSE에는 4 개의 32 비트 정수 벡터에 대한 32 비트 정수 최소 및 최대 명령어가 포함되어 있습니다. AVX2 (Haswell 이상)는 동일하지만 256b 벡터에 대해 8 개의 정수를 갖습니다. 효율적인 셔플 명령도 있습니다.

독립적 인 작은 정렬이 많은 경우 벡터를 사용하여 4 또는 8 정렬을 병렬로 수행 할 수 있습니다. Esp. 요소를 무작위로 선택하는 경우 (정렬 할 데이터가 메모리에서 연속적이지 않음) 셔플을 피하고 필요한 순서대로 간단히 비교할 수 있습니다. 10 ints의 4 개 (AVX2 : 8) 목록의 모든 데이터를 보유하는 10 개의 레지스터는 여전히 스크래치 공간에 대해 6 개의 regs를 남깁니다.

연관된 데이터도 정렬해야하는 경우 벡터 정렬 네트워크의 효율성이 떨어집니다. 이 경우 가장 효율적인 방법은 압축 비교를 사용하여 요소가 변경된 마스크를 가져오고 해당 마스크를 사용하여 관련 데이터의 벡터를 참조하는 것입니다.


26

롤링되지 않은 분기없는 선택 정렬은 어떻습니까?

#include <iostream>
#include <algorithm>
#include <random>

//return the index of the minimum element in array a
int min(const int * const a) {
  int m = a[0];
  int indx = 0;
  #define TEST(i) (m > a[i]) && (m = a[i], indx = i ); 
  //see http://stackoverflow.com/a/7074042/2140449
  TEST(1);
  TEST(2);
  TEST(3);
  TEST(4);
  TEST(5);
  TEST(6);
  TEST(7);
  TEST(8);
  TEST(9);
  #undef TEST
  return indx;
}

void sort( int * const a ){
  int work[10];
  int indx;
  #define GET(i) indx = min(a); work[i] = a[indx]; a[indx] = 2147483647; 
  //get the minimum, copy it to work and set it at max_int in a
  GET(0);
  GET(1);
  GET(2);
  GET(3);
  GET(4);
  GET(5);
  GET(6);
  GET(7);
  GET(8);
  GET(9);
  #undef GET
  #define COPY(i) a[i] = work[i];
  //copy back to a
  COPY(0);
  COPY(1);
  COPY(2);
  COPY(3);
  COPY(4);
  COPY(5);
  COPY(6);
  COPY(7);
  COPY(8);
  COPY(9);
  #undef COPY
}

int main() {
  //generating and printing a random array
  int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
  std::random_device rd;
  std::mt19937 g(rd());
  std::shuffle( a, a+10, g);
  for (int i = 0; i < 10; i++) {
    std::cout << a[i] << ' ';
  }
  std::cout << std::endl;

  //sorting and printing again
  sort(a);
  for (int i = 0; i < 10; i++) {
    std::cout << a[i] << ' ';
  } 

  return 0;
}

http://coliru.stacked-crooked.com/a/71e18bc4f7fa18c6

유일하게 관련된 줄은 처음 두 개 #define입니다.

두 개의 목록을 사용하고 첫 번째 목록을 10 번 완전히 다시 검사하여 선택 정렬이 잘못 구현되지만 분기 및 가변 길이 루프를 피하므로 최신 프로세서 및 작은 데이터 세트로 보완 할 수 있습니다.


기준

정렬 네트워크에 대해 벤치마킹했으며 코드가 느려 보입니다. 그러나 언 롤링과 사본을 제거하려고했습니다. 이 코드를 실행 :

#include <iostream>
#include <algorithm>
#include <random>
#include <chrono>

int min(const int * const a, int i) {
  int m = a[i];
  int indx = i++;
  for ( ; i<10; i++) 
    //see http://stackoverflow.com/a/7074042/2140449
    (m > a[i]) && (m = a[i], indx = i ); 
  return indx;
}

void sort( int * const a ){
  for (int i = 0; i<9; i++)
    std::swap(a[i], a[min(a,i)]); //search only forward
}


void sortNet10(int * const data) {  // ten-input sorting network by Waksman, 1969
    int swap;
    if (data[0] > data[5]) { swap = data[0]; data[0] = data[5]; data[5] = swap; }
    if (data[1] > data[6]) { swap = data[1]; data[1] = data[6]; data[6] = swap; }
    if (data[2] > data[7]) { swap = data[2]; data[2] = data[7]; data[7] = swap; }
    if (data[3] > data[8]) { swap = data[3]; data[3] = data[8]; data[8] = swap; }
    if (data[4] > data[9]) { swap = data[4]; data[4] = data[9]; data[9] = swap; }
    if (data[0] > data[3]) { swap = data[0]; data[0] = data[3]; data[3] = swap; }
    if (data[5] > data[8]) { swap = data[5]; data[5] = data[8]; data[8] = swap; }
    if (data[1] > data[4]) { swap = data[1]; data[1] = data[4]; data[4] = swap; }
    if (data[6] > data[9]) { swap = data[6]; data[6] = data[9]; data[9] = swap; }
    if (data[0] > data[2]) { swap = data[0]; data[0] = data[2]; data[2] = swap; }
    if (data[3] > data[6]) { swap = data[3]; data[3] = data[6]; data[6] = swap; }
    if (data[7] > data[9]) { swap = data[7]; data[7] = data[9]; data[9] = swap; }
    if (data[0] > data[1]) { swap = data[0]; data[0] = data[1]; data[1] = swap; }
    if (data[2] > data[4]) { swap = data[2]; data[2] = data[4]; data[4] = swap; }
    if (data[5] > data[7]) { swap = data[5]; data[5] = data[7]; data[7] = swap; }
    if (data[8] > data[9]) { swap = data[8]; data[8] = data[9]; data[9] = swap; }
    if (data[1] > data[2]) { swap = data[1]; data[1] = data[2]; data[2] = swap; }
    if (data[3] > data[5]) { swap = data[3]; data[3] = data[5]; data[5] = swap; }
    if (data[4] > data[6]) { swap = data[4]; data[4] = data[6]; data[6] = swap; }
    if (data[7] > data[8]) { swap = data[7]; data[7] = data[8]; data[8] = swap; }
    if (data[1] > data[3]) { swap = data[1]; data[1] = data[3]; data[3] = swap; }
    if (data[4] > data[7]) { swap = data[4]; data[4] = data[7]; data[7] = swap; }
    if (data[2] > data[5]) { swap = data[2]; data[2] = data[5]; data[5] = swap; }
    if (data[6] > data[8]) { swap = data[6]; data[6] = data[8]; data[8] = swap; }
    if (data[2] > data[3]) { swap = data[2]; data[2] = data[3]; data[3] = swap; }
    if (data[4] > data[5]) { swap = data[4]; data[4] = data[5]; data[5] = swap; }
    if (data[6] > data[7]) { swap = data[6]; data[6] = data[7]; data[7] = swap; }
    if (data[3] > data[4]) { swap = data[3]; data[3] = data[4]; data[4] = swap; }
    if (data[5] > data[6]) { swap = data[5]; data[5] = data[6]; data[6] = swap; }
}


std::chrono::duration<double> benchmark( void(*func)(int * const), const int seed ) {
  std::mt19937 g(seed);
  int a[10] = {10,11,12,13,14,15,16,17,18,19};
  std::chrono::high_resolution_clock::time_point t1, t2; 
  t1 = std::chrono::high_resolution_clock::now();
  for (long i = 0; i < 1e7; i++) {
    std::shuffle( a, a+10, g);
    func(a);
  }
  t2 = std::chrono::high_resolution_clock::now();
  return std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
}

int main() {
  std::random_device rd;
  for (int i = 0; i < 10; i++) {
    const int seed = rd();
    std::cout << "seed = " << seed << std::endl;
    std::cout << "sortNet10: " << benchmark(sortNet10, seed).count() << std::endl;
    std::cout << "sort:      " << benchmark(sort,      seed).count() << std::endl;
  }
  return 0;
}

정렬 네트워크와 비교 하여 브랜치리스 선택 정렬에서 지속적으로 더 나은 결과를 얻고 있습니다.

$ gcc -v
gcc version 5.2.0 (GCC) 
$ g++ -std=c++11 -Ofast sort.cpp && ./a.out
seed = -1727396418
sortNet10: 2.24137
sort:      2.21828
seed = 2003959850
sortNet10: 2.23914
sort:      2.21641
seed = 1994540383
sortNet10: 2.23782
sort:      2.21778
seed = 1258259982
sortNet10: 2.25199
sort:      2.21801
seed = 1821086932
sortNet10: 2.25535
sort:      2.2173
seed = 412262735
sortNet10: 2.24489
sort:      2.21776
seed = 1059795817
sortNet10: 2.29226
sort:      2.21777
seed = -188551272
sortNet10: 2.23803
sort:      2.22996
seed = 1043757247
sortNet10: 2.2503
sort:      2.23604
seed = -268332483
sortNet10: 2.24455
sort:      2.24304

4
결과는 그리 인상적이지는 않지만 실제로 내가 기대했던 것입니다. 정렬 네트워크는 스왑이 아닌 비교를 최소화합니다. 모든 값이 캐시에 이미 있으면 스왑보다 훨씬 저렴하므로 스왑 수를 최소화하는 선택 정렬이 우선합니다. (그리고 더 많은 비교는 없습니다 : 29 개의 비교, 29 개의 스왑을 가진 네트워크?; 45 개의 비교 및 ​​최대 9 개의 스왑을 가진 선택 정렬)

7
라인 for ( ; i<10; i++) (m > a[i]) && (m = a[i], indx = i ); 이 예외적으로 최적화 되지 않는 한 아 그리고 그것은 가지가 있습니다 . (단락은 보통 분기의 형태입니다)

1
@EugeneRyabtsev도 마찬가지이지만 항상 똑같은 무작위 시퀀스가 ​​제공되므로 취소해야합니다. 로 바꾸려고 std::shuffle했습니다 for (int n = 0; n<10; n++) a[n]=g();. 실행 시간이 절반으로 줄어들고 네트워크가 더 빨라졌습니다.
DarioP

이것은 libc ++와 어떻게 비교 std::sort됩니까?
gnzlbg

1
@gnzlbg 나도 시도했지만 std::sort너무 나쁘게 수행하여 벤치 마크에 포함시키지 않았습니다. 작은 데이터 세트의 경우 상당한 오버 헤드가 있다고 생각합니다.
DarioP

20

질문은 이것이 일종의 웹 기반 응용 프로그램이라고 말하지 않습니다. 내 눈을 사로 잡은 한 가지는 :

수십억 개의 요소로 구성된 데이터 세트를 샘플링하고 있으며 10 개의 숫자를 골라서 단순화해야 할 때마다 정렬합니다 (정렬 된 10 개의 요소 목록에서 결론을 내립니다).

소프트웨어 및 하드웨어 엔지니어로서 이것은 "FPGA" 를 절규 합니다. 정렬 된 숫자 집합에서 데이터를 가져와야하거나 데이터가 어디에서 나오는지 어떤 결론을 내야할지 모르겠지만이 1 억에서 10 억 사이 의 "정렬 및 분류" "작업 분석 초당을 . 과거에 FPGA 지원 DNA 시퀀싱 작업을 수행했습니다. 문제가 해당 유형의 솔루션에 적합 할 때 FPGA의 거대한 처리 능력을 능가하는 것은 거의 불가능합니다.

어떤 수준에서 유일한 제한 요소는 얼마나 빨리 데이터를 FPGA에 삽으로 넣을 수 있고 얼마나 빨리 데이터를 꺼낼 수 있는지가됩니다.

참고로, 저는 초당 약 3 억 픽셀의 속도로 32 비트 RGB 이미지 데이터를 수신하는 고성능 실시간 이미지 프로세서를 설계했습니다. 데이터는 FIR 필터, 행렬 곱셈기, 룩업 테이블, 공간 에지 검출 블록 및 다른 끝에서 나오기 전에 여러 가지 다른 연산을 통해 스트리밍됩니다. 이 모든 것은 상대적으로 작은 Xilinx Virtex2 FPGA에서 약 33MHz에서 정확히 기억한다면 400MHz까지 내부 클럭킹이 가능합니다. 예, DDR2 컨트롤러를 구현하고 DDR2 메모리 뱅크 2 개를 실행했습니다.

FPGA는 수백 MHz에서 작동하면서 매 클럭 전환마다 10 개의 32 비트 수를 출력 할 수 있습니다. 데이터가 처리 파이프 라인을 채우면 작업 시작시 짧은 지연이 발생합니다. 그 후에는 시계 당 하나의 결과를 얻을 수 있어야합니다. 또는 정렬 및 분석 파이프 라인 복제를 통해 처리를 병렬화 할 수있는 경우 이상. 해결책은 원칙적으로 거의 사소합니다.

요점은 다음과 같습니다. 어플리케이션이 PC에 바운드되지 않고 데이터 스트림 및 처리가 FPGA 솔루션 (독립형 또는 기계의 보조 프로세서 카드)과 "호환"할 수있는 방법은 없습니다. 알고리즘에 관계없이 모든 언어로 작성된 소프트웨어로 달성 가능한 수준의 성능을 능가 할 수 있습니다.

편집하다:

빠른 검색을 실행하여 자신에게 유용한 논문을 찾았습니다. 2012 년으로 거슬러 올라간 것 같습니다. 오늘날에도 성능을 훨씬 더 향상시킬 수 있습니다. 여기있어:

FPGA에서 네트워크 정렬


10

필자는 최근 Bose-Nelson 알고리즘을 사용하여 컴파일 타임에 정렬 네트워크를 생성 하는 작은 클래스 를 작성했습니다.

10 개의 숫자에 대해 매우 빠른 정렬을 만드는 데 사용할 수 있습니다.

/**
 * A Functor class to create a sort for fixed sized arrays/containers with a
 * compile time generated Bose-Nelson sorting network.
 * \tparam NumElements  The number of elements in the array or container to sort.
 * \tparam T            The element type.
 * \tparam Compare      A comparator functor class that returns true if lhs < rhs.
 */
template <unsigned NumElements, class Compare = void> class StaticSort
{
    template <class A, class C> struct Swap
    {
        template <class T> inline void s(T &v0, T &v1)
        {
            T t = Compare()(v0, v1) ? v0 : v1; // Min
            v1 = Compare()(v0, v1) ? v1 : v0; // Max
            v0 = t;
        }

        inline Swap(A &a, const int &i0, const int &i1) { s(a[i0], a[i1]); }
    };

    template <class A> struct Swap <A, void>
    {
        template <class T> inline void s(T &v0, T &v1)
        {
            // Explicitly code out the Min and Max to nudge the compiler
            // to generate branchless code.
            T t = v0 < v1 ? v0 : v1; // Min
            v1 = v0 < v1 ? v1 : v0; // Max
            v0 = t;
        }

        inline Swap(A &a, const int &i0, const int &i1) { s(a[i0], a[i1]); }
    };

    template <class A, class C, int I, int J, int X, int Y> struct PB
    {
        inline PB(A &a)
        {
            enum { L = X >> 1, M = (X & 1 ? Y : Y + 1) >> 1, IAddL = I + L, XSubL = X - L };
            PB<A, C, I, J, L, M> p0(a);
            PB<A, C, IAddL, J + M, XSubL, Y - M> p1(a);
            PB<A, C, IAddL, J, XSubL, M> p2(a);
        }
    };

    template <class A, class C, int I, int J> struct PB <A, C, I, J, 1, 1>
    {
        inline PB(A &a) { Swap<A, C> s(a, I - 1, J - 1); }
    };

    template <class A, class C, int I, int J> struct PB <A, C, I, J, 1, 2>
    {
        inline PB(A &a) { Swap<A, C> s0(a, I - 1, J); Swap<A, C> s1(a, I - 1, J - 1); }
    };

    template <class A, class C, int I, int J> struct PB <A, C, I, J, 2, 1>
    {
        inline PB(A &a) { Swap<A, C> s0(a, I - 1, J - 1); Swap<A, C> s1(a, I, J - 1); }
    };

    template <class A, class C, int I, int M, bool Stop = false> struct PS
    {
        inline PS(A &a)
        {
            enum { L = M >> 1, IAddL = I + L, MSubL = M - L};
            PS<A, C, I, L, (L <= 1)> ps0(a);
            PS<A, C, IAddL, MSubL, (MSubL <= 1)> ps1(a);
            PB<A, C, I, IAddL, L, MSubL> pb(a);
        }
    };

    template <class A, class C, int I, int M> struct PS <A, C, I, M, true>
    {
        inline PS(A &a) {}
    };

public:
    /**
     * Sorts the array/container arr.
     * \param  arr  The array/container to be sorted.
     */
    template <class Container> inline void operator() (Container &arr) const
    {
        PS<Container, Compare, 1, NumElements, (NumElements <= 1)> ps(arr);
    };

    /**
     * Sorts the array arr.
     * \param  arr  The array to be sorted.
     */
    template <class T> inline void operator() (T *arr) const
    {
        PS<T*, Compare, 1, NumElements, (NumElements <= 1)> ps(arr);
    };
};

#include <iostream>
#include <vector>

int main(int argc, const char * argv[])
{
    enum { NumValues = 10 };

    // Arrays
    {
        int rands[NumValues];
        for (int i = 0; i < NumValues; ++i) rands[i] = rand() % 100;
        std::cout << "Before Sort: \t";
        for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
        std::cout << "\n";
        StaticSort<NumValues> staticSort;
        staticSort(rands);
        std::cout << "After Sort: \t";
        for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
        std::cout << "\n";
    }

    std::cout << "\n";

    // STL Vector
    {
        std::vector<int> rands(NumValues);
        for (int i = 0; i < NumValues; ++i) rands[i] = rand() % 100;
        std::cout << "Before Sort: \t";
        for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
        std::cout << "\n";
        StaticSort<NumValues> staticSort;
        staticSort(rands);
        std::cout << "After Sort: \t";
        for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
        std::cout << "\n";
    }

    return 0;
}

if (compare) swap명령문 대신 min 및 max에 대한 삼항 연산자를 명시 적으로 코딩합니다. 이것은 분기없는 코드를 사용하도록 컴파일러를 조금씩 움직입니다.

벤치 마크

다음 벤치 마크는 clang -O3으로 컴파일되어 2012 년 중반 macbook air에서 실행되었습니다.

무작위 데이터 정렬

DarioP의 코드와 비교해 보면, 다음은 크기가 10 인 32 만 int 배열을 백만 초 단위로 정렬하는 데 걸린 시간입니다 (밀리 초).

Hardcoded Sort Net 10 : 88.774ms
템플릿 Bose-Nelson sort 10 : 27.815ms

이 템플릿 방식을 사용하면 컴파일 타임에 다른 요소 수에 대한 정렬 네트워크를 생성 할 수도 있습니다.

다양한 크기의 백만 개의 배열을 정렬하는 데 걸리는 시간 (밀리 초)입니다.
크기 2, 4, 8의 배열에 대한 밀리 초 수는 각각 1.943, 8.655, 20.246입니다.
C ++ 템플릿 Bose-Nelson 정적 정렬 타이밍

크레딧 글렌 Teitelbaum 펼쳐진 삽입 정렬합니다.

다음은 6 개 요소의 작은 배열에 대한 정렬 당 평균 클럭입니다. 이 질문에서 벤치 마크 코드와 예제를 찾을 수 있습니다.
가장 빠른 종류의 고정 길이 6 int 배열

Direct call to qsort library function       : 326.81
Naive implementation (insertion sort)       : 132.98
Insertion Sort (Daniel Stutzbach)           : 104.04
Insertion Sort Unrolled                     : 99.64
Insertion Sort Unrolled (Glenn Teitelbaum)  : 81.55
Rank Order                                  : 44.01
Rank Order with registers                   : 42.40
Sorting Networks (Daniel Stutzbach)         : 88.06
Sorting Networks (Paul R)                   : 31.64
Sorting Networks 12 with Fast Swap          : 29.68
Sorting Networks 12 reordered Swap          : 28.61
Reordered Sorting Network w/ fast swap      : 24.63
Templated Sorting Network (this class)      : 25.37

6 요소에 대한 질문에서 가장 빠른 예만큼 빠릅니다.

정렬 된 데이터 정렬 성능

종종 입력 배열이 이미 정렬되었거나 대부분 정렬되어있을 수 있습니다.
이러한 경우 삽입 정렬이 더 나은 선택이 될 수 있습니다.

여기에 이미지 설명을 입력하십시오

데이터에 따라 적절한 정렬 알고리즘을 선택할 수 있습니다.

벤치 마크에 사용 된 코드는 여기 에서 찾을 수 있습니다 .


아래에 내 알고에 대한 비교를 추가 할 수 있습니까?
Glenn Teitelbaum

당신이 추가 기회 @GlennTeitelbaum 당신의 벤치 마크 및 공개 방법 및 결과를?
greybeard

정렬 된 입력 정렬에 대한 데이터 추가를위한 조언.
greybeard

일부 시스템 v1 = v0 < v1 ? v1 : v0; // Max이 대체 할 수있는 경우에 여전히이 분기 수 v1 += v0 - t있다면 있기 때문 t이다 v0다음 v1 + v0 -t == v1 + v0 - v0 == v1다른 t입니다 v1v1 + v0 -t == v1 + v0 - v1 == v0
글렌 Teitelbaum

삼항은 일반적 으로 최신 컴파일러 에서 maxss또는 minss명령어 로 컴파일됩니다 . 그러나 작동하지 않는 경우 다른 교환 방법을 사용할 수 있습니다. :)
벡터화

5

네트워크 정렬은 작은 배열에서 빠를 가능성이 높지만, 올바르게 최적화 된 경우 삽입 정렬을 이길 수없는 경우가 있습니다. 예를 들어 2 개의 요소가있는 배치 삽입 :

{
    final int a=in[0]<in[1]?in[0]:in[1];
    final int b=in[0]<in[1]?in[1]:in[0];
    in[0]=a;
    in[1]=b;
}
for(int x=2;x<10;x+=2)
{
    final int a=in[x]<in[x+1]?in[x]:in[x+1];
    final int b=in[x]<in[x+1]?in[x+1]:in[x];
    int y= x-1;

    while(y>=0&&in[y]>b)
    {
        in[y+2]= in[y];
        --y;
    }
    in[y+2]=b;
    while(y>=0&&in[y]>a)
    {
        in[y+1]= in[y];
        --y;
    }
    in[y+1]=a;
}

왜 반복하는지 모르겠습니다 in[y+2]= in[y];. 오타?
Glenn Teitelbaum

와, 내가 어떻게 했어? 그리고 누군가가 알아 차리는데 얼마나 오래 걸렸습니까? 답은 오타가 아닙니다. 나는 키와 값 배열을 가진 다른 알고리즘을 채택하고있었습니다.
warren

3

완전히 풀 수 있습니다 insertion sort

이를 쉽게하기 위해 재귀를 template함수 오버 헤드없이 사용할 수 있습니다. 그것은 이미를하기 때문에 template, int수 있습니다 template뿐만 아니라 매개 변수입니다. 이것은 또한 10이 아닌 코딩 배열 크기를 간단하게 만듭니다.

클래스를 마지막 항목의 인덱스를 사용하기 때문에 int x[10]호출 을 정렬 할 수 insert_sort<int, 9>::sort(x);있습니다. 이것은 랩핑 될 수 있지만 더 많은 코드를 읽을 수 있습니다.

template <class T, int NUM>
class insert_sort;

template <class T>
class insert_sort<T,0>
// stop template recursion
// sorting 1 item is a no-op
{
public:
    static void place(T *x) {}
    static void sort(T * x) {}
};

template <class T, int NUM>
class insert_sort
// use template recursion to do insertion sort
// NUM is the index of the last item, eg. for x[10] call <9>
{
public:
    static void place(T *x)
    {
        T t1=x[NUM-1];
        T t2=x[NUM];
        if (t1 > t2)
        {
            x[NUM-1]=t2;
            x[NUM]=t1;
            insert_sort<T,NUM-1>::place(x);
        }
    }
    static void sort(T * x)
    {
        insert_sort<T,NUM-1>::sort(x); // sort everything before
        place(x);                    // put this item in
    }
};

내 테스트에서 이것은 정렬 네트워크 예제보다 빠릅니다.


0

여기 에 설명 된 것과 비슷한 이유로 다음 정렬 기능 sort6_iterator()sort10_iterator_local()정렬 네트워크가 여기 에서 가져온 위치에서 잘 수행되어야합니다 .

template<class IterType> 
inline void sort10_iterator(IterType it) 
{
#define SORT2(x,y) {if(data##x>data##y)std::swap(data##x,data##y);}
#define DD1(a)   auto data##a=*(data+a);
#define DD2(a,b) auto data##a=*(data+a), data##b=*(data+b);
#define CB1(a)   *(data+a)=data##a;
#define CB2(a,b) *(data+a)=data##a;*(data+b)=data##b;
  DD2(1,4) SORT2(1,4) DD2(7,8) SORT2(7,8) DD2(2,3) SORT2(2,3) DD2(5,6) SORT2(5,6) DD2(0,9) SORT2(0,9) 
  SORT2(2,5) SORT2(0,7) SORT2(8,9) SORT2(3,6) 
  SORT2(4,9) SORT2(0,1) 
  SORT2(0,2) CB1(0) SORT2(6,9) CB1(9) SORT2(3,5) SORT2(4,7) SORT2(1,8) 
  SORT2(3,4) SORT2(5,8) SORT2(6,7) SORT2(1,2) 
  SORT2(7,8) CB1(8) SORT2(1,3) CB1(1) SORT2(2,5) SORT2(4,6) 
  SORT2(2,3) CB1(2) SORT2(6,7) CB1(7) SORT2(4,5) 
  SORT2(3,4) CB2(3,4) SORT2(5,6) CB2(5,6) 
#undef CB1
#undef CB2
#undef DD1
#undef DD2
#undef SORT2
}

이 함수를 호출하기 위해 std::vector반복자를 전달했습니다 .


0

삽입 정렬은 평균 29,6 비교를 통해 최상의 경우 9와 최악의 45 (역순으로 주어진 입력)로 10 개의 입력을 정렬해야합니다.

{9,6,1} 셸 정렬은 10 개의 입력을 정렬하기 위해 평균 25.5 비교가 필요합니다. 가장 좋은 경우는 14 비교, 최악은 34, 역 입력 정렬에는 22가 필요합니다.

따라서 삽입 정렬 대신 쉘 정렬을 사용하면 평균 사례가 14 % 줄어 듭니다. 최상의 경우는 56 % 증가하지만 최악의 경우는 24 % 감소합니다. 이는 최악의 경우 성능을 확인하는 것이 중요한 응용 프로그램에서 중요합니다. 반대의 경우는 51 % 감소합니다.

삽입 정렬에 익숙한 것 같으므로 {9,6}에 대한 정렬 네트워크로 알고리즘을 구현 한 다음 그 후 삽입 정렬 ({1})을 선택할 수 있습니다.

i[0] with i[9]    // {9}

i[0] with i[6]    // {6}
i[1] with i[7]    // {6}
i[2] with i[8]    // {6}
i[3] with i[9]    // {6}

i[0 ... 9]        // insertion sort
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.