Yarışma: Gauss tarafından dağıtılan büyük bir veri dizisini sıralamanın en hızlı yolu


71

Bu soruya ilginin ardından, bir yarışma önererek cevapları biraz daha objektif ve nicel yapmanın ilginç olacağını düşündüm.

Fikir basit: 50 milyon gauss dağılımlı çift içeren bir ikili dosya oluşturdum (ortalama: 0, stdev 1). Amaç, bunları bellekte olabildiğince hızlı bir şekilde sıralayacak bir program yapmaktır. Python'da çok basit bir referans uygulaması tamamlamak 1m4s sürer. Ne kadar düşük gidebiliriz?

Kurallar şu şekildedir: "gaussian.dat" dosyasını açan bir programla cevap verin ve bellekteki sayıları sıralar (bunları çıkarmaya gerek yoktur) ve programı oluşturmak ve çalıştırmak için talimatlar. Program, Arch Linux makinemde çalışabilmelidir (yani, bu sisteme kolayca kurulabilen herhangi bir programlama dilini veya kütüphanesini kullanabilirsiniz).

Programın makul bir şekilde okunabilir olması gerekir, böylece başlatmanın güvenli olduğundan emin olabilirim (yalnızca montajcı çözüm yok, lütfen!).

Cevapları makinemde çalıştıracağım (dört çekirdekli, 4 Gigabayt RAM). En hızlı çözüm, kabul edilen cevabı ve 100 puan ödül alacaktır :)

Sayıları üretmek için kullanılan program:

#!/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)

Basit referans uygulaması:

#!/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)

EDIT: sadece 4 GB RAM, üzgünüm

2 numaralı DÜZENLEME: Yarışmanın amacının veriler hakkında önceden bilgi kullanıp kullanamayacağımızı görmek olduğuna dikkat edin . farklı programlama dili uygulamaları arasında sinir bozucu bir eşleşme olması gerekmiyor!


1
Her değeri alın ve doğrudan "beklenen" konumuna getirin, yer değiştiren değer için tekrarlayın. Bununla ilgili bir çift sorunun nasıl çözüleceğinden emin değilim. Tamamlanıncaya kadar, kabarcık sıralama tamamlanıncaya kadar (bir çift geçer geçmelidir).

1
Yarın akşama kadar kapatılmadıysa kova sıralama çözümü

1
@static_rtti - ağır bir CG kullanıcısı olarak, bu tam olarak CG.SE'de kesmek istediğimiz bir şeydir. Herhangi bir okuma moduna, bunu CG'ye taşıyın, kapatmayın.
arrdem

1
CodeGolf.SE'ye Hoşgeldiniz! SO'nun orijinalinin yorumunun birçoğunu, bunun ait olup olmadığına ilişkin olarak temizledim ve CodeGolf.SE ana akımına yakın olacak şekilde yeniden etiketlendi.
dmckee

2
Buradaki en zor mesele, nesnel kazanma kriterleri aramamız ve "en hızlı" platform bağımlılıklarını ortaya koyuyor ... cpython sanal makinesinde uygulanan bir O (n ^ {1.2}) algoritması, bir O (n ^ {1.3} ) c de uygulanan benzer bir sabit algoritma? Genel olarak, her bir çözümün performans özellikleri hakkında bir tartışma öneriyorum, çünkü bu, insanların neler olup bittiğini yargılamaya yardımcı olabilir.
dmckee

Yanıtlar:


13

İşte C ++ 'da ilk önce sayıları beklenen aynı eleman sayısına sahip kovalara bölen ve ardından her bir kovayı ayrı ayrı sıralayan bir çözüm. Vikipedi'deki bazı formüllere dayanarak kümülatif dağılım işlevinin bir tablosunu hazırlar ve daha sonra hızlı bir yaklaşım elde etmek için bu tablodaki değerleri enterpolasyon eder.

Dört çekirdeği kullanmak için birkaç adım birden fazla iş parçacığında ilerler.

#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;
}

Derlemek ve çalıştırmak için bu komutu kullanın:

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

EDIT: Tüm kovalar şimdi tekrar diziye kopyalanması gereğini ortadan kaldırmak için aynı diziye yerleştirilir. Ayrıca önceden hesaplanmış değerlere sahip tablonun boyutu azaldı, çünkü değerler yeterince doğru. Yine de, 256'dan daha büyük kova sayısını değiştirirsem, programın çalışması bu kova sayısından daha uzun sürer.

EDIT: Aynı algoritma, farklı programlama dili. Java yerine C ++ kullandım ve çalışma süresi makinemde ~ 3.2s'den ~ 2.35s'ye düşürüldü. En iyi kova sayısı hala 256 civarında (yine bilgisayarımda).

Bu arada, tbb gerçekten harika.

