Wolfram 系统的内部
大多数 Wolfram 语言文档主要涉及解释 Wolfram 语言做什么,而不是怎样做. 但是本章的目的是至少说一点Wolfram 语言怎样做要做的事情. "
关于内部实现的一些注释" 提供了更多的详细信息.
应当认识到了解 Wolfram 语言的内部或许是智力方面的兴趣,在实用中其通常是不重要的.
实际上,Wolfram 语言的要点之一是提供了一个环境,在其中能执行数学和其他运算,而不必考虑这些运算如何在计算机中进行的细节.
例如,当用户想要对多项式
分解因式时,仅需要给 Wolfram 系统一个命令
Factor[x^15-1] 即可. 用户不必知道由 Wolfram 语言内部代码执行的这种因式分解的复杂细节.
实际上,在几乎所有的 Wolfram 系统的使用中,Wolfram 语言内部如何工作的问题被证明是毫不相干的. 在大多数情况下,把 Wolfram 系统看作一个执行指定数学和其他运算的抽象系统就足够了.
用户可能认为了解 Wolfram 语言在内部是如何工作的对于确定其给出的答案是必要的. 但这只是很罕见的情形. 对于Wolfram 语言进行的绝大多数运算完全由数学和其其运算的定义来指定.
例如,
3^40 将总是
12157665459056928801,而不管 Wolfram 语言内部如何计算这个结果.
然而,有一些情形,几个答案是等价的. 例如,在计算符号积分时,通常有几个不同的表达式,其导数是相同的. 这些表达式中的哪几个由
Integrate 实际产生可以依赖于
Integrate 的内部工作方式.
在数值计算中出现类似的现象. 例如,
FindRoot 给出函数的一个根. 但是如果有几个根,哪个根被返回取决于
FindRoot 在内部如何工作的详细资料.
求出
的一个特殊的根:
使用不同的初始点,不同的根被求出. 对于每个初始点,哪个根被求出,依赖于内部使用的算法:
如果把近似数值计算扩大到其有效性的极限处,对于内部算法的依赖会是更明显的.
例如,如果给
NIntegrate 一个病态被积函数,是否生成一个有意义的结果可能依赖于其使用的内部算法.
传统数值计算系统一直倾向于遵循这种思想:所有的计算应当产生至少具有相同精度的结果. 这个思想的推论是:仅仅看一个结果来知道其是否精确是不够的;用户还必须分析求出该结果的内部算法. 这个事实一直使人们相信,知道数值计算的内部算法总是重要的.
但是,对于 Wolfram 语言采取的方法,这是罕见的情形. 因为 Wolfram 系统通常能使用其任意精度的数值计算能力给出结果,所产生的每一数位遵循正在执行的运算的精确数学规范.
尽管这是一个近似数值计算,但是每一位都由
的数学定义来决定:
再一次,这里的每一位由
的数学定义来决定:
如果使用机器精度数,Wolfram 系统不能给出可靠的结果,答案依赖于所使用的内部算法:
当用户得到的结果可能被内部算法影响时,不应当依赖这些结果. 如果没有别的问题,那么 Wolfram 语言的不同版本在这些结果中可能表现出的差别,或者因为算法在不同计算机系统上运行有一些不同,或者因为本质上不同的算法被使用在不同时期发行的版本中.
在一种计算机上的结果:
特别在 Wolfram 语言更高级的应用中,有时可能值得花费精力去分析内部算法,以便预测对给定计算的哪一种做法是更有效的. 实际上,通过这种分析,在特定的计算中有时会产生很大的改进.
但是,更多情况下,这种分析是不值得的. 因为 Wolfram 语言内部是相当复杂的,即便给出一个特定的算法的基本描述,要达到这个算法在特定场合的实际行为的具体实现通常是极端困难的.
一个典型的问题是 Wolfram 语言有许多内部的最优化方法. 运算的效率在很大程度上被是否在计算中允许使用内部的优化算法所影响.
numbers | 二进制数字序列 |
strings | 字符码字节或字节对序列 |
symbols | 指向中心符号表的指针 |
general expressions | 指向头和元素的指针序列 |
当用户在 Wolfram 语言插入输入时,一个数据结构被建立在计算机的内存中来表示已经输入的表达式.
一般地,表达式的不同部分被存储在内存的不同地方. 例如,对一个列表如
{2,x,y+z},其
“主要部分
” 将被存储在一个地方,而每个实际元素将被存储在不同的地方.
列表的主要部分仅由三个指明计算机内存地址的指针组成. 在该地址上,形式列表元素的实际表达式能被找到. 这些表达式依次包含指向子表达式的指针. 当人们达到一个对象,例如一个数或者一个字符串时(该对象以二进制类型直接存储在内存中),这是指针链结束.
对于 Wolfram 语言运算至关重要的是符合,例如
x 的表示方法. 当
x 出现在表达式中间时,Wolfram 语言用一个指针代表. 但是,这个指针总是指向内存中的同一个地方
——由 Wolfram 语言进程定义的所有符号中心表的入口.
这个表是关于每个符号所有信息的存储器. 其包含指向给出符号名的串的指针,以及指向给出该符号计算规则的表达式的指针.
Wolfram 系统使用的每一块内存都保持着当前指向的指针计数. 当这个计数降为零时,Wolfram 系统知道这块内存不再被使用,并且立即使这块内存可用于新的任务.
这个策略基本上保证内存不被浪费,Wolfram 系统使用的任何一块内存实际上正存储着用户需要访问的 Wolfram 系统进程数据.
■ 建立相应于用户给出的输入表达式. |
■ 使用对于该对象的所有已知规则处理该表达式. |
■ 生成相应于结果表达式的输出. |
在 Wolfram 语言的中心处有一个在概念上很简单的称为运算器的程序,其取出表达式中的每一个函数,并且进行计算.
当函数是建立在 Wolfram 语言中的数以千计的函数中的一个时,运算器所做的事情是直接执行 Wolfram 语言中的内部代码. 这个代码被建立来执行相应于该函数的运算. 然后建立一个新的表达式来代表结果.
使 Wolfram 语言成为自包含系统的基本特征.
Wolfram 语言内部函数的一个重要特征是支持
通用运算. 这意味着在这些函数之外,用户可以构建程序进行计算机能够做的任何类型的运算.
这样,Wolfram 语言内部函数的一个小的子集合对于支持通用运算已经很充分了. 但是具有函数的整个集合使得在实际运用中更容易构建程序.
然而,因为 Wolfram 语言支持通用计算,故而用户决不要修改其内部函数,执行特定任务时,用户必须做的事情是以适当的方式将这些函数结合起来.
通用计算是所有标准计算机语言的基础. 但许多语言依赖于
编译的思想. 例如,使用 C 或者 Fortran 是,用户首先写出程序,然后编译生成能在计算机上实际执行的机器代码.
Wolfram 语言不需要用户进行编译:一旦用户输入完表达式,表达式中的函数能立即被执行.
Wolfram 语言常常对用户输入的表达式进行预处理,安排事务以便随后的运用尽可能的有效. 但是这样的预处理决不影响生成的结果,而且很少能明显地被看到.
Wolfram 语言的内部函数实现计算机学科和数学中的大量算法. 其中一些算法是很老的. 但大多数算法必须被创建或至少被专门修改才能用于 Wolfram 语言. 在 Wolfram 语言中更多的数学算法最终进行以往的手工运算. 然而,在几乎所有的情形,这些算法使用的方法完全不同于那些平常在手工计算中的方法.
符号积分提供了一个例子. 在手工计算中,符号积分通过大量的涉及变量替换等的技巧来进行.
但在 Wolfram 语言中,符号积分由相当少的非常系统化的程序来进行. 对于不定积分,这些程序的思想是找出积分的最一般形式,然后对其微分求出待定系数.
这个程序常常在中间阶段产生大量复杂的代数表达式,有时是非常复杂类型的数学函数. 但这个程序的优点是完全系统化的,其运算不需要特殊的只有人类才能提供的智慧.
因此,在 Wolfram 语言做积分时,可以相信其将规则地获得结果,但不能期望导出这些结果的方法与手工导出的方法非常相同.
对于 Wolfram 语言中的大多数数学算法这同样是对的. 一个显著的特征是即使对描述起来很简单的运算,在 Wolfram 语言中执行该运算的系统算法也涉及到相当高深的数学或者计算上的概念.
例如,对
的多项式进行因式分解,首先以一个质数如 17 为模,找出通过按该模化简
的高次幂得到的矩阵和最初的多项式的零空间. 然后,使用代数和分析中错综复杂的定理,按该质数的幂依次
“移动
” 模获得整数的因子.
强有力的系统化算法的使用在使 Wolfram 语言的内部函数能处理困难和一般的情况中是重要的. 但是对于实际问题中相当常见的简单情况,常常可能使用更简单、更有效的算法.
因此,Wolfram 语言的内部函数常常有大量的处理各种特殊情况的附加部分. 这些附件部分可能使内部代码的复杂性增加了许多,常常把本来可能是5页的算法导成数百页长.
Wolfram 语言中的大多数算法,包括所有特殊的情况,都是人为构建的. 但一些算法由计算机自动地创建.
许多数学函数的机器精度的数值计算就是例子. 这种算法的主要部分是尽可能短的但产生最好的数值近似的公式.
使用在 Wolfram 语言中的大多数这种公式实际上由 Wolfram 语言自己导出. 导出公式的运算常常需要几个月的时间,但结果是能被用来以最优的方式计算函数的短小公式.
Wolfram 系统是当今最复杂的软件系统之一. 由数百万行 C/C++、Java 和 Wolfram 语言写成.
Wolfram 系统中的 C 代码实际上是用支持一定的内存管理和面向对象特征的扩展 C 语言写成的. Wolfram 语言代码使用
Share 和
DumpSave 进行了优化.
在 Wolfram 语言内核中,不同部分的代码构成大致如下:语言和系统占 30%;数值计算占 20%;代数计算占 20%;图形与内核输出占 30%.
大多数代码是相当密集的和算法化的:实际上是简单过程或表的那些部分使用最少的代码,这是因为这些代码趋向于在较高的层次上
——常常直接在 Wolfram 系统中编写.
内核中的源代码,对于运行 Wolfram 系统的所有计算机系统是完全相同的.
然而,对于前端,需要大量专门化的代码来支持不同类型的用户界面环境. 前端包含大约 700,000 行独立于系统的 C++ 源代码,其中大约 200,000 行涉及表达式的格式构造. 这里有 50,000 到 100,000 行的特殊代码,这些代码专门处理每个不同的用户界面环境.
Wolfram 系统使用客户
——服务器计算模型. 前端和内核通过 Wolfram Symbolic Transfer Protocol (WSTP) 来连接
——使用同样的系统与其他程序通讯. WSTP
支持多个传输层,包含基于 TCP/IP 的和使用共享内存的.
前端与内核使用三个独立的 WSTP
链接来连接. 一个是用于用户提交的计算. 第二个是用于前端求解
Dynamic 表达式的值. 三是用于内核,以通知前端应该使哪个
Dynamic 对象失效.
在 Wolfram 语言内核的 C 代码部分,主要通过交换完整的 Wolfram 系统表达式实现不同部分之间的通信,以此来获得模块性和一致性.
但是应当注意,即使系统的不同部分在源代码层次是相当独立的,其也有许多算法的相互依赖性. 例如,我们可以常常看到使用了大量代数算法的数值函数,或者使用嵌入在不同 Wolfram 系统函数中的高级数学算法的图形代码.
自从1986年 Wolfram 系统开始发展以来,平均每年有1000开发人员从事 Wolfram 系统源代码的创建. 此外,相当的精力或者更多的精力用在代码的测试和验证上.
自从第1版发行以来,Wolfram 系统的源代码已经有了很大改变. 在内核中的代码总行数从第1版的 150,000 行,到第2版的 350,000 行,又到第3版的 600,000 行,第4版的 800,000 行,第5版的一千五百万行,到第6版的两千五百万行. 此外,在每个阶段,现有代码都被修改了
——因此,在第6版中,仅有很少的一部分代码与第1版中的代码是相同的.
然而,尽管在内部代码中有这些变化,Wolfram 语言用户层次的设计一直保持与第1版的兼容性. 到目前为止,添加了许多新功能,但 Wolfram 语言第1版中创建的程序无需任何改变,绝对能在第 6 版中运行.
Wolfram 语言的每个版本在发行以前都进行了大量的测试. 其中大多数测试通过以 Wolfram 语言写成的自动系统来进行.
自动系统给 Wolfram 语言提供数百万个输入,并检查从中得到的输出的正确性. 在做这样的测试时,常常有一些精妙之处:人们必须说明随机算法的不同行为的原因和不同计算机的机器精度的差别等问题.
- 对于每个 Wolfram 语言函数,输入被设计用于普通的和极端情形.
- 输入被设计用于内部代码的每个特征.
- Wolfram 语言文档系统中的所有例子和其他有关 Wolfram 语言书中的例子被使用.
- 测试是从数学基准构建的,而测试集合是从大量的网站构建的.
- 标准的数值表被光学扫描作为测试输入.
- 来自所有的标准数学表中的公式被输入.
- 教科书中的练习被输入.
- 对于函数对,例如 Integrate 和 D 或者 Factor 和 Expand,生成随机表达式进行测试.
当测试运行时,自动测试系统不仅检查结果,还检查辅助的事项. 诸如:信息、内存使用和速度等.
还有一个特殊的装有仪器的 Wolfram 语言版本. 其被建立用于执行内部一致性检验. Wolfram 语言的这个版本以真实的 Wolfram 语言的速度的一小半的速度运行,每一步都检查内部的一致性,中断能力等等.
该 Wolfram 语言版本还记录 Wolfram 系统源代码的哪一部分已经被访问,允许人们认可 Wolfram 语言中的所有函数已经被给定的检验测试过.
所有标准的 Wolfram 语言测试被例行地在 Wolfram 语言每个版本、在不同计算机上运行. 根据计算机系统的速度,这些测试花费计算机几个小时到几天的时间.
但是,即使有这么多检验,在 Wolfram 语言这样复杂的系统中仍不可避免地存在着错误.
Wolfram 语言的正确性标准肯定比典型的数学证明高得多. 但正如一个很长的证明不可避免的包含错误并且多年来没有被检查出来一样,像 Wolfram 语言这样的复杂软件系统也将包含错误,甚至数百万人使用了还检查不出来.
不管怎样,毕竟已经做了这么多测试,用户在自己的工作过程中发现 Wolfram 语言的错误的概率是极低的.
很可能有许多次 Wolfram 语言做了用户不期望的事情. 但应该认识到,这样的事情发生,大多数是因为用户的输入或者某种理解出了错误,而不是 Wolfram 系统的内部代码本身的问题.
如果用户确信自己发现了 Wolfram 系统中的真正的错误,那么应当与
Wolfram Research Technical Support 联系,以便该错误能在将来的版本中被纠正.