Underhanded code yarışması: Çok hızlı değil sıralama [kapalı]


28

Görev

Seçtiğiniz dilde, standart girdiden EOF'ye kadar girdi satırlarını okuyan ve ardından sortkomut satırı programına benzer şekilde ASCIIbetical sırayla standart çıktıya yazan bir program yazın. Python'da kısa, desteklenmeyen bir örnek:

import sys

for line in sorted(sys.stdin):
    print(line.rstrip('\n'))

Desteklenen kısım

OS Savaşı'na benzer bir şekilde , amacınız, platformunuzun rakip bir platformda çok daha yavaş çalışmasını sağlayarak tercih ettiğiniz platformun “daha ​​iyi” olduğunu kanıtlamaktır. Bu yarışmanın uğruna, bir platform, aşağıdakilerin herhangi bir birleşiminden oluşur:

  • İşlemci
    • Mimari (x86, Alfa, ARM, MIPS, PowerPC vb.)
    • Bitness (64 bit vs. 32 bit vs. 16 bit)
    • Büyük-küçük-endian'a karşı
  • İşletim sistemi
    • Windows vs. Linux vs. Mac OS vb.
    • Aynı işletim sisteminin farklı versiyonları
  • Dil uygulaması
    • Farklı derleyici / tercüman satıcıları (örneğin, MSVC ++ vs. GCC)
    • Aynı derleyici / tercümanın farklı sürümleri

Gereksinimleri gibi kod yazarak gereksinimlerini karşılayabilir olsa da:

#ifndef _WIN32
    Sleep(1000);
#endif

Böyle bir cevap telafi edilmemelidir. Amaç ince olmaktır. İdeal olarak, kodunuz platforma bağlı değil gibi görünmelidir . Eğer varsa do birine sahip #ifdef(dayanan veya koşullar ifadeleri os.nameya System.Environment.OSVersionya neyse), onlar (tabii bir yalan, temelinde) makul bir gerekçe olmalıdır.

Cevabınıza ekleyin

  • Kod
  • “Favori” ve “unfavorite” platformlarınız.
  • Programınızı test etmek için bir giriş.
  • Aynı giriş için her platformda çalışma süresi.
  • Programın neden unfavorite platformunda bu kadar yavaş çalıştığının bir açıklaması .

4
Bu düşündüğümden daha zor. Ben ile gelip tek cevaplar çok uzun ya vardır ve biraz bariz ya da çok kısa ve son derece :-( bariz
alıngan ossifrage

2
Bu soruyu konu dışı olarak kapatmak için oy veriyorum, çünkü bu konudaki aşikar zorluklar artık konu dışı. meta.codegolf.stackexchange.com/a/8326/20469
kedi

Yanıtlar:


29

C

CleverSort

CleverSort, son teknoloji ürünü (yani aşırı tasarlanmış ve en iyi alt) iki aşamalı dize sıralama algoritmasıdır.

1. adımda, giriş satırlarını radix sıralama ve her satırın ilk iki baytı kullanarak önceden sıralayarak başlar . Radix sıralama karşılaştırmalı değildir ve karakter dizileri için çok iyi çalışır.

2. adımda , önceden sıralanmış dizeler listesinde ekleme sıralamasını kullanır . Liste neredeyse 1. adımdan sonra sıralandığından, bu sıralama için ekleme sıralama oldukça verimlidir.

kod

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Convert first two bytes of Nth line into integer

#define FIRSTSHORT(N) *((uint16_t *) input[N])

int main()
{
    char **input = 0, **output, *ptemp;
    int first_index[65536], i, j, lines = 0, occurrences[65536];
    size_t temp;

    // Read lines from STDIN

    while(1)
    {
        if(lines % 1000 == 0)
            input = realloc(input, 1000 * (lines / 1000 + 1) * sizeof(char*));

        if(getline(&input[lines], &temp, stdin) != -1)
            lines++;
        else
            break;
    }

    output = malloc(lines * sizeof(char*));

    // Radix sort

    memset(occurrences, 0, 65536 * sizeof(int));

    for(i = 0; i < lines; i++) occurrences[FIRSTSHORT(i)]++;

    first_index[0] = 0;

    for(i = 0; i < 65536 - 1; i++)
        first_index[i + 1] = first_index[i] + occurrences[i];

    memset(occurrences, 0, 65536 * sizeof(int));

    for(i = 0; i < lines; i++)
    {
        temp = FIRSTSHORT(i), output[first_index[temp] + occurrences[temp]++] = input[i];
    }

    // Insertion sort

    for(i = 1; i < lines; i++)
    {
        j = i;

        while(j > 0 && strcmp(output[j - 1], output[j]) > 0)
            ptemp = output[j - 1], output[j - 1] = output[j], output[j] = ptemp, j--;
    }

    // Write sorted lines to STDOUT

    for(i = 0; i < lines; i++)
        printf("%s", output[i]);
}

Platformlar

Hepimiz büyük-endian makinelerinin küçük-endian emsallerinden çok daha verimli olduğunu biliyoruz. Karşılaştırma için, CleverSort'u açık olan optimizasyonlarla derleyeceğiz ve rastgele olarak 4 baytlık satırlar içeren (100.000'den fazla dizeden oluşan) büyük bir liste oluşturacağız:

$ gcc -o cleversort -Ofast cleversort.c
$ head -c 300000 /dev/zero | openssl enc -aes-256-cbc -k '' | base64 -w 4 > input
$ wc -l input
100011 input

