f#の実装では、結果のキーの構造比較を使用します。
let sortBy keyf seq =
let comparer = ComparisonIdentity.Structural
mkDelayedSeq (fun () ->
(seq
|> to_list
|> List.sortWith (fun x y -> comparer.Compare(keyf x,keyf y))
|> to_array) :> seq<_>)
(並べ替えも)
let sort seq =
mkDelayedSeq (fun () ->
(seq
|> to_list
|> List.sortWith Operators.compare
|> to_array) :> seq<_>)
Operators.compareとComparisonIdentity.Structural.Compareの両方が(最終的に)になります
let inline GenericComparisonFast<'T> (x:'T) (y:'T) : int =
GenericComparisonIntrinsic x y
// lots of other types elided
when 'T : float = if (# "clt" x y : bool #)
then (-1)
else (# "cgt" x y : int #)
ただし、Operatorのこれへのルートは完全にインラインであるため、JITコンパイラは、(いずれの場合も必要な)デリゲート呼び出しを除いて、追加のメソッド呼び出しオーバーヘッドなしで直接二重比較命令を挿入することになります。
sortByは比較子を使用するため、追加の仮想メソッド呼び出しを実行しますが、基本的にはほぼ同じです。
比較すると、OrderBy関数も(Using EqualityComparer<T>.Default
)の仮想メソッド呼び出しを実行する必要がありますが、重要な違いは、その場で並べ替えられ、結果としてこのために作成されたバッファーを使用することです。比較すると、sortByを見ると、リストがソートされ(マージソートのように見えるStableSortImplementationが使用されています)、そのコピーが新しい配列として作成されていることがわかります。この追加のコピー(入力データのサイズを考えると)は、異なるソート実装も影響を与える可能性がありますが、速度低下の主な原因である可能性があります。
とはいえ、これはすべて推測です。この領域がパフォーマンスの観点から懸念事項である場合は、単にプロファイルを作成して、何が時間がかかっているかを確認する必要があります。
並べ替え/コピーの変更がどのような影響を与えるかを確認したい場合は、次の方法を試してください。
// these are taken from the f# source so as to be consistent
// beware doing this, the compiler may know about such methods
open System.Collections.Generic
let mkSeq f =
{ new IEnumerable<'b> with
member x.GetEnumerator() = f()
interface System.Collections.IEnumerable with
member x.GetEnumerator() = (f() :> System.Collections.IEnumerator) }
let mkDelayedSeq (f: unit -> IEnumerable<'T>) =
mkSeq (fun () -> f().GetEnumerator())
// the function
let sortByFaster keyf seq =
let comparer = ComparisonIdentity.Structural
mkDelayedSeq (fun () ->
let buffer = Seq.to_array seq
Array.sortInPlaceBy (fun x y -> comparer.Compare(keyf x,keyf y)) buffer
buffer :> seq<_>)
非常に大きな(>百万)入力シーケンスを使用すると、repl内で妥当なパーセンテージのスピードアップが得られますが、桁違いのようなものはありません。いつものように、あなたのマイレージは変わるかもしれません。