1500以上の行(多くの重複がある)を読み込むcsvパーサーがあり、構造体に解析されたらコレクションに追加する必要があります。各構造体には、プロパティKey(int)、A(datetime)、およびB(int)(およびここでは関係のない他のプロパティ))があります。
要件A:コレクションはキーによる一意性を強制する必要があります。
要件B:後のステップで、プロパティA(timestamp) then B(int)でソートされたコレクションが必要です。
制約:構造体は、最終的に、隣人への参照を使用して、順番に1つずつトラバースする必要があります(LinkedListはここで最もクリーンなソリューションを示します)。この操作のポイントは、セットを分割することです。これがパーティショニングが発生する可能性のある最も早いものであると仮定してください(つまり、解析段階でパーティショニングすることができません)。
SortedSetは要件Aで非常にうまく機能し、O(log n)の挿入はHashSet<T>
のO(1)よりもはるかに遅いにもかかわらず、非常にパフォーマンスが高いことがわかりましたキーでのソートに注意してください。コレクションが巨大になるとHashSet<T>
は行き詰まりますが、これは明らかに既知の問題ですが、SortedSet<T>
はこの欠点を被りません。
問題:要件Bのステップに到達すると、コレクション(SortedSet<T>
としてメソッドに渡されるIEnumerable<T>
)のソートに時間がかかりすぎます(20+数分の粉砕、すべてメモリ内、ページファイルの使用なし)。
質問:この問題に対処するのに最適なコレクションはどれですか? 1つのアイデアは、2つのコレクションを使用することです。1つは一意性(キーのHashSet<int>
またはSortedSet<int>
など)を適用し、2つ目のSortedSet<T>
は解析段階でソートを処理します可能な限りアップストリーム)。しかし、アプリケーションはすでにメモリを集中的に使用しているため、ページファイルを必要とすることによるパフォーマンスのペナルティは法外です。
1つの特性による一意性を強制するが、他の無関係な特性によるソートを行う単一のコレクションに対して、どのようなオプションがありますか? SortedSet<T>
はIComparer<T>
(ただしIComparer<T>
とIEquitable<T>
の両方ではない)を使用するため、CompareToに依存して一意性を適用する場合、要件に合わないようです。 SortedSetをサブクラス化する方法はありますか?
編集:ソートコード:
SortedSet<Dto> parsedSet = {stuff};
var sortedLinkedStructs = new LinkedList<Dto>(parsedSet.OrderBy(t => t.Timestamp).ThenBy(i => i.SomeInt));
構造体:
public readonly struct Dto: IEquatable<Dto>, IComparer<Dto>, IComparable<Dto>
{
public readonly datetime Timestamp;
public readonly int SomeInt;
public readonly int Key;
ctor(ts, int, key){assigned}
public bool Equals(Dtoother) => this.Key == other.Key;
public override int GetHashCode() => this.Key.GetHashCode();
public int Compare(Dto x, Dto y) => x.Key.CompareTo(y.Key);
public int CompareTo(Dto other) => this.Key.CompareTo(other.Key);
}
これは直接的な答えではないかもしれませんが、それは私が同じような規模の同じようなシステムにうまく使った方法です。これは、スタックオーバーフローに関する質問リストを駆動する「タグエンジン」用です。基本的に、私は:
struct Question {
// basic members - score, dates, id, etc - no text
}
および基本的にオーバーサイズのQuestion[]
(実際にはアンマネージメモリでQuestion*
を使用しますが、それはGPUコードと共有できる必要があるためです無関係な理由のため)。データの取り込みは、Question[]
の連続する行を取り出すだけです。このデータはソートされません-ソースデータとしてそのまま残されます-追加(新しいキー)または上書き(同じキー)だけがあります。 最悪の場合最大容量に達した場合、データを新しい配列に再割り当てしてブロックコピーする必要があるかもしれません。
今、そのデータをソートする代わりに、私はseparatelyを保持しますint[]
(以前と同じ理由で、実際にはint*
ですが... meh)、 int[]
は、Question[]
内のactualデータのindexです。そのため、最初は0, 1, 2, 3, 4, 5, ...
になる可能性があります(これを事前にフィルタリングするため、保持したい行のみが含まれます-「削除済み」の削除など)。
either修飾子並列クイックソート( http://stackoverflow.com/questions/1897458/parallel-sort-algorithm を参照)または変更された「内省的」 sort "( here など)-ソートの最後に0, 3, 1, 5, ...
が表示される場合があります。
さて、データを反復処理するには、int[]
を反復処理し、それをQuestion[]
のactualデータのルックアップとして使用します。これにより、ソート中のデータの移動量が最小限に抑えられ、複数の個別のソート(おそらく異なる事前フィルターを使用)を非常に効率的に保持できます。 15Mのデータを並べ替えるのに数ミリ秒しかかかりません(これは1分ごとに発生し、新しい質問をStack Overflowに取り込むか、既存の質問への変更を記録します)。
できるだけ速くソートを行うために、複合ソートをsingle整数値で表すことができ、非常に効果的なソート(内省的ソートで使用可能)を可能にするソートコードを記述しようとします。たとえば、「最後のアクティビティ日、次に質問ID」ソートのコードは次のとおりです。
public override bool SupportsNaturallySortableUInt64 => true;
public override unsafe ulong GetNaturallySortableUInt64(Question* question)
{
// compose the data (MSB) and ID (LSB)
var val = Promote(question->LastActivityDate) << 32
| Promote(question->Id);
return ~val; // the same as ulong.MaxValue - val (which reverses order) but much cheaper
}
これは、LastActivityDate
を32ビット整数として扱い、32ビット左シフトし、Id
を32ビット整数として構成することにより機能します。つまり、日付とidを比較できます。単一の操作で。
または、「スコア、回答スコア、ID」の場合:
public override unsafe ulong GetNaturallySortableUInt64(Question* question)
{
// compose the data
var val = Promote(question->Score) << 48
| Promote(question->AnswerScore) << 32
| Promote(question->Id);
return ~val; // the same as ulong.MaxValue - val (which reverses order) but much cheaper
}
GetNaturallySortableUInt64
は要素ごとに1回だけ呼び出されることに注意してください。同じサイズのulong[]
(はい、実際にはulong*
)の作業領域に入れられるため、最初は2つのワークスペースは次のようになります:
int[] ulong[]
0 34243478238974
1 12319388173
2 2349245938453
... ...
これで、int[]
とulong[]
を見るだけで並べ替えを行うことができ、ulong[]
ベクトルが並べ替えられた順序になり、int[]
には見るアイテムのインデックス。