Hacker News 中文摘要

RSS订阅

软件,从第一性原理出发 -- Software, from First Principles

文章摘要

文章揭示了智能手机和笔记本电脑背后惊人的物理现实:经过数百年努力,一块近乎完美的岩石被雕刻出微观电路,通过控制电子流动实现计算、记忆和思考,以纳秒级速度完成数十亿次运算,让用户得以专注于任务本身。

文章总结

好的,这是根据您的要求,对原文主要内容进行的中文重述,已保留关键细节并删减了与主题无关的内容。


标题:从第一性原理理解软件

当你使用智能手机或电脑时,很容易忘记其背后物理现实的惊人之处。那流畅体验的背后,凝聚了物理学家、化学家、数学家、工程师和设计师几个世纪的努力。你的设备里有一块近乎完美的纯净硅片,上面蚀刻着微观电路。通过控制这些电路中的电子流动,我们能让这块硅片进行计算、记忆和思考,从而解放我们的脑力。一次操作耗时不到十亿分之一秒,让你在眨眼间就能完成数十亿次计算。这一切如此无缝,作为用户,我们得以忽略所有复杂性,专注于手头的任务。

然而,随着软件主导我们生活的方方面面,忽略其底层原理正变得越来越困难。数字世界如今对我们的日常决策影响不亚于物理世界。就像我们学习物理或生物基础来理解物理环境一样,理解软件对于驾驭数字环境至关重要。没有这种基本素养,我们可能会被动地被那些旨在捕获我们时间和注意力的系统所管理或利用。有了这种理解和清晰的认知,我们就能更好地使用技术,对数字安全做出明智的决定,甚至能为自己特定的需求构建软件。

本文旨在剥离软件和计算机的“魔法”,证明理解其工作原理并非计算机科学学位的特权。为此,我将尽可能避免使用专业术语,只引入必要的概念,并运用第一性原理思维来构建知识体系。在表象之下,计算并非枯燥的技术规格列表,而是一个关于人类智慧和解决问题的迷人故事,每一层新构建都建立在前一层之上。通过跟随这个故事,你可以建立清晰的思维模型,更好地了解使用设备时后台发生的一切。你将不仅理解技术如何运作,还会了解内置的、保护你的无形安全措施。

操控物理来完成数学运算

世界上大多数问题的解决都涉及数学计算,无论是预测天气、计算飞机航线,还是检查桥梁的结构完整性。最初,“计算机”只是挤满了手工进行这些计算的数学家的房间,有些计算需要数天时间。在战争和太空竞赛期间,风险更高,计算弹道导弹和太空火箭的轨迹需要求解数千个复杂的方程。由于哪怕微小的误差都可能导致火箭爆炸、导弹偏离目标或桥梁倒塌,这些计算必须反复验证。这个过程非常缓慢、昂贵且容易出错。

因此,迫切需要一种能够可靠、快速执行计算的机器。科学家们寻找能够模拟计数的物理过程,早期的尝试之一是机械计算器,由互锁的齿轮和杠杆构成。但它也有自身的问题:机械部件会随时间磨损,需要持续维护,而且从根本上来说速度很慢。

真正的飞跃来自我们从机械部件转向电力,使用电开关作为基本构建模块。我们谈论的不是家里的物理电灯开关,而是通过施加电压自动控制的开关。一个电开关有两种状态:断开(无电流,代表0)和闭合(有电流,代表1)。

通过以不同方式连接这些开关,我们可以做出逻辑决策。最基本的配置称为逻辑门: * 与门(串联):如果两个开关一个接一个放置,只有当开关A 开关B都闭合时,电流才会流动。 * 或门(并联):如果两个开关并排放置,当开关A 开关B闭合时,电流就会流动。 * 非门:一个设计为“常闭”的开关,只有在施加电压时才断开并切断电流。

我们可以使用这三种门的组合来构建任何逻辑规则,无论多复杂。因为每个逻辑规则都可以表示为一个表格,而任何表格都可以用这种方法构建,所以与、或、非这三者组合在数学上是完备的,能够表示任何可以想象的逻辑规则。

