Contest: il modo più veloce per ordinare una vasta gamma di dati distribuiti gaussiani


71

Seguendo l'interesse per questa domanda , ho pensato che sarebbe stato interessante rendere le risposte un po 'più obiettive e quantitative proponendo un concorso.

L'idea è semplice: ho generato un file binario contenente 50 milioni di doppi distribuiti gaussiani (media: 0, stdev 1). L'obiettivo è quello di creare un programma che li ordinerà in memoria il più velocemente possibile. Un'implementazione di riferimento molto semplice in python richiede 1m4s per il completamento. Quanto in basso possiamo andare?

Le regole sono le seguenti: rispondere con un programma che apre il file "gaussian.dat" e ordina i numeri in memoria (non è necessario emetterli) e le istruzioni per la creazione e l'esecuzione del programma. Il programma deve essere in grado di funzionare sulla mia macchina Arch Linux (il che significa che è possibile utilizzare qualsiasi linguaggio di programmazione o libreria facilmente installabile su questo sistema).

Il programma deve essere ragionevolmente leggibile, in modo che io possa essere sicuro di avviarlo (nessuna soluzione solo per assemblatore, per favore!).

Eseguirò le risposte sulla mia macchina (quad core, 4 Gigabyte di RAM). La soluzione più veloce otterrà la risposta accettata e un premio di 100 punti :)

Il programma utilizzato per generare i numeri:

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

La semplice implementazione di riferimento:

#!/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: solo 4 GB di RAM, mi dispiace

EDIT # 2: Nota che il punto del concorso è vedere se possiamo usare le informazioni precedenti sui dati . non dovrebbe essere una partita pissing tra diverse implementazioni del linguaggio di programmazione!


1
Prendi ogni valore e spostalo direttamente nella posizione "prevista", ripeti per il valore spostato. Non sono sicuro di come risolvere un problema di coppia con quello. Al termine, eseguire l'ordinamento delle bolle fino al completamento (un paio di passaggi dovrebbero fare).

1
Stasera pubblicherò una soluzione di ordinamento bucket se non è stata chiusa entro allora :)

1
@static_rtti - come utente pesante di CG, questo è esattamente il genere di cose a cui "noi" piace hackerare su CG.SE. A qualsiasi mod di lettura, sposta questo su CG, non chiuderlo.
Arrdem,

1
Benvenuto in CodeGolf.SE! Ho cancellato gran parte del commento dall'originale SO riguardo a dove questo appartiene o meno, e ri-taggato per essere più vicino al mainstream CodeGolf.SE.
dmckee,

2
L'unico problema qui è che cerchiamo criteri vincenti oggettivi e "più veloce" introduce dipendenze della piattaforma ... un algoritmo O (n ^ {1.2}) implementato sulla macchina virtuale cpython batte un O (n ^ {1.3} ) algoritmo con una costante simile implementata in c? In genere suggerisco alcune discussioni sulle caratteristiche prestazionali di ciascuna soluzione, in quanto ciò può aiutare le persone a giudicare ciò che sta accadendo.
dmckee,

Risposte:


13

Ecco una soluzione in C ++ che prima suddivide i numeri in bucket con lo stesso numero previsto di elementi, quindi ordina ogni bucket separatamente. Precompila una tabella della funzione di distribuzione cumulativa basata su alcune formule di Wikipedia e quindi interpola i valori di questa tabella per ottenere un'approssimazione veloce.

Numerosi passaggi vengono eseguiti in più thread per utilizzare i quattro core.

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

Per compilarlo ed eseguirlo, utilizzare questo comando:

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

EDIT: Tutti i bucket sono ora posizionati nello stesso array per rimuovere la necessità di copiare nuovamente i bucket nell'array. Inoltre è stata ridotta la dimensione della tabella con valori precalcolati, poiché i valori sono sufficientemente precisi. Tuttavia, se cambio il numero di bucket oltre 256, l'esecuzione del programma richiede più tempo rispetto a quel numero di bucket.

EDIT: lo stesso algoritmo, diverso linguaggio di programmazione. Ho usato C ++ invece di Java e il tempo di esecuzione è stato ridotto da ~ 3.2s a ~ 2.35s sulla mia macchina. Il numero ottimale di bucket è ancora intorno a 256 (di nuovo, sul mio computer).

A proposito, Tbb è davvero fantastico.

