生锈速度与c

2021-03-13 12:33:54

用RURE编写的程序的运行时速度和内存使用应该与在C中编写的程序相同,但这些语言的整体编程风格足够多于它且难以概括其速度。这是它们的摘要'重复,其中c更快,rust更快。

免责声明:它并不意味着成为一个客观的基准,揭示了关于这些语言的无可争议的真理。这些语言在理论上实现的效果有什么显着差异,以及它们如何在实践中使用。这种特殊的比较是基于我自己的主观体验,包括截止日期,写作错误和懒惰。我一直在使用Rust作为我的主要语言超过4年,并且在此之前十年。我在这里特别比较了与c ++的比较,与c ++的比较将有更多" ifs"和#34; BUTS"我不想进入。

Rust'抽象是一把双刃剑。它们可以隐藏次优代码,但也使得更容易进行算法改进并利用高度优化的库。

我从来没有担心我将用锈病击中表演死胡同。始终是不安全的逃生舱口,允许非常低级别的优化(并且它不需要经常需要)。

无所畏惧的并发是真实的。借款检查员的偶尔尴尬地支付了并行编程实用。

我的整体感觉是,如果我能花无限的时间和努力,我的C程序就会比铁锈快或更快,因为理论上没有,没有C可以' t做那种生锈可以。但在实践中,C具有更少的抽象,原始标准库,可怕的依赖情况,我只是没有时间在每次重塑车轮时重新发明。

rust和c都控制了数据结构的布局,整数大小,堆栈与堆内存分配,指针间接,并且通常转换为具有Little&#34的可理解机代码;魔术"由编译器插入。 Rust甚至承认字节有8位,签名数字可以溢出!

尽管Rust具有更高级别的构造,如迭代器,特征和其他"零成本抽象和#34;它们' Re设计用于可预见地优化直接的机器代码。 Rust'类型的内存布局很简单,例如,可延长的字符串和矢量正是{数据*,容量,长度}。 RUDEN' t有任何概念,如移动或复制构造函数,因此保证对象的传递比传递指针或memcpy更复杂。

借款只是一个编译时静态分析。它没有做任何事情,并且在代码生成之前甚至完全剥离了一生信息。没有自动拨射或任何类似的东西。

一个尸体缺乏存在的一个案例"愚蠢的"代码生成器正在解除。虽然Rustn' t使用例外用于正常误差处理,但恐慌(未处理的致命错误)可以选择类似于C ++异常。它可以在编译时间(Panic = abort)禁用,但即使是RURRN和#39; T希望其他代码抛出C ++异常或使用LONGJMP' s堆栈帧。

Rust与LLVM具有良好的集成,因此它支持链路时间优化,包括跨越C / C ++ / Rust语言边界的ToLto甚至内联。在那里' S简介者引导优化。尽管RustC产生了更详细的LLVM IR,但优化器仍然可以很好地处理它。

当用GCC编译时,我的一些C代码比LLVM在内,而且没有用于GCC的铁锈前端,因此锈病错失了。

从理论上讲,由于更严格的不可变节和混叠规则,Rust甚至比C更好地优化,但在实践中,这并未发生。除了C的优化是在LLVM中的过度测试和开发的,因此RUDE始终继续等待更多的错误修正以达到LLVM以达到其全部潜力。

RUDE代码是低级,可预测的足以我可以手术调整它将优化的装配。 RUTR支持SIMD内在机构,给予联系,呼叫约定等控制,锈病与C的C型分析仪通常与盒子中的RUST一起使用(例如,我可以使用Xcode'&#39上的xcode' SA Rust-C-Swift三明治)。

一般来说,在表现绝对关键的情况下,需要对最后一点进行手工优化,优化生锈ISN' t与C不同。

计算到了。可以在RUDE中使用循环{断裂}仿真定期转到的明智使用。 CO中的大量用途是用于清理,其中RUDEN' T需要。但是,但是在有一个非标准的GOTO延伸的非标准转移。'非常有用的口译员。锈病' t做它(你可以写一场比赛并希望它' ll优化),但如果我需要翻译,我试图利用Cranelift Jit。

AlloCa和C99可变长度阵列。即使在C中,这些是争议的,因此Rust远离他们。

值得注意的是,生锈目前只支持一个16位架构。第1层支持专注于32位和64位平台。

RUDE强烈更喜欢注册大小的使用而不是32位INT。虽然Rust可以使用I32就像C可以使用size_t,但默认值会影响典型代码的写入方式。 Usize更容易在64位平台上优化,而无需依赖未定义的行为,但额外的位可能会对寄存器和内存进行更多压力。

惯用锈蚀始终通过指针和串和切片的指针和大小。它是n' t,直到我从c从c移植到生锈,我意识到只有多少c函数只拍摄到内存的指针,没有大小,并且希望最好的(大小是间接着图的背景,或者只是假设足够大的任务)。