由于开关只有两种状态,我们无法使用通常的十位数字(09)来表示数字。因此,我们使用二进制,它完全依赖于01(每个数字称为一个比特)。二进制计数的工作原理与机械齿轮计数器相同,但只有两个数字。要了解这些开关如何执行数学运算,让我们看看两个单比特数相加时会发生什么。注意到模式:位仅在输入不同时为1,这正好是异或门的行为。进位位仅在两个输入都为1时为1,这正好是与门的行为。通过将这些门连接起来,并将进位输出馈送到下一个比特的输入,我们可以对任意大小的二进制数进行加法。由于减法只是加一个负数,乘法是重复加法,因此每个算术运算都可以简化为这些基本门电路的组合。

在早期计算机中,我们使用真空管来实现这些开关。但随着工程师们试图构建更大、功能更强的机器,他们遇到了物理瓶颈。真空管体积庞大、耗能巨大且经常故障。为了进一步创新,他们不能仅仅优化脆弱的玻璃管,必须回到物理基础,发明更好的开关。这导致了1947年第一个晶体管的发明,这是一种更小、更冷、更可靠的固态电子开关。今天几乎所有芯片中使用的改进设计是:在硅片上方一个薄绝缘层上施加电压到栅极电极,该电压产生的电场穿过绝缘体,将硅自身的电子吸引到其表面,在源极漏极之间形成一个薄导电通道;移除电压则通道消失,切断电流。在随后的几十年里,随着我们开发出将数千个这样的晶体管直接印刷到硅晶圆上的方法,我们创造了集成电路(或芯片)。

将现实转化为比特

我们已经看到双态开关如何让我们用二进制系统表示和计算数字。但我们不想局限于数字和逻辑;我们的大部分现实世界并不能整齐地排列成数字。例如,声音是空气压力的连续波动。但是,我们如何用逻辑门来表示这种连续的、波动的电信号呢?

克劳德·香农在他1948年关于信息论的奠基性论文中正式确立了这种转变。他推广了“比特”这个术语,并建立了采样定理:如果你以足够高的频率对一个连续的模拟信号进行采样,以规则的时间间隔进行离散测量,你就可以将其数字化,并在目的地完美地重建它。每个样本使用的比特数,即位深度,决定了可用的离散量化级别数。在传输线路的中继点,模拟放大器会同时放大减弱的信号和累积的噪声。而数字再生器则做得更好:它读取每个传入脉冲,判断它代表哪个电平,然后重新发送一个干净的副本,在此过程中丢弃所有噪声。

一旦我们将信息表示为二进制比特,就需要单位来衡量它。单个比特太小,所以我们把它们分组: * 字节:8个比特的组,有256个可能的值——足以存储一个文本字符。 * 千字节:大约一千字节——一页纯文本。 * 兆字节:大约一百万字节——一首MP3歌曲或一张高分辨率照片。 * 吉字节:大约十亿字节——一部高清电影或一个视频游戏。 * 太字节:大约一万亿字节——一个现代硬盘的容量。

构建通用计算机

在最早的计算机中,硬件就是程序。要执行特定任务,工程师必须配置一个定制的物理电路。改变任务意味着物理上重新连接线路或拨动沉重的开关,这是一个繁琐且极易出错的过程。

突破是存储程序计算机。艾伦·图灵从理论上证明,一个单一的通用图灵机可以通过从内存中读取指令来模拟任何计算。约翰·冯·诺依曼在1945年的架构将其转化为具体设计:一个中央处理器通过共享总线从同一内存中读取数据和指令。此后几乎所有计算机都遵循这种结构。

我们不是为单一类型的计算硬连线一个特定电路,而是构建一个带有标准逻辑电路的通用处理器,这些电路可以重复用于不同的操作。我们通过向处理器提供操作码来告诉它做什么。像0001这样的二进制数如何物理地激活一个电路?它通过一个由逻辑门构成的指令解码器来实现。解码器有专门的门来检测特定的组合,当某个门被触发时,它就像一个物理路由器,将电流发送到CPU的相应部分。