EDIT: Sono stato ispirato dalla grande soluzione di Alexandru e ho sostituito lo std :: sort nell'ultima fase con una versione modificata del suo ordinamento radix. Ho usato un metodo diverso per gestire i numeri positivi / negativi, anche se ha bisogno di più passaggi attraverso l'array. Ho anche deciso di ordinare esattamente l'array e rimuovere l'ordinamento di inserzione. In seguito passerò un po 'di tempo a testare come questi cambiamenti influenzano le prestazioni e possibilmente ripristinarli. Tuttavia, usando l'ordinamento radix, il tempo è passato da ~ 2,35 a ~ 1,63 secondi.


Bello. Ho 3.055 sul mio. Il più basso in cui sono stato in grado di ottenere il mio è stato 6.3. Sto cercando tra i tuoi per migliorare le statistiche. Perché hai scelto 256 come numero di bucket? Ho provato 128 e 512, ma 256 ha funzionato al meglio.
Scott,

Perché ho scelto 256 come numero di bucket? Ho provato 128 e 512, ma 256 ha funzionato al meglio. :) L'ho trovato empiricamente e non sono sicuro del motivo per cui l'aumento del numero di bucket rallenta l'algoritmo: l'allocazione della memoria non dovrebbe richiedere così tanto tempo. Forse qualcosa legato alla dimensione della cache?
K21

2.725s sulla mia macchina. Abbastanza bello per una soluzione Java, tenendo conto del tempo di caricamento della JVM.
static_rtti,

2
Ho scambiato il tuo codice per usare i pacchetti nio, secondo la mia e la soluzione di Arjan (ha usato la sua sintassi, poiché era più pulita della mia) ed è stato in grado di ottenerlo .3 secondi più veloce. Ho un SSD, mi chiedo quali potrebbero essere le implicazioni in caso contrario. Elimina anche alcuni dei tuoi piccoli colpi. Le sezioni modificate sono qui.
Scott,

3
Questa è la soluzione parallela più veloce nei miei test (16core cpu). 1.22 secondi dal 1.94s secondo posto.
Alexandru,

13

Senza diventare intelligenti, solo per fornire un selezionatore ingenuo molto più veloce, eccone uno in C che dovrebbe essere praticamente equivalente al tuo Python:

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

Compilato con gcc -O3, sulla mia macchina questo richiede più di un minuto in meno rispetto al Python: circa 11 secondi rispetto agli 87 secondi.


1
Ho preso 10.086s sulla mia macchina, il che ti rende l'attuale leader! Ma sono abbastanza sicuro che possiamo fare di meglio :)

1
Potresti provare a rimuovere il secondo operatore ternario e semplicemente a restituire 1 per quel caso perché i doppi casuali non sono uguali tra loro in questa quantità di dati.
Codismo,

@Codismo: aggiungerei che non ci interessa scambiare posizioni di dati equivalenti, quindi anche se potessimo ottenere valori equivalenti sarebbe una semplificazione appropriata.

10

Ho partizionato in segmenti in base alla deviazione standard che dovrebbe essere suddivisa al meglio in 4 °. Modifica: riscritto sulla partizione in base al valore x in http://it.wikipedia.org/wiki/Error_function#Table_of_values

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

Ho provato a usare secchi più piccoli, ma sembrava avere poco effetto una volta 2 * oltre il numero di core disponibili. Senza raccolte parallele, ci vorrebbero 37 secondi sulla mia scatola e 24 con le raccolte parallele. Se si esegue il partizionamento tramite distribuzione, non è possibile utilizzare solo un array, quindi c'è un po 'più di sovraccarico. Non sono chiaro quando un valore verrebbe inscatolato / unbox in scala.

Sto usando la scala 2.9, per la raccolta parallela. Puoi semplicemente scaricare la distribuzione di tar.gz.

