Kontes kode curang: Sortir tidak terlalu cepat [ditutup]

28

Tugas

Tulis sebuah program, dalam bahasa pilihan Anda, yang membaca jalur input dari input standar hingga EOF, dan kemudian menuliskannya ke output standar dalam urutan ASCIIbetikal, mirip dengan sortprogram baris perintah. Contoh pendek dan tidak licik dalam Python adalah:

import sys

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

Bagian yang curang

Mirip dengan Perang OS , tujuan Anda adalah untuk membuktikan bahwa platform favorit Anda adalah "lebih baik", dengan membuat program Anda sengaja berjalan jauh lebih lambat pada platform yang bersaing. Demi kontes ini, "platform" terdiri dari kombinasi dari:

  • Prosesor
    • Arsitektur (x86, Alpha, ARM, MIPS, PowerPC, dll.)
    • Bitness (64-bit vs 32-bit vs 16-bit)
    • Big- versus little-endian
  • Sistem operasi
    • Windows vs Linux vs Mac OS, dll.
    • Versi berbeda dari sistem operasi yang sama
  • Implementasi bahasa
    • Vendor kompiler / juru bahasa yang berbeda (mis., MSVC ++ vs. GCC)
    • Versi berbeda dari kompiler / juru bahasa yang sama

Meskipun Anda dapat memenuhi persyaratan dengan menulis kode seperti:

#ifndef _WIN32
    Sleep(1000);
#endif

Jawaban seperti itu seharusnya tidak dibatalkan. Tujuannya adalah untuk menjadi halus. Idealnya, kode Anda harus terlihat seperti tidak tergantung platform sama sekali. Jika Anda tidak memiliki #ifdefpernyataan (atau kondisi berdasarkan os.nameatau System.Environment.OSVersionatau apa pun), mereka harus memiliki pembenaran yang masuk akal (berdasarkan kebohongan, tentu saja).

Sertakan dalam jawaban Anda

  • Kode
  • Platform "favorit" dan "tidak disukai" Anda.
  • Masukan untuk menguji program Anda.
  • Waktu berjalan di setiap platform, untuk input yang sama.
  • Deskripsi mengapa program berjalan sangat lambat pada platform yang tidak disukai.
dan04
sumber
4
Ini lebih sulit daripada yang saya kira. Satu-satunya jawaban yang bisa saya
berikan
2
Saya memberikan suara untuk menutup pertanyaan ini sebagai di luar topik karena tantangan curang tidak lagi pada topik di situs ini. meta.codegolf.stackexchange.com/a/8326/20469
cat

Jawaban:

29

C

CleverSort

CleverSort adalah algoritma penyortiran string dua langkah yang canggih (yaitu over-engineered dan sub-optimal).

Pada langkah 1, itu dimulai dengan pra-pengurutan jalur input menggunakan pengurutan radix dan dua byte pertama dari setiap baris. Radix sort tidak komparatif dan bekerja sangat baik untuk string.

Pada langkah 2, ia menggunakan jenis penyisipan pada daftar string yang telah diurutkan sebelumnya. Karena daftar ini hampir diurutkan setelah langkah 1, jenis penyisipan cukup efisien untuk tugas ini.

Kode

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

Platform

Kita semua tahu bahwa mesin big-endian jauh lebih efisien daripada mesin kecil-endian mereka. Untuk pembandingan, kami akan mengompilasi CleverSort dengan optimisasi dihidupkan dan membuat daftar besar secara acak (lebih dari 100.000 string) dari baris 4-byte:

$ 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

Benchmark big-endian

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

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

Tidak terlalu buruk.

Bechmark Little-Endian

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

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

Boo, Endian kecil! Boo!

Deskripsi

Jenis penyisipan sangat efisien untuk daftar yang hampir diurutkan, tetapi sangat tidak efisien untuk daftar yang diurutkan secara acak.

Bagian curang CleverSort adalah makro FIRSTSHORT :

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

Pada mesin big-endian, memesan string dua bilangan bulat 8-bit secara leksikografis atau mengubahnya menjadi bilangan bulat 16-bit dan memesannya setelah itu menghasilkan hasil yang sama.

Secara alami, ini dimungkinkan pada mesin-mesin little-endian juga, tetapi makro seharusnya

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

yang berfungsi seperti yang diharapkan di semua platform.

"Benchmark big-endian" di atas sebenarnya adalah hasil dari menggunakan makro yang tepat.

Dengan mesin makro dan mesin little-endian yang salah, daftar diurutkan berdasarkan karakter kedua dari setiap baris, menghasilkan urutan acak dari sudut pandang leksikografis. Jenis penyisipan berperilaku sangat buruk dalam kasus ini.

Dennis
sumber
16

Python 2 vs. Python 3

Jelas, Python 3 beberapa kali lipat lebih cepat dari Python 2. Mari kita ambil contoh penerapan algoritma Shellsort ini :

Kode

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

Tolok ukur

Siapkan input tes. Ini diambil dari jawaban Dennis tetapi dengan lebih sedikit kata - Python 2 sangat lambat ...

$ 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

Di mana kode curangnya?

Saya berasumsi beberapa pembaca mungkin ingin memburu penipu itu sendiri, jadi saya menyembunyikan jawabannya dengan tag spoiler.

Caranya adalah pembagian integer dalam perhitungan max_sequence_element. Dalam Python 2 1/2bernilai nol dan karenanya ekspresi selalu nol. Namun, perilaku operator berubah menjadi pembagian titik mengambang di Python 3. Karena variabel ini mengontrol panjang urutan kesenjangan, yang merupakan parameter kritis Shellsort, Python 2 berakhir menggunakan urutan yang hanya berisi nomor satu sementara Python 3 menggunakan urutan yang benar. Ini menghasilkan waktu lari kuadratik untuk Python 2.

Bonus 1:

Anda dapat memperbaiki kode hanya dengan menambahkan titik setelah 1atau 2dalam perhitungan.

Bonus 2:

Setidaknya di komputer saya Python 2 sedikit lebih cepat dari Python 3 saat menjalankan kode tetap ...

René
sumber
Permainan yang bagus! Waktu Nitpix: flagterlihat hanya untuk menulis, tidak bisakah Anda menghapusnya? Juga, rtampaknya berlebihan jika Anda melakukannya if lst[i+h] < lst[i]: .... Di sisi lain, jika Anda tetap rmengapa melakukan swap? Tidak bisakah kamu melakukannya lst[i+h] = lst[i]? Apakah semua ini merupakan gangguan yang disengaja?
Jonas Kölker