到目前为止描述的所有电路都是组合逻辑的,它们的输出纯粹是当前输入的函数,没有对过去的记忆。这引出一个问题:电路如何随时间保持一个值,即使在输入信号消失后也能记住其状态?解决方案是反馈。取两个或非门,将每个门的输出连接到另一个门的输入。结果是一个SR锁存器,以其两个输入命名:置位(将存储值强制为1)和复位(将其强制为0)。这种自我维持的循环就是记忆。为了消除SR锁存器的缺陷,我们在同一个锁存器之上构建了一个D触发器。它不是直接暴露置位和复位,而是从单个输入D派生它们,这样置位和复位永远不会同时为1。为了协调更新时机,我们在其后面加上一个时钟信号,一个像心跳一样在01之间稳定脉冲的控制线。触发器只在时钟滴答的精确上升沿更新。这是基本的单比特存储元件。一个寄存器只是一组触发器,每个比特一个,CPU用它来保存正在积极计算的数字。

通过将这个存储概念放大,将数百万个这样的基本存储单元连接成一个巨大的、可寻址的网格,我们就得到了主内存(通常称为RAM)。注意,这种内存有一个根本限制:锁存器的反馈回路只有在电流流过门时才能保持其比特。切断电源,RAM中的每个比特都会消失。这种内存被称为易失性。对于断电后必须保留的数据,我们需要存储设备,它们以断电后仍能持续的物理状态记录比特。这种速度差距造成了分工,解释了你的许多日常体验:程序和文件存在于存储中,并被复制到RAM中进行处理。这引发了一个先有鸡还是先有蛋的问题:如果RAM在开机时是空的,而程序必须加载到RAM中才能运行,那么谁来加载第一个程序?答案是一个小型的第三种内存,ROM芯片,其比特在制造时永久固定。CPU被硬连线,在通电瞬间从ROM的地址开始执行。那里存储的小程序,称为固件,做足够的工作来找到驱动器并将操作系统从存储复制到RAM,然后移交控制权。

有了存储临时计算结果和长指令列表的方法,我们终于拥有了实现存储程序计算机所需的一切。我们不再需要物理上重新连线机器来改变其任务。相反,我们可以通过简单地将新的指令序列加载到内存中来改变计算机的行为。这就是创造软件的飞跃。

每个处理器由三个核心组件组成: * 控制单元:从内存读取指令并引导电流流向以激活特定电路。 * 算术逻辑单元:执行计算的一组逻辑门。 * 寄存器:即时、高速的存储槽。

这些组件通过一组称为总线的共享线路与外部内存交互。处理器执行一个连续循环:从内存中取指译码,并在算术逻辑单元中执行,将结果写入寄存器。这个循环每秒运行数十亿次。一个微小的晶体振荡器以固定频率发出电脉冲,我们称之为时钟速度,以吉赫兹为单位。每个脉冲标志着一个周期的边界,每个周期将取指-译码-执行循环推进一步。

为了顺序执行指令,CPU使用一个称为程序计数器的特殊寄存器。它保存着要取出的下一条指令的内存地址。每次取指后,程序计数器自动前进到下一个地址。取出的指令保存在指令寄存器中,同时控制单元对其进行译码并将电流路由到正确的电路。为了重复动作或做出决策,我们使用跳转指令。跳转会覆盖程序计数器。通过顺序推进、比较和条件跳转这些机制,我们可以构建任何循环或分支逻辑。

在我们的笔记本电脑和智能手机上,我们有一个由微小、可变色的发光单元组成的大网格,称为像素。现代屏幕使用帧缓冲器来控制,计算机保留一块内存区域,其中每个地址对应屏幕上的一个特定像素坐标。绘制用户界面就是CPU足够快地将颜色值写入这些地址,使其看起来像平滑、移动的图像。每个像素的颜色被编码为三个独立的字节:绿,每个范围从0到255。到目前为止,信息只向外流动,从内存到屏幕。输入则通过相同的总线反向传输。当你按下一个键时,键盘向CPU发送一个称为中断的电信号。CPU暂停其取指-译码-执行循环,跳转到一个小的处理程序,从设备读取键码,存储它,然后在不到一微秒的时间内精确地从中断点恢复执行。