Per compilare: scalac SortFile.scala (l'ho appena copiato direttamente nella cartella scala / bin.

Per eseguire: JAVA_OPTS = "- Xmx4096M" ./scala SortFile (l'ho eseguito con 2 concerti di ram e ho ottenuto all'incirca allo stesso tempo)

Modifica: rimosso allocateDirect, più lento della semplice allocazione. Rimosso il priming della dimensione iniziale per i buffer di array. In realtà ha fatto leggere tutti i valori 50000000. Riscritto per evitare problemi con l'auto boxing (ancora più lento dell'ingenuo 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.185s! Bello per una soluzione di scala, immagino ... Inoltre, bravo per aver fornito la prima soluzione che utilizza effettivamente la distribuzione gaussiana in qualche modo!

1
Miravo solo a competere con la soluzione c #. Non immaginavo di battere c / c ++. Inoltre ... si sta comportando in modo molto diverso per te che per me. Sto usando openJDK da parte mia ed è molto più lento. Mi chiedo se l'aggiunta di più partizioni possa aiutare nel tuo ambiente.
Scott,

9

Basta inserirlo in un file cs e compilarlo con csc in teoria: (Richiede mono)

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

Posso eseguire le vostre soluzioni con Mono? Come dovrei farlo?

Non hai usato Mono, non ci ho pensato, dovresti essere in grado di compilare l'F # e quindi eseguirlo.

1
Aggiornato per utilizzare quattro thread per migliorare le prestazioni. Ora mi dà 6 sec. Si noti che questo potrebbe essere notevolmente migliorato (probabilmente 5 secondi) se si utilizza solo un array di riserva ed si evita di inizializzare una tonnellata di memoria a zero, cosa che viene eseguita dal CLR, poiché tutto viene scritto almeno una volta.

1
9.598s sulla mia macchina! Sei il leader attuale :)

1
Mia madre mi ha detto di stare lontano dai ragazzi con Mono!

8

Poiché conosci la distribuzione, puoi utilizzare un ordinamento O (N) di indicizzazione diretta. (Se ti stai chiedendo di cosa si tratta, supponi di avere un mazzo di 52 carte e vuoi ordinarlo. Basta avere 52 cestini e lanciare ogni carta nel proprio cestino.)

Hai 5e7 doppie. Allocare un array di risultati R di 5e7 doppie. Prendi ogni numero xe ottieni i = phi(x) * 5e7. Fondamentalmente R[i] = x. Avere un modo per gestire le collisioni, ad esempio spostando il numero con cui potrebbe scontrarsi (come nella semplice codifica hash). In alternativa, potresti ingrandire R un paio di volte più grande, riempito con un valore vuoto unico . Alla fine, devi solo spazzare via gli elementi di R.

phiè solo la funzione di distribuzione cumulativa gaussiana. Converte un numero distribuito gaussiano tra +/- infinito in un numero distribuito uniforme tra 0 e 1. Un modo semplice per calcolarlo è con la ricerca e l'interpolazione della tabella.


3
Fai attenzione: conosci la distribuzione approssimativa, non la distribuzione esatta. Sai che i dati sono stati generati usando una legge gaussiana, ma dato che sono limitati, non seguono esattamente una gaussiana.

@static_rtti: in questo caso l'approssimazione necessaria di phi creerebbe una seccatura maggiore di qualsiasi irregolarità nel set di dati IMO.

1
@static_rtti: non deve essere esatto. Deve solo distribuire i dati in modo che siano approssimativamente uniformi, quindi non si accumulano troppo in alcuni punti.

Supponiamo di avere 5e7 doppie. Perché non solo rendere ogni voce in R un vettore di, diciamo, 5e6 vettori di doppio. Quindi, push_back ogni doppio nel suo vettore appropriato. Ordina i vettori e il gioco è fatto. Questo dovrebbe richiedere tempo lineare nella dimensione dell'input.
Neil G

In realtà, vedo che mdkess ha già trovato quella soluzione.
Neil G,

8

Ecco un'altra soluzione sequenziale:

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

