アンダーハンドコードコンテスト:それほど速くない並べ替え[終了]


28

タスク

sortコマンド行プログラムと同様に、標準入力からEOFまで入力行を読み取り、ASCII順で標準出力に書き込むプログラムを、選択した言語で作成します。Pythonでの手つかずの短い例は次のとおりです。

import sys

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

下手な部分

OS Warと同様に、あなたの目標は、競合するプラットフォーム上でプログラムを意図的にはるかにゆっくり実行させることにより、お気に入りのプラットフォームが「より良い」ことを証明することです。このコンテストのために、「プラットフォーム」は以下の任意の組み合わせで構成されます。

  • プロセッサー
    • アーキテクチャ(x86、Alpha、ARM、MIPS、PowerPCなど)
    • ビットネス(64ビット対32ビット対16ビット)
    • ビッグエンディアンとリトルエンディアン
  • オペレーティング・システム
    • Windows vs. Linux vs. Mac OSなど
    • 同じオペレーティングシステムの異なるバージョン
  • 言語実装
    • さまざまなコンパイラ/インタープリターベンダー(例:MSVC ++とGCC)
    • 同じコンパイラー/インタープリターの異なるバージョン

次のようなコードを書くことで要件を満たすことができますが:

#ifndef _WIN32
    Sleep(1000);
#endif

そのような答えは支持されるべきではありません。目標は微妙なことです。理想的には、コードはプラットフォームにまったく依存していないように見えるはずです。あなたがいる場合行う任意の持っている#ifdef文(またはに基づいて、条件os.nameまたはSystem.Environment.OSVersionまたは何を)、彼らは(もちろん嘘に基づいて)もっともらしい正当性を持っている必要があります。

回答に含める

  • コード
  • あなたの「お気に入り」および「非お気に入り」プラットフォーム。
  • プログラムをテストするための入力。
  • 同じ入力に対する各プラットフォームでの実行時間。
  • お気に入りのプラットフォームでプログラムが非常にゆっくり実行される理由の説明。

4
これは思ったより難しいです。私が思いつく唯一の答えは、非常に長くて少し明白なもの、または非常に短くて非常に明白なもののいずれかです:
squeamish ossifrage 14年

2
このサイトでは、人手不足の課題がトピックではなくなったため、この質問をトピック外として終了することに投票しています。meta.codegolf.stackexchange.com/a/8326/20469
cat

回答:


29

C

クレバーソート

CleverSortは、最先端の(つまり、過剰に設計された、最適ではない)2ステップ文字列ソートアルゴリズムです。

ステップ1では、基数ソートと各行の最初の2バイトを使用して入力行を事前にソートすることから開始します。基数ソートは非比較的であり、文字列に対して非常にうまく機能します。

ステップ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をコンパイルし、4バイト行の巨大なリスト(100,000を超える文字列)をランダムに作成します。

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

ビッグエンディアンのマシンでは、2つの8ビット整数の文字列を辞書式に順序付けするか、16ビット整数に変換し、その後に順序付けしても同じ結果が得られます。

当然、これはリトルエンディアンのマシンでも可能ですが、マクロは

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

すべてのプラットフォームで期待どおりに機能します。

上記の「ビッグエンディアンベンチマーク」は、実際には適切なマクロを使用した結果です。

間違ったマクロとリトルエンディアンのマシンでは、リストは各行の2番目の文字で事前にソートされ、辞書編集の観点からランダムな順序になります。この場合、挿入ソートの動作は非常に悪くなります。


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

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

下手なコードはどこにありますか?

一部の読者はトリックスターを自分で追い詰めたいと思うので、ネタばれタグで答えを隠します。

トリックは、の計算における整数除算max_sequence_elementです。Python 2では1/2ゼロと評価されるため、式は常にゼロです。ただし、Python 3では演算子の動作が浮動小数点除算に変更されました。この変数は、Shellsortの重要なパラメーターであるギャップシーケンスの長さを制御するため、Python 2は、Python 3は正しいシーケンスを使用します。これにより、Python 2の2次実行時間が発生します。

ボーナス1:

1または2、計算の後にドットを追加するだけでコードを修正できます。

ボーナス2:

少なくとも私のマシンでは、固定コードを実行すると、Python 2はPython 3よりも少し高速です...


よくやった!Nitpix time:flag書き込み専用に見えますが、削除できませんでしたか?また、rそうすれば無駄だif lst[i+h] < lst[i]: ...。一方で、rなぜスワップするのかを維持する場合は?できませんlst[i+h] = lst[i]か?これはすべて意図的な気晴らしですか?
ジョナスケルカー14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.