这就完成了机器:一个执行指令的处理器,一个保存程序和数据的存储器,以及用于输出和输入的中断驱动路径。在硬件故事本身继续发展之前,值得一看。工程师们在1990年代积极推动时钟速度,但大约在4 GHz以上,芯片散发的热量超过了可移除的范围。与其对抗热量,业界转向了多核心,在一个芯片上放置几个独立的处理器,每个运行自己的指令流。当通用扩展放缓时,业界再次转向专用硅片。现代芯片将芯片区域专用于特定工作负载,GPU并行运行数千个简单计算,神经加速器则为AI模型背后的矩阵运算而构建。这些电路用CPU的灵活性换取了在一类问题上的原始吞吐量。

用英语编写软件

处理器只理解二进制。编写软件最初意味着手动查找二进制操作码。为了简化,程序员编写了一个称为汇编器的翻译程序。它将人类可读的助记符直接映射到它们的二进制等价物。格雷斯·霍珀在1952年构建了第一个编译器A-0,证明了程序可以自动将人类可读的代码翻译成机器指令。这创造了一个复合反馈循环。早期编译器是用汇编手工编写的,但一旦一种语言变得足够有表现力,它自己的编译器就可以用该语言重写,这个过程称为引导,使编译器自托管。C编译器是用C写的。Go编译器是用Go写的。软件现在被用来构建更好的软件。

由于汇编与特定芯片的物理设计紧密相关,用一种处理器汇编编写的代码无法在另一种上运行。为了解决这个问题,我们创建了高级语言,如C、Fortran或Python。这些语言让我们使用熟悉的抽象来表达逻辑,如变量、循环和函数,而不是寄存器和跳转。在这个层面上,编程更多是关于设计算法,即解决特定问题的逐步逻辑指令。算法是抽象概念,而程序是该算法在特定编程语言中的具体实现。通过用更接近人类语言的代码编写,软件变得更容易编写、阅读和维护。

例如,我们模拟器中倒计时的抽象算法很简单: 1. 从一个数字开始(5)。 2. 如果数字是零,停止。 3. 否则,减1并重复步骤2。

要将此算法转化为可执行的程序,我们将这些步骤翻译成一种编程语言。在C中,我们可以使用一个简单的while循环来编写它。编译器将这个高级循环翻译成等效的汇编指令序列,为我们处理寄存器和跳转细节。

考虑读取一个文本文件。在C中,你必须手动从操作系统请求一个句柄来打开文件,预留一块临时内存(称为缓冲区)来保存字符,将字节读入其中,并在完成后关闭文件。要理解实际发生了什么,文件中存储的每个字符都使用一种称为ASCII的标准编码映射到一个数字。每次我们想读取文件时都写这些代码行很快就会变得令人疲惫。为了避免重复,高级语言允许我们将指令打包成可重用的块,称为函数。函数只是一个执行特定任务的命名代码块。通过将指令序列包装在函数内部,我们可以通过调用其名称一次运行所有指令。这就是函数的作用:给一个过程起个名字,这样你就可以在更高的层次上推理,而无需在脑海中记住实现细节。

Python,作为一种更高级的语言,将这个抽象概念发挥到了极致。open("notes.txt").read() 一行代码就完成了C语言中复杂的多步骤过程。Python的open().read() 仍然在打开文件句柄、在读取时增长缓冲区直到文件结束,并在之后释放它。CPU仍然在做同样的工作,但语言内部处理了缓冲区管理、资源清理和所有错误处理。这种简单性是Python在学术界和研究领域如此受欢迎的原因。

要理解为什么它们之间存在如此大的性能差异,以及为什么我们仍然需要C或C++在Python下面进行繁重的工作,我们必须看看这些语言执行方式的根本区别。C是一种编译型语言。在运行C程序之前,编译器将整个源代码翻译成针对你特定CPU的原生二进制机器码。当你运行它时,处理器直接以最高速度执行它。Python,另一方面,是一种解释型语言。你的脚本不是被提前翻译成原生机器码,而是被交给一个称为解释器的程序,该程序自己逐步执行每个步骤。这种额外的间接层在每个操作上都引入了性能开销。这是我们为便利付出的代价。

C和Python只是庞大高级语言生态系统中的两个例子。今天,我们根据需求选择不同的语言。这种选择很大程度上受失败成本的指导。在典型的网络应用中,风险相对较低;但在飞机、医疗设备或核反应堆中,错误的后果可能是致命的。在安全关键系统中,我们选择优先考虑严格编译时验证、内存安全和确定性行为的语言和工程实践。