Dubito che superi la soluzione multi-thread, ma i tempi sul mio laptop i7 sono (stdsort è la soluzione C ++ fornita in un'altra risposta):

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

Si noti che questa soluzione presenta una complessità temporale lineare (poiché utilizza la rappresentazione speciale dei doppi).

EDIT : corretto l'ordine degli elementi in aumento.

EDIT : velocità migliorata di quasi mezzo secondo.

EDIT : velocità migliorata di altri 0,7 secondi. Ha reso l'algoritmo più intuitivo per la cache.

EDIT : velocità migliorata di altri 1 secondo. Dato che ci sono solo 50.000.000 di elementi, posso ordinare parzialmente la mantissa e usare insert sort (che è compatibile con la cache) per riparare gli elementi fuori posto. Questa idea rimuove circa due iterazioni dall'ultimo ciclo di ordinamento radix.

EDIT : 0,16 secondi in meno. Il primo std :: reverse può essere eliminato se l'ordine di classificazione è invertito.


Ora sta diventando interessante! Che tipo di algoritmo di ordinamento è?
static_rtti,

2
Ordinamento radix delle cifre meno significative . Puoi ordinare la mantissa, quindi l'esponente, quindi il segno. L'algoritmo qui presentato porta questa idea un ulteriore passo avanti. Può essere parallelizzato usando un'idea di partizionamento fornita in una risposta diversa.
Alexandru,

Abbastanza veloce per una soluzione a thread singolo: 2.552s! Pensi di poter cambiare la tua soluzione per sfruttare il fatto che i dati sono normalmente distribuiti? Probabilmente potresti fare di meglio delle migliori soluzioni multi-thread attuali.
static_rtti,

1
@static_rtti: Vedo che Damasco Steel ha già pubblicato una versione multithread di questa implementazione. Ho migliorato il comportamento della cache di questo algoritmo, quindi ora dovresti ottenere tempi migliori. Si prega di testare questa nuova versione.
Alexandru,

2
1.459 nei miei test sui latest. Mentre questa soluzione non è il vincitore secondo le mie regole, merita davvero grandi complimenti. Congratulazioni!
static_rtti,

6

Prendere la soluzione di Christian Ammer e parallelizzarla con i Threaded Building Blocks di Intel

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

Se hai accesso alla libreria Intel Performance Primitives (IPP), puoi utilizzare il suo ordinamento radix. Sostituisci e basta

#include <tbb/parallel_sort.h>

con

#include "ipps.h"

e

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

con

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

Sul mio laptop dual core, i tempi sono

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 sembra abbastanza bello e facile da usare!

2
TBB è assurdamente fantastico. È esattamente il giusto livello di astrazione per il lavoro algoritmico.
drxzcl,

5

Che ne dici di un'implementazione di quicksort parallelo che sceglie i suoi valori pivot in base alle statistiche della distribuzione, garantendo così partizioni di uguali dimensioni? Il primo perno sarebbe alla media (zero in questo caso), la coppia successiva sarebbe al 25 ° e al 75 ° percentile (+/- -0,67449 deviazioni standard) e così via, con ciascuna partizione che dimezza il set di dati rimanente di più o meno perfettamente.


Questo è effettivamente quello che ho fatto sul mio .. ovviamente hai ricevuto questo post prima che potessi finire il mio writeup.

5

Molto brutto (perché usare le matrici quando posso usare le variabili che finiscono con i numeri), ma codice veloce (il mio primo tentativo di std :: thread), tutto il tempo (tempo reale) sul mio sistema 1,8 s (rispetto a std :: sort () 4,8 s), compilare con g ++ -std = c ++ 0x -O3 -march = native -pthread Basta passare i dati tramite stdin (funziona solo per 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);
}

// Modifica modificata per leggere il file gaussian.dat.


Potresti cambiarlo per leggere gaussian.dat, come fanno le precedenti soluzioni C ++?

Ci proverò più tardi quando torno a casa.
static_rtti,

Ottima soluzione, tu sei l'attuale leader (1.949s)! E buon uso della distribuzione gaussiana :)
static_rtti

4

Una soluzione C ++ che utilizza std::sort(eventualmente più veloce di qsort, per quanto riguarda le prestazioni di qsort vs std :: sort )

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

Non posso dire gaussian.datcon certezza quanto tempo impieghi perché ho solo 1 GB sulla mia macchina e con il codice Python dato sono riuscito a creare un file con solo 25 milioni di doppi (senza ottenere un errore di memoria). Ma sono molto interessato per quanto tempo dura l'algoritmo std :: sort.


6.425s! Come previsto, C ++ brilla :)

@static_rtti: ho provato l' algoritmo Swimsons Timsort (come suggerito da Matthieu M. nella tua prima domanda ). Ho dovuto apportare alcune modifiche al sort.hfile per compilarlo con C ++. Era circa due volte più lento di std::sort. Non so perché, forse a causa delle ottimizzazioni del compilatore?
Christian Ammer,

4

Ecco un mix dell'ordinamento radicale di Alexandru con il pivot intelligente filettato di Zjarek. Compilalo con

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

È possibile modificare la dimensione della radice definendo STEP (ad es. Aggiungere -DSTEP = 11). Ho trovato il meglio per il mio laptop è 8 (impostazione predefinita).

Per impostazione predefinita, divide il problema in 4 pezzi ed esegue quello su più thread. Puoi cambiarlo passando un parametro di profondità alla riga di comando. Quindi, se hai due core, eseguilo come

sorter_gaussian_radix 50000000 1

