प्रतियोगिता: गाऊसी-वितरित डेटा की एक बड़ी सरणी को सॉर्ट करने का सबसे तेज़ तरीका


71

इस सवाल में रुचि के बाद , मैंने सोचा कि किसी प्रतियोगिता का प्रस्ताव करके उत्तर को थोड़ा और अधिक उद्देश्यपूर्ण और मात्रात्मक बनाना दिलचस्प होगा।

यह विचार सरल है: मैंने 50 मिलियन गाऊसी-वितरित डबल्स (औसत: 0, stdev 1) युक्त एक बाइनरी फ़ाइल उत्पन्न की है। लक्ष्य एक ऐसा कार्यक्रम बनाना है जो इनको स्मृति में जितनी जल्दी हो सके छाँटे। अजगर में एक बहुत ही सरल संदर्भ कार्यान्वयन को पूरा करने के लिए 1 मी 4 एस लेता है। हम कितना नीचे जा सकते हैं?

नियम इस प्रकार हैं: एक प्रोग्राम के साथ उत्तर दें जो फ़ाइल "गॉसियनडैट" को खोलता है और मेमोरी में संख्याओं को सॉर्ट करता है (उन्हें आउटपुट करने की आवश्यकता नहीं है), और प्रोग्राम बनाने और चलाने के लिए निर्देश। कार्यक्रम मेरे आर्क लिनक्स मशीन पर काम करने में सक्षम होना चाहिए (जिसका अर्थ है कि आप किसी भी प्रोग्रामिंग भाषा या लाइब्रेरी का उपयोग कर सकते हैं जो इस सिस्टम पर आसानी से इंस्टॉल करने योग्य है)।

कार्यक्रम यथोचित पठनीय होना चाहिए, ताकि मैं यह सुनिश्चित कर सकूं कि इसे लॉन्च करना सुरक्षित है (कोई कोडांतरक-केवल समाधान, कृपया!)।

मैं अपनी मशीन (क्वाड कोर, 4 गीगाबाइट रैम) पर उत्तर चलाऊंगा। सबसे तेज़ समाधान को स्वीकृत उत्तर और 100 अंक का इनाम मिलेगा :)

कार्यक्रम संख्या उत्पन्न करने के लिए उपयोग किया जाता है:

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

सरल संदर्भ कार्यान्वयन:

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

EDIT: केवल 4 GB RAM, क्षमा करें

EDIT # 2: ध्यान दें कि प्रतियोगिता का बिंदु यह देखना है कि क्या हम डेटा के बारे में पूर्व सूचना का उपयोग कर सकते हैं । यह अलग प्रोग्रामिंग भाषा कार्यान्वयन के बीच एक pissing मैच नहीं माना जाता है!


1
प्रत्येक मान लें और इसे सीधे अपनी "अपेक्षित" स्थिति में ले जाएं, विस्थापित मूल्य के लिए दोहराएं। सुनिश्चित नहीं हैं कि उस के साथ युगल समस्या को कैसे हल किया जाए। जब किया जाता है, बबल पूरा होने तक सॉर्ट करता है (एक जोड़े को पास करना चाहिए)।

1
मैं कल शाम एक बाल्टी सॉर्ट समाधान पोस्ट करूँगा अगर यह तब तक बंद नहीं हुआ है :)

1
@static_rtti - एक भारी सीजी उपयोगकर्ता के रूप में, यह बिल्कुल "हम" जैसी चीज है जो CG.S. पर हैक करना पसंद करता है। किसी भी रीडिंग मोड में, इसे CG में ले जाएं, इसे बंद न करें।
arrdem

1
CodeGolf.SE में आपका स्वागत है! मैंने SO मूल से बहुत सारी कमेंट्री साफ़ कर दी है कि यह कहाँ है या क्या नहीं है, और कोडगॉल्फ के पास होने के लिए फिर से टैग किया गया है। मुख्यधारा में।
dmckee

2
यहाँ एक मुश्किल मुद्दा यह है कि हम वस्तुनिष्ठ मानदंड की तलाश करते हैं, और "सबसे तेज़" प्लेटफ़ॉर्म निर्भरता का परिचय देता है ... ओ (एन ^ {1.2}) एल्गोरिथ्म को cpython वर्चुअल मशीन पर लागू करता है, जो O (n ^ {1.3) को हराता है। ) एल्गोरिथ्म एक समान निरंतर सी में लागू किया गया? मैं आमतौर पर प्रत्येक समाधान की प्रदर्शन विशेषताओं पर कुछ चर्चा का सुझाव देता हूं, क्योंकि इससे लोगों को यह पता लगाने में मदद मिल सकती है कि क्या हो रहा है।
dmckee

जवाबों:


13

यहां सी ++ में एक समाधान है जो पहले एक ही अपेक्षित संख्या वाले तत्वों के साथ बाल्टी में संख्याओं को विभाजित करता है और फिर प्रत्येक बाल्टी को अलग से अलग करता है। यह विकिपीडिया के कुछ सूत्रों के आधार पर संचयी वितरण समारोह की एक तालिका को प्रस्तुत करता है और फिर इस तालिका से मूल्यों का तेजी से अनुमान लगाने के लिए प्रक्षेपित करता है।

चार कोर के उपयोग के लिए कई चरणों में कई चरण चलते हैं।

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

#include <tbb/parallel_for.h>

using namespace std;

typedef unsigned long long ull;

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

const double fourOverPI = 4 / M_PI;

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

const double sqrt2 = sqrt(2);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    puts("done.");

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

    //print(data, N);

    return 0;
}

इसे संकलित करने और चलाने के लिए, इस कमांड का उपयोग करें:

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

संपादित करें: सभी बाल्टियों को एक ही सरणी में रखा जाता है ताकि बाल्टी को वापस सरणी में कॉपी करने की आवश्यकता को हटाया जा सके। इसके अलावा, पूर्व-अंकित मूल्यों के साथ तालिका का आकार कम कर दिया गया था, क्योंकि मान पर्याप्त रूप से सटीक हैं। फिर भी, अगर मैं बाल्टी की संख्या को 256 से ऊपर बदल दूं, तो उस बाल्टी की संख्या के मुकाबले कार्यक्रम को चलने में अधिक समय लगता है।

संपादित करें: एक ही एल्गोरिथ्म, विभिन्न प्रोग्रामिंग भाषा। मैंने जावा के बजाय C ++ का उपयोग किया और मेरी मशीन पर चलने का समय ~ 3.2 से कम होकर ~ 2.35 हो गया। बाल्टी की इष्टतम संख्या अभी भी लगभग 256 (फिर से, मेरे कंप्यूटर पर) है।

वैसे, tbb वाकई कमाल का है।

