用Swift编写高性能数字程序:探索与反思

在过去的几周里,我一直在为一些数字编程库而工作<一个href="https://developer.apple.com/swift/">斯威夫特。但是等等,Swift不正是iOS程序员用来构建应用程序的工具吗?没有任何更多!现在Swift运行在Linux和Mac上,可以用于<一个href="https://vapor.codes/">web应用程序,<一个href="https://medium.com/quick-code/lets-build-a-command-line-app-in-swift-328ce274f1cc">命令行工具,几乎任何你能想到的东西。

在数字编程(如训练机器学习模型)中使用Swift不是很多人正在研究的领域。关于这个主题的信息很少。但是经过几周的研究和试验,我已经成功地创建了两个库,它们可以达到与精心优化的向量化C代码相同的速度,同时又简洁易用。在本文中,我将带您经历这段旅程,并向您展示我学到的如何有效地使用Swift进行数字编程。manbetx官网手机登录我将主要包括来自我的<一个href="https://github.com/jph00/BaseMath">实抹图书馆,这对于提供通用的数学函数浮动,以及针对它们的各种集合的优化版本。(一路走来,关于Swift和其他语言,我有很多话要说,既有肯定的,也有否定的;manbetx官网手机登录如果你对你最喜欢的编程语言有很深的感情,不喜欢看到对它的任何批评,你可能想要跳过这篇文章!)

在以后的文章中,我还将展示如何通过与英特尔的性能库为C.接口,以获得额外的速度和功能

背景

通常在新年前后,我会试着尝试一种新的语言或框架。一个对我特别有效的方法是看看那些创建了我最喜欢的语言、书籍和图书馆的人现在在做什么。这种方法使我成为Delphi、Typescript和c# (Anders Hejlsberg,在我使用Turbo Pascal之后)、Perl (Larry Wall,在我使用之后)的早期用户rn)、JQuery(约翰·瑞西格,我读过之后现代Javascript), 和更多。所以,当我得知<一个href="https://en.wikipedia.org/wiki/Chris_Lattner">克里斯Lattner(谁写的精彩LLVM)正在创造一个新的名为深学习框架<一个href="https://www.tensorflow.org/swift/">斯威夫特对Tensorflow(我会缩写成S4TF从这里),我决定我应该看一看。

注意,S4TF是只是一个无聊的斯威夫特包装器Tensorflow!这是第一个<一个href="https://github.com/tensorflow/swift/blob/master/docs/DesignOverview.md">认真努力我已经看到了合并<一个href="https://techburst.io/deep-learning-est-mort-vive-differentiable-programming-5060d3c55074">可微的编程深入到广泛使用的语言的核心。我希望S4TF能给我们提供一种语言和框架,第一次把可微分编程作为编程世界的头等公民来对待,并允许我们做这样的事情:

这些东西是无法在S4TF,至少尚未(事实上,它是几乎没有的深度学习功能还工作等项目前期)。但是我完全相信他们最终发生,这种情况发生的时候,我相信,在斯威夫特使用微程序将在斯威夫特比任何其他语言一个更好的体验。

我是幸运的,在足以最近的一次会议在克里斯磕碰,当我告诉他我在S4TF兴趣,他还跟愿意帮我开始使用斯威夫特。manbetx官网手机登录我总是发现,和我一起工作对我的工作效率和幸福感的影响要远远大于和我一起工作对我的工作效率和幸福感的影响什么我的工作,所以这是一个极好的理由花费时间在这个项目上。克里斯一直有用得不得了,他是超级漂亮,以及 - 这样的感谢,克里斯!

manbetx官网手机登录关于迅速

Swift是一种通用的、多范式的、编译的编程语言。它是由Chris Lattner在苹果公司时创建的,支持了Objective-C(苹果设备编程的主要语言)中的许多概念。Chris向我描述这种语言为“语法糖”<一个href="https://www.infoworld.com/article/3247799/development-tools/what-is-llvm-the-power-behind-swift-rust-clang-and-more.html">LLVM,因为它与那个编译器框架中的许多思想非常接近。

我已经编写了大约30年的代码,在此期间使用了几十种语言(甚至还使用过其他语言)<一个href="https://perl6.org/archive/rfc/82.html">导致了一些。我总是希望,当我开始学习一门新语言的时候,会有一些开阔思维的新想法出现,而且斯威夫特绝对不会让人失望。Swift力求表达、灵活、简洁、安全、易于使用和快速。大多数语言在这些方面都有很大的妥协。以下是我个人对我使用和喜欢的一些语言的看法,但所有这些语言都有局限性,有时让我感到沮丧:

我想说的是,斯威夫特实际上在避免任何重大妥协方面做得相当不错(锈病可能也是这样;我还没有认真地使用过它,所以不能做出明智的评论)。这不是最好在任何我所提到的领域,但它不是太遥远无论是。我不知道其他单一的语言,可以作出这样的要求(但要注意,它也有其缺点,我将在这篇文章的最后一段地址)。我会在每个反过来看简单:

Protocol-oriented编程