EDIT: Alexandru'nun mükemmel çözümünden ilham aldım ve son aşamadaki std :: sortini onun radix sıralama şeklinin değiştirilmiş bir versiyonuyla değiştirdim. Dizi içinde daha fazla geçiş olmasına rağmen pozitif / negatif sayılarla başa çıkmak için farklı bir yöntem kullandım. Ayrıca diziyi tam olarak sıralamaya ve yerleştirme sıralamasını kaldırmaya karar verdim. Daha sonra bu değişikliklerin performansı nasıl etkilediğini ve muhtemelen geri döndürdüğünü test ederek biraz zaman geçireceğim. Bununla birlikte, radix sıralama kullanılarak, süre ~ 2.35s'den ~ 1.63s'e düşürüldü.


Güzel. Bende 3.055 var. En düşük benimkini alabildim 6,3. İstatistikleri iyileştirmek için seninkileri seçiyorum. Neden 256 tane kova sayısı seçtiniz? 128 ve 512'yi denedim ancak 256 en iyi sonucu verdi.
Scott

Neden 256 tane kova sayısı seçtim? 128 ve 512'yi denedim ancak 256 en iyi sonucu verdi. :) Ampirik olarak buldum ve kova sayısını arttırmanın neden algoritmayı yavaşlattığından emin değilim - bellek ayırma o kadar uzun sürmemeli. Belki önbellek boyutu ile ilgili bir şey?
k21

Makinemde 2.725. JVM'nin yükleme süresini dikkate alarak bir java çözümü için oldukça hoş.
static_rtti

2
Kodumu, benim ve Arjan'ın çözümüne göre (benimkinden daha temiz olduğu için sözdizimini kullandım) ve benimkilerimden -3 saniye sonra alabildim. Bir SSD’m var, eğer çıkmazsa ne olacağını merak ediyorum. Aynı zamanda, biraz titremenizden de kurtulur. Modded bölümler burada.
Scott

3
Bu, testlerimdeki en hızlı paralel çözümdür (16 çekirdekli işlemci). 1.22, 1.94.
Alexandru

13

Akıllı olmadan, sadece daha hızlı bir naif sıralayıcı sağlamak için, Python'nuzla hemen hemen aynı olması gereken C'de bir tane:

#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;
}

Derlenmiş gcc -O3, makinemde bu Python'dan bir dakikadan daha az zaman alıyor: 87 s'ye kıyasla yaklaşık 11 s.


1
Makinemde 10.086 aldı, bu sizi şu anki lider yapıyor! Ama daha iyisini yapabileceğimizden eminim :)

1
İkinci üçlü işleci kaldırmayı deneyebilir ve bu durumda sadece 1 değerini döndürürsünüz, çünkü rastgele çiftler bu miktarda veride birbirine eşit değildir.
Codism

@Codism: Eşdeğer verilerin yerlerini değiştirmeyi umursamayacağımızı eklerdim, bu yüzden eşdeğer değerler elde etsek bile uygun bir basitleştirme olur.

10

En iyi dördüncü parçaya bölünmesi gereken standart sapmaya göre segmentlere ayrıldım. Düzenleme: http://en.wikipedia.org/wiki/Error_function#Table_of_values içindeki x değerine göre bölüme yeniden yazılmıştır

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

Daha küçük kovalar kullanmayı denedim, ancak mevcut çekirdek sayısının 2 kat ötesinde bir etkisi çok az görünüyordu. Paralel koleksiyonlar olmadan, paralel koleksiyonlarla kutumda 37 saniye, paralel koleksiyonlarla da 24 saniye sürecek. Dağıtım yoluyla bölümlere ayırıyorsanız, yalnızca bir dizi kullanamazsınız, bu nedenle biraz daha fazla ek yükü vardır. Bir skalada ne zaman bir kutunun / kutunun açılacağı konusunda net değilim.

Paralel koleksiyon için scala 2.9 kullanıyorum. Bunun tar.gz dağıtımını indirebilirsiniz.

Derlemek için: scalac SortFile.scala (Sadece doğrudan scala / bin klasörüne kopyaladım.

Çalıştırmak için: JAVA_OPTS = "- Xmx4096M" ./scala SortFile (2 konser tokmak ile koştum ve aynı zamanda vardım)

Düzenleme: Kaldırıldı tahsisatDirect, sadece tahsisattan daha yavaş. Dizi arabellekleri için başlangıç ​​boyutunun kaldırılması kaldırıldı. Aslında 50000000 değerinin tamamını okumuş oldu. Umarım otomatik boks sorunlarından kaçınmak için yeniden yazılmıştır (yine de saf c'den daha yavaş)

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.185s! Scala çözümü için güzel, sanırım ... Ayrıca, Gauss dağılımını bir şekilde kullanan ilk çözümü sağlamak için bravo!

1
Sadece c # çözümüyle rekabet etmeyi hedefliyordum. C / c ++ 'ı yeneceğimi düşünmedim. Ayrıca .. bu senin için benden çok farklı davranıyor. Sonunda openJDK kullanıyorum ve daha yavaştır. Acaba daha fazla bölüm eklemek env'nize yardımcı olur mu?
Scott

9

Sadece bunu bir cs dosyasına koyun ve csc ile teoride derleyin: (mono gerektirir)

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();
        }
    }
}