此外,现代世界的编程很少是从头开始编写所有东西。在几乎任何语言中,我们都可以找到由他人创建的预编写代码包,称为。这些库为我们解决常见问题,从解析日期和发出网络请求到渲染复杂的3D图形,使我们能够通过组装现有的构建块来构建强大的软件,从而解放我们专注于独特的想法

在软件之间共享处理器和内存

最初,计算机一次只运行一个程序。随着硬件变得更强大,这种效率非常低下。为了解决这个问题,1960年代的计算机系统引入了分时。UNIX,于1969年在贝尔实验室开发,基于这个概念创建了一个统一的操作系统设计,即位于应用程序和硬件之间的后台协调软件。然而,1980年代的早期个人电脑(运行像MS-DOS这样的系统)倒退了一步。因为早期的桌面微处理器很简单,它们仍然一次只能运行一个程序。如果应用程序崩溃,它会破坏整个系统的内存,迫使硬重启。

为了将UNIX级别的稳定性和多任务处理带到个人电脑,我们需要硬件强制执行的边界。现代CPU通过支持不同的特权模式来解决这个问题。操作系统的核心协调器,内核,在特权的内核模式下运行(赋予其对物理硬件的直接、无限制访问)。同时,用户程序在受限的用户模式下运行。在用户模式下,CPU硬件物理地阻止任何对硬件或内存的直接访问;如果程序需要读取文件或绘制屏幕,它必须执行一个系统调用,一个触发硬件中断的特殊指令,将控制权交给内核以安全地执行任务。

为了为用户程序虚拟化物理硬件,内核依赖三个主要抽象: 1. 多任务处理(CPU虚拟化):操作系统通过在多个程序之间快速切换来共享单个CPU。为了防止任何单个程序独占处理器,一个硬件定时器定期中断CPU,将控制权交还给内核。在这个上下文切换期间,内核保存当前程序的寄存器,加载另一个程序的已保存寄存器,并交换执行。这发生得如此之快,以至于每个程序都感觉它独自拥有CPU。 2. 虚拟内存(RAM隔离):为了防止程序相互冲突或读取敏感数据,内核隔离内存。程序在虚拟地址空间中运行。一个硬件单元在运行时将这些虚拟地址转换为物理RAM。程序无法访问其分配空间之外的内存。 3. 文件系统(存储抽象):操作系统维护一个查找表,将文件路径映射到它们在物理驱动器上的确切物理位置,呈现一个清晰的层次树,并在幕后处理访问控制。

通过在特权内核模式和受限用户模式之间切换,操作系统确保单个崩溃的应用程序不会拖垮整个系统。

让计算机相互通信

互联网始于1969年的ARPANET,一个旨在连接全国计算机的美国国防研究网络。为此,工程师们必须解决一个基本问题:数据应该如何传输?当时的电话网络使用电路交换,它在通话期间建立一条专用的、连续的物理连接。但计算机只是间歇性地发送数据,使得专用线路效率极低。相反,ARPANET开创了分组交换。在这种设计下,数据被分成称为数据包的小块,包含有效载荷和目标地址。路由器逐跳传递这些数据包,每个数据包找到自己的路径。这使得网络具有令人难以置信的弹性,数据包可以绕过故障路由,因此没有单个断开的链接能切断整个连接。TCP/IP,在1983年被采纳为通用标准,成为将这些独立网络统一成一个全球互联网的共同语言。

为了在这个尽力而为的系统中确保可靠交付,我们使用一组协议: * IP 将单个数据包从一台机器路由到另一台机器,但不保证它们到达。 * TCP 位于IP之上,对每个数据包进行编号并要求确认,以便任何丢失的数据包被自动重发,为应用程序提供可靠的字节流。 * HTTP 及其加密形式HTTPS,位于TCP之上,定义了用于获取文档和数据的请求-响应格式。

