컨테스트 : 대규모 가우시안 분산 데이터를 정렬하는 가장 빠른 방법


71

이 질문에 대한 관심에 따라, 콘테스트를 제안함으로써 답변을 좀 더 객관적이고 정량적으로 만드는 것이 흥미로울 것이라고 생각했습니다.

아이디어는 간단합니다. 5 천만 개의 가우시안 분산 복식 (평균 : 0, 표준 1)을 포함하는 이진 파일을 생성했습니다. 목표는 가능한 빨리 메모리에 정렬하는 프로그램을 만드는 것입니다. 파이썬에서 매우 간단한 참조 구현은 1m4를 완료하는 데 필요합니다. 우리는 얼마나 낮을 수 있습니까?

규칙은 다음과 같습니다. "gaussian.dat"파일을 열고 메모리에서 숫자를 정렬 (출력 할 필요 없음)하는 프로그램과 프로그램을 작성하고 실행하기위한 지침으로 응답하십시오. 이 프로그램은 아치 리눅스 머신에서 작동 할 수 있어야합니다 (즉,이 시스템에 쉽게 설치 가능한 프로그래밍 언어 나 라이브러리를 사용할 수 있음).

프로그램을 읽을 수 있어야 합리적으로 시작할 수 있습니다 (어셈블러 전용 솔루션은 없습니다!).

나는 내 컴퓨터 (쿼드 코어, 4 기가 바이트의 RAM)에서 답을 실행할 것입니다. 가장 빠른 솔루션은 허용 된 답변과 100 점 현상금을 얻습니다 :)

숫자를 생성하는 데 사용 된 프로그램 :

#!/usr/bin/env python
import random
from array import array
from sys import argv
count=int(argv[1])
a=array('d',(random.gauss(0,1) for x in xrange(count)))
f=open("gaussian.dat","wb")
a.tofile(f)

간단한 참조 구현 :

#!/usr/bin/env python
from array import array
from sys import argv
count=int(argv[1])
a=array('d')
a.fromfile(open("gaussian.dat"),count)
print "sorting..."
b=sorted(a)

편집 : 4GB의 RAM 만 유감입니다.

편집 # 2 : 컨테스트의 요점은 데이터에 대한 사전 정보를 사용할 수 있는지 확인하는 것 입니다. 다른 프로그래밍 언어 구현 사이에 오줌을 싸는 것은 아닙니다!


1
각 값을 가져 와서 "예상"위치로 직접 이동 한 다음 변위 된 값에 대해 반복하십시오. 그 문제를 해결하는 방법을 잘 모르겠습니다. 완료되면 기포 정렬이 완료 될 때까지 정렬합니다 (두 번 통과해야 함).

1
만약 이것이 끝나지 않았다면 내일 저녁에 버킷 정렬 솔루션을 게시 할 것입니다 :)

1
@static_rtti-CG를 많이 사용하는 CG.SE에서 "우리"를 해킹하는 것과 똑같습니다. 판독 모드로 전환하려면 CG로 옮기십시오. 닫지 마십시오.
arrdem

1
CodeGolf.SE에 오신 것을 환영합니다! 나는 이것이 어디에 있거나 존재하지 않는지에 대한 SO 원본에서 많은 논평을 지우고 CodeGolf.SE 주류에 더 가깝게 태그를 다시 붙였습니다.
dmckee

2
여기서 가장 까다로운 문제는 객관적인 승리 기준을 찾고 "가장 빠른"플랫폼 의존성을 소개한다는 것입니다. cpython 가상 머신에 구현 된 O (n ^ {1.2}) 알고리즘이 O (n ^ {1.3} c) 비슷한 상수를 가진 알고리즘? 나는 일반적으로 각 솔루션의 성능 특성에 대한 논의를 제안합니다. 이는 사람들이 무슨 일이 일어나고 있는지 판단하는 데 도움이 될 수 있습니다.
dmckee

답변:


13

다음은 C ++의 솔루션으로 먼저 숫자를 동일한 예상 개수의 요소가있는 버킷으로 분할 한 다음 각 버킷을 개별적으로 정렬합니다. Wikipedia의 일부 수식을 기반으로 누적 분포 함수 테이블을 미리 계산 한 다음이 테이블의 값을 보간하여 빠른 근사값을 얻습니다.

4 개의 코어를 사용하기 위해 여러 단계가 여러 스레드에서 실행됩니다.

#include <cstdlib>
#include <math.h>
#include <stdio.h>
#include <algorithm>

#include <tbb/parallel_for.h>

using namespace std;

typedef unsigned long long ull;

double signum(double x) {
    return (x<0) ? -1 : (x>0) ? 1 : 0;
}

const double fourOverPI = 4 / M_PI;

double erf(double x) {
    double a = 0.147;
    double x2 = x*x;
    double ax2 = a*x2;
    double f1 = -x2 * (fourOverPI + ax2) / (1 + ax2);
    double s1 = sqrt(1 - exp(f1));
    return signum(x) * s1;
}

const double sqrt2 = sqrt(2);

double cdf(double x) {
    return 0.5 + erf(x / sqrt2) / 2;
}

const int cdfTableSize = 200;
const double cdfTableLimit = 5;
double* computeCdfTable(int size) {
    double* res = new double[size];
    for (int i = 0; i < size; ++i) {
        res[i] = cdf(cdfTableLimit * i / (size - 1));
    }
    return res;
}
const double* const cdfTable = computeCdfTable(cdfTableSize);

double cdfApprox(double x) {
    bool negative = (x < 0);
    if (negative) x = -x;
    if (x > cdfTableLimit) return negative ? cdf(-x) : cdf(x);
    double p = (cdfTableSize - 1) * x / cdfTableLimit;
    int below = (int) p;
    if (p == below) return negative ? -cdfTable[below] : cdfTable[below];
    int above = below + 1;
    double ret = cdfTable[below] +
            (cdfTable[above] - cdfTable[below])*(p - below);
    return negative ? 1 - ret : ret;
}

void print(const double* arr, int len) {
    for (int i = 0; i < len; ++i) {
        printf("%e; ", arr[i]);
    }
    puts("");
}

void print(const int* arr, int len) {
    for (int i = 0; i < len; ++i) {
        printf("%d; ", arr[i]);
    }
    puts("");
}

void fillBuckets(int N, int bucketCount,
        double* data, int* partitions,
        double* buckets, int* offsets) {
    for (int i = 0; i < N; ++i) {
        ++offsets[partitions[i]];
    }

    int offset = 0;
    for (int i = 0; i < bucketCount; ++i) {
        int t = offsets[i];
        offsets[i] = offset;
        offset += t;
    }
    offsets[bucketCount] = N;

    int next[bucketCount];
    memset(next, 0, sizeof(next));
    for (int i = 0; i < N; ++i) {
        int p = partitions[i];
        int j = offsets[p] + next[p];
        ++next[p];
        buckets[j] = data[i];
    }
}

class Sorter {
public:
    Sorter(double* data, int* offsets) {
        this->data = data;
        this->offsets = offsets;
    }

    static void radixSort(double* arr, int len) {
        ull* encoded = (ull*)arr;
        for (int i = 0; i < len; ++i) {
            ull n = encoded[i];
            if (n & signBit) {
                n ^= allBits;
            } else {
                n ^= signBit;
            }
            encoded[i] = n;
        }

        const int step = 11;
        const ull mask = (1ull << step) - 1;
        int offsets[8][1ull << step];
        memset(offsets, 0, sizeof(offsets));

        for (int i = 0; i < len; ++i) {
            for (int b = 0, j = 0; b < 64; b += step, ++j) {
                int p = (encoded[i] >> b) & mask;
                ++offsets[j][p];
            }
        }

        int sum[8] = {0};
        for (int i = 0; i <= mask; i++) {
            for (int b = 0, j = 0; b < 64; b += step, ++j) {
                int t = sum[j] + offsets[j][i];
                offsets[j][i] = sum[j];
                sum[j] = t;
            }
        }

        ull* copy = new ull[len];
        ull* current = encoded;
        for (int b = 0, j = 0; b < 64; b += step, ++j) {
            for (int i = 0; i < len; ++i) {
                int p = (current[i] >> b) & mask;
                copy[offsets[j][p]] = current[i];
                ++offsets[j][p];
            }

            ull* t = copy;
            copy = current;
            current = t;
        }

        if (current != encoded) {
            for (int i = 0; i < len; ++i) {
                encoded[i] = current[i];
            }
        }

        for (int i = 0; i < len; ++i) {
            ull n = encoded[i];
            if (n & signBit) {
                n ^= signBit;
            } else {
                n ^= allBits;
            }
            encoded[i] = n;
        }
    }

    void operator() (tbb::blocked_range<int>& range) const {
        for (int i = range.begin(); i < range.end(); ++i) {
            double* begin = &data[offsets[i]];
            double* end = &data[offsets[i+1]];
            //std::sort(begin, end);
            radixSort(begin, end-begin);
        }
    }

private:
    double* data;
    int* offsets;
    static const ull signBit = 1ull << 63;
    static const ull allBits = ~0ull;
};

void sortBuckets(int bucketCount, double* data, int* offsets) {
    Sorter sorter(data, offsets);
    tbb::blocked_range<int> range(0, bucketCount);
    tbb::parallel_for(range, sorter);
    //sorter(range);
}

class Partitioner {
public:
    Partitioner(int bucketCount, double* data, int* partitions) {
        this->data = data;
        this->partitions = partitions;
        this->bucketCount = bucketCount;
    }

    void operator() (tbb::blocked_range<int>& range) const {
        for (int i = range.begin(); i < range.end(); ++i) {
            double d = data[i];
            int p = (int) (cdfApprox(d) * bucketCount);
            partitions[i] = p;
        }
    }

private:
    double* data;
    int* partitions;
    int bucketCount;
};

const int bucketCount = 512;
int offsets[bucketCount + 1];

