带Swift的高性能数字编程:探索与思考

在过去的几周里,我一直致力于为迅捷.但是等一下,Swift不正是iOS程序员用来构建应用程序的吗?再也没有了!如今,Swift在Linux和Mac上运行,可用于Web应用程序命令行工具,几乎任何你能想到的事情。

使用Swift进行数字编程,比如训练机器学习模型,不是很多人都在努力的领域。关于这个话题的信息很少。但是经过几周的研究和实验,我成功地创建了几个库,它们可以达到与仔细优化的矢量化C代码相同的速度,同时简洁易用。在本文中,我将带您经历这段旅程,并向您展示我学到的关于如何有效地使用Swift进行数字编程的知识。manbetx官网手机登录我将主要从我的巴斯马特图书馆,它为浮标双重的,以及针对各种集合的优化版本。(一路上,我有很多话要说,正的和负的,manbetx官网手机登录关于swift和其他语言;如果你是一个与你最喜欢的编程语言有着深厚情感联系的人,并且不喜欢看到任何对它的批评,你可能想跳过这篇文章!)

在未来的文章中,我还将展示如何通过与英特尔的C性能库接口来获得更多的速度和功能。

背景

通常在新年前后,我会尝试使用一种新的语言或框架。一个对我特别有效的方法是看看那些建立了我最喜欢的语言的人,书,图书馆也在这样做。这种方法使我成为Delphi的早期用户,Typescript以及C(安德斯·海斯伯格,在我用了他的涡轮帕斯卡之后,波尔(拉里·沃尔,我用过以后)jquery(约翰·雷西格,读完之后现代JavaScript)还有更多。所以当我得知克里斯拉特纳(编写了精彩的LLVM)正在创建一个新的深入学习框架,名为TensorFlow的Swift(我会缩短到S4TF从这里)我决定去看看。

注意,S4TF是只不过是TensorFlow的一个无聊的快速包装!这是第一次认真努力我已经看到合并可微程序设计深入到一种广泛使用的语言的中心。我希望S4TF能为我们提供一种语言和框架,第一次,将可微程序设计视为编程界的一流公民,并允许我们做以下事情:

这些东西在S4TF中不可用,至少到目前为止(事实上,这是项目的早期阶段,几乎所有的深度学习功能都不起作用)。但我完全希望它们最终会发生,当这种情况发生时,我相信在swift中使用可区别编程在swift中会比在任何其他语言中都有更好的体验。

我很幸运在最近的一次会议上遇到了克里斯,当我告诉他我对S4TF感兴趣时,manbetx官网手机登录他很友善,愿意帮助我开始学斯威夫特。我一直都发现我的工作比我的工作效率和幸福更重要。什么我工作,所以这是另一个花时间在这个项目上的好理由。克里斯帮了大忙,他也非常好,所以谢谢,克里斯!

manbetx官网手机登录关于Swift

Swift是一种通用工具,多范式编译的程序设计语言。它是由Chris Lattner在苹果时开始的,并支持了Objective-C(用于苹果设备编程的主要语言)中的许多概念。克里斯形容我的语言为“语法糖LLVM“,因为它与编译器框架中的许多思想非常接近。

我已经编码30年了,在那个时候已经使用了几十种语言(甚至贡献了一些.我一直希望,当我开始寻找一种新的语言时,会有一些思想打开新的思路去发现,斯威夫特绝对不会让人失望。斯威夫特努力表现,灵活的,简洁的,安全的,易于使用,而且速度快。大多数语言在这些领域中至少有一个领域会有明显的妥协。以下是我个人对我使用和喜欢的一些语言的看法,但所有这些都有局限性,我有时会感到沮丧:

我想说的是,斯威夫特实际上在避免任何重大妥协方面都做得很好(也可能是生锈;我没有认真使用过,所以不能发表有见地的评论)。这不是最好的在我提到的任何地区,但也不算太远。我不知道还有哪种语言可以做出这样的声明(但请注意,它也有其缺点,我将在本文的最后一节中介绍)。我将依次简要介绍:

面向协议的程序设计

让斯威夫特避免妥协的主要技巧是面向协议的程序设计.基本的想法是我们尝试使用价值类型尽可能多。在大多数语言中,易用性很重要,引用类型被广泛使用,因为它们允许使用垃圾收集,虚拟功能,超类行为,诸如此类。面向协议的编程是Swift获得这些好处的方法,同时避免了引用类型的开销。此外,通过避免引用类型,当我们有两个变量指向同一个对象时,我们可以避免所有引入的复杂错误。

值类型也非常适合编程的功能风格,因为它们允许更好地支持不可变性和相关的功能问题。很多程序员,尤其是在JavaScript世界中,最近对代码如何更简洁的理解,可以理解,正确,通过利用功能风格。

如果你使用过C语言,你已经熟悉了用结构给你一个值类型,并使用为您提供一个引用类型。这就是处理事情的速度。

在我们达成协议之前,让我们介绍一些其他的基本原理:自动参考计数(弧)和写上拷贝.

自动参考计数(ARC)

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

ARC对于处理swift的引用类型(有时我们仍然需要使用)都很重要,还可以处理值类型对象中的内存使用,这些对象与copy-on-write语义共享内存,或嵌入在引用类型中的。克里斯还向我提到了许多其他好处:它提供了确定性破坏,消除了GC终结器的常见问题,允许缩小到不需要/不需要GC的系统,消除不可预测/不可重复的停顿。

写上拷贝

在大多数语言中,值类型的一个主要问题是,如果有类似于大数组的东西,你不想把整个东西传递给函数,因为这需要大量缓慢的内存分配和复制。所以在这种情况下,大多数语言都使用指针或引用。斯威夫特然而,传递对原始内存的引用,但是如果引用改变了对象,只有这样它才会被复制(这是在后台自动完成的)。因此,我们得到了价值和参考类型组合的最佳性能特征!这被称为“书面副本”,这在一些S4TF文档中被称为“奶牛”(是的,还有牛脸表情符号!)

Cow还帮助以功能性的方式进行编程,然而,在需要的时候仍然允许变异,但是没有不必要的复制或手动引用的冗长的开销。

协议

对于值类型,我们不能使用继承层次结构来获得面向对象编程的好处(尽管如果使用引用类型,仍然可以使用这些层次结构,也有Swift的支持)。所以,相反,斯威夫特给我们协议.多种语言,例如,typescript,C,和Java,有这样的想法界面—描述对象可以包含哪些属性和方法的元数据。乍一看,协议看起来很像接口。例如,这是我的basemath库的定义组合存储器,它是一个协议,用于描述包装其他集合的集合。它定义了两个属性,数据终结索引,一种方法,下标(这是Swift中的一种特殊方法,并提供索引功能,就像一个数组)。这个协议定义简单地说符合此协议必须提供这三件事的实现。

公众的 协议 组合存储器 {
  关联类型 保管部可变集合 哪里 保管部.索引=利息
  类型别名 索引=利息

  var 数据 保管部 {得到 设置}
  var 终结索引 利息 {得到}
  下标 利息>保管部.元素 {得到 设置}
}

这是一个通用的协议。通用协议不使用 类标记,而是使用关联类型关键字。所以在这种情况下,组合存储器就是说数据属性包含一个名为保管部哪个符合可变集合协议,而这种类型反过来又有一个关联类型打电话索引必须是类型利息为了符合组合存储器.它还说下标方法返回保管部元素 关联类型包含。正如你所看到的,协议提供了一个相当有表现力的类型系统。

现在再看一看,你会看到别的东西……还有实现方式为本协议提供!

公众的 延伸 组合存储器 {
  下标 利息>保管部.元素 {
    得到 { 返回 数据[]     }
    设置 { 数据[] = 新值 }
  }
  var 终结索引 利息 {
    返回 数据.计数
  }
}

这就是事情变得非常有趣的地方。通过提供实现,我们将自动向任何符合此协议的对象添加功能。例如,以下是basemath的完整定义对齐存储,类提供类似数组的功能,但在内部使用对齐的内存,快速矢量化代码通常需要:

公众的  对齐存储<T支持基本路径> 基站器 组合存储器 {
  公众的 类型别名 元素=T
  公众的 var 数据 不可更改的缓冲指针<T>

  公众的 必修的 初始化γ 数据 不可更改的缓冲指针<T> {自己.数据=数据}
  公众的 必修的 方便 初始化γ 计数 利息      { 自己.初始化不可更改的缓冲指针计数)) }
  公众的 必修的 方便 初始化γ 数组 数组<T> { 自己.初始化不可更改的缓冲指针数组)) }

  德尼特 { 不可更改的rawbufferpointer数据.解除分配() }

  公众的 var  穆特普特 {得到 {返回 数据.}
  公众的 芬克 复制()>自我 { 返回 .初始化数据.复制() }
}