e se hai 16 core

sorter_gaussian_radix 50000000 4

La profondità massima in questo momento è 6 (64 thread). Se si mettono troppi livelli, si rallenta il codice.

Una cosa che ho anche provato è stato l'ordinamento radix dalla libreria Intel Performance Primitives (IPP). L'implementazione di Alexandru risolve sensibilmente l'IPP, con un IPP più lento del 30% circa. Questa variante è inclusa anche qui (commentata).

#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 : ho implementato i miglioramenti della cache di Alexandru e questo si è ridotto del 30% delle volte sul mio computer.

EDIT : questo implementa un tipo ricorsivo, quindi dovrebbe funzionare bene sulla macchina a 16 core di Alexandru. Utilizza anche l'ultimo miglioramento di Alexandru e rimuove uno degli inversi. Per me, questo ha dato un miglioramento del 20%.

EDIT : risolto un bug di segno che causava inefficienza quando c'erano più di 2 core.

EDIT : rimosso lambda, quindi verrà compilato con le versioni precedenti di gcc. Include la variazione del codice IPP commentata. Ho anche corretto la documentazione per l'esecuzione su 16 core. Per quanto ne so, questa è l'implementazione più rapida.

EDIT : risolto un bug quando STEP non era 8. Aumentato il numero massimo di thread a 64. Aggiunte alcune informazioni di temporizzazione.


Bello. L'ordinamento Radix è molto ostile alla cache. Vedi se riesci a ottenere risultati migliori cambiando step(11 era ottimale sul mio laptop).
Alexandru,

Hai un bug: int cnt[mask]dovrebbe essere int cnt[mask + 1]. Per risultati migliori utilizzare un valore fisso int cnt[1 << 16].
Alexandru,

Proverò tutte queste soluzioni più tardi oggi quando torno a casa.
static_rtti

1.534s !!! Penso che abbiamo un leader :-D
static_rtti

@static_rtti: potresti riprovare? È diventato significativamente più veloce dell'ultima volta che l'hai provato. Sulla mia macchina, è sostanzialmente più veloce di qualsiasi altra soluzione.
Damasco in acciaio

2

Immagino che questo dipenda davvero da cosa vuoi fare. Se vuoi ordinare un gruppo di gaussiani, allora questo non ti aiuterà. Ma se vuoi un gruppo di gaussiani ordinati, questo lo farà. Anche se questo non risolve il problema, penso che sarà interessante confrontare le routine di ordinamento effettive.

Se vuoi qualcosa per essere veloce, fai di meno.

Invece di generare un gruppo di campioni casuali dalla distribuzione normale e quindi l'ordinamento, è possibile generare un gruppo di campioni dalla distribuzione normale in ordine ordinato.

È possibile utilizzare la soluzione qui per generare n numeri casuali uniformi in ordine ordinato. Quindi è possibile utilizzare il cdf inverso (scipy.stats.norm.ppf) della distribuzione normale per trasformare i numeri casuali uniformi in numeri dalla distribuzione normale tramite il campionamento della trasformazione inversa .

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

Se vuoi rendere le tue mani più sporche, immagino che potresti essere in grado di accelerare i numerosi calcoli inversi di cdf usando un qualche tipo di metodo iterativo e usando il risultato precedente come ipotesi iniziale. Poiché le ipotesi saranno molto vicine, probabilmente una singola iterazione ti darà una grande precisione.


2
Bella risposta, ma sarebbe barare :) L'idea della mia domanda è che mentre gli algoritmi di ordinamento hanno ricevuto un'enorme attenzione, non c'è quasi letteratura sull'uso di conoscenze precedenti sui dati per l'ordinamento, anche se i pochi documenti che hanno risolto il problema hanno riportato buoni guadagni. Quindi vediamo cosa è possibile!

2

Prova questo cambiamento della soluzione di Guvante con questo Main (), inizia l'ordinamento non appena viene eseguita la lettura di 1/4 IO, è più veloce nel mio test:

    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. Leggermente più veloce :)

2

Poiché conosci la distribuzione, la mia idea sarebbe quella di creare k bucket, ciascuno con lo stesso numero previsto di elementi (poiché conosci la distribuzione, puoi calcolarlo). Quindi in O (n) tempo, spazzare l'array e mettere gli elementi nei loro secchi.