Çözümlerinizi Mono ile çalıştırabilir miyim? Bunu nasıl yapmalıyım?

Mono kullanmamış, bunu düşünmemiş, F # 'yi derlemeli ve çalıştırmalısınız.

1
Performansı artırmak için dört iş parçacığı kullanmak için güncellendi. Şimdi bana 6 saniye verir. Yalnızca bir yedek dizi kullanırsanız ve her şey en az bir kere yazıldığından, CLR tarafından yapılan bir ton belleği sıfırlamaktan kaçınırsanız, bunun önemli ölçüde geliştirilebileceğini (5 sn muhtemel) unutmayın.

1
Makinemde 9.598! Şu anki lider sensin :)

1
Annem Mono'lu çocuklardan uzak durmamı söyledi!

8

Dağılımın ne olduğunu bildiğinizden, doğrudan indeksleme O (N) sıralama kullanabilirsiniz. (Bunun ne olduğunu merak ediyorsanız, 52 kartlık bir desteniz olduğunu ve sıralamak istediğinizi varsayalım. 52 kutu atıp her kartı kendi bölmesine atmanız yeterli.)

5e7 çiftin var. 5e7'lik bir sonuç dizisini R iki katına ayırın. Her numarayı al xve al i = phi(x) * 5e7. Temelde yapın R[i] = x. Çarpışmayı ele alabileceği, örneğin çarpıştığı sayıyı (basit karma kodlamada olduğu gibi) taşımak gibi bir yol var. Alternatif olarak, R'yi benzersiz bir boş değerle dolu, birkaç kat daha büyük yapabilirsiniz . Sonunda, sadece R'nin elemanlarını topladın.

phisadece Gauss kümülatif dağılım fonksiyonudur. +/- sonsuzluk arasındaki bir gaussian dağıtılmış sayıyı 0 ile 1 arasındaki eşit dağıtılmış bir sayıya dönüştürür. Bunu hesaplamanın basit bir yolu, tablo arama ve enterpolasyondur.


3
Dikkatli olun: yaklaşık dağılımı biliyorsunuz, tam dağılımı değil. Verilerin bir Gauss yasası kullanılarak üretildiğini biliyorsunuz, ancak sonlu olduğundan, tam olarak bir Gauss uymuyor.

@static_rtti: Bu durumda, gerekli phi yaklaşımı IMO veri setindeki düzensizliklerden daha büyük bir güçlük yaratacaktır.

1
@static_rtti: kesin olması gerekmiyor. Sadece verileri yaymak zorundadır, bu yüzden yaklaşık olarak tekdüzelidir, bu nedenle bazı yerlerde çok fazla toplanmaz.

Diyelim ki 5e7 çiftiniz var. Neden sadece R'deki her girişi 5e6 vektörlü çift vektör yapmıyorsunuz? Ardından, her iki katı da uygun vektörde geri itin. Vektörleri sırala ve bittin. Bu, girişin boyutunda doğrusal zaman almalıdır.
Neil G,

Aslında, bu mdkess'in zaten bu çözümü bulduğunu görüyorum.
Neil G

8

İşte başka bir sıralı çözüm:

#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;
}

Çok iş parçacıklı çözümü geçtiğinden şüpheliyim, ancak i7 dizüstü bilgisayarımdaki zamanlamalar: (stdsort başka bir cevapta verilen C ++ çözümü):

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

Bu çözümün doğrusal zaman karmaşıklığına sahip olduğuna dikkat edin (çünkü çiftlerin özel gösterimini kullanır).

EDIT : Artan elemanların sırası düzeltildi.

EDIT : Neredeyse yarım saniye daha yüksek hız.

EDIT : 0,7 saniye daha arttırılmış hız. Algoritmayı daha önbellek dostu yaptı.

EDIT : 1 saniye daha iyileştirilmiş hız. Sadece 50.000.000 eleman bulunduğundan, kısmen mantisleri sınıflandırabilir ve yerinde olmayan elemanları düzeltmek için insert sort (önbellek dostu olan) kullanabilirim. Bu fikir, son sayı tabanı sıralama döngüsünden iki yineleme etrafında kalkar.

EDIT : 0.16 daha az saniye. Sıralama sırası tersine çevrilirse ilk std :: reverse özelliği elimine edilebilir.


Şimdi bu ilginçleşiyor! Ne çeşit bir sıralama algoritması?
static_rtti

2
En küçük önemli sayı tabanı sırası . Mantisleri, sonra üsleri ve işaretleri işaretleyebilirsiniz. Burada sunulan algoritma bu fikri bir adım daha ileri götürür. Farklı bir cevapta verilen bir bölümleme fikri kullanılarak paralelleştirilebilir.
Alexandru

