- PR -

C# マージソートの高速化について

1
投稿者投稿内容
そんぴん
会議室デビュー日: 2006/08/16
投稿数: 16
投稿日時: 2009-01-31 07:11
C#を使い、下のようなコードでマージソートを実現していますが
自分のソフトの中では特にパフォーマンスが要求される部分です。
より高速化するにはどうしたらいいでしょう。

諸先輩方、アドバイスお願い致します


//配列のインデックス[n,1]をキーにソート
//flipf=true 小さい順に並べる
private void mergesort(ref decimal[,] tmp, ref decimal[,] data, int keyindex, int First, int Last, Boolean flipF)//マージソート
{
int i, j, k, p, mid;


if (First < Last)
{

mid = (int)((First + Last) / 2);
mergesort(ref tmp, ref data, keyindex,First, mid, flipF);
mergesort(ref tmp, ref data, keyindex, mid + 1, Last, flipF);
p = 0; //作業領域のインデックス
for (i = First; i <= mid; i++)
{
tmp[p,0] = data[i,0];
tmp[p,1] = data[i,1];
tmp[p,2] = data[i,2];
p++;
}
i = mid + 1; j = 0; k = First;
while ((i <= Last) && (j < p))
{
if ((flipF && (tmp[j, keyindex] > data[i, keyindex]))
|| (!flipF && (tmp[j, keyindex] < data[i, keyindex])))
{
data[k,0] = data[i,0];
data[k, 1] = data[i, 1];
data[k, 2] = data[i, 2];
k++; i++;
}
else
{
data[k,0] = tmp[j,0];
data[k, 1] = tmp[j, 1];
data[k, 2] = tmp[j, 2];
k++; j++;
}
}
while (j < p)
{
data[k,0] = tmp[j,0];
data[k, 1] = tmp[j, 1];
data[k, 2] = tmp[j, 2];
k++; j++;
}

}

}
やんち
常連さん
会議室デビュー日: 2008/10/24
投稿数: 32
投稿日時: 2009-01-31 09:41
ソートプログラムを高速化するために最適化を行う場合、

1)実際に運用で使用する予定のデータ件数で実行検証を行う必要がある
データ件数がわからないと、どのような最適化が効果的であるか予想できません。

2)元のプログラムでどれ位実行時間が掛かるのか計測してみる。
実際に実行時間を計測し、目標とする実行時間を提示し、最適化方法を模索しないと、
どこまで最適化を進めるべきなのか明確にならない。

3)再帰処理は2重ループに書き直しましょう。
再帰処理を使うとアルゴリズムの実装は簡単になりますが、
2重ループの方が、消費メモリを大幅に節約できます。
2重ループに書き直した方がパフォーマンスも幾分か改善されるでしょう。
(データ件数によっては誤差程度しか高速化しないかもしれません)

また、データの特性によってはマージソート以外のアルゴリズム
の方が、高速化できるかもしれませんので、こちらも検討の必要があると思います。

また、使用可能メモリの制限が厳しく、マージソートを使用せざるを得ない場合でも、
マージソートと他のソートアルゴリズムを組み合わせる事によって、
高速化を行える場合があります。
(実際に、ソートアルゴリズムを複数組み合わせて、最適化を行う事はよくあります)
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2009-01-31 09:51
引用:

そんぴんさんの書き込み (2009-01-31 07:11) より:
C#を使い、下のようなコードでマージソートを実現していますが
自分のソフトの中では特にパフォーマンスが要求される部分です。
より高速化するにはどうしたらいいでしょう。


ソートというものは、速度は基本的にどのソートアルゴリズムを使うかで決まってしまいます。あとは実装をどうするかだけなのですが、そんなに工夫する余地は残っていません。通常は、標準で付属のライブラリーを使う以外の選択肢はなく、C# でしたら Array.Sort メソッドあたりを使うことになると思います。

提示されたコードはそれより速いのでしょうか?まずはそこから調べられることをお勧めします。
そんぴん
会議室デビュー日: 2006/08/16
投稿数: 16
投稿日時: 2009-02-01 10:22
現在のコードはかなり最適化を進めた結果ですが、
行き詰って、質問させていただきました。

配列のデータ入出力にポインタが使えていないのがネックのように感じています。

>unibonさん
Arrayクラスに2次元配列のソートはあるのでしょうか?
あれば比較が可能です。もしやり方があれば、ご教示お願い致します。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2009-02-01 13:18
引用:

そんぴんさんの書き込み (2009-02-01 10:22) より:
Arrayクラスに2次元配列のソートはあるのでしょうか?
あれば比較が可能です。もしやり方があれば、ご教示お願い致します。