正如你所看到的,根本没有多少代码。但是这个类提供了协议的所有功能随机存取集可变集合可表示的YarrayLiteral可等值的,和基站器(其中包括数百种方法,如地图找到液滴,和距离)这是可能的,因为这个类所遵循的协议,基站器组合存储器,通过以下方式提供此功能:协议扩展(直接,或者通过他们依次遵守的其他协议)。

顺便说一下,你可能已经注意到我定义了对齐存储作为,不结构,尽管我之前大肆宣传过价值类型!manbetx官网手机登录重要的是要认识到在某些情况下仍然需要上课。苹果的文档提供了一些有益的指导关于这个话题。结构尚不支持的一件事是德尼特;也就是说,在对象被破坏时运行一些代码的能力。在这种情况下,当我们处理完对象后,我们需要重新分配内存,所以我们需要戴尼特,这意味着我们需要上课。

你会发现你真正需要使用协议的一个常见情况是你想要的行为抽象类.Swift根本不支持抽象类,但通过使用协议(例如在上述代码中组合存储器定义数据但没有在协议扩展中实现,因此,它就像一个抽象属性)。多重继承也是如此:Swift类不支持它,但是你可以遵守多种协议,每一个都可以有扩展(这有时被称为混入在斯威夫特)协议扩展与特点生锈和类型类在哈斯克尔