int main(int argc, char** argv) {
    if (argc != 2) {
        printf("Usage: %s N\n N = the size of the input\n", argv[0]);
        return 1;
    }

    puts("initializing...");
    int N = atoi(argv[1]);
    double* data = new double[N];
    double* buckets = new double[N];
    memset(offsets, 0, sizeof(offsets));
    int* partitions = new int[N];

    puts("loading data...");
    FILE* fp = fopen("gaussian.dat", "rb");
    if (fp == 0 || fread(data, sizeof(*data), N, fp) != N) {
        puts("Error reading data");
        return 1;
    }
    //print(data, N);

    puts("assigning partitions...");
    tbb::parallel_for(tbb::blocked_range<int>(0, N),
            Partitioner(bucketCount, data, partitions));

    puts("filling buckets...");
    fillBuckets(N, bucketCount, data, partitions, buckets, offsets);
    data = buckets;

    puts("sorting buckets...");
    sortBuckets(bucketCount, data, offsets);

    puts("done.");

    /*
    for (int i = 0; i < N-1; ++i) {
        if (data[i] > data[i+1]) {
            printf("error at %d: %e > %e\n", i, data[i], data[i+1]);
        }
    }
    */

    //print(data, N);

    return 0;
}

컴파일하고 실행하려면 다음 명령을 사용하십시오.

g++ -O3 -ltbb -o gsort gsort.cpp && time ./gsort 50000000

편집 : 버킷을 다시 어레이로 복사 할 필요가 없도록 모든 버킷이 동일한 어레이에 배치됩니다. 또한 값이 충분히 정확하기 때문에 사전 계산 된 값이있는 테이블의 크기가 줄었습니다. 그래도 256 개가 넘는 버킷 수를 변경하면 해당 버킷 수보다 프로그램 실행 시간이 더 오래 걸립니다.

편집 : 동일한 알고리즘, 다른 프로그래밍 언어. Java 대신 C ++을 사용했으며 컴퓨터에서 실행 시간이 ~ 3.2 초에서 ~ 2.35 초로 줄었습니다. 최적의 버킷 수는 여전히 256 대 (내 컴퓨터에서)입니다.

그건 그렇고, tbb는 정말 훌륭합니다.

편집 : 나는 Alexandru의 훌륭한 솔루션에서 영감을 얻어 마지막 단계의 std :: sort를 수정 된 버전의 기수로 대체했습니다. 배열을 통해 더 많은 패스가 필요하더라도 양수 / 음수를 처리하기 위해 다른 방법을 사용했습니다. 또한 배열을 정확하게 정렬하고 삽입 정렬을 제거하기로 결정했습니다. 나중에 이러한 변화가 성능에 어떤 영향을 미치고 되돌릴 수 있는지 테스트하는 데 시간을 할애합니다. 그러나 기수 정렬을 사용하면 시간이 ~ 2.35 초에서 ~ 1.63 초로 줄었습니다.


좋은. 나는 3.055를 얻었다. 내가 얻을 수있는 가장 낮은 것은 6.3이었습니다. 나는 통계를 개선하기 위해 당신을 선택하고 있습니다. 버킷 수로 256을 선택한 이유는 무엇입니까? 128과 512를 시도했지만 256이 가장 효과적이었습니다.
Scott

버킷 수로 256을 선택한 이유는 무엇입니까? 128과 512를 시도했지만 256이 가장 효과적이었습니다. :) 나는 경험적으로 그것을 발견했고 버킷 수를 늘리면 알고리즘이 느려지는 이유를 모르겠습니다. 메모리 할당이 그렇게 오래 걸리지 않아야합니다. 캐시 크기와 관련이 있습니까?
k21

내 컴퓨터에서 2.725s. JVM의 로딩 시간을 고려하여 Java 솔루션에 매우 좋습니다.
static_rtti

2
내 및 Arjan의 솔루션 (내 구문보다 내 구문이 깨끗했기 때문에)에 따라 nio 패키지를 사용하도록 코드를 전환했으며 .3 초 더 빨리 얻을 수있었습니다. 나는 ssd를 가지고 있고, 그렇지 않은 경우 그 의미가 무엇인지 궁금합니다. 그것은 또한 당신의 비트 twiddling의 일부를 제거합니다. 수정 된 섹션이 있습니다.
Scott

3
이것은 내 테스트 (16core CPU) 에서 가장 빠른 병렬 솔루션입니다 . 1.94 초에서 1.22 초.
Alexandru

13

똑똑하지 않고 훨씬 더 순진한 분류기를 제공하기 위해 C에는 파이썬과 거의 동일해야합니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int cmp(const void* av, const void* bv) {
    double a = *(const double*)av;
    double b = *(const double*)bv;
    return a < b ? -1 : a > b ? 1 : 0;
}
int main(int argc, char** argv) {
    if (argc <= 1)
        return puts("No argument!");
    unsigned count = atoi(argv[1]);

    double *a = malloc(count * sizeof *a);

    FILE *f = fopen("gaussian.dat", "rb");
    if (fread(a, sizeof *a, count, f) != count)
        return puts("fread failed!");
    fclose(f);

    puts("sorting...");
    double *b = malloc(count * sizeof *b);
    memcpy(b, a, count * sizeof *b);
    qsort(b, count, sizeof *b, cmp);
    return 0;
}

gcc -O3내 컴퓨터에서으로 컴파일 하면 파이썬보다 1 분 이상 걸립니다 .87 초에 비해 약 11 초입니다.


1
내 컴퓨터에서 10.086s를 사용하여 현재 리더가되었습니다! 그러나 나는 우리가 더 잘할 수 있다고 확신한다 :)

1
임의의 double이 이러한 양의 데이터에서 서로 같지 않기 때문에 두 번째 삼항 연산자를 제거하고 해당 경우 1을 반환 할 수 있습니까?
Codism

@ Codism : 동등한 데이터의 위치를 ​​바꾸는 것을 신경 쓰지 않으므로 동등한 값을 얻을 수 있다고해도 적절한 단순화가 될 것입니다.

10

표준 편차를 기준으로 세그먼트로 분할하여 4로 나누는 것이 가장 좋습니다. 편집 : http://en.wikipedia.org/wiki/Error_function#Table_of_values의 x 값을 기준으로 파티션으로 다시 작성

http://www.wolframalpha.com/input/?i=percentages+by++normal+distribution

더 작은 버킷을 사용해 보았지만 사용 가능한 코어 수보다 2 * 한 번만 효과가 거의없는 것 같습니다. 병렬 컬렉션이 없으면 내 상자에서 37 초, 병렬 컬렉션에서 24 초가 걸립니다. 배포를 통해 파티션을 나누는 경우 배열을 사용할 수 없으므로 오버 헤드가 더 큽니다. 스칼라에서 값을 언제 상자에 넣거나 상자에 넣을지 명확하지 않습니다.

병렬 컬렉션에 scala 2.9를 사용하고 있습니다. tar.gz 배포판 만 다운로드하면됩니다.

컴파일하려면 : scalac SortFile.scala (방금 scala / bin 폴더에 직접 복사했습니다.

실행하려면 : JAVA_OPTS = "-Xmx4096M"./scala SortFile (2 기가의 램으로 실행했으며 거의 ​​같은 시간이 걸렸습니다)

편집 : assignDirect가 제거되었습니다. 할당보다 느립니다. 배열 버퍼의 초기 크기 프라이밍을 제거했습니다. 실제로 전체 50000000 값을 읽었습니다. 오토 박싱 문제를 피하기 위해 재 작성했습니다 (아직도 순진한 c보다 느립니다)

import java.io.FileInputStream;
import java.nio.ByteBuffer
import java.nio.ByteOrder
import scala.collection.mutable.ArrayBuilder


object SortFile {

//used partition numbers from Damascus' solution
val partList = List(0, 0.15731, 0.31864, 0.48878, 0.67449, 0.88715, 1.1503, 1.5341)

val listSize = partList.size * 2;
val posZero = partList.size;
val neg = partList.map( _ * -1).reverse.zipWithIndex
val pos = partList.map( _ * 1).zipWithIndex.reverse

def partition(dbl:Double): Int = { 

//for each partition, i am running through the vals in order
//could make this a binary search to be more performant... but our list size is 4 (per side)

  if(dbl < 0) { return neg.find( dbl < _._1).get._2  }
  if(dbl > 0) { return posZero  + pos.find( dbl > _._1).get._2  }
      return posZero; 

}

  def main(args: Array[String])
    { 

    var l = 0
    val dbls = new Array[Double](50000000)
    val partList = new Array[Int](50000000)
    val pa = Array.fill(listSize){Array.newBuilder[Double]}
    val channel = new FileInputStream("../../gaussian.dat").getChannel()
    val bb = ByteBuffer.allocate(50000000 * 8)
    bb.order(ByteOrder.LITTLE_ENDIAN)
    channel.read(bb)
    bb.rewind
    println("Loaded" + System.currentTimeMillis())
    var dbl = 0.0
    while(bb.hasRemaining)
    { 
      dbl = bb.getDouble
      dbls.update(l,dbl) 

      l+=1
    }
    println("Beyond first load" + System.currentTimeMillis());

    for( i <- (0 to 49999999).par) { partList.update(i, partition(dbls(i)))}

    println("Partition computed" + System.currentTimeMillis() )
    for(i <- (0 to 49999999)) { pa(partList(i)) += dbls(i) }
    println("Partition completed " + System.currentTimeMillis())
    val toSort = for( i <- pa) yield i.result()
    println("Arrays Built" + System.currentTimeMillis());
    toSort.par.foreach{i:Array[Double] =>scala.util.Sorting.quickSort(i)};

    println("Read\t" + System.currentTimeMillis());

  }
}

1
8.185! 스칼라 솔루션에 좋은 것 같아요 ... 또한 가우스 분포를 실제로 사용하는 첫 번째 솔루션을 제공하는 브라보!

1
나는 C # 솔루션과 경쟁하는 것을 목표로했습니다. 내가 c / c ++를 이길 것이라고 생각하지 않았다. 또한 .. 그것은 당신과 나에게 훨씬 다르게 행동합니다. 내 끝에 openJDK를 사용하고 있으며 훨씬 느립니다. 더 많은 파티션을 추가하면 환경에 도움이되는지 궁금합니다.
Scott

9

이것을 CSS 파일에 넣고 이론상으로 csc로 컴파일하십시오. (모노가 필요합니다)

using System;
using System.IO;
using System.Threading;

namespace Sort
{
    class Program
    {
        const int count = 50000000;
        static double[][] doubles;
        static WaitHandle[] waiting = new WaitHandle[4];
        static AutoResetEvent[] events = new AutoResetEvent[4];

        static double[] Merge(double[] left, double[] right)
        {
            double[] result = new double[left.Length + right.Length];
            int l = 0, r = 0, spot = 0;
            while (l < left.Length && r < right.Length)
            {
                if (right[r] < left[l])
                    result[spot++] = right[r++];
                else
                    result[spot++] = left[l++];
            }
            while (l < left.Length) result[spot++] = left[l++];
            while (r < right.Length) result[spot++] = right[r++];
            return result;
        }

        static void ThreadStart(object data)
        {
            int index = (int)data;
            Array.Sort(doubles[index]);
            events[index].Set();
        }

        static void Main(string[] args)
        {
            System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
            watch.Start();
            byte[] bytes = File.ReadAllBytes(@"..\..\..\SortGuassian\Data.dat");
            doubles = new double[][] { new double[count / 4], new double[count / 4], new double[count / 4], new double[count / 4] };
            for (int i = 0; i < 4; i++)
            {
                for (int j = 0; j < count / 4; j++)
                {
                    doubles[i][j] = BitConverter.ToDouble(bytes, i * count/4 + j * 8);
                }
            }
            Thread[] threads = new Thread[4];
            for (int i = 0; i < 4; i++)
            {
                threads[i] = new Thread(ThreadStart);
                waiting[i] = events[i] = new AutoResetEvent(false);
                threads[i].Start(i);
            }
            WaitHandle.WaitAll(waiting);
            double[] left = Merge(doubles[0], doubles[1]);
            double[] right = Merge(doubles[2], doubles[3]);
            double[] result = Merge(left, right);
            watch.Stop();
            Console.WriteLine(watch.Elapsed.ToString());
            Console.ReadKey();
        }
    }
}

Mono로 솔루션을 실행할 수 있습니까? 어떻게해야합니까?

Mono를 사용하지 않았고, 그렇게 생각하지 않았다면 F #을 컴파일 한 다음 실행할 수 있어야합니다.

1
성능을 향상시키기 위해 4 개의 스레드를 사용하도록 업데이트되었습니다. 이제 6 초를줍니다. 예비 어레이를 하나만 사용하고 메모리를 0으로 초기화하지 않으면 CLR에서 수행하는 모든 것이 최소 한 번 이상 기록되므로이 기능이 크게 향상 될 수 있습니다 (5 초).

1
내 컴퓨터에서 9.598! 당신은 현재 리더입니다 :)