संपादित करें: मैं अलेक्जेंड्रू के महान समाधान से प्रेरित था और अपने अंतिम चरण के संशोधित संस्करण द्वारा स्टैड की जगह :: छँटाई। मैंने सकारात्मक / नकारात्मक संख्याओं से निपटने के लिए एक अलग विधि का उपयोग किया, भले ही इसके लिए सरणी से अधिक पास की आवश्यकता हो। मैंने भी सरणी को ठीक से सॉर्ट करने और प्रविष्टि सॉर्ट को निकालने का निर्णय लिया। मैं बाद में कुछ समय परीक्षण में बिताऊंगा कि ये परिवर्तन प्रदर्शन को कैसे प्रभावित करते हैं और संभवतः उन्हें वापस करते हैं। हालांकि, मूलांक क्रमांक का उपयोग करके, समय ~ 2.35s से घटकर ~ 1.63 हो गया।


अच्छा लगा। मुझे खदान पर 3.055 मिला। सबसे कम मुझे मेरा प्राप्त करने में सक्षम था 6.3। मैं आँकड़ों को बेहतर पाने के लिए आपके माध्यम से चुन रहा हूँ। आपने 256 को बाल्टी की संख्या के रूप में क्यों चुना? मैंने 128 और 512 की कोशिश की, फिर भी 256 ने सबसे अच्छा काम किया।
स्कॉट

मैंने 256 को बाल्टी की संख्या के रूप में क्यों चुना? मैंने 128 और 512 की कोशिश की, फिर भी 256 ने सबसे अच्छा काम किया। :) मैंने इसे अनुभवजन्य रूप से पाया और मुझे यकीन नहीं है कि बाल्टी की संख्या बढ़ने से एल्गोरिथ्म धीमा हो जाता है - स्मृति आवंटन को लंबे समय तक नहीं लेना चाहिए। शायद कैश आकार से संबंधित कुछ?
k21

मेरी मशीन पर 2.725s। एक जावा समाधान के लिए बहुत अच्छा है, जेवीएम के लोडिंग समय को ध्यान में रखते हुए।
static_rtti

2
मैंने आपके कोड को मेरे और अर्जन के समाधान के अनुसार (अपने सिंटैक्स का उपयोग किया है, क्योंकि यह मेरे से साफ था) का उपयोग करने के लिए स्विच किया गया था और इसे प्राप्त करने में सक्षम था। 3 सेकंड तेजी से। मुझे एक ssd मिला है, मुझे आश्चर्य है कि यदि निहितार्थ नहीं तो क्या हो सकता है। यह आपके कुछ बिट ट्विडलिंग से भी छुटकारा दिलाता है। मॉड्यूल्ड सेक्शन यहां हैं।
स्कॉट

3
यह मेरे परीक्षणों (16कोर सीपीयू) पर सबसे तेज़ समानांतर समाधान है । 1.22 से 1.94 से दूसरे स्थान पर है।
अलेक्जेंड्रू

13

स्मार्ट होने के बिना, बस बहुत तेज़ भोले सॉर्टर प्रदान करने के लिए, यहां सी में एक है जो आपके पायथन एक के बराबर होना चाहिए:

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

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

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

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

के साथ संकलित gcc -O3, मेरी मशीन पर यह पायथन से एक मिनट से भी कम समय लगता है: 87 एस की तुलना में लगभग 11 एस।


1
मेरी मशीन पर 10.086s लिया, जो आपको वर्तमान नेता बनाता है! लेकिन मुझे पूरा यकीन है कि हम बेहतर कर सकते हैं :)

1
क्या आप दूसरे टर्नरी ऑपरेटर को हटाने की कोशिश कर सकते हैं और बस उस मामले के लिए 1 वापस कर सकते हैं क्योंकि यादृच्छिक मात्रा डेटा की इन राशि में एक दूसरे के बराबर नहीं है।
कोडिज्म

@ शरीरवाद: मैं जोड़ूंगा कि हम समतुल्य डेटा के स्थानों की अदला-बदली के बारे में परवाह नहीं करते हैं, इसलिए यदि हम समान मूल्य प्राप्त कर सकते हैं तो यह एक उपयुक्त सरलीकरण होगा।

10

मैंने मानक विचलन के आधार पर खंडों में विभाजन किया जो इसे 4 वें में सबसे नीचे तोड़ देना चाहिए। संपादित करें: http://en.wikipedia.org/wiki/Error_function#Table_of_values में x मान के आधार पर विभाजन को फिर से लिखा गया

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

मैंने छोटी बाल्टियों का उपयोग करने की कोशिश की, लेकिन ऐसा लगता है कि उपलब्ध कोर की संख्या से परे 2 * एक बार बहुत कम प्रभाव पड़ा। किसी भी समानांतर संग्रह के बिना, मेरे बॉक्स पर 37 सेकंड और समानांतर संग्रह के साथ 24 सेकंड लगेंगे। यदि वितरण के माध्यम से विभाजन किया जाता है, तो आप केवल एक सरणी का उपयोग नहीं कर सकते हैं, इसलिए कुछ और ओवरहेड है। मैं इस बात पर स्पष्ट नहीं हूं कि स्केला में कोई मूल्य कब बॉक्सिंग / अनबॉक्स किया जाएगा।

मैं समानांतर उपयोग के लिए scala 2.9 का उपयोग कर रहा हूं। आप बस इसके tar.gz वितरण को डाउनलोड कर सकते हैं।