Quindi ordinare contemporaneamente i secchi. Supponiamo di avere k bucket e n elementi. Un secchio richiederà (n / k) lg (n / k) il tempo di ordinare. Supponiamo ora di avere processori p che puoi usare. Poiché i bucket possono essere ordinati in modo indipendente, è necessario un moltiplicatore di ceil (k / p). Questo dà un tempo di esecuzione finale di n + ceil (k / p) * (n / k) lg (n / k), che dovrebbe essere molto più veloce di n lg n se scegli bene k.


Penso che questa sia la soluzione migliore.
Neil G,

Non conosci esattamente il numero di elementi che finiranno in un secchio, quindi la matematica è in realtà sbagliata. Detto questo, questa è una buona risposta, penso.
poulejapon,

@pouejapon: hai ragione.
Neil G,

Questa risposta sembra davvero carina. Il problema è che non è molto veloce. L'ho implementato in C99 (vedi la mia risposta), e certamente batte facilmente std::sort(), ma è molto più lento della soluzione di radixsort di Alexandru.
Sven Marnach,

2

Un'idea di ottimizzazione di basso livello è quella di inserire due doppi in un registro SSE, in modo che ogni thread funzioni con due elementi alla volta. Questo potrebbe essere complicato da fare per alcuni algoritmi.

Un'altra cosa da fare è ordinare l'array in blocchi compatibili con la cache, quindi unire i risultati. Dovrebbero essere utilizzati due livelli: ad esempio i primi 4 KB per L1 e 64 KB per L2.

Questo dovrebbe essere molto adatto alla cache, poiché l'ordinamento bucket non andrà fuori dalla cache e l'unione finale percorrerà la memoria in sequenza.

Oggi il calcolo è molto più economico degli accessi alla memoria. Tuttavia, abbiamo un gran numero di elementi, quindi è difficile stabilire quale sia la dimensione dell'array quando un ordinamento inattaccabile dalla cache è più lento di una versione a bassa complessità non sensibile alla cache.

Ma non fornirò un'implementazione di quanto sopra poiché lo farei in Windows (VC ++).


2

Ecco un'implementazione dell'ordinamento del bucket di scansione lineare. Penso che sia più veloce di tutte le attuali implementazioni a thread singolo ad eccezione dell'ordinamento radix. Dovrebbe avere un tempo di esecuzione previsto lineare se sto valutando il cdf in modo sufficientemente accurato (sto usando l'interpolazione lineare dei valori che ho trovato sul web) e non ho fatto errori che causerebbero un'eccessiva scansione:

#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
Ci proverò più tardi oggi quando torno a casa. Nel frattempo, posso dire che il tuo codice è molto brutto? :-D
static_rtti

3.071s! Non male per una soluzione a thread singolo!
static_rtti,

2

Non lo so, perché non riesco a modificare il mio post precedente, quindi ecco la nuova versione, 0,2 secondi più veloce (ma circa 1,5 s più veloce nel tempo della CPU (utente)). Questa soluzione ha 2 programmi, prima calcola i quantili per la distribuzione normale per l'ordinamento bucket e la memorizza in tabella, t [double * scale] = indice bucket, dove scale è un numero arbitrario che rende possibile raddoppiare il casting. Quindi il programma principale può utilizzare questi dati per mettere i doppi nel bucket corretto. Ha uno svantaggio, se i dati non sono gaussiani non funzioneranno correttamente (e inoltre ci sono quasi zero possibilità di funzionare in modo errato per la distribuzione normale), ma la modifica per casi speciali è facile e veloce (solo il numero di controlli di bucket e cadendo su standard ::ordinare()).

Compilazione: g ++ => http://pastebin.com/WG7pZEzH programma di supporto

g ++ -std = c ++ 0x -O3 -march = native -pthread => http://pastebin.com/T3yzViZP programma di ordinamento principale


1.621s! Penso che tu sia il leader, ma sto rapidamente perdendo la strada con tutte queste risposte :)
static_rtti

2

Ecco un'altra soluzione sequenziale. Questo usa il fatto che gli elementi sono distribuiti normalmente e penso che l'idea sia generalmente applicabile per ottenere l'ordinamento vicino al tempo lineare.