让Swift避免妥协的主要诀窍是它的使用Protocol-oriented编程。基本的思想是我们尝试使用<一个href="https://en.wikipedia.org/wiki/Value_type_and_reference_type">值类型越多越好。在易用性,使用的是最重要的语言,引用类型被广泛使用,因为它们允许使用垃圾收集,虚函数,覆盖超类的行为,等等。协议的面向对象编程是越来越多的这些优点,同时避免引用类型的开销斯威夫特的方式。此外,通过避免引用类型,我们避免了所有推出的复杂的错误时,我们有两个变量在同样的事情指指点点。

值类型也是功能性的编程风格一场伟大的比赛,因为它们允许更好的支持不变性和相关功能的关注。许多程序员,特别是在世界上的Javascript,最近开发的代码如何能更简洁,易懂,并且是正确的,通过利用功能性风格的理解。

如果您使用过像c#这样的语言,您就已经熟悉了用结构体给你一个值类型,并使用提供引用类型。这也正是斯威夫特处理事情的方式。

在我们讨论协议之前,让我们先提一下其他一些基本原则:自动引用计数(弧),即写即拷

自动引用计数(ARC)

来自文档:“Swift使用自动引用计数(ARC)来跟踪和管理你的应用程序的内存使用。在大多数情况下,这意味着内存管理在Swift中“只起作用”,您不需要自己考虑内存管理。manbetx官网手机登录当不再需要类实例时,ARC会自动释放类实例使用的内存。引用计数传统上被动态语言如Perl和Python所使用。在现代编译语言中看到它是很不寻常的。然而,Swift的编译器在不引入开销的情况下,努力仔细地跟踪引用。

ARC对于处理Swift的引用类型(我们有时仍然需要使用)和处理具有写时复制语义的共享内存中的值类型对象的内存使用(或者嵌入在引用类型中)都很重要。克里斯还向我提到了其他一些好处:它提供了确定性破坏,消除了<一个href="https://www.viva64.com/en/b/0437/">GC终结器的常见问题允许缩减到不/可不想GC系统,并消除不可预测/不可再现的暂停。

即写即拷

在大多数语言中,值类型的一个主要问题是,如果您有一个类似于大数组的东西,您不会希望将整个东西传递给一个函数,因为这将需要大量缓慢的内存分配和复制。所以大多数语言在这种情况下使用指针或引用。但是,Swift传递了一个对原始内存的引用,但是如果这个引用使对象发生了变化,那么只有在这个时候它才会被复制(这是在后台自动完成的)。所以我们得到了最好的性能特征值和参考类型的结合!这被称为“copy- at - write”,在一些S4TF文档中被愉快地称为“COW”(是的,还有一个“COW face”的表情符号!)

COW也有助于在功能的编程风格,但仍然需要,但是当没有不必要的复制或手动参考冗长的开销允许突变。

协议

值类型,我们不能用继承层次来获得面向对象编程的好处(尽管你仍然可以使用这些如果你使用引用类型,这也被斯威夫特支持)。因此,相反,斯威夫特给我们协议。很多语言,如打字稿,C#和Java,有想法接口描述对象可以包含的属性和方法的元数据。乍一看,协议很像接口。例如,下面是来自我的BaseMath库的定义ComposedStorage,它是一个协议,描述包装其他一些集合的集合。它定义了两个属性,数据endIndex一种方法,(这是Swift中的一种特殊方法,提供索引功能,就像数组一样)。这个协议定义简单地说明了这一点符合本协议必须提供这三件事的实现。

公共协议ComposedStorage{associatedtype存储:MutableCollection在哪里存储指数= =Inttypealias指数=Intvar数据:存储{得到}varendIndex:Int{得到}(一世:Int)- >存储元件{得到}}

这是一个通用的协议。通用协议不使用<类型>标记类似泛型类,但使用associatedtype关键字。在这种情况下,ComposedStorage是说,数据属性包含称为的泛型类型的内容存储这符合MutableCollection协议,而这又类型有一个associatedtype被称为指数哪一种类型Int为了符合ComposedStorage。它还说方法返回存储元件associatedtype包含。正如您所看到的,协议提供了相当有表现力的类型系统。

现在再往下看,你会看到一些其他的东西实现为这个协议提供!

公共扩展ComposedStorage{(一世:Int)- >存储元件{得到{返回数据(一世]}{数据(一世]=newValue}}varendIndex:Int{返回数据计数}}

这就是事情变得有趣的地方。通过提供实现,我们自动地向任何符合这个协议的对象添加功能。例如,这里是来自BaseMath的整个定义AlignedStorage类提供了类似数组的功能,但在内部使用对准内存,这通常需要快速向量化代码:

公共AlignedStorage<T:SupportsBasicMath>:BaseVector,ComposedStorage{公共typealias元件=T公共var数据:UnsafeMutableBufferPointer<T>公共要求初始化(_数据:UnsafeMutableBufferPointer<T>){自我数据=数据}公共要求方便初始化(_计数:Int){自我初始化(UnsafeMutableBufferPointer(计数))}公共要求方便初始化(_数组:排列<T>){自我初始化(UnsafeMutableBufferPointer(数组))}deinit{UnsafeMutableRawBufferPointer(数据)释放()}公共varp:MutPtrT{得到{返回数据p}}公共函数复制()- >自我{返回初始化(数据复制())}}