संकलन करने के लिए: स्केलैक SortFile.scala (मैंने इसे सीधे स्कैला / बिन फ़ोल्डर में कॉपी किया है।

चलाने के लिए: JAVA_OPTS = "- Xmx4096M" ./scala SortFile (मैंने इसे 2 गिग्स के साथ भागकर राम के बारे में बताया)

संपादित करें: हटाए गए आवंटन को हटा दें, केवल आवंटित करने की तुलना में धीमा। सरणी बफ़र्स के लिए प्रारंभिक आकार की प्राइमिंग निकाल दी गई है। वास्तव में इसने पूरे 50000000 मूल्यों को पढ़ा। उम्मीद है कि ऑटोबॉक्सिंग के मुद्दों से बचने के लिए (अभी भी भोले ग की तुलना में धीमी है)

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! एक स्कैला समाधान के लिए अच्छा है, मुझे लगता है ... इसके अलावा, ब्रावो पहला समाधान प्रदान करने के लिए जो वास्तव में किसी तरह से गाऊसी वितरण का उपयोग करता है!

1
मैं केवल c # समाधान के साथ प्रतिस्पर्धा करना चाहता था। मुझे नहीं पता था कि मैं c / c ++ को हराऊंगा। इसके अलावा .. यह आपके लिए मेरे मुकाबले बहुत अलग व्यवहार कर रहा है। मैं अपने अंत पर openJDK का उपयोग कर रहा हूं और यह बहुत धीमा है। मुझे आश्चर्य है कि यदि अधिक विभाजन जोड़ने से आपके एनवी में मदद मिलेगी।
स्कॉट

9

बस इसे एक सीएस फ़ाइल में रखें और इसे csc के साथ सिद्धांत में संकलित करें: (मोनो की आवश्यकता है)

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

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

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

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

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

क्या मैं मोनो के साथ आपके समाधान चला सकता हूं? मैं इसे कैसे करूं?

मोनो का उपयोग नहीं किया गया है, इस बारे में नहीं सोचा, आप एफ # संकलन और फिर इसे चलाने में सक्षम होना चाहिए।

1
प्रदर्शन को बेहतर बनाने के लिए चार थ्रेड्स का उपयोग करने के लिए अद्यतन किया गया। अब मुझे 6 सेकंड देता है। ध्यान दें कि यह काफी सुधार किया जा सकता है (5 सेकंड की संभावना) यदि आप केवल एक अतिरिक्त सरणी का उपयोग करते हैं और एक टन मेमोरी को शून्य से शुरू करने से बचते हैं, जो सीएलआर द्वारा किया जाता है, क्योंकि सब कुछ कम से कम एक बार लिखा जा रहा है।

1
मेरी मशीन पर 9.598s! आप वर्तमान नेता हैं :)

1
मेरी माँ ने मुझे मोनो के साथ लोगों से दूर रहने के लिए कहा था!

8

चूंकि आप जानते हैं कि वितरण क्या है, आप एक प्रत्यक्ष अनुक्रमण O (N) सॉर्ट का उपयोग कर सकते हैं। (यदि आप सोच रहे हैं कि क्या है, तो आपके पास 52 कार्ड का एक डेक है और आप इसे सॉर्ट करना चाहते हैं। बस 52 डिब्बे हैं और प्रत्येक कार्ड को खुद के बिन में टॉस करें।)

आपके पास 5e7 युगल हैं। 5e7 डबल्स के परिणाम सरणी R आवंटित करें। प्रत्येक नंबर लें xऔर प्राप्त करें i = phi(x) * 5e7। मूल रूप से करते हैं R[i] = x। टक्करों को संभालने का एक तरीका है, जैसे कि संख्या को हिलाना हो सकता है (जैसे कि साधारण हैश कोडिंग में)। वैकल्पिक रूप से, आप R को कुछ समय बड़ा बना सकते हैं, जो एक अद्वितीय रिक्त मान से भरा होता है । अंत में, आप सिर्फ R के तत्वों को स्वीप करते हैं।

phiबस गॉसियन संचयी वितरण समारोह है। यह एक गॉसियन डिस्ट्रिब्यूटेड नंबर को +/- इनफिनिटी के बीच 0 और 1. के बीच एक समान डिस्ट्रीब्यूटेड नंबर में कनवर्ट करता है। यह गणना का एक सरल तरीका टेबल लुकअप और इंटरपोलेशन के साथ है।


3
सावधान रहें: आप अनुमानित वितरण जानते हैं, सटीक वितरण नहीं। आप जानते हैं कि डेटा एक गाऊसी कानून का उपयोग करके उत्पन्न किया गया था, लेकिन चूंकि यह परिमित है, यह वास्तव में एक गाऊसी का पालन नहीं करता है।

@static_rtti: इस मामले में phi के लिए आवश्यक अनुमान डेटा सेट OO में किसी भी अनियमितता से बड़ी परेशानी पैदा करेगा।

1
@static_rtti: इसका सटीक होना आवश्यक नहीं है। यह केवल डेटा को फैलाने के लिए है, इसलिए यह लगभग समान है, इसलिए यह कुछ स्थानों पर बहुत अधिक गुच्छा नहीं करता है।

मान लीजिए कि आपके पास 5e7 युगल हैं। क्यों न आर के एक वेक्टर में प्रत्येक प्रविष्टि करें, कहते हैं, 5e6 डबल के वैक्टर। फिर, प्रत्येक डबल को उसके उपयुक्त वेक्टर में पुश करें। वैक्टर को क्रमबद्ध करें और आपका काम हो गया। यह इनपुट के आकार में रैखिक समय लेना चाहिए।
नील जी

वास्तव में, मुझे लगता है कि mdkess पहले से ही उस समाधान के साथ आया था।
नील जी

8

यहाँ एक और अनुक्रमिक समाधान है:

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

typedef unsigned long long ull;

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

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

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

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

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

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

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

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

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

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

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

मुझे संदेह है कि यह बहु-थ्रेडेड घोल को हराता है, लेकिन मेरे i7 लैपटॉप पर टाइमिंग है (स्टडोर्ट एक और उत्तर में उपलब्ध C ++ समाधान है):

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

ध्यान दें कि इस समाधान में रैखिक समय जटिलता है (क्योंकि यह युगल के विशेष प्रतिनिधित्व का उपयोग करता है)।

संपादित करें : तत्वों के बढ़ते क्रम का निर्धारण।

संपादित करें : बेहतर गति लगभग आधा सेकंड।

संपादित करें : एक और 0.7 सेकंड से बेहतर गति। एल्गोरिथ्म को अधिक कैश फ्रेंडली बनाया।

संपादित करें : एक और 1 सेकंड से बेहतर गति। चूंकि केवल 50.000.000 तत्व हैं, मैं आंशिक रूप से मंटिसा को सॉर्ट कर सकता हूं और आउट-ऑफ-प्लेस तत्वों को ठीक करने के लिए इंसर्ट सॉर्ट (जो कि कैश फ्रेंडली है) का उपयोग कर सकता हूं। यह विचार पिछले मूलांक छंटाई लूप से दो पुनरावृत्तियों को हटाता है।

EDIT : 0.16 कम सेकंड। पहले क्रम :: रिवर्स को समाप्त किया जा सकता है अगर छँटाई क्रम उलट हो।


अब यह दिलचस्प हो रहा है! यह किस प्रकार का एल्गोरिथ्म है?
static_rtti

2
कम से कम महत्वपूर्ण अंक मूलांक । आप मंटिसा को सॉर्ट कर सकते हैं, फिर एक्सपोनेंट, फिर साइन। यहां प्रस्तुत एल्गोरिदम इस विचार को एक कदम आगे ले जाता है। एक अलग जवाब में प्रदान की गई विभाजन विचार का उपयोग करके इसे समानांतर किया जा सकता है।
अलेक्जेंड्रू

