【问题标题】:Does functional programming reduce the Von Neumann bottleneck?函数式编程会减少冯诺依曼瓶颈吗?
【发布时间】:2018-02-07 22:38:39
【问题描述】:

我相信(通过阅读)从 CPU 缓存到主内存的总线上读/写数据对计算任务(需要在总线上移动数据)的完成速度有很大的限制 - 冯诺依曼瓶颈。

到目前为止,我遇到过几篇文章,其中提到函数式编程比其他范例(例如命令式方法)具有更高的性能。 OO(在某些计算模型中)。

有人可以解释一下纯函数式编程可以减少这个瓶颈的一些方法吗? IE。是否发现以下任何一点(通常)是正确的?

  1. 使用不可变数据结构通常意味着通过总线传输的数据更少 - 写入更少?

  2. 使用不可变数据结构意味着数据更有可能在 CPU 缓存中徘徊 - 因为对现有状态的更新越少意味着从缓存中刷新对象的次数越少?

  3. 是否有可能使用不可变数据结构意味着我们甚至可能永远不会从主内存中读回数据,因为我们可能会在计算期间创建对象并将其保存在本地缓存中,然后在同一时间片中创建一个新的不可变对象(如果需要更新),然后我们永远不会使用原始对象,即。我们正在更多地处理位于本地缓存中的对象。

【问题讨论】:

  • 虽然可以对不可变对象进行一些小的优化,但最大的性能提升可能是由于延迟评估。不是立即处理每个函数,而是仅在我们需要结果时才评估函数。虽然这可以在技术上也可以通过 OO 编程等来实现,但它需要大量的簿记。就我个人而言,我会说使用不可变结构可以增加数据量,因为每次构造一个新的对象时,旧的对象都会从缓存中换出。
  • 我认为这个问题虽然完全有效,但可能更适合CS stack exchange
  • 您是在特别询问多核并发下的内存吗?我认为这些观点中的任何一点都不能说是普遍的,但我只熟悉 GHC 运行时。
  • @WillemVanOnsem 延迟评估似乎是 TensorFlow 使用的最类似于 Haskell 的功能。
  • 在实践中,GPU 解放了我们,它们运行着相当典型的 SIMD 架构,尽管非常广泛。如果您谈论的是日常 CPU,出于相当基本的原因,最快的语言是命令式语言。

标签: scala performance haskell functional-programming


【解决方案1】:

哦,伙计,这是经典之作。 John Backus’ 1977 ACM Turing Award lecture is all about that: “Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs.”(论文“Lambda:The Ultimate Goto”在同一会议上发表。)

我猜你或者提出这个问题的人都想到了那个讲座。 Backus 所说的“冯诺依曼瓶颈”是“一个连接管,可以在 CPU 和存储之间传输单个字(并向存储发送地址)。”

CPU 仍然有数据总线,尽管在现代计算机中,它通常足够宽以容纳单词向量。我们也没有摆脱需要存储和查找大量地址的问题,例如指向列表和树的子节点的链接。

但巴克斯不仅仅是在谈论物理架构(强调):

这个管子不仅是问题数据流量的真正瓶颈,而且,更重要的是,它是一个智力瓶颈,让我们一直坚持一个字一个字的思考而不是鼓励我们根据手头任务的更大概念单元进行思考。因此,编程基本上是规划和详细说明通过冯诺依曼瓶颈的大量文字流量,其中大部分流量与重要数据本身无关,而是在哪里可以找到它。

从这个意义上说,函数式编程在很大程度上成功地让人们编写更高级的函数,例如映射和归约,而不是像 C 的 for 循环这样的“一次单词思维” . 如果你尝试用 C 对大量数据执行操作,那么今天,就像 1977 年一样,你需要将其编写为顺序循环。潜在地,循环的每次迭代都可以对数组的任何元素或任何其他程序状态做任何事情,甚至可以对循环变量本身做任何事情,并且任何指针都可能对这些变量中的任何一个进行别名。当时,Backus 的第一个高级语言 Fortran 的 DO 循环也是如此,除了指针别名的部分。为了在今天获得良好的性能,您尝试帮助编译器弄清楚,不,循环实际上并不需要按照您真正指定的顺序运行:这是一个可以并行化的操作,例如一些的缩减或转换其他数组或单独的循环索引的纯函数。

但这不再适合现代计算机的物理架构,它们都是矢量化对称多处理器——就像 70 年代后期的 Cray 超级计算机,但速度更快。

确实,C++ 标准模板库现在具有完全独立于实现细节或数据内部表示的容器算法,而 Backus 自己的创建 Fortran 在 1995 年添加了 FORALLPURE