如您所见,根本没有多少代码。但是这个类提供了协议的所有功能RandomAccessCollection,MutableCollection,ExpressibleByArrayLiteral,EquatableBaseVector(这包括数以百计的方法,如地图,找到,dropLast距离)。这是可能的,因为协议,这个类符合,BaseVectorComposedStorage,提供此功能协议的扩展(或者直接,或者通过他们反过来遵守的其他协议)。

顺便说一句,你可能已经注意到,我定义AlignedStorage作为,不结构体尽管我所有的早先有关值类型的炒作!manbetx官网手机登录要认识到,仍然有一些地方需要类情况是很重要的。苹果的文档提供了一些<一个href="https://developer.apple.com/documentation/swift/choosing_between_structures_and_classes">有用的指导关于这个主题。structs(还)不支持的一件事是deinit;也就是说,当一个对象被销毁时,能够运行一些代码。在本例中,我们需要在对象完成时释放内存,因此我们需要deinit,这意味着我们需要一个类。

一种常见的情况下,你会发现你真的需要使用的协议是你想要的行为抽象类。斯威夫特完全不支持抽象类,但你可以通过使用协议(例如,在上面的代码获得同样的效果ComposedStorage定义了数据但没有实现它在协议扩展,因此它的作用就像一个抽象的属性)。同样是多重继承的情况:它不是由斯威夫特类的支持,但你可以遵循多个协议,每一个都可以有扩展(这有时被作为mixin在斯威夫特)。协议扩展共享很多的想法与特征在铁锈和类型类在Haskell。

泛型在浮点和双精度上

对于数字编程,如果您正在创建一个库,那么您可能希望它至少能够透明地支持浮动。然而,斯威夫特并没有使这个容易。有一个叫协议BinaryFloatingPoint这在理论上支持这些类型,但不幸的是,只有三个数学函数在Swift中定义了这个协议(ABS,马克斯最小值- 和标准的数学运算符+ - * /)。

当然,您可以简单地为每种类型提供单独的功能,但是您必须处理创建所有内容的两个版本,您的用户也必须处理相同的问题。有趣的是,我没有在网上找到关于这个问题的讨论,斯威夫特自己的图书馆在很多地方都遇到了这个问题。正如下面所讨论的,Swift在数值编程中使用得并不多,这些是我们必须处理的问题。顺便说一下,如果你在网上搜索数字编程代码,你会经常看到的使用CGFloat类型(它受Objective-C的命名约定和限制的影响,稍后我们会学到更多),但这只提供了对它的支持manbetx官网手机登录一个浮点数或双精度数(取决于所运行的系统)。这一事实CGFloat在Swift的Linux端口中存在是相当奇怪的,因为它仅仅是为了苹果特有的兼容性而创建的;它几乎肯定不是你想要使用的东西。

解决这个问题其实是相当简单的,并且是如何使用协议的一个很好的例子。在实抹我创建了SupportsBasicMath协议,这在下面提取:

公共协议SupportsBasicMath:BinaryFloatingPoint{函数log2()- >自我函数logb()- >自我函数nearbyint()- >自我函数RINT()- >自我函数()- >自我}

然后我们告诉斯威夫特说浮动符合这一协议,我们还提供了方法的实现:

扩展浮动:SupportsBasicMath{@inlinable公共函数log2()- >浮动{返回基金会log2(自我)}@inlinable公共函数logb()- >浮动{返回基金会logb(自我)}@inlinable公共函数nearbyint()- >浮动{返回基金会nearbyint(自我)}@inlinable公共函数RINT()- >浮动{返回基金会RINT(自我)}@inlinable公共函数()- >浮动{返回基金会(自我)}}

现在在我们的库代码中,我们可以简单地使用SupportsBasicMath作为一个通用型的约束,我们可以直接调用所有常用的数学函数。(斯威夫特已经提供支持,以透明的方式基本数学运算,所以我们没有做任何事情,使这项工作。)

如果您认为编写所有这些包装器函数一定很痛苦,那么不要担心——我使用了一个方便的技巧,这意味着计算机为我完成了这些工作。诀窍在于使用<一个href="https://github.com/jph00/gyb">GYB使用python代码自动生成方法的模板,像这样:

%对于Fbinfs:函数美元{F}(_b:自我)- >自我%结束#F

如果你看一下Swift代码库本身,你会发现这个技巧被广泛使用,例如用来定义<一个href="https://github.com/apple/swift/blob/master/stdlib/public/Platform/tgmath.swift.gyb">基本数学函数他们自己。希望在将来的某个版本中,我们能在标准库中看到通用的数学函数。在此期间,使用就可以了SupportsBasicMath从巴实抹。

性能技巧和结果

Swift真正酷的地方之一是,像上面这样的包装器没有运行时开manbetx官网手机登录销。如你所见,我用inlinable属性,它告诉LLVM,它的确定以代替调用与实际的函数体这一功能。这种zero-overhead抽象是c++最重要的特性之一;在如此简洁和富有表现力的语言中看到Swift,真是令人惊叹。