एकल पिरोया समाधान के लिए बहुत तेज़: 2.552s! क्या आपको लगता है कि आप इस तथ्य का उपयोग करने के लिए अपना समाधान बदल सकते हैं कि डेटा सामान्य रूप से वितरित किया गया है? आप शायद वर्तमान सर्वश्रेष्ठ बहु-थ्रेडेड समाधानों से बेहतर कर सकते हैं।
स्थिर_परिवर्तन

1
@static_rtti: मैं देख रहा हूं कि दमिश्क स्टील ने पहले ही इस कार्यान्वयन का एक बहुस्तरीय संस्करण पोस्ट कर दिया है। मैंने इस एल्गोरिथ्म के कैशिंग व्यवहार में सुधार किया है, इसलिए आपको अब बेहतर समय प्राप्त करना चाहिए। कृपया इस नए संस्करण का परीक्षण करें।
अलेक्जेंड्रू

2
मेरे अक्षांश परीक्षणों में 1.459 एस। जबकि यह समाधान मेरे नियमों के अनुसार विजेता नहीं है, यह वास्तव में बड़े यश का हकदार है। बधाई हो!
स्थैतिक_आरती

6

क्रिश्चियन आमेर का घोल लेना और इसे इंटेल के थ्रेडेड बिल्डिंग ब्लॉक्स के साथ समानांतर करना

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

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

यदि आपके पास इंटेल के प्रदर्शन प्रिमिटिव (IPP) लाइब्रेरी तक पहुंच है, तो आप इसके मूलांक क्रमांक का उपयोग कर सकते हैं। बस प्रतिस्थापित करें

#include <tbb/parallel_sort.h>

साथ में

#include "ipps.h"

तथा

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

साथ में

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

मेरे दोहरे कोर लैपटॉप पर, टाइमिंग हैं

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

1
2.958s! TBB बहुत अच्छा और प्रयोग करने में आसान लगता है!

2
टीबीबी बेतुका भयानक है। यह एल्गोरिथम के काम के लिए अमूर्तता का सही स्तर है।
drxzcl

5

वितरण के आँकड़ों के आधार पर अपने धुरी मूल्यों को चुनने वाले समानांतर क्विकॉर्ट के कार्यान्वयन के बारे में कैसे , जिससे समान आकार के विभाजन सुनिश्चित होते हैं? पहली धुरी मीन पर होगी (इस मामले में शून्य), अगली जोड़ी 25 वें और 75 वें प्रतिशत (+/- -0.67449 मानक विचलन) पर होगी, और इसी तरह, प्रत्येक विभाजन के साथ शेष बचे हुए सेट को आधा या अधिक करना होगा। पूरी तरह से कम।


यह प्रभावी रूप से मैंने क्या किया है .. इससे पहले कि मैं अपना राइटअप पूरा कर पाता इससे पहले आपको यह पोस्ट मिल गया।

5

बहुत बदसूरत (क्यों मैं जब संख्याओं के साथ समाप्त होने वाले चर का उपयोग कर सकता हूं तो सरणियों का उपयोग करें), लेकिन तेज कोड (मेरी पहली कोशिश करने के लिए :: थ्रेड्स), पूरे समय (समय वास्तविक) मेरे सिस्टम पर 1,8 s (एसटीडी की तुलना में) :: सॉर्ट (4,8 s), g ++ -std = c ++ 0x -O3 -march = native -pthread के साथ संकलित करें बस स्टड के माध्यम से डेटा पास करें (केवल 50M के लिए काम करता है)।

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

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

// gaussian.dat फ़ाइल को पढ़ने के लिए परिवर्तन संपादित करें।


क्या आप इसे बदलने के लिए gaussian.dat को बदल सकते हैं, जैसा कि ऊपर C ++ समाधान करते हैं?

मैं बाद में कोशिश करूँगा जब मैं घर आऊंगा।
static_rtti

बहुत अच्छा समाधान, आप वर्तमान नेता (1.949) हैं! और गाऊसी वितरण का अच्छा उपयोग :)
static_rtti

4

C ++ का उपयोग करके एक C ++ समाधान std::sort(अंततः qsort की तुलना में तेज़, qsort बनाम std :: सॉर्ट के प्रदर्शन के बारे में )

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

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

मैं विश्वसनीय नहीं कह सकता कि मुझे कितना समय लगता है क्योंकि मेरे पास मेरी मशीन पर केवल 1GB है और दिए गए पायथन कोड के साथ मैं gaussian.datकेवल 25mio युगल (मेमोरी त्रुटि प्राप्त किए बिना) के साथ एक फ़ाइल बना सकता हूं । लेकिन मुझे बहुत दिलचस्पी है कि कितने समय तक एसटी :: सॉर्ट एल्गोरिथ्म चलता है।


6.425s! जैसा कि अपेक्षित था, सी ++ चमकता है :)

@static_rtti: मैंने स्वेन्सन टिम्सॉर्ट एल्गोरिथम की कोशिश की है (जैसा कि आपके पहले प्रश्न में मैथ्यू एम से सुझाव दिया गया है )। मुझे sort.hइसे C ++ के साथ संकलित करने के लिए फ़ाइल में कुछ बदलाव करने थे । की तुलना में यह लगभग दोगुना था std::sort। पता नहीं क्यों, शायद संकलक अनुकूलन के कारण?
क्रिश्चियन आमेर

4

यहाँ अलेक्जेंड्रू के मूलांक के साथ ज़ारारेक के थ्रेडेड स्मार्ट पिविंग का मिश्रण है। इसके साथ संकलित करें

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

आप STEP (जैसे ऐड -DSTEP = 11) को परिभाषित करके मूलांक का आकार बदल सकते हैं। मुझे अपने लैपटॉप के लिए सर्वश्रेष्ठ 8 (डिफ़ॉल्ट) मिला।

डिफ़ॉल्ट रूप से, यह समस्या को 4 टुकड़ों में विभाजित करता है और इसे कई थ्रेड्स पर चलाता है। आप कमांड लाइन के लिए एक गहरा पैरामीटर पास करके बदल सकते हैं। तो अगर आपके पास दो कोर हैं, तो इसे चलाएं

sorter_gaussian_radix 50000000 1

और अगर आपके पास 16 कोर हैं

sorter_gaussian_radix 50000000 4

अभी अधिकतम गहराई 6 (64 धागे) है। यदि आप बहुत अधिक स्तर रखते हैं, तो आप कोड को धीमा कर देंगे।

एक चीज जो मैंने भी आजमाई थी, वह इंटेल परफॉर्मेंस प्राइमिटिव्स (IPP) लाइब्रेरी का मूलांक था। अलेक्जेंड्रू के कार्यान्वयन में आईपीपी की तीव्रता कम होती है, जिसमें आईपीपी लगभग 30% धीमा होता है। यह भिन्नता यहाँ भी शामिल है (टिप्पणी की गई है)।

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

