在 F# 中比较两个字节数组的最快方法

Posted

技术标签:

【中文标题】在 F# 中比较两个字节数组的最快方法【英文标题】:Fastest way to compare two byte arrays in F# 【发布时间】:2014-10-28 10:04:23 【问题描述】:

我正在编写一个应用程序,它使用 Kinect 照片数据并在 F# 中比较两个帧。我正在使用 Rob Miles Learn the Kinect Api 第 74 页作为路标,但我没有使用指针并且性能受到影响。 Kinect 帧的字节为 1,228,800 字节。我是这样写比较的:

member this.differenceCount(currentImageBytes) =
    if  previousImageBytes |> Seq.length = 0 then
        previousImageBytes <- currentImageBytes
        0
    else
        let bytes = Seq.zip previousImageBytes currentImageBytes
        let differenceCount = bytes |> Seq.mapi(fun i e -> i, e)
                                    |> Seq.filter(fun (i,e) -> i % 4 <> 0 )
                                    |> Seq.map snd
                                    |> Seq.filter(fun (p,c) -> p <> c)
                                    |> Seq.length
        previousImageBytes <- currentImageBytes
        differenceCount

当我运行它时,屏幕会滞后,因为(我认为)处理数组花费的时间太长。此外,错误率接近50%。

1) 我处理问题的方法有误吗? 2) 有没有办法优化我的代码以加快速度?

【问题讨论】:

嗨 - 这个问题不是 F# 特定的(请参阅 ***.com/questions/43289/…) - 实际上你可以翻译那里的所有答案 - 但如果你真的需要速度(并且是在 Windwos 上)而不是从那里获取 PInvoke 答案(如果需要,您可以将其放入 C# dll 中)并使用它 - 您永远不会在 .net 中获得如此快的东西 我认为这是正确的轨道。我让 PInvoke 与 memcmp 一起工作,但它返回 0、1 或 -1。我需要差异的总数。有没有一种本地方法可以做到这一点? 哦 - 我想我错过了这个 - 我不知道这个 no 的本地版本 - 这是一个疯狂的猜测,但我敢打赌,做这样的事情的最好方法是移动它到 GPU 着色器(或编写低级 C/C++/Asm 实现) :-( 我不确定 GPU 着色器是什么。我看看是否有人编写了我可以这样使用的 C/C++ 实现:***.com/questions/15313646/… 着色器是在 GPU 中的一个着色器单元上运行的简单程序 - 由于 GPU 有很多这样的单元,您可以获得巨大的并行处理能力,当然这用于完成你的最新游戏——但我的意思是半开玩笑——现在我会寻找一个本地实现或采用约翰提议的方式 【参考方案1】:

您通过元组进行的序列映射/过滤会导致大量装箱开销。以下示例避免了装箱并并行工作,这(在我的机器上)要快 50 倍。

let parallelRanges =
    let kinectSize = 1228800
    let subSize = kinectSize / Environment.ProcessorCount
    let iMax = kinectSize - 1
    let steps = [| -1 .. subSize .. iMax |]
    steps.[steps.Length - 1] <- iMax
    steps |> Seq.pairwise |> Seq.toArray |> Array.map (fun (x, y) -> x + 1, y)

let countDiffs (prevBytes:byte[]) (curBytes:_[]) =
    let count (fromI, toI) =
        let rec aux i acc =
            if i > toI then acc
            elif i % 4 <> 0 && prevBytes.[i] <> curBytes.[i] then 
                aux (i + 1) (acc + 1)
            else aux (i + 1) acc
        aux fromI 0

    parallelRanges
    |> Array.Parallel.map count
    |> Array.sum

【讨论】:

很好奇 - 如果不并行运行,这能快多少?我希望它仍然比上一个更快。 @Reed:当只使用一个核心时,它仍然是 ca。比原版快 20 倍。使用四个核心,速度提高了大约 50 倍。【参考方案2】:

如果没有一些测试数据来分析和比较,我会做类似的事情

let len = Array.length previousImageBytes
let mutable count = 0
for i in 0 .. 4 .. (len-1) do
    if previousImageBytes.[i] <> currentImageBytes.[i] then
        count <- count+1

它对数据进行了一次而不是 5 次,并避免了速度较慢的 seq 函数。

【讨论】:

一个小改动:for i in 0 .. 4 .. len - 1 do 但还是不够快。我可能不得不使用另一个线程并只是对帧进行采样。 如果没有一些数据进行基准测试,这非常困难。并修复了第一个错误 仅供参考 - 原始数据只有 2 次传递(if 中的 Seq.length 和地图/过滤器,一次传递)【参考方案3】:

我已经在 F# 中为我一直从事的项目尝试了几种不同的字节数组比较实现。

一般来说,对于这个特定的问题,我发现“更惯用的 F#”==“更慢”。所以我最终得到了这个:

// this code is very non-F#-ish.  but it's much faster than the
// idiomatic version which preceded it.

let Compare (x:byte[]) (y:byte[]) =
    let xlen = x.Length
    let ylen = y.Length
    let len = if xlen<ylen then xlen else ylen
    let mutable i = 0
    let mutable result = 0
    while i<len do
        let c = (int (x.[i])) - int (y.[i])
        if c <> 0 then
            i <- len+1 // breaks out of the loop, and signals that result is valid
            result <- c
        else
            i <- i + 1
    if i>len then result else (xlen - ylen)

我违反了几项良好函数式编程的准则。我使用 if-then-else 而不是 match。我有变量。我伪造了一个 break 语句,对循环索引变量进行了粗俗的更改。

尽管如此,这最终比任何更惯用的方法都要快得多。即使是像带有尾递归的闭包这样简单的东西也比较慢。 (分析器表明闭包涉及堆上的内存分配。)

包含此代码的项目在 GitHub 上:

https://github.com/ericsink/LSM

fs/lsm.fs 的提交历史应该包含我其他失败尝试的记录。

【讨论】:

你为什么不使用递归,通过返回来打破循环? 注意,在这个例子中,我们要查找不相等的元素个数,而不仅仅是第一个的索引

以上是关于在 F# 中比较两个字节数组的最快方法的主要内容,如果未能解决你的问题,请参考以下文章

查看固定长度数组之间有多少字节相等的最快方法

在opencv中使用imshow显示字节数组的最快方法是啥?

从 C++ 中的字节数组中提取非零索引的最快方法是啥

检查字节数组是不是全为零的最快方法

在c ++中将4个字节转换为float的最快方法

将现有数组归零的最快方法是啥?