私は、よっぽどのことがないかぎり、標準のライブラリーが使うインターフェースに合わせておくほうが良いと思います。すなわち Arrays.Sort や List クラスの Sort メソッドなどに、自分のプログラムを合わせます。
ソートは古典的なものですので、C# も Java もすでに十分洗練されたインターフェースを持っていると私は考えます。

最悪の場合、インターフェースを合わせるために、要素すべてのコピーが必要になるかもしれませんが、メモリーは最大でも今の2倍ほど必要になるだけですし、コピーの速度は O(n) ですので、さほど問題にはならないはずです。(ここで O はいわゆるラージオー。)
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2009-02-02 00:09
ためしに Arrays.Sort を使うサンプルコードを書いてみました。ケアレスミス的なバグはあるかもしれませんが、コピーの手間を入れても、提示されたコード(mergesort)よりも、3割ほど速かったです。
コード:
using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace Hoge
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // 3百万件の例
            decimal[,] data = new decimal[3000000, 3];
            for (int i = 0; i < data.GetLength(0); i++)
            {
                for (int j = 0; j < data.GetLength(1); j++)
                {
                    // サンプル実行用のテキトーな値(降順の値)を入れる。
                    data[i, j] = data.GetLength(0) - 1 - i;
                }
            }

            DateTime a = DateTime.Now;
            decimal[][] array = new decimal[data.GetLength(0)][];
            for (int i = 0; i < data.GetLength(0); i++)
            {
                array[i] = new decimal[] { data[i, 0], data[i, 1], data[i, 2] }; 
            }
            DateTime b = DateTime.Now;

            int keyIndex = 0;
            bool flipFlag = true;
            Array.Sort(array, new MyComparer(keyIndex, flipFlag));

            DateTime c = DateTime.Now;
            for (int i = 0; i < data.GetLength(0); i++)
            {
                data[i, 0] = array[i][0];
                data[i, 1] = array[i][1];
                data[i, 2] = array[i][2];
            }
            DateTime d = DateTime.Now;
            Console.WriteLine("ab = " + (b - a));
            Console.WriteLine("bc = " + (c - b));
            Console.WriteLine("cd = " + (d - c));
            Console.WriteLine("ad = " + (d - a));
        }
    }

    class MyComparer : IComparer<decimal[]>
    {
        private int keyIndex;
        private bool flipFlag;

        public MyComparer(int argKeyIndex, bool argFlipFlag)
        {
            keyIndex = argKeyIndex;
            flipFlag = argFlipFlag;
        }

        public int Compare(decimal[] x, decimal[] y)
        {
            if (x[keyIndex] < y[keyIndex])
            {
                return flipFlag ? -1 : 1;
            }
            else if (x[keyIndex] == y[keyIndex])
            {
                return 0;
            }
            else if (x[keyIndex] > y[keyIndex])
            {
                return flipFlag ? 1 : -1;
            }
            else
            {
                throw new Exception();
            }
        }
    }
}

unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2009-02-02 01:28
コンパレーター(じゃなくて .NET だとコンパラー?コンペアラー?)を最適化するともう少し(さっきより1〜2割ほど)速くなるみたいです。
コード:
    class MyComparer : IComparer<decimal[]>
    {
        private int keyIndex;
        private bool flipFlag;

        public MyComparer(int argKeyIndex, bool argFlipFlag)
        {
            keyIndex = argKeyIndex;
            flipFlag = argFlipFlag;
        }

        public int Compare(decimal[] x, decimal[] y)
        {
            return flipFlag ? decimal.Compare(x[keyIndex], y[keyIndex]) 
                            : decimal.Compare(y[keyIndex], x[keyIndex]);
        }
    }


私はこのへんが限界で、あとはそもそも Sort が必要なのか、や、件数を減らせないか、や、ソートは別スレッドで裏で動かすなどのようなことはできないか、などというあたりを検討したほうが良いと思います。
そんぴん
会議室デビュー日: 2006/08/16
投稿数: 16
投稿日時: 2009-02-02 18:40

>>私は、よっぽどのことがないかぎり、標準のライブラリーが使うインターフェースに>>合わせておくほうが良いと思います。

私もこの意見に賛成です。ごちゃごちゃ小細工しても、結局大差ないか遅くなるのがオチのような気がします。

それから、

サンプルコードありがとうございました。大変参考になります。いただいたコードを元に実装テストをしてみます。
1

スキルアップ/キャリアアップ(JOB@IT)