1
어머니는 모노를 가진 사람들과 떨어져 있으라고 말했습니다!

8

분포가 무엇인지 알기 때문에 직접 인덱싱 O (N) 정렬을 사용할 수 있습니다. (이것이 무엇인지 궁금하다면, 52 장의 덱이 있고 그것을 정렬하고 싶다고 가정하십시오. 52 개의 용지함이 있고 각 카드를 자신의 용지함에 버립니다.)

5e7 복식이 있습니다. 5e7의 결과 배열 R을 두 배로 할당합니다. 각 번호 x를 가져 와서 확인하십시오 i = phi(x) * 5e7. 기본적으로 R[i] = x. 충돌하는 숫자 이동과 같은 충돌을 처리 할 수있는 방법이 있어야합니다 (단순 해시 코딩에서와 같이). 또는 고유 한 값으로 채워진 R을 몇 배 더 크게 만들 수 있습니다. 결국, 당신은 R의 요소를 쓸어 버립니다.

phi가우스 누적 분포 함수입니다. +/- 무한대 사이의 가우시안 분산 수를 0과 1 사이의 균일 한 분산 수로 변환합니다. 계산하는 간단한 방법은 테이블 조회 및 보간입니다.


3
주의 : 정확한 분포가 아니라 대략적인 분포를 알고 있습니다. 데이터가 가우시안 법칙을 사용하여 생성되었다는 것을 알고 있지만, 유한하기 때문에 가우시안을 정확하게 따르지 않습니다.

@static_rtti :이 경우 필요한 phi 근사값은 데이터 세트 IMO의 불규칙성보다 더 큰 번거 로움을 유발합니다.

1
@static_rtti : 정확할 필요는 없습니다. 데이터를 퍼 뜨리기 만하면 거의 균일하므로 일부 장소에서는 너무 많이 모이지 않습니다.

5e7 복식이 있다고 가정하십시오. R의 각 엔트리를 5e6 벡터의 벡터로 두 번 만들어 보지 않겠습니까? 그런 다음 적절한 벡터에서 각 double을 push_back합니다. 벡터를 정렬하면 완료됩니다. 입력 크기에 선형 시간이 걸립니다.
Neil G

실제로, 나는 mdkess가 이미 그 솔루션을 생각해 냈습니다.
Neil G

8

다음은 또 다른 순차적 솔루션입니다.

#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <ctime>

typedef unsigned long long ull;

int size;
double *dbuf, *copy;
int cnt[8][1 << 16];

void sort()
{
  const int step = 10;
  const int start = 24;
  ull mask = (1ULL << step) - 1;

  ull *ibuf = (ull *) dbuf;
  for (int i = 0; i < size; i++) {
    for (int w = start, v = 0; w < 64; w += step, v++) {
      int p = (~ibuf[i] >> w) & mask;
      cnt[v][p]++;
    }
  }

  int sum[8] = { 0 };
  for (int i = 0; i <= mask; i++) {
    for (int w = start, v = 0; w < 64; w += step, v++) {
      int tmp = sum[v] + cnt[v][i];
      cnt[v][i] = sum[v];
      sum[v] = tmp;
    }
  }

  for (int w = start, v = 0; w < 64; w += step, v++) {
    ull *ibuf = (ull *) dbuf;
    for (int i = 0; i < size; i++) {
      int p = (~ibuf[i] >> w) & mask;
      copy[cnt[v][p]++] = dbuf[i];
    }

    double *tmp = copy;
    copy = dbuf;
    dbuf = tmp;
  }

  for (int p = 0; p < size; p++)
    if (dbuf[p] >= 0.) {
      std::reverse(dbuf + p, dbuf + size);
      break;
    }

  // Insertion sort
  for (int i = 1; i < size; i++) {
    double value = dbuf[i];
    if (value < dbuf[i - 1]) {
      dbuf[i] = dbuf[i - 1];
      int p = i - 1;
      for (; p > 0 && value < dbuf[p - 1]; p--)
        dbuf[p] = dbuf[p - 1];
      dbuf[p] = value;
    }
  }
}

int main(int argc, char **argv) {
  size = atoi(argv[1]);
  dbuf = new double[size];
  copy = new double[size];

  FILE *f = fopen("gaussian.dat", "r");
  fread(dbuf, size, sizeof(double), f);
  fclose(f);

  clock_t c0 = clock();
  sort();
  printf("Finished after %.3f\n", (double) ((clock() - c0)) / CLOCKS_PER_SEC);
  return 0;
}

멀티 스레드 솔루션을 능가하는 것은 의심 스럽지만 i7 랩톱의 타이밍은 다음과 같습니다 (stdsort는 다른 답변으로 제공되는 C ++ 솔루션입니다).

$ g++ -O3 mysort.cpp -o mysort && ./mysort 50000000
Finished after 2.10
$ g++ -O3 stdsort.cpp -o stdsort && ./stdsort
Finished after 7.12

이 솔루션은 특별한 복식 표현을 사용하기 때문에 선형 시간 복잡성을 가지고 있습니다.

편집 : 요소의 순서가 증가하도록 수정했습니다.

편집 : 속도가 거의 0.5 초 향상되었습니다.

편집 : 속도가 0.7 초 더 향상되었습니다. 알고리즘을보다 캐시 친화적으로 만들었습니다.

편집 : 속도가 1 초 더 향상되었습니다. 50.000.000 개의 요소 만 있기 때문에 가수를 부분적으로 정렬하고 삽입 정렬 (캐시 친화적)을 사용하여 외부 요소를 수정할 수 있습니다. 이 아이디어는 마지막 기수 정렬 루프에서 약 두 번의 반복을 제거합니다.

편집 : 0.16 초 미만. 정렬 순서가 바뀌면 첫 번째 std :: reverse를 제거 할 수 있습니다.


이제는 흥미로워지고 있습니다! 어떤 종류의 알고리즘입니까?
static_rtti

2
최소 유효 자릿수 기수 정렬 . 가수, 지수, 부호를 정렬 할 수 있습니다. 여기에 제시된 알고리즘은이 아이디어를 한 단계 더 발전시킵니다. 다른 답변으로 제공된 파티션 아이디어를 사용하여 병렬화 할 수 있습니다.
Alexandru

단일 스레드 솔루션에 매우 빠름 : 2.552 초! 데이터가 정상적으로 분포되어 있다는 사실을 이용하기 위해 솔루션을 변경할 수 있다고 생각하십니까? 현재 최고의 멀티 스레드 솔루션보다 더 잘 할 수 있습니다.
static_rtti

1
@ static_rtti : Damascus Steel은 이미이 구현의 멀티 스레드 버전을 게시 한 것으로 보입니다. 이 알고리즘의 캐싱 동작을 향상 시켰으므로 이제 더 나은 타이밍을 얻을 수 있습니다. 이 새 버전을 테스트하십시오.
Alexandru

2
최신 테스트에서 1.459 초 이 솔루션은 내 규칙에 따라 승자가 아니지만 실제로 큰 도움이 필요합니다. 축하합니다!
static_rtti

6

Christian Ammer의 솔루션을 가져 와서 Intel의 Threaded Building Blocks와 병렬화

#include <iostream>
#include <fstream>
#include <algorithm>
#include <vector>
#include <ctime>
#include <tbb/parallel_sort.h>