HTTPS中的S代表安全,这是互联网上最重要的日常保护措施。你的数据包经过许多你不控制的机器,其中任何一台都可能读取或更改通过的数据。因此,在发送数据之前,你的浏览器和服务器执行一次额外的握手来验证服务器的身份并商定一个临时的加密密钥;此后,它们之间的所有流量在数学上被加扰,只有两个端点可以解扰。中间的机器仍然可以看到你正在与哪个服务器通信,但看不到你在说什么:你的密码、消息和卡号在通过敌对领土时是不可读的。浏览器地址栏中的挂锁图标仅仅意味着这个机制正在运行。

万维网是建立在这个网络之上的一个独立应用程序,由蒂姆·伯纳斯-李于1991年发明。互联网在机器之间移动数据包,而万维网是一个链接文档(页面)的系统,任何浏览器都可以检索和渲染。每个页面都用HTML编写,这是一组描述文档结构和内容的标签,浏览器解析它来知道要绘制什么。当你输入一个地址时,DNS将人类可读的名称翻译成IP地址,你的浏览器通过HTTP/TCP请求文件,然后浏览器的引擎绘制像素。由于网页运行由陌生人编写的代码,浏览器强制执行一个沙箱,一个隔离的运行时环境,防止页面代码读取你的本地文件或直接访问你的操作系统。

浏览器也悄然成为历史上最重要的软件分发平台。在它之前,发布软件意味着为Windows和macOS编译单独的安装程序,并说服用户下载并运行可执行文件。当谷歌在2008年推出Chrome,配备了一个名为V8的足够快以运行真实应用程序的新JavaScript引擎时,它开启了一波新的网络应用。开发者现在可以编写一个代码库,部署到服务器,并通过一个网址到达地球上的每个设备。用户每次访问都能自动获得最新版本,无需安装步骤。网络标准也随之发展。今天的浏览器是庞大、复杂的软件平台。它们通过暴露API来弥合网络应用和系统之间的差距。结合通过WebAssembly以接近原生速度运行编译语言的能力,浏览器已成为最终的抽象层。它作为我们花费大部分计算时间的主要环境,而操作系统在后台静默运行。然而,这种便利性伴随着一个巨大的权衡。通过在安全的沙箱内运行,网络应用放弃了直接、低级的对底层硬件和操作系统API的访问。此外,在浏览器内运行软件意味着执行多层虚拟机和渲染引擎,与原生二进制文件相比,引入了巨大的性能和内存开销。

为了看到这个多层堆栈的实际运作,考虑当浏览器实际加载一个页面或一个网络应用获取数据时会发生什么。在我们编写高级代码来触发这些操作的同时,底层系统必须协调一个复杂的网络协议例程。例如,在任何HTTP数据可以交换之前,TCP必须通过三次握手建立一个可靠的连接。当你的浏览器发出请求时,它到达一个服务器,一台持续运行、等待响应传入连接的计算机。在这台计算机上,网络服务器软件监听传入的HTTP流量,处理加密,并将请求引导到我们的应用程序代码。在实践中,现代软件很少在一台专用机器上运行。它运行在云基础设施上,即由少数大型提供商拥有的仓库规模的数据中心,组织在那里租用虚拟机而不是购买物理硬件。大多数服务器端软件需要持久化和查询结构化数据。这是由数据库处理的,专门为可靠存储记录和高效检索而构建的软件。数据库使用SQL进行组织和查询。今天有许多数据库引擎可供选择。关系型数据库如PostgreSQL使用SQL将数据组织在表中,而非关系型数据库如MongoDB则将数据存储为灵活的文档。

你只能控制你理解的东西

这个堆栈中的每一层都被设计成在一切正常工作时可以被忽略。这就是抽象的全部意义:它让你在不把每一层都记在脑子里就能推理一个问题,从而为手头的任务释放脑力带宽。知道为给定问题思考哪一层至关重要。你不会深入,除非问题指引你到那里,而问题通常会告诉你在哪里看。但是,当确实需要深入时,你需要对当前使用的抽象之下的东西有所了解,否则就是瞎猜。当调试高级代码中没有明显问题的性能问题时,我们可能需要检查较低级别的交互,如系统调用、调整数据库查询计划或识别由未释放分配引起的内存泄漏。有时解决问题意味着一直深入到基础,比如在真空管无法再扩展时发明晶体管。