让我们通过运行一个简单的基准测试:添加,来做一些实验,看看这是如何工作的2.0数组中的每个元素都以Swift格式浮动。假设我们已经分配了一个适当大小的数组,我们可以使用这个代码(注意:基准是在一个抹简单的函数,倍的代码块):

基准(标题: “迅速添加”){对于i在0 .. 迅速添加:0.963毫秒

在一毫秒内添加一百万浮点数是非常令人印象深刻的!但是看看如果我们尝试一个小的调整会发生什么:

基准(标题:“swift ptr add”){let (p1,p2) = (ar1.p,ar2.p) for i in 0.. swift ptr add: .487 ms

这几乎是相同的代码,但快两倍 - 所以那里发生了什么?实抹添加p财产排列,它返回指针到数组的内存中;所以上面的代码使用的是指针,而不是数组对象本身。通常情况下,由于Swift必须处理COW的复杂性,它无法完全优化这样的循环。但是通过使用指针,我们可以跳过这些检查,Swift可以全速运行代码。注意,由于是写时复制,如果你给数组赋值,它可能会移动,如果你调整它的大小,它也会移动;因此,您应该只在需要时获取指针。

上面的代码仍然相当笨拙,但是Swift使我们能够轻松地提供优雅的、惯用的界面。我添加了一个新的地图方法排列,这使结果成预分配阵列,而不是创建一个新的数组。这里的定义地图(它使用来自一些抹到类型别名让它多一点简洁):

@inlinable公共函数地图<T:BaseVector>(_F:UnaryF,_DEST:T)在哪里自我元件= =T元件{pSrc=p;pde=DESTp;ñ=计数对于一世0.. <ñ{pde(一世]=F(pSrc(一世])}}

正如你所看到的,这是显而易见SWIFT CODE。最酷的是,这让我们现在用的这个简洁明了的代码,仍然可以得到我们以前看到相同的性能:

基准(标题: “地图添加”){ar1.map({$ 0 + 2.0},AR2)}>地图添加:0.518毫秒

我认为这是相当了不起的;我们已经能够创建一个简单的API,它的速度和指针代码一样快,但是对于类用户来说,复杂性完全被隐藏了。当然,我们没有真的知道这有多快,因为我们还没有和c比较,所以我们下一个做。

使用C

Swift的一个真正的优点是,添加你编写的C代码或使用外部Cmanbetx官网手机登录库是多么容易。要使用我们自己的C代码,我们只需创建一个新的包<一个href="https://swift.org/package-manager/">斯威夫特包管理器(SPM),弹出一个。c文件在其来源目录,。h文件在其/来源包括目录中。(顺便说一句,在BaseMath。h文件完全是自动生成的。c文件使用gyb !)这个层次的C语言集成是这是罕见的,其影响是巨大的。这意味着所有的C库,包括操作系统内置的所有功能,优化的数学库,Tensorflow底层的C API等等,都可以从Swift直接访问。如果您出于某种原因需要亲自访问C语言,那么您可以不需要任何手动接口代码或任何额外的构建步骤。

这是C中的求和函数浮动同系列版本类似,两者都是由一个gyb模板生成的):

空虚smAdd_float(常量浮动*pSrc,常量浮动瓦尔,浮动*pDst,常量intlen){对于(int一世=0;一世<len;++一世){pDst(一世]=pSrc(一世]+瓦尔;}}

要调用它,我们需要传入计数作为一个Int32;实抹添加c属性转换为数组(也可以直接使用numericCast (ar1.count)。结果:

基准(标题:“C添加”){smAdd_float (ar1。2.0 p, ar2。p,一个[R1.c)} > C add: .488 ms

基本上和Swift的速度是一样的。这是一个非常鼓舞人心的结果,因为它表明,我们可以得到相同的性能优化C使用Swift。而且不只是任何Swift,而是习惯用法和简洁的Swift,这(多亏了像这样的方法)减少地图可以更接近望的数学方程比是这个快速大部分语言。

减少

现在让尝试不同的实验:以我们的数组的总和。这里有最地道的SWIFT代码:

基准(标题:“reduce sum”){a1 = a1 .reduce(0.0, +)} > reduce sum: 1.308 ms

...这是一个循环同样的事情:

基准(标题:“loop sum”){a1 = 0;对于i在0.. loop sum: 1.331 ms

让我们看看我们之前的指针技巧这次是否也有帮助:

基准(标题: “指针总和”){让P1 = ar1.p A1 = 0;对于i在0 .. <大小{A1 + = P1 [I]}}>指针之和:1.379毫秒

嗯,这是奇怪的。这不是任何更快,这表明它没有获得最佳的性能。让我们再切换到C,看看它是如何执行有:

浮动smSum_float(常量浮动*pSrc,常量intlen){浮动[R=0;对于(int一世=0;一世<len;++一世){[R+ =pSrc(一世];}返回[R;}

结果:

基准(标题:“C sum”){a1 = smSum_float(ar1)。C sum: .230 ms

我将此性能与Intel的优化性能库版本进行了比较总和并发现这是速度甚至比他们的手工优化汇编!为了得到这个比雨燕表现得更好,我也不过需要知道一个小窍门(由LLVM的矢量文档提供),这与编译-ffast-math国旗。对于像这样的数值编程,我建议您总是至少使用这些标记(这是我在这些实验中使用的所有标记,尽管您也可以添加它们)-march =本地,并将优化级别从O2对于Ofast):

-Xswiftc -Ounchecked -xCC -ffast,数学-xCC -02

我们为什么需要这面旗帜?因为严格来说,加法是不结合的,这是由于浮点数的特性。但是,在实践中,这是非常不可能是大多数人会关心的事情!manbetx官网手机登录默认情况下,clang将使用“严格正确”的行为,这意味着它不能用SIMD向量化循环。但随着-ffast-math我们告诉编译器,我们不介意将加法作为关联运算(以及其他操作)处理,因此它将使循环向量化,使速度提高4倍。

要想在C语言代码中获得良好的性能,还需要记住另一件重要的事情,那就是确保您拥有常量就像我在上面的代码中所做的那样,标记在不会改变的所有东西上。

不幸的是,目前似乎还没有一种方法可以快速地向量化任何缩减。所以至少现在,我们必须使用C来获得良好的性能。这并不是语言本身的限制,只是Swift团队还没有着手实现的一个优化。

好消息是:实抹添加总和方法排列,它使用这个优化的C版本,因此如果使用BaseMath,您将自动获得此性能。所以测试1的结果是:失败。我们没能让纯粹的Swift达到和C一样的性能,但至少我们有了一个可以从Swift调用的不错的C版本。让我们继续进行另一个测试,看看我们是否可以通过避免任何减少来获得更好的性能。

临时存储

那么,如果我们想要做的功能降低,如SUM-的平方?理想情况下,我们希望能够结合我们地图风格上与总和,但没有得到斯威夫特的减少未优化的性能损失。为了使这项工作,关键是要使用临时存储。如果我们使用地图功能上面结果存储在预先分配的内存,我们可以传递给我们的C总和实现。我们需要一个类似于静态变量的东西来存储预先分配的内存,但是我们必须处理锁来处理线程之间的争用。为了避免这种情况,我们可以使用线程本地存储(TLS)。像大多数语言一样,Swift提供TLS功能;然而,它并没有将其作为核心语言的一部分(例如,c#),而是提供了一个类,我们可以通过该类进行访问Thread.current.threadDictionary。BaseMath将预分配的内存添加到这个字典中,并在内部作为tempStore;这是随后的内部实现一元函数的减少(也有二元和三元版本)的:

@inlinable公共函数总和(_F:UnaryF)- >元件{自我地图(F,tempStore)返回tempStore总和()}

我们可以这样使用:

基准(标题:“lib sum(sqr)”){a1 = ar1.sum(Float.sqr)} > lib sum(sqr): .786 ms

这提供了一个很好的速度比一般的斯威夫特减少版本:

基准(标题: “减少sumsqr”){A1 = ar1.reduce(0.0,{$ 0 + Float.sqr($ 1)})}> sumsqr减少:1.459毫秒

下面是C版:

浮动smSum_sqr_float(常量浮动*限制pSrc,常量intlen){浮动[R=0;的#pragma铛环interleave_count(8)对于(int一世=0;一世<len;++一世){[R+ =sqrf(pSrc(一世]);}返回[R;}

我们来试试:

基准(标题:“c sumsqr”){a1 = smSum_sqr_float(ar1)。csumsqr: .229毫秒

所有标准一元数学函数总和的C实现由实抹可用,所以你可以通过简单地使用调用上面的执行:

基准(标题:“lib sumsqr”){a1 = ar1.sumsqr()} > c sumsqr: .219 ms

总结:而Swift版本使用临时存储(并调用C作为最终的和)的速度是直接使用的两倍减少,使用C语言又快了3倍或3倍以上。

正如你所看到的,Swift中的数字编程有很多优点。manbetx官网手机登录通过方便的自动内存管理和优雅的语法,您可以获得优化后的C的性能。

我用最简洁,最灵活的语言是Python。而且我用最快的是C(嗯......其实这是FORTRAN,但我们不要去那里。)那么它是怎样堆栈起来反对这些高吧?这种想法,我们可以在一个语言比较的Python的灵活性和C的速度是一个了不起的成就本身!

总的来说,我的观点是,通常情况下,用Swift编写代码比用Python编写我想要编写的代码要多一些,而且抽象通用代码的方法也少一些。例如,我在Python中经常使用decorator,并使用它们为我编写大量代码。我使用* ARGS* * kwargs很多(新的)<一个href="https://github.com/apple/swift-evolution/blob/master/proposals/0195-dynamic-member-lookup.md">动态特性在Swift中可以提供一些这样的功能,但还不够)。我一次压缩多个变量(在Swift中,你必须压缩多个变量的对,然后使用嵌套的parens对它们进行解构)。然后是你必须写的代码,让你的类型整齐排列。

我还发现Swift的性能比C. C.更难推理和优化。C. C.在性能方面有自己的怪癖(比如需要使用)manbetx官网手机登录常量有时甚至需要限制但是它们通常有更好的文档说明、更好的理解和更一致。此外,clang和gcc等C编译器还使用pragmas等提供了强大的附加功能经济新闻甚至可以自动并行化代码。

话虽如此,雨燕更接近实现Python的表现和C的速度比我用过的任何其他语言的组合。

还有一些问题需要注意。需要考虑的一件事是,面向协议的编程需要一种非常不同的方式来处理您可能已经习惯的事情。从长远来看,这可能是一件好事,因为学习新的编程风格可以帮助你成为一个更好的程序员;但它可能会在最初几周带来一些挫折。

这个问题是特别具有挑战性,因为斯威夫特的编译器经常不知道哪里的协议型问题的根源确实是的,其猜测类型的能力仍然是相当片状。所以极其细微的变化,如改变一个类型的名称,或更改其中一个类型的约束定义,可以改变的东西,用来工作,到的东西,吐出来的四个屏幕的错误消息。我的建议是尝试在一个独立的测试文件来创建你的类型结构的最小版本,并得到的东西先在那里工作。

请注意,但是,易用性通常需要妥协。Python是特别容易,因为它是完全竭诚为您搬起石头砸自己的脚。斯威夫特至少可以确保你第一次知道如何解开鞋带。克里斯告诉我:首先建立斯威夫特当间距是为了优化重要的事情就是“端到端的时间去正确执行任何你正在试图做的”。这包括时间砸向了代码,时间调试它,和时间来重构/维持它,如果你正在做一个改变现有的代码库。我还没有足够的经验,但我怀疑在这个尺度上斯威夫特将会是一个伟大的表演者。

Swift有一些地方我不喜欢:由于苹果与Objective-C的历史,它的打包系统,它的社区,以及缺乏c++支持而做出的妥协。或者更准确地说:它主要是雨燕生态系统我不喜欢。语言本身就很令人愉快。生态系统可以被修复。但是,现在,这是敏捷程序员必须处理的情况,所以让我们依次看看这些问题。

objective - c

Objective-C是20世纪80年代开发的一种语言,旨在将Smalltalk的一些面向对象的特性引入c语言。它是一个非常成功的项目,在1988年被NeXT选为NeXTSTEP编程的基础。随着NeXT被苹果收购,它成为了苹果设备编码的主要语言。今天,它显示了它的年龄,以及决定使它成为严格的c超集所带来的限制。例如,Objective-C不支持真正的函数重载。相反,它使用了一种叫做选择,它们只是必需的关键字参数。每个函数的全名由函数名与所有选择器名的串联来定义。AppleScript也使用了这个想法,它提供了与允许名称非常类似的东西打印是指在不同环境下不同的事情:

打印文件2打印文件2的第1页到第5页

而AppleScript又继承了HyperTalk的这一思想。HyperTalk是一种语言,创建于1987年,专为深受苹果喜爱(也为苹果所怀念)的用户设计的。HyperCard程序。考虑到所有这些历史,不出意外的是,如今苹果公司的大多数人都非常喜欢“必选命名参数”这个概念。也许更重要的是,它为Objective-C的设计者提供了一个有用的妥协,因为他们能够避免在语言中添加真正的函数重载,保持与C的紧密兼容性。

不幸的是,这个限制在今天仍然影响着Swift,而40多年前,正是这种情况导致了它在Objective-C中的引入。斯威夫特提供真正的函数重载,这在数字编程中尤其重要,因为您真的不想为浮点数、双精度数和复数(以及四元数等等)创建整个单独的函数。但是默认情况下,所有的关键字名称都是要求,这可导致冗长,在视觉上杂乱代码。和苹果的风格指南大力推动编码这样的风格;他们的Objective-C和斯威夫特,风格指南密切互为镜像,而不是让程序员能够真正利用斯威夫特的独特能力。您可以通过在前面的参数名称与选择的需要命名参数了_, BaseMath在不需要可选参数的任何地方使用。

事情变得相当冗长的另一个方面是,当谈到与合作基金会的主类库,它也被Objective-C使用。Swift的标准库缺少很多你需要的功能,所以你经常需要求助于Foundation来完成工作。但你不会喜欢的。在享受使用Swift这样一种优雅设计的语言的乐趣之后,使用它访问像基础一样笨拙的库就显得特别悲哀了。manbetx官网手机登录例如,Swift的标准库没有提供一种内建的方式来格式化精度固定的浮点数,所以我决定将这个功能添加到我的SupportsBasicMath协议。这是代码:

扩展SupportsBasicMath{公共函数(_数字:Int)- >字符串{FMT=NumberFormatter()FMTminimumFractionDigits=数字FMTmaximumFractionDigits=数字返回FMT(:自我的NSNumber)??\ (自我)}}

我们可以添加这个功能浮动通过编写这样的扩展真的很酷,处理Swift转换失败的能力也很酷??操作符。但是看看实际使用的代码的冗长NumberFormatter从基础类!它甚至不接受浮动要么但是尴尬的是NSNumber类型来自Objective-C(由于在Objective-C中缺少泛型,这本身就是一个笨拙的解决方案)。所以我要加上一个的NSNumber财产SupportsBasicMath做铸造。

Swift语言本身有助于支持更简洁的样式,比如f (0) {}关闭的风格。简洁是用于数字编程很重要,因为它让我们写我们的代码更密切地反映了我们正在实施的数学和理解一目了然整个方程式。对于这个(以及更多)高超的论述,请参阅艾弗森的图灵奖演讲<一个href="https://dl.acm.org/citation.cfm?id=1283935">符号作为思考的工具。

Objective-C也没有命名空间,这意味着每个项目会选择一些2或3个字母的前缀,并将其添加到所有符号中。大多数基础库仍然使用从Objective-C继承的名称,所以你会发现自己使用了像这样的类型CGFloat和喜欢的功能CFAbsoluteTimeGetCurrent。(这些符号,我的每一次我型一个肯定宝宝麒麟大声呼喊在痛苦中...)

雨燕队取得了惊人的决定使用Objective-C的实现提供了基础,并在苹果设备上运行时,斯威夫特其他图书馆,但在Linux上使用本地斯威夫特库。其结果是,有时你会看到每个平台上不同的行为。例如,在苹果设备上的单元测试框架是无法找到并运行被写成协议扩展试验,但他们在Linux下正常工作。

总的来说,我觉得Objective-C的约束和历史似乎太频繁地渗入到Swift编程中,而每次它发生时,都会出现真正的摩擦。然而,随着时间的推移,这些问题似乎在减少,我希望在未来我们会看到越来越多的人摆脱Objective-C的束缚。例如,我们可能会看到为一些Objective-C类库创建习惯用法的快速替换的真正努力。

社区

在过去的几年里,我一直在使用Python,但是有一件事一直困扰着我,那就是Python社区中有太多的人只使用过这一种语言(因为它是一种很棒的初学者语言,而且被广泛地传授给本科生)。因此,人们没有意识到不同的语言可以用不同的方式做事,每种选择都有其优缺点。相反,在Python世界中,人们倾向于认为Python方法是唯一正确的方法。

我在Swift中看到了类似的东西,但在某些方面它甚至更糟:大多数Swift程序员最初都是作为Objective-C程序员开始的。所以你在网上看到的很多讨论都是来自于Objective-C程序员写Swift的风格与Objective-C的做法非常相似。而且几乎所有的编程都是在Xcode中完成的(Xcode几乎是我最不喜欢的IDE,除了它的出色之外斯威夫特操场因此,你会在网上找到很多建议,告诉你如何通过让Xcode为你做事来解决快速问题,而不是自己编写代码。

大多数敏捷程序员都在编写iOS应用程序,所以你也会找到很多关于如何设计移动GUI的指导,但是几乎没有关于如何为Linux分发命令行程序或如何编译静态库的信息。manbetx官网手机登录总的来说,由于对Swift的Linux支持仍然很新,没有太多关于如何使用它的信息,而且许多库和工具在Linux下无法工作。

大多数时候,当我在跟踪协议一致性的问题,或者试图找出如何优化代码时,我能找到的唯一信息就是苹果Swift语言团队的邮件列表讨论。这些讨论往往集中在编译器和库的内部,而不是如何使用它们。因此,在应用程序开发人员讨论如何使用Xcode和Swift语言实现讨论如何修改编译器之间,有一个很大的空白。现在在[https://forums.swift.org/]的Discorse论坛周围已经形成了一个很好的社区,希望随着时间的推移,它能够成为一个对Swift程序员有用的知识库。

包装和安装

Swift有一个官方认可的软件包系统,称为Swift软件包管理器(SPM)。不幸的是,这是我使用过的最糟糕的包装系统之一。我注意到,在创建包管理器时,几乎每种语言都从头开始重新创建所有东西,并且无法利用以前尝试的所有成功和失败。斯威夫特遵循了这种不幸的模式。

有一些真正优秀的包装系统。也许最好的是Perl的CPAN,它包括一个国际自动测试服务,可以在各种各样的系统上测试所有的包,深度集成文档,有优秀的教程,等等。另一个很棒的(更现代的)系统是conda它不仅可以处理特定于语言的库(主要关注Python),还可以自动安装兼容的系统库和二进制文件,还可以管理主目录中的所有操作,因此甚至不需要根访问。而且它在Linux、Mac和Windows上都运行良好。它可以处理已编译模块或源代码的分发。

另一方面,SPM没有这些系统的任何优点。尽管Swift是一种编译语言,但它没有提供创建或分发编译包的方法,这意味着您的包的用户必须安装构建它所需的所有先决条件。SPM不允许您描述如何构建您的包,因此(例如)如果您使用BaseMath,那么当您构建使用它的东西时,您需要记住添加良好性能所需的标志。

处理依赖关系的方式非常笨拙。Git标记或分支用于依赖项,在本地开发构建版本和打包版本之间切换并不容易(例如-e旗帜皮普或者是conda开发命令)。相反,你必须修改改变依赖的位置包文件,并记住它切换回你提交之前。

要记录SPM的所有缺陷需要很长的时间;相反,您可以在这样的假设下工作:无论您现在使用什么打包系统,您所欣赏的任何有用的特性都可能不在SPM中。希望有人能考虑为Swift建立一个基于conda的系统,我们可以开始使用它……

此外,安装斯威夫特的是一个烂摊子。在Linux上,例如,只有Ubuntu的支持,和不同的版本需要不同的安装程序。在Mac上,雨燕的版本是绑版本的Xcode在混乱和尴尬的方式,以及命令行版本的Xcode是有些不同的,但多少有些联系,以多种方式让我的大脑受到伤害。再次,畅达似乎是它可以提供避免这种情况最好的选择,因为一个畅达包可用于支持Linux的任何口味和Mac也可以以同样的方式支持。如果工作完成后得到斯威夫特上畅达,那么这将是可能的,只是说conda安装迅速在任何系统上,一切都会只是工作。这也将为版本控制、隔离环境和复杂的依赖跟踪提供解决方案。

(If you’re on Windows, you are, for now, out of luck. There’s an old unofficial port to Cygwin. And Swift runs fine on the Windows Subsystem for Linux. But no official native Windows Swift as yet, sadly. But there is some excellent news on this front: a hero named Saleem Abdulrasool has made<一个href="https://forums.swift.org/t/windows-nightlies/19174">大踏步完全独立地实现一个完整的本地端口,在过去的几天里,它已经达到了绝大多数Swift测试套件都能通过的程度。)

c++

当苹果使用Objective-C来实现他们的“C with object”解决方案时,世界其他地方都使用c++。最终,Objective-C扩展也被添加到c++中,以创建“objective - c++”,但没有尝试在不同的语言之间统一概念,因此产生的语言是一个变种<一个href="https://en.wikipedia.org/wiki/Objective-C">许多重要的限制。然而,该语言有一个很好的子集可以绕过C语言的一些最大限制;例如,您可以使用函数重载,并可以访问一个丰富的标准库。

不幸的是,斯威夫特无法与C接口++的。即使是一些简单的含重载函数的头文件会导致雨燕语言的互操作失败。

对于数字程序员来说,这是一个大问题,因为当今许多最有用的数字库都是用c++编写的。例如,<一个href="https://pytorch.org/cppdocs">阿托恩图书馆在PyTorch的心脏是C ++。有充分的理由认为数字程序员对C ++瘦:它提供了所需的简洁和表现力的解决方案,为数字编程问题的功能。例如,朱莉娅程序员(正确)引以为傲的是多么容易,以支持他们的语言,他们中有记载关键广播功能<一个href="https://nextjournal.com/sdanisch/the-julia-challenge">茱莉亚挑战。在c++中,这个挑战优雅而快速<一个href="https://medium.com/@wolfv/the-julia-challenge-in-c-21272d36c002">解决方案。你不会找到在纯C这样的事情,但是。

因此,这意味着一个庞大而越来越多的用于数字编程的最重要的基石是边界为雨燕的程序员了。这是一个严重的问题。(你可以写纯C封装为C ++类,然后创建一个使用这些包装迅捷的类,但是这是一个非常大,非常枯燥的工作,这我不知道很多人都可能走上。)

其他语言已经展示了围绕这个问题的潜在方法。例如,Windows上的c#提供了与c++ /CLI接口的“It Just Works”(IJW),这是一个支持. net的c++超集。更有趣的是<一个href="https://github.com/mono/CppSharp">CPPSharp项目利用LLVM自动生成的C ++代码C#的包装,没有调用开销。

解决这个问题并不容易为斯威夫特。但由于雨燕使用LLVM,并已与接口C(和Objective-C),这也许是更好地拿出几乎比任何其他语言的最佳解决方案。除非,也许,朱莉娅的,因为他们已经<一个href="https://github.com/Keno/Cxx.jl">已经做到了这一点。<一个href="https://github.com/JuliaInterop/CxxWrap.jl">两次。

结论

Swift是一种非常有趣的语言,它支持快速、简洁、富有表现力的数字编程。Tensorflow项目的Swift可能是创建一种编程语言的最佳机会,在这种语言中,可微分编程是头等公民。Swift还让我们可以轻松地与C代码和库进行接口。

然而,斯威夫特在Linux上还很不成熟,包装体系薄弱,安装笨重,和库从一些起争执由于历史联系的Objective-C的困扰。

那么它是如何叠起?In the data science world, we’re mainly stuck using either R (which is the least pleasant language I’ve ever used, but with the most beautifully designed data munging and plotting libraries anywhere) or Python (which is painfully slow, very hard to parallelize, but is extremely expressive and has the best deep learning libraries available). We really need another option. Something that is fast, flexible, and provides good interop with existing libraries.

总的来说,Swift语言本身看起来正是我们所需要的,但是生态系统的大部分需要被取代,或者至少需要显著地升级。虽然S4TF项目似乎有可能创造出一些重要的东西,但还没有数据科学生态系统。如果你有兴趣参与一件有巨大潜力的事情,并且有一些非常优秀的人在努力实现它,这是一个非常好的地方,你可以花时间在这上面,这样你就可以帮助解决前进道路上的障碍。