int main(void)
{
    std::ifstream ifs("gaussian.dat", std::ios::binary | std::ios::in);
    std::vector<double> values;
    values.reserve(50000000);
    double d;
    while (ifs.read(reinterpret_cast<char*>(&d), sizeof(double)))
    values.push_back(d);
    clock_t c0 = clock();
    tbb::parallel_sort(values.begin(), values.end());
    std::cout << "Finished after "
              << static_cast<double>((clock() - c0)) / CLOCKS_PER_SEC
              << std::endl;
}

인텔의 IPP (Performance Primitives) 라이브러리에 액세스 할 수 있으면 기수 정렬을 사용할 수 있습니다. 그냥 교체

#include <tbb/parallel_sort.h>

#include "ipps.h"

tbb::parallel_sort(values.begin(), values.end());

std::vector<double> copy(values.size());
ippsSortRadixAscend_64f_I(&values[0], &copy[0], values.size());

듀얼 코어 랩탑에서 타이밍은

C               16.4 s
C#              20 s
C++ std::sort   7.2 s
C++ tbb         5 s
C++ ipp         4.5 s
python          too long

1
2.958s! TBB는 꽤 시원하고 사용하기 쉬운 것 같습니다!

2
TBB는 터무니없이 훌륭합니다. 알고리즘 작업에 대한 올바른 추상화 수준입니다.
drxzcl

5

방법의 일 구현에 대해 평행 퀵 함으로써 동일한 크기의 파티션을 보장 분포의 통계에 기초하여 피벗 값을 선택? 첫 번째 피벗은 평균 (이 경우 0)이고 다음 쌍은 25 번째 및 75 번째 백분위 수 (+/- -0.67449 표준 편차) 등이되며 각 파티션은 나머지 데이터 세트를 절반 이상 줄입니다. 덜 완벽합니다.


그것은 내가 실제로 한 일입니다. 물론 글쓰기를 마치기 전에이 글을 올렸습니다.

5

매우 추한 (숫자로 끝나는 변수를 사용할 수있을 때 배열을 사용하는 이유), 빠른 코드 (첫 번째 std :: threads 시도), 내 시스템 1,8 초 (std :: sort와 비교) () 4,8 s), g ++로 컴파일 -std = c ++ 0x -O3 -march = native -pthread stdin을 통해 데이터를 전달하십시오 (50M에만 해당).

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <thread>
using namespace std;
const size_t size=50000000;

void pivot(double* start,double * end, double middle,size_t& koniec){
    double * beg=start;
    end--;
    while (start!=end){
        if (*start>middle) swap (*start,*end--);
        else start++;
    }
    if (*end<middle) start+=1;
    koniec= start-beg;
}
void s(double * a, double* b){
    sort(a,b);
}
int main(){
    double *data=new double[size];
    FILE *f = fopen("gaussian.dat", "rb");
    fread(data,8,size,f);
    size_t end1,end2,end3,temp;
    pivot(data, data+size,0,end2);
    pivot(data, data+end2,-0.6745,end1);
    pivot(data+end2,data+size,0.6745,end3);
    end3+=end2;
    thread ts1(s,data,data+end1);
    thread ts2(s,data+end1,data+end2);
    thread ts3(s,data+end2,data+end3);
    thread ts4(s,data+end3,data+size);
    ts1.join(),ts2.join(),ts3.join(),ts4.join();
    //for (int i=0; i<size-1; i++){
    //  if (data[i]>data[i+1]) cerr<<"BLAD\n";
    //}
    fclose(f);
    //fwrite(data,8,size,stdout);
}

// gaussian.dat 파일을 읽도록 변경되었습니다.


위의 C ++ 솔루션과 마찬가지로 gaussian.dat를 읽도록 변경할 수 있습니까?

나중에 집에 오면 다시 시도하겠습니다.
static_rtti

아주 좋은 해결책, 당신은 현재 리더입니다 (1.949s)! 가우스 분포의 좋은 사용 :)
static_rtti

4

C ++ 솔루션을 사용하여 std::sort(관련 결국 빠른 qsort가보다 표준 대를 qsort의 성능 :: 종류 )

#include <iostream>
#include <fstream>
#include <algorithm>
#include <vector>
#include <ctime>

int main(void)
{
    std::ifstream ifs("C:\\Temp\\gaussian.dat", std::ios::binary | std::ios::in);
    std::vector<double> values;
    values.reserve(50000000);
    double d;
    while (ifs.read(reinterpret_cast<char*>(&d), sizeof(double)))
        values.push_back(d);
    clock_t c0 = clock();
    std::sort(values.begin(), values.end());
    std::cout << "Finished after "
              << static_cast<double>((clock() - c0)) / CLOCKS_PER_SEC
              << std::endl;
}

내 컴퓨터에 1GB 만 있기 때문에 시간이 얼마나 걸리는지 믿을 수 없으며 주어진 Python 코드를 gaussian.dat사용하면 25mio의 두 배로 만 파일을 만들 수 있습니다 (메모리 오류가 발생하지 않음). 그러나 std :: sort 알고리즘이 얼마나 오래 실행되는지 관심이 있습니다.


6.425 초! 예상대로 C ++가 빛을 발합니다 :)

@ static_rtti : Swensons Timsort 알고리즘을 시도했습니다 (첫 번째 질문 에서 Matthieu M.이 제안한대로 ). sort.hC ++로 컴파일 하기 위해 파일을 약간 변경해야했습니다 . 보다 약 두 배 느 렸습니다 std::sort. 컴파일러 최적화로 인해 이유를 모르십니까?
Christian Ammer

4

다음은 Zjarek의 스레드 스마트 피봇과 Alexandru의 기수 종류를 혼합 한 것입니다. 와 컴파일

g++ -std=c++0x -pthread -O3 -march=native sorter_gaussian_radix.cxx -o sorter_gaussian_radix

STEP을 정의하여 기수 크기를 변경할 수 있습니다 (예 : add -DSTEP = 11). 랩톱에 가장 적합한 것이 8 (기본값)이라는 것을 알았습니다.

기본적으로 문제를 4 개로 나누고 여러 스레드에서 실행합니다. 깊이 매개 변수를 명령 행에 전달하여이를 변경할 수 있습니다. 따라서 코어가 두 개인 경우 다음과 같이 실행하십시오.

sorter_gaussian_radix 50000000 1

16 개의 코어가 있다면

sorter_gaussian_radix 50000000 4

현재 최대 깊이는 6 (64 스레드)입니다. 레벨을 너무 많이 넣으면 코드 속도가 느려집니다.

내가 시도한 것 중 하나는 인텔 성능 기본 (IPP) 라이브러리의 기수 정렬이었습니다. Alexandru의 구현은 IPP를 크게 넘어서며 IPP는 약 30 % 느려집니다. 해당 변형도 여기에 포함됩니다 (설명).

#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <ctime>
#include <iostream>
#include <thread>
#include <vector>
#include <boost/cstdint.hpp>
// #include "ipps.h"

#ifndef STEP
#define STEP 8
#endif

const int step = STEP;
const int start_step=24;
const int num_steps=(64-start_step+step-1)/step;
int size;
double *dbuf, *copy;

clock_t c1, c2, c3, c4, c5;

const double distrib[]={-2.15387,
                        -1.86273,
                        -1.67594,
                        -1.53412,
                        -1.4178,
                        -1.31801,
                        -1.22986,
                        -1.15035,
                        -1.07752,
                        -1.00999,
                        -0.946782,
                        -0.887147,
                        -0.830511,
                        -0.776422,
                        -0.724514,
                        -0.67449,
                        -0.626099,
                        -0.579132,
                        -0.53341,
                        -0.488776,
                        -0.445096,
                        -0.40225,
                        -0.36013,
                        -0.318639,
                        -0.27769,
                        -0.237202,
                        -0.197099,
                        -0.157311,
                        -0.11777,
                        -0.0784124,
                        -0.0391761,
                        0,
                        0.0391761,
                        0.0784124,
                        0.11777,
                        0.157311,
                        0.197099,
                        0.237202,
                        0.27769,
                        0.318639,
                        0.36013,
                        0.40225,
                        0.445097,
                        0.488776,
                        0.53341,
                        0.579132,
                        0.626099,
                        0.67449,
                        0.724514,
                        0.776422,
                        0.830511,
                        0.887147,
                        0.946782,
                        1.00999,
                        1.07752,
                        1.15035,
                        1.22986,
                        1.31801,
                        1.4178,
                        1.53412,
                        1.67594,
                        1.86273,
                        2.15387};


class Distrib
{
  const int value;
public:
  Distrib(const double &v): value(v) {}

  bool operator()(double a)
  {
    return a<value;
  }
};


void recursive_sort(const int start, const int end,
                    const int index, const int offset,
                    const int depth, const int max_depth)
{
  if(depth<max_depth)
    {
      Distrib dist(distrib[index]);
      const int middle=std::partition(dbuf+start,dbuf+end,dist) - dbuf;

      // const int middle=
      //   std::partition(dbuf+start,dbuf+end,[&](double a)
      //                  {return a<distrib[index];})
      //   - dbuf;

      std::thread lower(recursive_sort,start,middle,index-offset,offset/2,
                        depth+1,max_depth);
      std::thread upper(recursive_sort,middle,end,index+offset,offset/2,
                        depth+1,max_depth);
      lower.join(), upper.join();
    }
  else
    {
  // ippsSortRadixAscend_64f_I(dbuf+start,copy+start,end-start);

      c1=clock();

      double *dbuf_local(dbuf), *copy_local(copy);
      boost::uint64_t mask = (1 << step) - 1;
      int cnt[num_steps][mask+1];

      boost::uint64_t *ibuf = reinterpret_cast<boost::uint64_t *> (dbuf_local);

      for(int i=0;i<num_steps;++i)
        for(uint j=0;j<mask+1;++j)
          cnt[i][j]=0;

      for (int i = start; i < end; i++)
        {
          for (int w = start_step, v = 0; w < 64; w += step, v++)
            {
              int p = (~ibuf[i] >> w) & mask;
              (cnt[v][p])++;
            }
        }

      c2=clock();

      std::vector<int> sum(num_steps,0);
      for (uint i = 0; i <= mask; i++)
        {
          for (int w = start_step, v = 0; w < 64; w += step, v++)
            {
              int tmp = sum[v] + cnt[v][i];
              cnt[v][i] = sum[v];
              sum[v] = tmp;
            }
        }

      c3=clock();

      for (int w = start_step, v = 0; w < 64; w += step, v++)
        {
          ibuf = reinterpret_cast<boost::uint64_t *>(dbuf_local);

          for (int i = start; i < end; i++)
            {
              int p = (~ibuf[i] >> w) & mask;
              copy_local[start+((cnt[v][p])++)] = dbuf_local[i];
            }
          std::swap(copy_local,dbuf_local);
        }

      // Do the last set of reversals
      for (int p = start; p < end; p++)
        if (dbuf_local[p] >= 0.)
          {
            std::reverse(dbuf_local+p, dbuf_local + end);
            break;
          }

      c4=clock();

      // Insertion sort
      for (int i = start+1; i < end; i++) {
        double value = dbuf_local[i];
        if (value < dbuf_local[i - 1]) {
          dbuf_local[i] = dbuf_local[i - 1];
          int p = i - 1;
          for (; p > 0 && value < dbuf_local[p - 1]; p--)
            dbuf_local[p] = dbuf_local[p - 1];
          dbuf_local[p] = value;
        }
      }
      c5=clock();

    }
}


