頻繁に使用する関数があるため、パフォーマンスを可能な限り向上させる必要があります。Excel からデータを取得し、データが特定の期間内にあるかどうか、およびピーク時 (Mo-Fr 8-20) であるかどうかに基づいて、データの一部を合計、平均、またはカウントします。
通常、データは約 30,000 行と 2 列 (時間ごとの日付、値) です。データの重要な特徴の 1 つは、日付列が時系列順に並べられていることです。
私は 3 つの実装を持っています。c# には拡張メソッドがあります (非常に遅く、誰かが興味を持っていない限り、それを示すつもりはありません)。
次に、この f# 実装があります。
let ispeak dts =
let newdts = DateTime.FromOADate dts
match newdts.DayOfWeek, newdts.Hour with
| DayOfWeek.Saturday, _ | DayOfWeek.Sunday, _ -> false
| _, h when h >= 8 && h < 20 -> true
| _ -> false
let internal isbetween a std edd =
match a with
| r when r >= std && r < edd+1. -> true
| _ -> false
[<ExcelFunction(Name="aggrF")>]
let aggrF (data:float[]) (data2:float[]) std edd pob sac =
let newd =
[0 .. (Array.length data) - 1]
|> List.map (fun i -> (data.[i], data2.[i]))
|> Seq.filter (fun (date, _) ->
let dateInRange = isbetween date std edd
match pob with
| "Peak" -> ispeak date && dateInRange
| "Offpeak" -> not(ispeak date) && dateInRange
| _ -> dateInRange)
match sac with
| 0 -> newd |> Seq.averageBy (fun (_, value) -> value)
| 2 -> newd |> Seq.sumBy (fun (_, value) -> 1.0)
| _ -> newd |> Seq.sumBy (fun (_, value) -> value)
これには 2 つの問題があります。
- 日付も値もdoubleなのでデータを用意する必要があります[]
- 日付が時系列であるという知識を利用していないため、不必要な反復を行っています。
これが、ブルート フォースの命令型 C# バージョンと呼ばれるものです。
public static bool ispeak(double dats)
{
var dts = System.DateTime.FromOADate(dats);
if (dts.DayOfWeek != DayOfWeek.Sunday & dts.DayOfWeek != DayOfWeek.Saturday & dts.Hour > 7 & dts.Hour < 20)
return true;
else
return false;
}
[ExcelFunction(Description = "Aggregates HFC/EG into average or sum over period, start date inclusive, end date exclusive")]
public static double aggrI(double[] dts, double[] vals, double std, double edd, string pob, double sumavg)
{
double accsum = 0;
int acccounter = 0;
int indicator = 0;
bool peakbool = pob.Equals("Peak", StringComparison.OrdinalIgnoreCase);
bool offpeakbool = pob.Equals("Offpeak", StringComparison.OrdinalIgnoreCase);
bool basebool = pob.Equals("Base", StringComparison.OrdinalIgnoreCase);
for (int i = 0; i < vals.Length; ++i)
{
if (dts[i] >= std && dts[i] < edd + 1)
{
indicator = 1;
if (peakbool && ispeak(dts[i]))
{
accsum += vals[i];
++acccounter;
}
else if (offpeakbool && (!ispeak(dts[i])))
{
accsum += vals[i];
++acccounter;
}
else if (basebool)
{
accsum += vals[i];
++acccounter;
}
}
else if (indicator == 1)
{
break;
}
}
if (sumavg == 0)
{
return accsum / acccounter;
}
else if (sumavg == 2)
{
return acccounter;
}
else
{
return accsum;
}
}
これははるかに高速です(主に期間が終了したときにループが終了するためだと思います)が、明らかに簡潔ではありません。
私の質問:
ソートされたシリーズの f# Seq モジュールで反復を停止する方法はありますか?
f# バージョンを高速化する別の方法はありますか?
誰かがこれを行うためのさらに良い方法を考えることができますか? どうもありがとう!
更新:速度比較
2013 年 1 月 1 日から 2015 年 12 月 31 日までの 1 時間ごとの日付 (約 30,000 行) と対応する値を含むテスト配列を設定しました。日付配列全体に 150 回の呼び出しを行い、これを 100 回繰り返しました - 15000 回の関数呼び出し:
上記の csharp の実装 (ループの外側に string.compare を使用)
1.36秒
マシューズ再帰 fsharp
1.55秒
トーマス配列 fsharp
1分40秒
私のオリジナルのシャープ
2分20秒
明らかに、これは常に私のマシンにとって主観的なものですが、アイデアを提供し、人々がそれを求めてきました...
また、これは再帰や for ループが array.map などよりも常に高速であることを意味するわけではないことに留意する必要があると思います。この場合、c# と f# の反復から早期に終了しないため、多くの不要な反復が行われます。再帰メソッドが持っている