哦,伙计,这是经典之作。 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 年添加了 FORALL 和 PURE。
当您查看当今的大数据问题时,您会发现我们用来解决这些问题的工具更像是函数式惯用语,而不是 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.Semigroup 的instances。如果您查看 STL 中的 <algorithm>,这些天您会看到类似的故事。
现在,我不会声称 C++ 已成为一种函数式语言。碰巧的是,我将程序中的所有对象设置为不可变的,并由 RIIA 自动收集,但这只是因为我对函数式编程有很多了解,这就是我现在喜欢编码的方式。语言本身并没有强加诸如不变性、垃圾收集或没有副作用之类的东西。但是,当我们看到巴克斯在 1977 年所说的真正冯诺依曼瓶颈时,“一个让我们始终坚持逐字思考的智力瓶颈,而不是鼓励我们用术语来思考手头任务的更大概念单元,”这适用于 C++ 版本吗?这些操作是系数向量上的线性代数,而不是一次一个单词。而 C++ 借用的想法——以及表达式模板背后的想法更是如此——主要是函数概念。 (将 sn-p 与它在 K&R C 中的样子进行比较,以及 Backus 在 1977 年图灵奖演讲的第 5.2 节中如何定义一个计算内积的函数程序。)
我还用 Haskell 编写了一个版本,但我认为这不是逃避冯诺依曼瓶颈的一个很好的例子。
完全有可能编写出符合 Backus 对冯诺依曼瓶颈的所有描述的函数式代码。回顾我这周写的代码,是我自己写的。构建列表的折叠或遍历?它们是高级抽象,但它们也被定义为一次一个单词的操作序列,当您创建和遍历单链表时,一半或更多的数据通过瓶颈是地址其他数据!它们是使数据通过冯诺依曼瓶颈的有效方法,这就是我这样做的基本原因:它们是编程冯诺依曼机器的绝佳模式。
但是,如果我们有兴趣以不同的方式编写代码,函数式编程为我们提供了这样做的工具。 (我不会声称它是唯一会做的事情。)将归约表示为 foldMap,将其应用于正确的向量类型,幺半群运算的关联性让您可以将问题分解为任何你想要的尺寸,然后组合起来。在单链表以外的数据结构上进行map而不是折叠操作,它可以自动并行化或向量化。或者以产生相同结果的其他方式进行转换,因为我们已经在更高的抽象层次上表达了结果,而不是特定的一次单词操作序列。
到目前为止,我的示例都是关于并行编程的,但我相信量子计算将从根本上改变程序的外观。