int main(int argc, char **argv) {
  size = atoi(argv[1]);
  copy = new double[size];

  dbuf = new double[size];
  FILE *f = fopen("gaussian.dat", "r");
  fread(dbuf, size, sizeof(double), f);
  fclose(f);

  clock_t c0 = clock();

  const int max_depth= (argc > 2) ? atoi(argv[2]) : 2;

  // ippsSortRadixAscend_64f_I(dbuf,copy,size);

  recursive_sort(0,size,31,16,0,max_depth);

  if(num_steps%2==1)
    std::swap(dbuf,copy);

  // for (int i=0; i<size-1; i++){
  //   if (dbuf[i]>dbuf[i+1])
  //     std::cout << "BAD "
  //               << i << " "
  //               << dbuf[i] << " "
  //               << dbuf[i+1] << " "
  //               << "\n";
  // }

  std::cout << "Finished after "
            << (double) (c1 - c0) / CLOCKS_PER_SEC << " "
            << (double) (c2 - c1) / CLOCKS_PER_SEC << " "
            << (double) (c3 - c2) / CLOCKS_PER_SEC << " "
            << (double) (c4 - c3) / CLOCKS_PER_SEC << " "
            << (double) (c5 - c4) / CLOCKS_PER_SEC << " "
            << "\n";

  // delete [] dbuf;
  // delete [] copy;
  return 0;
}

편집 : Alexandru의 캐시 개선을 구현하여 내 컴퓨터에서 약 30 %의 시간을 단축했습니다.

편집 : 이것은 재귀 정렬을 구현하므로 Alexandru의 16 코어 머신에서 잘 작동합니다. 그것은 또한 Alexandru의 마지막 개선을 사용하고 그 반대의 것을 제거합니다. 나에게 이것은 20 % 향상되었다.

편집 : 코어가 2 개 이상일 때 비 효율성을 유발하는 사인 버그를 수정했습니다.

편집 : 람다를 제거하여 이전 버전의 gcc로 컴파일합니다. 주석 처리 된 IPP 코드 변형이 포함됩니다. 또한 16 코어에서 실행하기위한 설명서를 수정했습니다. 내가 알 수있는 한, 이것이 가장 빠른 구현입니다.

편집 : STEP 8이 아닌 버그 수정 . 최대 스레드 수를 64로 늘 렸습니다. 일부 타이밍 정보가 추가되었습니다.


좋은. 기수 정렬은 캐시가 매우 비우호적입니다. 변경하여 더 나은 결과를 얻을 수 있는지 확인하십시오 step(11은 내 랩톱에서 최적이었습니다).
Alexandru

버그 int cnt[mask]가 있습니다 : 해야합니다 int cnt[mask + 1]. 더 나은 결과를 얻으려면 고정 된 값을 사용하십시오 int cnt[1 << 16].
Alexandru

오늘 집에 돌아와서이 솔루션들을 모두 시험해 볼 것입니다.
static_rtti

1.534 초 !!! 나는 우리가 리더 - D 생각
static_rtti

@static_rtti : 다시 시도해 주시겠습니까? 마지막으로 시도한 것보다 훨씬 빠릅니다. 내 컴퓨터에서는 다른 솔루션보다 훨씬 빠릅니다.
다마스커스 스틸

2

나는 이것이 정말로 당신이하고 싶은 것에 달려 있다고 생각합니다. 가우시안을 정렬하려면 도움이되지 않습니다. 그러나 가우시안 분류를 원한다면 이것이 될 것입니다. 이것이 문제를 조금 놓치더라도 실제 정렬 루틴과 비교하는 것이 흥미로울 것이라고 생각합니다.

빠른 것을 원한다면 덜하십시오.

정규 분포에서 임의의 무작위 샘플을 생성 한 다음 정렬하는 대신 정규 분포에서 정렬 된 순서로 많은 샘플을 생성 할 수 있습니다.

당신은 솔루션을 사용할 수 있습니다 여기 에 정렬 된 순서로 n 개의 균일 난수를 생성 . 그런 다음 정규 분포의 역 cdf (scipy.stats.norm.ppf)를 사용하여 균일 변환 난수를 역 변환 샘플링을 통해 정규 분포의 숫자로 변환 할 수 있습니다.

import scipy.stats
import random

# slightly modified from linked stackoverflow post
def n_random_numbers_increasing(n):
  """Like sorted(random() for i in range(n))),                                
  but faster because we avoid sorting."""
  v = 1.0
  while n:
    v *= random.random() ** (1.0 / n)
    yield 1 - v
    n -= 1

def n_normal_samples_increasing(n):
  return map(scipy.stats.norm.ppf, n_random_numbers_increasing(n))

손이 더러워지기를 원한다면 반복적 인 방법을 사용하고 이전 결과를 초기 추측으로 사용하여 많은 역 cdf 계산 속도를 높일 수 있다고 생각합니다. 추측이 매우 가깝기 때문에 단일 반복이 큰 정확도를 제공 할 것입니다.


2
좋은 대답이지만 부정 행위가 될 것입니다.) 내 질문의 아이디어는 정렬 알고리즘에 큰 관심을 기울 였지만 몇 가지 논문이 있지만 정렬을 위해 데이터에 대한 사전 지식을 사용하는 것에 대한 문헌은 거의 없다는 것입니다. 이 문제를 해결하여 좋은 이익을보고했습니다. 무엇이 가능한지 보자!

2

이 Main ()을 사용하여 Guvante의 솔루션을 변경해보십시오 .1 / 4 IO 판독이 완료되면 정렬이 시작되며 테스트에서 더 빠릅니다.

    static void Main(string[] args)
    {
        FileStream filestream = new FileStream(@"..\..\..\gaussian.dat", FileMode.Open, FileAccess.Read);
        doubles = new double[][] { new double[count / 4], new double[count / 4], new double[count / 4], new double[count / 4] };
        Thread[] threads = new Thread[4];

        for (int i = 0; i < 4; i++)
        {
            byte[] bytes = new byte[count * 4];
            filestream.Read(bytes, 0, count * 4);

            for (int j = 0; j < count / 4; j++)
            {
                doubles[i][j] = BitConverter.ToDouble(bytes, i * count/4 + j * 8);
            }

            threads[i] = new Thread(ThreadStart);
            waiting[i] = events[i] = new AutoResetEvent(false);
            threads[i].Start(i);    
        }

        WaitHandle.WaitAll(waiting);
        double[] left = Merge(doubles[0], doubles[1]);
        double[] right = Merge(doubles[2], doubles[3]);
        double[] result = Merge(left, right);
        Console.ReadKey();
    }
}

8.933 초 약간 빠름 :)

2

분포를 알고 있기 때문에 내 생각은 각각 동일한 예상 개수의 요소를 가진 k 버킷을 만드는 것입니다 (분포를 알고 있기 때문에 이것을 계산할 수 있음). 그런 다음 O (n) 시간에 배열을 스윕하고 요소를 버킷에 넣습니다.

그런 다음 버킷을 동시에 정렬하십시오. k 개의 버킷과 n 개의 요소가 있다고 가정하십시오. 버킷은 정렬하는 데 (n / k) lg (n / k) 시간이 걸립니다. 이제 사용할 수있는 p 프로세서가 있다고 가정하십시오. 버킷은 독립적으로 정렬 할 수 있으므로 처리 할 ceil (k / p)의 배수가 있습니다. 이것은 n + ceil (k / p) * (n / k) lg (n / k)의 최종 런타임을 제공하며, k를 잘 선택하면 n lg n보다 훨씬 빠릅니다.


이것이 최선의 해결책이라고 생각합니다.
Neil G

버킷에 들어갈 요소의 수를 정확히 모르므로 수학은 실제로 잘못되었습니다. 그것은 좋은 생각입니다.
poulejapon

@pouejapon : 네 말이 맞아.
Neil G

이 답변은 소리가 정말 좋은. 문제는 정말 빠르지 않다는 것입니다. 나는 이것을 C99에서 구현했으며 (내 대답 참조) 확실히 쉽게 이길 수 std::sort()있지만 Alexandru의 기수 솔루션보다 속도가 느립니다.
Sven Marnach 2016 년

2

하나의 낮은 수준의 최적화 아이디어는 SSE 레지스터에 두 개의 더블을 맞추는 것이므로 각 스레드는 한 번에 두 개의 항목으로 작동합니다. 일부 알고리즘에서는이 작업이 복잡 할 수 있습니다.

또 다른 방법은 캐시 친화적 청크로 배열을 정렬 한 다음 결과를 병합하는 것입니다. L1의 경우 처음 4KB, L2의 경우 64KB와 같은 두 가지 수준을 사용해야합니다.