Big-endian benchmark

$ time ./cleversort < input > /dev/null

real    0m0.185s
user    0m0.181s
sys     0m0.003s

O kadar da eski püskü değil.

Küçük Endian Bechmark

$ time ./cleversort < input > /dev/null

real    0m27.598s
user    0m27.559s
sys     0m0.003s

Boo, küçük Endian! Boo!

Açıklama

Ekleme sıralama neredeyse sıralanan listeler için oldukça verimlidir, ancak rastgele sıralananlar için korkunç derecede verimsizdir.

CleverSort'un temel kısmı FIRSTSHORT makrosudur :

#define FIRSTSHORT(N) *((uint16_t *) input[N])

Büyük endian makinelerinde, iki 8 bitlik tamsayıdan oluşan bir dizgeyi sözlüksel olarak sıralamak veya bunları 16 bitlik tam sayılara dönüştürmek ve daha sonra sıralamak aynı sonuçları verir.

Doğal olarak, bu küçük-endian makinelerinde de mümkündür, ancak makro

#define FIRSTSHORT(N) (input[N][0] | (input[N][1] >> 8))

tüm platformlarda beklendiği gibi çalışıyor.

Yukarıdaki "big-endian benchmark" aslında doğru makroyu kullanmanın sonucudur.

Yanlış makro ve küçük bir endian makinesiyle, liste her satırın ikinci karakterine göre önceden sıralanır, bu da sözlükten bakış açısından rasgele bir sıralamaya yol açar. Ekleme sıralama bu durumda çok kötü davranır.


16

Python 2 ve Python 3

Açıkçası, Python 3, Python 2'den daha hızlı bir kaç büyüklük emridir . Shellsort algoritmasının bu uygulamasını örnek olarak alalım:

kod

import sys
from math import log

def shellsort(lst):

    ciura_sequence = [1, 4, 10, 23, 57, 132, 301, 701]  # best known gap sequence (Ciura, 2001)

    # check if we have to extend the sequence using the formula h_k = int(2.25h_k-1)
    max_sequence_element = 1/2*len(lst)
    if ciura_sequence[-1] <= max_sequence_element:
        n_additional_elements = int((log(max_sequence_element)-log(701)) / log(2.25))
        ciura_sequence += [int(701*2.25**k) for k in range(1,n_additional_elements+1)]
    else:
        # shorten the sequence if necessary
        while ciura_sequence[-1] >= max_sequence_element and len(ciura_sequence)>1:
            ciura_sequence.pop()

    # reverse the sequence so we start sorting using the largest gap
    ciura_sequence.reverse()

    # shellsort from http://sortvis.org/algorithms/shellsort.html
    for h in ciura_sequence:
        for j in range(h, len(lst)):
            i = j - h
            r = lst[j]
            flag = 0
            while i > -1:
                if r < lst[i]:
                    flag = 1
                    lst[i+h], lst[i] = lst[i], lst[i+h]
                    i -= h
                else:
                    break
            lst[i+h] = r

    return lst

# read from stdin, sort and print
input_list = [line.strip() for line in sys.stdin]
for line in shellsort(input_list):
    print(line)

assert(input_list==sorted(input_list))

Karşılaştırma

Bir test girişi hazırlayın. Bu Dennis'in cevabından alınmıştır ancak daha az kelime ile - Python 2 sooo yavaştır ...

$ head -c 100000 /dev/zero | openssl enc -aes-256-cbc -k '' | base64 -w 4 > input

Python 2

$ time python2 underhanded2.py < input > /dev/null 

real    1m55.267s
user    1m55.020s
sys     0m0.284s

Python 3

$ time python3 underhanded2.py < input > /dev/null 

real    0m0.426s
user    0m0.420s
sys     0m0.006s

Ana kod nerede?

Bazı okuyucuların hileci kendileri avlamak isteyebileceğini farz ediyorum, bu yüzden cevabı bir spoiler etiketiyle gizledim.

İşin püf noktası, hesaplamadaki tamsayı bölümüdür max_sequence_element. Python 2'de 1/2sıfırı değerlendirir ve dolayısıyla ifade her zaman sıfırdır. Bununla birlikte, operatörün davranışı Python 3'te kayan nokta bölünmesine dönüştü. Bu değişken Shellsort'un kritik bir parametresi olan boşluk dizisinin uzunluğunu kontrol ettiğinden, Python 2 Python iken yalnızca bir sayı içeren bir diziyi kullanarak sona erer. 3 doğru sırayı kullanır. Bu Python 2 için ikinci dereceden bir çalışma süresine neden olur.

Bonus 1:

Kodu , hesaplamada 1veya işleminden sonra bir nokta ekleyerek düzeltebilirsiniz 2.

Bonus 2:

En azından makinemde Python 2, sabit kodu çalıştırırken Python 3'ten biraz daha hızlı ...


Güzel oynadı! Nitpix zaman: flagsadece yazma görünüyor, çıkaramıyor musunuz? Ayrıca, ryaparsanız gereksiz görünüyor if lst[i+h] < lst[i]: .... Öte yandan, rneden değiş tokuş edersen ? Sadece yapamaz lst[i+h] = lst[i]mısın? Tüm bunlar kasıtlı bir dikkat dağıtıcı mı?
Jonas Kölker
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.