当您查看当今的大数据问题时,您会发现我们用来解决这些问题的工具更像是函数式惯用语,而不是 Backus 在 50 年代和 60 年代设计的命令式语言。你不会在 2018 年写一堆 for 循环来做机器学习;你会在 Tensorflow 之类的东西中定义一个模型并对其进行评估。如果您想同时使用大量处理器处理大数据,了解您的操作是关联的非常有帮助,因此可以按任何顺序分组然后组合,从而实现自动并行化和矢量化。或者数据结构可以是无锁和无等待的,因为它是不可变的。或者说一个向量上的变换是一个映射,可以用 SIMD 指令在另一个向量上实现。

示例

去年,我用几种不同的语言编写了几个简短的程序来解决一个涉及找到最小化三次多项式的系数的问题。 C11 中的蛮力方法在相关部分看起来像这样:

      static variable_t ys[MOST_COEFFS];

//      #pragma omp simd safelen(MOST_COEFFS)
      for ( size_t j = 0; j < n; ++j )
        ys[j] = ((a3s[j]*t + a2s[j])*t + a1s[j])*t + a0s[j];

      variable_t result = ys[0];

//      #pragma omp simd reduction(min:y)
      for ( size_t j = 1; j < n; ++j ) {
        const variable_t y = ys[j];
        if (y < result)
          result = y;
      } // end for j

C++14 版本的对应部分如下所示:

  const variable_t result =
    (((a3s*t + a2s)*t + a1s)*t + a0s).min();

在这种情况下,系数向量是std::valarray 对象,STL 中的一种特殊类型的对象,它限制了它们的组件如何别名,其成员操作受到限制,并且对什么有很多限制操作对声音进行矢量化是安全的,就像对纯函数的限制一样。允许减少的列表,如末尾的.min(),是not coincidentally,类似于Data.Semigroupinstances。如果您查看 STL 中的 &lt;algorithm&gt;,这些天您会看到类似的故事。

现在,我不会声称 C++ 已成为一种函数式语言。碰巧的是,我将程序中的所有对象设置为不可变的,并由 RIIA 自动收集,但这只是因为我对函数式编程有很多了解,这就是我现在喜欢编码的方式。语言本身并没有强加诸如不变性、垃圾收集或没有副作用之类的东西。但是,当我们看到巴克斯在 1977 年所说的真正冯诺依曼瓶颈时,“一个让我们始终坚持逐字思考的智力瓶颈,而不是鼓励我们用术语来思考手头任务的更大概念单元,”这适用于 C++ 版本吗?这些操作是系数向量上的线性代数,而不是一次一个单词。而 C++ 借用的想法——以及表达式模板背后的想法更是如此——主要是函数概念。 (将 sn-p 与它在 K&R C 中的样子进行比较,以及 Backus 在 1977 年图灵奖演讲的第 5.2 节中如何定义一个计算内积的函数程序。)

我还用 Haskell 编写了一个版本,但我认为这不是逃避冯诺依曼瓶颈的一个很好的例子。

完全有可能编写出符合 Backus 对冯诺依曼瓶颈的所有描述的函数式代码。回顾我这周写的代码,是我自己写的。构建列表的折叠或遍历?它们是高级抽象,但它们也被定义为一次一个单词的操作序列,当您创建和遍历单链表时,一半或更多的数据通过瓶颈是地址其他数据!它们是使数据通过冯诺依曼瓶颈的有效方法,这就是我这样做的基本原因:它们是编程冯诺依曼机器的绝佳模式。

但是,如果我们有兴趣以不同的方式编写代码,函数式编程为我们提供了这样做的工具。 (我不会声称它是唯一会做的事情。)将归约表示为 foldMap,将其应用于正确的向量类型,幺半群运算的关联性让您可以将问题分解为任何你想要的尺寸,然后组合起来。在单链表以外的数据结构上进行map而不是折叠操作,它可以自动并行化或向量化。或者以产生相同结果的其他方式进行转换,因为我们已经在更高的抽象层次上表达了结果,而不是特定的一次单词操作序列。

到目前为止,我的示例都是关于并行编程的,但我相信量子计算将从根本上改变程序的外观。

【讨论】:

  • 调用具有优化例程的框架没有本质上的功能。
  • @Veedrac 同意,但是有一些功能性的东西,比如说,构建一个对象来表示一个表达式,将其限制为没有副作用的操作,并懒惰地评估它。
  • 我不会说这是对您的示例的良好描述,例如。 C++ 的 STL 或 Tensorflow,既严格又有副作用。
  • 也许你会接受 C++ 中的表达式模板作为更好的例子(尽管严格)?
  • 感谢您将这个有用的答案汇总到一个明显的争论点上,它肯定会对我有所帮助。
猜你喜欢
  • 2017-10-10
  • 1970-01-01
  • 2020-02-07
  • 2010-10-20
  • 2023-03-02
  • 2015-01-05
  • 2011-02-16
  • 2015-04-03
  • 2020-08-03
相关资源
最近更新 更多