버킷 정렬은 캐시 외부로 이동하지 않으며 최종 병합은 순차적으로 메모리를 이동하므로 캐시에 매우 친숙해야합니다.

요즘 계산은 메모리 액세스보다 훨씬 저렴합니다. 그러나 우리는 많은 항목을 가지고 있기 때문에 벙어리 캐시 인식 정렬이 복잡성이 낮은 캐시 비 인식 버전보다 느린 경우 배열 크기가 어느 것인지 알기가 어렵습니다.

그러나 Windows (VC ++)에서 구현하기 때문에 위의 구현을 제공하지 않습니다.


2

다음은 선형 스캔 버킷 정렬 구현입니다. 기수 정렬을 제외한 모든 현재 단일 스레드 구현보다 빠르다고 생각합니다. cdf를 충분히 정확하게 추정하고 (웹에서 찾은 값의 선형 보간을 사용하고 있음) 과도한 스캔을 유발하는 실수를하지 않은 경우 선형 예상 실행 시간이 있어야합니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <ctime>

using std::fill;

const double q[] = {
  0.0,
  9.865E-10,
  2.8665150000000003E-7,
  3.167E-5,
  0.001349898,
  0.022750132,
  0.158655254,
  0.5,
  0.8413447460000001,
  0.9772498679999999,
  0.998650102,
  0.99996833,
  0.9999997133485,
  0.9999999990134999,
  1.0,
};
int main(int argc, char** argv) {
  if (argc <= 1)
    return puts("No argument!");
  unsigned count = atoi(argv[1]);
  unsigned count2 = 3 * count;

  bool *ba = new bool[count2 + 1000];
  fill(ba, ba + count2 + 1000, false);
  double *a = new double[count];
  double *c = new double[count2 + 1000];

  FILE *f = fopen("gaussian.dat", "rb");
  if (fread(a, 8, count, f) != count)
    return puts("fread failed!");
  fclose(f);

  int i;
  int j;
  bool s;
  int t;
  double z;
  double p;
  double d1;
  double d2;
  for (i = 0; i < count; i++) {
    s = a[i] < 0;
    t = a[i];
    if (s) t--;
    z = a[i] - t;
    t += 7;
    if (t < 0) {
      t = 0;
      z = 0;
    } else if (t >= 14) {
      t = 13;
      z = 1;
    }
    p = q[t] * (1 - z) + q[t + 1] * z;
    j = count2 * p;
    while (ba[j] && c[j] < a[i]) {
      j++;
    }
    if (!ba[j]) {
      ba[j] = true;
      c[j] = a[i];
    } else {
      d1 = c[j];
      c[j] = a[i];
      j++;
      while (ba[j]) {
        d2 = c[j];
        c[j] = d1;
        d1 = d2;
        j++;
      }
      c[j] = d1;
      ba[j] = true;
    }
  }
  i = 0;
  int max = count2 + 1000;
  for (j = 0; j < max; j++) {
    if (ba[j]) {
      a[i++] = c[j];
    }
  }
  // for (i = 0; i < count; i += 1) {
  //   printf("here %f\n", a[i]);
  // }
  return 0;
}

1
오늘 집에 돌아 오면 나중에 시도해 볼게요. 그동안 코드가 매우 추악하다고 말할 수 있습니까? :-D
static_rtti

3.071! 단일 스레드 솔루션에는 나쁘지 않습니다!
static_rtti

2

이전 게시물을 편집 할 수없는 이유를 모르겠습니다. 여기서 새 버전이 0.2 초 빠릅니다 (그러나 CPU 시간 (사용자)에서는 약 1.5 초 빠릅니다). 이 솔루션에는 2 개의 프로그램이 있으며, 먼저 버킷 정렬에 대한 정규 분포에 대한 Quantile을 사전 계산하고이를 t [double * scale] = bucket index 테이블에 저장합니다. 여기서 scale은 임의의 숫자로, 두 배로 캐스팅 할 수 있습니다. 그런 다음 메인 프로그램은이 데이터를 사용하여 복식을 올바른 버킷에 넣을 수 있습니다. 데이터가 가우시안이 아닌 경우 데이터가 올바르게 작동하지 않으며 (정규 배포에서는 잘못 작동 할 가능성이 거의 없음) 한 가지 단점이 있지만, 특수한 경우에 대한 수정이 쉽고 빠릅니다 (버킷 수만 확인하고 표준에 빠짐) ::종류()).

컴파일 : g ++ => http://pastebin.com/WG7pZEzH 도우미 프로그램

g ++ -std = c ++ 0x -O3 -march = native -pthread => http://pastebin.com/T3yzViZP 기본 정렬 프로그램


1.621 초! 나는 당신이 지도자라고 생각하지만, 나는이 모든 답변으로 빠르게 잃어 가고 있습니다 :)
static_rtti

2

이리 또 다른 순차적 솔루션입니다. 이것은 요소가 정규 분포라는 사실을 사용하며, 아이디어는 일반적으로 선형 시간에 가까운 정렬에 적용 가능하다고 생각합니다.

알고리즘은 다음과 같습니다.

  • 대략적인 CDF (참조 phi() 구현의 기능 )
  • 모든 요소에 대해 정렬 된 배열에서 대략적인 위치를 계산하십시오. size * phi(x)
  • 최종 위치에 가까운 새 배열에 요소 배치
    • 내 구현 대상 배열에 약간의 틈이 있으므로 삽입 할 때 너무 많은 요소를 이동할 필요가 없습니다.
  • insertsort를 사용하여 최종 요소를 정렬하십시오 (insertsort는 최종 위치까지의 거리가 상수보다 작은 경우 선형입니다).

불행히도, 숨겨진 상수는 상당히 크며이 솔루션은 기수 정렬 알고리즘보다 두 배 느립니다.


1
2.470 초! 아주 좋은 아이디어. 아이디어가 흥미 있다면 솔루션이 가장 빠르지는 않습니다. :)
static_rtti

1
이것은 내 것과 동일하지만 더 나은 캐시 성능을 위해 파이 계산과 시프트를 함께 그룹화합니다.
jonderry 2016 년

@ jonderry : 귀하의 솔루션을 상향 조정했습니다. 당신의 아이디어를 훔치려는 것은 아닙니다. 나는 (비공식) 테스트 세트에
Alexandru

2

Intel의 Threaded Building Blocks를 사용하는 개인적으로 좋아하는 것이 이미 게시되었지만 JDK 7 및 새로운 포크 / 조인 API를 사용하는 조잡한 병렬 솔루션이 있습니다.

import java.io.FileInputStream;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.concurrent.*;
import static java.nio.channels.FileChannel.MapMode.READ_ONLY;
import static java.nio.ByteOrder.LITTLE_ENDIAN;


/**
 * 
 * Original Quicksort: https://github.com/pmbauer/parallel/tree/master/src/main/java/pmbauer/parallel
 *
 */
public class ForkJoinQuicksortTask extends RecursiveAction {

    public static void main(String[] args) throws Exception {

        double[] array = new double[Integer.valueOf(args[0])];

        FileChannel fileChannel = new FileInputStream("gaussian.dat").getChannel();
        fileChannel.map(READ_ONLY, 0, fileChannel.size()).order(LITTLE_ENDIAN).asDoubleBuffer().get(array);

        ForkJoinPool mainPool = new ForkJoinPool();

        System.out.println("Starting parallel computation");

        mainPool.invoke(new ForkJoinQuicksortTask(array));        
    }

    private static final long serialVersionUID = -642903763239072866L;
    private static final int SERIAL_THRESHOLD = 0x1000;

    private final double a[];
    private final int left, right;

    public ForkJoinQuicksortTask(double[] a) {this(a, 0, a.length - 1);}

    private ForkJoinQuicksortTask(double[] a, int left, int right) {
        this.a = a;
        this.left = left;
        this.right = right;
    }

    @Override
    protected void compute() {
        if (right - left < SERIAL_THRESHOLD) {
            Arrays.sort(a, left, right + 1);
        } else {
            int pivotIndex = partition(a, left, right);
            ForkJoinTask<Void> t1 = null;

            if (left < pivotIndex)
                t1 = new ForkJoinQuicksortTask(a, left, pivotIndex).fork();
            if (pivotIndex + 1 < right)
                new ForkJoinQuicksortTask(a, pivotIndex + 1, right).invoke();

            if (t1 != null)
                t1.join();
        }
    }

    public static int partition(double[] a, int left, int right) {
        // chose middle value of range for our pivot
        double pivotValue = a[left + (right - left) / 2];

        --left;
        ++right;

        while (true) {
            do
                ++left;
            while (a[left] < pivotValue);

            do
                --right;
            while (a[right] > pivotValue);

            if (left < right) {
                double tmp = a[left];
                a[left] = a[right];
                a[right] = tmp;
            } else {
                return right;
            }
        }
    }    
}

중요한 면책 조항 : 포크 / 조인에 대한 빠른 정렬 조정을 https://github.com/pmbauer/parallel/tree/master/src/main/java/pmbauer/parallel 에서 가져 왔습니다.

