Tag: 性能

Swift比目标C快多少

支持动态库。 Swift支持的动态库,它们直接加载到应用程序的内存中,可以优化应用程序的性能。 与应用程序的直接连接允许它们独立于操作系统进行更新。 它有助于使您的解决方案保持最新状态,减小应用程序大小,并加快新内容的加载时间。 Objective-C不支持动态库,这是一个主要缺点。 事实是它们更大,因为外部程序内置在可执行文件中。 动态库较小,因为动态库只有一个副本存储在内存中。 2. 运行时代码编译 目标C使用运行时代码编译,而不是编译时间。 这意味着当Objective-C对象在代码中调用另一个对象时,将涉及额外的间接级别。 通常,这发生得非常快,但是当代码编译发生很多次时,它变得可以测量,而swift需要更少的编译时间。 3. 参考类型与值类型 Swift的String,Dictionary和Array是结构。 这意味着它们是值类型的,并且不支持继承,因此Swift可以静态链接并可以内联调用其方法。 它们也是写时复制的,因此,如果您仅从这些数据结构之一中读取数据,Swift可以通过边界检查将许多访问转换为指针算法。 4. 常量和可选的用法 与C相比,Swift允许和鼓励使用常量的次数更多。 优化器可以假设常量永远不会改变-甚至禁止使用后门技术(例如对常量形成UnsafePointer)-这样它就可以安全地缓存先前获取的值并执行其他优化。 可选选项可确保某些指针永远不会为nil,因此编译器可以省略nil检查。 6. 更多注意事项 Swift对象可以彼此调用,而无需执行消息发送,这是Objective-C性能的瓶颈。 Objective-C的对象具有神奇的属性,可以对消息进行自省和动态处理。 这是非常强大的功能,但是会增加每条消息的成本。 迅捷对象不需要此。 因此可以更加轻巧。 从理论上讲,没有理由说Swift代码不能跟C ++一样快。 在简单的测试中,将优化器设置为“ 11”,Swift代码似乎具有与C / C ++类似的性能。

对图像进行下采样以获得更好的内存消耗和UICollectionView性能

任何可能出错的地方都会出错。 墨菲定律 在开发的每个步骤中,我们都会做出影响应用程序整体性能的架构决策。 我们都非常了解功耗和内存消耗对于移动应用程序极为重要。 我们也知道可用内存和应用程序的相对性能之间存在某种关联。 但是,在当今快速解决方案的世界中,缩短的期限和避免过早优化的精神使您容易错过重要的事情。 让我们看一下常见任务-图片库。 对于各种图像布局,它看起来可能会有所不同。 但是它们的共同点是-一批图像同时显示在屏幕上。 问题定义 假设您已决定从服务器获取下载的图像,并将其显示在UIImageView 。 这种方法完全没错。 此外,Apple建议在所有常见情况下使用UIImage和UIImageView来显示图像。 仅当您进行了某些特定的图像处理时例外。 让我们回到画廊。 可能您已经使用Simulator和最新的iPhone版本在不同的图像集上测试了该应用程序。 现在准备进行质量检查阶段。 Beta测试人员和QA工程师选择您的应用程序,然后您会看到以下看起来很奇怪的崩溃报告: 您将开始使用特定的图像集测试您的应用,然后看到以下内容: 几乎每个致力于性能最佳实践的WWDC会话都表示,iOS应用程序应使用尽可能少的内存。 内存是iOS上最受限制的资源。 系统可能要求的可用内存比其释放的速度快。 正如文档所述,此WWDC会话iOS没有传统的磁盘交换,而是使用内存压缩器技术。 普通用户的设备上有多个应用程序。 许多应用程序可能仍在后台,并继续消耗一些内存。 系统本身正在消耗的部分内存。 在这一点上,您可能认为仍然应该留有足够的内存来平稳地运行应用程序。 无论如何,iOS足够聪明,可以卸载一个或两个烦人的内存使用者。 但实际上,系统设置了内存限制,每个应用程序都可以使用该内存限制。 由于超出限制,前台应用程序中正在运行的应用程序可能会被关闭。 那么,为什么图像会导致这种后果呢? 图像渲染流程 在iOS中显示图像的最常见方法是使用UIImageView和UIImage 。 UIImage类负责管理图像数据,转换,应用适当的比例因子。 UIImageView —用于在应用程序界面中显示图像。 在WWDC上:Apple的图像和图形最佳实践工程师提供了一个非常简单直观的图表,说明了其实际工作方式。 基于此,当您使用UIImage在UIImageView绘制图像时,实际上需要执行几个步骤: 1.将压缩的图像数据加载到内存。 2.将压缩的图像数据转换为渲染系统可以理解的格式。 3.渲染解码图像。 让我们在这里停下来。 我们需要了解什么是图像,我们拥有哪种图像类型和格式以及如何存储图像。 图片类型 首先,有两种主要的图像类型:栅格(位图)和矢量。 光栅图像表示为由每个像素的编码后的单个值填充的矩形网格。 矢量图像是根据2D点定义的,由线,多边形和其他形状连接。 与栅格不同,矢量格式存储用于绘制图像的指令。 光栅图像和矢量图像各有优缺点,通常用于不同目的。 向量通常用于将要应用于物理产品,徽标,技术图纸,文本,图标等图像的图像,其中包含尖锐的几何形状。 矢量图像的主要优点是分辨率独立性。 这意味着可扩展性而又不损失清晰度和质量。 矢量图像使用从一个点到另一点的数学计算来形成线条和形状,这就是为什么它对每种分辨率和缩放都产生相同结果的原因。 […]

