Data.Set成员并行查找未获多核性能提升问题咨询
嘿,这个问题我之前也碰到过类似的情况!先直接给你结论:完全不需要重新编译Data.Set库——问题出在你的并行策略实现、GHC线程启用方式,以及Data.Set本身的特性上,和库的编译选项无关。
为什么你的cntPar没利用多核?
咱们一步步拆解原因:
Data.Set.member的开销太小Data.Set基于平衡二叉树实现,member操作是O(log n)复杂度,单个查找的开销非常低。而并行化本身有线程调度、上下文切换的开销,如果每个并行任务的工作量(比如每个chunk里的元素数量)太小,调度开销会直接盖过并行带来的收益,反而比串行还慢。而列表查找是O(n),单个查找开销大,并行能有效分摊工作量,所以提速明显。你的分块逻辑有问题
代码里的transpose chnks完全没必要,甚至会帮倒忙!比如你把原列表分成n个chunk,再转置,会让每个子列表变成原列表的第1、n+1、2n+1...个元素,这既没有合理划分工作量,还可能破坏缓存局部性(Data.Set的查找虽然是树结构,但连续查找相近元素可能有缓存优势,转置后元素分散,反而更慢)。可能没正确启用GHC的多核支持
这是最容易忽略的点:GHC的并行代码需要编译时加-threaded选项,并且运行时指定线程数。如果你只是编译了代码但没加-threaded,或者运行时没加+RTS -N<核数>(比如./your-program +RTS -N4),那并行代码还是会以单线程运行,自然看不到性能提升。
怎么修改才能让并行版本提速?
给你几个具体的优化建议:
正确启用多核编译和运行
编译命令改成:ghc -threaded YourModule.hs运行时指定使用的核数:
./YourModule +RTS -N4 # 4代表用4核,根据你的机器调整调整分块策略,去掉
transpose
直接用分好的chunk,不要转置。另外要控制chunk的大小,确保每个chunk的工作量足够大(比如每个chunk至少有几千个元素),让并行任务的计算时间远大于调度开销。比如可以用numCapabilities(当前可用的线程数)来划分:import GHC.Conc (numCapabilities) cntPar :: S.Set Int -> [Int] -> Int cntPar set xs = let chunkSize = max 1000 (length xs `div` numCapabilities) chnks = chunksOf chunkSize xs in sum $ parMap rpar (cntSeq set) chnks调整并行策略的严格性
虽然Int是严格类型,但如果你的cntSeq返回的是更复杂的结构,可能需要用rdeepseq代替rpar来确保计算完全并行。不过对于当前的Int返回值,rpar已经足够,但可以试试:sum $ parMap rdeepseq (cntSeq set) chnks
最后再提醒一下
只有当你的总查找次数非常多(比如百万级以上),或者Data.Set的规模极大(log n的开销足够明显)时,并行查找才有实际意义。如果只是小规模的数据,串行版本反而会更快。
内容的提问来源于stack exchange,提问作者Filip Federowicz