이를 실행하려면 JDK 7 (http://jdk7.java.net/download.html)의 베타 빌드가 필요합니다.

내 2.93Ghz 쿼드 코어 i7 (OS X)에서 :

파이썬 참조

time python sort.py 50000000
sorting...

real    1m13.885s
user    1m11.942s
sys     0m1.935s

Java JDK 7 포크 / 조인

time java ForkJoinQuicksortTask 50000000
Starting parallel computation

real    0m2.404s
user    0m10.195s
sys     0m0.347s

또한 병렬 읽기와 바이트를 두 배로 변환하여 실험을 시도했지만 차이점이 없었습니다.

최신 정보:

누구든지 데이터의 병렬 로딩을 실험하고 싶다면 병렬 로딩 버전이 아래에 있습니다. 이론적으로 IO 장치의 병렬 용량이 충분하다면 (SSD는 보통)이 속도가 조금 더 빨라질 수 있습니다. 바이트에서 Doubles를 만드는 데 약간의 오버 헤드가 있으므로 병렬로 더 빠를 수도 있습니다. 내 시스템 (Ubuntu 10.10 / Nehalem Quad / Intel X25M SSD 및 OS X 10.6 / i7 Quad / Samsung SSD)에는 큰 차이가 없었습니다.

import static java.nio.ByteOrder.LITTLE_ENDIAN;
import static java.nio.channels.FileChannel.MapMode.READ_ONLY;

import java.io.FileInputStream;
import java.nio.DoubleBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveAction;


/**
 *
 * Original Quicksort: https://github.com/pmbauer/parallel/tree/master/src/main/java/pmbauer/parallel
 *
 */
public class ForkJoinQuicksortTask extends RecursiveAction {

   public static void main(String[] args) throws Exception {

       ForkJoinPool mainPool = new ForkJoinPool();

       double[] array = new double[Integer.valueOf(args[0])];
       FileChannel fileChannel = new FileInputStream("gaussian.dat").getChannel();
       DoubleBuffer buffer = fileChannel.map(READ_ONLY, 0, fileChannel.size()).order(LITTLE_ENDIAN).asDoubleBuffer();

       mainPool.invoke(new ReadAction(buffer, array, 0, array.length));
       mainPool.invoke(new ForkJoinQuicksortTask(array));
   }

   private static final long serialVersionUID = -642903763239072866L;
   private static final int SERIAL_THRESHOLD = 0x1000;

   private final double a[];
   private final int left, right;

   public ForkJoinQuicksortTask(double[] a) {this(a, 0, a.length - 1);}

   private ForkJoinQuicksortTask(double[] a, int left, int right) {
       this.a = a;
       this.left = left;
       this.right = right;
   }

   @Override
   protected void compute() {
       if (right - left < SERIAL_THRESHOLD) {
           Arrays.sort(a, left, right + 1);
       } else {
           int pivotIndex = partition(a, left, right);
           ForkJoinTask<Void> t1 = null;

           if (left < pivotIndex)
               t1 = new ForkJoinQuicksortTask(a, left, pivotIndex).fork();
           if (pivotIndex + 1 < right)
               new ForkJoinQuicksortTask(a, pivotIndex + 1, right).invoke();

           if (t1 != null)
               t1.join();
       }
   }

   public static int partition(double[] a, int left, int right) {
       // chose middle value of range for our pivot
       double pivotValue = a[left + (right - left) / 2];

       --left;
       ++right;

       while (true) {
           do
               ++left;
           while (a[left] < pivotValue);

           do
               --right;
           while (a[right] > pivotValue);

           if (left < right) {
               double tmp = a[left];
               a[left] = a[right];
               a[right] = tmp;
           } else {
               return right;
           }
       }
   }

}

class ReadAction extends RecursiveAction {

   private static final long serialVersionUID = -3498527500076085483L;

   private final DoubleBuffer buffer;
   private final double[] array;
   private final int low, high;

   public ReadAction(DoubleBuffer buffer, double[] array, int low, int high) {
       this.buffer = buffer;
       this.array = array;
       this.low = low;
       this.high = high;
   }

   @Override
   protected void compute() {
       if (high - low < 100000) {
           buffer.position(low);
           buffer.get(array, low, high-low);
       } else {
           int middle = (low + high) >>> 1;

           invokeAll(new ReadAction(buffer.slice(), array, low, middle),  new ReadAction(buffer.slice(), array, middle, high));
       }
   }
}

업데이트 2 :

고정 된 코어 수를 설정하기 위해 약간의 수정으로 12 개 코어 개발 머신 중 하나에서 코드를 실행했습니다. 결과는 다음과 같습니다.

Cores  Time
1      7.568s
2      3.903s
3      3.325s
4      2.388s
5      2.227s
6      1.956s
7      1.856s
8      1.827s
9      1.682s
10     1.698s
11     1.620s
12     1.503s

이 시스템에서 나는 1m2.994s를 사용하는 Python 버전과 1.925s를 사용하는 Zjarek의 C ++ 버전을 시도했습니다 (어떤 이유로 Zjarek의 C ++ 버전은 static_rtti의 컴퓨터에서 비교적 빠르게 실행되는 것처럼 보입니다).

또한 파일 크기를 100,000,000 배로 두 배로 늘리면 어떻게 되었습니까?

Cores  Time
1      15.056s
2      8.116s
3      5.925s
4      4.802s
5      4.430s
6      3.733s
7      3.540s
8      3.228s
9      3.103s
10     2.827s
11     2.784s
12     2.689s

이 경우 Zjarek의 C ++ 버전은 3.968 초였습니다. 파이썬은 여기서 너무 오래 걸렸습니다.

150,000,000 배가 :

Cores  Time
1      23.295s
2      12.391s
3      8.944s
4      6.990s
5      6.216s
6      6.211s
7      5.446s
8      5.155s
9      4.840s
10     4.435s
11     4.248s
12     4.174s

이 경우 Zjarek의 C ++ 버전은 6.044입니다. 나는 심지어 파이썬을 시도하지 않았다.

C ++ 버전은 자바가 약간 흔들리는 결과와 매우 일치합니다. 먼저 문제가 커지면 조금 더 효율적이지만 다시 효율성이 떨어집니다.


1
이 코드는 이중 값을 올바르게 구문 분석하지 않습니다. 파일에서 값을 올바르게 구문 분석하려면 Java 7이 필요합니까?
jonderry

1
아, 바보 IO 코드를 여러 줄에서 한 줄로 리팩토링 한 후 엔디안을 다시 설정하는 것을 잊었습니다. Java 7은 물론 Java 6에 별도로 포크 / 조인을 추가하지 않는 한 Java 7이 필요합니다.
arjan

내 컴퓨터에서 3.411s. 나쁜하지만 koumes21의 자바 솔루션 :보다 느리게하지 않음
static_rtti

1
나는 koumes21의 솔루션을 너무 로컬에서 내 시스템의 상대적인 차이점을 확인하기 위해 너무 로컬에서 시도 할 것입니다. 어쨌든 koumes21의 '잃어버린'수치는 훨씬 더 영리한 솔루션이므로 부끄러운 일이 아닙니다. 이것은 포크 / 조인 풀에 던져지는 거의 표준 퀵 정렬입니다.)
arjan

1

전통적인 pthread를 사용하는 버전. Guvante의 답변에서 복사 한 병합 코드. 로 컴파일하십시오 g++ -O3 -pthread.

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <algorithm>

static unsigned int nthreads = 4;
static unsigned int size = 50000000;

typedef struct {
  double *array;
  int size;
} array_t;


void 
merge(double *left, int leftsize,
      double *right, int rightsize,
      double *result)
{
  int l = 0, r = 0, insertat = 0;
  while (l < leftsize && r < rightsize) {
    if (left[l] < right[r])
      result[insertat++] = left[l++];
    else
      result[insertat++] = right[r++];
  }

  while (l < leftsize) result[insertat++] = left[l++];
  while (r < rightsize) result[insertat++] = right[r++];
}


void *
run_thread(void *input)
{
  array_t numbers = *(array_t *)input;
  std::sort(numbers.array, numbers.array+numbers.size); 
  pthread_exit(NULL);
}

int 
main(int argc, char **argv) 
{
  double *numbers = (double *) malloc(size * sizeof(double));

  FILE *f = fopen("gaussian.dat", "rb");
  if (fread(numbers, sizeof(double), size, f) != size)
    return printf("Reading gaussian.dat failed");
  fclose(f);

  array_t worksets[nthreads];
  int worksetsize = size / nthreads;
  for (int i = 0; i < nthreads; i++) {
    worksets[i].array=numbers+(i*worksetsize);
    worksets[i].size=worksetsize;
  }

  pthread_attr_t attributes;
  pthread_attr_init(&attributes);
  pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_JOINABLE);

  pthread_t threads[nthreads];
  for (int i = 0; i < nthreads; i++) {
    pthread_create(&threads[i], &attributes, &run_thread, &worksets[i]);
  }

  for (int i = 0; i < nthreads; i++) {
    pthread_join(threads[i], NULL);
  }

  double *tmp = (double *) malloc(size * sizeof(double));
  merge(numbers, worksetsize, numbers+worksetsize, worksetsize, tmp);
  merge(numbers+(worksetsize*2), worksetsize, numbers+(worksetsize*3), worksetsize, tmp+(size/2));
  merge(tmp, worksetsize*2, tmp+(size/2), worksetsize*2, numbers);

  /*
  printf("Verifying result..\n");
  for (int i = 0; i < size - 1; i++) {
    if (numbers[i] > numbers[i+1])
      printf("Result is not correct\n");
  }
  */

  pthread_attr_destroy(&attributes);
  return 0;
}  

내 노트북에서 다음과 같은 결과를 얻습니다.

real    0m6.660s
user    0m9.449s
sys     0m1.160s

1

알려진 분포를 실제로 사용하려고하는 순차적 C99 구현은 다음과 같습니다. 기본적으로 배포 정보를 사용하여 단일 버킷 정렬을 수행 한 다음 버킷 한계 내에서 균일 한 분포를 가정하고 각 데이터를 원래 버퍼에 다시 복사하도록 수정 된 선택 정렬을 가정하면 각 버킷에서 몇 라운드의 빠른 정렬을 수행합니다. 퀵 정렬은 분리 점을 기억하므로 선택 정렬은 작은 덩어리에서만 작동해야합니다. 그리고 그 모든 복잡성에도 불구하고 (그렇기 때문에?) 실제로 빠르지는 않습니다.

Φ를 빠르게 평가하기 위해 값은 몇 점으로 샘플링되고 나중에 선형 보간에서만 사용됩니다. 근사치가 엄격하게 단조로운 한 Φ가 정확하게 평가되는지는 실제로 중요하지 않습니다.

출력 함 크기는 출력 함 오버 플로우 가능성을 무시할 수 있도록 선택됩니다. 보다 정확하게 현재 매개 변수를 사용하면 50000000 개의 요소 데이터 집합이 빈 오버플로를 유발할 가능성은 3.65e-09입니다. (이는 푸 아송 분포생존 함수 를 사용하여 계산할 수 있습니다 .)

컴파일하려면 사용하십시오

gcc -std=c99 -msse3 -O3 -ffinite-math-only