Tek bir dişli çözüm için oldukça hızlı: 2.552s! Verilerin normal olarak dağıtıldığından yararlanmak için çözümünüzü değiştirebileceğinizi düşünüyor musunuz? Muhtemelen mevcut en iyi çoklu iş parçacıklı çözümlerden daha iyisini yapabilirsin.
static_rtti

1
@static_rtti: Şam Steel'in zaten bu uygulamanın okuyuculu bir versiyonunu yayınladığını görüyorum. Bu algoritmanın önbelleğe alma davranışını geliştirdim, bu yüzden şimdi daha iyi zamanlamaları almalısınız. Lütfen bu yeni sürümü test edin.
Alexandru

2
En son testlerimde 1.459. Bu çözüm benim kurallarım uyarınca kazanmasa da, gerçekten büyük övgüler hak ediyor. Tebrikler!
static_rtti

6

Christian Ammer'in çözümünü alarak Intel'in Dişli Yapı Taşlarıyla paralel hale getirme

#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;
}

Intel'in Performans İlkelleri (IPP) kütüphanesine erişiminiz varsa, radix sıralamasını kullanabilirsiniz. Sadece değiştir

#include <tbb/parallel_sort.h>

ile

#include "ipps.h"

ve

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

ile

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

Çift çekirdekli dizüstü bilgisayarımda zamanlamalar

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 oldukça serin ve kullanımı kolay görünüyor!

2
TBB saçma harika. Algoritmik iş için tam olarak doğru soyutlama seviyesi.
drxzcl

5

Dağıtım istatistiklerine göre pivot değerlerini seçen ve böylelikle eşit büyüklükte bölümler sağlayan paralel bir quicksort uygulamasına ne dersiniz ? İlk pivot ortalama (bu durumda sıfır), bir sonraki çift 25 ve 75. yüzdelik noktalarda (+/- -0.67449 standart sapmalar) olacaktı, ve böylece her bölüm kalan verileri daha fazla yarıya indirecek veya daha az mükemmel.


Bu benim yaptığım etkin bir şeydi ... tabii ki yazmamı bitirmeden önce bu yazıyı aldın.

5

Çok çirkin (neden sayılarla biten değişkenleri kullanabildiğimde dizileri kullan), ancak hızlı kod (ilk denemeye std :: thread), bütün zaman (gerçek zamanlı) sistemimde 1,8 s (std :: sort ile karşılaştırarak) () 4,8 s), g ++ -std = c ++ 0x -O3 -march = native -pthread ile derleyin Sadece stdin'den veri iletin (yalnızca 50M için çalışır).

#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 dosyasını okumak için düzenleme yapıldı.


Yukarıdaki C ++ çözümlerinde olduğu gibi, gaussian.dat dosyasını okumak için değiştirebilir misiniz?

Eve geldiğimde daha sonra deneyeceğim.
static_rtti

Çok güzel bir çözüm, mevcut lider sensin (1.949s)! Ve gauss dağılımının güzel kullanımı :)
static_rtti 18

4

Kullanarak bir C ++ çözümü std::sort(sonunda qsort'tan daha hızlı , qsort - std :: sort'in performansı ile ilgili )

#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;
}

Ne kadar uzun sürdüğünü söyleyemem çünkü gaussian.datmakinemde yalnızca 1 GB'im var ve verilen Python koduyla yalnızca 25 milyon çiftle (Bellek hatası almaksızın) bir dosya oluşturabiliyorum. Fakat std :: sort algoritmasının ne kadar süre çalıştığını çok merak ediyorum.


6.425s! Beklendiği gibi, C ++ parlıyor :)

@static_rtti: Timsort algoritmasını denedim (ilk sorunuzda Matthieu M.'den önerildiği gibi ). sort.hDosyada C ++ ile derlemek için bazı değişiklikler yapmak zorunda kaldım . Bundan iki kat daha yavaştı std::sort. Neden, belki de derleyici optimizasyonları nedeniyle olduğunu bilmiyor musunuz?
Christian Ammer

4

İşte Alexandru'nun radix çeşitliliğini Zjarek'in dişli akıllı döndürme işleminin bir karışımı. İle derleyin

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

Radix boyutunu STEP tanımlayarak değiştirebilirsiniz (örneğin, add -DSTEP = 11). Dizüstü bilgisayarım için en iyisini buldum 8 (varsayılan).

Varsayılan olarak, sorunu 4 parçaya böler ve bunu çoklu iş parçacıklarında çalıştırır. Bunu bir derinlik parametresini komut satırına ileterek değiştirebilirsiniz. Yani, iki çekirdeğiniz varsa,

sorter_gaussian_radix 50000000 1

ve eğer 16 çekirdeğiniz varsa

sorter_gaussian_radix 50000000 4

Şu anda maksimum derinlik 6'dır (64 konu). Çok fazla seviye koyarsanız, kodu yavaşlatırsınız.

Ayrıca denediğim bir şey, Intel Performance Primitives (IPP) kitaplığındaki radix sıralama oldu. Alexandru'nun uygulaması, IPP'yi yaklaşık% 30 oranında yavaşlatırken, sağlam bir şekilde IPP'yi tehdit ediyor. Bu varyasyon da buraya dahil edilmiştir (yorumlanmıştır).

#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;
}

