什么时候可以使用可变变量并行调用函数?

When can I call a function using mutable variables in parallel?

提问人:Soldalma 提问时间:6/3/2018 更新时间:6/5/2018 访问量:115

问:

在看了一个有趣的讲座后Phil Trelford

https://www.youtube.com/watch?v=hx2vOwbB-X0

我对通过用数组替换列表以及更普遍地使用可变变量来加速代码的可能性很感兴趣。所以我做了一个简单的测试:

let xs = [0.0..1000000.00]
let axs = List.toArray xs

let f x = sin (x * x)

List.map f xs // Real: 00:00:00.170, CPU: 00:00:00.187, GC gen0: 5, gen1: 3, gen2: 1

Array.map f axs // Real: 00:00:00.046, CPU: 00:00:00.046, GC gen0: 0, gen1: 0, gen2: 0

映射数组的速度比映射列表快三倍以上。在这一点上,我还没有测试调用的函数计算密集度更高的速度差异。这种差异可能只是由于在数组中的项中移动速度更快,并且当每次迭代都是计算密集型时,可能会变得微不足道。

尽管如此,在某些情况下,使用数组或更普遍的可变变量可能会产生重大影响。

在将我的代码更改为使用数组而不是列表之前,我希望更清楚地了解并行化代码时的后果。

一般来说,什么时候可以使用可变变量而不会冒着出现并行化代码问题的风险?有没有一个简单的测试可以让我确定并行调用时函数的鲁棒性?

并行处理 f# 可变

评论


答:

9赞 rmunn 6/4/2018 #1

数组的速度差异与可变性无关;这一切都与缓存位置有关。数组在内存中是连续的,因此它们比列表更快地循环访问:F# 列表是单链表,因此每个项都可以(并且通常)位于不同的内存位置。这意味着您不会从 CPU 的缓存中受益,而对于数组,一旦您支付了从内存中检索第一个项目的成本,那么第 2 个到第 N 个项目(其中 N 的值取决于您正在检索的项目的大小)已经在缓存中,并准备好进行近乎即时的检索。如果 F# 有一个 ImmutableArray 类,并且你使用了它,那么在映射该 ImmutableArray 时,你将获得与从可变数组映射相同的速度优势。

至于你的主要问题,关于何时将可变变量与并行代码一起使用是安全的,简单的测试是问“我是否真的在改变多个线程正在使用的数据?如果您不改变数据,那么让多个线程并行访问它是安全的。即使数据可以被改变(例如,一个数组),只要你实际上没有改变它,那么你的并行代码就不会遇到问题。如果你确实改变了数据,那么你必须处理锁定,以及锁定带来的所有问题,如资源匮乏、死锁等。

因此,简单的经验法则是“变异数据 + 并行性 = 痛苦”。如果更改数据但不运行并行代码,则痛苦程度会小得多。如果你不改变你的数据,那么并行代码不会给你带来痛苦。但是,如果您同时做这两件事,请做好头痛的准备。

评论

0赞 Fyodor Soikin 6/5/2018
我突然想到,由于 .NET 内存分配的工作方式,一次性初始化的列表实际上可能是内存本地的。
3赞 Fyodor Soikin 6/5/2018 #2

虽然@rmunn为实际问题提供了一个很好的答案,但我觉得我必须写这个附录,因为我认为它非常重要,而且它太长了,不适合发表评论。

这是为了回答隐含的问题,我将其解读为“既然可变数据更快,我不应该总是使用可变数据吗?"

的确,一般来说,可变数据结构,如果你做对了,在表面上会更快,那么我们为什么不一直使用它们呢?如果你做对了,跳跃也比函数调用快,那么我们为什么不一直使用 goto 呢?手动内存管理,如果做对了,消耗的内存更少,而且比垃圾回收更快,那么我们为什么要使用垃圾回收呢?而且(可以说)直接用汇编代码甚至二进制代码编写,如果你真的、真的正确的话,比编译更快,那么为什么我们有高级语言呢?

以上所有问题的答案是,性能并不是软件开发中唯一关心的问题。这甚至不是最重要的问题。甚至可以说,它远不是最重要的问题。在现代,更重要的是可读性、稳定性、可维护性、整体弹性。

在设计系统时,首先尝试猜测瓶颈可能在哪里,然后仔细设计这些地方,并在它们周围放置一些日志记录和仪器。在对程序进行编码时,首先要使它们具有可读性、可理解性、可维护性。然后衡量性能 - 在生产环境中,或者在暂存环境中(如果您负担得起的话)。我所说的“测量”并不是说“它是最快的吗?”,我的意思是“它对我们的目的来说足够快吗?”。如果是,那就太好了。如果不是,请找出减速的确切位置,并优化该位置。随着时间的流逝,随着经验的积累,您对潜在瓶颈的猜测会越来越好。

不要试图提前优化:你最终只会手上一团糟,你必须很快把它扔掉。过早优化是万恶之源