LoginSignup
0
1

Radix Sortで浮動小数点数をソートする!

Posted at

Open In Colab

Radix Sortで浮動小数点数をソートする!

皆さん、基数ソート(Radix Sort)はご存知ですね?O(kN)のすごいやつです。実はMacに搭載されているBSDのsortコマンドは、--radixsortオプションがあったりします。ただし、このコマンドは数値には使えないとマニュアルに書いてあります。もともと文字列用に考案されたものだからでしょうか。そんな中、こんな記事を見つけました。整数はもとより、浮動小数点数でも基数ソート出来るよ〜という記事です。詳しくは記事を読んでいただくとして、この記事にはソースコードが付いています。これは早速ダウンロードしてColab上で性能検証したい!ということでやってみました。

乱数の生成

乱数を10億個生成します。20分以上かかります。

%%time
from random import random

with open('input', 'w') as fout:
    for _ in range(1000000000):
        print((random() - 0.5) * 1000000, file=fout)
CPU times: user 23min 45s, sys: 36.8 s, total: 24min 22s
Wall time: 24min 37s

sortコマンド

比較のため、sortコマンドでソートして、時間を測定します。40分近くかかります。

%%time
!sort -n input > output_sort
CPU times: user 11.2 s, sys: 1.37 s, total: 12.6 s
Wall time: 39min 30s
!wc -l output_sort
!head output_sort
1000000000 output_sort
-499999.99974544795
-499999.99890903855
-499999.9982175315
-499999.9977990386
-499999.9960182383
-499999.9955448005
-499999.99452409195
-499999.9943637242
-499999.9931208382
-499999.9897155051

ちゃんとソートできています。

std::sort

STLのsortを使ってソートしてみます。

%%writefile /content/std_sort_vector.cpp

#include <stdio.h>
#include <stdlib.h>
#include <chrono>
#include <vector>
#include <algorithm>

int main(int argc, char *argv[])
{
    char buf[BUFSIZ];
    std::vector<double> input;

    auto start = std::chrono::system_clock::now();
    while (fgets(buf, BUFSIZ, stdin) != NULL) {
        input.push_back(atof(buf));
    }
    auto end = std::chrono::system_clock::now();
    double elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count();
    fprintf(stderr, "load: %f sec\n", elapsed / 1000);

    start = std::chrono::system_clock::now();
    std::sort(input.begin(), input.end());
    end = std::chrono::system_clock::now();
    elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count();
    fprintf(stderr, "sort: %f sec\n", elapsed / 1000);

    start = std::chrono::system_clock::now();
    for (size_t i = 0; i < input.size(); i++)
    {
        printf("%.12lf\n", input[i]);
    }
    end = std::chrono::system_clock::now();
    elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count();
    fprintf(stderr, "out:  %f sec\n", elapsed / 1000);
}
Writing /content/std_sort_vector.cpp
!g++ -Ofast std_sort_vector.cpp
%%time
!./a.out < input > output_std_vector
load: 287.233000 sec
sort: 152.108000 sec
out:  1266.370000 sec
CPU times: user 7.85 s, sys: 876 ms, total: 8.73 s
Wall time: 28min 26s
!wc -l output_std_vector
!head output_std_vector
1000000000 output_std_vector
-499999.999745447945
-499999.998909038550
-499999.998217531480
-499999.997799038596
-499999.996018238307
-499999.995544800477
-499999.994524091948
-499999.994363724196
-499999.993120838189
-499999.989715505100

IOの時間が26分で同じとすると、sortコマンドのソートの時間は15分です。STLのsortは2分半ですから相当早い。速いとは聞いていましたが、浮動小数点数に特化しているのも効いている可能性があります。sortコマンドは高機能な分、余計な処理が挟まっているのかもしれません。

ska sort

それでは基数ソートを測定しましょう。

%cd /content
!git clone https://github.com/skarupke/ska_sort.git
/content
Cloning into 'ska_sort'...
remote: Enumerating objects: 16, done.[K
remote: Total 16 (delta 0), reused 0 (delta 0), pack-reused 16[K
Receiving objects: 100% (16/16), 14.25 KiB | 7.13 MiB/s, done.
Resolving deltas: 100% (4/4), done.
%%writefile /content/ska_sort.cpp
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <chrono>
#include "ska_sort/ska_sort.hpp"

int main(int argc, char *argv[])
{
    char buf[BUFSIZ];
    std::vector<double> input;
    double f;

    auto start = std::chrono::system_clock::now();
    while (fgets(buf, BUFSIZ, stdin) != NULL) {
        input.push_back(atof(buf));
    }
    auto end = std::chrono::system_clock::now();
    double elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count();
    fprintf(stderr, "load: %f sec\n", elapsed / 1000);

    start = std::chrono::system_clock::now();
    ska_sort(input.begin(), input.end());
    end = std::chrono::system_clock::now();
    elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count();
    fprintf(stderr, "sort: %f sec\n", elapsed / 1000);

    start = std::chrono::system_clock::now();
    for (size_t i = 0; i < input.size(); i++)
    {
        printf("%.12lf\n", input[i]);
    }
    end = std::chrono::system_clock::now();
    elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count();
    fprintf(stderr, "out:  %f sec\n", elapsed / 1000);
}
Writing /content/ska_sort.cpp
!g++ -Ofast ska_sort.cpp
%%time
!./a.out < input > output_ska
load: 279.663000 sec
sort: 59.707000 sec
out:  1266.158000 sec
CPU times: user 7.29 s, sys: 839 ms, total: 8.13 s
Wall time: 26min 46s
!wc -l output_ska
!head output_ska
1000000000 output_ska
-499999.999745447945
-499999.998909038550
-499999.998217531480
-499999.997799038596
-499999.996018238307
-499999.995544800477
-499999.994524091948
-499999.994363724196
-499999.993120838189
-499999.989715505100

速い。sort部分の処理時間はSTLのsortの半分以下です。IOを含めたコマンドとしては5%ほどの時間短縮ですが、内部で浮動小数点数のソートが必要なプログラムにはとても有効なことが分かりました。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1