#ifndef STEP
#define STEP 8
#endif

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

clock_t c1, c2, c3, c4, c5;

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


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

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


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

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

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

      c1=clock();

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

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

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

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

      c2=clock();

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

      c3=clock();

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

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

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

      c4=clock();

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

    }
}


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

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

  clock_t c0 = clock();

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

  // ippsSortRadixAscend_64f_I(dbuf,copy,size);

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

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

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

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

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

संपादित करें : मैंने अलेक्जेंड्रू के कैश सुधारों को लागू किया, और मेरी मशीन पर लगभग 30% समय मुंडा।

EDIT : यह एक पुनरावर्ती प्रकार को लागू करता है, इसलिए इसे अलेक्जेंड्रू की 16 कोर मशीन पर अच्छी तरह से काम करना चाहिए। यह अलेक्जेंड्रू के अंतिम सुधार का भी उपयोग करता है और रिवर्स में से एक को हटा देता है। मेरे लिए, इसने 20% सुधार दिया।

EDIT : एक संकेत बग को ठीक किया गया जो कि 2 से अधिक कोर होने पर अक्षमता का कारण बना।

EDIT : लैम्ब्डा को हटा दिया गया है, इसलिए यह gcc के पुराने संस्करणों के साथ संकलित होगा। इसमें IPP कोड भिन्नता शामिल है जिस पर टिप्पणी की गई है। मैंने 16 कोर पर चलने के लिए दस्तावेज भी तय किए। जहां तक ​​मैं बता सकता हूं, यह सबसे तेज कार्यान्वयन है।

संपादित करें : STEP 8 नहीं होने पर एक बग फिक्स्ड। थ्रेड्स की अधिकतम संख्या 64 तक बढ़ाई गई। कुछ समय की जानकारी जोड़ी गई।


अच्छा लगा। मूलांक सॉर्ट बहुत कैश है। देखें कि क्या आप बदलकर बेहतर परिणाम प्राप्त कर सकते हैं step(11 मेरे लैपटॉप पर इष्टतम था)।
अलेक्जेंड्रू

आपके पास एक बग है: int cnt[mask]होना चाहिए int cnt[mask + 1]। बेहतर परिणामों के लिए एक निश्चित मूल्य का उपयोग करें int cnt[1 << 16]
अलेक्जेंड्रू

मैं इन सभी समाधानों को आज बाद में आजमाऊँगा जब मुझे घर मिलेगा।
static_rtti

1.534s !!! मुझे लगता है कि हमारे पास एक नेता हैं :-D
static_rtti

@static_rtti: क्या आप इसे फिर से आज़मा सकते हैं? पिछली बार की तुलना में आपने इसे आजमाया है। मेरी मशीन पर, यह किसी भी अन्य समाधान की तुलना में काफी तेज है।
दमिश्क इस्पात

2

मुझे लगता है कि यह वास्तव में आप क्या करना चाहते हैं पर निर्भर करता है। यदि आप गाऊसी लोगों का एक समूह बनाना चाहते हैं, तो यह आपकी मदद नहीं करेगा। लेकिन अगर आप सॉर्ट किए गए गॉसियंस का एक गुच्छा चाहते हैं, तो यह होगा। यहां तक ​​कि अगर यह समस्या को थोड़ा याद करता है, तो मुझे लगता है कि बनाम वास्तविक छंटाई दिनचर्या की तुलना करना दिलचस्प होगा।

अगर आप कुछ तेज करना चाहते हैं, तो कम करें।

सामान्य वितरण से यादृच्छिक नमूनों का एक गुच्छा उत्पन्न करने और फिर छाँटने के बजाय, आप क्रमबद्ध क्रम में सामान्य वितरण से नमूने का एक गुच्छा उत्पन्न कर सकते हैं।

आप क्रमबद्ध क्रम में n समरूप यादृच्छिक संख्याएँ उत्पन्न करने के लिए यहाँ समाधान का उपयोग कर सकते हैं । फिर आप व्युत्क्रम नमूनाकरण के माध्यम से सामान्य वितरण से समान यादृच्छिक संख्याओं को संख्या में बदलने के लिए सामान्य वितरण के व्युत्क्रम cdf (scipy.stats.norm.ppf) का उपयोग कर सकते हैं ।

import scipy.stats
import random

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

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

यदि आप अपने हाथों को गंदे करना चाहते हैं, तो मुझे लगता है कि आप किसी प्रकार की पुनरावृत्ति विधि का उपयोग करके और अपने प्रारंभिक अनुमान के रूप में पिछले परिणाम का उपयोग करके कई उलटा सीएफडी गणनाओं को गति देने में सक्षम हो सकते हैं। चूंकि अनुमान बहुत करीब होने जा रहे हैं, शायद एक एकल पुनरावृत्ति आपको बहुत सटीकता देगी।


2
अच्छा जवाब, लेकिन यह धोखा होगा :) मेरे सवाल का विचार यह है कि एल्गोरिदम को सॉर्ट करते समय बहुत ध्यान दिया गया है, छंटाई के लिए डेटा के बारे में पूर्व ज्ञान के उपयोग पर लगभग कोई साहित्य नहीं है, भले ही कुछ कागजात हों इस मुद्दे को संबोधित करने से अच्छा लाभ हुआ है। तो आइए देखें कि क्या संभव है!

2

इस मुख्य () के साथ गुवाँटे के समाधान को बदलने की कोशिश करें, जैसे ही 1/4 IO रीडिंग होती है, यह छंटनी शुरू हो जाती है, यह मेरे परीक्षण में तेज़ है:

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

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

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

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

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

8.933s। थोड़ा और तेज :)

2

चूंकि आप वितरण को जानते हैं, मेरा विचार के बकेट्स बनाने का होगा, प्रत्येक समान तत्वों की अपेक्षित संख्या के साथ (चूंकि आप वितरण जानते हैं, आप इसकी गणना कर सकते हैं)। फिर ओ (एन) समय में, सरणी को स्वीप करें और तत्वों को अपनी बाल्टियों में डालें।

फिर समवर्ती बाल्टियों को क्रमबद्ध करें। मान लीजिए कि आपके पास k बाल्टी, और n तत्व हैं। एक बाल्टी को सॉर्ट करने के लिए (n / k) lg (n / k) समय लगेगा। अब मान लीजिए कि आपके पास p प्रोसेसर हैं जिनका आप उपयोग कर सकते हैं। चूंकि बाल्टी को स्वतंत्र रूप से सॉर्ट किया जा सकता है, इसलिए आपके पास सौदा करने के लिए छत (के / पी) का गुणक है। यह n + Ceil (k / p) * (n / k) lg (n / k) का एक अंतिम रनटाइम देता है, जिसे यदि आप k अच्छी तरह से चुनते हैं, तो n lg n की तुलना में तेजी से एक अच्छा सौदा होना चाहिए।