Swift中的字符串,字符和性能-深入探讨

扫描令牌所用时间的三分之二全部花在了用于Character类型的初始化程序中。 到底是怎么回事? 初始化程序的第一个参数标签提供了一个小提示: _builtinExtendedGraphemeClusterLiteral 。 注意单词“ literal”。此初始化程序不用于从我要遍历的字符串中提取Character值; 它用于从源代码中其他地方的文字文本创建Character值。 在nextToken下找到这些标记的唯一地方是我的令牌生成器的switch/case模式。 他们真的造成那么多的开销吗? 我们可以让Swift编译器使用-emit-sil选项发出“规范SIL”(Swift中级语言),以更仔细地了解如何将这些case模式编译为较低级的代码。 (我在存储库中包含了一个小脚本,该脚本可以执行此操作,并且还可以分解Swift符号。)让我们找到与匹配逗号字符的case模式对应的SIL(为简洁起见,重新格式化了行号和范围): %448 = string_literal utf8“,” %449 =应用%26(%448,%23,%24,%25): $ @ convention(method) (Builtin.RawPointer,Builtin.Word, Builtin.Int1,@ thin Character.Type)-> @owned字符 %449的第二行和第三行所示的方法签名%449其放弃,但我们可以通过查看值%26来确认它,该值是被调用的函数: %26 = function_ref @ Swift.Character.init( _builtinExtendedGraphemeClusterLiteral:Builtin.RawPointer, utf8CodeUnitCount:Builtin.Word, isASCII:Builtin.Int1)-> Swift.Character : $ @ convention(method)(Builtin.RawPointer,Builtin.Word, Builtin.Int1,@ thin Character.Type)-> @owned字符 这是什么意思呢? 这意味着, 每次通过扫描循环(即,针对字符串中的每个字符)时,Swift都会调用此初始化程序来创建case模式中的每个字符,以便将当前字符与该模式进行比较,直到找到匹配项为止;并且该初始化程序如果您习惯于字符类型实际上只是一个数字代码单元的语言,则它的成本将大大超出您的预期。 通过查看Swift标准库源代码,我们可以看到将字符串文字(例如”,” )转换为Character时发生的动作序列: 编译器将UTF-8编码的文字表示形式嵌入可执行文件的数据段中。 在使用文字的地方,Swift调用Character.init(_builtinExtendedGraphemeClusterLiteral:utf8CodeUnitCount:isASCII) , Character.init(_builtinExtendedGraphemeClusterLiteral:utf8CodeUnitCount:isASCII)其传递步骤1中嵌入的字符串数据的地址(源)。 反过来,此初始化程序在String上调用相同的初始化程序。 最终,分配了StringBuffer […]

Swift中的禁止@inline属性

最初发布于 swiftrocks.com 。 @inline属性是Swift中那些晦涩的事物之一-在苹果的文档中找不到它,它不能帮助您编写更@inline代码,也无济于事,只能帮助编译器做出优化决策,但这与一个非常重要的问题有关。应用性能的一个方面。 在编程中, 函数内联是一种编译器优化技术,它通过直接用方法的内容替换对某些方法的调用来消除调用某些方法的开销,基本上是假装该方法根本就不存在。 这极大地提高了性能。 例如,考虑以下代码: func calculateAndPrintSomething() { var num = 1 num *= 10 num /= 5 print(“My number: \(num)”) } print(“I’m going to do print some number”) calculateAndPrintSomething() print(“Done!”) 假定在其他任何地方都没有使用calculateAndPrintSomething() ,那么很明显,该方法不需要存在于已编译的二进制文件中,其目的只是为了使代码更易于阅读。 通过函数内联,Swift编译器可以通过将其替换为内容来消除调用无用方法的开销: //The compiled binary version of the above example print(“I’m going to do print some number”) var num = […]

快速技巧使您的应用更有趣

