专栏名称: 聊聊架构
聊聊架构
目录
相关文章推荐
字节跳动技术团队  ·  基于LLM的AI应急:多模态信息智能化分析整 ... ·  23 小时前  
字节跳动技术团队  ·  远程访问代理+内网穿透:火山引擎边缘网关助力 ... ·  2 天前  
字节跳动技术团队  ·  稀土掘金 x Trae ... ·  2 天前  
51好读  ›  专栏  ›  聊聊架构

为什么说C语言不是低级语言?

聊聊架构  · 公众号  · 架构  · 2018-08-20 18:41

正文

请到「今天看啥」查看全文


现代高端 CPU 上的寄存器重命名引擎是晶模和功率的最大消耗者之一。更糟糕的是,在运行指令时我们无法将其关闭或对其进行功率门控。但这个单元在 GPU 上显然是不存在的,GPU 的并行性源于多个线程,而非标量代码的指令。如果指令不需要对依赖项进行重拍序,那么寄存器重命名就不是必需的。

让我们来看一下 C 语言抽象机内存模型的另一个核心部分:平面内存。为了降低延迟,现代处理器通常在寄存器和主存储器之间使用了三级高速缓存。

顾名思义,缓存对程序员是透明的,因此对 C 语言是不可见的。使用缓存是让代码在现代处理器上快速运行的最重要的方法之一,但这完全被抽象机隐藏了起来,程序员必须了解高速缓存的实现细节(例如,两个 64 字节的值可能会处在同一个高速缓存行中)才能写出高效的代码。

优化 C 语言

低级语言的一个常见属性是运行速度快,它们应该能够在不使用特别复杂的编译器的情况下编译成快速执行的代码。足够聪明的编译器可以加快一门语言的运行速度,C 语言支持者在谈论其他编程语言时却常常忽略了这一点。

通过简单编译就能获得快速执行的代码,但对 C 语言来说并不是这么一回事。尽管处理器架构师努力设计可以快速运行 C 语言代码的芯片,但 C 语言程序员所期望的性能水平只能通过非常复杂的编译器转换来实现。Clang 编译器(包括 LLVM 的相关部分)大约有 200 万行代码。即使只算上为了让 C 语言代码运行更快所需的分析和转换,也会增加近 20 万行代码(不包括注释和空行)。

例如,在 C 语言中,在处理大量数据时需要使用循环来串行地处理每个元素。要在现代 CPU 上以最佳方式运行,编译器必须先确定循环是独立的。这个时候可以借助 C 语言的 restrict 关键字,它可以保证对一个指针的写入不会干扰对另一个指针的读取。但这些信息比 Fortran 要少得多,这也是 C 语言在高性能计算方面未能取代 Fortran 的重要原因。

一旦编译器确定循环是独立的,那么下一步就是尝试对结果进行矢量化,因为现代处理器的矢量代码吞吐量是标量代码的四到八倍。这类处理器的低级语言将具有任意长度的原生矢量类型。LLVM IR(中间表示)就是这样,因为将大型矢量运算分成较小的矢量运算总是比构造更大的矢量运算更容易。

在这种情况下,优化器必须与 C 语言内存布局保证作斗争。C 语言保证具有相同前缀的结构体可以互换使用,并且它将结构体字段的偏移量暴露给了语言。这意味着编译器不能随意重新排序字段或通过插入填充来改进矢量化(例如,将数组结构体转换为一组结构体,或者反过来)。对于低级语言来说,这不一定是个问题。在低级语言中,对数据结构体布局的细粒度控制是一个特性,但它确实会让快速运行 C 语言代码变得更难。







请到「今天看啥」查看全文