EDIT : Alexandru'nun önbellek geliştirmelerini uyguladım ve bu da makinemdeki zamanın% 30'unu azalttı.

EDIT : Bu özyinelemeli bir sıralama uygular, bu yüzden Alexandru'nun 16 çekirdek makinesinde iyi çalışması gerekir. Ayrıca Alexandru'nun son gelişimini kullanır ve tersine çevirenlerden birini kaldırır. Benim için bu% 20 iyileşme sağladı.

EDIT : 2'den fazla çekirdek olduğunda verimsizliğe neden olan bir işaret hatası düzeltildi.

EDIT : lambda kaldırıldı, böylece gcc'nin eski sürümleriyle derlenecek. Yorumlanan IPP kodu varyasyonunu içerir. Ayrıca 16 çekirdekte çalışmaya yönelik belgeleri de düzelttim. Söyleyebileceğim kadarıyla, bu en hızlı uygulama.

EDIT : STEP 8 olmadığında bir hata düzeltildi. Maksimum iş parçacığı sayısı 64'e yükseldi. Bazı zamanlama bilgileri eklendi.


Güzel. Radix sıralama çok dostça önbellek. Bakarak değiştirerek daha iyi sonuçlar elde edip edemeyeceğinizi görün step(11 dizüstü bilgisayarımda en iyiydi).
Alexandru

Bir hatan var: int cnt[mask]olmalı int cnt[mask + 1]. Daha iyi sonuçlar için sabit bir değer kullanın int cnt[1 << 16].
Alexandru

Bütün bu çözümleri bugün eve geldiğimde deneyeceğim.
static_rtti

1.534s !!! Sanırım bir liderimiz var :-D
static_rtti

@static_rtti: Bunu tekrar deneyebilir misiniz? En son denediğinizden çok daha hızlı bir şekilde kazanılmıştır. Makinemde, diğer tüm çözümlerden çok daha hızlı.
Şam Çeliği,

2

Sanırım bu gerçekten ne yapmak istediğine bağlı. Bir grup Gauss'u sıralamak istiyorsanız, o zaman bu size yardımcı olmaz. Ama bir avuç dolusu Gaussian istiyorsanız, bu olacak. Bu sorunu biraz özlese bile, gerçek sıralama yordamlarını karşılaştırmanın ilginç olacağını düşünüyorum.

Hızlı bir şeyin olmasını istiyorsanız, daha azını yapın.

Normal dağılımdan bir grup rastgele örnek üretmek ve sonra sıralamak yerine, sıralı bir sırada normal dağılımdan bir grup örnek oluşturabilirsiniz.

Sen çözümü kullanabilirsiniz burada sıralanmış sırayla n tekdüze rasgele sayı üretmek için. Ardından, normal dağılımın ters cdf'sini (scipy.stats.norm.ppf), tekdüze rasgele sayıları, ters dönüşüm örneklemesi ile normal dağılımdaki sayılara çevirmek için kullanabilirsiniz .

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))

Ellerinizi kirletmek istiyorsanız, bir tür yinelemeli yöntem kullanarak ve önceki sonucu ilk tahmininiz olarak kullanarak birçok ters cdf hesaplamasını hızlandırabileceğinizi tahmin ediyorum. Tahminler çok yakın olacağından muhtemelen tek bir yineleme size büyük bir doğruluk verecektir.


2
Güzel cevap, ama bu aldatma olur :) Benim sorumun fikri, sıralama algoritmalarının çok büyük dikkat göstermesine rağmen, sıralama için veriler hakkında önceki bilgilerin kullanımı hakkında az sayıda makale olmasına rağmen, neredeyse hiçbir literatürün bulunmamasıdır. Ele alınan mesele güzel kazanımlar olduğunu bildirdi. O halde neyin mümkün olduğunu görelim!

2

Bu değişen Guvante'nin bu Main () çözümünü deneyin, 1/4 IO okuması biter bitmez sıralamaya başlar, testimde daha hızlıdır:

    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.933s. Biraz daha hızlı :)

2

Dağılımı bildiğinizden, benim fikrim, her biri aynı sayıda beklenen öğeye sahip k kovaları yapmak olacaktır (dağılımı bildiğinizden beri bunu hesaplayabilirsiniz). Sonra O (n) zamanında, diziyi süpürün ve öğeleri kovalarına yerleştirin.