L'algoritmo è così:

  • CDF approssimativo (vedi phi()funzione nell'implementazione)
  • Per tutti gli elementi, calcolare la posizione approssimativa nella matrice ordinata: size * phi(x)
  • Metti gli elementi in un nuovo array vicino alla loro posizione finale
    • Nella mia implementazione la matrice di destinazione ha alcune lacune, quindi non devo spostare troppi elementi durante l'inserimento.
  • Utilizzare insertsort per ordinare gli elementi finali (insertsort è lineare se la distanza dalla posizione finale è inferiore a una costante).

Sfortunatamente, la costante nascosta è piuttosto grande e questa soluzione è due volte più lenta dell'algoritmo di ordinamento radix.


1
2.470s! Idee molto carine. Non importa che la soluzione non sia la più veloce se le idee sono interessanti :)
static_rtti

1
È uguale al mio, ma raggruppando i calcoli del phi e i turni insieme per migliorare le prestazioni della cache, giusto?
jonderry,

@jonderry: ho votato a favore della tua soluzione, ora che capisco cosa fa. Non intendevo rubare la tua idea. Ho incluso la tua implementazione nella mia serie (non ufficiale) di test
Alexandru,

2

Il mio preferito personale con i Threaded Building Blocks di Intel è già stato pubblicato, ma ecco una soluzione parallela grezza che utilizza JDK 7 e la sua nuova API fork / join:

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

Importante dichiarazione di non responsabilità : ho preso l'adattamento dell'ordinamento rapido per fork / join da: https://github.com/pmbauer/parallel/tree/master/src/main/java/pmbauer/parallel

Per eseguire questo è necessario un beta build di JDK 7 (http://jdk7.java.net/download.html).

Sul mio Quad Core i7 da 2.93 Ghz (OS X):

Riferimento Python

time python sort.py 50000000
sorting...

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

Fork / join Java JDK 7

time java ForkJoinQuicksortTask 50000000
Starting parallel computation

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

Ho anche provato a fare qualche esperimento con la lettura parallela e convertendo i byte in doppi, ma non ho visto alcuna differenza lì.

Aggiornare:

Se qualcuno vuole sperimentare il caricamento parallelo dei dati, la versione di caricamento parallelo è di seguito. In teoria, ciò potrebbe renderlo ancora un po 'più veloce, se il tuo IO Device ha una capacità parallela sufficiente (gli SSD di solito lo fanno). C'è anche un certo sovraccarico nella creazione di doppi da byte, in modo che potrebbe potenzialmente andare più veloce anche in parallelo. Sui miei sistemi (Ubuntu 10.10 / Nehalem Quad / Intel X25M SSD e OS X 10.6 / i7 Quad / Samsung SSD) non ho visto alcuna differenza reale.

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:

Ho eseguito il codice su una delle nostre 12 macchine di sviluppo core con una leggera modifica per impostare un numero fisso di core. Ciò ha dato i seguenti risultati:

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

Su questo sistema ho anche provato la versione Python che ha richiesto 1m2.994s e la versione C ++ di Zjarek che ha richiesto 1.925s (per qualche ragione la versione C ++ di Zjarek sembra funzionare relativamente più velocemente sul computer di static_rtti).

Ho anche provato cosa è successo se ho raddoppiato la dimensione del file a 100.000.000 di doppie:

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

In questo caso, la versione C ++ di Zjarek ha richiesto 3.968s. Python ha impiegato troppo tempo qui.

150.000.000 di doppie:

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

In questo caso, la versione C ++ di Zjarek era 6.044s. Non ho nemmeno provato Python.

La versione C ++ è molto coerente con i suoi risultati, dove Java oscilla leggermente. In primo luogo diventa un po 'più efficiente quando il problema aumenta, ma poi di nuovo meno efficiente.


1
Questo codice non analizza correttamente i doppi valori per me. Java 7 è necessario per analizzare correttamente i valori dal file?
jonderry,

1
Ah, sciocco me. Ho dimenticato di impostare di nuovo l'endianness dopo che ho refactored localmente il codice IO da più righe a una. Normalmente sarebbe necessario Java 7, a meno che tu non abbia aggiunto fork / join separatamente a Java 6 ovviamente.
Arjan,

3.411s sulla mia macchina. Non male, ma più lento della soluzione java di koumes21 :)
static_rtti

1
Proverò qui la soluzione di koumes21 troppo localmente per vedere quali sono le differenze relative sul mio sistema. Comunque, nessuna vergogna nel 'perdere' dai koumes21 poiché è una soluzione molto più intelligente. Questo è solo un ordinamento rapido quasi standard gettato in un fork / join pool;)
arjan

1

Una versione che utilizza pthreads tradizionali. Codice per l'unione copiato dalla risposta di Guvante. Compila con 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;
}  