मुझे लगता है कि यह सबसे अच्छा समाधान है।
नील जी

आप वास्तव में उन तत्वों की संख्या नहीं जानते हैं जो एक बाल्टी में समाप्त हो जाएंगे, इसलिए गणित वास्तव में गलत है। यह कहा जा रहा है, मुझे लगता है कि यह एक अच्छा जवाब है।
पौलजापोन

@pouejapon: आप सही कह रहे हैं।
नील जी

यह उत्तर वास्तव में अच्छा लगता है । समस्या यह है - यह वास्तव में तेज़ नहीं है। मैंने इसे C99 में लागू किया (मेरा उत्तर देखें), और यह निश्चित रूप से आसानी से धड़कता है std::sort(), लेकिन यह अलेक्जेंड्रू के रेडिक्ससॉर्ट समाधान की तुलना में धीमा है।
स्वेन मार्नाच

2

एक निम्न-स्तरीय अनुकूलन विचार एसएसई रजिस्टर में दो डबल्स फिट करने के लिए है, इसलिए प्रत्येक थ्रेड एक समय में दो वस्तुओं के साथ काम करेगा। यह कुछ एल्गोरिदम के लिए करने के लिए जटिल हो सकता है।

एक और बात यह है कि सरणी को कैश-फ्रेंडली चंक्स में छांटना है, फिर परिणामों को मर्ज करना है। दो स्तरों का उपयोग किया जाना चाहिए: उदाहरण के लिए L1 के लिए पहले 4 KB फिर L2 के लिए 64 KB।

यह बहुत कैश-फ्रेंडली होना चाहिए, क्योंकि बाल्टी सॉर्ट कैश के बाहर नहीं जाएगी, और अंतिम मर्ज मेमोरी क्रमिक रूप से चलेगा।

इन दिनों कम्प्यूटेशन मेमोरी एक्सेस की तुलना में बहुत सस्ता है। हालाँकि, हमारे पास बड़ी संख्या में आइटम हैं, इसलिए यह बताना मुश्किल है कि कौन सा सरणी आकार है जब डंब कैश-अवेयर सॉर्ट कम-कॉम्प्लेक्सिटी गैर-कैश-अवगत संस्करण की तुलना में धीमा है।

लेकिन मैं उपरोक्त कार्यान्वयन प्रदान नहीं करूंगा क्योंकि मैं इसे Windows (VC ++) में करूंगा।


2

यहाँ एक रेखीय स्कैन बकेट सॉर्ट कार्यान्वयन है। मुझे लगता है कि यह मूलांक के अलावा सभी मौजूदा एकल-थ्रेडेड कार्यान्वयन से अधिक तेज़ है। यदि मेरे पास cdf का सही आकलन हो रहा है तो इसका रैखिक अपेक्षित समय होना चाहिए (मैं वेब पर पाए गए मानों के रैखिक प्रक्षेप का उपयोग कर रहा हूं) और ऐसी कोई गलती नहीं की है जिससे अत्यधिक स्कैनिंग हो सके:

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

using std::fill;

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

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

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

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

1
मैं आज बाद में यह कोशिश करूँगा जब मुझे घर मिलेगा। इस बीच, क्या मैं कह सकता हूं कि आपका कोड बहुत बदसूरत है? :-D
static_rtti

3.071s! एकल-थ्रेडेड समाधान के लिए बुरा नहीं है!
19_19 को static_rtti

2

मुझे नहीं पता, मैं अपने पिछले पोस्ट को संपादित क्यों नहीं कर सकता, इसलिए यहां नया संस्करण, 0,2 सेकंड तेज (लेकिन सीपीयू समय (उपयोगकर्ता) में लगभग 1,5 सेकंड तेज)। इस समाधान में 2 प्रोग्राम हैं, पहले बाल्टी सॉर्ट के लिए सामान्य वितरण के लिए क्वांटिल्स को प्रीक्लुलेट करता है, और इसे टेबल, टी [डबल * स्केल] = बाल्टी इंडेक्स में संग्रहीत करता है, जहां स्केल कुछ मनमाना संख्या है जो डबल को संभव बनाने के लिए कास्टिंग करता है। फिर मुख्य कार्यक्रम इस डेटा का उपयोग सही बाल्टी में डबल्स लगाने के लिए कर सकते हैं। इसकी एक खामी है, अगर डेटा गॉसियन नहीं है तो यह सही तरीके से काम नहीं करेगा (और सामान्य वितरण के लिए गलत तरीके से काम करने का लगभग शून्य मौका भी है), लेकिन विशेष मामले के लिए संशोधन आसान और तेज है (केवल बकेट चेक की संख्या और std पर गिरना :: तरह ())।

संकलन: जी ++ => http://pastebin.com/WG7pZEzH सहायक कार्यक्रम

g ++ -std = c ++ 0x -O3 -march = native -pthread => http://pastebin.com/T3yzViZP मुख्य छँटाई कार्यक्रम


1.621s! मुझे लगता है कि आप नेता हैं, लेकिन मैं इन सभी उत्तरों के साथ तेजी से हार रहा हूं :)
static_rtti

2

यहां एक और अनुक्रमिक समाधान है। यह एक इस तथ्य का उपयोग करता है कि तत्व सामान्य रूप से वितरित किए जाते हैं, और मुझे लगता है कि यह विचार आम तौर पर रैखिक समय के करीब छांटने के लिए लागू होता है।

एल्गोरिथ्म इस तरह है:

  • अनुमानित सीडीएफ ( phi()कार्यान्वयन में कार्य देखें )
  • सभी तत्वों के लिए छांटे गए सरणी में अनुमानित स्थिति की गणना करें: size * phi(x)
  • तत्वों को उनकी अंतिम स्थिति के करीब एक नई सरणी में रखें
    • मेरे कार्यान्वयन गंतव्य सरणी में कुछ अंतराल हैं इसलिए मुझे सम्मिलित करते समय बहुत सारे तत्वों को स्थानांतरित करने की आवश्यकता नहीं है।
  • अंतिम तत्वों को क्रमबद्ध करने के लिए आवेषण का उपयोग करें (यदि अंतिम स्थिति की दूरी एक स्थिर से कम है तो आवेषण लंबवत है)।

दुर्भाग्य से, छिपी स्थिरांक काफी बड़ी है और यह समाधान मूलांक के एल्गोरिथ्म की तुलना में दोगुना है।