将视图控制器转换的延迟减少75ms 介绍 无论何时出现新的视图控制器,都会给用户带来延迟,既要等待在主线程上建立视图,又要等待从服务器获取新数据。 通过一点技巧,您可以将这些延迟减少75ms。 它看起来似乎并不庞大,但是75ms可能会对用户参与度产生重大影响。 这篇文章的灵感来自Instant.page,它的功能大致相同,只是针对网站。 绝招 很简单:当您有一个UIButton触发向新视图控制器的过渡时,您可以在touchDown而不是touchUpInside上设置新的VC。 粗略地说,您来自:

Swift中的惰性序列及其工作方式

您也可以在我的博客SwiftRocks中阅读此帖子,最好在此处进行格式化! 诸如map和filter类的高阶函数的用法在Swift项目中非常常见,因为它们是简单的算法,可让您将广泛的构想转换为简单的单行代码。 不幸的是,他们并不能解决所有问题-至少不是在其默认实现中。 高阶函数很渴望 :它们立即使用闭包并返回一个新数组,无论您需要提前返回还是仅使用特定元素。 当性能很重要时,您可能会被迫编写专门的助手方法来避免高阶的急切性质: 让地址= getFirstThreeAddresses(withIdentifier:“ HOME”) func getFirstThreeAddresses(withIdentifier identifier:String)-> [地址] { //不使用.filter {}。prefix(3),因为我们需要早日返回 变量地址= [地址]() 用于allAddresses中的地址,其中address.identifier ==标识符{ address.append(地址) 如果addresss.count == 3 { 打破 } } 寄信人地址 } 幸运的是,Swift有一种使用高阶函数的方式,同时仍然保持了辅助方法的改进性能-可以通过lazy属性访问Swift标准库Sequences和Collections惰性版本。 这些惰性变体的工作方式与常规变体一样,但有一点不同:它们具有诸如map和filter类的方法的自定义实现,以使它们懒惰地工作-意味着实际的计算只会在需要它们的地方和时间进行。 let allNumbers = Array(1 … 1000)let normalMap = allNumbers.map {$ 0 * 2} //将映射整个序列,而不管您需要做什么。letlazyMap = allNumbers.lazy.map {$ 0 * 2} //这里什么也没有发生。print(lazyMap [0])//打印2,但是其他所有内容都保持不变! 尽管起初有些令人恐惧,但它们使您可以减少大多数for循环,并尽早返回单层。 […]

内存管理和值类型的性能

最初发布于 swiftrocks.com 。 您很可能在iOS生涯中至少问过自己一次, struct和class之间有什么区别。 实际上,在使用一个或另一个之间的选择总是归结为值语义和引用语义 ,但是两者之间的性能差异是可表达的,并且取决于对象的内容,尤其是在处理值类型时,它们之间可能会偏重一个或另一个。 有人可能会说,对于应用程序级别的开发人员而言,内存体系结构的知识是无关紧要的,我对此表示部分赞同。 知道如何在这里和那里节省一些钱不会对新型iPhone产生明显的影响,过早的优化是一个非常不明智的做法。 但是,引用和值类型在滥用时都会严重降低您的应用程序的速度,这些知识将确定您是否可以有效解决问题。 要了解两者之间更深的差异,让我们回顾一下进程的地址空间:(为简单起见,使用单线程) |————————-|- | 说明| |——————————– | 全球数据 |——————————– | 堆| |——————————– | 什么都没有(堆栈和堆向此处增长)| |——————————– | 堆叠 |——————————– 在内存体系结构中,堆栈与您已经知道的数据结构没有什么不同,并且堆栈分配是一种简单快速的分配/释放涉及堆栈的内存的方法。 应用程序中的每个“作用域”(就像方法的内部内容一样)将提供它需要运行的内存量,将堆栈指针按此数量移动并运行-将数据添加到它现在构成的空内存地址中。 丢失作用域后,堆栈指针将减少相同的数量,从而安全地重新分配所有作用域的数据。 分配/取消分配堆栈内存的成本实际上就是分配整数的成本。 在堆栈分配中,作用域收集的数据表示归因于该作用域的所有数据,例如方法参数,返回值,但更重要的是: 值类型 。 只要在编译时就知道值类型的大小并且不包含/不包含值类型,就可以按引用类型递归地使用它,就不需要引用计数,并且它的寿命将是静态的 -等于寿命其范围。 它会在堆栈上完全分配,并且在释放范围时,值类型也会分配。 缺少引用计数开销和堆栈分配的存在可以显着提高性能。 PS:所有基准测试均使用-O。 我必须添加一些特殊的逻辑和关键字/属性以防止编译器跳过我的方法,但是为了使代码易于阅读,我将它们隐藏在示例中。 struct EmptyStruct { 私人租借号码:Int64 = 1 //默认情况下,空类具有64位存储空间 //指针 //因此我们要在结构中添加64位以进行公平比较。 } @inline(never)func createABunchOfEmptyStructs(){ for _ in […]