并非所有绑定检查都经过优化。对于Arr或Arr.iter()中的项目。for_each(...)与它们一样高效,但如果需要0的表单{arr [i]},则性能取决于llvm优化器能够证明长度匹配。有时它可以' t,绑定检查禁止自动侵入。当然,这是安全和不安全的各种解决方法。

"聪明"内存用途是锈的。在c中,任何事情都会去。例如,在C i&#39中; d被诱惑重复使用分配的缓冲器,以便以后的另一个目的(一种称为Heartbleed的技术)。它与可变大小的数据(例如path_max)具有固定大小缓冲区的方便,以避免(重新)种植缓冲区的分配。惯用锈病仍然可以控制内存分配的批次,并且可以像内存池一样做基础,将多个分配与一个,预期空间等相结合,但通常它使用户朝向"无聊"使用或记忆。

在借用检查规则的情况下使事情变得艰难,简单的出路是做额外的复制或使用参考计数。随着时间的推移,我学到了一堆借款检查伎俩,并调整了我的编码风格来借来借阅 - 检查器友好,所以这不再有更多。这从来没有成为一个主要问题,因为如有必要,那里往返于"生"指针。

Rust'借用检查员是讨厌的,用于讨厌讨厌双链列表,但幸运的是,连接的列表在21世纪的硬件上慢慢(糟糕的缓存局部,没有矢量化)。 Rust' S标准库有链表列表,以及更快,借阅验频友好的集装箱。

借用检查员可以' t容忍:内存映射的文件(从进程外部的神奇变更违反引用的独占语义)和自我参照结构(通过价值的传递将制作它的内部指针摇晃)。这些案件与原始指针一起解决,该指针与C或精神体操一样安全,以使它们保持安全的抽象。

生锈,单线程程序只是不存在于概念。 RUST允许单个数据结构对性能无线安全,但允许在线程(包括全局变量)之间共享的任何内容必须同步或标记为不安全。

我一直忘记生锈' s字符串支持一些廉价的地置操作,如make_ascii_lowercase()(直接等同于我的直接等同于c),并且不必要地使用Unicode-Aware,复制.to_lowercase( )。谈到字符串,UTF-8编码并不像它看起来那么大的问题,因为字符串具有.as_bytes()视图,因此如果需要,可以以Unicode忽略的方式处理它们。

libc倒后来弯腰,使STDOUT和PUTC相当快。生锈' S Libstd的魔法较少,所以我/ o isn' t缓冲,除非包裹在bufwriter中。我看到的人抱怨他们的生锈比Python慢​​,它是因为逃避了99%的时间按字节冲洗结果字节,完全如此。

每种操作系统都会填写一些内置的标准C库,即C可执行文件所获得的"免费&#34 ;,,,,,,,, A"你好世界" C可执行可以' t实际上打印任何内容,它只将打印机呼叫与操作系统一起发布。 Rust Can' t计数含有Rust' Salds标准图书馆内置的iS,所以Rust可执行文件束位的生锈' S标准库(300kb或更高)。幸运的是,它是一次性开销。对于嵌入式开发,标准库可以关闭,生锈将生成"裸"代码。

在每函数基础生锈代码上大小与C大小相同,但是在那里的一个问题"泛型膨胀"通用功能为每个类型的类型获取优化版本,它们与它们一起使用,所以它可能最终有8个版本的相同功能。货物膨胀有助于找到这些。

它' S超级易于使用锈蚀剂。与JS / NPM同样,在那里'是制作小型单一目的库的文化,但他们会加起来。最终,所有可执行文件最终包含Unicode归一化表,7个不同的随机数生成器,以及带有Brotli支持的HTTP / 2客户端。货物树对复制和剔除它们是有用的。

我'谈到了很多关于开销的大量,但Rust也有最终更高效和更快的地方:

C库通常将不透明指针返回到其数据结构,以隐藏实现细节并确保只有一个结构实例的一个副本。此费用堆分配和指针间接。 Rust'内置隐私,独特的所有权规则和编码约定让库按值暴露它们的对象,以便图书馆用户决定是否将它们放在堆上或堆栈上。堆栈上的对象可以非常积极地优化,甚至完全优化。

默认生锈可以从标准库,依赖项和其他编译单位的内联函数。在C i' m有时不愿意拆分文件或使用库,因为它会影响内嵌,并且需要标题和符号可见度的微单位。

struct字段重新排序以最小化填充。编译C与-WPadding显示我多久忘记此细节。

字符串在他们的&#34中编码了它们的尺寸;脂肪"指针。这使得长度检查快速,消除了意外O(n²)串循环的风险,并允许在不修改内存或复制以添加\ 0终止器的情况下使子字符串放置(例如,将字符串拆分为令牌)。

