Конкурс непрохідного коду: Не дуже швидкий сортування [закрито]


28

Завдання

Напишіть програму на обраній вами мові, яка зчитує рядки введення зі стандартного вводу до EOF, а потім записує їх на стандартний вихід у ASCIIбетичному порядку, аналогічно програмі sortкомандного рядка. Короткий короткий приклад в Python:

import sys

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

Нижня частина

Як і у програмі «Війна ОС» , ваша мета полягає у тому, щоб довести, що ваша улюблена платформа «краща», шляхом того, щоб ваша програма свідомо працювала набагато повільніше на конкуруючій платформі. Заради цього конкурсу «платформа» складається з будь-якої комбінації:

  • Процесор
    • Архітектура (x86, Alpha, ARM, MIPS, PowerPC тощо)
    • Біт (64-бітний проти 32-бітний проти 16-розрядний)
    • Великий проти малого ендіану
  • Операційна система
    • Windows проти Linux проти Mac OS тощо.
    • Різні версії однієї операційної системи
  • Мовна реалізація
    • Різні постачальники компіляторів / перекладачів (наприклад, MSVC ++ проти GCC)
    • Різні версії одного компілятора / перекладача

Хоча ви можете відповідати вимогам, написавши такий код:

#ifndef _WIN32
    Sleep(1000);
#endif

Таку відповідь не слід оскаржувати. Мета - бути тонким. В ідеалі ваш код повинен виглядати так, що він взагалі не залежить від платформи. Якщо ж є якісь - або #ifdefзаяви (або умови , засновані на os.nameабо System.Environment.OSVersionабо будь-який інший ), вони повинні мати правдоподібне обгрунтування (на основі брехні, звичайно).

Включіть у свою відповідь

  • Кодекс
  • Ваші "улюблені" та "нечесні" платформи.
  • Вхід, з яким можна протестувати свою програму.
  • Час роботи на кожній платформі, для одного входу.
  • Опис того, чому програма так повільно працює на нечесній платформі.

4
Це важче, ніж я думав. Єдині відповіді, які я можу придумати, є або дуже довгими і трохи очевидними, або дуже короткими і надзвичайно очевидними :-(
писклява косточка

2
Я голосую за те, щоб закрити це питання як позатематичне, тому що недостатньо важкі виклики вже не є темою на цьому сайті. meta.codegolf.stackexchange.com/a/8326/20469
кіт

Відповіді:


29

С

CleverSort

CleverSort - це найсучасніший (тобто надмірно сконструйований та недооптимальний) двоступеневий алгоритм сортування рядків.

На кроці 1 він починається попередньою сортуванням вхідних рядків за допомогою радіоскопічного сортування та перших двох байтів кожного рядка. Сорт Radix є порівняльним і дуже добре працює для рядків.

На кроці 2 використовується сортування вставок у попередньо відсортованому списку рядків. Оскільки список майже відсортований після кроку 1, сортування вставки є досить ефективним для цього завдання.

Код

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

Платформи

Усі ми знаємо, що машини з великим ендіаном набагато ефективніші, ніж їхні маломісткі колеги. Для порівняльного аналізу ми складемо CleverSort з увімкненими оптимізаціями та створимо випадковим чином величезний список (трохи більше 100 000 рядків) 4-байтових рядків:

$ 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

Еталон великого рівня

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

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

Не надто пошарпаний.

Мало-ендіанський візит

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

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

Бу, маленький Ендіан! Бу!

Опис

Сортування вставки насправді досить ефективне для майже відсортованих списків, але жахливо неефективне для випадково відсортованих.

Нижня частина CleverSort - це макрос FIRSTSHORT :

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

На машинах з великим ендіаном впорядкування рядка з двох 8-бітних цілих чисел лексикографічно або перетворення їх у 16-бітні цілі числа та впорядкування їх після цього дає ті ж результати.

Звичайно, це можливо і на малоінтенсивних машинах, але макрос повинен був бути

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

яка працює, як очікувалося, на всіх платформах.

Наведений вище показник "великого ендіану" насправді є результатом використання відповідного макросу.

З неправильним макросом і малоінтенсивним машиною список попередньо сортується за другим символом кожного рядка, в результаті чого відбувається випадкове впорядкування з лексикографічної точки зору. Різновиди введення в цьому випадку ведуть себе дуже погано.


16

Python 2 проти Python 3

Очевидно, Python 3 на кілька порядків швидший, ніж Python 2. Візьмемо для прикладу цю реалізацію алгоритму Shellsort :

Код

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

Орієнтир

Підготуйте тестовий вхід. Це взято з відповіді Денніса, але з меншою кількістю слів - Python 2 - так повільно ...

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

Пітон 2

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

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

Пітон 3

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

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

Де знаходиться неприхований код?

Я припускаю, що деякі читачі, можливо, захочуть відшукати хитрощів, тому я приховую відповідь тегом спойлера.

Трюк - це ціле ділення в обчисленні max_sequence_element. У Python 2 1/2оцінюється до нуля, а значить, вираз завжди дорівнює нулю. Однак поведінка оператора змінилася на поділ з плаваючою комою в Python 3. Оскільки ця змінна контролює довжину послідовності розривів, що є критичним параметром Shellsort, Python 2 закінчується з використанням послідовності, яка містить лише число один, поки Python 3 використовує правильну послідовність. Це призводить до квадратичного часу виконання для Python 2.

Бонус 1:

Ви можете виправити код, просто додавши крапку після обчислення 1або 2в.

Бонус 2:

Принаймні, на моїй машині Python 2 трохи швидше, ніж Python 3, коли працює фіксований код ...


Гарно зіграно! Час Nitpix: flagвиглядає лише для запису, ви не можете його видалити? Крім того, rздається зайвим, якщо ви це зробите if lst[i+h] < lst[i]: .... З іншого боку, якщо ви тримаєте, rнавіщо робити своп? Ти не міг просто так зробити lst[i+h] = lst[i]? Чи все це навмисне відволікання?
Jonas Kölker
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.