1
2.470s! बहुत अच्छे विचार। इससे कोई फर्क नहीं पड़ता कि समाधान सबसे तेज़ नहीं है अगर विचार दिलचस्प हैं :)
static_rtti

1
यह मेरा जैसा ही है, लेकिन phi संगणना को एक साथ समूहीकृत करना और बेहतर कैश प्रदर्शन के लिए एक साथ बदलाव करना, सही है?
जॉन्ड्री

@ जॉन्डर्री: मैंने आपके समाधान को बढ़ा दिया, अब जब मैं समझ गया कि यह क्या करता है। अपने विचार को चुराने का मतलब नहीं था। मैंने अपने कार्यान्वयन को मेरे (अनौपचारिक) परीक्षणों के सेट में शामिल किया
अलेक्जेंड्रू

2

इंटेल के थ्रेडेड बिल्डिंग ब्लॉक्स का उपयोग करने वाला मेरा व्यक्तिगत पसंदीदा पहले से ही पोस्ट किया गया है, लेकिन यहां JDK 7 और इसके नए कांटे / ज्वाइन एपीआई का उपयोग करते हुए एक कच्चा समानांतर समाधान है:

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


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

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

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

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

        ForkJoinPool mainPool = new ForkJoinPool();

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

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

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

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

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

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

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

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

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

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

        --left;
        ++right;

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

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

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

महत्वपूर्ण अस्वीकरण : मैंने कांटे / से जुड़ने के लिए त्वरित-प्रकार का अनुकूलन लिया: https://github.com/pmbauer/parallel/tree/master/src/main/java/pmbauer/parallel

इसे चलाने के लिए आपको JDK 7 (http://jdk7.java.net/download.html) का बीटा बिल्ड चाहिए।

मेरे 2.93Ghz क्वाड कोर i7 (OS X) पर:

अजगर का संदर्भ

time python sort.py 50000000
sorting...

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

जावा JDK 7 कांटा / शामिल हो

time java ForkJoinQuicksortTask 50000000
Starting parallel computation

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

मैंने समानांतर पढ़ने और बाइट्स को डबल्स में बदलने के साथ कुछ प्रयोग करने की कोशिश की, लेकिन मुझे वहां कोई अंतर नहीं दिखाई दिया।

अपडेट करें:

यदि कोई डेटा के समानांतर लोडिंग के साथ प्रयोग करना चाहता है, तो समानांतर लोडिंग संस्करण नीचे है। सिद्धांत रूप में, यह अभी भी थोड़ा तेज हो सकता है, अगर आपके आईओ डिवाइस में पर्याप्त समानांतर क्षमता (एसएसडी आमतौर पर करते हैं)। बाइट्स से डबल्स बनाने में भी कुछ ओवरहेड है, ताकि संभवतः समानांतर में भी तेजी से जा सके। अपने सिस्टम पर (Ubuntu 10.10 / Nehalem Quad / Intel X25M SSD, और OS X 10.6 / i7 Quad / Samsung SSD) मैंने कोई वास्तविक अंतर नहीं देखा।

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

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


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

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

       ForkJoinPool mainPool = new ForkJoinPool();

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

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

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

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

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

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

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

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

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

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

       --left;
       ++right;

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

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

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

}

class ReadAction extends RecursiveAction {

   private static final long serialVersionUID = -3498527500076085483L;

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

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

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

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

Update2:

मैंने कोर की एक निश्चित मात्रा निर्धारित करने के लिए मामूली संशोधन के साथ हमारी 12 कोर देव मशीनों में से एक पर कोड निष्पादित किया। इससे निम्नलिखित परिणाम मिले:

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

इस प्रणाली पर मैंने पायथन संस्करण भी आजमाया, जिसमें 1m2.994 और Zjarek का C ++ संस्करण लिया, जिसमें 1.925 का समय लगा (किसी कारण से Zjarek का C ++ संस्करण, static_rtti के कंप्यूटर पर अपेक्षाकृत तेज़ चलने लगता है)।

मैंने भी कोशिश की कि क्या हुआ अगर मैंने फ़ाइल का आकार 100,000,000 डबल्स तक बढ़ा दिया:

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

इस मामले में, ज़ेजरेक के सी ++ संस्करण ने 3.968 लिया। अजगर ने यहां बहुत लंबा समय लिया।

150,000,000 डबल्स:

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

इस मामले में, ज़ेजरेक का सी ++ संस्करण 6.044 था। मैंने पायथन का प्रयास भी नहीं किया।

C ++ संस्करण अपने परिणामों के साथ बहुत सुसंगत है, जहां जावा थोड़ा स्विंग करता है। पहले यह थोड़ा अधिक कुशल हो जाता है जब समस्या बड़ी हो जाती है, लेकिन फिर कम कुशल होती है।


1
यह कोड मेरे लिए दोहरे मानों को सही ढंग से पार नहीं करता है। क्या जावा 7 को फ़ाइल से मानों को सही ढंग से पार्स करने के लिए आवश्यक है?
जॉन्ड्री

1
आह, मुझे मूर्ख। मैं स्थानीय स्तर पर एक से कई लाइनों से IO कोड को पुनःप्राप्त करने के बाद फिर से एंडियननेस सेट करना भूल गया। जावा 7 की सामान्य रूप से आवश्यकता होगी, जब तक कि आप कांटा जोड़ / अलग से जावा के 6 कोर्स में शामिल न हों।
अर्जन

मेरी मशीन पर 3.411s। बुरा नहीं है, लेकिन koumes21 के जावा समाधान की तुलना में धीमा :)
static_rtti

1
मैं यहाँ स्थानीय स्तर पर koumes21 के समाधान को देखने की कोशिश करूँगा कि मेरे सिस्टम में सापेक्ष अंतर क्या हैं। वैसे भी, koumes21 से 'खोने' में कोई शर्म नहीं है क्योंकि यह एक बहुत अधिक चतुर समाधान है। यह एक लगभग मानक त्वरित-प्रकार है जो कांटा / ज्वाइन पूल में फेंका जाता है;)
अर्जन

1

पारंपरिक pthreads का उपयोग कर एक संस्करण। Guvante के उत्तर से कॉपी किए गए विलय के लिए कोड। के साथ संकलित करें g++ -O3 -pthread

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

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

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


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

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


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

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

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

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

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

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

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

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

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

  pthread_attr_destroy(&attributes);
  return 0;
}  

मेरे लैपटॉप पर मुझे निम्नलिखित परिणाम मिले:

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

1

