gpt4 book ai didi

f# - 聚合函数 - f# vs c# 性能

转载 作者:行者123 更新时间:2023-12-04 19:58:39 25 4
gpt4 key购买 nike

我有一个经常使用的功能,因此性能需要尽可能好。它从 excel 中获取数据,然后根据数据是否在某个时间段内以及是否是高峰时段(周一至周五 8 至 20 日)对部分数据进行求和、平均或计数。

数据通常约为 30,000 行和 2 列(每小时日期、值)。数据的一个重要特征是日期列按时间顺序排列

我有三个实现,带有扩展方法的 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)

我看到两个问题:
  • 我需要准备数据,因为日期和值都是 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# 版本的速度吗?
  • 有人能想到更好的方法吗?
    非常感谢!

  • 更新:速度比较

    我设置了一个测试数组,其每小时日期为 1/1/13-31/12/15(大约 30,000 行)和相应的值。我在日期数组上进行了 150 次调用,并重复了 100 次 - 15000 次函数调用:

    我上面的 csharp 实现(在循环之外使用 string.compare)

    1.36 秒

    马修斯递归 fsharp

    1.55 秒

    托马斯数组 fsharp

    1分40秒

    我原来的 fsharp

    2分20秒

    显然,这对我的机器来说总是主观的,但给出了一个想法,人们要求它......

    我还认为应该记住,这并不意味着递归或 for 循环总是比 array.map 等更快,只是在这种情况下,它会进行很多不必要的迭代,因为它没有 c# 和 f# 的早期退出迭代递归方法有

    最佳答案

    使用 Array而不是 ListSeq使这大约快 3-4 倍。您不需要生成索引列表,然后将其映射到两个数组中的查找项目 - 相反,您可以使用 Array.zip将两个数组组合成一个数组,然后使用 Array.filter .

    一般来说,如果你想要性能,那么使用数组作为你的数据结构是有意义的(除非你有很长的管道)。 Array.zip 等函数和 Array.map可以计算整个数组大小,分配它,然后执行有效的命令式操作(同时从外部看起来仍然有效)。

    let aggrF (data:float[]) (data2:float[]) std edd pob sac =
    let newd =
    Array.zip data data2
    |> Array.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 |> Array.averageBy (fun (_, value) -> value)
    | 2 -> newd |> Array.sumBy (fun (_, value) -> 1.0)
    | _ -> newd |> Array.sumBy (fun (_, value) -> value)

    我也改了 isbetween - 它可以简化为一个表达式,你可以标记它 inline ,但这并没有增加那么多:
    let inline isbetween r std edd = r >= std && r < edd+1.

    为了完整起见,我使用以下代码(使用 F# Interactive)对此进行了测试:
    #time 
    let d1 = Array.init 1000000 float
    let d2 = Array.init 1000000 float
    aggrF d1 d2 0.0 1000000.0 "Test" 0

    原始版本约为 600 毫秒,使用数组的新版本需要 160 毫秒到 200 毫秒。 Matthew 的版本大约需要 520 毫秒。

    除此之外,我在 BlueMountain Capital 度过了最后两个月,致力于 F# 的时间序列/数据框架库,这将使这变得更简单。它正在进行中,库的名称也将更改,但您可以在 BlueMountain GitHub 中找到它.代码看起来像这样(它使用时间序列是有序的事实,并在过滤之前使用切片来获取相关部分):
    let ts = Series(times, values)
    ts.[std .. edd] |> Series.filter (fun k _ -> not (ispeak k)) |> Series.mean

    目前,这不会像直接数组操作那样快,但我会研究一下:-)。

    关于f# - 聚合函数 - f# vs c# 性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19451198/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com