浮点数和双精度数上的泛型

对于数字编程,如果您正在创建一个库,那么您可能希望它至少透明地支持浮标双重的.然而,斯威夫特不容易做到这一点。有一个协议叫做二元浮点理论上支持这些类型,但不幸的是,只有三个swift中的数学函数被定义为该协议(防抱死制动系统最大值,和-以及标准数学运算符+-*/

你可以,当然,只需为每种类型提供单独的功能,但接下来你要处理的是创建两个版本的所有东西,你的用户也必须处理同样的问题。有趣的是,我在网上找不到关于这个问题的讨论,斯威夫特自己的图书馆在多个地方都面临着这个问题。如下文所述,Swift在数字编程中的应用并不多,这些是我们必须处理的问题。顺便说一句,如果您在线搜索数字编程代码,你会经常看到CG浮标类型(遭受了Objective-C的命名约定和限制,稍后我们将进一步了解)。manbetx官网手机登录但这只为浮动或双浮动(取决于运行的系统)。事实是CG浮标在swift的linux端口中存在是很奇怪的,因为它的创建只是出于苹果特定的兼容性原因;它几乎肯定不是你想用的东西。

解决这个问题其实相当简单,并且是如何使用协议的一个很好的例子。在BaseMath中,我创建了支持基本路径协议,提取如下:

公众的 协议 支持基本路径二元浮点 {
  芬克 登录2() > 自我
  芬克 洛布() > 自我
  芬克 近邻() > 自我
  芬克 林特() > 自我
  芬克 () > 自我
   
}

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

延伸 浮标  支持基本路径 {
  @可链接的 公众的 芬克 登录2() > 浮标 {返回 基础.登录2自己}
  @可链接的 公众的 芬克 洛布() > 浮标 {返回 基础.洛布自己}
  @可链接的 公众的 芬克 近邻() > 浮标 {返回 基础.近邻自己}
  @可链接的 公众的 芬克 林特() > 浮标 {返回 基础.林特自己}
  @可链接的 公众的 芬克 () > 浮标 {返回 基础.自己}
   
}

现在在我们的库代码中,我们可以简单地使用支持基本路径作为对泛型类型的约束,我们可以直接调用所有常见的数学函数。(Swift已经以透明的方式为基础数学运算符提供了支持,所以我们不需要做任何事情来实现这一目标。)

如果您认为编写所有这些包装器函数一定是一个很大的痛苦,别担心,我使用了一个方便的技巧,这意味着电脑帮了我。诀窍是使用吉布使用python代码自动生成方法的模板,像这样:

% 对于 f 在里面 二进制位
  芬克 ${f}(γ  自我 > 自我
% 结束 γ f

如果你看一下swift代码库本身,你会发现这种把戏被大量使用,例如,定义基本数学函数他们自己。希望在未来的版本中,我们可以在标准库中看到通用的数学函数。与此同时,只使用支持基本路径来自巴斯马特。

性能技巧和结果

关于Swift的一个真正酷的事情是像上面这样的包装器没有运行manbetx官网手机登录时开销。如你所见,我已经用可链接的属性,它告诉LLVM用实际的函数体替换对该函数的调用是可以的。这种类型的零开销抽象是C++最重要的特性之一;看到它用如此简洁而富有表现力的语言如雨燕真是太不可思议了。

让我们做一些实验来看看这是如何工作的,通过运行一个简单的基准:添加2.0条对1000000个数组中的每个元素进行快速浮动。假设我们已经分配了一个适当大小的数组,我们可以使用此代码(注:基准是basemath中的一个简单函数,乘以一个代码块):

基准(标题:“Swift Add”)用于0中的i。
        
         Swift地址:.963 ms
        

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

基准(标题:“Swift ptr add”)让(p1,p2)=(ar1.p,ar2.p)用于0中的i。
        
         swift ptr地址:.487 ms
        

几乎是相同的代码,但速度是原来的两倍-那到底发生了什么?basemath添加了属性到数组,返回一个指针阵列内存;所以上面的代码使用指针,而不是数组对象本身。通常情况下,因为斯威夫特必须处理奶牛的复杂性,它不能完全优化这样的循环。但是通过使用指针,我们跳过那些支票,而且swift可以全速运行代码。注意,由于写时复制,如果分配给数组,它可能会移动,它也可以移动,如果你做一些事情,如调整大小;因此,你应该只在你需要的时候抓住指针。

上面的代码仍然相当笨拙,但Swift使我们很容易提供一个优雅的、惯用的界面。我添加了一个新的地图方法数组,将结果放入预先分配的数组中,而不是创建一个新数组。以下是对地图(它使用basemath中的一些类型别名使其更简洁):

@可链接的 公众的 芬克 地图<T基站器>γ f 一元 γ  T 哪里 自我.元素=T.元素 {
   PSRC =   最大的 = .  n = 计数
  对于  在里面 n {最大的[] = fPSRC[)}
}

正如你所看到的,这是简单的银行代码。很酷的是,这让我们现在可以使用这个清晰简洁的代码,仍然可以获得我们以前看到的相同性能:

基准(标题:“地图添加”)ar1.map($0+2.0,ar2)>地图添加:.518 ms

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

使用C

关于Swift的一个真正的好处是,添加您编写的C代码是多么容manbetx官网手机登录易,或者使用外部C库。要使用我们自己的C代码,我们只是用Swift软件包经理扫描探针显微镜)流行音乐C文件在其来源目录,和Ah文件在其来源/包括目录。(噢,顺便说一句,在基础数学中h文件完全由C文件使用GYB!)这一级别的C集成是极其稀有,其影响是巨大的。这意味着所有的C库,包括操作系统内置的所有功能,优化的数学库,TensorFlow的基础C API,因此,所有这些都可以直接从swift访问。如果你出于任何原因需要亲自去C,然后你可以,没有任何手动接口代码或任何额外的构建步骤。

这是c中的和函数(这是浮动版本双重的版本相似,这两个模板是由一个GYB模板生成的):

无效 斯马德里浮标康斯特 浮动* PSRC 康斯特 浮动 瓦尔 浮动* 血小板活化因子 康斯特 int 伦恩 {
  对于 int = <伦恩 ++ { 血小板活化因子[] = PSRC[]+瓦尔 }
}

打这个电话,我们需要通过计数作为一个英特32;basemath添加了C属性到数组以实现此目的(或者,您可以简单地使用数字广播(ar1.count).结果如下:

基准(标题:“c add”)smadd_float(ar1.p,2,AR2.Par1.c)>c添加:.488 ms

基本上和斯威夫特一样快。这是一个非常令人鼓舞的结果,因为它表明,我们可以得到与优化C相同的性能使用swift。不仅仅是任何雨燕,但惯用简洁,迅速,哪种方法(多亏了减少地图比大多数速度如此之快的语言更接近于数学方程式。

约简

现在让我们尝试另一个实验:取数组的和。以下是最常用的swift代码:

基准(标题:“减少总和”)a1=ar1.减少(0.0,+)>减少和:1.308 ms

…这里也有一个循环:

基准(标题:“循环总和”)a1=0;我在0…
        
         回路和:1.331 ms
        

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

基准(标题:“指针和”)让p1=ar1.p a1=0;我在0…
        
         指针和:1.379 ms
        

这很奇怪。再快不过了,这表明它没有获得最佳的性能。让我们再次切换到C,看看它在这里的表现如何:

浮动 SMSUME浮标康斯特 浮动* PSRC 康斯特 int 伦恩 {
  浮动 R = 
  对于 int = <伦恩 ++ { R += PSRC[ }
  返回 R
}

结果如下:

基准(标题:“c sum”)a1=smsum_float(ar1.p,ar1.c)>c总和:.230 ms

我将此性能与Intel的优化性能库版本总和并且发现这甚至比他们的手工优化汇编程序还要快!为了让它比斯威夫特表现得更好,不过,我确实需要知道一些技巧(由LLVM的矢量化文档提供)。它是用FFAST数学旗帜。对于这样的数值编程,我建议你至少使用这些标记(这是我在这些实验中使用的所有标记,尽管你也可以添加-三月=本地,并将优化级别从O2对于奥斯特):

-xswiftc-ouncheck-xcc-ffast math-xcc-o2

为什么我们需要这面旗帜?因为严格来说,加法不具有关联性,因为浮点数的奇怪。但这是,在实践中,不太可能成为大多数人关心的事情!manbetx官网手机登录默认情况下,Clang将使用“严格正确”的行为,这意味着它不能用SIMD对循环进行矢量化。但与FFAST数学我们告诉编译器,我们不介意将加法看作是关联的(除其他外)。所以它将使循环向量化,速度提高了4倍。

为了在C代码中获得良好的性能,需要记住的另一件重要的事情是确保康斯特标记在所有不会改变的东西上,正如我在上面的代码中所做的。

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

好消息是:basemath添加了总和方法数组,它使用了这个优化的C版本,所以如果你使用basemath,你会自动获得这个性能。所以测试1的结果是:失败。我们没能做到像C一样迅速的完成任务。但至少我们有一个不错的C版本,我们可以从斯威夫特打电话。让我们继续进行另一个测试,看看我们是否可以通过避免任何减少来获得更好的性能。

临时储存

如果我们想做一个函数约简,比如平方和?理想的,我们希望能够将我们的地图上方样式总和,但没有得到斯威夫特未经优化的削减的绩效惩罚。为了使这项工作顺利进行,诀窍是使用临时存储。如果我们使用我们的地图上面的函数将结果存储在预分配的内存中,然后我们可以把它传给我们的C总和实施。我们需要一个静态变量来存储预先分配的内存,但是接下来我们必须处理锁来处理线程之间的争用。为了避免这种情况,我们可以使用线程本地存储(TLS)。像大多数语言一样,swift提供TLS功能;但是,它不是核心语言的一部分(比如,说,C)它提供了一个类,我们可以通过线程.当前.3上瘾.basemath将预分配的内存添加到此字典,并使其在内部可用临时仓库;这就是一元函数约简的内部实现(也有二进制和三元版本可用):

@可链接的 公众的 芬克 总和γ f 一元>元素 {
  自己.地图f 临时仓库
  返回 临时仓库.总和()
}

然后我们可以如下使用:

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

这比普通的雨燕更快减少版本:

基准(标题:“reduce sumsqr”)a1=ar1.reduce(0.0,$0+float.sqr($1))>减少sumsqr:1.459 ms

这是C版:

浮动 SMSUME Sqrr-浮标康斯特 浮动* 限制 PSRC 康斯特 int 伦恩 {
  浮动 R = 
  pragma clang loop interleave_count(8)  对于 int = <伦恩 ++ { R += 正交频分复用PSRC[ }
  返回 R
}

让我们试试看:

基准(标题:“c sumsqr”)a1=smsum_sqr_float(ar1.p,ar1.c)>c sumsqr:.229毫秒

C所有标准一元数学函数的求和实现都由basemath提供,因此,您可以通过以下方法调用上述实现:

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

总之:虽然使用临时存储的Swift版本(并且只调用C来获得最终的金额)是使用的速度的两倍减少,使用C的速度再快3倍以上。

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

我使用的最简洁和灵活的语言是python。我用得最快的是C(实际上是Fortran,但是,我们不要去那里。)那么,它是如何与这些高铁堆叠起来的呢?我们可以将一种语言与Python的灵活性和C的速度进行比较的想法本身就是一个惊人的成就!

总体而言,我的观点是,一般来说,写我想写的东西需要比用python写的代码快一点,而且提取公共代码的方法也越来越少。例如,我在python中经常使用装饰器,并使用它们为我编写大量的代码。我使用*参数*克沃斯很多(新的)动态特性在Swift中可以提供一些功能,但不会太远)。我一次将多个变量压缩在一起(在swift中,您必须为多个变量压缩成对的变量,然后使用嵌套parens来破坏它们)。然后还有一些代码,你必须编写这些代码来让你的类型排列得很好。

我也发现斯威夫特的表现比C更难解释和优化。manbetx官网手机登录C在性能方面有自己的怪癖(例如需要使用康斯特有时甚至需要限制帮助编译器),但是它们通常都有更好的文档记录,更好地理解,更加一致。也,C编译器(如clang和gcc)使用诸如OMP甚至可以自动并行代码。

这么说之后,比我使用过的任何其他语言都更接近于实现Python的表现力和C的速度的结合。

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

这个问题特别具有挑战性,因为Swift的编译器通常不知道协议类型问题的真正来源是什么,它猜测类型的能力仍然相当薄弱。所以非常微小的变化,例如更改类型的名称,或者更改类型约束的定义位置,可以改变过去工作的东西,它会弹出四个屏幕显示错误信息。我的建议是尝试在独立的测试文件中创建类型结构的最小版本,先把事情做好。

注:然而,这种易用性通常需要妥协。巨蟒特别容易,因为你很高兴射自己的脚。斯威夫特至少能确保你首先知道如何解开鞋带。克里斯告诉我:首先,在构建Swift时,关键是要优化“端到端时间,以便正确实现您正在尝试做的任何事情”。这包括敲出代码的时间,调试时间到了,如果您要更改现有的代码库,还需要重新构造/维护它。我还没有足够的经验,但我怀疑在这个标准上,斯威夫特会成为一个伟大的表演者。

斯威夫特的某些部分我不喜欢:苹果公司在客观-C的历史上做出的妥协,这是包装系统,是社区,并且缺少C++的支持。或者更准确地说:它主要是快速生态系统我不喜欢。语言本身很令人愉快。生态系统是固定的。但是,现在,这就是Swift程序员必须处理的情况,因此,让我们依次研究这些问题。

Objtovi-C

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

打印第1页打印文档2打印第1页到第5页文档2

applesccript反过来从hypertalk继承了这个想法,1987年为苹果公司广受爱戴(和怀念)而创立的一种语言。超卡程序。鉴于这段历史,今天,要求命名论点的想法是苹果公司的大多数人都非常喜欢的,这并不奇怪。也许更重要的是,它为Objective-C的设计者提供了一个有用的折衷方案,因为他们能够避免向语言添加真正的函数重载,与C保持密切的兼容性。

不幸的是,这种限制影响了今天的迅速发展,40多年后的情况导致了它在客观C中的引入。迅捷提供真正的函数重载,这在数字编程中特别重要,如果您真的不想为浮点数创建整个独立的函数,双打,复数(和四元数,等等)。但默认情况下,所有关键字名称仍然是必修的,这会导致代码冗长且视觉混乱。苹果的风格指南大力推广这种编码风格;他们的目标——C和斯威夫特,样式引导彼此紧密镜像,而不是让程序员真正利用Swift的独特功能。通过在参数名称前加前缀γ,basemath在任何不需要可选参数的地方都使用它。

另一个让事情变得相当冗长的领域是基础,苹果的主要类库,这也是Objective-C使用的。Swift的标准库缺少了许多您需要的功能,所以你经常需要转向基金会来完成工作。但你不会喜欢的。在使用了如此优雅的设计语言如swift之后,使用它作为一个图书馆的笨拙的基础,有一些特别的悲哀。manbetx官网手机登录例如,Swift的标准库没有提供一种内置的方式来以固定的精度格式化浮动,所以我决定把这个功能添加到支持基本路径协议。代码如下:

延伸 支持基本路径 {
  公众的 芬克 一串γ 数字利息 >  {
     FMT = 数字格式化()
    FMT.最小分位数 = 数字
    FMT.最大分数位数 = 数字
    返回 FMT.一串 自己.NS编号 ?? \自己
  }
}

我们可以将此功能添加到浮标双重的写这样的扩展名真的很酷,和使用swift处理失败转换的能力一样??操作员。但是看看实际使用数字格式化从基础开始上课!它甚至不接受浮标双重的,但是尴尬数字对象Objective-C中的类型(由于Objective-C中缺少泛型,这本身就是一个笨拙的解决方案)。所以我必须添加一个NS编号属性到支持基本路径做铸造。

Swift语言本身有助于支持更简洁的风格,如{f(0美元)}封口样式。简洁对于数字编程很重要,因为它允许我们编写代码来更紧密地反映我们正在实现的数学,一眼就能理解整个方程。为了对这一点(以及更多)进行全面的阐述,看艾弗森的图灵奖演讲符号作为思考的工具.

Objective-C也没有名称空间,这意味着每个项目都会选择一些2或3个字母的前缀,并将其添加到所有符号中。大多数基础库仍然使用从Objtovi-C继承的名称,所以你会发现自己使用的类型CG浮标功能就像cfAbsoluteTimeGetcurrent(绝对时间电流).(每次我输入这些符号中的一个,我确信一头独角兽宝宝会痛苦地大叫……)

Swift团队做出了惊人的决定,在苹果设备上运行SWIFT时,使用基础和其他库的Objy-C实现,但是要在Linux上使用本机Swift库。因此,有时,您会在每个平台上看到不同的行为。例如,Apple设备上的单元测试框架无法找到并运行作为协议扩展编写的测试,但是它们在Linux下工作得很好。

总体而言,我觉得Objective-C的约束和历史似乎过于频繁地被快速编程所取代,每次发生的时候,有一个真正的摩擦突然出现。随着时间的推移,然而,这些问题似乎正在减少,我希望在未来,我们会看到越来越多的人从目标C的枷锁中迅速挣脱出来。例如,也许我们将看到为一些Objective-C类库创建惯用的swift替换程序的真正努力。

社区

在过去的几年里我一直在使用python,一直困扰我的一件事是,在Python社区中有太多的人只使用过一种语言(因为它是一种伟大的初学者语言,并且被广泛地教授给未毕业的学生)。因此,没有意识到不同的语言可以以不同的方式做事情,每种选择都有自己的利弊。相反,在巨蟒世界里,人们有一种倾向,认为Python方法是唯一真正的方法。

我在斯威夫特看到类似的东西,但在某些方面,情况更糟:大多数敏捷的程序员都是从Objective-C程序员开始的。所以,你在网上看到的很多讨论都来自于Objective-C程序员,他们以一种与Objective-C中的工作方式非常相似的方式编写Swift。几乎所有人都用xcode编程(这几乎肯定是我最不喜欢的IDE,除了它的美妙迅捷的操场特征),所以你会在网上找到很多建议,告诉你如何通过让Xcode为你做事来解决快速问题,而不是自己编写代码。

大多数Swift程序员正在编写iOS应用程序,因此,您还可以找到许多关于如何布局移动图形用户界面的指导,但是几乎没有关于如何为Linux分发命令行程序的信息,manbetx官网手机登录或者如何编译静态库。一般来说,因为Linux对Swift的支持还很新,没有太多关于如何使用它的信息,许多库和工具在Linux下不工作。

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

包装和安装

斯威夫特拥有官方批准的包裹系统,称为Swift Package Manager(SPM)。不幸的是,这是我用过的最差的包装系统之一。我注意到几乎每种语言,创建包管理器时,从零开始重新投资,并且未能充分利用以前尝试的所有成功和失败。斯威夫特遵循了这种不幸的模式。

有一些真正优秀的包装系统。最好的,也许,以前还是Perl的CPAN,其中包括一项国际自动化测试服务,该服务在各种系统上测试所有软件包,深入整合文档,有很好的教程,还有更多。另一个伟大的(更现代的)系统是康达,它不仅处理特定于语言的库(重点是python),而且还处理自动安装兼容的系统库和二进制文件,并管理主目录中的所有操作,因此您甚至不需要根目录访问。它在Linux上很好地工作,雨衣,和Windows。它可以处理两个编译模块的分发,或来源。

扫描探针显微镜另一方面,没有这些系统的任何好处。尽管Swift是一种汇编语言,它不提供创建或分发已编译包的方法,这意味着包的用户必须安装构建它的所有先决条件。SPM不允许您描述如何构建包,所以(例如)如果您使用basemath,那么在构建使用它的东西时,您应该记住添加良好性能所需的标志。

处理依赖关系的方式真的很尴尬。Git标记或分支用于依赖项,在本地开发构建和打包版本之间切换是不容易的(比如,例如-e旗到匹普康达发展命令)。相反,您必须修改包文件以更改依赖项的位置,在你承诺之前记得把它换回来。

记录SPM的所有缺陷需要很长时间;相反,你可以假设,你所欣赏的任何有用的功能,无论你现在使用的包装系统,可能不会在SPM。希望有人能为Swift建立一个基于Conda的系统,我们可以开始使用它……

也,安装斯威夫特是一团糟。在Linux上,例如,仅支持Ubuntu,不同的版本需要不同的安装程序。在MAC上,Swift版本以一种令人困惑和尴尬的方式绑定到Xcode版本,命令行和Xcode版本有些不同,但有些联系,让我的大脑受伤。再一次,康达似乎可以提供避免这种情况的最佳选择,因为一个Conda包可以支持任何风格的Linux,Mac也可以用同样的方式支持。如果是为了迅速到达康达,那么就可以说Conda安装Swift在任何系统上,一切都会只是工作.这也将为版本控制提供解决方案,孤立的环境,以及复杂的依赖跟踪。

(如果你在窗户上,你是,现在,不走运。到塞格温有一个非官方的老港口。Swift在Linux的Windows子系统上运行良好。但是还没有官方的本地窗口,悲哀地。但这方面有一些好消息:一个名叫萨利姆·阿卜杜勒·拉苏尔的英雄大步为了永久地建成一个完整的本土港口,在过去的几天里,它已经达到了大多数swift测试套件都能通过的程度。)

C++

虽然苹果公司在其“C with Objects”解决方案中使用了Objective-C,世界其他地方都用C++进行了。最终,Objto-C扩展也被添加到C++中,要创建“Objective-C++”,但并没有试图统一不同语言的概念,所以产生的语言是许多重大限制.然而,有一个很好的语言子集,可以绕过C的一些最大限制;例如,您可以使用函数重载,并且可以访问丰富的标准库。

不幸的是,SWIFT根本无法与C++进行接口。即使像包含重载函数的头文件这样简单的东西也会导致swift语言interop失败。

这对数字程序员来说是个大问题,因为当今许多最有用的数值库都是用C++编写的。例如,这个阿滕PyRo火炬中心的图书馆是C++。数字程序员倾向于C++是有充分理由的:它提供了对数字编程问题简洁而有表现力的解决方案所需的特性。例如,Julia程序员(正确地)为用他们的语言支持关键的广播功能是多么容易而自豪,他们记录在朱莉娅挑战赛.在C++中,这个挑战是优雅而快速的。解决方案.你在纯C中找不到这样的东西,然而。

因此,这意味着大量和不断增加的数字编程最重要的构建块超出了Swift程序员的界限。这是一个严重的问题。(可以为C++类编写简单的C包装器,然后创建一个使用这些包装器的swift类,但这是一项非常大和非常无聊的工作,我不确定是否有很多人会从事这项工作。)

其他语言也显示出了解决这一问题的潜在方法。例如,Windows上的C++提供了“只是工作”(IJW)与C++/CLI接口,一个C++的超集,支持.NET。更有趣的是,这个CPP-夏普项目利用LLVM自动生成C++代码的包装器,不需要调用开销。

解决这个问题对斯威夫特来说并不容易。但因为Swift使用LLVM,并且已经与C(和Objective-C)进行了接口,因此,比起几乎任何其他语言,提出一个伟大的解决方案可能是更好的选择。除了,也许,对朱丽亚来说,既然他们已经已经这样做了.两次.

结论

斯威夫特是一种非常有趣的语言,可以支持快速,简洁的,富有表现力的数字编程。TensorFlow项目的Swift可能是创建一种编程语言的最佳机会,在这种语言中,可微编程是一流的公民。Swift还允许我们轻松地与C代码和库进行交互。

然而,Linux上的Swift还不成熟,包装体系薄弱,安装很笨重,由于与客观C的历史联系,图书馆也面临着一些困难。

那么它是如何堆积起来的呢?在数据科学领域,我们主要使用r(这是我用过的最不愉快的语言,但是有了最漂亮的设计,任何地方都可以使用数据挖掘和绘制库,或者使用python(速度非常慢,很难平行化,但是表现力极强,拥有最好的深度学习库)。我们真的需要另一个选择。有点快,灵活的,并提供与现有库的良好互操作。

总体而言,斯威夫特语言本身正是我们需要的,但是,许多生态系统需要被取代,或者至少要大幅升级。没有数据科学生态系统可言,尽管S4TF项目似乎会创造一些重要的部分。如果你有兴趣成为一个有巨大潜力的事物的一部分,这真的是一个好地方。有一些真正伟大的人努力做到这一点,一路上你可以帮你把疣治好。