यहां एक अनुक्रमिक C99 कार्यान्वयन है जो वास्तव में ज्ञात वितरण का उपयोग करने की कोशिश करता है। यह मूल रूप से वितरण सूचना का उपयोग करके एक प्रकार का बकेट सॉर्ट करता है, फिर बकेट की सीमा के भीतर एक समान वितरण मानकर प्रत्येक बकेट पर क्विकॉर्ट के कुछ राउंड और अंत में डेटा को मूल बफर में कॉपी करने के लिए एक संशोधित चयन प्रकार होता है। Quicksort विभाजन बिंदुओं को याद करता है, इसलिए चयन सॉर्ट को केवल छोटे cunks पर संचालित करने की आवश्यकता होती है। और उस जटिलता के बावजूद (क्योंकि?), यह वास्तव में तेजी से भी नहीं है।

मूल्यांकन को तेज बनाने के लिए, मूल्यों को कुछ बिंदुओं में नमूना लिया जाता है और बाद में केवल रैखिक प्रक्षेप का उपयोग किया जाता है। यह वास्तव में मायने नहीं रखता है अगर does का सही मूल्यांकन किया जाता है, जब तक कि सन्निकटन सख्ती से एकरस होता है।

बिन आकार को ऐसे चुना जाता है कि बिन अतिप्रवाह की संभावना नगण्य है। अधिक सटीक रूप से, वर्तमान मापदंडों के साथ, 50000000 तत्वों के डेटासेट में एक बिन अतिप्रवाह का कारण होगा 3.65e-09। (यह पॉइसन वितरण के अस्तित्व समारोह का उपयोग करके गणना की जा सकती है ।)

संकलन करने के लिए, कृपया उपयोग करें

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

चूंकि अन्य समाधानों की तुलना में काफी अधिक संगणना है, इसलिए इन संकलक झंडों को कम से कम यथोचित रूप से तेज बनाने की आवश्यकता है। -msse3रूपांतरणों के बिना वास्तव में धीमी गति doubleसे intबनने के लिए । यदि आपका आर्किटेक्चर SSE3 का समर्थन नहीं करता है, तो ये रूपांतरण lrint()फ़ंक्शन का उपयोग करके भी किया जा सकता है ।

कोड बल्कि बदसूरत है - यकीन नहीं अगर यह "यथोचित पठनीय" होने की आवश्यकता को पूरा करता है ...

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

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

double phi_vals[PHI_VALS];

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

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

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

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

4.098s! मुझे इसे (erf के लिए) संकलन करने के लिए जोड़ना था।
static_rtti

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

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

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

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

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


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


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


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

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

        return 0;
}

यह प्रत्येक तत्व को उचित रूप से एक बिन में रखने के लिए erf () का उपयोग करता है, फिर प्रत्येक बिन को क्रमबद्ध करता है। यह सरणी को पूरी तरह से जगह में रखता है।

पहला पास: docanim () प्रत्येक बिन में तत्वों की संख्या गिनता है।

दूसरा पास: विभाजन () सरणी को अनुमति देता है, प्रत्येक तत्व को उसके उचित बिन में रखता है

तीसरा पास: सॉर्टबिन () प्रत्येक बिन पर एक qsort करता है।

यह भोले की तरह है, और हर मूल्य के लिए दो बार महंगी एरफ () फ़ंक्शन को कॉल करता है। पहली और तीसरी पास संभावित समानांतर हैं। दूसरा अत्यधिक धारावाहिक है और संभवतः इसकी अत्यधिक यादृच्छिक मेमोरी एक्सेस पैटर्न द्वारा धीमा कर दिया गया है। सीपीयू की मेमोरी स्पीड अनुपात के आधार पर प्रत्येक डबल बिन नंबर को कैश करना भी सार्थक हो सकता है।

यह प्रोग्राम आपको उपयोग करने के लिए डिब्बे की संख्या चुनने देता है। कमांड लाइन में बस एक दूसरा नंबर जोड़ें। मैंने इसे gcc -O3 के साथ संकलित किया है, लेकिन मेरी मशीन इतनी कमजोर है कि मैं आपको कोई अच्छा प्रदर्शन नंबर नहीं बता सकता।

संपादित करें: Poof! मेरा C प्रोग्राम जादुई तरीके से st ++ का उपयोग करके C ++ प्रोग्राम में बदल गया है!


आप तेज stdnormal_cdf के लिए phi का उपयोग कर सकते हैं ।
अलेक्जेंड्रू

मुझे कितने डिब्बे लगाने चाहिए, लगभग?
19_ पर स्टेटिक_रर्ट

@ अलेक्सांद्रु: मैंने नॉर्म्डकॉन्फ़ में एक टुकड़े-टुकड़े रैखिक सन्निकटन को जोड़ा और केवल 5% गति प्राप्त की।
१४:१४

@static_rtti: आपको कोई काम नहीं करना है। डिफ़ॉल्ट रूप से, कोड डिब्बे की गिनती को चुनता है इसलिए औसत बिन आकार 10/11 का 128kb है। बहुत कम डिब्बे और आपको विभाजन का लाभ नहीं मिलता है। बहुत से और विभाजन चरण कैश ओवरफ्लो करने के कारण कम हो जाते हैं।
frud

10.6s! मैंने डिब्बे की संख्या के साथ थोड़ा खेलने की कोशिश की, और मुझे 5000 (3356 के डिफ़ॉल्ट मूल्य से थोड़ा अधिक) के साथ सर्वश्रेष्ठ परिणाम मिले। मुझे कहना होगा कि मुझे आपके समाधान के लिए बहुत बेहतर प्रदर्शन देखने की उम्मीद थी ... शायद यह तथ्य है कि आप संभावित तेज एसटीडी के बजाय क्सोर्ट का उपयोग कर रहे हैं :: सी + + समाधानों की तरह?
स्थैतिक_परिवर्तन

1

माइकल हर्फ़ ( मूलांक ट्रिक्स ) द्वारा मूलांक प्रकार के कार्यान्वयन पर एक नज़र डालें । std::sortमेरे पहले उत्तर में एल्गोरिथ्म की तुलना में मेरी मशीन छँटाई 5 गुना तेज थी । सॉर्टिंग फ़ंक्शन का नाम है RadixSort11

int main(void)
{
    std::ifstream ifs("C:\\Temp\\gaussian.dat", std::ios::binary | std::ios::in);
    std::vector<float> v;
    v.reserve(50000000);
    double d;
    while (ifs.read(reinterpret_cast<char*>(&d), sizeof(double)))
        v.push_back(static_cast<float>(d));
    std::vector<float> vres(v.size(), 0.0);
    clock_t c0 = clock();
    RadixSort11(&v[0], &vres[0], v.size());
    std::cout << "Finished after: "
              << static_cast<double>(clock() - c0) / CLOCKS_PER_SEC << std::endl;
    return 0;
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.