Sonra aynı anda kovaları sıralayın. Farz edelim ki k kovası ve n elemanı var. Bir kova sıralamak için (n / k) lg (n / k) zaman alacaktır. Şimdi, kullanabileceğiniz bir işlemciniz olduğunu varsayalım. Kovalar bağımsız olarak sıralanabildiğinden, başa çıkmak için bir çarpan tavana (k / p) sahipsiniz. Bu, k 'yi seçerseniz n lg n'den daha hızlı olması gereken n + tavan (k / p) * (n / k) lg (n / k)' lik bir çalışma süresi verir.


Bence bu en iyi çözüm.
Neil G

Bir kovaya girecek element sayısını tam olarak bilmiyorsunuz, bu yüzden matematik gerçekten yanlıştır. Söyleniyor, bu bence iyi bir cevap.
poulejapon

@pouejapon: Haklısın.
Neil G

Bu cevap kulağa çok hoş geliyor . Sorun şudur - gerçekten hızlı değil. Bunu C99'da uyguladım (cevabımı görün) ve kesinlikle kolayca atıyor std::sort(), ancak Alexandru'nun radixsort çözümünden çok daha yavaş.
Sven Marnach

2

Düşük seviyeli bir optimizasyon fikri, bir SSE siciline iki ikiye sığdırmaktır, böylece her bir diş bir anda iki öğe ile çalışacaktır. Bu bazı algoritmalar için yapmak için karmaşık olabilir.

Yapılması gereken bir başka şey de diziyi önbellek dostu parçalara ayırmak ve sonuçları birleştirmek. İki seviye kullanılmalıdır: örneğin ilk önce L1 için 4 KB, sonra L2 için 64 KB.

Bu oldukça önbellek dostu olmalıdır, çünkü kova sıralama önbelleğin dışına çıkmaz ve son birleştirme bellekte sırayla yürür.

Bugünlerde hesaplama, hafıza erişiminden çok daha ucuz. Bununla birlikte, çok sayıda öğemiz var, bu yüzden dilsiz önbellek uyumlu sıralama, düşük karmaşıklığa sahip önbellek uyumlu olmayan bir sürümden daha yavaş olduğunda dizi boyutunun hangisi olduğunu söylemek zor.

Ancak Windows'ta (VC ++) yaptığım için yukarıdakilerin bir uygulamasını sunmayacağım.


2

İşte doğrusal tarama kovası sıralama uygulaması. Radix sıralaması dışındaki tüm tek iş parçacıklı uygulamalardan daha hızlı olduğunu düşünüyorum. Eğer cdf'yi yeterince doğru tahmin edersem (web'de bulduğum değerlerin doğrusal enterpolasyonunu kullanıyorum) ve aşırı taramaya neden olacak herhangi bir hata yapmadıysam, beklenen beklenen çalışma süresi olmalıydı:

#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
Bugün eve sonra geldiğimde bunu deneyeceğim. Bu arada, kodunuzun çok çirkin olduğunu söyleyebilir miyim? :-D
static_rtti

3.071s! Tek iş parçacıklı bir çözüm için fena değil!
static_rtti

2

Bilmiyorum, neden önceki yazımı düzenleyemiyorum, bu yüzden işte 0,2 saniye daha yeni sürüm (CPU süresinde (kullanıcı) yaklaşık 1,5 sn daha hızlı). Bu çözüm 2 programa sahiptir, ilk önce kepçe sıralaması için normal dağılım için kuantemleri önceden hesaplar ve tabloyu saklar, t [çift * ölçek] = kepçe indeksi, burada ölçeklendirme işlemi çift yapmayı mümkün kılan bazı rasgele sayıdır. O zaman ana program bu verileri çiftleri doğru kepçeye koymak için kullanabilir. Bir sakıncası vardır, eğer veriler gauss değilse, doğru çalışmayacaktır (ve aynı zamanda normal dağılım için yanlış çalışma şansı neredeyse yoktur), ancak özel durum için değişiklik yapmak kolay ve hızlıdır (sadece kovaların sayısı kontrol edilir ve std'ye düşer. ::çeşit()).

Derleme: g ++ => http://pastebin.com/WG7pZEzH yardımcı programı

g ++ -std = c ++ 0x -O3-march = yerel -pthread => http://pastebin.com/T3yzViZP ana sıralama programı


1.621s! Sanırım lider sensin, ama hızlıca bütün bu cevapları
izliyorum

2

İşte başka bir sıralı çözüm. Bu, öğelerin normal dağılışı olduğu gerçeğini kullanır ve bence bu fikir genel olarak doğrusal zamana göre sıralama yapmak için uygulanabilir.

Algoritma şöyle:

  • Yaklaşık CDF ( phi()uygulamadaki fonksiyonuna bakınız )
  • Tüm elemanlar için, sıralanmış dizideki yaklaşık konumu hesaplayın: size * phi(x)
  • Öğeleri yeni bir diziye son konumlarına yaklaştırın
    • Uygulamamda, hedef dizinin içinde bazı boşluklar var, bu yüzden eklerken çok fazla öğe kaydırmam gerekmiyor.
  • Son elemanları sıralamak için insansort kullanın (son pozisyona uzaklık bir sabitten küçükse, insansort doğrusaldır).

Ne yazık ki, gizli sabit oldukça büyüktür ve bu çözüm, radix sıralama algoritmasından iki kat daha yavaştır.


1
2.470s! Çok güzel fikirler. Fikirler
ilginçse

1
Bu benimkiyle aynı, ancak phi hesaplamalarını birlikte gruplandırın ve daha iyi önbellek performansı için birlikte kayıyor, değil mi?
jonderry

@ jonderry: Çözümünüzü yükselttim, şimdi ne yaptığını anladım. Fikrini çalmak istememiştim. Uygulamanızı (resmi olmayan) testlerime
Alexandru

2

Intel'in Dişli Yapı Taşlarını kullanan kişisel favorim çoktan gönderildi, ancak JDK 7 ve yeni çatal / join API'sini kullanan kaba bir paralel çözüm:

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;
            }
        }
    }    
}

