在 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# 中比较两个字节数组的最快方法的主要内容,如果未能解决你的问题,请参考以下文章