Sul mio laptop ottengo i seguenti risultati:

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

1

Ecco un'implementazione sequenziale di C99 che cerca di sfruttare davvero la distribuzione nota. Fondamentalmente esegue un singolo round di ordinamento bucket utilizzando le informazioni di distribuzione, quindi alcuni round di quicksort su ciascun bucket assumendo una distribuzione uniforme entro i limiti del bucket e infine un ordinamento di selezione modificato per copiare i dati nel buffer originale. Il quicksort memorizza i punti di divisione, quindi l'ordinamento di selezione deve operare solo su piccoli blocchi. E nonostante (perché?) Di tutta quella complessità, non è nemmeno molto veloce.

Per velocizzare la valutazione Φ, i valori vengono campionati in alcuni punti e successivamente viene utilizzata solo l'interpolazione lineare. In realtà non importa se Φ viene valutato esattamente, purché l'approssimazione sia strettamente monotonica.

Le dimensioni del contenitore sono scelte in modo tale che la possibilità di un trabocco del contenitore sia trascurabile. Più precisamente, con i parametri attuali, la possibilità che un set di dati di 50000000 elementi provochi un overflow del cestino è 3.65e-09. (Questo può essere calcolato usando la funzione di sopravvivenza della distribuzione di Poisson .)

Per compilare, si prega di utilizzare

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

Poiché esiste un calcolo notevolmente maggiore rispetto alle altre soluzioni, questi flag del compilatore sono necessari per renderlo almeno ragionevolmente veloce. Senza -msse3le conversioni da doublea intdiventare veramente lento. Se la tua architettura non supporta SSE3, queste conversioni possono anche essere fatte usando la lrint()funzione.

Il codice è piuttosto brutto - non sono sicuro che questo soddisfi il requisito di essere "ragionevolmente leggibile" ...

#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! Ho dovuto aggiungere -lm per compilarlo (per 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;
}

Questo utilizza erf () per posizionare ogni elemento in modo appropriato in un bin, quindi ordina ogni bin. Mantiene l'array completamente in posizione.

Primo passaggio: docensus () conta il numero di elementi in ogni bin.

Secondo passaggio: partition () consente l'array, posizionando ogni elemento nel relativo contenitore

Terzo passaggio: sortbins () esegue un qsort su ogni bin.

È un po 'ingenuo e chiama la costosa funzione erf () due volte per ogni valore. Il primo e il terzo passaggio sono potenzialmente parallelizzabili. Il secondo è altamente seriale ed è probabilmente rallentato dai suoi schemi di accesso alla memoria altamente casuali. Potrebbe anche valere la pena memorizzare nella cache il numero di bin di ciascun doppio, a seconda del rapporto tra potenza della CPU e velocità della memoria.

Questo programma ti consente di scegliere il numero di bin da utilizzare. Aggiungi un secondo numero alla riga di comando. L'ho compilato con gcc -O3, ma la mia macchina è così debole che non posso dirti nessun numero di buone prestazioni.

Modifica: Poof! Il mio programma C si è magicamente trasformato in un programma C ++ usando std :: sort!


Puoi usare phi per uno stdnormal_cdf più veloce.
Alexandru,

Quanti cassonetti dovrei mettere, approssimativamente?
static_rtti,

@Alexandru: ho aggiunto un'approssimazione lineare a tratti a normcdf e ho guadagnato solo circa il 5% di velocità.
dal

@static_rtti: non devi metterne nessuno. Per impostazione predefinita, il codice sceglie il conteggio dei contenitori, quindi la dimensione media del cestino è 10/11 di 128kb. Troppi pochi contenitori e non si ottiene il vantaggio del partizionamento. Troppi e la fase di partizione si impantana a causa del trabocco della cache.
dal

10.6s! Ho provato a giocare un po 'con il numero di bin e ho ottenuto i migliori risultati con 5000 (leggermente oltre il valore predefinito di 3356). Devo dire che mi aspettavo di vedere prestazioni molto migliori per la tua soluzione ... Forse è il fatto che stai usando qsort invece dello std :: sort delle soluzioni C ++ potenzialmente più veloce?
static_rtti,

1

Dai un'occhiata all'implementazione dell'ordinamento radix di Michael Herf ( Radix Tricks ). Sulla mia macchina l'ordinamento era 5 volte più veloce rispetto std::sortall'algoritmo nella mia prima risposta. Il nome della funzione di ordinamento è 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;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.