与C ++模板一样,RUST为每个类型的副本生成通用代码的副本;重新使用,因此诸如SORT()和像哈希表相同的容器等功能始终针对其类型进行优化。在C中,我必须在带有宏或较低的高效功能之间选择Hacks,这些功能在void *和可变大小上工作。

防锈迭代器可以组合成链式以单位优化的链接。所以而不是一系列电话购买(它);用它);打破它);更改);邮件(升级(IT));最终可能最终重写相同的缓冲区,我可以称之为。所有这一切都在一个组合通过。 (0..1000).map(| x | x * 2).sum()编译返回999000。

同样,读取和编写接口允许函数流传输无缓冲的数据。它们很好地组合,因此我可以将数据写入一条流,从而在飞行中计算数据的CRC,如果需要,将框架/逃逸添加,压缩它,并将其写入网络,所有在一个呼叫中。并且我可以将这样的组合流作为输出流传递给我的HTML模板引擎,因此现在每个HTML标记都将足够智能地发送自身压缩。底层机制只是普通的金字塔旁边的lead_stream.write(字节)呼叫,所以技术上没有什么能阻止我在c中做同样的事情,除了缺乏特征和泛型的c意味着它'非常难以实际做到这一点在实践中,除了在运行时建立回调,哪个是高效的。

在C它中,大多数时候都非常合理地使用线性搜索,因为谁将保持哈希表的十亿分半例行实施?没有内置容器,依赖关系是一种痛苦,所以我写下ad-hoc链接列表并一直剪切角落。除非绝对必要,否则我赢得了' t and and tring写一个复杂的b树的实现。我' ll使用qsort + bisect并调用一天。 OTOH在Rust中只需要1或2行代码来获得非常高质量的实现各种容器。这意味着我的生锈程序每次都可以使用适当,令人难以置信的优化数据结构。

这几天似乎一切都需要JSON。 Rust' serde是世界上最快的JSOS解析器之一,它将直接解析为锈迹,因此使用解析的数据也非常快速,有效。

Rust强制执行所有代码和数据的线程安全,即使在第三方库中,即使该代码的作者没有注意螺纹安全性。一切都是坚持特定的线程安全保证,或者允许跨线使用'如果我编写任何没有线程安全的代码,则编译器将指向它不安全的位置。

那个'■显着不同的情况。通常没有库函数可以信任线程安全,除非它们'否则清楚地记录。它' s到程序员,以确保所有代码都是正确的,并且编译器通常可以' t帮助任何一个。多线程C代码承担了更多的责任,风险更多,所以它的吸引力假装多核CPU只是一个FAD,想象用户与剩下的7或15个核心有更好的事情。

RUDR保证自由于数据种族和记忆不安全(例如,使用欠缺的错误,即使是线程)。不仅仅是一些可以用启发式或在instressed构建的运行时找到的种族,但所有数据都在任何地方种族。这是救命,因为数据竞争是最糟糕的并发错误。他们' ll在我的用户身上发生'机器,但不是在我的调试器中。还有其他种类的并发虫子,如使用锁定原语的差,导致更高级别的逻辑竞争条件或死锁,而且生锈可以消除它们,但它们通常更容易诊断和修复。

在C我赢得了'敢于在简单的循环上做几个以上的OpenMP Pragmas。我试着用任务和线程更加冒险,并最终每次后悔后悔。

rust有很好的数据并行,线程池,队列,任务,无锁数据结构等。在这种构建块的帮助下,以及类型系统的强大安全网,我可以很容易地平行锈迹。在某些情况下,它有足以用par_iter()替换erter(),如果它编译,它可以工作!它并不总是一个线性加速(Amdahl'法律是残酷的),但它往往是2×-3×速度相对较少的工作。

有一个有趣的差异,如何生锈和C图书馆文件线程安全。 Rust有一个词汇表,用于线程安全的具体方面,例如发送和同步,防护和细胞。在C,有没有任何词"你可以在一个线程上分配它,并在另一个线程上释放它,但是你可以' t一次使用两个线程" RUTR描述了在数据类型方面的线程安全,这概括了使用它们的所有功能。在C线程中,在各个函数和标志的上下文中讨论了谈论。生锈' s保证往往是编制的,或至少无条件。在c它中,'常见的是查找"这只是当涡轮喷油尺寸设置为7&#34时的线程安全。

Rust是低水平的,如有必要,可以针对最大性能优化,以及C.高级抽象,简单的内存管理和可用库的丰富,往往会使Rust程序有更多的代码,做更多,做更多的措施如果未选中,可以加起来膨胀。然而,Rust程序也很好地优化,有时优于C.虽然C对于在Byte-Byte指针逐个级别写入最小代码,但RURR具有强大的功能,可有效地将多个功能或甚至整个库结合在一起。

但是,即使当量的C代码过于冒险,最大的潜力也能够无所畏惧地并行化大多数生锈代码。在这个方面,Rust是一种比C更成熟的语言。