다른 솔루션보다 훨씬 더 많은 계산이 있기 때문에 이러한 컴파일러 플래그는 적어도 합리적으로 빨리 만들기 위해 필요합니다. 없으면 -msse3에서 변환 double하는 int정말 느린된다. 아키텍처가 SSE3를 지원하지 않는 경우이 lrint()기능을 사용하여 이러한 변환을 수행 할 수도 있습니다 .

코드가 다소 추악합니다. 이것이 "합리적으로 읽을 수있는"요구 사항을 충족하는지 확실하지 않습니다

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>

#define N 50000000
#define BINSIZE 720
#define MAXBINSIZE 880
#define BINCOUNT (N / BINSIZE)
#define SPLITS 64
#define PHI_VALS 513

double phi_vals[PHI_VALS];

int bin_index(double x)
{
    double y = (x + 8.0) * ((PHI_VALS - 1) / 16.0);
    int interval = y;
    y -= interval;
    return (1.0 - y) * phi_vals[interval] + y * phi_vals[interval + 1];
}

double bin_value(int bin)
{
    int left = 0;
    int right = PHI_VALS - 1;
    do
    {
        int centre = (left + right) / 2;
        if (bin < phi_vals[centre])
            right = centre;
        else
            left = centre;
    } while (right - left > 1);
    double frac = (bin - phi_vals[left]) / (phi_vals[right] - phi_vals[left]);
    return (left + frac) * (16.0 / (PHI_VALS - 1)) - 8.0;
}

void gaussian_sort(double *restrict a)
{
    double *b = malloc(BINCOUNT * MAXBINSIZE * sizeof(double));
    double **pos = malloc(BINCOUNT * sizeof(double*));
    for (size_t i = 0; i < BINCOUNT; ++i)
        pos[i] = b + MAXBINSIZE * i;
    for (size_t i = 0; i < N; ++i)
        *pos[bin_index(a[i])]++ = a[i];
    double left_val, right_val = bin_value(0);
    for (size_t bin = 0, i = 0; bin < BINCOUNT; ++bin)
    {
        left_val = right_val;
        right_val = bin_value(bin + 1);
        double *splits[SPLITS + 1];
        splits[0] = b + bin * MAXBINSIZE;
        splits[SPLITS] = pos[bin];
        for (int step = SPLITS; step > 1; step >>= 1)
            for (int left_split = 0; left_split < SPLITS; left_split += step)
            {
                double *left = splits[left_split];
                double *right = splits[left_split + step] - 1;
                double frac = (double)(left_split + (step >> 1)) / SPLITS;
                double pivot = (1.0 - frac) * left_val + frac * right_val;
                while (1)
                {
                    while (*left < pivot && left <= right)
                        ++left;
                    while (*right >= pivot && left < right)
                        --right;
                    if (left >= right)
                        break;
                    double tmp = *left;
                    *left = *right;
                    *right = tmp;
                    ++left;
                    --right;
                }
                splits[left_split + (step >> 1)] = left;
            }
        for (int left_split = 0; left_split < SPLITS; ++left_split)
        {
            double *left = splits[left_split];
            double *right = splits[left_split + 1] - 1;
            while (left <= right)
            {
                double *min = left;
                for (double *tmp = left + 1; tmp <= right; ++tmp)
                    if (*tmp < *min)
                        min = tmp;
                a[i++] = *min;
                *min = *right--;
            }
        }
    }
    free(b);
    free(pos);
}

int main()
{
    double *a = malloc(N * sizeof(double));
    FILE *f = fopen("gaussian.dat", "rb");
    assert(fread(a, sizeof(double), N, f) == N);
    fclose(f);
    for (int i = 0; i < PHI_VALS; ++i)
    {
        double x = (i * (16.0 / PHI_VALS) - 8.0) / sqrt(2.0);
        phi_vals[i] =  (erf(x) + 1.0) * 0.5 * BINCOUNT;
    }
    gaussian_sort(a);
    free(a);
}

4.098! 컴파일하려면 -lm을 추가해야했습니다 (erf 용).
static_rtti

1
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <memory.h>
#include <algorithm>

// maps [-inf,+inf] to (0,1)
double normcdf(double x) {
        return 0.5 * (1 + erf(x * M_SQRT1_2));
}

int calcbin(double x, int bins) {
        return (int)floor(normcdf(x) * bins);
}

int *docensus(int bins, int n, double *arr) {
        int *hist = calloc(bins, sizeof(int));
        int i;
        for(i = 0; i < n; i++) {
                hist[calcbin(arr[i], bins)]++;
        }
        return hist;
}

void partition(int bins, int *orig_counts, double *arr) {
        int *counts = malloc(bins * sizeof(int));
        memcpy(counts, orig_counts, bins*sizeof(int));
        int *starts = malloc(bins * sizeof(int));
        int b, i;
        starts[0] = 0;
        for(i = 1; i < bins; i++) {
                starts[i] = starts[i-1] + counts[i-1];
        }
        for(b = 0; b < bins; b++) {
                while (counts[b] > 0) {
                        double v = arr[starts[b]];
                        int correctbin;
                        do {
                                correctbin = calcbin(v, bins);
                                int swappos = starts[correctbin];
                                double tmp = arr[swappos];
                                arr[swappos] = v;
                                v = tmp;
                                starts[correctbin]++;
                                counts[correctbin]--;
                        } while (correctbin != b);
                }
        }
        free(counts);
        free(starts);
}


void sortbins(int bins, int *counts, double *arr) {
        int start = 0;
        int b;
        for(b = 0; b < bins; b++) {
                std::sort(arr + start, arr + start + counts[b]);
                start += counts[b];
        }
}


void checksorted(double *arr, int n) {
        int i;
        for(i = 1; i < n; i++) {
                if (arr[i-1] > arr[i]) {
                        printf("out of order at %d: %lf %lf\n", i, arr[i-1], arr[i]);
                        exit(1);
                }
        }
}


int main(int argc, char *argv[]) {
        if (argc == 1 || argv[1] == NULL) {
                printf("Expected data size as argument\n");
                exit(1);
        }
        int n = atoi(argv[1]);
        const int cachesize = 128 * 1024; // a guess
        int bins = (int) (1.1 * n * sizeof(double) / cachesize);
        if (argc > 2) {
                bins = atoi(argv[2]);
        }
        printf("Using %d bins\n", bins);
        FILE *f = fopen("gaussian.dat", "rb");
        if (f == NULL) {
                printf("Couldn't open gaussian.dat\n");
                exit(1);
        }
        double *arr = malloc(n * sizeof(double));
        fread(arr, sizeof(double), n, f);
        fclose(f);

        int *counts = docensus(bins, n, arr);
        partition(bins, counts, arr);
        sortbins(bins, counts, arr);
        checksorted(arr, n);

        return 0;
}

이것은 erf ()를 사용하여 각 요소를 적절하게 저장소에 넣은 다음 각 저장소를 정렬합니다. 배열을 완전히 제자리에 유지합니다.

첫 번째 단계 : docensus ()는 각 빈의 요소 수를 계산합니다.

두 번째 단계 : partition ()은 배열을 치환하여 각 요소를 적절한 빈에 넣습니다.

세 번째 단계 : sortbins ()는 각 빈에 대해 qsort를 수행합니다.

그것은 순진하고 값이 비싼 erf () 함수를 두 번 호출합니다. 첫 번째와 세 번째 패스는 잠재적으로 병렬화 가능합니다. 두 번째는 매우 직렬 적이며, 임의 메모리 액세스 패턴으로 인해 느려질 수 있습니다. 또한 CPU 전력 대 메모리 속도 비율에 따라 각 더블의 빈 수를 캐시하는 것이 좋습니다.

이 프로그램을 사용하면 사용할 용지함 수를 선택할 수 있습니다. 명령 행에 두 번째 숫자를 추가하십시오. 나는 그것을 gcc -O3로 컴파일했지만 내 기계는 너무 약해서 좋은 성능 수치를 말할 수 없다.

편집 : of ! 내 C 프로그램은 std :: sort를 사용하여 마술처럼 C ++ 프로그램으로 변환되었습니다!


더 빠른 stdnormal_cdf를 위해 phi 를 사용할 수 있습니다 .
Alexandru

대략 몇 개의 쓰레기통을 넣어야합니까?
static_rtti

@ Alexandru : normcdf에 조각 선형 근사치를 추가하고 속도는 약 5 %였습니다.
fred

@static_rtti : 아무 것도 넣을 필요가 없습니다. 기본적으로 코드는 구간 수를 선택하므로 평균 구간 크기는 10 / 11 / 128kb입니다. 빈이 너무 적 으면 파티셔닝의 이점을 얻지 못합니다. 캐시 오버 플로우로 인해 너무 많아 파티션 단계가 중단됩니다.
fred

10.6 초! 빈 수로 비트를 재생하려고 시도했으며 5000으로 최상의 결과를 얻었습니다 (기본값은 3356보다 약간 큼). 솔루션의 성능이 훨씬 향상 될 것으로 예상됩니다. 아마도 아마도 C ++ 솔루션의 std :: sort가 더 빠른 대신 qsort를 사용하고 있습니까?
static_rtti

1

Michael Herf ( Radix Tricks ) 의 기수 정렬 구현을 살펴보십시오 . 내 컴퓨터 std::sort에서 첫 번째 답변 의 알고리즘에 비해 정렬이 5 배 빠릅니다 . 정렬 기능의 이름은입니다 RadixSort11.

int main(void)
{
    std::ifstream ifs("C:\\Temp\\gaussian.dat", std::ios::binary | std::ios::in);
    std::vector<float> v;
    v.reserve(50000000);
    double d;
    while (ifs.read(reinterpret_cast<char*>(&d), sizeof(double)))
        v.push_back(static_cast<float>(d));
    std::vector<float> vres(v.size(), 0.0);
    clock_t c0 = clock();
    RadixSort11(&v[0], &vres[0], v.size());
    std::cout << "Finished after: "
              << static_cast<double>(clock() - c0) / CLOCKS_PER_SEC << std::endl;
    return 0;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.