在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
通过数组进行映射比通过列表进行映射快三倍以上。此时,当调用的函数计算量更大时,我还没有测试速度差异。差异可能只是因为更快地遍历数组中的项目,并且在每次迭代都是计算密集时可能变得无关紧要。
尽管如此,必须存在使用数组或更常见的可变变量才能产生显着差异的情况。
在更改我的代码以使用数组而不是列表之前,我想更清楚地了解代码并行化时的后果。
一般情况下,何时可以使用可变变量而不存在并行化代码问题?是否有一个简单的测试可以让我在并行调用时确定函数的稳健性?
答案 0 :(得分:9)
阵列的速度差异与可变性无关;关于cache locality的全部内容。数组在内存中是连续的,因此它们比列表更快地迭代:F#列表是单链表,因此每个项目可以(通常是)位于不同的内存位置。这意味着您不会从CPU的缓存中受益,而对于数组,一旦您支付了从内存中检索第一个项目的成本,那么第二个到第N个项目(其中值N取决于您正在检索的项目的大小已经在缓存中并准备好进行近乎即时的检索。如果F#有一个ImmutableArray类并且你使用了它,那么当从ImmutableArray映射到你的可变数组时,你将获得相同的速度优势。
关于你的主要问题,关于何时使用并行代码的可变变量是安全的,简单的测试是问我是否实际上突变多个数据线程正在使用?"如果您不改变数据,那么让多个线程并行访问它是安全的。即使数据可以被突变(例如,数组),只要你实际上没有变异它,那么你的并行代码就不会遇到问题。如果您确实改变了数据,那么您必须处理锁定以及锁定所带来的所有问题,例如资源匮乏,死锁等。
所以简单的经验法则是#34;变异数据+平行主义=痛苦"。如果您改变数据但不运行并行代码,那么您的痛苦就会少得多。如果您没有改变数据,那么并行代码不会让您感到痛苦。但是,如果你正在做两件事,那就为头痛做好准备。
答案 1 :(得分:3)
虽然@rmunn已经为实际问题提供了一个很好的答案,但我觉得我必须写这个附录,因为我认为它非常重要,而且评论太长了。
这是为了回答隐含的问题,我将其视为" 由于可变数据更快,我不应该总是使用可变数据吗? "
确实如此,一般来说,可变数据结构,如果你说得对,表面上会更快,那么为什么我们不能一直使用它们呢?如果你做得对,跳跃也比函数调用更快,那么为什么don't we use goto
all the time呢?手动内存管理也是如此,如果你做对了,会占用更少的内存,和比垃圾收集更快,那么我们为什么要使用垃圾收集呢?并且(可以说)确实是直接在汇编中编写,甚至是二进制代码,如果你真的,真的正确,它比编译更快,那么我们为什么要使用高级语言呢? ?
上述所有问题的答案是性能不是软件开发中唯一关注的问题。它甚至不是最重要的关注点。甚至有人认为它不是接近最关键的问题的顶端。在现代更重要的是可读性,稳定性,可维护性,整体弹性。
在设计系统时,首先尝试猜测瓶颈可能存在的位置,然后仔细设计这些位置并在它们周围放置一些记录和检测。编写程序时,首先要使它们易读,易懂,易于维护。然后测量性能 - 在生产中,或在您可以负担得起的临时环境中。通过"衡量"我的意思并不是“它是最快的?”#34;我的意思是"它的速度是否足以达到我们的目的?"。如果是,那就好了。如果不是,请找出减速的确切位置,并优化该位置。随着时间的推移,凭借经验,您的猜测潜在的瓶颈会变得越来越好。
不要提前尝试优化:你最终会弄得手头一团糟,而且你很快就要扔掉它。 Premature optimization is the root of all evil