Önemli sorumluluk reddi : Çatal / birleştirme için hızlı sıralama uyarlamasını şu adresten aldım: https://github.com/pmbauer/parallel/tree/master/src/main/java/pmbauer/parallel

Bunu çalıştırmak için bir JDK 7 beta sürümüne ihtiyacınız var (http://jdk7.java.net/download.html).

2.93Ghz Dört çekirdekli i7'mde (OS X):

Python referansı

time python sort.py 50000000
sorting...

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

Java JDK 7 çatal / katılmak

time java ForkJoinQuicksortTask 50000000
Starting parallel computation

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

Paralel okuma ve baytları iki katına dönüştürme konusunda da bazı deneyler yapmaya çalıştım, ancak orada hiçbir fark görmedim.

Güncelleme:

Herhangi biri verinin paralel yüklenmesini denemek isterse, paralel yükleme sürümü aşağıdadır. Teoride, eğer IO cihazınız yeterli paralel kapasiteye sahipse (SSD'ler genellikle yapar), bu durum biraz daha hızlı ilerleyebilir. Ayrıca baytlardan Çiftler oluşturma konusunda ek yükler de vardır, böylece potansiyel olarak paralel olarak daha hızlı gidebilir. Sistemlerimde (Ubuntu 10.10 / Nehalem Dörtlü / Intel X25M SSD ve OS X 10.6 / i7 Dörtlü / Samsung SSD) Gerçek bir fark görmedim.

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));
       }
   }
}

Update2:

Kodu, 12 çekirdek makinemizden birinde, sabit miktarda çekirdek ayarlamak için küçük bir değişiklikle çalıştırdım. Bu, aşağıdaki sonuçları verdi:

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

Bu sistemde 1m2.994s süren Python sürümünü ve 1.925s süren Zjarek'in C ++ sürümünü de denedim (bir nedenden dolayı Zjarek'in C ++ sürümü static_rtti bilgisayarında nispeten daha hızlı çalışıyor gibi görünüyor).

Ayrıca, dosya boyutunu 100.000.000 çift olarak ikiye katlarsam ne olduğunu da denedim:

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

Bu durumda, Zjarek'in C ++ sürümü 3.968'i aldı. Python burada çok uzun sürdü.

150.000.000 çift:

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

Bu durumda, Zjarek'in C ++ sürümü 6.044 idi. Python'u denemedim bile.

C ++ sürümü, Java'nın biraz sallandığı sonuçlarıyla çok uyumlu. İlk önce sorun büyüdüğünde biraz daha verimli olur, ancak daha sonra tekrar daha az verimli olur.


1
Bu kod benim için çifte değerleri doğru şekilde ayrıştırmaz. Java 7, dosyadaki değerleri doğru şekilde ayrıştırmak zorunda mı?
jonderry

1
Ah, aptal ben. Yerel olarak IO kodunu birden fazla satırdan bir satırına yeniden bağladıktan sonra endianness ayarını unuttum. Java 7'ye ayrı olarak çatal / birleştirme yapmazsanız normalde Java 7'ye ihtiyaç duyulur.
arjan

Makinemde 3.411. Fena değil, ama koumes21'in java çözümünden daha yavaş :)
static_rtti

1
Sistemimde nispi farklılıkların ne olduğunu görmek için koumes21'in çözümünü burada çok yerel olarak deneyeceğim. Her neyse, çok daha akıllıca bir çözüm olduğundan, koumes21'den 'kaybedilmekten' utanmak yok. Bu sadece bir çatal / katılmak havuza atılmış neredeyse standart bir hızlı sıralama;)
arjan

1

Geleneksel pthreads kullanan bir versiyon. Birleşme kodu Guvante'nin cevabından kopyalandı. İle derleyin 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;
}  