没有对底层系统的理解,软件工程师就无法真正控制让软件按他们期望的方式工作,无论是优化其性能、保证其稳定性还是保持其安全。他们完全受制于他们选择的抽象。本文中的每一层都是由对基础有很好理解的人构建的。晶体管来自理解量子力学的物理学家。编译器来自理解汇编并想要一种更便携、可读的方式来表达程序的程序员。操作系统来自理解物理处理器和内存,并希望安全高效地共享这些资源的程序员。每一个新的抽象都是由能够看透他们正在工作的那一层的人构建的。

这种连续的抽象分层说明了软件版本的杰文斯悖论。随着技术进步使计算资源(如CPU周期和内存)更高效、更便宜,我们并没有减少对它们的消耗。相反,我们利用节省下来的资源来构建更雄心勃勃的系统。历史表明,新的创新很少减少我们的工作量;它们只是扩展了我们的视野。同样的悖论现在正在软件生产本身这个层面上重演。随着AI代理使生成代码变得极其便宜,我们不会写更少的软件;我们将写多得多的软件。

同样的原则也适用于安全。一个软件漏洞很少是一个神秘的、独立的问题类别;其核心,它只是一个错误,一个开发者没有考虑到的边缘情况或意外场景。当一个程序接收到它未被设计处理的输入或遇到一个状态时,它会不可预测地行为。攻击者通过向系统提供触发这些未处理路径的特定输入来利用这些漏洞,从而绕过检查或访问受限数据。理解你代码的局限性以及它如何处理意外场景,是区分安全系统与其他系统的关键。

对软件如何管理内存和与硬件交互的透彻理解,让我们有信心,即使在严格限制下,我们的系统也能可预测、安全且高效地运行。

我们比以往任何时候都更需要这种深刻的理解。随着AI代理使每个人都能比以往编写和发布更多代码,软件工程的性质正在发生变化。当事情出错时,那些对基础没有扎实掌握的人将发现自己受制于AI模型,在黑暗中猜测,试图调试他们无法表述的问题。没有对底层系统的清晰思维模型,他们将不知道如何向代理准确传达问题,或验证提出的解决方案是否正确。

另一方面,对于那些非常理解这些抽象的人来说,AI代理不是威胁,而是倍增器。他们可以用精确、有根据的指令指导代理,根据对底层应该发生什么的清晰思维模型来验证生成的代码,并在代理卡住时直接介入修复问题。曾经使单个工程师富有成效的判断力,现在可以扩展到他们指导的每个代理,让一个人构建以前需要整个团队才能构建的系统。我希望这篇文章已经为你提供了那个立足点。

评论总结

根据评论内容,主要观点和论据如下:

正面评价(多数): - 文章图解丰富,有助于理解抽象概念(评论2、6、7)。关键引用:"I really like the diagrams! This is a great way to show what happens deep down."(评论2);"The interactive diagrams helped make some concepts click"(评论6)。 - 内容详实,适合不同层次读者(评论4、5、7)。关键引用:"Congratulations! This is incredibly well done."(评论4);"as a frontend dev it really makes me appreciate how cool all the tech I'm using actually is."(评论7)。

改进建议(少数): - 文本过多,部分内容价值不高(评论2)。关键引用:"There's a lot of text and not all of it is valuable"(评论2)。 - 劫持浏览器“返回”按钮令人困扰(评论2)。关键引用:"Hijacking the browser's 'back' button is kind of annoying."(评论2)。 - 建议移除AI相关表述,避免分散主题(评论5)。关键引用:"I'd remove the AI messaging, it distracts from the main ideas."(评论5)。 - 文章过长,可拆分为更易消化的部分(评论5)。关键引用:"Maybe too long for a single reading though, you might have better response by publishing in more digestable chunks."(评论5)。

负面批评(个别): - 对“CPU是石头”的比喻表示反感(评论3)。关键引用:"I'm so tired of this cliche... a CPU is a rock about as much as much as an airplane is a rock"(评论3)。 - 认为该比喻带有居高临下的态度(评论3)。关键引用:"it just shows them being condescending"(评论3)。

其他: - 推荐相关书籍《Code》(评论8)。关键引用:"This is the book to read if you are interested in the topic"(评论8)。