OCaml is suited for Jane Street's trading systems due to its balance of performance, correctness, and dynamism. It is memory-safe, has a strong type system for catching bugs, and is performant enough to handle large data feeds and real-time processing. Its conciseness and readability, combined with type inference, make it ideal for rapid iteration and maintaining correctness in a high-stakes environment.
Jane Street's trading operations demand high performance to handle millions of transactions per second, speed of implementation to adapt to dynamic markets, and correctness to ensure software accurately executes trading strategies. These requirements stem from the need to process real-time data, iterate quickly on new ideas, and avoid costly errors in automated trading systems.
OCaml's type system, particularly its type inference, eliminates the need for explicit type declarations, reducing boilerplate code. This allows developers to write concise, readable code while maintaining type safety. The language's abstraction tools also enable efficient reuse of code, further minimizing repetitive patterns.
Concurrency in OCaml refers to managing multiple tasks that may not execute simultaneously but need to handle uncertainty in task timing, such as network communication. Parallelism, on the other hand, involves executing tasks simultaneously across multiple hardware cores. OCaml has strong concurrency support through libraries like Async but lacks shared-memory parallelism, requiring message-passing for distributed systems.
Correctness is critical in automated trading systems because these systems directly interact with financial markets and can commit to transactions without human oversight. Errors in such systems can lead to significant financial losses, especially if they occur in fast loops. Ensuring correctness involves rigorous testing, type safety, and careful modeling of the trading environment.
Jane Street emphasizes code readability and rigorous code review. They use a custom code review system called Iron, which helps catch bugs, share institutional knowledge, and improve the overall quality of the codebase. This focus on readability ensures that code is easier to modify, understand, and evolve, fostering a culture of high-quality engineering.
Jane Street mitigates software failure risks through institutional humility, redundant engineering, and the ability to quickly shut down systems. They have physical and logical mechanisms, like a 'big red button,' to halt trading when anomalies are detected. This approach prioritizes safety over availability, ensuring that systems can be turned off during critical failures to prevent catastrophic losses.
Type inference in OCaml allows the compiler to automatically determine the types of expressions without requiring explicit type annotations from the programmer. This reduces boilerplate code, improves readability, and maintains type safety. It enables developers to write concise, expressive code while still benefiting from the robustness of a static type system.
Jane Street avoids shared-memory parallelism in OCaml because it is difficult to reason about and can introduce subtle bugs, especially in systems with mutable state. Instead, they use message-passing between separate processes, which simplifies concurrency management and allows for distributed systems that span multiple machines.
Yaron Minsky's perspective on probability has shifted to emphasize the importance of expectancy and avoiding absolute certainty. He now views assigning zero probability to an event as irrational, as it disregards potential evidence to the contrary. This mindset, influenced by financial decision-making, encourages more careful and rational assessments of risks and outcomes.
Yaron Minsky是Jane Street Capital的量化研究和技术主管。Jane Street之所以独特,不仅因为它取得了令人难以置信的成功,还在于它几乎完全使用OCaml,这是一种函数式编程语言。Yaron,欢迎来到软件工程日报。
谢谢。很高兴来到这里。也许值得更正一下。所以我是技术主管,不再是量化研究主管了。那部分领域已经发展了,Jane Street的人比我同时担任这两个职位时要多得多。哦,好的。有趣。那么,Jane Street是一家交易公司。交易需求是如何驱动公司的技术需求的呢?是的。
它以多种方式驱动它。我认为它显然对性能施加了很大的压力,因为交易应用程序主要由大量数据组成,您需要实时使用这些数据才能拥有任何类型的响应系统。每秒产生数百万笔交易的市场数据馈送并非超出您所能获得的范围,而且您是从多个不同的来源提取数据的。
这在构建能够响应数据的实时系统以及在收集数据、获取准确的时间戳(以便您了解过去发生的事情)和能够进行批量研究(您查看并分析大量历史数据以了解过去发生的事情)方面都会给您带来成本。所有这些都需要良好的性能。
我认为另一件非常重要的事情是实施速度,也就是完成新事物的能力。市场极其动态。因此,您必须不断提出新想法并快速迭代。交易领域的一个良好不变式是您正在做的所有事情都会消失。
如果您继续以现在的相同水平做同样的工作,并继续执行这些策略,您会发现它们会越来越糟糕。而建立良好、可持续发展的业务的唯一方法是不断寻找新的机会,并努力改进您已经做的事情。这给动态性带来了很大的压力,也就是快速创造新事物的能力。
那么这些——哦,请继续。哦,最后,我认为第三点非常重要,那就是正确性。对于程序员来说,交易在某种意义上是一件令人恐惧的事情,自动化交易更是如此,因为您正在编写一个可以访问您钱包的程序。如果您不觉得这令人恐惧,那么您就没有认真思考这个问题。我们都是糟糕的程序员。我们健忘。我们犯了很多错误,这些错误最终会出现在我们的代码中——
您必须付出很多努力和工程才能确保您构建的软件能够正确地执行您的意图。这既包括您在任何地方都能看到的普通软件正确性概念,也包括仔细考虑您正在构建软件所依据的世界模型,无论这是否真的正确,以及您期望的模型与现实世界之间的偏差可能给您带来麻烦的方式。所以就像三件事。
性能、正确性和动态性。这些是交易带来的三大压力。是的。在我们讨论OCaml如何很好地封装这三个特性之前,让我们谈谈一些没有封装这些特性的语言。有哪些语言表现出与这种正确性、敏捷性和性能相反的特性?
当然。我认为您会发现,这并不是说
人们使用的语言在所有方面都严格不好,对吧?发生的事情是它们做出了不同的权衡。我的意思是,有些语言的设计真的很糟糕,并且变得流行是因为它们碰巧在某些利基市场中拥有良好的库。但通常广泛使用的语言都相当不错。它们是合理的,但它们并不擅长与其他语言擅长完全相同的方面。例如,C语言在许多方面都是一种美丽的语言,是……
为您提供一种非常接近机器的编程模型。它让您对内存表示有很大的控制权,并且可以让您以非常自然的方式编写高性能代码。但也很难推断复杂的C代码,因为编译器和语言几乎没有保护您免受我们易犯错的人类所犯的普通错误的影响。所以这是一种在权衡的一方面失败的语言,并且
安全故事不是很好,这使得我们对程序所关心的正确性故事更难提供。并非不可能,但我认为难度要大得多。另一种以不同方式失败的可爱语言是Python。Python是一种非常用户友好的语言,我认为这种语言的设计真正关注普通人的思维方式。
我认为Python之所以很棒的原因之一是,您查看一段Python代码,它在许多情况下几乎是伪代码。所以Python有一些可爱之处,但它慢得令人难以置信,这使得它不适合大多数这些应用程序。我认为Python等语言缺乏类型安全意味着对于更大的代码库来说,很难使用。我认为这部分是因为……
在动态类型语言中,重构变得非常困难,因为语言(特别是像OCaml这样的语言)的类型系统(出于我们稍后将要讨论的原因)使重构变得容易得多。完全缺乏类型系统意味着,如果您有一些具有许多客户端的库,并且您想更改该库的语义,
很难找到并正确修复所有使用它的位置。它使某些类型的转换变得非常困难。实际上,Python社区中一个很好的例子是从Python 2.7迁移到Python 3,Python 3已经存在很长时间了。有很多原因(社会学和其他原因)导致人们没有完全转向Python 3。但我认为其中一个原因是,在进行此迁移时,您从类型系统那里得到的帮助很少。
所以有一些工具可以帮助迁移,但基本事实是,语言的动态类型特性使得这变得更加困难。当然。那么让我们开始讨论OCaml。鉴于Jane Street的技术需求,为什么OCaml如此吸引人?因此,OCaml具有一些不错的特性,使其在所有我们一直在讨论的不同事物之间的权衡中占据了一个非常舒适的位置。所以OCaml
例如,它既安全,这意味着它具有内存安全性和类型系统中大量良好的支持,可以帮助您发现错误。同时,它仍然相当高效。
我认为许多提供表达能力和安全性的高级语言在性能方面都有很大的折衷。对于OCaml来说,情况要好得多。您的OCaml代码通常不会像C代码那样快,但您可以让它非常接近。当然,如果您愿意以谨慎的方式编写程序的核心部分,您可以获得速度非常接近C代码的OCaml代码。它也是……
一种既简洁又易于阅读的语言,一旦您习惯了阅读函数式语言,它就非常易于阅读,但它具有动态语言的简洁性和静态类型系统语言的完整类型安全性的优点。因此,它在编程语言设计领域以几种不同的方式占据了有利地位。我认为语言的另一个非常好的方面是
它允许您以相当声明式的方式编写内容,这意味着声明式是一个有趣的词。目前还不清楚它的确切含义,但我大致的意思是,您可以编写一些非常简单且易于阅读和理解的内容,同时具有相当易于理解的性能行为。因此,举一个在这方面与之截然不同的语言的例子是SQL,您在其中编写SQL语句,它处于这种高级形式,很容易理解您想要实现的目标。
然后查询优化器会创造奇迹或不会创造奇迹。因此,您知道,给定SQL查询的性能可能会因查询优化器做出的选择而相差几个数量级。它在许多方面都无法控制。
因此,SQL中存在一种权衡,您放弃对性能的精确控制以换取使用非常高级的语言。而OCaml为您提供了一种相当高级的语言,并具有对性能的精确控制,这是一种不寻常的权衡。
所以您说过OCaml的两个有用特性是简洁性和类型。OCaml如何以对Jane Street等交易系统如此有用的方式如此重要地体现简洁性和类型?所以首先,也许值得说的是,我认为这些——
这些特性无论您是否正在构建交易系统都是有价值的。我认为类型系统和简洁的代码在您的编程语言中始终是一件好事。但让您在同一种语言中拥有这两种特性的关键是类型推断。所以您得到了类型。您在OCaml中编写的每个表达式,该表达式的类型都由编译器精确地知道。但是
它主要通过推断而不是您必须明确说明类型来知道。如果您回顾编程语言的历史,回到20世纪50年代,当时早期——最早取得任何真正成功的两种语言是Lisp和Fortran。Lisp是无类型的,而Fortran是有类型的。Fortran之所以有类型的主要原因是出于性能考虑。Lisp是无类型的,它为您提供了更好的表达能力和简洁性。
而类型在Fortran等语言中之所以如此昂贵的原因之一是表达类型的方式非常简单,非常具体。例如,每次您有一个变量时,您都必须说明该变量的类型。而OCaml以及更广泛的ML系列语言都内置了非常通用的类型推断,因此
几乎所有您关心的类型都可以通过查看您想要执行的具体操作并进行推断来自动推断。您将这些东西加在一起。它们必须是数字。这种简单的推断在程序的许多不同部分中交织在一起,可以让您的代码的完整类型被推断出来。这种代码推断是否有性能损失?没有,因为这一切都在编译时完成。
所以编译时有一些成本,但实际上OCaml是一个相当快的编译器。例如,比C++编译器快得多。有很多原因,但基本上没有明显的性能损失。
这很有趣,因为ML使用的类型推断算法在其渐近线中是双指数的。因此,您可以编写一个17行的程序,该程序直到宇宙热寂才会进行类型检查。但事实证明,对于算法来说难以进行类型检查的程序对于人们来说也很难思考。所以没有人写过它们。
奇怪的是,即使在实践中渐近线很糟糕,但实际上编译器速度非常快。是的,非常有趣。那么让我们更多地讨论类型系统。我浏览了您发表的这个YouTube演讲,您提到了类型系统的所有这些有趣的特性。有参数多态性、代数数据类型、幻像类型等等……
您说实际上更吸引人的是简单的特性,而不是更复杂的特性。所以也许您可以谈谈类型系统中真正重要的简单特性。
很好。我认为您已经提到了两个最重要的特性。第一个是参数多态性,尽管它有一个听起来很花哨的名字,但它实际上是一个简单的特性。另一个是方差的存在,这是我们所说的代数数据类型的关键部分。让我首先谈谈参数多态性。它或多或少与Java中称为泛型的特性相同。
这是拥有语言中某个值、对象或任何实体的能力,该实体以某种其他类型为参数。例如,几乎所有您可以想到的语言都有一个数组类型。您通常可以在多次实例化数组类型。
容器的类型。因此,您可以拥有整数数组或浮点数数组,或者拥有整数和字符串对的数组,或者任何其他类型。有一个自由类型参数。参数多态性是OCaml中的默认多态性,而不是像Java这样的语言,其中默认多态性是我们所说的子类型化。这种类型是一种关系,这是一个。
我认为事实证明,大多数时候您想要的这种多态性是这种我们称为参数多态性的东西,而不是子类型化。两者都有用,但如果您必须选择一个默认值,参数多态性更有用。
它在容器等方面大量出现。例如,如果您考虑一下……如果您想要一个函数来迭代容器并例如查找所有值的总和,或者根据您传入的函数来组合所有值,那么该函数的自然类型是一种参数类型。
也就是说,您希望能够根据容器类中元素的类型(例如数组中元素的类型)来执行此操作,您希望能够遍历数组并将所有元素组合成单个值。也许折叠函数是求和,也许是最大值,或者任何适合您特定应用程序的函数。但是这种泛型迭代器
自然地以参数方式表达,这意味着当我们谈论容器类型时,我们谈论的类型始终相同。而在面向对象的语言中,正常情况是子类型化,出现的不同事物,泛型事物的不同实例,您说,好吧,它是一些子类型,但可能不是相同的子类型。
这就是参数多态性和子类型化之间根本区别,子类型化完全是关于,我想要一些与这个接口匹配的东西,但任何与接口匹配的东西都可以。参数多态性完全是关于,这里有一些东西,我不知道它是什么,但是一旦我决定它是什么,它总是相同的。并且
在某种意义上,整个描述听起来有点花哨,我戴上了我的类型理论帽子,这一切听起来比实际情况要复杂得多。那么,为了澄清那些可能想知道这一切类型根源的人……这些类型系统有什么好处?这些类型系统只是允许您在编译时捕获更多错误吗?我认为有两件事……
好吧,让我们退一步。有一个类型系统,还有一个非常丰富、表达能力强的类型系统。我认为拥有即使是非常基本的类型系统也能为您提供的一件事,人们通常不会想到的是工具。能够查找事物的值和查找事物的类型等基本的IDE式功能在具有类型系统的语言中工作得更好。
所以这是您甚至在进入像ML这样的语言之前就能获得的一种优势。仅仅拥有良好的类型就非常有价值。类型的另一个很好的工具方面是,当您拥有信息丰富的类型来告诉您您的函数可能做什么或您的对象如何构造时,类型本身充当文档的形式。与您在大型代码库中找到的任何其他形式的文档不同,它是一种不会欺骗您的文档。类型系统所说的内容实际上是正确的,因为编译器会检查它。
然后问题是,从像Java这样的相当简单且表达能力不强的类型系统迁移到像OCaml这样的语言中更丰富的类型系统,您会得到什么?我认为关键优势有两个。一个是它使许多事情变得更容易做。
在许多情况下,您必须绕过Java的类型系统才能获得所需的性能,并且必须对事物进行大量复杂的声明以使其清楚您想要做什么。例如一个非常愚蠢但简单的例子是,如果您考虑一下旧的Java,在泛型之前的Java,1995年的原始Java没有泛型。实际上需要一些Haskell程序员才能将泛型引入Java。
在泛型之前的Java中,如果您想返回两件事,那么您会怎么做?好吧,您可以为表达您想要返回的这两件不同的事情创建一个新类。在具有参数多态性的更丰富的语言中,您可以说一个通用的东西。我有一个对的概念。
我不必每次都声明它。它是可重用的。我可以在其中使用任何不同类型的类型。所以有时我返回一个int和一个float的对,有时我返回一个字符的对。我可以返回任何我想要的对。我不必对类型系统说额外的话来允许它让我这样做。因此,拥有更具表达能力的类型可以让您做您想做的事情,而无需填写如此多的表格。您可以用更少的样板代码来完成事情。
当然。请继续。然后另一件事是,它们确实会捕获错误。它们会捕获更多错误。我认为ML程序员总是在说谎,那就是当您用OCaml或类似语言编写程序时,一旦编译完成,它基本上就可以工作了。
从某种意义上说,这是一个荒谬的谎言。类型系统无法捕获许多种类型的错误。但它也惊人地接近真相。您习惯于必须努力克服的大量琐碎错误在像OCaml这样的系统中消失了。我认为最容易理解的一种可能是空指针错误。
我认为人们很难完全接受这样一个事实,那就是当您用OCaml之类的语言编程时,您基本上不会遇到空指针错误。该语言中有足够的支持,可以使用类型系统来证明这些错误不可能发生。这是一个非常有效的工具。空指针错误只是一种错误。还有许多其他类型的错误……
类型系统的严谨和仔细使用可以使这些错误消失,并且只需付出相对较少的努力即可使它们消失。
嗯,在函数式语言中避免空指针错误的这种特性似乎是一个主题。我们做了一个关于Elm的节目,它有点像Haskell,它就像Haskell与JavaScript的结合,基本上是这样。而且,你知道,人们谈论的是在Elm中重建他们的JavaScript应用程序,并且永远不会遇到未定义的错误。这有点……
你知道,这是独一无二的,因为在JavaScript中,你通常是在编写代码,而且你总是会得到未定义的错误。未定义不是变量或其他什么。未定义不是函数。这就是它的说法。所以是的,这绝对是共鸣的主题。所以你提到了这一点,通过使用OCaml获得的样板代码减少。一个有趣的引言是,
我从你那里听到的是,你付给人们的钱不足以让他们仔细审查枯燥的代码。你当时这么说,比如样板代码很无聊,读起来很无聊。人们不想读它。所以他们不会读它。然后你就会在样板代码中出现错误。这就像,你知道的,然后它以某种方式让你失败。所以……
你能解释一下这种样板代码的减少吗?这是如何工作的?你是说OCaml使代码更有趣吗?你这是什么意思?我想有两类样板代码值得区分。一种是类型系统样板代码,您必须进行大量声明以向编译器解释您正在做什么。
这肯定会使代码更难阅读,因为它会在逻辑周围插入所有这些噪声。OCaml的一个好处是,如果您编写代码,您会得到与Python一样简洁的代码,但具有类型安全语言的性能和正确性特性。这种简洁性之所以有价值,部分原因在于它使事情更容易阅读。
所以这是避免一种样板代码的一种方式。但更一般地说,OCaml真正擅长的一件事是,它为您提供了良好的抽象工具,在语言中以轻量级的方式来处理您需要多次以相同方式执行的操作并对其进行参数化,例如,而不是重新分类
多次重写相同的代码,我只是编写一个我在本地定义的小函数,该函数接受一些不同的参数,然后执行适当的操作,然后多次调用该函数。现在,函数的不同调用阐明了您必须执行它的不同时间之间的区别,并明确说明了什么相同。函数做了什么,函数的主体是保留的部分。然后参数是每次调用都会更改的部分。
所以这几乎是一种压缩形式,对吧,您可以将重复的部分巧妙地分解出来,这样您就不必一遍遍地说同样的话了。众所周知的,不要重复自己原则。明白了。但是不同的语言对它的支持程度不同,理想情况下,您希望拥有一种能够有效地做到这一点的语言。在这方面没有提供太多抽象工具的语言是C语言,对吧?
当您与非常优秀的C程序员讨论他们如何处理避免重复时,他们有时会大量使用宏系统,使用磅定义等,以解决C的抽象在这方面相对较弱的问题。但这本身就有一系列问题。宏比函数更难推理。所以它只会让你的代码更……它解决了一些问题,但在它的运行过程中也创造了其他问题。宏和函数有什么区别?
这是一个好问题。您可以将宏视为转换程序的函数,对吧?它生成代码,而不仅仅是做某事。您可以将函数视为具有抽象定义的东西,然后函数的用户不必考虑内部发生了什么。而当您进行句法转换时,可能会发生许多难以考虑的复杂事情。需要注意的一个基本问题是变量捕获,对吧?
如果您有一个宏,它接收一些变量的名称,然后扩展一些代码,生成一些代码,那么您需要注意的一点是,您可能在生成的代码中引用的一个变量也可能被传递给宏的变量引用。因为您只是编写一些编写代码的东西,所以它很容易在其中编写错误并错误地识别不应该识别的变量。
而如果您在普通编程中调用函数,您永远不会遇到这种混淆,另一侧函数的名称是一种无关紧要的细节,您传递给函数的参数的名称是一种无关紧要的句法细节,它永远不会干扰您正在编写的代码的含义。
因此,我们已经讨论了很多OCaml的优点,我想谈谈一些缺点以及这些缺点如何改变Jane Street的编程模型。OCaml的并发支持是什么?也许值得区分两个令人困惑且非常相似的词,并发和并行。并行是关于
同时利用多块硬件,同时进行物理操作。并发在某种意义上是关于在不确定性存在的情况下进行编程,特别是关于事情可能发生时间的不明确性。因此,并发程序是一个有很多事情在逻辑上同时进行的程序,并且
在某种意义上同时进行,但不一定使用多块硬件来执行它。一个可以并发但不并行的程序的一个很好的例子是,您可能会编写一个监听网络并与其他许多服务器建立连接的程序,并且正在向所有服务器发送和接收消息。
您希望逻辑地描述程序的不同部分,这些部分同时向不同的服务器发送消息并等待来自它们的响应。它不确定接下来会发生什么。为此,您需要一些良好的模型来编写这些复杂的并发程序。同时,并行是……
只是您想要从这里获得更多性能的地方。您可以使用并发来实现并行。您可以有很多东西在彼此之间发送消息并等待彼此的输入。这是一种构建分布式或并行系统的方法。但是并发和并行是不同的概念。OCaml有一个非常直接且良好的并发故事,那就是有很多有用的库可以为您提供构建并发程序的精美抽象。
我们有一个名为Async的库。
它为您提供了一个基于有时称为期货的模型的模型,其中期货是一个可能尚未确定的值。通过使用这些小的组合函数(通常称为组合器),您可以根据这些期货将计算串在一起,您可以说,我将调用此函数,它会给我一个作为期货的值,然后我将附加另一个计算,当该期货被填充时,
下一个函数将运行。您可以将这些合理的运算序列组合到同一个程序中。因为您可以控制交错,当这些东西从一个切换到另一个控制时,更容易推断不同部分之间的交互。所以您遇到的正常问题是,您有
许多并发线程遍历相同的内存空间,你必须使用互斥锁、条件变量和信号量。这种代码难以推理,而像async这样的抽象为你提供了编写易于思考的并发程序的极好方法。但OCaml在这方面做得不太好的是并行性,也就是说OCaml没有任何……
任何运行多个共享相同内存空间的线程的方法,这些线程可以同时使用不同的硬件。因此,你无法饱和,你知道,你盒子上的八个核心,用八个在同一个OCaml程序中运行的OCaml线程。
你需要做的是运行多个通过消息传递进行通信的OCaml程序。这就是我们在这里构建系统的方式。它实际上非常好。我们非常喜欢这种方法。实际上,现在正在积极开展工作,以实现多核垃圾收集器,以便你可以在同一个OCaml进程中拥有多个线程。
奇怪的是,我们认为这对社区来说是一件好事,因为人们关心它。但我们不清楚我们将如何使用它。因为我们可以并且确实构建了可以利用大量计算基础设施的高度并行系统。但我们只是以不同的方式去做。使用消息传递构建事物的一个好处是,你不会局限于物理盒子。你可以使用相同的抽象集编写跨越多个盒子的系统。所以……
所以我们没有共享内存并行性。这目前还不是OCaml生态系统的一个特性。我猜一年后,我们可能会将其作为生态系统的一部分。但我不确定这是你想要的东西。特别是在金融领域,是否存在通过不具备这种级别的并行性而避免的共享内存风险?
或者更确切地说,以不同的方式实现并行性。我认为,是的,你会避免很多风险,因为编写使用相同共享内存的代码……
并以有效且正确的方式与之交互非常困难。所以,一件事是,我们没有任何共享内存的程序。事实上,我们经常有程序会设置显式的共享内存段,不同的程序将读取和写入这些段以相互通信。但我们没有将其作为程序的经纬线的一部分,其中所有内容都可能共享。
当我们共享时,我们通常会有明确划分的区域来进行共享。思考这些实际上非常困难。例如,我们上次构建这些抽象之一时,我们实际上是去挖掘了一些关于x86内存模型如何工作的论文,以更好地理解它,因为它实际上非常微妙。如果你去阅读英特尔手册,他们会告诉你一些难以理解的谎言,这些谎言实际上与硬件的工作方式不符。
这是因为你可以高效实现的内存模型在其方面并不简单——例如英特尔的内存模型相当不错。就这些而言,它相对简单。但每个人都有一个简单的内存模型,比如,哦,有一堆内存,然后不同的东西从中读取和写入,所有这些都按某种顺序发生。
就像那样行不通。当有缓存时,事情并非如此。你知道,我可能会写入某些内容,你可能会,你知道,你可能会写入某些内容,并且顺序,没有一个明确定义的顺序,你可以想象整个事情是如何发生的。他们将解释系统上两个不同参与者的观点。因此,共享内存并发性很复杂,难以思考,如果你在……
OCaml,即使它是一种函数式语言,我们也用OCaml编写了很多命令式代码。OCaml与Haskell不同,它是一种可以在任何地方编写命令式代码的语言。类型系统中没有任何约束阻止你这样做。在这种环境中,拥有许多共享相同内存空间的线程,很难推理。我认为……
任何真正关心正确性的人都应该三思而后行,不要在一种自由线程模型中进行编程,在这种模型中,每个人都必须非常自律,并将所有锁放在正确的位置才能使事情顺利进行。这很难做到正确。
嗯。既然我们谈到了分布式系统,我很想更好地了解Jane Street更大的架构。也许如果你有任何有趣的分布式系统故事或你一直在努力解决的一些问题,听一两个轶事会很棒。当然。我想一个有趣的是……所以整个分布式系统应该如何设计的问题是……
我特别感兴趣,因为我的学术背景实际上是分布式系统。所以我上了研究生院,并有很多计划去当教授,我
我学习了很多关于构建严格一致的复制系统的各种巧妙算法,这是分布式系统文献讨论的核心内容之一。如果你看看谷歌、Facebook等等公司,他们实际上在自己的系统中使用了这些原语。例如,谷歌有一个名为Chubby的系统,它是他们的锁服务器。
它是一个分布式系统。你有多个不同的chubby实例,但它们试图呈现一个一致系统的幻觉。他们使用了一堆花哨的算法。特别是,他们对一种称为Paxos的算法进行了变体,这是一种来自分布式系统文献的著名算法,它具有传奇般的复杂性和难以正确实现。关于这一点的一个有趣的轶事是,几年前,我们的一位实习生,一位非常聪明的名叫Ed Yang的家伙,
在完成了一些其他更直接有用的事情之后,我们认为这是一种宣传,为什么我们不用我们漂亮的并发编程库去构建一个Paxos实现呢?让我们看看它运行得如何。他开始构建一个,最终他提出了一个我们可以用来实验的不错的实现。但在过程中,关于如何将算法组合在一起有一些问题。因此,我们查看了谷歌发表的关于该主题的论文之一,并且
他们描述了一些不错的技巧,这些技巧基本上可以使与参与此分布式系统的宿主集合的组成员资格有关的技术方面更容易实现。在他们提出的算法中,有一部分对Ed来说不太有意义,他说,我认为这是不对的。我查看了代码,我认为,
是的,我认为这是不对的。我不明白为什么这会是正确的。我给我在康奈尔大学合作过的一位研究生发了一封电子邮件,他写过许多关于这个领域的论文。他说,不,我认为这是不对的。然后我给谷歌的论文作者发了一封电子邮件,我想强调的是,他不是傻瓜。他是一个我在研究生时阅读过他的论文的人,关于同一个主题。我向他指出,他就像,是的,你是对的。这是一个错误。并且,赞扬他们能够在他们的论文中正确地描述足够的信息,嗯,什么正在发生,嗯,
你可以真正理解实现,足以发现其中的错误。这非常好。与最初的Paxos论文相反。对。所以我认为最初的Paxos论文,我不知道有任何——我不知道有任何错误。这是Leslie Lamport撰写的一篇优美而迷人的论文。我推荐它。它完全难以理解。它被称为《兼职议会》,它有点像——它是一个分布式系统的扩展寓言,就好像它是一个在希腊帕克索斯岛上喧闹的议会一样。对。
这很有趣,但很难理解。然后他写了另一篇名为《Paxos简化》的论文。然后我认识的一位康奈尔大学的家伙,Robert Van Rennessey,写了一篇名为《Paxos适度复杂》的论文。有一系列关于试图理解Paxos的论文。无论如何——如果我记得没错的话,第一篇Paxos论文,他提交给了ACM,他们只是没有接受,因为他们认为,这个人显然疯了。是的。
我还没听说过这个故事。他说,实际上,我想出了如何做分布式系统。所以我认为这里一个有趣的问题是,Paxos的整个想法是否像人们认为的那样广泛适用。很难正确实现Paxos。因此,你试图用像Paxos这样的东西实现的基本内容是所谓的状态机复制,其思想是你对发生的事情、所有事件的线性顺序有一些明确的概念。
并且你有一种明确的方法来解码这些事件并构建状态。这就是状态机的一部分。如果你有一种可靠地以相同的顺序向所有人分发这些事件的方法,那么所有副本都可以按照解码消息的方式继续进行。你有一种非常容易理解的系统复制方法。
Paxos试图解决的基本问题是,当调用调子的人,即决定事件顺序的系统部分,当该事物死亡时会发生什么?我们在这里遇到的经验之一是,我们沿着这些思路构建了一个基于状态机复制的系统,其中
系统的一部分称为排序器,它决定所有事件的顺序。
但是当排序器死亡时,会发生什么情况是人类必须实际介入并手动进行一些检查,并确保死亡的事物没有半死并仍在生成消息,确保它实际上已经死亡,然后再切换到备份。这意味着你会有几分钟的停机时间,因为有人会仔细考虑这种情况。这是缺点。缺点是,当主服务器死亡时,你有一些额外的工作要做,你必须小心地才能恢复。对。
并且存在一个问题,即在什么情况下实现这些花哨算法的额外复杂性是值得的,因为如果你购买一台不错的规格高的机器,它的平均故障时间大约是每两到三年一次。因此,如果我每两到三年可以停机三分钟,我不确定Paxos的所有复杂性是否值得去做。甚至除此之外,
你期望从Paxos实现错误中产生的错误数量我认为可能大于你期望你的优质双电源双网络机器崩溃的错误数量。所以这是你在谈论的安全性和活性。没错。我认为部分原因是Paxos是——Paxos,我绝对认为它在某些应用领域很重要——
但这是一种交易,你使系统更加复杂,从而隐含地牺牲了安全性。
以换取一个更活跃的系统,实际上更活跃的系统是一种自动化方式。如果你查看Paxos系统,当领导者死亡时,因为最终他们确实有一个领导者来决定下一个事务是什么。当领导者死亡时,在领导者选举算法启动并尝试选择新的领导者时,会有一点延迟。在许多这样的系统中,从一个领导者故障转移到另一个领导者确实需要10秒钟或类似的时间。所以这并不是说你没有停机时间。只是停机时间更短。
重要的是,它需要更少的运营工作和更少的运营知识来进行故障转移。你不必有一个聪明的人在思考它。它只是在后台发生。是的,你停机了几秒钟,但这并没有什么大不了的。
所以向Raft的转变——Raft有点像双赢,对吧?因为据我所知,Raft就像你只是获得了一点更多的简单性,获得了一个类似Paxos的系统,但更简单。所以我认为Raft是——它似乎是一个值得尝试的方向,我认为Raft是——我自己没有阅读过论文,但与人们交谈后,我认为它比Paxos稍微简单一些,但它仍然不是我认为根本上更简单。
我仍然认为这是一个难以正确实现的复杂事情。即使对于像Raft这样的系统,也存在巨大的性能权衡。因此,使用单个排序器构建的系统每秒可以执行数百万个事务。如果你可以在不完全疯狂的情况下用OCaml编写极其快速的网络处理器,你就不必……
决定使用FPGA或其他东西重写所有内容。使用普通硬件上的普通OCaml代码,你可以编写可以每秒处理数百万条消息并将消息从你的盒子线到线以几微秒的速度进出的东西。这没什么大不了的。你无法在类似Paxos的系统中做到这一点。根本不可能。所以你在做……
再说一次,我不想说这类东西完全没用。远非如此。我认为它有很多合理的应用。并且可能是像Chubby这样的东西是完全合理的用途,还有Zookeeper等等。事实上,我们开始在内部使用Zookeeper。但我猜想人们过度应用了像Paxos这样的东西,并认为它与实际相关的领域比实际更多。我认为他们真的……
让我感到惊讶的是,你似乎更提倡活性,而不是安全性。我们开始这次谈话时强调了安全性的必要性,尤其是在你的领域。不,不。我认为我提倡的是实际上是更多的安全性,而不是活性。哦,好的。因为当领导者失败时会发生不好的事情是系统停止了。它完全停止了。对。这是一个活性问题,而不是一个安全问题。
我认为你可以通过一种微妙的方式从这里获得更多安全性。这并不是因为算法,你知道,至少算法的柏拉图理想是完全安全的,对吧?正确的Paxos实现没有任何问题。但作为一个实际的软件工程问题,生成正确的Paxos实现并不容易。我认为这有点像密码学故事。这不是你应该考虑自己滚动的事情之一。
要获得正确的Paxos实现需要令人震惊的工程量。我不确定我会对没有正式正确性证明的Paxos实现感到满意。它很微妙。好吧,我想你使用Zookeeper是有道理的,因为这么多眼睛已经覆盖了Zookeeper。你不太可能因为缺乏关注而导致安全性降低。这是真的。我们不使用Zookeeper来……
绝对关键的正确性的事情,对吧?我们构建各种各样的基础设施。我们构建向市场发送订单的东西,我们还构建构建系统,我们构建监控工具,我们构建用于部署服务器的东西,而不是所有东西。基础设施通常很重要,应该高质量,但并非所有东西都同样可怕到出错。我们将不同的工程技术应用于系统的不同部分。
好的,很有趣。好吧,这似乎是一个很好的过渡,可以谈谈与更广泛的市场互动。你曾经说过,编写与更广泛的市场互动的软件、与更广泛的市场接口的软件是可怕的。这是为什么?所以有几件事。一个是这些……
这些代表你进行交易的系统正在让你承担交易,你可能会在这些交易中损失很多钱。如果你有一个系统一遍又一遍地在快速循环中做出同样的愚蠢决定,你将损失很多钱。
你可能会在注意到之前损失令人震惊的金额。从某种意义上说,这与与外部世界互动的混乱细节关系不大,而更多地与我们正在做的核心语义有关,对吧?我们在交易。交易是可怕的。让这件事在没有人工监督的情况下发生,而是由我们这样一些程序员编写并努力使其正确的程序。这本身就是一件可怕的事情。事实上,世界非常复杂且难以建模,这使得情况变得更糟。
你可能对市场如何运作以及它们实际如何运作的简单模型之间存在很大的差距。如果你总是可以依赖你正在交易你认为的符号——你正在交易你认为你正在交易的工具,并且你获得的市场数据是好的,并且你对当前含义的正确理解以及所有可能影响今天与昨天证券含义的不同公司行为是清晰的,当你拥有复杂的——
通过套利关系相关。这是一种我可能应该解释的行话,但明显的例子是ETF之类的东西,其中ETF是一种代表一篮子其他证券的证券。你可能会根据对ETF含义的一些理解来交易它,它可能会以你无法理解而其他人可以理解的方式发生变化。因此,有很多方法可以让你对现实情况的理解与实际情况之间的差距搞砸你。
这增加了它的复杂性。而且由于金融软件很多都很糟糕,情况变得更糟。例如,我们在世界各地的市场进行交易,其中一些市场的设计并不完善。因此,你知道,你尝试,尤其当你尝试进入新的和更模糊的东西时。
你会发现技术中各种奇怪的东西,你想要硬保证。我想知道,如果我发送5000股的订单,并且我想以不低于33.30美元的价格成交,我想知道我不会以超过该数量的股票或任何更糟糕的价格成交。这似乎是一个简单的基本保证。并不是每个交易所都能成功地遵守。他们有错误。他们有问题。他们也犯错误。
那么你如何对冲这种风险呢?你只需要有机构的谦逊吗?因为听起来你必须为许多黑天鹅事件做好准备,无论是ETF没有按照你想要的方式交易,还是系统出现故障。你如何将为这类事情做好准备制度化?所以我认为机构的谦逊是一个很好的说法。
我认为风险管理从技术堆栈一直延伸到交易决策。你需要害怕可能出错的各种事情。我们试图非常小心,并努力思考我们不期望可能发生的事情,关于不同方式
我们担心的不同风险彼此相关。我认为,金融世界最可怕的事情之一是,当事情出错时,它们有时会同时出错。例如,交易所的错误很可能暴露的时候,事情非常繁忙,对吧?当事情非常繁忙时,这通常是因为你对世界的各种其他假设也可能被违反了。
所以你会得到各种各样的东西。只是另一个可怕的事情可以想象。当市场变得非常繁忙时,你知道还会发生什么吗?你的监控可能会下降。因此,同时面临金融市场中的奇怪行为、大量交易以及监控中的故障以及你正在处理的基础系统的语义中的奇怪故障,你只需要害怕所有这些事情。并且当事情出错时要仔细考虑。
关于可能的原因。当事情看起来进展顺利时,你应该有点担心,也许它们出了问题,而你没有意识到。你有没有一个可以按下以关闭系统的红色大按钮?我们有很多方法可以关闭东西,包括一个字面上的物理红色大按钮。哦。是的,我喜欢这么说,是的,它是红色的。它切断了向心跳其他事物并使其保持活动状态的事物的电源。就像我们……
我喜欢认为我们的交易系统最精心设计的技能之一是不交易的能力。我们试图将这一点冗余地设计到系统中。哦,好的。所以也许你可以在每个火灾警报器旁边放一个红色大按钮,人们可以拉动。你有没有相反的东西,比如一个绿色大按钮,你可以在剧烈波动的时候按下?所以,不。不。
所以这是备份。我认为我们的一种普遍存在的风险思维方式是,不交易是安全的。因此,当事情看起来不好的时候,你应该关闭的默认设置。现在你必须小心这一点,因为从……
做好实际业务工作的角度来看,你希望尽可能经常地保持活跃。你正在向市场提供服务。你正在成为人们交易的另一方。当事情复杂而疯狂时,人们最需要你的服务,并且愿意为你的服务支付最多的费用。因此,你在疯狂的时候保持活跃和交易会得到很好的补偿。
但即便如此,我们强烈认为,在事情变得足够糟糕时关闭系统非常重要。因此,如果你在代码中遇到一些疯狂的情况,你会想,如果发生这种情况,然后发生另一件事会怎样?我得到了这个奇怪的角落,它不应该真正发生。好吧,我们通常会说,正确的事情是关闭。
这与我不知道的Facebook非常不同,因为对他们来说,我认为可用性至高无上。例如,如果他们向你展示错误的一组点赞,这并没有什么大不了的。但如果他们没有上线,每个人都会生气。所以我应该澄清我所说的波动性是什么意思。我的意思是,比如说,嗯,一场战争,你知道,一场战争在某个完全意想不到的地方爆发,或者一架飞机坠毁,总统在里面。并且在这种情况下,它可能不会导致像
市场故障或其他什么东西,但它是一种情况,也许你有一些受益于市场波动的头寸,然后你可能想要按下绿色大按钮并利用这一点。也许这根本没有意义。我不知道那个绿色按钮会做什么,但我认为值得说的一件事是……我想一个可以购买所有东西的大按钮。就像,我认为这没有帮助。我要说的一件事是,我认为
在出现一些可怕的消息导致整个市场疯狂的那一天,你预计软件方面会发生不好的事情。因为你会有过多的负载。你将遇到通常不会遇到的奇怪情况。所以这些事情确实相关。另一个很好的相关性例子——这不是一个坏事件,而是Facebook的IPO——
对。
我认为这是事情的方式。从金融角度来看的极端情况也是从技术角度来看的极端情况。有趣。所以你正在谈论这种机构的谦逊。我对Jane Street作为一家技术组织的其他工作方式感到好奇,这可能是听众可以应用到自己公司的方式。Jane Street的一些独特原则是什么?
所以我不知道我是否提升到了原则的水平,但我们非常关注代码的可读性和代码审查。我们投入了大量精力来开发代码审查工具。因此,我们有自己的代码审查系统Iron,它基本上用于我们在这里进行的所有开发。我们非常重视代码审查的原因之一是,其中一个原因是……
这是一种发现错误的方法。但我认为它实际上比这更重要。这不仅仅是确保事情正确。代码审查是一种对你的代码库做很多好事的方法。这是一种共享机构知识、提高你的公交车系数的方法。如果多个人正在阅读代码,那么一个人被公共汽车撞倒不会完全破坏你对相关系统的机构知识。这是一种很好的培训人员的方法。我认为最重要的是,
它会改善你的系统的工程设计,因为它迫使你编写可以阅读的系统。我认为可读的代码与不可读的代码非常不同,并且在功能方面通常更好。足够简单的代码也更容易修改,更有可能按照编写者想要的方式进行,更容易发展,做新的事情。
所以我认为代码审查是我们机构对软件的整体方法的重要组成部分,我认为即使许多地方都有代码审查,我认为我们比我所知道的任何组织都花更多的时间和更仔细地考虑它。明白了。所以为了开始结束,我想问一个哲学问题。自从你开始在Jane Street从事这些金融技术以来,你对金钱的定义是如何改变的?
所以我不知道我的金钱定义是否完全改变了,但我确实对我会称之为期望的东西的想法有了不同的看法,对吧?思考一下你在货币或其他方面做出的某种选择的价值是什么。我想,让我告诉你一个关于这个的简短轶事。当我开始投资时,
在简街。事实上,在我开始之前,我为该公司做了一年的咨询工作,在我做博士后研究期间,我兼职了一半时间。公司早期的一位员工正在和我谈论正在发生的事情。他说,你知道,你提前一年离职的几率有多大?我说,啊,由于学术界工作的各种细节,我可能不太想提前离开。他说,你永久留下来的几率有多大?我说,零。我
他立即回应说,哈哈,我买下了,也就是说,他很乐意和我打赌,我留下来的概率是存在的。这让我思考我现在和当时对这类事情的看法。就像我现在会认为,将某事的概率描述为零几乎违反了我的职业道德。
从某种意义上说,说某事发生的概率为零,如果你从贝叶斯角度来看,就好像说,无论你向我展示多少相反的证据,我仍然相信它是不真实的。对吧。上帝本人可以从天堂降临,说你将在接下来的一年继续在简街工作。而我仍然不会相信。
我认为这种关注点的转变,我认为,来自于思考你决策的货币影响。我们一次又一次看到的非常明显的一个影响是,当你要求某人试图估计他们对某事的信心程度,某事有多大可能是真的时,他们会做得非常糟糕,往往过于自信。
另一方面,如果你说,你会押多少钱?你知道,以什么赔率?你突然退后一步,更仔细地思考它,并说出更有意义的话。所以我认为,思考你行为的货币后果确实有助于你更理性地思考概率。
是的,我绝对记得有这种感觉。我过去玩过很多扑克。正如你所说,我记得读过扑克玩家丹·哈灵顿的这句话。他说,你应该总是……在任何情况下,你的对手虚张声势的最小概率是 5%。并且这种事实上的底线是你的对手只是在胡说八道……
并非零,呃,这对我来说总是很有趣。我确实发现它是一个有用的启发式方法,但它不一定是直观的。你会认为奶奶永远不会虚张声势,或者诸如此类的事情。但事实上,我不知道。这是一个有用的启发式方法。所以我真的很喜欢这种概率思维模型。奶奶是个精明的人,对吧?你永远不知道她的牌桌形象。
无论如何,贾伦·明斯基,感谢你来到软件工程日报。和你交谈真是太棒了,很有趣。时间过得真快。太好了。非常感谢。