Dizüstü bilgisayarımda aşağıdaki sonuçları alıyorum:

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

1

İşte bilinen dağıtımdan gerçekten faydalanmaya çalışan sıralı bir C99 uygulaması. Temel olarak dağıtım bilgisini kullanarak tek bir kova sıralama turu yapar, daha sonra her bir kova üzerinde birkaç hızlı hızlı tur kovanın sınırları içinde eşit bir dağılım varsayar ve son olarak verileri tekrar orijinal tampona kopyalamak için değiştirilmiş bir seçim sıralama yapar. Hızlı bağlantı noktası bölünmüş noktaları hafızaya alır, bu nedenle seçim sırasının sadece küçük parçalarda çalışması gerekir. Ve tüm bu karmaşıklığa rağmen (çünkü?), Gerçekten hızlı bile değil.

Hızlı değerlendirme yapmak için değerler birkaç noktadan örneklenir ve daha sonra sadece doğrusal enterpolasyonda kullanılır. Yaklaşım kesinlikle monotonik olduğu sürece Φ tam olarak değerlendirilip değerlendirilmediği önemli değildir.

Kutu boyutları, bir kutu taşması ihtimalinin önemsiz olduğu şekilde seçilmiştir. Daha doğrusu, mevcut parametrelerle, 50000000 elemanlarından oluşan bir veri kümesinin kutu taşmasına neden olma olasılığı 3.65e-09'dır. (Bu kullanarak hesaplanabilir hayatta kalma fonksiyonunu arasında Poisson dağılımına .)

Derlemek için lütfen kullan

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

Diğer çözümlerden çok daha fazla hesaplama yapıldığından, bu derleyici bayraklarının en azından makul şekilde hızlı olması için gereklidir. Olmadan -msse3dönüşümler doubleiçin intgerçekten yavaş olur. Mimariniz SSE3'ü desteklemiyorsa, bu dönüşümler lrint()işlev kullanılarak da yapılabilir .

Kod oldukça çirkin - bu "makul bir şekilde okunabilir" olmanın şartını yerine getirip getirmediğinden emin değil ...

#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.098s! Derlemek için -lm eklemek zorunda kaldım (erf için).
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;
}

Bu, her bir öğeyi uygun bir şekilde bir çöp kutusuna yerleştirmek için erf () işlevini kullanır, ardından her çöp kutusunu sıralar. Diziyi tamamen yerinde tutar.

İlk geçiş: docensus () her bir kutudaki elementlerin sayısını sayar.

İkinci geçiş: partition (), her öğeyi uygun bölmesine yerleştirerek diziye izin verir

Üçüncü geçiş: sortbins () her kutuda bir qsort gerçekleştirir.

Bir tür naif ve her değer için iki kez pahalı erf () işlevini çağırıyor. Birinci ve üçüncü geçişler potansiyel olarak paralelleştirilebilir. İkincisi oldukça seridir ve büyük olasılıkla rasgele bellek erişim biçimleriyle yavaşlar. CPU gücünün bellek hızı oranına bağlı olarak her bir çiftin kutu numarasını önbelleğe almak faydalı olabilir.

Bu program kullanılacak çöp tenekelerinin sayısını seçmenize izin verir. Sadece komut satırına ikinci bir numara ekleyin. Ben gcc -O3 ile derledim, ancak makinem çok zayıf, size herhangi bir iyi performans rakamı söyleyemem.

Düzenleme: Poof! C programım sihirli bir şekilde std :: sort kullanarak bir C ++ programına dönüştü!


Daha hızlı bir stdnormal_cdf için phi kullanabilirsiniz .
Alexandru

Yaklaşık kaç tane kutu koymalıyım?
static_rtti

@Alexandru: Normcdf'ye parça parça doğrusal bir yaklaşım ekledim ve sadece yaklaşık% 5 hız kazandım.
frud

@static_rtti: Hiç koymak zorunda değilsiniz. Varsayılan olarak, kod bölme sayısını seçer, böylece ortalama bin boyutu 10/11 / 128kb'dir. Çok az sayıda kutu ve bölümlemenin avantajlarından faydalanamazsınız. Çok fazla ve bölme aşaması, önbellek taşması nedeniyle yavaşlıyor.
frud

10.6s! Kutu sayısıyla biraz oynamaya çalıştım ve en iyi sonuçları 5000 ile aldım (varsayılan olarak 3356'nın biraz üzerinde). Çözümünüz için çok daha iyi bir performans görmem gerektiğini söylemeliydim ... Belki de daha hızlı std :: type C ++ çözümlerinin yerine qsort kullanıyor olmanız gerçektir.
static_rtti

1

Michael Herf ( Radix Tricks ) tarafından yapılan radix sort uygulamasına bir göz atın . Makine üzerinde sıralama, std::sortilk cevabımdaki algoritmaya göre 5 kat daha hızlıydı . Sıralama işlevi adıdır 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;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.