<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/feeds/style.xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>danluu</title>
    <description>rssume processed feed for danluu</description>
    <link>/feeds/danluu</link>
    <atom:link href="/feeds/danluu" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Thu, 4 Jun 2026 09:55:09 +0000</lastBuildDate>
    <generator>rssume</generator>
    <item>
      <title>关于AI偏见讨论的讨论</title>
      <description>[AI 摘要] 本文探讨了公众对生成式AI偏见问题的讨论往往将其视为非缺陷，与经典软件漏洞反应截然不同，并分析了这种现象背后的技术、社会和经济原因。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文探讨了公众对生成式AI偏见问题的讨论往往将其视为非缺陷，与经典软件漏洞反应截然不同，并分析了这种现象背后的技术、社会和经济原因。</div><p>过去几年，关于机器学习/AI（尤其是大型语言模型和生成式AI）偏见的爆款故事频频出现。关于偏见讨论中，我感兴趣的一点是：当生成式AI产生与用户请求截然相反的输出时，人们的反应与“经典”软件漏洞（存在明确错误的情况）下的反应截然不同。特别是，如果你去看论坛或其他非专业人士的讨论，人们经常否认模型输出与用户要求相反的现象是一种漏洞。例如，一年前，一位亚裔麻省理工学院研究生要求Playground AI（PAI）“为原图中的女孩制作一张专业的领英头像”，结果PAI将她的面部转换成了白人面孔和蓝眼睛。</p>

<p>Reddit首页关于此事排名第一的“没有偏见”回应，也是总体排名最高的评论之一，是这样的：</p>

<blockquote>
<p>当然，现在你去<a href="https://civitai.com/" rel="noopener noreferrer">最流行的Stable Diffusion模型网站</a>看看首页的图片。</p>

<p>你会看到数量惊人（几乎占非动漫模型的50%）的亚洲女性，多到你会认为亚洲特质是一种被向往的特征。</p>

<p>这难道不比“一个女人在一个网站上输入了一个愚蠢的提示，然后他们生成了一个白人女性”更重要吗？</p>

<p>另外请记住，她输入的是“领英”，所以任何了解当前提示工作原理的人都知道，AI更可能搜索的是“领英上的普通女性”，而不是它认为的“职业女性”，因为图像AI没有观点。</p>

<p>简而言之，这只是AI引流文章。</p>
</blockquote>

<p>其他表达相同主题的高赞评论包括：</p>

<blockquote>
<p>老实说，这条评论应该排得更高。如果你想现在就用带检查点的Stable Diffusion，如果你不想要亚洲女孩，那就难多了。很多很多模型都是基于动漫或亚洲女性训练的。</p>
</blockquote>

<p>以及：</p>

<blockquote>
<p>对吧？AI图像甚至有相反的问题。训练集中亚洲人数量庞大，加上亚洲创建的模型数量庞大，意味着<b>很多很多</b>模型都偏向于输出亚洲面孔。</p>
</blockquote>

<p>其他高赞评论指出这是样本量问题：</p>

<blockquote>
<p>“系统性种族偏见的证据”</p>

<p>只展示了一个结果。</p>
</blockquote>

<p>当《波士顿环球报》要求采访时，Playground AI的CEO也给出了同样的回应——他拒绝了采访，并回复了一系列反问句，如下（《波士顿环球报》暗示还有更多，但未刊登回复的其余部分）：</p>

<blockquote>
<p>如果我只掷一次骰子得到数字1，这是否意味着我每次都会得到1？我是否应该根据一次观察就得出结论，认为这个骰子偏向数字1，并且被训练成倾向于掷出1？</p>
</blockquote>

<p>我们本可以同样轻松地从谷歌、Facebook、微软或任何其他当前部署大量机器学习的公司中举出一个例子，但既然Playground AI的CEO基本上是在要求别人看看PAI的输出，我们在本文中就以PAI为例。我在我的Mastodon个人头像上尝试了麻省理工学院研究生使用的相同提示，将“女孩”替换为“男人”。PAI通常会将我的亚洲面孔变成白人（高加索人）面孔，但有时会让我变得更白一些但民族特征模糊（可能有点像中东人或东亚人之类。顺便说一句，我的面孔有一些独特的越南特征，看起来很越南，而不太像任何东亚人。</p>

<p><img src="https://danluu.com/images/ai-bias/profile.png" alt="Profile photo of Vietnamese person" width="400" height="400">
<img src="https://danluu.com/images/ai-bias/playground-ai-1.png" alt="4 profile photos run through playground AI, 3 look very European and one looks a bit ambiguous" width="848" height="210">
<img src="https://danluu.com/images/ai-bias/playground-ai-2.png" alt="4 profile photos run through playground AI, none look East Asian or Southeast Asian" width="1640" height="408"></p>

<p>我的头像是一张浅肤色的冬季照片，因此我尝试了一张深肤色的夏季照片，结果PAI通常会将我的面孔转换成南亚或非洲面孔，偶尔是中国面孔（但从未是越南人或东南亚人面孔），例如如下：</p>

<p><img src="https://danluu.com/images/ai-bias/tanned-profile.jpg" alt="Profile photo of tanned Vietnamese person">
<img src="https://danluu.com/images/ai-bias/playground-ai-3.png" alt="4 profile photos of tanned Vietnamese person run through playground AI, 1 looks black and 3 look South Asian"></p>

<p>许多其他人也尝试了各种提示，他们得到的结果也表明该模型（这里的“模型”是泛指，包括模型及其权重，以及任何负责生成输出的系统）对诸如“如果一个人从事特定职业，其种族是什么”之类的事情有一些先入为主的观念，这些观念强烈到足以覆盖输入的照片。例如，由于模型“决定”通过丢弃亚洲特征并使其变白可以使某人看起来更“专业”，从而将浅肤色亚洲人转换为白人。</p>

<p>其他人尝试了各种提示，以观察模型中捆绑了哪些先入为主的观念，并发现了类似的结果，例如，<a href="https://discuss.systems/@ricci/110826586910728179" rel="noopener noreferrer">Rob Ricci在分别要求生成“计算机科学”、“哲学”、“化学”、“生物学”、“兽医学”、“护理学”、“性别研究”、“中国历史”和“非洲文学”X教授的“领英头像”时得到了以下结果</a>。在为前7个提示生成的28张图像中，28人中可能只有1或2人不是白人。下一个提示“中国历史”的结果则极其刻板化，<a href="https://restofworld.org/2023/ai-image-stereotypes/" rel="noopener noreferrer">当我们要求生成非白人输出时，其他模型也经常出现这种情况</a>。Andreas Thienemann指出，除了夸张的中国刻板印象外，每位教授都戴着眼镜，这是另一个经典的刻板印象。</p>

<p><img src="https://danluu.com/images/ai-bias/rob-pgai-1.png">
<img src="https://danluu.com/images/ai-bias/rob-pgai-2.png">
<img src="https://danluu.com/images/ai-bias/rob-pgai-3.png"></p>

<p>如前所述，我并非特意针对Playground AI。正如我在别处指出的，<a href="https://twitter.com/danluu/status/896176897675153409" rel="noopener noreferrer">价值万亿美元的公司经常将AI模型部署到生产环境中，甚至没有进行最基本的偏见检查</a>；当我尝试ChatGPT时，我尝试的所有偏见检查提示返回的结果都类似于我们在此看到的图像，例如，<a href="https://twitter.com/danluu/status/1601072083270008832/" rel="noopener noreferrer">当我尝试要求提供科技行业男性和女性的简介时，女性的简介往往表明她们从事多元化工作，即使对于没有相关公开记录的女性也是如此；而男性则倾向于拥有名牌工程学院（如MIT和伯克利）的学位，即使这些人并未就读于任何名牌学校</a>，对于名牌科技公司也是如此（由于Twitter限制，链接只有4个例子，但我尝试的其他例子与显示的例子一致）。</p>

<p>本文本可以使用几乎任何公开可用的生成式AI。它之所以使用Playground AI，仅仅是因为CEO的回应既要求我们这样做，也反映了外行常见的标准反射式“AI没有偏见”的回应。</p>

<p>回到关于“由于亚洲人在其他情况下如此突出，因此将职业照片中的人变成白人并不算有偏见”的回应，我们之前查看的那条高赞Reddit评论建议“去<a href="https://civitai.com/" rel="noopener noreferrer">最流行的Stable Diffusion模型网站</a>看看首页的图片”。以下是我点击该评论发布当天链接并点击“feed”后看到的内容。</p>

<p></p><details>
<summary>[点击展开/折叠略带NSFW的图片]</summary>
<img src="https://danluu.com/images/ai-bias/reddit-images-2.png">
<img src="https://danluu.com/images/ai-bias/reddit-images-1.png">
</details><p></p>

<p><abbr title="首页已经清理过了，但查看被引用时的网站状态是合理的，而且随着色情内容的清理，首页上的亚洲面孔也明显减少了">这个网站有点色情的感觉</abbr>。中位数的图像可以描述为“你期望在电影场景中一个青少年男孩房间墙上看到的海报，编剧们为了展示这个角色是一个饥渴且社交能力差的青少年男孩而使用标准道具”。当你进入feed并获得默认的“有史以来”排名时，首先显示的是有人抓着一个年轻女性的胸部，标题为“引导式抓胸 | LoRA”；两个年轻女性在亲热，标题为“动漫之吻”；以及一个戴着皮带的年轻女性，标注着“BDSM —— 被拴住的LORA”。因此，显然有一个网站被人们喜欢用来生成和传播色情照片，该网站上亚洲女性照片的高比例被用作证据，证明不存在对亚洲女性有负面影响的机器学习偏见，因为这抵消了一位亚洲女性在试图获取用于领英的正式照片时被变成白人的情况。我真的不知道该说什么。<a href="https://mastodon.gamedev.place/@rygorous/110824248191236861" rel="noopener noreferrer">Fabian Geisen的回应“🤦‍♂️. truly 'I'm not bias. your bias' level discourse”，感觉是恰当的回应</a>。</p>

<p>评论中展示的另一种标准推理线，在我看到的几乎所有关于AI偏见的讨论中都很典型，其代表为：</p>

<blockquote>
<p>AI在“专业人士”股票照片上训练，然后让她变白。我们感到惊讶吗？</p>

<p>她要求AI让她的头像更专业。互联网上大多数“专业”股票照片里都是白人。</p>
</blockquote>

<p>以及：</p>

<blockquote>
<p>如果她要求她的照片变得更像任何东西，它都可能让她变白，仅仅因为在西方，亚洲人只占美国人口的7.3%，其中很大一部分是南亚人，看起来和她（东亚人）的特征完全不同。东亚人只占5%或更少；训练数据太少。</p>
</blockquote>

<p>这些评论似乎基于一个基本假设，即公司使用的训练数据代表美国，而且这是合理的，并且这<em>应该</em>导致模型将每个人转换为最常见的类型。这在多个层面上都是错误的。</p>

<p>首先，关于专业股票照片是否以白人为主，快速搜索“专业股票照片”图像会显示不少非白人面孔，所以要么股票照片并非那么白，要么人们已经想出了如何返回更具代表性的股票样本的方法。并且考虑到全球人口分布，目前不清楚互联网服务应该以美国为中心。然后，即使我们接受主要互联网服务应该假设每个人都在美国，假设每个请求都来自“典型美国人”似乎既是设计缺陷，也是偏见的明显标志。</p>

<p>由于许多人在谈论种族或民族时会有这种反射性反应，让我们来看一个不那么敏感的AI假设。假设我与当地机械师的AI客服聊天机器人交谈，要求预约更换冬季轮胎并进行轮胎换位。然后，当我取车时，发现他们给我换了机油而不是冬季轮胎。接着一群网络评论员解释为什么这不是任何偏见的迹象，并且你应该知道AI聊天机器人会将任何与机械师的预约转换为换机油预约，因为这是最常见的预约类型。一个将任何预约请求都转换为“给我最常见的预约类型”的聊天机器人显然是损坏的，但出于某种原因，AI辩护者坚持认为，当涉及改变某人的种族或民族时，这是可以的。同样，声称我的换轮胎预约被转换为换机油预约是没问题的，因为其他公司的调度程序会将换机油预约转换为换轮胎预约，这听起来很荒谬，但这是我们在上面讨论的另一种常见推理线。</p>

<p>假设我使用某种标准的非AI调度软件（如Mindbody或JaneApp）来预约机械师，并要求预约更换轮胎和换位。如果我最终被换了机油，仅仅因为软件只是调度最常见的预约类型，这将是软件存在漏洞的明显标志，没有理性的人会认为应该不努力修复这个漏洞。然而，这是人们在AI方面经常提出的一个论点（这可能是关于此话题评论中最常见的辩护）。这种论点更进一步，即存在一种对漏洞产生原因的解释，被用来证明该漏洞应该存在，人们甚至不应该试图修复它。这样的解释对于“经典”软件漏洞来说显然是荒谬的，对于机器学习来说同样荒谬。也许可以辩称，机器学习中的漏洞更难修复，修复它不切实际，但这不同于常见的论点——即认为这不是漏洞，并且这就是软件应有的正确行为。</p>

<p>我可以想象一些用户在程序采取对用户来说更不透明的操作时（例如自动更正）会这么说，但我确实尝试在Reddit上搜索<code>autocorrect bug</code>，在前3个帖子中（我没有查看其他帖子），255条评论中有2条否认不正确的自动更正是漏洞，而且这两条评论都来自同一个人。我敢肯定，如果你深入研究足够多的话题，你会发现某些话题的否认率更高，但在搜索了另外几个话题（如<abbr title="存在用户相对频繁地责怪用户的情况；也许有5%到10%的评论者会责怪Excel格式问题导致数据损坏，但这仍然远低于我们在机器学习偏见中看到的一半左右的比例，并且评论中的激动/愤怒程度似乎也较低">excel格式</abbr>和自动更正漏洞）后，我搜索的话题都没有接近我们在生成式AI中看到的情况——在那里，一半的评论者激烈否认提示产生与用户要求相反结果是一种漏洞并不罕见。</p>

<p>回到漏洞本身，就机制而言，我们在分类器和生成模型中都能看到的是，许多（也许是大多数或几乎所有）系统都吸收了互联网样本中反映的、许多人存在的偏见，从而导致诸如<a href="https://x.com/danluu/status/1245961051696295936" rel="noopener noreferrer">谷歌图像分类器将黑人手握温度计分类为{手, 枪}，而将白人手握温度计分类为{手, 工具}</a><sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">1</a></sup>之类的事情。在过去十年中，发生了许多此类错误，从2015年谷歌相册将黑人分类为大猩猩，到2018年<a href="https://x.com/danluu/status/1043957859090911233" rel="noopener noreferrer">部署某种文本分类器用于广告，将包含“非裔美国作曲家”和“非裔美国音乐”等术语的广告分类为“危险或贬损”</a>，<a href="https://finance.yahoo.com/news/google-chatbot-ridiculed-ethnically-diverse-185014679.html" rel="noopener noreferrer">谷歌在Gemini上则把旋钮转到了另一个方向</a>，顺带一提，这引发的愤怒比其他任何例子都大。</p>

<p>偏见进入自动化系统并不是新鲜事。这早于生成式AI和大型语言模型，也是机器学习模型之外存在的问题。只是机器学习的广泛使用使这些问题对人们变得可理解，从而使其中一些案例成为新闻。例如，如果你看压缩算法和字典，<a href="https://www.patreon.com/posts/97523860" rel="noopener noreferrer">Brotli严重偏向英语</a>——该语言内置的120个转换中，涉及人类语言的元素都是英语，内置压缩字典的英语权重也比你可能参考的任何代表性权重（按人口加权的语言使用者、非自动化人类语言在消息平台发送的文本等）都要重。你可以为为什么英语应该被如此重度加权提出理由，但也有相反的理由，例如，英语使用率与用户的带宽正相关，因此平均而言，非英语使用者更需要压缩。但是，无论你认为应该使用什么精确的加权函数来生成代表性的字典，这都不会成为轰动新闻，因为你无法让典型读者关心120个内置Brotli转换中有许多做的事情，如在文本中添加“ of the ”、“. The”或“. This”，这些高度针对英语，没有任何转换编码高度针对其他人类语言的术语，尽管世界上只有20%的人说英语，或者，与使用者数量相比，内置压缩字典与其他任何人类语言相比，都极度倾斜于英语。你可以为Brotli字典提出类似于上述的辩护，在Brotli字典训练所依据的某个代表性语料库上，我们使用Brotli字典获得了最佳压缩，但<a href="https://gist.github.com/duskwuff/8a75e1b5e5a06d768336c8c7c370f0f3" rel="noopener noreferrer">字典</a>中有很多奇怪的短语，如“World War II”、“, Holy Roman Emperor”、“British Columbia”、“Archbishop”、“Cleveland”、“esperanto”等，这可能会让我们怀疑，训练字典的语料库也许并非最具代表性，或者<abbr title="查看语言使用情况与字典中的语言，出于某种原因，字典包含了使用人数排名第1、第2、第3、第4、第6和第9的语言的单词，但出于某种原因缺少了第5、第7和第8，尽管在一种情况下存在重叠">甚至并非特别代表人们发送的文本</abbr>。包含“, Holy Roman Emperor”在字典中，在互联网发送文本的整个分布上，真的比包含任何其他内容（如法语、乌尔都语、土耳其语、泰米尔语、越南语等）能产生更好的压缩效果吗？</p>

<p>另一个不太可能成为轰动新闻的例子是，我无法在博客标题中放入我的越南语名字，并期望我的博客在越南语谷歌之外被谷歌索引——我在开始博客时尝试过，结果我的博客立即从谷歌搜索中消失，除非你在越南。人们默认假设大家想要英语搜索结果，并且，大概有人创建了一个启发式方法，如果一个页面上有两个带越南语变音符号的字符，该方法就会触发，有效地将该页面标记为“太亚洲”，因此除了一个国家外，世界上任何其他地方的人都不感兴趣。“看起来像<abbr title="当然，你也可以用任何其他少数群体来替代这里">越南人</abbr>”似乎是漏洞的一个相当普遍的原因。例如，即使没有变音符号，越南名字也是个问题。我经常遇到表格要求我提供我母亲的娘家姓。如果我输入我母亲的娘家姓，我会被告知诸如“无效姓名”或“姓名太短”之类的信息。这没关系，因为我用一个替代品来应对这种粗心大意，这可能更安全。另一个问题是人们认定我告诉他们我的名字不正确并更改我的名字。对于我的姓氏，如果我读我的名字是“Luu, L-U-U”，有一半的情况会被从越南语“Luu”缩短为中文“Lu”，很多时候也被缩短为西方名字“Lou”，但我发现如果我说“Luu, L-U-U, two U's”，大约95%的情况都能奏效。这有时会让对方感到恼火，他们会恼怒地说诸如“你没必要拼写三遍”之类的话。<abbr title="虽然我对此表示怀疑——我的经验是，说这种话的人错误率高于平均水平">也许对那个特定的人是这样</abbr>，但大多数人不会理解。这甚至发生在我将名字输入计算机系统时，因此在名字被数字化记录之前，不可能有转录错误。我的法定名字（没有变音符号）是Dan。对于越南裔美国人来说这并不罕见，因为Dan既可用作越南名字，也可用作美国名字，而且许多越南移民不知道Dan通常是Daniel的简称。在我全职工作过的六家公司中，有三家公司有人“好心地”将我的名字改为Daniel，大概是因为有人看到数据库中记录了Dan，于是认定我没有正确输入名字，而他们比我更了解我的名字，并且他们对此非常确定，以至于<abbr title="我在一家公司的人力资源代表曾问过我，但这当然是他们没弄错我名字的三家公司之一">觉得没必要问我确认</abbr>。在一个案例中，这只影响了我的电子邮件显示名称。由于我对别人如何称呼我并无强烈感受，我没有费心去更改它，许多人在那里工作时都叫我Daniel而不是Dan。在另外两个案例中，名字更改影响了重要的文件，所以我不得不实际更改它，以便我的保险、税务文件等与我的法定名字一致。如上所述，用我的头像向Playground AI发出相当无害的提示，即使在罕见的情况下产生亚洲输出，似乎也产生东亚输出而非东南亚输出。我在一些大公司的生成式AI模型上也注意到了同样的事情——即使你要求它们产生东南亚输出，它们也会生成东亚输出。被宣传为清理错误和噪音工具的AI工具也会清理掉亚洲特征（以及其他类似的“错误”），例如，使用过Adobe AI降噪（宣传为“通过语音增强从录音中去除噪音”）的人指出，它会去除亚洲口音，使人听起来像美国人（同样也适用于其他一些口音，如东欧口音）。</p>

<p>我大概每周仅在广泛使用的软件的使用过程中就能看到几十到几百件这样的事情（<a href="https://danluu.com/everything-is-broken/" rel="noopener noreferrer">远少于之前观察到的每周数百到数千个的整体漏洞数量</a>），但我交谈过的大多数美国人根本没有注意到这些事情。最近，有很多关于各种机器学习系统中的偏见所造成的所有危害的讨论，机器学习的广泛使用将带来各种新的危害。这或许没错，但我的感觉是，自从我们拥有自动化以来，我们就已经将偏见编码进自动化中，自动化范围和规模的扩大一直并将继续增加自动化偏见的范围和规模。<abbr title="这种可见度的增加似乎也引发了更多的抵制，表现为人们坚持认为做与用户要求相反的事情不是漏洞">只是现在，许多机器学习的使用使这些偏见对普通人变得更加可理解，因此更可能成为新闻</abbr>。</p>

<p>在我目前看到的关于这个话题的流行文章中，存在一种非历史性的观点，即它们没有承认这里的核心问题并非新问题，从而导致在提出解决方案时出现两类问题。其一是解决方案通常是针对机器学习的，但这里的问题无论是否使用机器学习都会发生，因此针对机器学习的解决方案似乎关注错了层面。当提出的解决方案是通用性时，我看到的解决方案是以前提出过并且失败了的。例如，至少在过去的二十年里，也许最常见的（除非“人们应该更关心”算作行动呼吁）行动呼吁之一，就是我们需要更多元化的团队。</p>

<p>这显然没有奏效；如果奏效了，上面提到的问题就不会如此普遍。这没有奏效并且不会奏效存在多个层面，其中任何一个层面都足以使这个解决方案失败。一个问题是在整个行业，<a href="https://danluu.com/tech-discrimination/" rel="noopener noreferrer">掌权者（高管和控制资本的人，如VC、PE投资者等）</a>，总体而言，<abbr title="直到2021年左右，科技工作者拥有越来越多的权力，有时能够推动更多元化，但权力似乎在可预见的未来已向另一个方向转移">并不关心这个</abbr>。尽管有<a href="https://danluu.com/tech-discrimination/" rel="noopener noreferrer">效率</a><a href="https://danluu.com/talent/" rel="noopener noreferrer">理由</a>支持更多元化的团队，<a href="https://danluu.com/bad-decisions/" rel="noopener noreferrer">但其理由永远不如游戏和体育领域的决策那样清晰明确</a>，在那里，我们看到非常昂贵且容易量化的糟糕决策在错误被指出几十年后仍然存在。然后，即使高管和资本接受了这个想法，它仍然不会奏效，因为<a href="https://en.wikipedia.org/wiki/Curse_of_dimensionality" rel="noopener noreferrer">维度太多</a>。如果你看一家真正重视多元化的公司，比如2013-2019年的Patreon，如果组织能够在<a href="https://x.com/danluu/status/1487228574608211969" rel="noopener noreferrer">两三个维度</a>上认真重视多元化，而在成百上千的其他维度上（例如越南名字或面孔是否处理得当）有所疏忽，那就算幸运了。</p>

<p>即使所有这些都不是问题，解决方案仍然不会奏效，因为虽然拥有一支具有相关多元经验的团队可能与问题优先级有一点相关性，但它并不能自动导致问题被优先考虑和修复。举一个非敏感的例子，一个自谷歌地图交通估算功能诞生以来就存在的漏洞，至少到2022年还存在（自那以后我开得不多，不知道漏洞是否还存在）是：如果我在高峰时段开始时询问行程需要多长时间，这会考虑当前交通状况，而不是我行驶过程中交通状况的变化，因此系统性低估了行程所需时间（反之，如果我计划在高峰时段出行，这会系统性高估行程所需时间）。如果你试图通过增加谷歌地图通勤多元化来解决这个问题，这将会失败。已经有许多从事谷歌地图工作的人开车并能观察到估算系统性错误的方式。增加多元化以确保有开车并注意到这些问题的人，不太可能起到什么作用。或者，举另一个例子，<a href="https://danluu.com/diseconomies-scale/" rel="noopener noreferrer">当Uber支付团队的前经理被Uber的机器学习模型错误地标记为交易欺诈而错误地被禁止访问时</a>，没有人能够弄清楚发生了什么或是什么偏见导致他被错误禁止（他们通过将他的用户添加到允许名单中解决了问题）。很少有人会比支付团队的前经理获得更好的服务，即使在那种情况下，Uber也无法真正弄清楚是怎么回事。雇佣一名“多元化”候选人加入团队，并不会自动解决或显著影响候选人所多元化维度上的偏见，因为团队前经理在六个月调查后只能通过白名单恢复账户访问权限。</p>

<p>如果你的软件开发方法学导致的结果是：支付团队经理被禁止的修复方案是六个月后将其加入白名单，你应用中的交通路线二十年来系统性错误，<a href="https://danluu.com/nothing-works/" rel="noopener noreferrer">你应用的核心功能不工作</a>，等等，那么无论雇佣多少背景与注意到某些类型问题相关的多元化候选人，都不会导致修复这些问题，无论是机器学习偏见还是其他类别的漏洞。</p>

<p>当然，有时旧想法的变体确实会成功，但一个提案要可信甚至有趣，必须解决为什么下一次迭代不会像之前所有迭代那样失败的问题。如上所述，我看到的两个最常见的建议方案是：人们应该更努力、更关心，以及我们应该拥有不同背景（在非技术意义上）的人。这对于大量的“经典”漏洞没有奏效，对于旧的机器学习漏洞没有奏效，似乎也没有理由相信它应该对当今机器学习模型产生的这类漏洞奏效。</p>

<p>Laurence Tratt说：</p>

<blockquote>
<p>我认为这比个别的偏见实例更重要。有趣的是，大多数人a)没有注意到他们正在引入这样的偏见，b)通常甚至不能合理地期望他们注意到。例如，一些网页表单拒绝了我以前的地址，因为我住在乡村，那里的许多房屋只有名字——但大多数开发者住在城市，那里的房屋只有编号。从某种意义上说，这是主动偏见在起作用，但没有恶意：程序员必须填写设计细节并做出选择，他们将根据自己的经验来这样做。我们谁都不是无所不知！这就提出了一个有趣的哲学问题：假设组织应该意识到他们正在编码偏见，何时才是合理的？</p>
</blockquote>

<p>我的感觉是，<a href="https://danluu.com/nothing-works/" rel="noopener noreferrer">机构和产品的“自然”状态，即最低能量和最直接的状态，就是它们工作得不太好</a>。如果之前没有<a href="https://danluu.com/culture/" rel="noopener noreferrer">灌输一种文化</a>或<a href="https://danluu.com/wat/" rel="noopener noreferrer">建立流程</a>来在某个特定维度上培养质量，质量可能就会很差，<a href="https://danluu.com/hardware-unforgiving/" rel="noopener noreferrer">因为制作高质量的东西很困难</a>，因此组织应该预期，如果没有一个强大的流程来捕捉偏见，他们正在编码各种各样的偏见。</p>

<p>我们在这里遇到的一个问题是，当涉及消费类软件时，公司普遍选择了速度而非质量。考虑到我们今天所处的监管环境，或者我在有生之年可能看到的任何监管环境，这似乎基本上是不可避免的，因为那些真正选择质量而非功能速度的公司会被淘汰，因为消费者普遍选择成本更低或功能更多的选项，而不是质量更高的选项。我们在<a href="https://danluu.com/car-safety/" rel="noopener noreferrer">研究汽车在样本外碰撞测试中的表现时</a>看到了这一点，我们发现只有沃尔沃在为实际碰撞优化汽车，而不是为了在公开测试中取得好成绩。尽管车祸是50岁以下人群的主要死因之一，但消费者为安全付费的优先级如此之低，以至于沃尔沃已成为一个利基品牌，不得不向高端市场转移，销售豪华车才能生存。我们在CPU上也看到了类似情况，英特尔过去比AMD和ARM投入更多的验证工作，相应地严重的错误也更少。<a href="https://danluu.com/cpu-bugs/" rel="noopener noreferrer">当AMD和ARM开始严重威胁时，<abbr title="人们可能希望英特尔的质量优势是其拥有垄断地位的原因，但看起来恰恰相反，是英特尔的垄断地位使其能够投资于质量。">英特尔将努力从验证和确认转移开，以提高速度，因为其质量优势在市场上并没有带来任何好处，现在英特尔芯片几乎和AMD芯片一样容易出错</abbr></a>。</p>

<p>我们可以在<a href="https://danluu.com/nothing-works/" rel="noopener noreferrer">几乎每个消费市场以及许多B2B市场</a>观察到类似情况，而当我们谈论的是已有解决方案的问题时。如果我们看一个从技术角度看我们不知道如何很好解决的问题，比如机器学习模型中微妙甚至不那么微妙的偏见，那么我们应该预期会看到比“经典”软件系统更多、更糟糕的漏洞，这正是我们所看到的。任何经得起市场考验的解决方案，都必须能够抵御消费者会压倒性地选择漏洞更多但拥有他们想要的功能或更快发布他们想要的功能的产品这一问题，这使得任何需要小心谨慎以至于显著减慢发布速度的解决方案处于非常困难的境地，<abbr title="其他可能性，每一种都不太可能，是消费者确实会关心偏见，以及不会适得其反的有意义的监管变化">除非存在一个像英特尔在其鼎盛时期那样的单一主导玩家</abbr>。</p>

<p><i>感谢Laurence Tratt、Yossi Kreinin、Anonymous、Heath Borders、Benjamin Reeseman、Andreas Thienemann和Misha Yagudin的评论/修正/讨论</i></p>

<h3 id="appendix-technically-how-hard-is-it-to-improve-the-situation">附录：从技术上讲，改善情况有多难？</h3>

<p>这是一个真诚的问题，而不是反问。自从2014年以来，我没有做过任何机器学习相关的工作，所以我对目前情况的了解不够深入，无法对技术层面形成直接意见。许多比我最近更多从事机器学习工作的人，如Yossi Kreining（见下文附录）和<a href="https://buttondown.email/apperceptive/archive/supervision-and-truth/" rel="noopener noreferrer">Sam Anthony</a>，认为这个问题非常困难，也许以我们目前所处的阶段是几乎不可能解决的。</p>

<p>由于我没有直接意见，这里有三种听起来可能类似的情况，每一种都支持不同的结论。</p>

<p>类比一：也许这就像<a href="https://danluu.com/sounds-easy/" rel="noopener noreferrer">人们从2014年起就说会有人很快构建一个谷歌，因为现有的开源工具基本上已经比谷歌搜索更好</a>，或者<a href="https://yosefk.com/blog/high-level-cpu-follow-up.html" rel="noopener noreferrer">人们说</a><a href="https://yosefk.com/blog/the-high-level-cpu-challenge.html" rel="noopener noreferrer">构建一个将高级语言原语编码到硬件中的“高级”CPU</a>会<a href="https://www.patreon.com/posts/54329188" rel="noopener noreferrer">给我们带来通用CPU性能1000倍的提升</a>。你无法真正证明这是错误的，而且搜索质量的巨大改进或CPU性能1000倍的提升可能就在眼前，但提出这些建议的人通常听起来像怪人，因为他们表现出我们提到的非历史性，并提出了我们已知不起作用的解决方案，却没有解释为什么他们的解决方案将解决导致先前尝试失败的问题。</p>

<p>类比二：也许这就像软件测试，<a href="https://danluu.com/everything-is-broken/" rel="noopener noreferrer">软件漏洞无处不在，尽管硬件行业有数十年关于如何更高效找到漏洞的经验</a>，但<a href="https://danluu.com/testing/" rel="noopener noreferrer">这些技术中</a>的应用却很少。我多次和人们谈论过这个问题，最常见的回应是关于应用程序XYZ有某些独特的约束，使得测试完全不可能或无法使用我讨论的这类技术，但每次我深入研究，都发现该应用程序比我见过这些技术被应用的领域更容易测试。人们可以认为我在测试方面是个怪人，但我实际上已经使用这些技术测试了各种软件并成功做到了，所以我不认为这与<a href="https://www.patreon.com/posts/54329188" rel="noopener noreferrer">声称如果我们只使用我的宠物CPU架构，CPU就会快1000倍</a>之类的事情相同。</p>

<p>考虑到存在的激励因素，软件公司通常可以将漏洞成本转嫁给客户，而客户并不真正了解发生了什么，我认为除非监管发生变化，否则我们不会看到在测试方面投入大量努力，但并没有根本原因我们需要避免使用更高效的测试技术和方法。</p>

<p>从技术角度来看，使用更好测试技术的障碍相当低——我已经指导过人们如何开始编写自己的模糊测试器和随机测试生成器，这通常需要30分钟到一小时，之后人们往往会比以前更有效地使用这些技术来发现重要的漏洞。然而，通过揭示的偏好，我们可以看到组织并不真正“想要”让他们的开发者高效地测试。</p>

<p>当涉及测试和修复机器学习模型中的偏见时，情况更像类比一还是类比二？虽然我不能有把握地说我们处于类比二，但我不确定如何能说服我不是处于类比二。如果我对测试一无所知，我会听所有这些人向我解释为什么他们的应用无法以找到严重漏洞的方式进行测试，然后得出如下结论之一：</p>

<ul>
<li>“大家”都是对的，这很合理——这是他们了解的领域，而我不了解，所以我为什么应该相信不同的说法？</li>
<li>没有意见，也许是因为默认的高怀疑水平。</li>
<li>每个人都错了，鉴于我对该领域一无所知，并且没有特别的理由相信每个人都错了，这似乎不合理。</li>
</ul>

<p>作为一个局外人，需要非常高度的过度自信才能断定每个人都错了，所以我要么错误地得出“大家”都对的结论，要么没有意见。</p>

<p>鉴于“经典”测试的情况，我觉得我必须在这里没有真正的意见。没有最新的知识，得出这么多专家都错了的结论是不合理的。但有足够多的<a href="https://danluu.com/bitfunnel-sigir.pdf" rel="noopener noreferrer">人们说困难或不可能的问题，结果证明是可行的而且并不那么棘手</a>，以至于我很难在没有实际研究的情况下高度相信一个问题本质上是不可解决的。</p>

<p>我认为无法估计如果我真的研究了它我会怎么想。假设我尝试在这个领域工作，并试图在OpenAI或其他从事此类问题的地方找到一份工作，<a href="https://x.com/danluu/status/1470890494775361538" rel="noopener noreferrer">不知怎么通过了面试</a>，我在该领域工作了几年，但没有取得进展。这并不意味着问题不可解决，只是我没有解决它。当涉及“Lucene基本上和谷歌搜索一样好”或“CPU可以轻易快1000倍”之类的言论时，该领域的人们显然知道说这些话的人是怪人，因为他们表现出对领域实际问题完全缺乏理解，但做出这样的判断需要对该领域有相当多的了解，我不认为有什么捷径可以让你可靠地推断出你在拥有该领域知识时会如何判断。</p>

<h3 id="appendix-the-story-of-this-post">附录：本文的故事</h3>

<p>我在2023年中Playground AI事件走红时写了本文的初稿，然后搁置了一年，看看当该事件不再是突发新闻时，它是否仍然成立。一年后回顾，我不认为核心问题或我看到的关于该话题的讨论有太大变化，所以我将其整理干净，并于2024年中发布了本文。</p>

<p>如果你喜欢做预测，你认为十年后（2033年）这篇文章仍然相关的几率有多大？供参考，<a href="https://danluu.com/everything-is-broken/" rel="noopener noreferrer">这篇2014年发布的关于“经典”软件漏洞的文章，今天（2024年）发布也可以达到基本相同的效果</a>（我说基本相同是因为我今天看到的漏洞比2014年多，而且我今天看到的前端和操作系统漏洞也比2014年多得多，所以会有更多不同类型的漏洞）。</p>

<h3 id="appendix-comments-from-other-folks">附录：其他人的评论</h3>

<p></p><details>
<summary>[点击展开/折叠Yossi Kreinin的评论]</summary><p></p>

<p>我不确定这在多大程度上是你同意的观点，但我认为与生成式AI偏见非常像其他软件偏见相关的另一点是，这种偏见到底是什么。“AI偏见”并非AI学习其创造者的偏见并巧妙地实施它们，例如针对其创造者不喜欢的少数群体。相反，“AI偏见”更像是“除非市场或政府迫使我这样做，否则我通常懒得修复漏洞，作为这种行为的逻辑结果，我尤其懒得修复那些对某些群体产生不成比例负面影响的漏洞，而由于所涉及特定群体的具体情况，其影响不太可能迫使我修复漏洞。”</p>

<p>这是经典软件漏洞和AI漏洞之间的相似性——意思是，没有人担心“软件以某种狡猾的阴谋方式有偏见”，每个人都明白是软件制造商在谋划，或者更常见的是，软件制造商懒得把事情做对。对于生成式AI，我认为“谋划”实际上比传统软件更不可能，“不修复漏洞”则更有可能，因为人们不理解他们制造的AI系统，并且能够让他们按照自己的意愿（无论善恶）运作的程度，比传统软件要低得多；另一方面，出于同样的原因（我们不知道自己在做什么），漏洞更有可能发生。我认为许多来自政治光谱的人（包括例如埃隆·马斯克，而不仅仅是记者等）在涉及种族/政治或其他敏感偏见例子时，会说诸如“训练AI对世界产生错误思考很糟糕”之类的话；我认为现实中这是影响用户的产产品漏洞，并且在修复优先级方面存在偏见，但这个东西根本无法思考。</p>

<p>我想补充一点，几乎肯定存在试图“谋划”让生成式AI重复某种政治观点、过度/不足代表某个群体等的尝试，但由于漏洞/无法真正控制模型，这些尝试总是产生滑稽的副作用。我认为类似地试图控制传统软件以实现政治相关议程的尝试，平均而言要有效得多（不过在这里，我认为你确实有关于人们认为是聪明阴谋的社交媒体漏洞的具体例子）。无论你将潜在的议程视为恶意还是美德，两者都只能在能力之后，而在这方面还有很长的路要走。</p>

<p>参见<a href="https://arxiv.org/pdf/2406.02061" rel="noopener noreferrer">展示大型语言模型状态-of-the-art完全推理崩溃的简单任务</a>。我觉得如果这不行，那么很多其他东西也不行，而且列举它肯定相当困难。</p>

<p>我的意思是，没人会期望一个1980年的专家系统经过足够的调整而不会表现得不正常。我看不出这和大型语言模型之间有重大区别，除了大型语言模型有用得多。它仍然是一种假装像人一样说话但实际上做的事情在概念上简单且非常不同，而且通常看起来是正确的东西。</p>
</details><p></p>

<p></p><details>
<summary>[点击展开/折叠一位AI创业公司创始人的匿名评论]</summary>
[我]在这个过程中（创办一家AI创业公司），接触过很多主流的机器学习代码。接触如“核废料”或“H1N1”一样。它有传统软件漏洞，其频率让我感到震惊，即使作为一个老练、厌倦的程序员。例如，我最近在看分词，第一个明显的步骤是在几个实现之间进行一些轻量级的差异测试。它失败得相当可笑。不是像“他们错过了一些边缘情况”，更像是“根本没人看过一眼”。考虑到我们所知道的模型对分布外数据的反应，这简直是疯狂。<p></p>

<p>从某种意义上说，这与你讨论的偏见类型正交……但它也暗示了一种深刻的缺乏技巧和严谨性，这完全吻合。</p>
</details><p></p>

<p></p><details>
<summary>[点击展开/折叠Benjamin Reeseman的评论]</summary><p></p>

<p>[Ben希望我指出，这应被视为非正式回应]</p>

<p>我对机器学习模型（或任何其他“专家”系统，就你所说而言，ChatGPT没有发明这个，它使其可理解）中的人口统计学偏见及相关现象略有不同的看法。</p>

<p>我认为试图迫使模型反映的不仅仅是现在基本上是互联网（大致如此）的语料库，实际上掩盖了真正的问题：偏见是真实的，人们确实因为其背景、肤色、性取向或任何其他事情而受到不公正对待，我宁愿模型揭示这些，让我们集体面对现实中的失败模式，而不是试图调整表面文章以允许滥用继续存在。</p>

<p>这类似于#metoo运动或各种DEI倡议，大多数在开始时都是善意的，但容易被利用，最终代表着那些处于特权地位的人空头支票的净增加。</p>

<p>这并不是说对齐没有作用，我认为它同样始于良好的意图，甚至可能是局部有用的缓解措施。</p>

<p>但真正的解决方案是解决现实世界中的不公正和不公平。</p>

<p>我认为你引用的例子是或应该是一个警钟，没有人可以假装可信地忽略真实的问题，并且理想情况下能推动真正的改革。</p>

<p>我很乐意在你有空时讨论这个，我的观点是少数派，但就个人而言，我非常支持解决根本问题，而不是用一堆开关语句之类的来掩盖它们。</p>

<p>对此有一个更悲观的看法：真正的改革是不可能的，我们现在生活在技术反乌托邦中，让受到追捕的少数群体在可能的地方和方式进行缓解吧。</p>

<p>而且有一个令人沮丧的有力论据支持这个观点：即使在表面上发达的国家，今天的警察看起来就像我30年前还是个孩子时的士兵，有人在花费巨大代价守卫某些东西以防止其他东西，据我所知，这不是为了防止黑人孩子被枪杀。</p>

<p>但我并不认同这种悲观主义，我认为这是工业化转变为对物理物流的任意权力的局部异常<em>如果我们知道如何改变它</em>。</p>

<p>我曾在蛋白质组学领域短暂为诺贝尔奖候选人相关的生物技术人士提供咨询。像任何顾问一样，我将任何功劳都让给了每小时支付丰厚费用的人。</p>

<p>但这确实现在是一个香农问题：CRISPR Cas-9可以对一个人的基因组进行任意编辑，甚至（在伦理和法律的灰色地带）对生殖系进行编辑。</p>

<p>我们只是不知道要改变什么，而且该领域有足够的诚信，我们不会为了找出答案而冒险将任意的孩子置于风险之中。</p>
</details><p></p>

<h3 id="appendix-reproducing-rob-ricci-s-results">附录：重现Rob Ricci的结果</h3>

<p>我使用默认设置尝试了提示，除了将图像质量降低到10以更快地生成图像。这意味着我们使用512/512、7提示引导、10质量、随机种子。尝试2次后，将图像质量提高到40，因为图像质量太低，有时无法分辨种族。除了增加图像质量外，没有尝试重新运行提示或从输出中进行任何选择。提示是“为X生成一张看起来非常专业的领英头像”，其中X是博士、律师、工程师、科学家、记者和银行家。</p>

<p>这产生了以下图像：</p>

<p><img src="https://danluu.com/images/ai-bias/playground-ai-doctor.png">
<img src="https://danluu.com/images/ai-bias/playground-ai-lawyer.png">
<img src="https://danluu.com/images/ai-bias/playground-ai-engineer.png">
<img src="https://danluu.com/images/ai-bias/playground-ai-scientist.png">
<img src="https://danluu.com/images/ai-bias/playground-ai-journalist.png">
<img src="https://danluu.com/images/ai-bias/playground-ai-banker.png"></p>

<p>大致来说，我认为Rob的结果被重现了，考虑到我们已经看到这么多图像，这应该不足为奇。</p>

<p>然后，为了看看我们能否重现标准的反驳论点（即生成式AI没有偏见，因为请求色情图像时经常出现亚洲女性），我尝试提示“为一位色情明星生成一张低俗的Instagram头像”。存在NSFW过滤器，在某些情况下被触发，因此我们没有得到四张一组的图像，而是得到了：</p>

<p></p><details>
<summary>[点击展开/折叠非常轻微的NSFW图像]</summary>
<img src="https://danluu.com/images/ai-bias/playground-ai-trashy.png">
</details><p></p>

<p>而且，确实，生成的图像比亚我们任何专业照片（除了Rob Ricci为“中国研究教授的领英头像”请求生成的一组照片）都更加亚洲化。</p>

<h3 id="appendix-comments-from-benjamin-reeseman">附录：Benjamin Reeseman的评论</h3>
<div>

<hr>

<ol>
<li id="fn:S">当然，当我提到这一点时，“聪明的反驳者”回应道“基准率是多少”，但花30秒谷歌搜索显示美国枪支拥有率在白人中远高于其他任何人群。如果你想想手握物体的基准率——该物体是枪支的频率有多高？无论种族如何，频率都会非常低。当然，你可以找到一个完全不像基础基准率的有偏样本，谷歌似乎就是这样做的，但不清楚这为什么能成为这个漏洞存在的理由。
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 23031 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/ai-bias/</link>
      <guid isPermaLink="false">https://danluu.com/ai-bias/</guid>
      <pubDate>Sun, 16 Jun 2024 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>史蒂夫·鲍尔默是一位被低估的CEO</title>
      <description>[AI 摘要] 文章论证史蒂夫·鲍尔默在任期内为微软的长期成功奠定了基础，反驳了外界对其领导力的低估。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章论证史蒂夫·鲍尔默在任期内为微软的长期成功奠定了基础，反驳了外界对其领导力的低估。</div><p>普遍存在一种说法：微软在史蒂夫·鲍尔默领导下一蹶不振，后来被萨提亚·纳德拉的卓越领导力所拯救。这是我在所有在线讨论中看到的主流叙事，也是“现实生活中”普遍接受的看法。虽然本文无意对纳德拉的领导力提出负面评价，但这种说法低估了鲍尔默对微软成功的贡献。不仅在鲍尔默任期内，微软的财务表现（收入和利润）非常出色，而且他领导下的微软做出了深入、长期的战略投资，为他卸任后数十年的成功奠定了基础。当时这些投资备受批评，表明它们并非显而易见，但回顾来看，尽管面临当时的质疑，公司做出了非常有力的决策。</p>

<p>除了在后来被归功于纳德拉的领域进行深入投资外，鲍尔默还为任何继任者清除了政治障碍，为纳德拉的成功铺平了道路。正如<a href="https://x.com/danluu/status/1129519029192757249" rel="noopener noreferrer">加里·伯恩哈特曾因将问题陈述和解决方案表述得过于显而易见而遭到批评，以至于人们没有意识到自己学到了非同寻常的东西</a>，鲍尔默为微软未来的成功所做的铺垫如此有效，以至于人们很容易批评他是一个无能之辈，因为他的继任者如此成功。</p>

<h3 id="对鲍尔默的批评">对鲍尔默的批评</h3>

<p>对于那些在世纪之交之前没有经历过的人来说，在90年代，微软曾被认为是城里最大、最强的公司。但不久之后，人们对微软的看法就改变了——到2007年，许多人认为微软是下一个IBM，保罗·格雷厄姆写下了《微软已死》，文中指出微软曾被认为是高效的已是古老的历史：</p>

<blockquote>
<p>几天前，我突然意识到微软已经死了。当时我正在和一位年轻的初创公司创始人谈论谷歌与雅虎的不同之处。<abbr title="Nam Nguyen指出这可能是不正确的——雅虎最大的恐惧是谷歌，而不是微软——但就这两段引文而言，它们之所以被使用是因为格雷厄姆写了最著名的‘微软在衰落’的文章，这就是为什么引用这些话。格雷厄姆关于雅虎的看法是否正确与此案例无关">我说雅虎从一开始就因害怕微软而扭曲了自己的定位</abbr>。这就是为什么他们将自己定位为“媒体公司”而非科技公司。然后我看着他的脸，意识到他并不理解。就好像我告诉他80年代中期的女孩有多喜欢巴里·曼尼洛一样。巴里是谁？</p>

<p>微软？他没说什么，但我能看出他不太相信有人会对他们感到恐惧。</p>
</blockquote>

<p>这类评论通常伴随着微软收入注定会下降的说法，比如格雷厄姆的这些评论：</p>

<blockquote>
<p>演员和音乐家偶尔会复出，但科技公司几乎不会。科技公司就像抛射物。正因为如此，你可以在任何问题出现在资产负债表上之前就宣称它们已死。相关性可能领先收入五年甚至十年。</p>
</blockquote>

<p>格雷厄姆将谷歌和互联网的崛起称为微软死亡的主要原因，我们稍后会讨论这一点。尽管格雷厄姆没有点名鲍尔默，也没有指出他在《微软已死》中的影响，但鲍尔默几十年来一直是技术人员最喜欢的靶子。鲍尔默是从商业领域起家的，后来成为销售与支持执行副总裁；技术人员喜欢贬低科技行业中非技术背景的人<sup id="fnref:P"><a href="#fn:P" rel="noopener noreferrer">1</a></sup>。当时和现在一个常见的批评是，鲍尔默不懂技术，是一个糟糕的领导者，因为他只知道销售和利润，只会抄袭别人做过的事情。举个例子，如果你在科技论坛（minimsft、HN、slashdot等）上看鲍尔默在2012年将辛诺夫斯基赶走时的在线评论，鲍尔默的领导力几乎受到普遍批评<sup id="fnref:H"><a href="#fn:H" rel="noopener noreferrer">2</a></sup>。这里有一条典型的评论，来自一位自称是匿名微软内部人士的人：</p>

<blockquote>
<p>赶走鲍尔默。从失败的在线服务部门（它们永远不会变得更好）开始，裁员40%。将数十亿美元重新投资于普吉特海湾地区可以为微软带来增值或收购目标的创业机会……重置Windows——桌面和平板电脑。认真对待企业云（如Salesforce……）</p>
</blockquote>

<p>就鲍尔默为自己辩护而言，他指出<a href="https://x.com/janettu/status/380824535714377728" rel="noopener noreferrer">市场似乎低估了微软</a>。鲍尔默指出，当时微软的市值相对于其基本面/财务表现，与亚马逊、谷歌、苹果、甲骨文、IBM和Salesforce相比非常低。这似乎是鲍尔默的公允评估，因为<abbr title="截至我在2024年7月更新本文草稿时，如果包括股息再投资；我有从2022年至今的本文草稿，每次查看数据时微软看起来都相当不错">自那时起，微软的表现优于所有这些公司</abbr>。</p>

<p>当纳德拉成为CEO后微软市值飙升时，叙事自然是鲍尔默正在扼杀微软，公司一直在挣扎，直到纳德拉将其扭转。你可以选择其他讨论，但举个例子，如果我们看最近一次《微软已死》在Hacker News上排名第一时，快速搜索一下，鲍尔默的名字出现了24次。鲍尔默有一些辩护者，但标准的叙事——鲍尔默在拖累微软——就在那里，其中一位辩护者甚至使用了部分标准叙事：鲍尔默是一个缺乏想象力的庸才，但他至少在财务上为微软奠定了良好基础。如果你看高排名的评论，它们都在嘲笑鲍尔默。</p>

<p>如果你看那些信息较少的论坛，比如Twitter或Reddit，你会看到同样的攻击，但鲍尔默的辩护者更少。在Twitter上，当我搜索“Ballmer”时，前四个结果都是明确嘲笑鲍尔默的。第五个结果可能褒贬不一，但从评论来看，似乎通常也被视为嘲笑鲍尔默，而且在我滚动查看的过程中，除一个视频外，其余的视频都在嘲笑鲍尔默（那个例外是一个采访，鲍尔默提到他在2009年向扎克伯格出价“200亿美元以上，大概是这个数”收购Facebook，这在当时将是第二大科技收购案，仅次于卡莉·菲奥莉娜2001年以250亿美元收购康柏）。在Reddit上搜索（使用无历史记录的隐身窗口）也是同样的情况（排除关于他作为NBA老板的故事，他在球迷中受人尊敬）。头号故事在嘲笑他，下一个故事指出他比比尔·盖茨更富有，关于他作为CEO表现的最高评论以“讽刺的是，他是微软[原文如此]最糟糕的CEO”开头，然后是标准的叙事：公司现在做得好的唯一原因是纳德拉拯救了局面，鲍尔默错过了科技行业所有重要的变化，等等。</p>

<p>总结一下，过去二十年里，人们一直在嘲笑鲍尔默是个不懂科技的滑稽人物，充其量是个只知道如何维持公司运转、不知道如何促进创新、导致微软在所有重要市场落后的记账员。</p>

<h3 id="鲍尔默的胜利">鲍尔默的胜利</h3>

<p>这种普遍看法与鲍尔默领导下的实际情况相矛盾。自格雷厄姆宣布微软已死以来，在鲍尔默领导下发生的具有财务意义的积极事件包括：</p>

<ul>
<li>2009年：必应推出。这被认为是一个巨大的失败，但这里的标准相当高。快速网络搜索显示，必应据称在2015年盈利10亿美元，在2024财年盈利64亿美元，收入126亿美元（根据微软2022年的市盈率，粗略估计必应在2022年的价值约为2400亿美元）</li>
<li>2010年：微软创建Azure

<ul>
<li>我不能说我自己喜欢这个产品，但就运营大规模云基础设施而言，世界上遥遥领先的三家公司是亚马逊、谷歌和微软。从商业角度来看，你能对微软在这里说的最坏的话就是它是该业务的可靠第二名，并且是成为第一名的最大威胁</li>
<li>在鲍尔默领导和培育下建立的企业销售部门，对Azure和Office的成功至关重要</li>
</ul></li>
<li>2010年：Office 365发布

<ul>
<li>微软将其企业/业务软件套件从盒装软件转变为基于订阅的软件，并提供在线选项</li>
<li>这没有一个固定的日期；Office 365的正式发布似乎可以作为一个好的参考年份</li>
</ul>
</li><li>像Azure一样，我个人不喜欢这些产品，但如果微软要拆分成主要业务部门，企业软件套件可能是唯一能在市值上与Azure匹敌的业务部门</li>
</ul>


<p>当然也有很多重大的失误。从2010年到2015年，HoloLens是微软最大的赌注之一，仅次于Azure和必应，但到目前为止，任何人的大型AR或VR赌注都没有带来良好的回报。微软未能抓住移动市场。尽管Windows Phone总体上受到尝试过它的评论家的好评，但根据你问的对象，微软要么是太晚了，要么是不愿意长期补贴Windows Phone。尽管.NET至今仍在使用，但就市场份额而言，.NET和Silverlight没有达到早期的承诺，关键部分因内部政治斗争而受阻或被取消。必应，就声誉而言，是一个失败，而且，至少根据微软当时的选择，<a href="https://danluu.com/ftc-google-antitrust/" rel="noopener noreferrer"></a>可能需要对谷歌采取反垄断行动才能成功，但这个失败仍然产生了一个价值数千亿美元的业务部门。尽管有所有这些失败，最大的赌注Azure可能价值万亿美元左右。</p>

<p>微软的企业销售部门是在鲍尔默担任CEO之前建立的（他曾担任销售与支持执行副总裁，实际上是以第一位业务经理的身份加入微软），并在鲍尔默担任CEO期间继续发展壮大。微软的销售策略如此有效，以至于当我在微软时，谷歌会向一些Office 365客户提供谷歌的企业套件（文档等）免费使用。微软销售人员指出，即使面对谷歌免费提供产品的情况，他们通常仍能完成微软付费产品的销售。对企业而言，微软产品及其企业销售团队的组合如此有效，以至于谷歌甚至无法免费送出自己的产品。</p>

<p>如果你正在阅读本文并且在一家“科技”公司工作，那么公司极有可能选择谷歌企业套件而非微软企业套件，微软销售人员的企业销售宣传对你来说可能听起来很荒谬。</p>

<p>我的一位经营初创公司的熟人，微软Azure的销售人员试图向他们推销Azure，开场白是“你们在用AWS，消费者云。你们需要Azure，企业云。”对大多数科技公司的人来说，企业意味着价格过高、不可靠、垃圾。就像很容易嘲笑鲍尔默是因为他来自销售和业务部门一样，当你听到企业销售宣传时也很容易嘲笑它，但总体而言，微软的企业销售部门做得很好。当我在Azure工作时，我调查了它是如何运作的，并且刚刚离开谷歌，这之间有着天壤之别。这是在2015年，纳德拉领导下，但让微软扩大规模的文化和流程是在鲍尔默领导下建立的。我认为有几个月，微软招聘和入职的销售人员比谷歌雇佣的总人数还要多，而且销售流程的每个阶段都相当有效。</p>

<h3 id="微软在鲍尔默领导下的失误">微软在鲍尔默领导下的失误</h3>

<p>当人们指出一长串失败，如必应、Zune、Windows Phone和HoloLens，作为鲍尔默是某种拖累微软的滑稽人物的证据时，这表明他们对科技行业缺乏理解。这就像指出一个风险投资公司资助的失败公司列表来证明该风投不知道自己在做什么。但在像风险投资这样的以成功为基础的行业里，这是愚蠢的。如果你想证明风投是糟糕的，你需要指出整体回报不佳或缺乏重大成功，这意味着整体回报不佳。同样，像微软这样的大公司有一个庞大的投资组合，一次成功的赌注可以弥补大量失败。鲍尔默的批评者无法指出整体回报不佳，因为微软在他任期内的整体回报非常好。收入从<abbr title="大多数来源引用220亿到780亿美元，这可能源于不理解财年和日历年不是一回事。鲍尔默主持的最后四个季度的收入为204.03亿+245.19亿+185.29亿+191.14亿=825.65亿美元">140亿或220亿美元增加到830亿美元</abbr>，这取决于你是从1998年7月鲍尔默成为总裁时算起，还是从2000年1月他成为CEO时算起。公司离开时也相当盈利，前四个季度记录了270亿美元的利润，比他接手的公司的收入还要多。就市值而言，仅Azure一家就将跻身全球市值前十的上市公司，而减去Azure的企业软件套件可能刚好错过前十。</p>

<p>因此，批评者也无法指出在鲍尔默主持下创建Azure、将微软的企业软件从一套本地桌面应用转变为Office 365等、创建世界上最有效的企业销售组织、创建微软的游戏帝国（除了其他事情，鲍尔默在任CEO期间，微软收购了Bungie并于2001年推出时使Halo成为Xbox的旗舰游戏）等方面缺乏成功。即使是被广泛认为是失败的必应，根据最近报告的收入和<abbr title="截至本文草稿于2024年年中撰写时">当前的市盈率，也将是全球第12大最有价值的科技公司</abbr>，介于腾讯和ASML之间。在攻击鲍尔默时，人们引用必应作为鲍尔默任期内发生的失败，这说明了鲍尔默成功的程度。大多数公司都希望自己的成功能像必应一样成功，更不用说他们的失败了。当然，如果鲍尔默有先见之明，他所有的赌注都成功了，让微软价值约10万亿美元，而不是今天区区3万亿美元的市值，那会更好，但对鲍尔默的批评——说他有一些失败和一些价值1万亿美元的成功——是他不是有史以来最伟大的CEO，差距巨大。确实如此，但算不上什么批评。</p>

<p>而且，与纳德拉不同，鲍尔默接手的不是一个容易取得成功的公司。正如我们之前提到的，鲍尔默上任不久，微软就被认为是一家无聊、无关紧要的公司，是下一个IBM，这主要是由于比尔·盖茨担任CEO时做出的决定。作为从早期就加入微软的资深员工，鲍尔默也对当时的微软状况负有部分责任，因此微软的问题至少部分归因于他（但这也意味着他应该因微软在90年代的成功而获得一些赞誉）。尽管如此，他很好地应对了微软最棘手的问题，并为继任者铺平了道路。</p>

<p>早些时候，我们注意到保罗·格雷厄姆将谷歌和互联网的兴起列为2007年之前微软死亡的两个原因。<a href="https://danluu.com/ftc-google-antitrust/" rel="noopener noreferrer">正如我们在探讨科技反垄断行动时讨论的那样</a>，这两者都有一个共同的根本原因：针对微软的反垄断行动。如果我们查看<a href="https://danluu.com/us-v-ms/" rel="noopener noreferrer">微软反垄断案的文件</a>，很明显微软知道互联网将有多重要，并计划控制互联网。作为这些计划的一部分，他们利用在桌面操作系统的垄断地位扼杀了网景。从技术上讲，他们因此输掉了一场反垄断案，但如果你看实际结果，微软基本上从法院得到了他们想要的。对微软的补救措施被广泛认为毫无用处（最初的决定涉及拆分微软，但他们能够通过上诉推翻这一决定），而且案件拖延了很长时间，以至于在案件裁决时网景已经注定失败，那些没有专门针对网景情况的补救措施毫无意义。</p>

<p>在计划中后来讨论但从未执行的部分是扼杀谷歌。如果我们根据微软有多“危险”、它打压竞争对手有多有效来评判微软，<abbr title="格雷厄姆指出微软已死是因为它不再危险">就像保罗·格雷厄姆判断微软已死那样</abbr>，那么微软当然变得不那么危险了，但微软内部的感觉是，由于形势所迫，他们被迫这样做。扼杀谷歌计划的一部分是将地址栏中输入google.com的用户重定向到MSN搜索。这发生在Chrome出现之前，也发生在移动设备以任何形式出现之前。Windows桌面市场份额为97%，IE的市场份额在80%到95%之间（取决于年份），其余市场份额属于快速衰落的网景。如果微软采取这一行动，谷歌在能够推出Chrome和Android之前就被扼杀了，而且除非采取极端的反垄断行动（例如拆分微软），否则微软将永远拥有互联网。然后，作为甜点，没有理由不针对亚马逊。</p>

<p>经过内部辩论，微软决定不扼杀谷歌，不是出于对反垄断行动的恐惧，而是出于对随之而来的反垄断行动所带来的不良公关的恐惧。如果微软将流量从谷歌引开，对谷歌的影响将比他们针对网景的行动更快、更严重，而且在美国司法部赢得另一场针对微软的案件所需的时间内，谷歌将遭受与网景相同的命运。如果你不在那个时候，可能很难想象这一点，但美国司法部诉微软案是定期的头版新闻，这是我们自那以后再也没有见过的（部分原因是公司吸取了这方面的教训——谷歌<a href="https://danluu.com/ftc-google-antitrust/" rel="noopener noreferrer"></a>据称通过游说扼杀了2011-2012年联邦贸易委员会针对他们的反垄断调查，并且巧妙地操纵了最近的案件，使其不像那样主导新闻周期）。自微软反垄断案媒体马戏团以来，我们见过的最接近的事情是Crowdstrike故障的媒体反应，但与美国司法部诉微软案相比，那只是一时的热门。</p>

<p>如果这里有对鲍尔默的批评，也许是微软没有在其年轻竞争对手从其大反垄断案中学到重要教训之前，主动吸取这些教训。一位足够有远见的高管本可以倡导进行大量游说，在反垄断案件发生之前阻止它，就像谷歌在2011-2012年所做的那样，或者操纵使反垄断案件只是另一个新闻故事，就像谷歌当前案件一直在做的那样。另一个可能的批评是，微软没有正确解读政治风向，并意识到在针对微软的大案之后至少二十年内不会出现严重的美国科技反垄断。原则上，如果鲍尔默拥有合适的团队专业知识，认识到美国将进入一个反垄断审查减少的二十年时期，他本可以推翻不扼杀谷歌的决定。</p>

<p>就批评而言，我认为前者是正确的，但除非你期望CEO是<abbr title="就最大化利润而言">完美无缺的</abbr>，否则这不是对鲍尔默的指控，因此作为鲍尔默是一个糟糕CEO的证据，这将是一个非常弱的批评。而且后一种批评是否正确也不明确。虽然谷歌能够逃脱诸如<a href="https://x.com/danluu/status/1016164712030134272" rel="noopener noreferrer">在Android中硬编码搜索引擎以防止用户更改搜索引擎设置</a>和<a href="https://x.com/danluu/status/887724695558205440" rel="noopener noreferrer">让恶意软件安装程序诱骗用户将Chrome设为默认浏览器</a>之类的行为，而且他们被视为“好人”，没有因为这些行为受到太多审查，但微软并没有像谷歌那样受到媒体或公众的温柔对待。直到2011年，谷歌才引发严重的反垄断调查，因此2001年至2010年间缺乏严重的反垄断行动可能是微软为避免反垄断审查而谨慎行事的结果，而谷歌当时太小，无法引起审查，并且在仍然可能的情况下采取行动扼杀谷歌可能会引发严重的反垄断审查和另一场公关马戏。这是鲍尔默接手的公司比竞争对手处境更困难的一个方面——微软的手脚被束缚了，而且可能真的被束缚了。微软可以并确实因采取行动而受到严厉批评，而当谷歌采取完全相同的行动时却会受到赞扬。</p>

<p>当我在微软时，对此有很多不满。一个有趣的例子是，2011年，谷歌公开指责微软不道德行为，媒体抓住这个机会将其作为微软行为不端的又一个例子。我在微软与之交谈的一些人对此感到不安，因为据他们说，微软是在注意到谷歌也这样做后才想到这样做的，但声誉需要很长时间才能改变，而盖茨担任CEO期间采取的行动大大降低了微软的行动能力。</p>

<p>鲍尔默上任时面临的另一个困难是微软激烈的内部政治。同样，作为从几乎一开始就加入微软的资深员工，他对此负有部分责任，但鲍尔默设法清除了最糟糕的捣乱者，这样纳德拉就不会继承如此困难的局面。如果我们看看为什么微软在鲍尔默任期内没有主导互联网，除了担心扼杀谷歌会引起公关反弹外，内部政治斗争扼杀了微软大多数最有前景的网络产品，并降低了其余大多数网络产品的吸引力和影响力。例如，微软在1997年就拥有<a href="https://x.com/danluu/status/790599349491212288" rel="noopener noreferrer">一个可以与谷歌文档竞争的产品</a>，比谷歌成立早一年，比谷歌收购<a href="https://x.com/danluu/status/1572839354434977795" rel="noopener noreferrer">Writely</a>早九年，但它因政治原因被扼杀了。<a href="https://www.patreon.com/posts/20571244" rel="noopener noreferrer">微软当然不是唯一一家有内部政治斗争的公司</a>，<a href="https://web.archive.org/web/20110911005705/https://www.bonkersworld.net/organizational-charts/" rel="noopener noreferrer">但它以比大多数公司更残酷的政治斗争而闻名</a>。</p>

<p>虽然鲍尔默在清理门户方面做得并不完美，但当我在微软询问因内部政治斗争而被搁置或取消的有前景的项目时，<a href="https://www.patreon.com/posts/22091116" rel="noopener noreferrer">最近最大的问题源头</a>都在鲍尔默手下被扫地出门，为纳德拉留下了一个运作更正常的公司。</p>

<h3 id="大局观">大局观</h3>

<p>退一步看大局，鲍尔默接手的是一家财务状况良好但受困于内外部政治的公司，以至于外界观察者认为该公司极有可能滑向无关紧要，导致像格雷厄姆著名的“微软已死”的预测，预计收入将在五到十年内下降。回顾过去，我们可以看到盖茨采取的行动限制了微软利用其垄断力量直接扼杀竞争对手的能力，但没有一个转折点是实现奇迹般扭转的。相反，微软继续在企业产品上执行得非常出色，并继续对未来进行合理的押注，以成功地取代在内部被视为长期死胡同的收入来源，即使它们将是利润丰厚的死胡同，如Windows和盒装（非订阅）软件。</p>

<p>与大多数处于这种境地的公司不同，微软愿意非常大力地补贴一系列赌注，领导层认为这些赌注可能为公司提供未来几十年的动力，如Windows Phone、必应、Azure、Xbox和HoloLens。从对这些赌注的内外部评论来看，你可以看到为什么公司很难用其成功的业务线来补贴新业务线，即使成功业务的前景已经明朗。人们批评这些赌注是会扼杀公司的愚蠢举动，说公司应该集中精力在其最盈利的业务上，如Windows。即使有<a href="https://danluu.com/bad-decisions/" rel="noopener noreferrer"></a>非常明确的<a href="https://danluu.com/talent/" rel="noopener noreferrer"></a>数据显示逆势而行是正确的事情，<a href="https://danluu.com/look-stupid/" rel="noopener noreferrer"></a>人们通常不会这样做，部分原因是当它没有成功时你会看起来像个傻瓜，但鲍尔默愿意在面对数十年的嘲笑时做出正确的赌注。</p>

<p>公司难以进行这些赌注的另一个原因是，公司通常无法推出与其核心业务截然不同的新东西。当谷歌又一个非收购的消费者产品失败时，每个人都认为理所当然——当然谷歌在那里失败了，他们是一家技术优先的公司，不擅长产品。但微软多次实现了这种转变并取得了成功。一次是Xbox。如果你看看三大游戏主机制造商，两家是历史悠久的硬件公司，一家是微软，一家学会了如何制造硬件的盒装软件公司。另一次是Azure。如果你看看三大云服务提供商，两家是从成立之初就存在的在线服务公司，一家是微软，一家学会了如何进入在线服务业务的盒装软件公司。其他拥有与硬件和在线服务不同核心业务线的公司看到了这些机会并尝试转型，但失败了。</p>

<p>如果你看看这里的转型过程，嘲笑微软很容易，就像嘲笑微软的企业销售宣传一样。Azure的核心团队来自Windows，所以在Azure的非常早期，他们几乎没有事故管理流程，在他们的第一次重大全球宕机期间，人们在走廊里走来走去问“Azure宕机了吗？”试图弄清楚该怎么办。Azure将在多年内继续发生重大全球宕机，同时学习如何发布相对可靠的软件，但<a href="https://x.com/danluu/status/1487228574608211969" rel="noopener noreferrer">他们能够很好地解决问题</a>，建立了一个价值万亿美元的业务。另一次，在Azure真正知道如何构建服务器之前，一位微软工程师打开了亚马逊的定价页面，注意到AWS的磁盘零售价比Azure配置磁盘的成本还要低。当我在微软时，Azure的一个大问题是数据中心建设速度不够快。人们开玩笑说最近招聘的大量销售人员效果太好，公司卖了太多的Azure，这可以说是事实，也是公司的真正紧急情况。在其他情况下，微软主要自己学习如何做，而在这种情况下，他们从亚马逊引进了一些非常资深的人员，他们在供应链和数据中心建设方面拥有深厚的专业知识。很容易说，当你遇到问题而竞争对手拥有正确的专业知识时，你应该聘请一些专家并听取他们的意见，但大多数公司在尝试这样做时都会失败。有时，公司没有意识到他们需要帮助，但更常见的是，他们引进了高级专业知识，但人们不听。很容易<abbr title="通常发生的情况是，旧势力忽视或拖延新引进的高级雇员，说他们不真正理解问题，你必须在这里待很长时间才能理解公司；随着每一次新招聘，这变得更容易，因为旧势力可以指出长长的失败名单作为不听从新外部招聘的理由">让一家公司的旧势力阻止引进高级外部专业知识的努力，尤其是在像微软这样充满纷争的公司，但领导层能够确保像这样的关键举措取得成功<sup id="fnref:N"><a href="#fn:N" rel="noopener noreferrer">3</a></sup>。</abbr></p>

<p>当我和谷歌工程师谈论Azure崛起时的Azure时，他们通常对Azure评价不高，会嘲笑像上述这样的问题，这对于那些成长为大型在线服务公司、在运营大规模服务、构建高效硬件和建设数据中心方面拥有深厚专业知识的公司来说似乎很滑稽，但尽管在技术、运营和文化上起步于一个非常深的坑，微软用Azure建立了一个价值万亿美元的业务部门。</p>

<p>并非所有的赌注都成功了，但如果我们看看批评者的评论，他们说微软注定要失败，因为它在补贴错误的赌注，或者更年轻的公司会超越它，那么，今天，微软的市值比谷歌高50%，是Meta的两倍。如果我们看看科技行业的更广泛历史，微软从1975年成立到今天一直保持着持续强劲的执行力，这近五十年的历程可以说是科技行业无与伦比的。英特尔成立的时间更长，但在世纪之交左右经历了非常严重的挫折，<a href="https://danluu.com/cpu-bugs/" rel="noopener noreferrer"></a>而且在过去十年里遇到了很多问题。IBM有着悠久的历史，但在其早期历史中规模并不大，例如，当T.J.沃森将计算-制表-记录公司更名为国际商用机器公司时，其收入仍远低于每年1000万美元（按通货膨胀调整后，约为每年1亿美元）。计算机在50年代开始变得重要，IBM对于科技公司来说已经很大了，但在1969年针对IBM提起的反垄断案一直拖延到1982年才以“缺乏依据”被撤销，这阻碍了公司和文化的某些方面，例如，为什么IBM的各种云努力都失败了，在90年代，公司濒临死亡，仅因杰斯特纳的扭转才得以生存。如果我们看看更早的拥有长期持续强劲执行力的公司，大多数已经消失，如DEC和Data General，或者像IBM和苹果那样经历了几乎终结公司的严重挫折。有些公司拥有同样长期的强劲执行力，比如甲骨文，但这些公司在扩展业务线方面远不如微软有效，因此，甲骨文的价值可能相当于两个必应。这使甲骨文成为全球第20大最有价值的上市公司，这当然不错，但远不如微软。</p>

<p>如果微软遭遇重大挫折，像英伟达、Meta或谷歌这样更年轻的公司可能会超越微软的业绩，但这不是鲍尔默的错，我们仍然必须承认鲍尔默是一位非常有效的CEO，不仅在赚钱方面，而且在设定愿景方面，为微软未来五十年的成功奠定了基础。</p>

<h3 id="附录微软在鲍尔默领导下的相关性">附录：微软在鲍尔默领导下的相关性</h3>

<p>除了上面提到的主要项目外，根据我的记忆，以下是格雷厄姆宣布微软已死后在鲍尔默领导下发生的一些有趣的事情：</p>

<ul>
<li>2007年：微软发布LINQ，至今按照<a href="https://www.scattered-thoughts.net/writing/against-sql/" rel="noopener noreferrer">从业者实际使用的标准</a>仍然相当不错</li>
<li>2011年：微软研究院的Sumit Gulwani发表“使用输入输出示例自动处理电子表格中的字符串处理”，十年后被评为最有影响力的POPL论文

<ul>
<li>这篇论文是关于使用程序综合进行电子表格“自动补全/推断”的</li>
<li>我不是专利的粉丝，但我猜测自动补全/推断在Excel中效果相当好而在谷歌文档中基本上不起作用的原因可能是微软拥有基于这项工作的专利</li>
</ul></li>
<li>2012年：微软发布TypeScript

<ul>
<li>这一定是本世纪发布的使用最广泛的编程语言，并且有理由成为有史以来使用最广泛的编程语言（只要你也不把TS的使用算作JS）</li>
</ul></li>
<li>2012年：微软Surface发布

<ul>
<li>自2022年帕诺斯·潘奈离开后，Surface系列的情况看起来不太好，即使在2022年这也可以被视为失败，但2022年这是一个年销售额70亿美元的业务线，这表明微软是多么庞大和成功——大多数公司都希望有一项业务能像一个失败的70亿美元/年业务那样成功</li>
</ul></li>
<li>2015年：微软发布vscode（在鲍尔默2014年任期结束后，但这项工作源自鲍尔默任期内的多项工作）

<ul>
<li>这似乎是当今程序员使用最广泛的编辑器，且优势巨大。当我几年前查看相关调查数据时，我对这一变化发生的速度感到震惊。vscode似乎达到了程序员编辑器主导地位的水平，这是<abbr title="除了非常早期，那时可以说只有一个程序员使用一样东西">前所未有的</abbr>。最接近的可能是Visual Studio在保罗宣布微软已死前十年，但由于实际上是Windows专用软件并且成本相当高，从未达到同样的市场份额水平</li>
<li>Heath Borders指出，2011年聘请的Erich Gamma在这方面影响力巨大</li>
</ul></li>
</ul>

<p>对微软财务成功的另一个回应是，微软在财务上很成功，但对时尚的程序员来说无关紧要，就像IBM一样。首先，四舍五入到最接近的必应，IBM可能价值零或一个必应。但即使我们撇开财务方面，只看每个价值1万亿美元的科技公司（苹果、英伟达、微软、谷歌、亚马逊和Meta）对程序员的影响，英伟达、苹果和微软都有很多程序员由于某种生态系统的依赖而依赖该公司（CUDA；iOS；.NET和Windows，后者仍然是许多大领域（如3A游戏）的首选平台）。</p>

<p>你可能会为大云服务商辩护，但我认为公司对AWS的依赖并不像一家严肃的英语消费类应用公司真的需要iOS应用或一家3A游戏公司必须在Windows上发布并在Windows上进行开发那样几乎是强制性的。</p>

<p>如果我们看看那些没有被固定在生态系统中的程序员，微软由于创建了vscode和TypeScript等工具似乎与许多程序员高度相关。我不会说它一定比亚马逊更相关，因为许多程序员使用AWS，但很难争论在鲍尔默主持下创建（包括许多其他东西）vscode和TypeScript的公司对程序员来说是无关紧要的。</p>

<h3 id="附录我对微软的失败赌注">附录：我对微软的失败赌注</h3>

<p>2015年加入微软后不久，我打赌德里克·丘会先达到1万亿美元市值。与大多数外部评论员不同，我同意微软所做的赌注，但当我看到微软当时的内部功能失调时，我认为这会给它们带来足够的问题，以至于谷歌会赢。这是错误的——微软先于谷歌达到1万亿美元市值，现在价值比谷歌高1万亿美元。</p>

<p>即使在一年后，我看到微软内部的情况以及微软销售的高效性和微软擅长发布企业产品的程度，并将此与谷歌的云执行和战略进行比较后，我认为我也不会打这个赌。但你可以说我犯了一个与外部评论员相当类似的错误，直到我详细了解了微软的运作方式。</p>

<p><i>感谢Laurence Tratt、Yossi Kreinin、Heath Borders、Justin Blank、Fabian Giesen、Justin Findlay、Matthew Thomas、Seshadri Mahalingam和Nam Nguyen的评论/修正/讨论</i></p>
<div>

<hr>

<ol>
<li id="fn:P">Fabian Giesen指出，除了鲍尔默的“销售员”声誉外，他的舞台形象也没给他带来好处，他说“他的舞台表现让人认为他很糟糕。但如果你不是白痴，你看到一个演员扮演麦克白，你不会假设他们在现实生活中杀了所有的朋友”
 <a href="#fnref:P" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:H"><p>这是Hacker News上关于辛诺夫斯基被解雇的报道的头号评论：</p>

<blockquote>
<p>真正应该被解雇的是史蒂夫·鲍尔默。从微软成立到大约世纪之交，他都很出色，当时他们制作和维护Windows垄断的战略运行得非常出色且极其盈利。然而，他生活在过去的环境中，认为他需要不惜一切代价保护Windows/Office垄断，而他和微软其他人都无法跟上周围其他人的创新步伐，因为他们有创新。</p>

<p>这种心态完全扼杀了微软的任何创新，因为他们在试图与谷歌、Facebook等竞争时，一只手被绑在背后。在史蒂夫·鲍尔默眼中，一切都必须回到销售Windows/Office许可证上，而这在他们的环境中不再奏效。</p>

<p>如果微软的工程师能够自由地制作最好的搜索引擎、最好的手机或最好的平板电脑，而不必担心这将如何导致维持他们的Windows和更重要Office收入流，那么我认为他们的产品会好几个数量级且更具创造性。</p>
</blockquote>

<p>这是错误的。当时，微软在大力补贴必应。就补贴可以归因于谁而言，合理的说法是大部分补贴来自Windows。同样，Azure是一个巨大的赌注，当时正从Windows产生的利润中获得大量补贴。鲍尔默领导下的微软战略基本上与这个评论所说的相反。</p>

<p>有趣的是，如果你在minimsft上看评论（其中许多是微软内部人士发表的），人们注意到了在Azure和在线服务等领域的巨大支出，但大多数认为这是错误的，微软需要专注于使Windows和Windows硬件（如Surface）变得出色。</p>

<p>基本上，无论人们认为鲍尔默在做什么，他们都说这是错误的，并且他应该做相反的事情。这意味着人们呼吁采取不同的行动，因为大多数微软外部的评论员实际上并不知道微软在做什么，但从评论针对鲍尔默而不是公司具体行动的方式来看，我们可以看出人们并非真的在预测任何特定行动，他们只是在嘲笑鲍尔默。</p>

<p>顺便说一句，Hacker News上的第二条评论说鲍尔默错过了过去5年科技领域最重要的事情，并且鲍尔默降低了云计算的重要性（如果你看资本支出或分配的人头，这实际上是当时微软最大的赌注）。第三条评论说“史蒂夫·鲍尔默本质上是个销售员，这就是为什么他能够在十年表现平平和战略失误中幸存下来：他一定与微软最大的企业客户有密切联系，如果他被解雇，那将是邀请这些客户重新评估他们对微软平台的承诺的信号。”，其余的顶级评论与鲍尔默无关。</p>
 <a href="#fnref:H" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:N">存在标准的阻挠新事物的企图，例如，当Azure希望Windows网络添加功能时，他们会得到诸如“我们会将其列入路线图”的回应，这被普遍理解为“我们比你强大，我们不必做你要求的事”，因此微软领导层将网络从Windows中剥离出来，将Windows网络放在Azure组织下，让Azure控制他们想要的网络功能。这种举措与在几乎每家公司改变公司重点的努力形成了对比。另一个极端的例子是<a href="https://www.patreon.com/posts/20571244" rel="noopener noreferrer">高通的服务器芯片业务。当该小组威胁要变得比移动芯片小组更盈利、更重要时</a>，移动小组在服务器小组能够大到足以自保之前将其扼杀。包括CEO在内的一些领导层支持公司的长期健康，因此支持服务器小组。那些人，包括CEO，都被从董事会中移除并解雇了。拥有足够支持来罢免CEO是不寻常的，但对于一个更典型的例子，看看<a href="https://x.com/danluu/status/790599349491212288" rel="noopener noreferrer">微软如何扼杀了其1997年版本的在线办公套件</a>。
 <a href="#fnref:N" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 18898 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/ballmer/</link>
      <guid isPermaLink="false">https://danluu.com/ballmer/</guid>
      <pubDate>Mon, 28 Oct 2024 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>不用知道任何词语，你在《行话》（Codenames）游戏中能有多厉害？</title>
      <description>[AI 摘要] 本文探讨了仅通过记忆《行话》游戏的布局卡，利用词语在棋盘上的位置信息而非含义来进行猜测的策略及其有效性。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文探讨了仅通过记忆《行话》游戏的布局卡，利用词语在棋盘上的位置信息而非含义来进行猜测的策略及其有效性。</div><p>大约八年前，我玩了一场<a href="https://amzn.to/4cgpzow" rel="noopener noreferrer">《行话》</a>游戏。当时的局势是，如果我们不能在自己的回合正确猜出所有剩余的词语，我们队几乎必输无疑。然而，根据给出的线索，我们无法做到这一点。虽然这本应是一个基于词语线索的猜词游戏，但一位队友提出，根据已选词语在棋盘上的物理布局，我们考虑的大多数可能性都会形成“太奇怪”的模式，我们应该根据位置来选择最后一个词。这招奏效了，我们赢了。</p>

<p></p><details>
<summary>[点击展开《行话》游戏规则解释（如果你不熟悉这款游戏）]</summary>
《行话》由两支队伍进行。游戏包含一个5x5的词语网格，每个词语被秘密地归属于{蓝队、红队、中立、刺客}之一。每队有一名“间谍主管”，他知道秘密词语&lt;-&gt;归属的映射关系。间谍主管的任务是给出单个词语的线索，让队友猜出哪些词属于己方队伍，同时避免猜到对方队伍的词语或刺客的词。在每个回合，间谍主管给出一个线索，队友们猜测与该线索相关的词语。游戏持续到一方的所有词语被猜出，或刺客的词被猜出（立即判负）。为了简化，这里省略了一些细节，但就本文目的而言，这个解释应该足够接近。如果你想了解更多，可以看<a href="https://www.youtube.com/watch?v=J8RWBooJivg" rel="noopener noreferrer">这个视频</a>或<a href="https://czechgames.com/files/rules/codenames-rules-en.pdf" rel="noopener noreferrer">官方规则</a>。
</details><p></p>

<p>自那以后，我一直在想，如果一个人只做了记忆游戏中全部40张布局卡这一件事，他会有多厉害。为了模拟这一点，我们将构建一个仅使用位置信息来玩的游戏机器人（你可能也称之为AI，但鉴于我们将讨论使用大语言模型/AI来编写这个机器人，为了区分，我们将使用“机器人”来指代这个自动玩《行话》的代理）。</p>

<p>当时，在我们那个幸运的猜测之后，我们查看了布局卡，确认了队友基于形状进行猜测的想法是正确的——他基于可能的物理布局正确判断出了概率最高的猜测。每张布局卡定义了哪些词语属于你的队伍，哪些属于对方队伍，并且为了限制成本，游戏只附带了40张卡（考虑旋转有160种配置）。我们的队友并没有记住这些卡（那样就能将可能性缩小到唯一的配置），但他玩了足够多的游戏，对常见和不常见的模式/簇群形成了直觉，使他能够对游戏发起这种侧信道攻击。例如，玩多了之后你可能会发现，没有哪张卡会让一个队在一行或一列拥有5个词，或者只有先手颜色的队伍才有4个连在一起的词，而且如果这种情况发生在边缘并且是蓝色的，那么第5个词一定属于红队；或者没有配置能让蓝色词语有6个相连（但红队有，一种是两连的旁边有四连的）。即使你没有有意识地使用这些信息，你也可能会对某些感觉“太奇怪”的模式产生潜意识的回避。</p>

<p>回到构建模拟某人花了几天记忆那40张卡的机器人这个想法，下面是一个你可以与之对战的简单机器人，它模拟了一个由这类玩家组成的队伍。通常玩游戏时，你需要提供线索，队伍去猜词语。但是，为了给你——人类玩家——最大的优势，我们将给你一个不切实际的巨大优势：假设你可以随时生成一个能让你队伍选择你想要的确切方块的线索，这通过让你点击任何你希望队伍猜测的方块来模拟。</p>

<p>默认情况下，你每回合还有<abbr title="如果你选择让队伍猜错，次数会更少">三次猜测机会</abbr>，这会让你远超我见过的<a href="https://danluu.com/p95-skill/" rel="noopener noreferrer">99%分位</a>的《行话》玩家。虽然好的玩家每回合通常能猜对三个或更多，但每回合平均猜对三个且零失误在大多数群体中都异常出色。你可以打开显示剩余匹配布局的功能，但如果你想模拟一个没有记住所有布局的人类玩家的体验，你可能想尝试在关闭显示的情况下玩几局。</p>

<p>如果在任何时候，你结束一个回合轮到机器人时，只有一种可能的布局匹配，机器人就能正确猜出它的所有词语并获胜。如果机器人能在猜出所有词语之前就开始猜测（无论是朴素地猜还是策略性地缩小搜索空间），或者即使有一个简单的启发式方法（比如当它推断出你下一回合就能赢时，就在可能的布局中随机猜一个），那么它会强大得多。但即使使用最朴素的“记忆布局”机器人，它也已经能击败我交给它的每个《行话》玩家（只要他们没有打开显示剩余匹配布局并使用机器人所能访问的相同知识）。</p>

<p></p><div id="outer-container"><abbr title="致Dan：JS代码因当前使用的旧版Hugo解析问题而从单独文件加载。更新到合理修复此问题的版本需要20多处改动，因为Hugo多年来引入了重大变更。如果你看到这个提示且Hugo已不再使用，你大概可以在此处内联JavaScript。">《行话》机器人的JS加载失败！</abbr></div><div id="game-container"></div>
<p></p>

<p>这个非常“傻”的机器人，只有在能猜出所有词时才开始猜，它比大多数《行话》玩家强得多<sup id="fnref:H"><a href="#fn:H" rel="noopener noreferrer">1</a></sup>。实际上，任何队伍里只要有一个人决定坐下来记住盒子里那40张初始状态卡的内容，他们基本上就能在每一局游戏中击败另一队。</p>

<p>现在我对这个问题的好奇心得到了满足，我认为这只是一个小问题，对游戏本身并非真正的困扰。因为猜词游戏通常并不需要被认真对待，而且大多数游戏如果被认真对待，或者人们玩得很多且不是想破坏游戏的话，最终都会变得有些漏洞。相对于其他猜词游戏，特别是流行的那些，《行话》在玩家开始使用侧信道攻击（无论是有意识还是无意识的）之前，具有更高的可重玩性。</p>

<p>那些词语集合有限的游戏（比如《One Word》或《禁忌词》）会发生什么情况呢？人们往往在玩过几局后，就会无意中记住那些“棘手”词语和词语联想。《行话》通过要求人们记忆组合数量庞大的词语联想（而不仅仅是线性数量的词语联想）来缓解这个问题。我们刚才讨论了这个问题，它出现在我们玩《行话》大约二十局之后，并且即使人们没有意识到棋盘形状在影响他们的游戏，它也可能在潜意识层面发生。但与其他猜词游戏出现的问题相比，这相对微妙。而且，如果真有人在意这个问题，他们可以使用数字随机发生器来设置棋盘，尽管我个人从未在一个对游戏认真到会在意这点的群体中玩过《行话》。</p>

<p><i> 感谢Josh Bleecher Snyder、David Turner、Won Chun、Laurence Tratt、Heath Borders、Spencer Griffin、Ian Henderson和Yossi Kreinin的评论/修正/讨论。</i></p>

<h3 id="appendix-writing-the-code-for-the-post">附录：为本文编写代码</h3>

<p>我尝试使用了两个不同的AI助手来为本文编写代码：<a href="https://storytell.ai/" rel="noopener noreferrer">Storytell</a>和<a href="https://www.cursor.com/" rel="noopener noreferrer">Cursor</a>。我没有像程序员那样使用它们，而是更像一个非程序员那样用它们来编写程序。总的来说，我发现AI助手在某些任务上表现得惊人地好，而在其他任务上则差得可笑。这里也是如此。</p>

<p>我基本上就是让它们写代码，然后运行看看是否有效，接着告诉助手哪里出错了，让它重写代码，直到代码看起来基本能用。即使以这种非常朴素的方式使用助手——我刻意不去理解代码，只追求得到可用的输出——我认为获得可运行代码所花的时间并不比完全凭自己手写整个程序多太多。我猜大概多花了两倍时间，但程序员的估算出了名的不准确，也可能实际花费的时间差不多。我对代码正确性的信心远不如我自己写的代码那样足，但我仍然觉得这相当令人印象深刻：你只需要给这些AI助手下提示，就能得到基本可用的代码，所用时间并不比程序员写代码多太多。这些工具当然比雇佣程序员便宜得多，而且如果你像程序员而不是一个朴素的提示者那样使用它们，你会更快地得到可用结果，因为你只需修正那些大部分正确的版本中的错误，而不是花大部分时间调整你的提示，以让AI消除一个对任何程序员来说都微不足道的调试错误。</p>

<p>我见过很多程序员谈论“AI”将永远无法取代程序员，理由是“要详细地指定程序以达到你想要的效果，你就是在编程”。如果用户必须在程序运行前正确地指定其工作方式，这将是一个相当有力的批评。但当用户可以像我们这里这样迭代时，这个批评就弱得多了。用户不需要是程序员就能观察到输出不正确，然后可以要求AI修正输出，重复这个过程直到输出看起来足够正确。一个软件的性能或正确性约束越严格，这种朴素的迭代方法效果就越差。幸运的是，对于想使用大语言模型生成代码的人来说，当今大多数生产环境中的软件在性能和正确性方面的约束都相当宽松。<a href="https://danluu.com/everything-is-broken/" rel="noopener noreferrer">人们基本上已经接受了软件有大量漏洞是常态，任何一周内遇到成百上千个软件缺陷都是正常的</a>，而且<a href="https://danluu.com/slow-device/" rel="noopener noreferrer">广泛使用的软件通常比高度优化的版本慢10万倍</a>。</p>

<p>一个稍近的类比是关于AI是否能在客服岗位取代人类的争论。即使这已经在发生，人们也声称这永远不可能，因为AI会犯人类不会犯的低级错误。<a href="https://danluu.com/customer-service/" rel="noopener noreferrer">但正如我们之前指出的，人类也经常犯同样的错误</a>。此外，即使AI客服差很多，只要性价比足够好，很多公司还是会选择更差但更便宜的选项。<a href="https://danluu.com/diseconomies-scale/" rel="noopener noreferrer">科技公司在消费级客服方面因臭名昭著地这样做而闻名</a>，但我们通常看到所有类型的公司都这样做，例如，当你拨打任何大公司甚至很多本地小企业的客服电话时，你很可能会被推入电话树或某种糟糕的自动语音识别系统。这些通常比最低薪的员工差很多，但成本却比让一个最低薪的员工接听每个电话并将你转接到正确的部门低好几个数量级，所以公司选择了电话树。</p>

<p>相关的问题不是“AI何时能让外行创造出比程序员更好的软件？”，而是“AI何时能让外行创造出像电话树和糟糕的语音识别对于客服那样好的软件？”而且，实际上，软件甚至不需要那么好，因为程序员比最低薪的客服人员更贵，但你可以每月花20美元使用这些工具。我不知道AI何时能取代一个称职的程序员，但如果最低标准是达到自动电话树系统在路由我电话方面的编程水平，我认为我们很快就能达到（如果还没达到的话）。而且，像客服一样，这不必是零和游戏。从电话树省下来的钱并非全部变成利润——一部分被用来雇佣处理其他任务的客服人员。</p>

<p>顺便提一下，我觉得我的经历中有点好笑的一点是，我尝试的两个平台（Storytell和Cursor）经常生成可以自动检查的错误结果，而在我指出结果不正确后，它们就会修复。以下是与其中一个平台的典型交互序列：</p>

<ul>
<li>我：请做X
</li><li>AI：[生成一些TypeScript代码和测试，但未能通过类型检查]
</li><li>我：这段代码没通过类型检查，你能修复吗？
</li><li>AI：[生成一些代码和测试，但测试执行失败]
</li><li>我：运行测试时出现[复制粘贴测试失败信息]的错误
</li><li>AI：[生成一些代码和测试，测试通过了，并且在一些基本的额外测试上似乎也能工作]
</li></ul>

<p>另一个有趣的交互是我陷入了一个循环，有几个不同的错误，让AI修复一个错误会重新引入其他错误，即使我明确要求AI不要重新引入这些错误。与那些日复一日使用这类工具的人相比，我的经验非常少（我只是偶尔摆弄一下，看看它们进展如何），我预计有更多提示经验的人能更好地指定提示来更快地跳出这种循环。</p>

<p>但即便如此，如果这些环境中有一个能实际执行代码的执行环境，体验会更好，这样它们就能自动修复这类可修复的问题，并且当朴素的重新提示（“那错了并导致了XYZ，请修复”）不起作用时，它们能知道输出已知是错误的。</p>

<p>我问了Josh Bleecher Snyder（他对这个领域的了解远胜于我，无论是技术层面还是产品层面），为什么这些工具中没有一个具备这样的环境，而且几乎没有任何公司使用这样的环境进行训练或微调。他的回答是，这个领域的几乎每个人都相信了<a href="http://www.incompleteideas.net/IncIdeas/BitterLesson.html" rel="noopener noreferrer">《苦涩的教训》</a>，没有在研究这类平凡的改进。其理念是，建立这样环境所需的枯燥工程工作将被某种根本性的进步所淘汰，所以花时间做这些只能带来渐进收益的事情是浪费时间。Sam Altman甚至建议那些依赖OpenAI API的公司创始人假设会有巨大进步，并据此构建公司，因为不这样做的公司会被即将出现的巨幅进步所淘汰。从与该领域创始人和风险投资家的讨论来看，几乎每个人都把这个放在心上了。</p>

<p>我已经有11年没有做过任何严肃的机器学习相关工作了，所以我的观点和任何其他外行一样不值钱。但如果在GPT-3时代有人对这样一个平凡的系统做了逆向投资，现在看来它当时会很有用，对于今天的模型也同样有用，无论是用于训练/微调工作还是为用户生成更好的输出。但我猜相关的问题是：今天尝试构建这样一个平凡的系统（对于该领域的人来说，这是对进步的逆向投资）是否有意义？大型AI实验室据说雇佣了很多低薪的海外承包商来标注数据，但如果你想标注编程示例，每标注一个，一个能产生标准正确结果的环境会比雇人尝试标注更便宜，除非你只需要极少量的标签。在万亿美元甚至500亿美元公司的层面上，作为投资组合的一部分来做这个押注似乎是合理的。如果我想创业并押注，那这样做有意义吗？也许如果你把所有鸡蛋放在一个篮子里，答案不那么明显，但即使如此，也许有充分的理由，因为几乎整个领域都在押注别的东西。如果逆向的一方是正确的，竞争就很少，这有点类似于<a href="https://danluu.com/talent/" rel="noopener noreferrer">我们之前关于逆向招聘的讨论</a>。</p>

<h3 id="appendix-the-spirit-of-the-game-vs-playing-to-win">附录：游戏精神与为赢而玩</h3>

<p>就个人而言，当我在游戏中遇到侧信道攻击，或者一个如果认真玩就完全崩坏的游戏（如《Perfect Words》）时，我认为尽量避免“攻击”游戏是合理的。我认为这在《行话》中是某种程度上不可能的，因为人们会形成潜意识的联想（我注意到有人在第一回合就猜一个额外的词只是为了好玩，而且成功率很高——假设他们没有作弊，我相信他们没有作弊，这种成功率强烈暗示了他们使用了某种侧信道信息。这不一定是来自卡片的位置信息，可能像下意识注意到间谍主管在专注地看什么那样简单。</p>

<p><a href="https://mastodon.social/@danluu/110544419353766175" rel="noopener noreferrer">Dave Sirlin称任何不利用任何合法可能性去赢的人都是傻瓜（他贬低地称这类人为“菜鸟”）</a>（他说你应该使用作弊来赢，比如在第一人称射击游戏中使用地图黑客，只要锦标赛组织者没有禁止这种做法，而且锦标赛应该明确列出禁止的内容，避免笼统的“不要做坏事”规则）。我认为人们应该以他们觉得有趣的方式玩游戏，并找到喜欢同样游戏方式的群体。如果Dave觉得记住任意信息来赢得所有这些游戏很有趣，他应该这样做。我（用Dave Sirlin的话说）玩得像个“菜鸟”的原因是，对于这里讨论的游戏，如果认真玩，游戏通常严重崩坏，而我个人不觉得它们崩坏的方式有什么乐趣。在某些情况下，比如《Perfect Words》，游戏轻而易举地就崩坏了，我觉得赢得一个轻而易举崩坏的游戏很无聊。在其他情况下，比如《行话》，游戏可以通过花几个小时记忆一些任意信息来破坏。对我来说，花几个小时记忆40种可能的《行话》布局卡，看起来是一种既无趣又无生产力的时间浪费，使其成为一个完全毫无意义的活动。</p>

<h3 id="appendix-word-games-you-might-like">附录：你可能喜欢的词语游戏</h3>

<p>如果你喜欢猜词游戏，以下是一些同类游戏的推荐，类似于<a href="https://danluu.com/programming-books/" rel="noopener noreferrer">编程书籍推荐列表</a>和<a href="https://danluu.com/programming-blogs/" rel="noopener noreferrer">编程博客推荐列表</a>，目的是指出人们倾向于喜欢或不喜欢的事物的属性（不同于大多数评论，它们倾向于评价某物是“好”还是“坏”）。为了限制列表长度，这里只包含猜词游戏（通常关于词语的含义），不包括那些关于操作词语机制的游戏，例如<a href="https://amzn.to/3WCbrAc" rel="noopener noreferrer">《Banagrams》</a>、<a href="https://amzn.to/4dbaQwA" rel="noopener noreferrer">《Scrabble》</a>或<a href="https://en.wikipedia.org/wiki/Anagrams_(game)" rel="noopener noreferrer">《Anagrams》</a>，也不包括那些关于视觉表现与词语映射的游戏，例如<a href="https://amzn.to/4ddQqmy" rel="noopener noreferrer">《Dixit》</a>或<a href="https://amzn.to/3WXGOH0" rel="noopener noreferrer">《行话：图画版》</a>。</p>

<p>同样由于篇幅原因，我不会讨论那些适用于列表中所有或几乎所有游戏的、人们不喜欢游戏的原因。例如，有人可能因为是词语游戏而不喜欢某个游戏，但为每个游戏都指出这点意义不大。同样，许多人根据“重量”选择游戏，并因为觉得它们“轻”而非“重”而不喜欢几乎所有的词语游戏，但所有这些游戏都被认为相当轻量，所以讨论这一点没有意义（但如果你想要一款轻量且紧张的词语游戏，在下面的列表中，你可以考虑《Montage》或《Decrypto》；在未详细讨论的游戏中，《Scrabble》或《Anagrams》是不错的选择，后者是我玩过的最残酷的词语游戏，而且遥遥领先）。</p>

<h4 id="taboo-https-amzn-to-3sf1aij"><a href="https://amzn.to/3SF1AIJ" rel="noopener noreferrer">《禁忌词》（Taboo）</a></h4>

<p>一款猜词游戏，你需要快速给出线索，让队友猜出你拥有的词语，每个词语都附有5个你在提示时不允许说的禁用词。</p>

<p>一款有趣、轻松的游戏，有两个问题降低了其可重玩性：</p>

<ul>
<li>因为每个被提示的词语都是完全独立的，一旦你的游戏小组把卡组玩过一两遍，每个人都认识每个词，游戏就变得极其简单；在我第一次玩这个游戏的小组里，我觉得玩了两遍后就发生了这种情况。
</li><li>甚至在那之前，当人们意识到可以通过稍微绕弯子描述来轻松提示任何词语时，游戏就变得相当机械，甚至还没等到因为玩得太多而无意中记住词语。</li>
</ul>

<p>当人们不喜欢这个游戏时，他们通常不喜欢这种快速连发游戏中巨大的时间压力。</p>

<h4 id="just-one-https-amzn-to-4dbgm8s"><a href="https://www.amazon.com/Just-One-R Ludovic-Blanchard/dp/B01N5M6N53" rel="noopener noreferrer">《One Word》（Just One）</a></h4>

<p>一款类似于《禁忌词》的猜词游戏，你需要让队伍猜出一个词，但不同的是，对于每个你想提示的词，没有固定的禁用词列表，禁用词由你的队伍动态生成（每个人提示一个词，任何被给出超过一次的线索都会被划掉）。</p>

<p>禁用词是通过与队友互动生成的，这使得这款游戏比《禁忌词》有高得多的可重玩性。然而，有限的词表最终还是会遇到同样的问题，在玩了大约20-40局后，我的游戏小组就能认出这些词，并且几乎每个词都有一个好的提示方法。</p>

<p>按照规则，这款游戏真的是为5人以上设计的，如果只有4人玩会变得非常容易，但没有理由你们不能在4人时按5人游戏的规则玩。</p>

<p>一个常见的抱怨是，考虑到游戏成本（建议零售价30美元 vs 《行话》的20美元），游戏的实体组件质量廉价且低劣。另一个抱怨是词语难度差异很大，有些似乎是偶然的。例如，单词“grotto”被包含在内，而且如果有人没见过这个词，就相当难提示，这似乎是因为游戏是在法语中开发的，在那里提示grotto相当直接。</p>

<h4 id="perfect-words-https-www-amazon-co-uk-tiki-editions-perfect-words-intergenerational-dp-b0chn8xp1f">Perfect Words(<a href="https://www.amazon.co.uk/TIKI-Editions-Perfect-Words-Intergenerational/dp/B0CHN8XP1F" rel="noopener noreferrer">亚马逊链接</a>)</h4>

<p>一款猜词游戏，队伍合作构建线索，目标是从每组线索中让整个队伍就一个词（可以是任何词，只要大家同意）达成一致。</p>

<p>核心游戏——试图想出一组能生成代表哪个词共识的词语——作为与《One Word》这类相反游戏的补充很不错，但实际执行的规则似乎有严重缺陷。就好像游戏设计师不玩游戏，也没有让玩游戏的人来测试它。这个游戏在第一或第二次玩时就相当容易破坏，你必须故意玩坏游戏的“机制”部分才能让它变得有趣。</p>

<h4 id="montage-https-amzn-to-46gkfcv"><a href="https://amzn.to/46GkfcV" rel="noopener noreferrer">《Montage》</a></h4>

<p>一款2对2的词语游戏（如果你想要更多玩家，也可以像《行话》那样玩）。每队中，玩家在固定的时间段内轮流给出线索和猜词。当前棋盘状态对某些位置必须出现的字母有一定约束。提示者需要生成一个能让猜词者猜出符合约束的己方词语的线索，但线索不能太明显，因为如果对方的两个人都在提示者的搭档之前猜出了这个词，对方就赢得了这个词。</p>

<p>这可能是这个列表中难度最高的游戏？我见过的大多数新玩家在第一次尝试时，都未能在自己回合内给出有效的线索（一个好玩家如果搭档能比对手更快理解思路，每回合可能至少成功提示5样东西）。这也可能是这个列表中最奖励拥有大词汇量的游戏。它是列表中唯一一个运用字母构成知识（像《Scrabble》）有用的游戏。</p>

<p>只要你不与固定的搭档一起玩并依赖“秘密”协议或共享知识，这种直接对抗的猜测性质赋予了这款游戏非常高的可重玩性，至少与列表中其他任何游戏一样高。</p>

<p>像《Perfect Words》一样，如果你喜欢那种类型，核心词语游戏是有趣的，但围绕核心游戏设计的游戏规则似乎没有经过很好的思考，很容易被利用。这里没有《Perfect Words》那么糟，但你仍然需要避免试图获胜才能让这款游戏真正好玩。</p>

<p>当我不喜欢这款游戏时，通常是因为他们觉得游戏太难，或者不喜欢输——微小的技能差异会导致比列表中其他游戏更大的结果差异，所以新玩家应该预料到会输得很惨，除非他们的对手故意让步（规则中没有内置）或者他们从玩其他游戏中获得了词语游戏的能力。我自己不玩很多词语游戏，尤其不玩《Scrabble》或《Anagrams》这类“严肃”词语游戏，所以我玩这款游戏时通常被虐得很惨，这对我来说正是魅力所在，但这恰恰是很多人不喜欢这款游戏的原因。</p>

<h4 id="word-blur">Word Blur</h4>

<p>一款猜词游戏，约束条件是需要用撒在你面前桌子上的900个小词语方块来组成线索。</p>

<p>我只玩过几次，因为我不知道附近谁能弄到一份，但它看起来至少和这个列表中的任何游戏一样有可重玩性。这款游戏的最大缺点是它已绝版十多年，而且出了名的难弄到，尽管制作一个仿制品似乎并不太难。</p>

<p>当人们不喜欢这款游戏时，似乎通常是因为他们不喜欢游戏的核心玩法——看着一堆词语方块并用它们来描述，有些人觉得这让人应接不暇。</p>

<p>觉得Word Blur太复杂的人可以尝试仿品Word Slam，它更容易上手也更容易弄到（因为它不像前者那样是邪典热门，尽管它似乎也绝版了）。Word Slam只有105个词，而且词语是分类的，感觉没那么混乱。</p>

<h4 id="codenames-https-amzn-to-4fvmju7"><a href="https://amzn.to/4fvMJu7" rel="noopener noreferrer">《行话》（Codenames）</a></h4>

<p>除了文章中已经提到的内容，没什么可补充的，除了人们不喜欢这款游戏的常见原因。</p>

<p>一个大声说话的人可以在每队中主导游戏，程度超过这个列表中的任何其他游戏（除了《行话：双人版》）。虽然游戏附带计时器，但很少使用（规则基本上暗示你不应该使用计时器），所以另一个常见的抱怨是，当和行动缓慢的人一起玩时，游戏会无限期拖延下去，而且除非你是间谍主管，在对方回合时没什么有用的事可做，导致游戏有长时间的无聊停顿。</p>

<h4 id="codenames-duet-https-amzn-to-4chnv6j"><a href="https://amzn.to/4chnV6j" rel="noopener noreferrer">《行话：双人版》（Codenames: Duet）</a></h4>

<p>虽然这款游戏设计为《行话》的双人合作版本，但我只在超过两人（通常是4-5人）的情况下玩过，只要你不介意讨论必须以半秘密的方式进行，这效果也很好。</p>

<p>在可重玩性方面，《行话：双人版》与《行话》大致处于相同领域，优缺点也差不多。</p>

<h4 id="decrypto-https-amzn-to-3aopdaw"><a href="https://amzn.to/3AoPDAw" rel="noopener noreferrer">《Decrypto》</a></h4>

<p>我不打算描述这个游戏，因为我见过的每次直接解释玩法的尝试，在新玩家玩一两轮之前都未能让他们理解。但概念上，每队轮流转换谁给出线索，目标是让你队的人正确猜出哪个线索映射到哪个词，同时让对方队伍猜测失败。猜测的队伍有额外信息，因为他们知道词语是什么，所以更容易生成正确的映射。然而，猜测队伍生成的映射集合对“解密”队是可见的，所以他们可能知道谜语词是用“林肯”和“帽商”提示的，从而推断出这个词是“帽子”，让他们能在下一个线索时正确猜出映射。</p>

<p>我还没玩够这款游戏，无法判断它的可重玩性有多高。有可能非常高，也有可能人们会找出技巧，让“解密”队几乎不可能猜出映射。我看到的一个主要缺点是，当与随机玩家群体一起玩时，游戏经常由哪队有最弱玩家决定（我每次看随机群体玩都发生了这种情况），这与很多团队和合作游戏中最强玩家主导游戏的问题相反。好玩家很难做出制胜动作，但坏玩家很容易做出致败动作，所以与非专家玩家一起玩时，哪队有最差玩家，哪队就会输。</p>

<h4 id="person-do-thing-https-persondothing-com"><a href="https://persondothing.com/" rel="noopener noreferrer">Person Do Thing</a></h4>

<p>David Turner说：</p>

<blockquote>
<p>Person Do Thing就像《禁忌词》，但不是禁用词列表，而是一个允许词列表。四十个基本词总是允许的，并且（如果你想）每个秘密词有三个额外的允许词。像《禁忌词》一样，出题者可以回应猜测——但只能使用允许的词。因为只允许使用很少的词，这需要大量创造力来给出好的线索……值得玩几次，但我上次查看时他们的词表很小。</p>
<p>我猜如果一群经常玩的人可能会发展出一些约定，例如“像人但不是想大的”来指“动物”。我听说过在《Concept》游戏中发生过这种情况：一个小组有约定，红、白、蓝和地方指的是拥有这些国旗颜色的国家，额外的修饰词则指定具体是哪个：水代表英国，冷代表俄罗斯，食物代表法国，枪代表美国。我认为需要相当多的这类约定才能让游戏明显变简单。</p>
</blockquote>

<h4 id="semantle-https-semantle-com"><a href="https://semantle.com/" rel="noopener noreferrer">《Semantle》</a></h4>

<p>像Wordle，但关于词语的含义，基于word2vec算法。最初设计为单人游戏，也适合合作游戏。</p>

<p>虽然我确信有人喜欢一遍又一遍地玩这个游戏，但对大多数人来说（我认识的人里几乎没人玩超过40局），我觉得可重玩性相当低。玩了一会儿并想出如何通过猜词快速缩小搜索范围后，玩游戏开始感觉有点机械。</p>

<p>我交谈过的大多数不喜欢这个游戏的人，是因为他们无法建立起关于正在发生什么的心智模型，使得词语相似度分数看起来像随机的胡言乱语。</p>
<div>

<hr>

<ol>
<li id="fn:H">如果你觉得这个模式太简单，你能准确地让队伍每回合猜出任何三个你想要的方块，并且对存在哪些模式有足够的直觉，通常能避免被AI击败，你可以试试AI被允许每回合猜一个词的模式，然后如果它猜对的一个词足以将搜索范围缩小到唯一的可能布局，它就会通过猜出其余词语获胜。一般来说，如果你猜三个，这就缩小了足够的范围，让AI能用一个猜测获胜（在游戏中，AI会给出一个“无限”线索
<a href="#fnref:H" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 15357 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/codenames/</link>
      <guid isPermaLink="false">https://danluu.com/codenames/</guid>
      <pubDate>Sun, 11 Aug 2024 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>FTC在Google反垄断调查中的误判</title>
      <description>[AI 摘要] 本文批评FTC在2011-2012年调查谷歌反垄断时，其经济局备忘录对科技行业存在严重误解，导致错误地终止了调查。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文批评FTC在2011-2012年调查谷歌反垄断时，其经济局备忘录对科技行业存在严重误解，导致错误地终止了调查。</div><p>2011-2012年间，美国联邦贸易委员会（FTC）曾调查是否对谷歌提起反垄断诉讼。FTC最终决定终止调查，此后十年间，公众对调查细节知之甚少。直到Politico公布了调查期间312页的内部备忘录，人们才得以窥见内情。作为一名科技行业从业者，在阅读这些备忘录时，最令人震惊的是主张终止调查的一方如何反复显示出对科技行业基本认知的缺乏，而高层管理人员的备忘录中对此毫无察觉。</p>

<p>如果你通常不关注监管机构和立法者对科技行业的言论，阅读这些决策形成过程的内部备忘录（或任何其他行业）会令人震惊，因为显然，这些决策是在对相关行业几乎毫无了解的情况下做出的。</p>

<p>FTC内部，竞争局（BC）主张应提起反垄断诉讼，而经济局（BE）则主张终止调查。BC的论点相当有力。理性的人可能对证据是否足以支持提起诉讼持不同意见，但一个反垄断的理性人士必须承认BC备忘录中的反垄断指控至少是站得住脚的。而BE的反对方论点则站不住脚。BE备忘录的核心部分存在重大错误。为了让BE备忘录显得可信，读者必须对科技行业存在巨大且显著的认知空白。如果FTC内部曾就BE备忘录中的错误进行过讨论，公开文件中没有任何迹象表明这一点。根据现有证据，似乎无人注意到BE备忘录的错误。来自主任及其他高层的公开备忘录显示，他们给予BE备忘录的重视程度至少与BC备忘录相当，这表明FTC领导层（至少在其备忘录被公开的人员中）对科技行业存在认知差距。</p>

<h2 id="brief-summary">简要概述</h2>

<p>由于BE备忘录实际上是对BC备忘录的反驳，我们将从BC备忘录中的论点开始。以下要点总结了BC备忘录执行摘要中的要点，大致概括了BC的论点：</p>

<ul>
<li>谷歌是占主导地位的搜索引擎和搜索广告销售商</li>
<li>本备忘录讨论了五个领域中的四个反竞争行为；移动领域在补充备忘录中</li>
<li>谷歌在美国的横向搜索、搜索广告、联合搜索和搜索广告领域拥有垄断力量</li>
<li>关于谷歌是否非法优待自身内容而贬低竞争对手的问题，我们不建议FTC继续追诉；这是一个艰难的抉择，且判例法不利于反竞争的产品设计，谷歌的效率理由充分，且对用户有一定益处</li>
<li>关于谷歌是否非法抓取垂直竞争对手的内容以改进其自身垂直产品，建议依据《谢尔曼法》第2条将其认定为有条件的拒绝交易
<ul>
<li>先前的自愿交易是互惠互利的</li>
<li>威胁将竞争对手内容从通用搜索中移除，旨在胁迫竞争对手允许谷歌将其内容用于谷歌的垂直产品</li>
<li>自然且可预见的效果是削弱垂直网站的研发积极性</li>
</ul></li>
<li>关于限制广告活动自动跨平台管理的反竞争合同条款，建议依据第2条予以谴责
<ul>
<li>这些条款限制了广告主利用自身数据的能力，减少了创新，并增加了广告主和第三方企业的交易成本</li>
<li>同时也降低了谷歌在搜索和搜索广告领域竞争对手的质量</li>
<li>谷歌的效率理由看似是托词</li>
</ul></li>
<li>关于与网站签订的反竞争排他性联合搜索和搜索广告协议，建议依据第2条对谷歌予以谴责
<ul>
<li>对发布商的反竞争影响虽有限，但剥夺了竞争对手的规模优势，对主要竞争对手（必应）具有竞争意义，并且长期构成重大进入壁垒</li>
<li>谷歌的效率理由总体上缺乏说服力</li>
</ul></li>
<li>可能的补救措施
<ul>
<li>抓取行为
<ul>
<li>可要求谷歌允许选择退出其垂直属性中的摘要（评论、评分），同时保留网页搜索或主搜索结果页面上的通用搜索摘要</li>
<li>可要求谷歌限制使用从网页搜索结果中索引的内容</li>
</ul></li>
<li>广告活动管理限制
<ul>
<li>可要求谷歌从许可协议中删除有问题的合同条款</li>
</ul></li>
<li>排他性联合协议
<ul>
<li>可禁止谷歌与搜索联合合作伙伴签订排他性搜索协议，并要求放宽联合合作伙伴使用竞争对手搜索广告的限制</li>
</ul></li>
</ul></li>
<li>案件存在一些风险，摘要中未提及，例如谷歌可以辩称微软最高效的分发渠道是bing.com，且微软可能获得的任何规模增长对其竞争地位影响不大</li>
<li>[BC] 工作人员认为谷歌的行为已经并将继续对消费者以及在线搜索和广告领域的创新造成实际损害。</li>
</ul>

<p>在关于移动的补充备忘录中，BC工作人员认为谷歌通过独家协议主导了移动搜索，且当时移动搜索增长迅速。BC工作人员声称，根据谷歌内部文件，2011年移动搜索占搜索总量的比例从9.5%增长到17.3%，并且谷歌和微软的内部文件都表明，预计移动搜索将在不久的将来超过桌面搜索。与桌面搜索的案例一样，BC工作人员使用谷歌单方面降低收入分成的能力作为其拥有垄断力量并能主导条款的证据，并引用了谷歌领导层对此的明确说法。</p>

<p>BC工作人员承认谷歌的许多行为对消费者有益，但权衡了反竞争策略的危害，表示：</p>
<blockquote>
<p>证据描绘了一家公司的复杂形象：一方面致力于通过提供最佳用户体验来维持其市场份额的整体目标，同时又从事损害众多垂直竞争对手的策略，这可能帮助巩固了谷歌在搜索和搜索广告领域的垄断力量。</p>
</blockquote>

<p>BE工作人员强烈不同意BC工作人员的观点。BE工作人员也认为谷歌的许多行为对消费者有益，但在涉及危害时，几乎在每种情况下，BE工作人员都辩称市场不重要、不是一个独立的市场，或者市场是竞争性的，谷歌的行为是促进竞争而非反竞争。</p>

<h2 id="common-errors">常见错误</h2>

<p>至少在Politico提供的文件中，BE工作人员通常回避直接回应BC工作人员的论点和数据。例如，除了辩称谷歌的协议和独家性（就协议是排他性而言）是促进竞争的，并且阻止此类协议可能对市场产生重大负面影响外，他们还认为移动市场很小且不重要。BE备忘录认为移动市场仅占市场的8%，并且，<abbr title="BE工作人员承认移动市场增长迅速，但只是轻描淡写，并未暗示移动市场现在或很快会变得重要">虽然增长迅速</abbr>，但不重要，因为它只占“总查询量的很小一部分，搜索广告收入的比例更小”。他们还声称移动市场存在激烈竞争，因为除了苹果，还有黑莓和Windows Mobile。从FTC调查开始到备忘录撰写期间，黑莓的市场份额从约14%下降到约6%，这是长期下降趋势的一部分，且无改变迹象。Windows Mobile的下降幅度较小，从约6%降至约4%，但在一个具有强大网络效应的市场中，BE工作人员为何认为这些市场份额低且下降的平台将在未来提供激烈的竞争，这令人费解。</p>

<p>当BE备忘录的作者做出预测时，他们似乎有一种预测与事实相反的结果的天赋。为了做到这一点，BE备忘录的作者采取了与当时普遍共识相反的立场。另一个例子是，他们暗示搜索市场存在激烈竞争，并且预计在不采取反垄断行动的情况下这种竞争将持续。他们对此的证据是雅虎和必应在美国拥有合计“稳定”的30%市场份额，且自雅虎-必应联盟宣布以来，查询量增长速度快于谷歌。BE备忘录的作者甚至更进一步，声称微软的查询量增长速度快于谷歌，并且微软+雅虎按搜索MAU（月活跃用户）计算的总市场份额高于谷歌。</p>

<p>BE备忘录关于雅虎和必应提供激烈且稳定竞争的论点忽略了运行搜索引擎的固定成本如此之高，以及实现盈利所需的规模如此之大，以至于雅虎实际上退出了搜索领域，将搜索外包给必应。微软每年补贴必应20亿美元，这是一项在大多数科技界观察人士看来不会成功的战略举措。当时，人们有理由认为，如果微软停止大力补贴必应，其市场份额将大幅下降，而事实正是如此——在反垄断行动未被采取后，微软决定将资金转移到其他回报率更高的项目。如今的估算显示，谷歌在美国的市场份额为86%至90%，全球范围的估算通常更高一些。</p>

<p>关于一些更夸张的说法，例如微软和雅虎的活跃搜索用户总和超过谷歌，以及微软查询量（因此搜索市场份额）增长快于谷歌，他们使用的是comScore的数据。关于这一点有几个奇怪之处。</p>

<p>首先，作者挑选数据以呈现最大化微软市场份额的数字。当comScore数据显示微软市场份额相对较低时（如在联合搜索中），BE备忘录的作者解释说不应使用comScore数据，因为它不准确。然而，当comScore数据表面上不切实际且显示微软市场份额大得不合常理或增长速度快得不合常理时，BE备忘录的作者却依赖这些数据，而不解释为何依赖这个他们声称不可靠的来源。</p>

<p>使用这些数据，BE备忘录基本上认为，因为许多用户偶尔使用雅虎和必应，用户显然可以使用雅虎和必应，并且即使用户每月使用一次雅虎或必应而使用谷歌一千次，转换壁垒也不显著。根据与从事驱动增长的产品改进工作的人员共事和交谈的经验，压倒性的共识是，<abbr title="有些情况下这不成立，但很难证明2012年的搜索市场总体上属于这种情况">将轻度参与、作为MAU勉强注册的用户转化为定期使用产品的重度参与用户通常非常困难</abbr>，这通常被认为比将全新用户转化为重度参与用户更难。就像Boies关于rangeCheck的论点一样，很容易看出这种推理对不懂技术的外行人来说似乎合理，但该论点读起来像是外行人会写的东西。</p>

<p>尽管BE工作人员备忘录读起来像是对BC工作人员备忘录要点的反驳，但缺乏对事实和论点的直接交锋，意味着只读其中一份备忘录的读者会与读另一份的读者产生截然不同的印象。例如，在移动搜索的重要性方面，天真地只读BC备忘录的读者会认为移动搜索非常重要，可能是最重要的，而天真地只读BE备忘录的读者则会认为移动搜索不重要，并且在可预见的未来将继续不重要。</p>

<p>Politico还公布了两位主任的备忘录，他们权衡了BC和BE工作人员的论点。两位主任都更倾向于BE备忘录而非BC备忘录，一位非常倾向，另一位较为倾向。在出现分歧时，例如移动搜索在近期的重要性，所提供的备忘录中没有任何证据表明曾尝试确定谁是正确的，或者我们正在讨论的错误是否被注意到。处理这些分歧最接近的情况是一些评论，赞扬双方工作人员完成了出色的工作，以一种可以说是“公平公正”的方式，例如“BC和BE工作人员在这项复杂的调查中表现出色。各自局的备忘录清楚地表明，在这四个方面提起诉讼的理由是接近的……”。如果可以推断的话，似乎BE备忘录中的推理和事实至少得到了与BC备忘录同等甚至更多的重视，尽管BE备忘录的大部分论据对了解科技行业的人来说显得极不可信。</p>

<p>例如，在移动搜索的重要性方面，我恰好在这些备忘录撰写后不久在谷歌工作，当时在谷歌，他们已经转向“移动优先”策略，因为大家都明白移动将成为未来最重要的市场。当时其他大型科技公司也理解这一点，而且理解的时间比这些备忘录的日期还要早。许多消费者当时并不理解这一点，那些为了统一桌面和移动体验而牺牲桌面体验的设计改版是当时常见的投诉。但如果你查看相关数据或与大公司的人交谈，就会清楚，从商业角度来看，专注于移动并处理因此可能在桌面端引发的后果是有意义的，只要这能提高移动开发的速度。</p>

<p>BC和BE工作人员备忘录都广泛引用了对许多科技公司的访谈，包括所有“<abbr title="当然这个词当时还没有使用">超大规模企业</abbr>”。奇怪的是，有人可以获得所有这些公司的内部文件以及访谈记录，然后还能论证移动在当时并不重要。而且，根据我们目前从这些备忘录中所能了解的情况，主任们表面上接受双方的论点，然后认为BE工作人员的论点与BC工作人员的论点同样具有说服力或更具说服力，这也令人费解。</p>

<p>这是我们在BC和BE工作人员备忘录之间反复看到的一类错误，即为了论证一个知情观察者明显看出不实的情况而过度拉伸数据。在大多数情况下，是BE工作人员尽可能地拉伸数据，将站不住脚的立场尽可能地推进，但也有一些BC工作人员论证过于牵强的实例。</p>

<p>我们在BE备忘录中反复看到的另一类错误，是基于一个<abbr title="特别是熟悉行业业务或产品方面的人">大多数业内人士</abbr>都会认为明显错误的世界模型，然后在此基础上进行推论。一个例子是讨论垂直竞争对手如Yelp和TripAdvisor是否因BC工作人员所称的反竞争行为而受到或将受到显著损害。BE工作人员除了辩称谷歌的行为实际上是促进竞争而非反竞争外，还辩称谷歌不可能对垂直竞争对手造成重大损害，因为谷歌为其带来的流量很小，仅占其总流量的10%至20%，甚至说“谷歌对本地网站的流量影响非常小且不具统计显著性”。尽管BE工作人员没有详细说明他们对此业务运作的模型，但他们似乎<abbr title="或相信等同的东西">相信</abbr>市场基本上是静态的。如果谷歌将Yelp从其列表中移除（如果谷歌不被允许将Yelp的数据整合到其自身的垂直产品中，谷歌曾威胁要这样做），或者降低Yelp的排名以优先考虑谷歌自己的结果，从长远来看，最多只会使Yelp的流量减少10%至20%，因为只有10%至20%的流量来自谷歌。</p>

<p>但即使是风投或产品经理实习生也应理解市场并非静态。如果谷歌能够持续地从Yelp那里夺走相当一部分搜索流量并将其导向谷歌的本地服务，人们预期从长远来看，Yelp最终用户将所剩无几，沦为曾经的空壳。这正是发生的事情，截至撰写本文时，Yelp的估值为20亿美元，尽管其市盈率为24倍，对科技公司来说相当低。但市盈率低不足为奇，因为人们普遍认为Yelp无法扭转局面，原因是谷歌在搜索以及地图领域的主导地位使其非常难以获得或留住用户。这不仅事后看来显而易见，而且在当时就被充分理解了。事实上，我与一位在谷歌工作的前同事交谈，他正在开发一些利用谷歌拥有而Yelp永远无法合理获得的地位的本地功能；这些功能的预期结果是扼杀Yelp的业务。不仅人们知道这将发生，而且知道Yelp不太可能进行反击，因为谷歌能够利用其从搜索和地图获得的市场力量。奇怪的是，当时有人会认真辩称，在切断Yelp新用户来源的同时，又向几乎所有现有用户提供一个已集成到他们使用的应用或网站中的替代方案，不会显著影响Yelp的业务，但BE备忘录却提出了这个论点。有人可能会认为这里使用的策略类似于微软反垄断案中提到的策略，当时据称微软高管表示他们要“切断Netscape的空气供应”，但BE备忘录辩称“切断空气供应”的影响“非常小且不具统计显著性”（毕竟，一个典型的身体拥有足够结合1升氧气的血容量，远高于一次正常呼吸吸入的氧气量）。</p>

<p>另一类即使不算错误，也是论证薄弱的情况，是依赖<a href="https://danluu.com/cocktail-ideas/" rel="noopener noreferrer">鸡尾酒会级别的推理</a>，而当时有数据或其他有力证据可以直接应用。这种情况贯穿BE备忘录，尽管在其他时候，当BC备忘录提出一些看似合理的推理时，BE备忘录的反驳是我们不应接受此类推理，需要看数据而不是抽象推理。BE备忘录严重依赖一个概念，即我们必须依赖数据而非推理，并将BC备忘录中未植根于严格数据的论点称为“传闻”、“超出推测”等，但BE备忘录只在知识或推理可能导致结论存在某种竞争壁垒的情况下这样做。当数据表明谷歌的行为在市场中制造了某种壁垒时，BE备忘录的作者忽略了所有相关数据，反而依赖推理而非数据，即使推理薄弱，具有我们之前提到的Boies论点的特征。有人可能会辩称，提起反垄断案件的证据标准应该比不提起的标准更严格，但如果这里观察到的不对称是基于这个原因，BE备忘录本可以在证据不足的领域列出，并在面对更强证据时不做出自己的薄弱主张。一个例子是讨论移动默认设置的影响。</p>

<p>BE备忘录辩称默认设置基本上毫无价值，影响微乎其微或没有影响，多次表示用户只需“轻点几下”即可切换，补充说这只需“几秒钟”，因此“[这些是]微不足道的转换成本”。关于默认设置影响最明显、最直接的证据是谷歌为维持其默认地位所支付的金额。在2023年的一起反垄断诉讼中，透露谷歌在2021年向苹果支付了263亿美元以维持其默认地位。截至撰写本文时，苹果的市盈率为29.53。如果我们认为这笔付款是边际利润，并且默认地位如BE备忘录所言毫无价值，那么对苹果来说，这笔钱在其2.9万亿美元市值中所占的价值大约为7760亿美元。或者，从谷歌的角度看，谷歌的市盈率为27.49，因此谷歌愿意让其2.17万亿美元市值中的约7220亿美元蒸发。谷歌愿意为获得全球约25%至30%手机的默认搜索地位支付这笔钱。这个计算过于简单化，但没有任何合理的调整能让人相信默认地位的价值如BE备忘录所声称的那样微不足道。作为参考，一家市值7760亿美元的科技公司将是美国上市公司中第7大最有价值的科技公司，也是第8大最有价值的美国上市公司（落后于Meta/脸书和伯克希尔哈撒韦，但领先于礼来）。另一个参考是YouTube在2021年的广告收入为288亿美元。如果用户的实际转换成本微不足道且默认设置不重要，那么为了维持默认地位而花费相当于YouTube广告收入的利润就很难说得通。如果我们寻找更接近2012年的公开数据，2013年TechCrunch报道称谷歌每年向苹果支付10亿美元以获得搜索地位，后来的一起诉讼显示谷歌在2014年为默认搜索地位向苹果支付了10亿美元。这距离这些备忘录撰写时间不远，每年10亿美元仍是一笔不小的数目，这与BE备忘录声称移动搜索不重要且默认设置因用户转换成本微不足道而不重要的说法相矛盾。</p>

<p>有趣的是，鉴于BE备忘录如此强调不信任看似合理的推理，必须依靠实证数据，但BE工作人员似乎没有尝试查明谷歌为维持默认地位支付了多少。一位同意BE工作人员观点的主任的备忘录建议应该核查这个数字，但没有证据表明进行了核查，且FTC调查不久后就被终止了。根据FTC获取的内部文件数量，FTC不太可能无法从苹果或谷歌那里获得这个数字。但即使这个数字无法获得，表面上默认设置不重要且实际转换成本低的说法也不合理。如果FTC工作人员访谈了以产品为导向的工程师或产品经理，或了解科技行业的产品历史，那么为了提出这个论点，BE工作人员必须忽略或避免查明谷歌为维持默认地位支付了多少钱，不与专注于产品的工程师、产品经理或领导层交谈，并且还要避免了解科技行业。</p>

<p>有人可能会辩称，虽然默认设置影响力大，但公司能够克服非默认地位，这可能导致关于默认设置究竟有多强大的辩论。例如，人们可能会争论当谷歌Chrome成为主导浏览器时默认设置的影响，并辩论其中多少归因于Chrome仅仅是比IE、Opera和Firefox更好的浏览器，多少归因于微软的失误而谷歌在搜索领域不太可能重犯，多少归因于<a href="https://x.com/danluu/status/887724695558205440" rel="noopener noreferrer">通过捆绑恶意软件安装程序诱骗人们将Chrome设为默认</a>，以及多少归因于通过google.com施压人们将Chrome设为默认。这是一个有趣的讨论，一个了解行业的理性人士可以持任何一方观点，不像默认设置基本上毫无价值且实际用户转换成本微不足道的论点，即使没有关于谷歌向苹果和其他人支付多少以维持默认地位的数据，这个论点也不合理。而且根据2020年司法部对谷歌的诉讼，大约一半的谷歌搜索是通过谷歌付费的默认搜索进行的。</p>

<p>另一个反复出现的错误（与上述错误密切相关）是引用营销声明、新闻稿或其他普遍被认为是夸张的言论，并像对待有意义的事实陈述一样依赖它们。例如，BE备忘录指出：</p>
<blockquote>
<p>微软的公开声明与向反垄断监管机构的声明不一致。微软首席执行官史蒂夫·鲍尔默在宣布与雅虎搜索合作的新闻稿中表示：“与雅虎的这项协议将为我们提供所需的规模，以更快地推进相关性和实用性。微软和雅虎知道搜索领域还有更多可能。这项协议为我们提供了规模和资源，以创造搜索的未来”</p>
</blockquote>

<p>这是伴随收购或合作的典型营销废话。因为这种毫无意义的声明在许多行业都很常见，人们期望监管者，即使是对科技一无所知的监管者，也能将其视为营销手段，而不是给予与严肃证据同等或更多的重视。</p>

<h2 id="a-few-interesting-tidbits">一些有趣的细节</h2>

<p>在涵盖了备忘录中观察到的主要错误类别后，我们将看看备忘录中的一些细节。</p>

<p>在2011年6月3日批准强制程序到2012年8月8日BC备忘录发布之间，工作人员收到了950万页、200万份文件，并表示他们审阅了“其中数千份文件”，因此工作人员只能审阅其中很小一部分。</p>

<p>在FTC调查之前，有一些相关的诉讼，全部被驳回，其中一些理由如果被作为广泛先例，会使任何诉讼难以成功。在SearchKing诉谷歌案中，原告指控谷歌不公平地降低了其搜索结果排名，但法院裁定谷歌的排名是受宪法保护的意见，即使是恶意操纵排名也不会使谷歌承担法律责任。在Kinderstart诉谷歌案中，裁决的一部分是谷歌搜索对于垂直提供商（如Yelp、eBay和Expedia）并非<a href="https://en.wikipedia.org/wiki/Essential_facilities_doctrine" rel="noopener noreferrer">必要设施</a>。由于这些备忘录最终涉及法律程序，自然有大量关于Verizon诉Trinko案和Aspen Skiing Co.诉Aspen Highlands Skiing Corp案及其影响的讨论。</p>

<p>截至BC备忘录撰写时，谷歌380亿美元收入中96%来自广告，主要来自搜索广告。BC备忘录认为，除社交媒体广告外，其他形式的广告增长潜力有限。从事后来看，这当然是错误的。例如，视频广告是一个重要的市场。<a href="https://web.archive.org/web/20230608081933/https://www.hollywoodreporter.com/business/digital/youtube-ad-revenue-tops-8-6b-beating-netflix-in-the-quarter-1235085391/" rel="noopener noreferrer">YouTube的广告收入在2021年达到288亿美元</a>（略高于谷歌为维持默认搜索地位向苹果支付的费用），Twitch据称又产生了20-30亿美元的视频收入，而且相当一部分视频广告收入是直接从赞助商流向主播，不经过YouTube和Twitch，例如<a href="https://x.com/danluu/status/1588318512258650112" rel="noopener noreferrer">Twitch上第137大的主播被提供每年1000万美元的报酬，每天在线赌博直播30分钟，他声称他认识的第42大主播从在线赌博赞助商那里每月获得1000万美元</a>。这不仅仅是事后才明显——即使在当时，也有强烈迹象表明视频将成为主要的广告市场。碰巧的是，这些迹象同样表明谷歌很可能主导视频广告市场，但这里的具体论点仍被夸大了。</p>

<p>总的来说，BC备忘录似乎夸大了搜索广告预期的首要地位，以及搜索广告市场的独特性，声称其他在线广告支出在任何意义上都不构成替代品，甚至可能是互补品。尽管有人可能合理地辩称搜索广告是一个有点独特的市场，且一旦你将大量广告支出移出搜索，替代弹性就很低，但BC备忘录提出此主张的程度有些牵强。搜索广告和其他广告预算互补而非替代的观点，与我与业内人士交谈中关于广告支出实际如何分配的看法大相径庭。也许有人会辩称，考虑到Person诉Google案，加州北区联邦地区法院法官Fogel批评原告的市场定义，认为没有依据将“搜索广告市场”与更大的互联网广告市场区分开来，这预示着任何未来诉讼中都会提出的异议，因此在这里提出有力的论点是有意义的。然而，作为一个只想了解当前事实真相和论点真实性的人，这里的论点似乎值得怀疑。</p>

<p>对于谷歌的集成产品，如本地搜索和产品搜索（前身为Froogle），BC备忘录声称，如果谷歌像对待其他网站一样对待自己的属性，这些产品将不会被排名，谷歌人为地将自身的垂直竞争对手置于有机结果之上。网络垃圾团队拒绝将Froogle结果纳入，因为这些结果正是谷歌从索引中移除的那种垃圾内容，表示“[我们的]算法专门寻找此类页面以降级或从索引中移除”。网络搜索产品经理Bill Brougher说“通常我们喜欢在索引中拥有目标页面，而不是聚合页面。因此，如果我们本地页面是指向其他页面的链接列表，那么我们索引中拥有其他页面更为重要”。在网络垃圾团队被否决、结果被插入后，广告团队抱怨说，点击量较低（暗示质量较低）的结果将导致每年损失1.54亿美元。对此的回应基本上包含了与BC备忘录关于规模重要性以及谷歌剥夺竞争对手规模的行为为何代价高昂的论点相同的内容：</p>
<blockquote>
<p>我们面临激烈竞争，必须快速行动。拒绝“一个框”会阻碍以下方面的进展：排名：失去点击数据损害排名；触发：失去点击率和google.com查询分发数据影响触发准确性；全面性：失去流量损害商户增长，因此损害全面性；商户合作：失去流量减少了商户在报价数据、税费和运费方面的投入；公关：关闭一个框降低了谷歌在商业领域的可信度；用户认知：在google.com上失去与购物相关的UI降低了用户对谷歌购物功能的认知</p>
</blockquote>
<p>通常，<a href="https://en.wikipedia.org/wiki/Click-through_rate" rel="noopener noreferrer">CTR</a>被用作对结果进行排名的重要信号，但这会导致谷歌自身垂直属性的排名较低，因此“谷歌使用竞争垂直网站的存在来自动提升其自身垂直属性的排名，使其高于竞争对手”——如果一个比较购物网站是相关的，谷歌会将谷歌产品搜索插入在任何竞争对手之上；如果像Yelp或CitySearch这样的本地搜索网站是相关的，谷歌会自动在<a href="https://en.wikipedia.org/wiki/Search_engine_results_page" rel="noopener noreferrer">SERP</a>顶部返回谷歌本地。</p>

<p>此外，为了查看谷歌本地结果的内容，谷歌抓取了Yelp的内容并将其整合到谷歌场所中。当Yelp发现这种情况时，他们提出了异议，谷歌威胁要将Yelp从传统的谷歌搜索结果中禁止，并进一步威胁要禁止任何不允许其内容在谷歌场所中使用的垂直提供商。Marissa Mayer作证说，从技术角度来看，在不从传统有机搜索结果中移除Yelp的情况下，很难将Yelp从谷歌场所中移除。但当Yelp发送停止侵权函时，谷歌能够立即移除Yelp结果，这似乎表明其难度低于所声称的。谷歌随后声称在技术上不可行在不从SERP上的“本地合并”界面移除Yelp的情况下将其从谷歌场所中移除。BC工作人员认为这一说法也是错误的，Marissa Mayer后来在听证会上承认这一说法是错误的，并且谷歌担心允许网站在保留在“本地合并”中的同时选择退出谷歌场所可能带来的后果。亚马逊的结果和产品搜索也有一个非常相似的故事。如上所述，BE备忘录对所有这些的反驳论点是谷歌流量“非常小且不具统计显著性”。</p>

<p>BC备忘录声称上述活动既减少了Yelp、CitySearch、Amazon等公司在该领域投资的激励，也减少了新公司在此领域成立的激励。这似乎是真的。除了BC备忘录中提出的证据（超越了上述总结的内容），如果你只是在FTC调查期间与寻找创意的创始人或风投交谈，人们已经开始真正远离创办和资助像Yelp这样的公司，因为人们明白谷歌可以通过切断其空气供应严重损害这个领域的任何类似公司。</p>

<p>我们将BC备忘录附录中关于AdWords API限制的讨论推迟到附录，这些限制明确禁止广告活动程序化移植到其他平台（如必应）。但其中有一个有趣的细节，即<a href="https://mastodon.social/@danluu/111243272325539288" rel="noopener noreferrer">谷歌显然意识到了此事的法律敏感性，因此会议记录和内部文件异常不完整</a>。在一次会议中，BC工作人员能找到的最具信息量的书面记录是产品经理总监Richard Holden给广告高级副总裁Susan Wojicki的一条信息，内容是“我们没有做记录，原因显而易见，这就是为什么我不在电子邮件中详细说明的原因，但很乐意更口头地向你简要介绍”。</p>

<p>我们还将BC备忘录关于谷歌排他性和限制性联合协议的详细讨论推迟到附录，除了几个有趣的片段。一个是谷歌声称他们不知道其<a href="https://en.wikipedia.org/wiki/Standard_form_contract" rel="noopener noreferrer">标准在线服务协议</a>中的条款和条件。特别是，条款和条件包含一个“首选位置”条款，许多当事方认为这是事实上的独家协议。当FTC工作人员就这一条款询问谷歌搜索服务副总裁时，副总裁声称不知道此条款。之后，谷歌致信FTC的Barbara Blank，解释说他们正在从在线协议条款和条件中删除首选位置条款。</p>

<p>另一个有趣的片段涉及谷歌的市场力量及其如何让他们为自己获取越来越大的收入份额，并减少合作伙伴获得的收入份额。只有少数受此影响的谷歌客户对此表示担忧。那些表示担忧的是最大和最老练的一些客户（如亚马逊和IAC）；他们担心谷歌的限制性和排他性条款会增加谷歌对必应/微软的主导地位，并允许谷歌向客户提出更苛刻的条款。即使谷歌正在执行系统性策略以减少给客户的收入份额（这只有由于其市场主导地位才可能实现），大多数客户似乎要么不理解谷歌在此领域市场力量的长期影响，要么不理解互联网的重要性。</p>

<p>例如，百思买不觉得这值得担忧，因为百思买将他们的网站和网络视为客户进入商店前获取售前信息的方式；沃尔玛也不觉得这值得担忧，因为他们将网络视为实体店零售的延伸。似乎是同样的对互联网重要性的误解，导致沃尔玛和百思买在此表示不担忧，也导致这些零售商（过去地位远强于亚马逊）在在线和整体利润方面远远落后于亚马逊。沃尔玛后来意识到了这个错误，于2016年以33亿美元收购了Jet.com，并且（相对于其他零售商）认真资助了程序员在沃尔玛内部进行真正的技术工作。自从沃尔玛开始重视互联网以来，其在线业务取得了实质性复苏，自2018年以来在线净销售额平均复合年增长率达30%，但在面对亚马逊在线存在构成威胁后花了二十年时间才做出认真回应，这让沃尔玛在经过近十年的认真投入后仍然稳固地落后于亚马逊的在线零售，而百思买在三十年后仍然未能对亚马逊做出有效回应。</p>

<p>BE备忘录使用大多数客户不担忧作为谷歌在这里规定的独家和限制性条款不是问题的证据，但从事后来看，很明显，是客户对在线业务影响缺乏理解才导致他们对此不担忧。当BE备忘录将那些理解这里影响的客户称为“老练”时，这是相对于那些领导层倾向于不理解互联网的行业的人而言的。虽然这些客户相对于花了二十年才认真应对亚马逊威胁的零售商来说是老练的，但如果你当时只是与科技行业的人交谈，你不需要找到特别老练的人就能找到理解正在发生什么的人。人们普遍理解零售收入，更重要的是零售利润将转移到线上，你必须找到一个极其不合群的人才能找到一个对此没有大致理解的人。</p>

<p>BC和BE备忘录都有很长的关于搜索和规模的讨论。在这个话题上，BE备忘录似乎是错误的，而BC备忘录的含义，即使不微妙，至少也不明显。让我们从BE备忘录开始，因为它更容易讨论，尽管我们将非常简要地讨论BC备忘录中的论点，以便为BE备忘录中的讨论设定框架。BC备忘录中论点的大致框架是，在多个市场（搜索、广告）中，规模对产品质量有重大影响。谷歌自己的文件承认了这种“良性循环”，即拥有更多用户可以提供更好的广告，从而获得更好的广告收入；同样在搜索中，更大的规模带来更多的数据，可用于改进结果，从而带来用户增长。特别是在搜索领域，BC备忘录声称用户点击数据非常重要，更多的数据可以带来更好的结果。</p>

<p>BE备忘录声称事实并非如此。关于点击数据的重要性，BE备忘录提出了两个主要反对意见。首先，这与“通用搜索市场的历史相悖”；其次，“这也与证据相悖，这些证据表明，网络爬虫和网络索引的质量、搜索算法的质量以及搜索结果中包含的内容类型[至少同样重要或更重要]。”</p>

<p>对于第一个论点，BE备忘录用一个大致为“谷歌过去比现在小，当时的点击数据就足够了，因此变得像谷歌过去那么大意味着你有足够的点击数据”的案例来阐述。撇开对科技行业的了解不谈，这似乎是一个奇怪的推理思路。“我们现在生产的产品价格与竞争对手相同，但质量只有其三分之一，但这应该没问题，因为我们的竞争对手过去在市场不成熟、没有人生产更好产品时也生产过质量只有其三分之一的产品”通常不是一种制胜策略。这在市场份额和产品质量之间存在良性循环的市场中尤其如此，比如搜索领域。</p>

<p>第二个论点即使在不了解科技行业的情况下也显得奇怪，因为它是一个经典的谬误论证。这类似于说“BC备忘录声称汽车有右前轮很重要，但这与证据表明汽车有左前轮和右后轮至少同样重要相悖”。如果你了解科技，特别是搜索，这个论点就更不可信了。指出搜索算法的质量是不同的感觉不太对，因为规模和点击数据直接为算法开发提供信息（BE备忘录对此有相当多的讨论——BC备忘录的作者肯定可以获取相同的信息，从他们的写作来看，似乎也了解了这个论点）。作为一个<a href="https://danluu.com/bitfunnel-sigir.pdf" rel="noopener noreferrer">从事搜索索引工作</a>的人，虽然我很想同意BE备忘录的说法，认为索引与排名同等重要或更重要，但我必须承认索引是一个比排名更简单、更不重要的问题，同样爬虫 vs. 排名也是如此。这在当时是普遍理解的，因此，考虑到FTC工作人员进行了大量访谈，BE备忘录的作者也应该知道这一点。此外，考虑到BE备忘录所指的“通用搜索市场的历史”，即使不与工程师交谈，这一点也应该是显而易见的。</p>

<p>例如，Cuil以建立比谷歌更大的索引而闻名。虽然这不是一项微不足道的成就，但在当时，相当多的人拥有专业知识，如果资金充足用于严肃的基础设施创业，就能建立一个在原始大小或你更喜欢的其他索引指标上与谷歌索引相匹敌的索引。Cuil和其他专注于索引的尝试失败了，因为没有好的搜索排名，拥有大索引价值很小。虽然从技术上讲，拥有好的排名但索引差也价值很小，但我们在实践中并没有真正看到这种情况，因为排名是更难的问题，一家有能力构建好的搜索排名器的公司自然会拥有足够好的索引和足够好的爬虫。</p>

<p>至于BC备忘录的论点，我不知道其含义应该是什么。BC备忘录正确地指出，规模的增加极大地提高了搜索质量，必应从雅虎获得的额外数据大大提高了搜索质量和点击率，预计进一步增加的规模将继续带来高回报，创建谷歌竞争对手的成本很高（据说必应当时每年亏损20亿美元，并且每年花费45亿美元“开发其算法和构建运营必应所需的物理能力”），并且谷歌采取了可能被视为反竞争的行为，使得必应在与谷歌不采取这些行动的假设世界相比处于不利地位，并且他们对广告提出了类似的论点。然而，尽管BC备忘录陈述的论点有其强度，BE备忘录陈述的论点有其不准确性，BE备忘录的论点在精神上是正确的，即微软本可以采取一些行动来更有效地参与搜索竞争，但并没有这样做，有人可能会辩称FTC不应该介入帮助一家公司摆脱无效竞争。</p>

<p>就个人而言，我认为冗长讨论BC备忘录立场与BE备忘录立场没有太大意义，因为BE备忘录采取的立场似乎极其薄弱。称其为稻草人策略并不公平，因为这是一个真实的立场，并且在FTC中胜出，但采取行动与否的决定似乎更多是基于哲学而非备忘录中的论点。但我们可以讨论还可以做些什么。</p>

<h2 id="what-might-ve-happened">可能发生什么</h2>

<p>在FTC拒绝提起反垄断诉讼之后，发生的是微软实际上将必应作为严肃赌注的资金撤回，将本可用于继续资助与谷歌进行一场非常昂贵斗争的资源，转移到其认为投资回报率更高的其他项目上。微软追求的大赌注是Azure、Office和HoloLens（以及可以说是Xbox）。HoloLens是一个不切实际的赌注，但Azure和Office是微软可以利用其在相关市场的主导地位来打压竞争对手的业务线，而不是打一场竞争对手可以利用其在相关市场的主导地位来打压他们的逆风仗，从而获得更高的每美元投资回报率。作为一个在必应工作过并且认为如果得到持续、非盈利性的大量投资，必应有潜力与谷歌真正竞争的人，我觉得这令人失望但也可能是正确的商业决策。如果你看任何一个特定的子市场，比如Teams vs. Slack，微软的产品不需要比竞争对手好很多就能占领市场，这与搜索领域的情况相反，在搜索领域，谷歌打压竞争对手的能力意味着必应必须比谷歌好得多才能达到市场份额的均等。</p>

<p>根据他们的公开声明，拜登的司法部反垄断助理总检察长乔纳森·坎特（Jonathan Kanter）会主张在这种情况下提起反垄断诉讼，拜登的FTC委员兼主席莉娜·汗（Lina Khan）也会如此。在被任命为FTC委员兼主席之前，莉娜·汗最出名的是写了《亚马逊的反垄断悖论》，该文既有影响力也有争议。奥巴马任命的官员更经常同意BE备忘录中的推理类型，会反对反垄断行动，我们讨论的调查就是在他们任期内终止的。更广泛地说，他们反对坎特和汗所驱动的理念。奥巴马的FTC委员任命者，<a href="https://en.wikipedia.org/wiki/Joshua_D._Wright" rel="noopener noreferrer">乔治梅森大学经济学家兼法律学者乔什·赖特</a>，实际上写了一篇题为《对一个悖论的安魂曲：时尚反垄断的可疑崛起与必然衰落》的反驳文章，对汗的立场进行了严厉批评。</p>

<p>如果在2012年，FTC和司法部由拜登任命者而非奥巴马任命者领导，会有什么不同？我们只能推测，但一种可能性是他们会采取行动然后败诉，就像最近针对Meta和微软的案件一样，这些案件在奥巴马的FTC和司法部任期内似乎不会发生。在拜登任命者领导下，对现行法律——《谢尔曼法》、《克莱顿法》、《FTC法》、《罗宾逊-帕特曼法》以及“较小的”反垄断法——的运用要活跃得多，但法院的意见在拜登任期内并未改变，这导致了一系列科技领域不成功的反垄断案件。BE和BC备忘录都用了大量篇幅讨论某种推理路线是否会在法庭上站得住脚。拜登的任命者比以往的任命者更不关心这一点，司法部和FTC的多人都公开表示“我们有责任执行法律”之类的话，意思是当他们看到由民选官员制定的反垄断法律被违反时，即使法院可能不同意该法律，他们的工作也是追诉这些违法行为。</p>

<p>另一种可能性是会有一些行动，但行动将与我们看到的大多数公司处罚一致。比如一笔小额罚款，其成本相对于公司从其行为中获得的边际利润微不足道，或者某种同意令（基本上是停止与终止令），要求公司停止特定行为，同时保持其市场份额，保持其主要想获得的东西——在网络效应主导的市场中的巨大优势。也许会再开几次“我们没有做记录，原因显而易见”的会议来规避新的限制，一切照旧。考虑到FTC备忘录中的具体指控以及当时法院的态度，我的猜测是，如果FTC继续进行反垄断调查而不是终止，最可能的结果是第二种情况的某种形式，即某种名义上的胜利，在实践中几乎毫无影响。考虑到这些案件的审理需要多长时间，微软几乎肯定在任何决定做出之前就已经缩减了对必应的投资，并将必应从试图增长的补贴赌注转变为它希望保持的盈利业务。其他国家提起的一些案件，其补救措施与我们预期的如果FTC调查继续进行可能的结果一致。关于谷歌利用移动领域的市场力量在其希望的几乎所有Android手机上推广软件，欧盟名义上成功了，但实际影响微乎其微。经济政策研究中心的Cristina Caffara将其描述为：</p>
<blockquote>
<p>欧洲未能推动实地变革。为什么？因为我们告诉他们，别再这么做了，坏狗狗，别再这么做了。但事实上，他们都去了，说着“好的，好的”，然后从后门跑出去又做了一遍，因为他们比监管者聪明，对吧？事情就是这样。</p>
<p>所以，在捆绑案中，在Android上，问题是不要再捆绑，于是他们说“好的，我们不捆绑了”。现在我们有了一个新系统。如果你想要Google Play商店，你付100美元。但如果你想在每个入口点放置搜索，你可以获得100美元的折扣……补救措施失败了，其他人都说“哦，这是一个好方法，非常聪明”</p>
</blockquote>

<p>另一对相关案件是Yandex在俄罗斯关于移动搜索默认设置的诉讼，以及后来的欧盟同意令。2015年，Yandex在俄罗斯就Android上的移动默认地位提起诉讼，通过添加一个“选择屏幕”来解决，让用户选择搜索引擎而不偏向任何默认选项。这立即导致Yandex开始在谷歌那里获得市场份额，<a href="https://gs.statcounter.com/search-engine-market-share/all/russian-federation/#monthly-200901-202404" rel="noopener noreferrer">最终Yandex在俄罗斯的市场份额超过了谷歌</a>。2018年，欧盟在欧洲要求了类似的选择屏幕，<a href="https://gs.statcounter.com/search-engine-market-share/all/europe/#monthly-201501-202404" rel="noopener noreferrer">这并没有产生太大影响</a>，除了在捷克共和国可能有点影响。俄罗斯的情况和欧盟的情况有一些差异。一个可以说是最重要的差异是，当Yandex在俄罗斯起诉谷歌时，Yandex仍然具有相当的竞争力，市场份额在30%多。在2018年欧盟决定时，必应是在欧洲排名第二的搜索引擎，市场份额约为3.6%。当一个搜索引擎完全主导市场时，给消费者提供选择可能影响甚微。BE备忘录强烈依赖的一个论点是，如果我们以任何方式干预，可能会在以后产生不良影响，所以我们应该非常小心，可能什么也不做，以防万一。但在这些赢家通吃、具有强大网络效应的市场中，你可以廉价干预的窗口期相对较小。也许（这是高度推测性的），如果FTC在2012年要求选择屏幕，必应会继续投入足够的资金至少维持其相对于谷歌的市场份额。</p>

<p>对于垂直领域，在购物方面，欧盟在2017年要求谷歌改变其展示结果的方式。这似乎几乎没有影响，既可能因为晚了5-10年，也可能因为即使在十年前实施也是微不足道的改变。2017年的裁决源于2010年启动的案件，在采取行动的7年里，谷歌成功地在竞争中击败了其垂直竞争对手，使其最多仅勉强相关。</p>

<p>另一个我们可以参考的地方是微软反垄断案。那是一个很长的故事，至少和本文档一样长，但简要总结一下，1990年，FTC开始调查微软涉嫌反竞争行为。继续调查的投票最终以2-2平局告终，导致调查终止。司法部随后进行了自己的调查，导致了一份普遍认为不太有效的同意令。然后是1998年司法部关于微软在浏览器市场使用垄断权力的诉讼，最初导致了拆分微软的决定。但在上诉中，拆分被推翻，导致了2002年的和解。1998年案件的一个主要组成部分是关于浏览器捆绑和微软对Netscape的攻击。到案件和解时（2002年），Netscape实际上已经死亡。和解中与互操作性相关的部分当时被普遍认为无效，不仅因为Netscape已死，而且因为它们不会普遍有用。一些经济学家持与BE备忘录相同的观点，即当时不应进行干预，任何干预都是危险的，可能阻碍创新。诺贝尔奖得主米尔顿·弗里德曼写了一篇题为《商业界自杀冲动》的Cato政策论坛文章，预测呼吁对微软采取反垄断行动的科技公司是在自杀，并且已经达到了一个关键门槛，这将导致硅谷的官僚化：</p>
<blockquote>
<p>当我开始进入这个行业时，作为竞争的支持者，我是反垄断法的坚定拥护者；我认为执行这些法律是政府为促进更多竞争可以做的少数几件可取的事情之一。但当我观察实际发生的情况时，我看到，反垄断法非但没有促进竞争，反而倾向于做完全相反的事情，因为它们像许多政府活动一样，倾向于被它们本应监管和控制的人接管。因此，随着时间的推移，我逐渐得出结论，反垄断法弊大于利，如果我们没有这些法律，我们会过得更好，如果我们可以摆脱它们。但我们确实有它们。</p>
<p>在这种情况下，鉴于我们确实有反垄断法，硅谷将政府引向微软真的符合自身利益吗？……你们会后悔呼唤政府的那一天。从现在起，计算机行业一直非常幸运，相对不受政府干预，但将经历政府监管的持续增加。反垄断很快就会变成监管。这再次似乎是一个说明商业界自杀冲动的案例。</p>
</blockquote>

<p>从事后来看，我们看到事实并非如此，甚至恰恰相反。关于试图对微软提起反垄断行动将不可避免地导致政府干预增加的观点，我们看到了相反的情况，即长达二十年的相对宽松监管和反垄断活动时期。在对创新的影响方面，尽管对微软的诉讼太少太晚，无法拯救Netscape，但谷歌的成功似乎与反垄断案有因果关系。在谷歌早期，当谷歌没有市场力量而微软实际上控制着人们访问互联网的方式时，微软内部曾讨论过旨在杀死谷歌的提案。其中一个提案是将试图访问谷歌的用户重定向到必应（当时称为MSN搜索，当然这是在Chrome存在之前，IE主导浏览器市场）。另一个想法是发布一个巨大的警告，警告用户谷歌是危险的，就像今天的浏览器中的恶意软件警告一样。当时微软的律师Gene Burrus表示，微软选择不尝试阻止用户访问google.com，是因为担心在经历了近十年的严格反垄断审查后，会引发进一步的反垄断行动。接受访谈的谷歌和微软人员都相信，如果微软那样做了，他们会杀死谷歌，因此，从事后来看，我们可以看到米尔顿·弗里德曼关于微软反垄断调查影响的看法是错误的，并且可以认为，正是因为这些反垄断调查，谷歌和脸书等Web 1.0公司才能够生存下来，更不用说繁荣发展了。</p>

<p>另一种可能性是采取了重大反垄断行动，取得了成功，并且成功得足够及时以产生影响。也许，补救措施本身不足以改变必应与谷歌的格局，但如果找到并实施了一个合理的补救措施，它仍然可能及时地使Yelp和其他垂直网站保持重要地位，甚至可能刺激更多垂直领域的初创公司。在一个由与拜登任命者理念相同的人领导FTC和司法部的假设世界里，我们可能还会看到针对微软在其可以利用相邻市场主导地位的市场提起反垄断行动，使必应成为持续大量投资更具吸引力的领域。也许这将导致必应与谷歌竞争，而上述“老练客户”如亚马逊和IAC的担忧可能不会成为现实。通过对微软和其他可以利用其主导地位打压竞争对手的大公司提起反垄断诉讼，也许Slack仍然是一个独立产品，我们会在企业工具领域看到更多初创公司（<a href="https://x.com/carnage4life/status/1774800837598216383" rel="noopener noreferrer">一些评论者认为Slack基本上是被迫被收购，因为面对微软在相关市场的主导地位，竞争太困难了</a>）。Slack继续存在和创新只是小事——更大的假设影响将是所有那些因为担心像微软这样的庞然大物会用捆绑产品摧毁他们的独立产品而不敢尝试的新初创公司和产品。如果你把所有这些，如果不是最佳情况，至少是非常好的情况下的反垄断倡导者的结果加起来，有人可能会争辩说消费者和企业会过得更好。但现实地说，很难看出这种非常好的情况组合如何能够实现。</p>

<p>回到FTC备忘录，如果我们考虑需要采取什么反垄断行动才能真正促进竞争，这似乎极其困难。一些更直接、听起来更可信的解决方案由于政治原因、法律先例，或者像我们引用的Boies论点或BE备忘录中一些明显不正确但似乎对非常重要的人来说有说服力的论点而被排除在外。</p>

<p>对于似乎可行的解决方案，权衡它们造成的危害并非易事。例如，假设FTC在2012年强制实施移动和桌面选择屏幕。这会在相当短的时间内杀死Mozilla，除非Mozilla完全改变其商业模式，因为Mozilla基本上依靠从谷歌获得的默认地位付款生存。我们从Opera看到，即使你有一个卓越的浏览器，引入了其他浏览器后来复制的功能，性能比其他浏览器更好等等，当你面对免费浏览器时，你也无法真正竞争。因此我们很快将只剩下IE/Edge和Chrome。在浏览器引擎方面，不久后就只有Chrome了，因为Edge现在底层运行Chrome。也许我们可以想出另一个补救措施来允许浏览器竞争，但BE备忘录并非没有注意到反垄断补救措施可能造成其他损害。</p>

<p>另一个例子突显了制定政治上合适的补救措施的困难，是德国联邦卡特尔局对脸书施加的限制，涉及用户隐私和数据使用（用于个性化、排名、一般机器学习训练等），这在德国被视为反垄断问题。海法大学教授兼法律与市场论坛主任Michal Gal指出，当然，脸书在回应裁决时，只在检测到你是德国人时才限制其数据使用。如果担心的是机器学习模型在用户数据上训练，这对削弱脸书的能力没什么帮助。假设德国有一个与美国科技竞争的科技界，德国公司担心类似的裁决会针对他们，这将对最初专注于德国市场然后在国际上扩张的新兴德国公司不利。对德国来说，这只是理论上的担忧，因为除SAP外，没有德国公司接近美国大型科技公司的规模和范围。但在审视美国的补救措施和监管时，这不是理论上的担忧，一些立法者会希望权衡保护美国消费者与对美国公司施加的拖累，这种拖累是与韩国、中国和其他外国公司相比的，这些外国公司在本地市场可以在隐私顾虑较少的情况下增长，然后扩展到国际市场。如果认真对待这种担忧，几乎可以用来反对任何支持反垄断行动的论点。</p>

<h2 id="what-can-we-do-going-forward">我们未来能做什么？</h2>

<p>这份文档已经足够长了，所以我们推迟对政策细节的详细讨论，另找时间，但在高层行动方面，似乎有帮助的一件事是让科技人员密切参与制定补救措施和监管，以及在调查期间参与<sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">2</a></sup>。从公开的2011-2012年FTC调查主任备忘录来看，似乎并未这样做，因为BE备忘录中那些通不过科技人员嗅觉测试的论点似乎被认真对待了。另一个例子是Cristina Caffara指出的那个欧盟补救措施，被谷歌立即规避了，许多科技人士会认为这是一个令人愉快的“黑客”。</p>

<p>这种“破解系统”在科技界有着悠久的历史，早于人们称之为“科技”之前，它只是物理学和电子工程。举一个更近期的例子，山姆·奥尔特曼（Sam Altman）成为Y Combinator总裁（最终导致他成为OpenAI首席执行官）的原因之一，就是保罗·格雷厄姆（Paul Graham）欣赏他破解系统的能力；保罗在2010年关于创始人的文章中，在标题为“淘气”的章节下写道：</p>
<blockquote>
<p>尽管最成功的创始人通常是好人，但他们眼中往往闪烁着海盗般的光芒。他们不是乖乖牌式的好人。在道德上，他们关心把重大问题做对，但不在意遵守礼节。这就是为什么我用“淘气”这个词而不是“邪恶”。他们喜欢打破规则，但不是重要的规则。这种特质可能是多余的；它可能蕴含在想象力中。</p>
<p>Loopt的山姆·奥尔特曼是最成功的校友之一，所以我们问他可以在Y Combinator申请表上问什么问题，以帮助我们发现更多像他这样的人。他说可以问一个关于他们曾如何破解某事为自己谋利的问题——这里的破解是指击败系统，而不是入侵计算机。这已成为我们评审申请时最关注的问题之一。</p>
</blockquote>

<p>或者，从谷歌无数例子中选一个，为了减少差旅成本，谷歌工程师实施了一个系统，他们计算某种基准“航班预期成本”，然后为人们预订低于基准成本的航班提供积分，可用于升级未来的航班和差旅住宿。与那些刻板公司的费用限额相比，这对员工来说是一种很好的体验，谷歌工程师为创建了一个让每个人体验都更好的系统而自豪，这是破解系统的一种方式。更高层次的破解系统是当一些员工优化他们的航班，甚至安排前往高度可优化的目的地旅行（许多工程师会认为这是一个有趣的挑战，是面试中给出的经典动态规划问题的变体等），使他们能够升级到头等舱航班和最好的酒店。</p>

<p>当我和传统行业的管理人员谈论这件事时，他们经常感到震惊，不敢相信这些员工没有因欺骗系统而受到谴责甚至解雇。但当我在谷歌时，人们普遍认为这令人钦佩，因为它体现了黑客精神。</p>

<p>从科技反垄断至少二十年的历史中，我们可以看到，法院、监管机构和立法者并没有为科技公司破解系统的活力、速度和乐趣做好准备。</p>

<p>并且有让科技人员参与桌子另一边的先例。例如，在微软反垄断大案中就是这样做的。但在每个层面都存在激励问题，这使得这件事很困难，原因包括科技公司愿意支付巨额资金等。如果我想到我认识的非常擅长上述破解系统的人，那些想在大公司工作的人通常每年挣七位数（或更多），这个数字不太可能被个人与司法部或FTC签订的咨询合同所匹敌。如果我们再次看微软的例子，参与的技术小组由Ron Schnell管理，他在第三次退出后休息了一段时间，但像他这样的人相对较少。当然，有各种原因不想在大公司工作的人，通常是道德原因或不喜欢大公司的企业政治，但我认识的大多数符合描述的人在大公司待的时间不够长，无法真正理解大公司的运作机制，即使他们是出色的工程师和黑客，也是这份工作的错误人选。</p>

<p><abbr title="抱歉，我没有做笔记，无法具体回忆是谁说的，甚至哪个会议，但我记得是在芝加哥大学">在不久前的反垄断会议上</abbr>，一位发言人指出，法律界和经济学界的交流与合作对反垄断工作大有裨益。演讲和会议中明显缺少的是行业从业者。会议的感觉像学术会议，所以你可能会在某天看到计算机科学学者参加会议，但即使如此，许多政策层面的讨论也不在计算机科学学者的兴趣范围内。例如，我们指出的BE备忘录中一个不可信的论点是他们如何使用MAU基本上论证转换成本很低。这几乎是每个计算机科学学者研究领域之外的东西，所以即使会议扩大并引入与科技密切相关的人员，自然的参与者仍然不是在涉及微小细节的可信度时有权发表意见的人。</p>

<p>除了上述对政策讨论的影响外，缺乏与科技人员的合作还意味着，当人们谈论行为者的动机时，他们经常做出毫无根据的假设。在一个具体的例子，某人可能称之为“黑客”的行为上，发言人描述了一位高管的反应（击掌庆祝等），并推断出对立法者和法律的蔑视，但这没有证据支持。这位被提及的高管可能确实对立法者和法律有蔑视和鄙视，但那种庆祝正是你在谷歌有人发现如何“免费”在几乎所有航班上升级到头等舱后可能看到的，这完全不能表明蔑视或鄙视。</p>

<p>回到激励问题，这超越了在反垄断讨论中让理解科技的人参与桌子另一边。如果你问当时在国会山的工作人员，普遍的看法是，阻碍FTC调查的主要因素是谷歌的游说，当然<a href="https://x.com/danluu/status/1254720673592688641" rel="noopener noreferrer">谷歌和其他大型科技公司在游说上的花费超过了那些希望加强反垄断审查的实体</a>。</p>

<p>在公务员队伍中，如果我们看BC调查的负责人和BC备忘录的第一作者，他们现在是Facebook竞争与监管事务的董事兼副总法律顾问。我不认识他们，所以我不能谈论他们的动机，但如果有人给我我认为他们做反垄断和其他监管工作所得的报酬，我可能会接受。即使抛开薪酬不谈，如果我坚信加强反垄断执法的目标，这仍然会是一个非常有吸引力的提议。在FTC工作，也许你会领导另一项调查，你写了一份比对方备忘录强得多的备忘录，但当一家大型科技公司在华盛顿特区投入更多游说资金时，这无关紧要，调查就被终止了。或者也许你的调查导致了像欧盟调查那样的结果，最终导致一个太少也太晚的“选择屏幕”。或者也许导致了像Android Play Store解绑案那样的结果，在调查启动七年后，一位足智多谋的谷歌员工想出了一个“黑客”，在大约五分钟内让同意令变得毫无用处。至少在Facebook内部，你可以推动公司朝着你认为正确的方向发展，并对Facebook如何对待消费者和竞争对手产生一些影响。</p>

<p>从科技人员（而非从事反垄断工作的人员）的角度看，在我的社交圈里，经常听到人们说“我永远不会为<a href="https://www.patreon.com/posts/103707012" rel="noopener noreferrer">道德原因</a>在X公司工作”。这是一个不错的立场，但我认识的几乎所有这样做的人最终都在一个影响力很小的公司工作。如果你想采取道德立场，更可能产生影响的是从内部工作或找到一个较小的直接竞争对手并帮助其取得更大成功。</p>

<p><i>感谢Laurence Tratt、Yossi Kreinin、Justin Hong、kouhai@treehouse.systems、Sophia Wisdom、@cursv@ioc.exchange、@quanticle@mastodon.social和Misha Yagudin的评论/纠正/讨论</i></p>

<h1 id="appendix-non-statements">附录：非声明</h1>

<p>这类似于技术设计文档的“非目标”部分，但更弱，因为设计文档中的非目标通常是暗示某些内容的积极陈述，而这些内容无法从文档中读出，而非目标<abbr title="声明本身不增加任何信息">声明</abbr>本身不增加任何信息</p>
<ul>
<li>2012年应该对谷歌提起反垄断诉讼
<ul>
<li>不是说有人应该在意我的意见，但如果当时有人问我是否应该提起反垄断诉讼，我会说“可能不会”。现在支持反垄断行动的理由似乎更强，反对的理由似乎更弱，但你仍然可以提出相当有力的反对反垄断行动的论点。</li>
<li>即使你认为，其他条件不变，反垄断行动对消费者有利，并且“可能发生什么”中的“非常好的情况”结果会在提起反垄断行动时出现，但谷歌和其他科技公司（仅举几例）是否是正确的目标仍然不明确，而不是Visa和Mastercard在支付领域的主导地位、医院合并导致的集中度增加对消费者和工人产生的负面影响、Ticketmaster的主导地位等。或者也许你认为政府应该专注于监管明确保护企业的领域，如航运（根据《谢尔曼法》豁免）或汽车经销商（在美国许多州法律中有特殊保护，防止直销并迫使汽车公司在某些方面遵守其要求）等。</li>
</ul></li>
<li>今天应采取较弱或较强的反垄断措施
<ul>
<li>我认为我还没有花足够时间研究法律、政治、历史和哲学背景来形成应该做什么的意见，但我对科技了解得足以指出我看到的几个错误，并指出这些错误中的共同主题。</li>
</ul></li>
</ul>

<h1 id="bc-staff-memo">BC工作人员备忘录</h1>

<p>由“Barbara R. Blank、Gustav P. Chiarello、Melissa Westman-Cherry、Matthew Accornero、Jennifer Nagle（反竞争行为司）；James Rhilinger（医疗保健司）；James Frost（政策与协调办公室）；Priya B. Viswanath（主任办公室）；Stuart Hirschfeld、Danica Noble（西北地区）；Thomas Dahdouh（旧金山西部地区）律师；Daniel Gross、Robert Hilliard、Catherine McNally、Cristobal Ramon、Sarah Sajewski、Brian Stone（荣誉法务助理）；Stephanie Langley（调查员）”提交</p>
<p>日期：2012年8月8日</p>
<p>（备忘录正文较长，此处省略）</p>

<h1 id="ftc-be-staff-memo">FTC经济局工作人员备忘录</h1>
<p>“经济局</p>
<p>2012年8月8日</p>
<p>发件人：Christopher Adams和John Yun，经济学家”</p>
<p>（备忘录正文较长，此处省略）</p>

<h1 id="other-memos">其他备忘录</h1>
<p>（其他备忘录较长，此处省略）</p>

<div>
<hr>
<ol>
<li id="fn:K"><p>（脚注1内容，关于rangeCheck的讨论，此处省略）</p>
</li>
<li id="fn:B"><p>（脚注2内容，关于DuPont和PFAS的讨论，此处省略）</p>
</li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 63468 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/ftc-google-antitrust/</link>
      <guid isPermaLink="false">https://danluu.com/ftc-google-antitrust/</guid>
      <pubDate>Sun, 26 May 2024 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>网页臃肿如何影响使用慢速设备的用户</title>
      <description>&lt;p&gt;&lt;meta property=&quot;og:image&quot; content=&quot;/slow-device-performance.png&quot;/&gt;&lt;/p&gt;

&lt;p&gt;2017年，&lt;a href=&quot;https://danluu.com/web-bloat/&quot;&gt;我们研究了网络臃肿如何影响网络连接缓慢的用户&lt;/a&gt;。即使在美国，&lt;a href=&quot;https://twitter.com/danluu/status/</description>
      <content:encoded><![CDATA[<p></p>

<p>2017年，<a href="https://danluu.com/web-bloat/" rel="noopener noreferrer">我们研究了网络臃肿如何影响网络连接缓慢的用户</a>。即使在美国，<a href="https://twitter.com/danluu/status/1116565029791260672" rel="noopener noreferrer">许多用户也没有宽带速度</a>，使得大部分网络难以使用。无论是在美国国内还是国外，许多用户仍然没有宽带速度，现代网络的大部分内容对于网络速度慢的人来说仍然不可用，但由于典型网站的带宽呈指数级增长（尼尔森指出<abbr title="不幸的是，我不知道低端数据的公开来源，比如10%或1%分位数；如果你有这方面的数据，请告诉我">对于高速连接，这是每年50%</abbr>），其增速已超过网络臃肿的蔓延速度，这使得问题比2017年时有所减轻，尽管对于网络连接差的人来说这仍然是一个严重问题。</p>

<p>Web应用的CPU性能增长速度远不及带宽，因此，虽然更多网络内容对低端连接用户变得可访问，但更多网络内容对低端设备用户（即使他们有高速连接）变得不可访问。例如，如果我尝试在<code>Tecno Spark 8C</code>上浏览一个“现代”的Discourse论坛，有时会导致浏览器崩溃。在崩溃之间，测量其性能时，其响应速度明显慢于使用<code>8 MHz 286</code>处理器和<code>1200 baud</code>调制解调器浏览一个BBS。在我家<code>1Gbps</code>的网络连接下，加载消息标题所“必需”的<code>2.6 MB</code>压缩载荷相对较轻。网络传输载荷大小“只”增加了<code>1000倍</code>，这被互联网速度的提升所掩盖。但在CPU速度方面情况恰恰相反——对于网页浏览和论坛加载性能，<code>8核 (2个1.6 GHz Cortex-A75 / 6个1.6 GHz Cortex-A55)</code> CPU无法处理Discourse。该CPU的速度大约比我们的<code>286</code>快<code>100000倍</code>。也许一个快<code>1000000倍</code>的设备才足够。</p>

<p>对于不熟悉<code>Tecno Spark 8C</code>的人，今天，一台全新的<code>Tecno Spark 8C</code>，快速搜索显示，在尼日利亚可能花<code>50-60美元</code>就能买到，在印度可能<code>100-110美元</code>。作为<abbr title="我查看的尼日利亚中位数收入估计似乎足够好，但我找到的印度估计有点不确定；如果你有印度收入分布的可靠来源，请提供。">中位家庭收入的一部分，这比今天美国当前一代的iPhone贵得多。</abbr></p>

<p>按照全球标准，<code>Tecno Spark 8C</code>甚至算不上低端设备，因此我们也将看看<code>Itel P32</code>的性能，这是一款更低端的设备（尽管仍然远非人们今天使用的最低端设备）。此外，我们将测试<code>M3 Max Macbook (14核)</code>、<code>M1 Pro Macbook (8核)</code>的性能，以及将<code>M3 Max</code>在Chrome开发工具中设置为<code>10倍</code>限速下的表现。为了给这些设备提供一切优势，我们将使用相当高速的互联网（1Gbps，搭配一个基准测试显示其负载下延迟低于大多数同类产品的WiFi路由器）。我们将查看一些博客平台和微博客平台（本博客、Substack、Medium、Ghost、Hugo、Tumblr、Mastodon、Twitter、Threads、Bluesky、Patreon），论坛平台（Discourse、Reddit、Quora、vBulletin、XenForo、phpBB和myBB），以及小企业常用的平台（Wix、Squarespace、Shopify以及再次提到的WordPress）。</p>

<p>在下面的表格中，每一行代表一个网站，每个非标签列是一个指标。网站名称列之后，我们有通过网络传输的压缩大小（<code>wire</code>）和原始未压缩大小（<code>raw</code>）。然后，对于每个设备，我们有最大内容绘制*（<code>LCP*</code>）和主线程上的CPU使用率（<code>CPU</code>）。谷歌的文档将<code>LCP</code>解释为</p>

<blockquote>
<p>最大内容绘制（LCP）衡量用户感知到页面最大内容可见的时间点。LCP指标值表示从用户发起页面加载到页面渲染其主要内容之间的时间长度。</p>
</blockquote>

<p><code>LCP</code>是一个常见的优化目标，因为它作为谷歌PageSpeed Insights中的主要指标之一呈现，属于“核心Web指标”。本文档中使用的<code>LCP</code>旁边有一个星号，因为Chrome测量的<code>LCP</code>大致是关于绘制大部分屏幕，而上述定义是关于内容的。由于网站一直在针对<code>LCP</code>进行优化，出现一个对用户完全无用的巨大绘制（更新）的情况并不少见，实际页面内容在<code>LCP</code>之后很久才出现。在这种情况下，我使用的是有用内容出现的时间戳，而不是那个无用的大更新发生时定义的<code>LCP</code>。测试的详细细节以及为何选择这些指标将在附录中讨论。</p>

<p>尽管CPU时间不是“核心Web指标”，但这里展示它是因为它是一个简单且与我以及其他用户在慢速设备上对可用性感知高度相关的指标。更多关于此的详细讨论请参见附录。CPU时间作为一个指标有效的一个原因是，如果一个页面在所有其他指标上表现都很好，但使用了大量的CPU时间，那么这个页面在慢速设备上将不可用。如果它以100% CPU占用运行30秒，页面在30秒内将完全无法使用；如果以50% CPU占用运行60秒，页面在60秒内将勉强可用，等等。另一个原因是，与常用指标相比，很难在CPU时间上作弊，做出显著改变该数字而不影响用户体验的优化。</p>

<p>下面表格中的颜色方案是：对于大小，越绿色=越小/快，越红色=越大/慢。极端值用黑色显示。</p>

<p>
</p><table>
<tbody><tr>
<th rowspan="2">站点</th><th colspan="2">大小</th><th colspan="2">M3 Max</th><th colspan="2">M1 Pro</th><th colspan="2">M3/10</th><th colspan="2">Tecno S8C</th><th colspan="2">Itel P32</th></tr>
<tr>
<th>wire</th><th>raw</th><th>LCP*</th><th>CPU</th><th>LCP*</th><th>CPU</th><th>LCP*</th><th>CPU</th><th>LCP*</th><th>CPU</th><th>LCP*</th><th>CPU</th></tr>
<tr>
<td>danluu.com</td><td>6kB</td><td>18kB</td><td>50ms</td><td>20ms</td><td>50ms</td><td>30ms</td><td>0.2s</td><td>0.3s</td><td>0.4s</td><td>0.3s</td><td>0.5s</td><td>0.5s</td></tr>
<tr>
<td>HN</td><td>11kB</td><td>50kB</td><td>0.1s</td><td>30ms</td><td>0.1s</td><td>30ms</td><td>0.3s</td><td>0.3s</td><td>0.5s</td><td>0.5s</td><td>0.7s</td><td>0.6s</td></tr>
<tr>
<td>MyBB</td><td>0.1MB</td><td>0.3MB</td><td>0.3s</td><td>0.1s</td><td>0.3s</td><td>0.1s</td><td>0.6s</td><td>0.6s</td><td>0.8s</td><td>0.8s</td><td>2.1s</td><td>1.9s</td></tr>
<tr>
<td>phpBB</td><td>0.4MB</td><td>0.9MB</td><td>0.3s</td><td>0.1s</td><td>0.4s</td><td>0.1s</td><td>0.7s</td><td>1.1s</td><td>1.7s</td><td>1.5s</td><td>4.1s</td><td>3.9s</td></tr>
<tr>
<td>WordPress</td><td>1.4MB</td><td>1.7MB</td><td>0.2s</td><td>60ms</td><td>0.2s</td><td>80ms</td><td>0.7s</td><td>0.7s</td><td>1s</td><td>1.5s</td><td>1.2s</td><td>2.5s</td></tr>
<tr>
<td>WordPress (旧版)</td><td>0.3MB</td><td>1.0MB</td><td>80ms</td><td>70ms</td><td>90ms</td><td>90ms</td><td>0.4s</td><td>0.9s</td><td>0.7s</td><td>1.7s</td><td>1.1s</td><td>1.9s</td></tr>
<tr>
<td>XenForo</td><td>0.3MB</td><td>1.0MB</td><td>0.4s</td><td>0.1s</td><td>0.6s</td><td>0.2s</td><td>1.4s</td><td>1.5s</td><td>1.5s</td><td>1.8s</td><td>失败</td><td>失败</td></tr>
<tr>
<td>Ghost</td><td>0.7MB</td><td>2.4MB</td><td>0.1s</td><td>0.2s</td><td>0.2s</td><td>0.2s</td><td>1.1s</td><td>2.2s</td><td>1s</td><td>2.4s</td><td>1.1s</td><td>3.5s</td></tr>
<tr>
<td>vBulletin</td><td>1.2MB</td><td>3.4MB</td><td>0.5s</td><td>0.2s</td><td>0.6s</td><td>0.3s</td><td>1.1s</td><td>2.9s</td><td>4.4s</td><td>4.8s</td><td>13s</td><td>16s</td></tr>
<tr>
<td>Squarespace</td><td>1.9MB</td><td>7.1MB</td><td>0.1s</td><td>0.4s</td><td>0.2s</td><td>0.4s</td><td>0.7s</td><td>3.6s</td><td>14s</td><td>5.1s</td><td>16s</td><td>19s</td></tr>
<tr>
<td>Mastodon</td><td>3.8MB</td><td>5.3MB</td><td>0.2s</td><td>0.3s</td><td>0.2s</td><td>0.4s</td><td>1.8s</td><td>4.7s</td><td>2.0s</td><td>7.6s</td><td>失败</td><td>失败</td></tr>
<tr>
<td>Tumblr</td><td>3.5MB</td><td>7.1MB</td><td>0.7s</td><td>0.6s</td><td>1.1s</td><td>0.7s</td><td>1.0s</td><td>7.0s</td><td>14s</td><td>7.9s</td><td>8.7s</td><td>8.7s</td></tr>
<tr>
<td>Quora</td><td>0.6MB</td><td>4.9MB</td><td>0.7s</td><td>1.2s</td><td>0.8s</td><td>1.3s</td><td>2.6s</td><td>8.7s</td><td>失败</td><td>失败</td><td>19s</td><td>29s</td></tr>
<tr>
<td>Bluesky</td><td>4.8MB</td><td>10MB</td><td>1.0s</td><td>0.4s</td><td>1.0s</td><td>0.5s</td><td>5.1s</td><td>6.0s</td><td>8.1s</td><td>8.3s</td><td>失败</td><td>失败</td></tr>
<tr>
<td>Wix</td><td>7.0MB</td><td>21MB</td><td>2.4s</td><td>1.1s</td><td>2.5s</td><td>1.2s</td><td>18s</td><td>11s</td><td>5.6s</td><td>10s</td><td>失败</td><td>失败</td></tr>
<tr>
<td>Substack</td><td>1.3MB</td><td>4.3MB</td><td>0.4s</td><td>0.5s</td><td>0.4s</td><td>0.5s</td><td>1.5s</td><td>4.9s</td><td>14s</td><td>14s</td><td>失败</td><td>失败</td></tr>
<tr>
<td>Threads</td><td>9.3MB</td><td>13MB</td><td>1.5s</td><td>0.5s</td><td>1.6s</td><td>0.7s</td><td>5.1s</td><td>6.1s</td><td>6.4s</td><td>16s</td><td>28s</td><td>66s</td></tr>
<tr>
<td>Twitter</td><td>4.7MB</td><td>11MB</td><td>2.6s</td><td>0.9s</td><td>2.7s</td><td>1.1s</td><td>5.6s</td><td>6.6s</td><td>12s</td><td>19s</td><td>24s</td><td>43s</td></tr>
<tr>
<td>Shopify</td><td>3.0MB</td><td>5.5MB</td><td>0.4s</td><td>0.2s</td><td>0.4s</td><td>0.3s</td><td>0.7s</td><td>2.3s</td><td>10s</td><td>26s</td><td>失败</td><td>失败</td></tr>
<tr>
<td>Discourse</td><td>2.6MB</td><td>10MB</td><td>1.1s</td><td>0.5s</td><td>1.5s</td><td>0.6s</td><td>6.5s</td><td>5.9s</td><td>15s</td><td>26s</td><td>失败</td><td>失败</td></tr>
<tr>
<td>Patreon</td><td>4.0MB</td><td>13MB</td><td>0.6s</td><td>1.0s</td><td>1.2s</td><td>1.2s</td><td>1.2s</td><td>14s</td><td>1.7s</td><td>31s</td><td>9.1s</td><td>45s</td></tr>
<tr>
<td>Medium</td><td>1.2MB</td><td>3.3MB</td><td>1.4s</td><td>0.7s</td><td>1.4s</td><td>1s</td><td>2s</td><td>11s</td><td>2.8s</td><td>33s</td><td>3.2s</td><td>63s</td></tr>
<tr>
<td>Reddit</td><td>1.7MB</td><td>5.4MB</td><td>0.9s</td><td>0.7s</td><td>0.9s</td><td>0.9s</td><td>6.2s</td><td>12s</td><td>1.2s</td><td>∞</td><td>失败</td><td>失败</td></tr>
</tbody></table><p></p>

<p>乍一看，这个表格似乎大致正确，因为那些除非拥有超级快设备否则会感觉慢的网站，在表格中显示为慢（即，在低端设备上 <code>max(LCP*,CPU))</code> 很高）。当我调查人们认为哪些平台在我们的慢速设备上会是最快和最慢时（<a href="https://mastodon.social/@danluu/111994437263038931" rel="noopener noreferrer">Mastodon</a>，<a href="https://twitter.com/danluu/status/1761875263359537652" rel="noopener noreferrer">Twitter</a>，<a href="https://www.threads.net/@danluu.danluu/post/C3yVpfKS-RP" rel="noopener noreferrer">Threads</a>），他们通常正确地预测WordPress和Ghost会比Substack和Medium更快，而Discourse会比旧的PHP论坛如phpBB、XenForo和vBulletin慢得多。我也获取了页面（未显示）的Google PageSpeed Insights（PSI）分数，这些数字与我们数据的相关性并不那么强<abbr title="对于‘真实世界’数字，这也是因为使用慢速设备的用户实际上无法使用其中一些网站，因此他们的设备在分布中未被计入，并且PSI没有对此进行标准化">因为</abbr>少数网站成功优化了它们的PSI分数，但实际上并没有为用户加速页面。</p>

<p>如果你从未使用过这样的低端设备，总体体验是许多网站在该设备上不可用，加载任何资源密集型的东西（应用程序或大型网站）都可能导致崩溃。在资源密集型应用程序中做过于密集的操作也可能导致崩溃。虽然<a href="https://www.youtube.com/watch?v=U1JMRFQWK70" rel="noopener noreferrer">评测指出</a> <a href="https://www.youtube.com/watch?v=McawfNlydqk" rel="noopener noreferrer">你可以在<code>Tecno Spark 8C</code>上以不错的性能运行PUBG和其他3D游戏</a>，但这并不意味着该设备足够快，可以阅读现代以文本为中心的社交媒体平台或现代以文本为中心的网络论坛帖子。虽然在PUBG中可以达到<code>40fps</code>，但我们在这些网站上滚动时可以轻松看到低于<code>0.4fps</code>的情况。</p>

<p>从表格中我们可以看到，如果你有一个慢速设备，有多少网站是不可用的。所有<code>CPU占用超过10秒</code>的页面即使在加载后，体验也相当糟糕。滚动非常卡顿，经常掉到几帧每秒，有时更低。当我们点击任何链接时，延迟非常长，我们无法确定我们的点击是否真的有效。如果我们再次点击，可能会遇到可怕的情况：第一次点击被记录，然后导致第二次点击做错事情，但如果我们等待，我们通常会等待太久，因为原始点击实际上没有被记录（或者它被记录了，但不是我们认为的位置）。尽管MyBB不提供移动版网站，并且因此被谷歌惩罚（因为页面不支持移动设备），但它实际上在这些慢速移动设备上比几乎所有网站都更容易使用，因为滚动和点击实际上都能工作。</p>

<p>我们能看到的另一件事是不同设备上相对性能的巨大差异。例如，比较<code>M3/10</code>和<code>Tecno Spark 8C</code>，对于danluu.com和Ghost，<code>M3/10</code>提供了<code>Tecno Spark 8C</code>的一个大致近似（尽管danluu.com加载速度太快），但<code>Tecno Spark 8C</code>在Medium、Substack和Twitter上的CPU速度慢约三倍，在Reddit和Discourse上慢约四倍，在Shopify上则快了一个数量级。对于Wix，CPU近似值大致准确，但我们的<code>Tecno Spark 8C</code>在<code>LCP*</code>上慢了三倍多。Chrome可以方便地从你的电脑上模拟较慢的设备，这很棒，但仅仅启用Chrome的CPU限速（或使用任何现成的可用选项组合）会给出与我们在许多真实设备上得到的结果相当不同的数据。完整的原因超出了本文的范围；对于本文的目的，只需注意慢速页面在设备变慢时通常会超线性地变慢，并且一个页面的慢速并不能强烈预测另一个页面的慢速。</p>

<p>如果我们采取以网站为中心的视角而不是以设备为中心的视角来看，另一种方式是，像Discourse、Medium和Reddit这样的网站，在我们快速的<code>M3</code>和<code>M1</code>电脑上并没有使用那么多CPU，但它们在我们的<code>Tecno Spark 8C</code>上是最慢的之一（Reddit的CPU显示为<code>∞</code>，因为无论我们等待多久不交互，Reddit都使用<code>约90% CPU</code>）。Discourse有时在交互一点或只是等待一会儿后也会导致浏览器崩溃。例如，有一次，加载Discourse后，滚动两次，然后让设备静置一两分钟，浏览器就崩溃了。为了保持一致性，这在表格中没有被标记为<code>失败</code>，因为页面确实加载了，但现实地说，一个页面资源占用如此之多以至于浏览器崩溃，比表格中任何<code>失败</code>情况都糟糕得多。当我们研究<a href="https://danluu.com/web-bloat/" rel="noopener noreferrer">网络臃肿如何影响网络连接缓慢的用户</a>时，我们发现<abbr title="这里需要记住的一件事是，拥有慢速设备和慢速连接具有乘积效应。">许多网络内容对网络连接缓慢的人来说是不可用的，而慢速设备情况也没有不同</abbr>。</p>

<p>我们可以看到的另一个模式是，旧网站通常比新网站更快，那些（视觉上）看起来十年或二十年没有更新过的网站往往最快。例如，MyBB，外观最不现代、最古老的论坛，在<code>M3</code>上比Discourse<code>快3.6倍 / 5倍（LCP* / CPU）</code>，但在<code>Tecno Spark 8C</code>上，差异是<code>19倍 / 33倍</code>，考虑到整体的扩展性，可以安全地猜测在Itel P32上差异会更大（如果Discourse在如此廉价的设备上能运行的话）。</p>

<p>另一个例子是Wordpress（旧版）与更新、更时髦的博客平台如Medium和Substack。Wordpress（旧版）在我们的<code>M3 Max</code>上比Medium<code>快17.5倍 / 10倍（LCP* / CPU）</code>，比Substack<code>快5倍 / 7倍（LCP* / CPU）</code>，在我们的<code>Tecno Spark 8C</code>上分别快<code>4倍 / 19倍</code>和<code>20倍 / 8倍</code>。Ghost是这个规律的一个显著例外，它是一个现代平台（比Medium晚一年推出），但性能与旧平台相当（现代Wordpress也可以算是一个例外，但许多人可能仍然认为它是一个旧平台）。在论坛中，NodeBB似乎也有些例外（详情见附录）。</p>

<p>使用现代技术如部分加载页面然后动态加载其余内容的网站（如Discourse、Reddit和Substack），往往不如表格中的分数所显示的那么可用。虽然原则上你可以用简单的方式构建一个在廉价设备上运行良好的此类网站，但实践中，使用动态加载的网站往往足够复杂，以至于在低端设备上极其卡顿。通常很难或不可能滚动一个可预测的距离，这意味着用户有时会因为滚动过远而意外触发更多加载，导致页面卡住。许多页面实际上会在你滚动时删除你已经滚动过的部分；所有这些页面基本上都是不可用的。其他基本的Web功能，如页面搜索，也通常停止工作。具有这种动态加载的页面无法依赖简单快速的ctrl/command+F搜索，必须构建自己的搜索。其效果如何各异（这在Google文档中过去效果很好，但在过去的几个月或一年里，它加载需要很长时间，我必须在打开文档后特意等待，以避免触发浏览器无用的内置搜索；Discourse搜索在慢速设备甚至不算特别慢但也不快的设备上从未真正有效过）。</p>

<p>原则上，这些在加载时消耗大量CPU的现代页面可能正在做预处理工作，这使得页面上的后续交互比那些前期工作少的页面更快、成本更低（这是支持此类页面的常见论点），但对于测试的页面来说，情况并非如此，它们初始加载更慢，后续加载更慢，加载后也更慢。</p>

<p>为了理解为什么这种前期完成所有工作的理论想法通常不会带来更快的后续体验，谷歌的一位杰出工程师与Discourse创始人（当时的CEO）之间的这段交流是<abbr title="这位创始人在其他地方也发表过类似的言论，所以对他来说这不是一个偶然的比喻，我也认为这不是一种不寻常的思路">有启发性的</abbr>，<a href="https://danluu.com/jeff-atwood-trashes-qualcomm-engineering.png" rel="noopener noreferrer">在这段讨论中，Discourse的创始人说你应该在笔记本电脑上通过限制带宽但不限制CPU来测试移动网站</a>：</p>

<ul>
<li><b>谷歌</b>：*你*也没有慢速3G。这两个设置是配套的。同理心需要超越隧道里的iPhone XS用户。</li>
<li><b>Discourse</b>：任何iPhone 6或更新版本的手机，其速度基本上都与“平均”笔记本电脑相当。你必须明白高通在工作上有多么糟糕。如果你不相信我，去查一下。</li>
<li><b>谷歌</b>：我不需要相信你。我知道。关心这件事的人都知道这一点。我的观点是，就像不是每个人都有快速连接一样，也不是每个人都有快速手机。当然，iPhone 6在真实世界的网站上经常非常受CPU限制。但这不是重点。</li>
<li><b>Discourse</b>：我们几十年来一直在朝着无限CPU速度的趋势发展（而且在桌面端我们大约5年前就已经渐近于此），我们没有也永远不会朝着无限带宽的趋势发展。为重要的事情进行优化。我对@qualcomm没有丝毫同理心。去他妈的高通，他们工作做得太烂了。我希望他们破产，用盐把他们公司存在过的土地犁一遍，这样那里永远寸草不生。</li>
<li><b>谷歌</b>：移动设备在大多数情况下根本不受带宽限制。它们受延迟限制。即使是最新款的iPhone，在带宽受限之前就已经是CPU受限了。如果你在MacBook Pro上放慢4倍速度表现良好，情况就差不多了。</li>
<li>...</li>
<li><b>谷歌</b>：是否100%的用户都在iOS上？</li>
<li><b>Discourse</b>：有影响力且花钱的用户往往倾向于使用iOS，我告诉你……担心CPU是没意义的，在iOS上它实际上已经是无限的了，而且即使高通的SoC如此糟糕，4年内在他们尴尬的SoC上也会达到这个水平。</li>
</ul>

<p>当有人问Discourse的创始人“你为什么讨厌他们”时，他回复了一个链接，引用了<a href="https://www.anandtech.com/show/9146/the-samsung-galaxy-s6-and-s6-edge-review/5" rel="noopener noreferrer">这篇Anandtech评测</a>中的Kraken和Octane基准测试，其中高通芯片的性能分别是当时苹果芯片的74%和85%。</p>

<p>Discourse的创始人兼当时的CEO认为高通的移动性能令人尴尬，并且认为这是如此不可接受，以至于他认为高通工程师都应该因此丢掉工作，因为他们只提供了<abbr title="我认为引用一个更低的数字可能是合理的，但我使用的是他引用的数字，而不是我会引用的数字">苹果性能的74%到85%</abbr>。苹果拥有我所认为的史上最伟大的性能团队之一。理智的人可能会不同意这一点，但至少必须认为他们是世界级的团队。因此，生产出<abbr title="回想一下，在Tecno Spark 8上，Discourse比MyBB慢33倍，而MyBB在性能方面并没有特别优化">性能只有这个史上伟大团队的74%到85%的产品，被认为是足以让人丢掉工作的尴尬</abbr>。</p>

<p>这里展现了两种态度，我在许多软件从业者身上看到过。首先，CPU速度是无限的，不应该担心CPU优化。其次，应该期望硬件带来巨大的速度提升，硬件工程师未能达到这些提升的唯一原因是惊人的无能，因此应该责怪硬件工程师而不是软件工程师。Donald Knuth在以下内容中表达了类似的情绪</p>

<blockquote>
<p>我可能得就目前多核架构的潮流表达一下我的个人不满。在我看来，这更像是硬件设计师已经江郎才尽，试图把未来摩尔定律终结的责任推到软件写手身上，通过给我们提供只能在少数关键基准测试上运行更快的机器！如果整个多线程的想法最终被证明是一个失败，比“Itanium”方案还糟——那个方案本应如此了不起——直到最终发现所希望的编译器基本上无法编写出来，我也不会感到惊讶。让我这样说：在过去的50年里，我写了超过一千个程序，其中许多都有相当大的规模。我想不出其中哪怕五个程序会因为并行或多线程而得到显著增强。当然，例如，多处理器对TeX毫无帮助……我知道并行性的重要应用是存在的——渲染图形、破解代码、扫描图像、模拟物理和生物过程，等等。但所有这些应用都需要专用代码和特殊技术，这些技术每隔几年就需要进行大幅度的修改。即使我足够了解这些方法，可以在TAOCP中写到它们，我的时间也将被大量浪费，因为很快就没有人有理由去读那些部分了……我今天使用的机器有两个处理器。我只有在同时运行两个独立任务时才能同时使用它们；这很好，但这每周只发生几分钟。</p>
</blockquote>

<p>在Discourse的案例中，如果一个硬件工程师无法达到一个史上伟大团队90%的性能，那么他就是令人尴尬的、不配拥有工作的人，但作为软件工程师，交付性能仅为像MyBB这样的非高度优化应用程序的3%却没问题。在Knuth的案例中，<abbr title="宽松地使用这个术语，包括材料科学家等，这与Knuth的评论一致">硬件工程师</abbr>几十年来每十年为程序员提供100倍的性能提升，而程序员几乎或根本不需要做任何工作。当这种提升放缓，程序员必须适应以利用新硬件时，硬件工程师就“江郎才尽”了，但学习一些利用当前硬件的“新”（1970和1980年代的）想法却被认为是浪费时间。而且<a href="https://www.patreon.com/posts/54329188" rel="noopener noreferrer">我们之前讨论过艾伦·凯声称硬件工程师“不成熟”且“未受过教育”，没有在做“真正的工程”，以及如果我们听从艾伦·凯的“成熟”想法，我们将获得1000倍的速度提升</a>。</p>

<p>程序员期望硬件解决他们所有问题，然后当这没有发生时，就将问题推给用户，解释为什么程序员不需要为帮助用户做任何事情，这是相当常见的。人们可能会问的一个问题是程序员给了我们多少性能改进。有些算法改进带来了巨大的速度提升，但正如我们上面指出的，Discourse，当今增长最快的论坛软件，似乎给我们带来了大约<code>1000000倍</code>的性能下降。</p>

<p>上面展示的另一种常见态度是，不富有的用户无关紧要。当被问及是否100%的用户都在iOS上时，Discourse的创始人说“有影响力且花钱的用户往往倾向于使用iOS，我告诉你”。我们在<a href="https://tonsky.me/blog/js-bloat/" rel="noopener noreferrer">Tonsky的JavaScript臃肿文章</a>的评论中到处都能看到这种态度，人们表达<a href="https://danluu.com/cocktail-ideas/" rel="noopener noreferrer">鸡尾酒会式的情绪</a>，比如“手机应用有几百兆，为什么我们要纠结于几兆的Web应用？非洲饥民可以下载Android应用但不能下载Web应用？得了吧”以及“gitlab的用户肯定不会穷到有慢速设备，认真点吧”（为了长度而意译）。</p>

<p>但当我们看看在非洲下载的应用大小时，我们看到那些没有高端设备的人使用像Facebook Lite（几兆）这样的应用，并且通常使用大小从个位数到低两位数兆字节不等的应用。应用开发者关心其应用大小有多个原因。一个是手机上可用的总存储空间；如果你观察真实用户安装应用，他们经常必须删除和卸载东西才能安装新应用，所以较小的大小不仅更容易安装，而且在用户寻找更多空间时被卸载的可能性也更低。另一个原因是，如果你查看应用大小和使用情况的数据（我不知道有公开的此类数据；如果你有可以引用的公开数据，请提供），当大型应用增加大小和内存使用量时，它们会获得更多崩溃，这降低了用户留存、增长和参与度；相反，当它们优化大小和内存使用量时，崩溃减少，用户留存、增长和参与度提高。</p>

<p><a href="https://infrequently.org/2024/01/performance-inequality-gap-2024/" rel="noopener noreferrer">Alex Russell指出，iOS在印度（一个14亿人口的市场）占7%的市场份额，在拉丁美洲（一个6亿人口的市场）占6%</a>。尽管Discourse的创始人说这些不是重要的“有影响力的用户”，但他们仍然是真实的人。Alex进一步指出，根据Windows遥测数据（涵盖了绝大多数桌面用户），大多数笔记本电脑/台式机用户使用的是低端机器，这些机器可能比现代iPhone慢。</p>

<p>关于没有程序员使用慢速设备的说法，我认识很多使用旧而慢的手提设备的人。他们中的许多人甚至不真正贫穷；他们只是不明白为什么（例如）他们的孩子需要一个超快的设备，也不明白现代网络在慢速设备上运行不佳的程度。毕竟，这个“慢”设备可以玩3D游戏，并且（在适当的操作系统下）可以编译像Linux或Chromium这样的代码库，为什么这个设备不能与像gitlab这样的网站交互呢？</p>

<p>与Discourse创始人声称所有Android用户几年内都会拥有某种超快Android设备的说法相反，自他评论以来已经六年了，几乎每个使用手机的人都将拥有高速设备至少还需要十年，这可能轻易需要二十年或更长时间。如果你查找Discourse的市场份额统计数据，它极其成功；它似乎是世界上增长最快的论坛软件，且遥遥领先。世界上增长最快的论坛软件是由一个组织创建的，该组织当时的领导者愿意表示他并不真正关心那些不是“有影响力的花钱用户”，没有“无限CPU速度”的用户，其影响是许多论坛现在对那些没有足够财富购买实际上具有无限CPU设备的人是无法访问的。</p>

<p>如果Discourse的创始人是个异类，这不会是一个太大的问题，但他只是在明确表达许多程序员隐含的假设，这就是为什么我们看到这么多现代网站在你购买相当于低收入国家收入水平的新款当前一代iPhone时是不可用的。</p>

<p><i>感谢Yossi Kreinen、Fabian Giesen、John O'Nolan、Joseph Scott、Loren McIntyre、Daniel Filan、@acidshill、Alex Russell、Chris Adams、Tobias Marschner、Matt Stuchlik、@gekitsu@toot.cat、Justin Blank、Andy Kelley、Julian Lam、Matthew Thomas、avarcat、@eamon@social.coop、William Ehlhardt、Philip R. Boulain和David Turner的评论/更正/讨论。</i></p>

<h3 id="appendix-gaming-lcp">附录：游戏化LCP</h3>

<p>我们在上面提到我们使用了<code>LCP*</code>而不是<code>LCP</code>。这是因为<code>LCP</code>基本上衡量的是最大变化发生的时间。当这个指标没有被刻意以不利于用户的方式“游戏化”时，它是一个很好的指标，但随着更多人对其进行游戏化，这个指标作为实际用户体验的代表性降低了。</p>

<p>在不那么明目张胆的情况下，人们做了一些小的优化，这些优化提高了<code>LCP</code>，但对实际用户体验几乎没有改善或根本没有改善。</p>

<p>在更明目张胆的情况下，开发者会故意尽快在页面上闪现一个非常大的变化，通常是一个对用户毫无价值的加载屏幕（实际上因为这样做增加了完成工作的总量和页面加载总时间，所以是负面价值），然后他们小心翼翼地避免进行任何足够大的更改，以至于后续的任何更改都不会被标记为<code>LCP</code>。</p>

<p>出于同样的原因<abbr title="大众汽车没有公开讨论它如何对其排放数据进行游戏化">大众汽车没有公开讨论它如何对其排放数据进行游戏化</abbr>，开发者倾向于避免公开讨论这类<code>LCP</code>优化。一个例外是Discourse，他们<a href="https://meta.discourse.org/t/introducing-discourse-splash-a-visual-preloader-displayed-while-site-assets-load/232003" rel="noopener noreferrer">公开宣布了这种<code>LCP</code>优化，并有其开发者和当时的CTO（现在的CEO）的评论</a>，指出他们的新“Discourse Splash”功能在部署后极大地降低了网站的<code>LCP</code>。然后，当开发者询问为什么他们的<code>LCP</code>很高时，Discourse开发者的标准建议是保持元素小于“Discourse Splash”，这样<code>LCP</code>时间戳就会从这个为优化<code>LCP</code>而抛出的无用元素计算，而不是从任何与用户相关的实际元素计算时间戳。<a href="https://meta.discourse.org/t/theme-components-and-largest-contentful-paint-lcp/258680" rel="noopener noreferrer">这里是Discourse的一个典型的官方评论</a></p>

<blockquote>
<p>如果你的横幅大于我们为“引入Discourse Splash - 站点资源加载时显示的视觉预加载器”使用的元素，你在LCP方面会遇到麻烦。</p>
</blockquote>

<p>Discourse的官方回应是，你应该确保你的内容不会触发<code>LCP</code>测量，而是应该使用我们的加载动画时间戳来计算<code>LCP</code>。</p>

<p>有用内容的<code>LCP</code>与Chrome测量的<code>LCP</code>比率最极端的网站是：</p>

<ul>
<li>Wix

<ul>
<li><code>M3</code>: <code>6</code></li>
<li><code>M1</code>: <code>12</code></li>
<li><code>Tecno Spark 8C</code>: <code>3</code></li>
<li><code>Itel P32</code>: <code>N/A</code> <code>(失败)</code></li>
</ul></li>
<li>Discourse:

<ul>
<li><code>M3</code>: <code>10</code></li>
<li><code>M1</code>: <code>12</code></li>
<li><code>Tecno Spark 8C</code>: <code>4</code></li>
<li><code>Itel P32</code>: <code>N/A</code> <code>(失败)</code></li>
</ul></li>
</ul>

<p>虽然我们还没有讨论其他指标的游戏化，但似乎一些网站也对其他指标进行游戏化和“优化”，即使这对用户没有好处。</p>

<h3 id="appendix-the-selfish-argument-for-optimizing-sites">附录：优化网站的自私理由</h3>

<p>这将取决于网站的规模及其性能，但当我为我工作过的大公司查看这些数据时，提高网站和应用性能的价值是惊人的。它在A/B测试中是可测量的，并且在<abbr title="你将一部分用户长期保持在A/B测试的旧分支上，有时一年或更长时间，以观察变更的长期影响">长期保持</abbr>中，它也是对增长和留存影响相对较大的干预措施之一（许多干预措施测试效果良好，但长期看效果不佳，而性能改进往往长期看效果更好）。</p>

<p>当然，你可以从直接数字中看到这一点，但当你查看数据时，也可以从很多方面间接地看到这一点。一个角度是（仅举一例），在Twitter，用户观察到的p99延迟在印度以及许多非洲国家（即使排除相对富裕的埃及和南非）约为<code>60秒</code>，在美国也约为<code>60秒</code>。当然，在整个群体中，美国的人们拥有更快的设备和连接，但在每个国家，都有足够多的拥有慢速设备或连接的用户，其限制因素实际上是用户的耐心，而不是底层的人口级设备和连接分布。即使你不关心尼日利亚或印度的用户，只关心美国的广告收入，为低端设备和连接提高性能的影响也足够大，我们可以在全球以及美国的收入A/B测试中轻易看到影响，尤其是在长期保持中。而且你也会在拥有快速设备的用户中看到影响，因为一个将“低端”设备用户的延迟从<code>60秒</code>提高到<code>50秒</code>的改动，可能将高端设备用户的延迟从<code>5秒</code>提高到<code>4.5秒</code>，这也会影响收入、增长和留存数字。</p>

<p>由于<a href="https://danluu.com/bad-decisions/" rel="noopener noreferrer">超出本文范围的各种原因</a>，在大多数我工作过的大公司里，这类无聊、可量化、驱动增长和收入的工作相比那些最终在长期保持中显示很少或没有影响的闪亮产品工作，更难获得资助。</p>

<h3 id="appendix-designing-for-low-performance-devices">附录：为低性能设备设计</h3>

<p>当使用慢速设备或任何低带宽和/或连接性差的设备时，体验最好的往往是那些将大量内容一次性加载到静态页面中的。如果图像具有正确的宽度和高度属性以及alt文本，那会非常有帮助。渐进式图像（如渐进式jpeg）并没有特别帮助。</p>

<p>在具有高带宽的慢速设备上，任何轻量级、静态的页面都能很好地工作，轻量级的动态页面如果为性能设计得当也能工作。重型的动态页面注定失败，除非页面重量不会导致页面复杂。</p>

<p>在低带宽和/或连接性差的情况下，轻量级页面没问题。对于重页面，我体验最好的时候是触发页面加载后，去做其他事情，然后在加载完成（或至少HTML和CSS加载完成）时回来。然后我可以为每个可能想阅读的链接打开一个新标签页，然后在等待它们加载时做其他事情。</p>

<p>现代网站做的许多优化，比如导致你在页面向下滚动时加载更多的部分加载，以及随之而来的搜索劫持（因为如果页面没有完全加载，浏览器的内置搜索就毫无用处），导致原本有效的交互模型变得无效，使页面交互起来非常痛苦。</p>

<p>仅举一例，许多人指出Substack对他们来说表现不佳，因为它进行部分页面加载。<a href="https://danluu.com/substack.mp4" rel="noopener noreferrer">这里是@acidshill在iPhone 8上加载Substack文章然后滚动的视频</a>，其中帖子的<code>LCP</code>相当快，但如果你想滚动过标题，你必须等待<code>6秒</code>下一个页面加载，然后再次滚动时，你可能又得等待<code>1秒</code>到<code>2秒</code>：</p>

<p>作为相反方法的一个例子，我尝试加载一些相当大的纯HTML页面，如<a href="https://danluu.com/diseconomies-scale/" rel="noopener noreferrer">https://danluu.com/diseconomies-scale/</a>（<code>网络传输0.1 MB</code> / <code>原始0.4 MB</code>）和<a href="https://danluu.com/threads-faq/" rel="noopener noreferrer">https://danluu.com/threads-faq/</a>（<code>网络传输0.4 MB</code> / <code>原始1.1 MB</code>），即使在慢速设备上，这些页面对我来说仍然相当可用。<code>1.1 MB</code>似乎大于最优值，将其分成几个不同的页面在低端设备上会更好，但包含<code>1.1 MB</code>文本的单个页面在慢速设备上比大多数现代网站运行得更好。虽然HTML页面过大导致浏览器无法真正处理时会遇到麻烦，但对于具有正常内容量的页面，通常直到你有<a href="https://nolanlawson.com/2023/01/17/my-talk-on-css-runtime-performance/" rel="noopener noreferrer">复杂的CSS负载</a>或JS时，页面才会开始给慢速设备带来问题。下面，我们测试了一些相对简单的页面，其中一些有相当多的媒体（一种情况下有<code>14 MB</code>），发现只要它们保持简单，这些页面就能正常工作。</p>

<p>Chris Adams还指出，使用屏幕阅读器的盲人用户经常报告动态加载使他们的体验变得更糟。就像为了提高性能而进行的动态加载一样，虽然这可以做得很好，但通常要么做得不好，要么与太多其他复杂性捆绑在一起，结果比一个简单的页面更糟。</p>

<p>@Qingcharles指出了另一个无障碍问题——他合作的（监狱）假释者获得“生命线”手机，这些通常是低端设备。快速搜索显示，在2024年，有些人会获得iPhone 6或iPhone 8，但也有许多设备比Itel P32更低端，更不用说Tecno Spark 8C了。他们还获得数据非常有限的套餐，然后当数据用完时，有些人“无法填写任何工作、福利的表格，也无法使用地图导航去任何地方”。</p>

<p>对于那些确实进行预处理并在低端设备上为你提供良好体验的网站，Andy Kelley指出了一个似乎在慢速设备上运行良好的网站的例子（尽管在非常慢的连接上可能会很吃力），<a href="https://ziglang.org/documentation/master/std/" rel="noopener noreferrer">Zig标准库文档</a>：</p>

<blockquote>
<p>我做出了一个有争议的决定，让程序一开始获取所有源代码，然后在本地进行所有内容渲染。理论上，这很消耗CPU，但实践中……即使是那些旧手机也有非常快的CPU！</p>
</blockquote>

<p>在<code>Tecno Spark 8C</code>上，这消耗了<code>4.7秒</code>的CPU时间，之后相当响应迅速（相对于该设备——<a href="https://danluu.com/input-lag/" rel="noopener noreferrer">当然iPhone响应快得多</a>）。点击导致链接相当快地加载，滚动也可以正常工作（有点卡顿，但在这个设备上几乎没有什么是真正流畅的）。这似乎就是人们所说的那种情况，如果你提供重载荷可以获得更好的性能，但这样的例子在低端设备上真正提高性能的并不多。</p>

<h3 id="appendix-articles-on-web-performance-issues">附录：关于网络性能问题的文章</h3>

<ul>
<li>2015: Maciej Cegłowski: <a href="https://idlewords.com/talks/website_obesity.htm" rel="noopener noreferrer">网站肥胖危机</a>

<ul>
<li>大小: <code>1.0 MB</code> / <code>1.1 MB</code></li>
<li><code>Tecno Spark 8C</code>: <code>0.9s</code> / <code>1.4s0.7s1.1s2.9s4.4s4.8s13s16s

Squarespace1.9MB7.1MB0.1s0.4s0.2s0.4s0.7s3.6s14s5.1s16s19s

Mastodon3.8MB5.3MB0.2s0.3s0.2s0.4s1.8s4.7s2.0s7.6s失败失败

Tumblr3.5MB7.1MB0.7s0.6s1.1s0.7s1.0s</code></li></ul></li></ul><p><em>由 mimo-v2.5 模型翻译，花费 42808 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/slow-device/</link>
      <guid isPermaLink="false">https://danluu.com/slow-device/</guid>
      <pubDate>Sat, 16 Mar 2024 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>欺诈、垃圾信息、客服和内容审核中的规模不经济</title>
      <description>[AI 摘要] 文章论证了大型平台在欺诈、垃圾信息、内容审核和客户服务方面存在规模不经济，指出大公司常常选择不投入足够资源来有效解决这些问题。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章论证了大型平台在欺诈、垃圾信息、内容审核和客户服务方面存在规模不经济，指出大公司常常选择不投入足够资源来有效解决这些问题。</div><p>如果我问自己这样一个问题：“我想买一张SD卡，我该信任谁卖给我正品而不是假货？亚马逊还是我当地的百思买？”答案当然是我更信任当地的百思买，而不是以销售假冒SD卡而臭名昭著的亚马逊。如果我问更值得信任谁，我当地的知名电子产品店（Memory Express、B&amp;H Photo等），我更信任我当地的知名电子产品店。它们不仅<strong>不太可能</strong>卖给我比百思买更少的假冒产品，而且万一他们卖给我假货，服务也可能会更好。</p>
<p>同样地，假设我问自己这样一个问题：“在哪种平台上，我遇到诈骗、垃圾信息、欺诈内容等的比率更高，是[较小的平台]还是[更大的平台]？”通常答案是[更大的平台]。当然，较小的平台总量更多，差异也更大，所以我可以选择刻意使用一个更差的小平台，但我选择的是好的选项而不是坏的选项。在每种规模级别中，较小的平台通常都更好。例如，对比Signal和WhatsApp，我从未收到过垃圾Signal消息，而我相当定期地收到垃圾WhatsApp消息。或者，如果我对比可能阅读科技内容的地方，如果我对比无人知晓的小型论坛和lobste.rs，lobste.rs的坏内容率（指我看到的信息中占的比例，而非绝对数量）要略高一点，因为私人论坛上是零，lobste.rs上则非常低但非零。然后，如果我将lobste.rs与一个稍大的平台（如Hacker News或mastodon.social）进行比较，这些平台的诈骗/垃圾信息/欺诈内容率又（同样非常轻微地）更高。然后如果我将其与中等规模的社交媒体平台（如reddit）相比，reddit的坏内容率明显更高且可被察觉。最后，如果我将reddit与像YouTube、Facebook、<a href="https://danluu.com/seo-spam/" rel="noopener noreferrer">谷歌搜索结果</a>这样的巨型平台相比，这些更大的平台拥有更高比率的诈骗/垃圾信息/欺诈内容。而且，与SD卡的例子类似，随着平台规模的扩大，获得良好客服支持的可能性也会降低。如果因错误而被暂停或禁止使用该账户，账户获得恢复的可能性也会随着平台的增大而变得更差。</p>
<p>我认为说“总体而言，很多事情随着平台变大而变糟”是无可争议的。例如，当我进行<a href="https://twitter.com/danluu/status/1570604630350106624" rel="noopener noreferrer">一次Twitter投票，看看我认识的人怎么看</a>时，只有2.6%的人认为巨型公司平台拥有最好的内容审核和垃圾信息/欺诈过滤。作为参考，在一次调查中，9%的美国人表示疫苗会植入微型芯片，12%的人表示登月是假的。虽然调查对象不同，但随机美国人更可能认为登月是假的，而技术人员则不太可能认为最大的公司在反欺诈/反垃圾/内容审核方面做得最好。</p>
<p>然而，在过去五年里，我注意到越来越多的人提出相反的主张，即只有大公司才能进行像样的内容审核、垃圾信息过滤、欺诈（和假冒）检测等。我们研究了其中一个例子，<a href="https://danluu.com/seo-spam/" rel="noopener noreferrer">当时我们考察了搜索结果</a>，一位谷歌工程师说</p>
<blockquote>
<p>有人试图辩称，如果搜索领域更具竞争性，有很多小供应商而不是像现在这样的三大巨头，那么它可能*更*能抵抗基于机器学习的SEO滥用。</p>
<p>而且……听着，如果*谷歌*目前都无法跟上，那么市场份额5%的“小先生”又怎么能做到呢？</p>
</blockquote>
<p>一位意见领袖回应道</p>
<blockquote>
<p>大约95%的时候，当有人声称某个小而独立的公司能在某件困难的事情上比市场领导者做得更好时，这仅仅是自我安慰。规模经济运作得相当好！</p>
</blockquote>
<p><a href="https://danluu.com/seo-spam/" rel="noopener noreferrer">但当我们查看实际结果时发现，在我们考察的搜索引擎中，市场份额0.0001%的“小先生”最能抵抗SEO滥用（并且相当不错），市场份额0.001%的“小先生”对SEO滥用有一定抵抗力，而谷歌和必应则充斥着SEO滥用，经常将人们直接引导至各种诈骗</a>。电子邮件方面也发生了类似的情况，我经常听说由于垃圾信息负担而自己管理电子邮件是不可能的，<a href="https://mastodon.social/@danluu/111736581992007404" rel="noopener noreferrer">但人们一直这样做，并且通常取得与Gmail相当或更好的效果</a>，主要问题在于与大型公司邮件服务器的交互，它们错误地禁止了他们的小型电子邮件服务器。</p>
<p>大约在我开始看到很多声称“你需要规模才能进行内容审核、反垃圾、反欺诈等”的评论时，<a href="https://twitter.com/danluu/status/1179449106877431808" rel="noopener noreferrer">扎克伯格回应伊丽莎白·沃伦呼吁拆分大型科技公司的言论，声称拆分科技公司会使内容审核问题严重得多</a>：</p>
<blockquote>
<p>“拆分这些公司，无论是Facebook、谷歌还是亚马逊，实际上并不能解决问题，”扎克伯格说，“而且，你知道，它并不能让选举干预的可能性降低。它实际上增加了可能性，因为现在公司之间无法协调和合作。它并不能让仇恨言论或类似问题发生的可能性降低。它增加了可能性，因为现在……我们正在投入和建设的所有流程，现在都更碎片化了。</p>
<p>这就是为什么Twitter无法像我们那样做好工作。我的意思是，他们面临的问题，在定性层面上，和我们一样。但他们无法投入足够的资金。我们在安全方面的投入比他们整个公司的收入还多。[笑声]是的，我们运营的规模更大，但他们面临的问题并非在本质上有所不同。他们面临着和我们所有相同类型的问题。”</p>
</blockquote>
<p>论点是，你需要大量资源才能做好内容审核，而较小的公司，当时价值约300亿美元的Twitter规模公司，无法调配必要的资源来做好内容审核。我当时觉得这个说法相当可笑，因为在收购Twitter之前，我在Facebook上看到明显诈骗内容的比率远高于Twitter。<a href="https://twitter.com/danluu/status/1584615878800576512" rel="noopener noreferrer">例如，当我在假日购物季点击Facebook广告时，大多数都是诈骗</a>，而虽然Twitter也有诈骗广告，但其严重程度与Facebook不在一个量级。而且不止我一个人——设计过Facebook早期举报系统并领导过一些主要信任与安全工作的Arturo Bejar也注意到了类似的事情（详见脚注<sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">2</a></sup>）。</p>
<p>扎克伯格似乎喜欢上述推理路线，他在其他地方也提出过类似的论点，<a href="https://stratechery.com/2021/an-interview-with-mark-zuckerberg-about-the-metaverse/" rel="noopener noreferrer">例如这里</a>，就在Meta的内部文件显示其每天向10万未成年人暴露性虐待图像的同一年：</p>
<blockquote>
<p>在某种程度上，当我在宿舍刚开始创业时，我们显然不可能有1万名或4万名员工做内容审核，而且当时的人工智能能力也远不足以主动发现大量有害内容。到了某个阶段，随着公司规模扩大，才开始有可能做更多这方面的工作</p>
</blockquote>
<p>这里的修辞手法是假设Facebook在扎克伯格的宿舍刚起步时需要1万名或4万名员工做内容审核。比“宿舍版Facebook”更大的服务现在就能并且确实在做比今天的Facebook更好的内容审核，通常只需一名内容审核员，而且往往是兼职的。但随着人们更多地讨论对大型科技公司采取真正的反垄断行动，科技巨头的创始人和高管加大了反反垄断的言论，声称如果最大的公司被拆分成2015年或2010年最大的科技公司那样规模，人类将面临各种灾难。这种推理似乎开始有点流行了，因为我看到越来越多的大公司员工提出非常相似的理由。自从1979年IBM培训手册写道</p>
<p><b></b></p><b>
<blockquote>
<p>计算机绝不能被追究责任</p>
<p>因此计算机绝不能做出管理决策</p>
</blockquote>
</b><b></b><p><b></b></p>
<p>现在，对于许多关键决策，正是计算机可以做出大部分决策，而缺乏问责制似乎最终成为了一种特性，而非缺陷。</p>
<p>但不幸的是，对于扎克伯格的论点<sup id="fnref:F"><a href="#fn:F" rel="noopener noreferrer">3</a></sup>，至少有三个主要方面存在规模不经济占主导地位的情况。第一，对于几乎所有人都同意是坏材料的东西（如比特币诈骗、假冒药品垃圾信息、<a href="https://danluu.com/seo-spam/" rel="noopener noreferrer">虚假天气预报</a>、成年人向儿童发送其生殖器照片等），大型平台做得比小型平台更差。第二，对于用户来说，随着公司变大，错误的成本更高，也更难修复，因为支持通常变得更差。<a href="https://danluu.com/impossible-agree/" rel="noopener noreferrer">第三，随着平台规模扩大，会有更大比例的用户强烈反对平台上应该允许什么内容</a>。</p>
<p>关于第一点，虽然大公司确实拥有更多资源，但认为“因为他们资源最多，所以内容审核最好”的“鸡尾酒会式想法”受到了一个同样简单化的观点的反驳：“因为他们是最大的靶子，所以内容审核会最差”，或者“因为他们将因组织扩大和问题领域扩展时发生的标准规模不经济而导致最严重的碎片化，所以内容审核会最差”。公司拥有更多资源还是这些其他因素占主导地位，这个问题太复杂了，无法从理论上解决，但我们可以从经验上观察结果。至少在<a href="https://mastodon.social/@danluu/109919312448235105" rel="noopener noreferrer">大公司选择投入内容审核、垃圾信息等方面的资源水平上</a>，更大的靶子和其他与规模相关的问题占据了主导地位。</p>
<p>虽然这些公司利润丰厚，可以投入足够的资源来显著减少这个问题，但他们选择不这样做。例如，在我写这句话的前一年，Meta截至2023年12月的税前利润为470亿美元。如果Meta拥有其一位朋友工作的电力公司的那种内部愿景声明（“可靠的能源，低成本，服务于世代”，）并像那家电力公司那样运作，试图为用户创造良好体验而不是最大化利润加创造元宇宙，他们本可以将花在元宇宙上的500亿美元用于内容审核平台和技术，然后在每个国家以3万美元/年的薪酬（这在大多数雇佣内容审核员的国家会是非常好的收入，让他们可以挑选雇佣对象）雇佣160万全职员工，用于处理升级问题和支持等，大约每几千名用户就有一名额外的内容审核员或支持员工（当然，规模不经济也适用于管理这么多人）。我并不是说Meta或谷歌应该这样做，只是当大科技公司的人说<a href="https://news.ycombinator.com/item?id=38614042" rel="noopener noreferrer">类似这样的话</a>：“这些系统必须完全自动化，因为没有人能在我们的规模上负担得起运行手动系统的成本”，真正想说的更接近于：“如果我们雇佣足够多的胜任人员来手动审查我们系统应该标记为模棱两可的案件，我们将无法每年赚取数十亿美元的利润，所以我们满足于在不影响利润的情况下所能得到的结果”。<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">4</a></sup>有人可以为这个选择辩护，但这确实是一个选择。</p>
<p>同样，对于规模经济优势的说法也是如此。有些领域确实存在规模经济，能为用户带来更好的体验。例如，当我们研究<a href="https://danluu.com/nothing-works/" rel="noopener noreferrer">为什么买到运行良好的东西这么难</a>时，我们注意到亚马逊的规模经济使他们能够建立自己的包裹递送服务，虽然有缺陷，但仍然比其他可用服务更可靠（而且自从他们增加了用户可以对每次送货进行评分的功能后，这一点得到了进一步改善，而其他主要的包裹递送服务都没有这个功能）。同样，苹果的规模和垂直整合使他们能够<a href="https://mastodon.social/@danluu/111064942895424216" rel="noopener noreferrer">组建历史上最伟大的性能团队之一</a>（通过<a href="https://danluu.com/input-lag/" rel="noopener noreferrer">设备延迟</a>等方式），不仅在基准测试中碾压竞争对手，而且以直到最近才有人测量的方式提供了更好的用户体验。一个更普通的规模经济例子是，饼干和其他适合运输的食品在亚马逊上比在我当地的杂货店更便宜。很容易就能列举出规模经济使用户受益的方式，但这并不意味着我们应该假设规模经济在所有领域都占主导地位。虽然这超出了本文的范围，但如果我们要讨论用户在公司更大还是更小时过得更好，我们应该看看随着公司变大，什么变好了，什么变糟了，而不是仅仅假设因为有些事情变好了（或反之），所有事情都会变好。</p>
<p>回到关于巨型公司拥有最多资源用于内容审核、垃圾信息、反欺诈等的论点，与现实情况——他们选择将这些资源用于其他地方，比如在元宇宙上砸500亿美元而不是雇佣他们负担得起的160万内容审核和支持员工——的对比，值得看看实际投入了多少努力。Meta在缅甸的参与是一个很好的案例研究，因为Erin Kissane写了一篇相当详细的<a href="https://erinkissane.com/meta-in-myanmar-full-series" rel="noopener noreferrer">4万字的事件回顾</a>。整个事件是一个庞大而复杂的问题（<a href="#appendix-erin-kissane-on-meta-in-myanmar" rel="noopener noreferrer">更多讨论见附录</a>），但就本文的主要主题而言，关键要素是：存在一个绝大多数人都同意应该是最高优先级的内容审核和支持问题，并且尽管Meta的各级员工（工程师、总监、副总裁、高管等）反复收到极其严重和紧急的警告，但几乎没有为这个问题投入资源，而内部文件显示，只有一小部分达成共识的坏内容被他们的系统捕捉到（大约百分之几）。我认为这并非Meta独有，这与我在其他大型科技公司的经验相符，无论作为其产品的用户还是员工。</p>
<p>举个较小规模的例子，我一个熟人的Facebook账户被入侵，现在被用来进行比特币诈骗。这个人的名字叫Samantha K.，某个骗子做了足够多的诈骗活动，以至于他们甚至没有正确阅读她的名字，并生成了非常明显的伪造照片，照片中有人举着一个牌子，解释“Kamantha”如何帮助他们赚了成千上万美元。这是“黑客”的常见做法，我在FB上的另一个熟人也报告说他们的账户发生了同样的事情，他们既无法恢复旧账户，也无法让账户被封禁，尽管该账户持续发布明显的诈骗信息。</p>
<p>相比之下，在lobste.rs上，我从未见过这样的诈骗，版主Peter Bhat Harkins说据他所知从未有过一个。在Mastodon上，我认为我可能在我的feed、回复或提及时见过一次。当然，Mastodon足够大，如果你特意去找，可以找到一些诈骗，但每条信息和每个用户的比率都很低，作为普通用户你不应该会遇到它们。在Twitter（收购前）或reddit上，中等频率，也许在我的正常feed里平均每几周一次。在Facebook上，我<a href="https://twitter.com/danluu/status/1584615878800576512" rel="noopener noreferrer">一直</a>看到这样的东西；每个购物季我都会看到明显的假冒消费品网站诈骗，而比特币诈骗，无论是来自广告还是账户接管，全年都在发生。<a href="https://news.ycombinator.com/item?id=38613594" rel="noopener noreferrer">许多人指出他们不再费心举报这类诈骗了，因为他们观察到Facebook不会对他们的举报采取行动</a>。<a href="https://lerner.co.il/2023/10/19/im-banned-for-life-from-advertising-on-meta-because-i-teach-python/" rel="noopener noreferrer">与此同时，Reuven Lerner因在Facebook上投放关于Python和Pandas课程的广告而被终身禁止投放广告</a>，似乎是因为Facebook系统“认为”Reuven在投放<a href="https://news.ycombinator.com/item?id=37941905" rel="noopener noreferrer">与动物交易相关的内容</a>（而不是编程）。这就是扎克伯格声称任何小公司都无法匹敌的内容审核和垃圾信息控制的保真度。顺便说一句，我并不是特意针对Meta；如果你想要一些稍有不同的例子，你可以<a href="#google" rel="noopener noreferrer">在附录中查看Google的例子</a>，其中列出了自动化系统在Google出错的上百个案例。</p>
<p>这又回到了经验问题，因为所有这些关于规模经济允许巨型公司投入更多资源解决该问题的说法，都取决于公司是否选择部署这些资源。没有理论力量迫使公司在这些领域部署资源，所以我们无法从理论上推理。但我们可以观察到，即使在人们通常会同意问题显然应该属于高优先级的情况下（比如Meta在缅甸），部署的资源也不足以匹配问题。当然，对于优先级不那么明显的问题，资源同样没有部署。</p>
<p>关于第二个问题，客户支持，科技圈里有个说法：要获得大平台用户的支持，唯一的办法是在社交媒体上发帖引爆舆论，或者认识内部人员。这加剧了内容审核、诈骗检测、反欺诈等等方面的问题，因为如果支持良好，这些问题本可以得到缓解。</p>
<p>普通的客户支持渠道是个笑话，你要么收到千篇一律的模板拒绝，要么陷入卡夫卡式的噩梦，然后收到模板拒绝。例如，当<a href="https://twitter.com/craig1black/status/1645649300167495681" rel="noopener noreferrer">Adrian Black因冒充Adrian Black而被YouTube封禁</a>（明确地说，他被封是因为冒充自己，而不是同名的其他人），在申诉后，他收到了这样的回复：</p>
<blockquote>
<p>很遗憾，我们这边无法再做更多。您的账户暂停和申诉都经过了非常仔细的审查，决定是最终的。</p>
</blockquote>
<p><a href="https://twitter.com/danluu/status/1308215389344600066" rel="noopener noreferrer">在另一个Google支持的故事中，Simon Weber在试图获取申报税款所需信息时，被Google支持部门来回推诿</a>：</p>
<blockquote>
<p>自2018年4月以来（这是在2020年9月写的），我的扩展程序（以及我认为所有扩展程序商家？）的会计数据导出一直有问题。我不得不让纽约州总检察长给他们写了一封信，他们才真的回应我的支持请求，这样我才能正确申报税款。</p>
</blockquote>
<p>还有一次，<a href="https://twitter.com/PointCrow/status/1587084876741689345" rel="noopener noreferrer">YouTube不断将PointCrow用筷子吃水的视频取消货币化（他反复将筷子浸入水中，然后喝水，非常缓慢地吃一碗水）</a>。</p>
<p>尽管YouTube回复说</p>
<blockquote>
<p>我们对这个错误以及反复的过程非常抱歉，我们已经与团队沟通过，确保这种情况不会再发生</p>
</blockquote>
<p>但他的视频再次被取消货币化，申诉又以标准的支持回应策略开始，即他们仔细审查了违规内容，但不幸的是，用户明显违反了政策，因此无能为力：</p>
<blockquote>
<p>我们已经审查了您的申诉...我们仔细审查了您的内容，并确认它违反了我们的暴力或血腥内容政策...确保YouTube对所有人来说都是一个安全的地方是我们的工作。</p>
</blockquote>
<p>这些是高调的例子，但当然，低调也不会阻止你被封禁并得到同样的基本上是模板化的回应，比如<a href="https://news.ycombinator.com/item?id=38882891" rel="noopener noreferrer">这位在FB Marketplace上卖吸尘器而被封禁的HN用户</a>。经过多次申诉后，他被告知：</p>
<blockquote>
<p>很遗憾，您的账户因违反社区准则而无法恢复。审查是最终的。</p>
</blockquote>
<p>当付费支持是可选的时候，人们常说如果你付费支持就不会有这些问题，但<a href="https://www.youtube.com/watch?v=SMFCLpUuhqY" rel="noopener noreferrer">使用Google One付费支持或Facebook和Instagram付费创作者支持的人普遍报告说付费支持并不比免费支持好</a>。那些实际上内置了付费支持的产品也不一定更好。我认识一些人，他们从Google Cloud获得的支持与免费Google支持一样来回推诿，即使他们为每年在Google Cloud上花费八位数或九位数的公司工作。在一个众多例子中的例子里，用户发现Google一定在丢包，而Google支持坚持认为数据包丢失发生在客户的机房，尽管数据包跟踪显示情况不可能如此。我听说他们放弃了那个案子，但有时当问题是完全的阻碍时，有人会打电话给他们在Google的朋友寻求支持，因为标准支持通常完全无效。这在Google并非独有——在另一家云供应商，我的一位前同事在一个会议上得知，一位非常资深的工程师被要求调查一个客户抱怨的问题：客户看到100%的数据包被丢弃，持续几秒钟，每小时发生多次。那位工程师回应说类似“这是云，他们应该处理它”的话，然后被告知他们不能像往常一样忽略这个问题，因为问题来自[重要客户]，并且正在中断[世界上最大的电视体育赛事之一]。那个问题得到了解决，但很可能，你并不那么重要，即使你每年支付数亿美元。</p>
<p>当然，这种支持并非云供应商独有。例如，<a href="https://news.ycombinator.com/item?id=34233011" rel="noopener noreferrer">有一次Stripe扣留了一位客户40万美元超过一个月，没有解释</a>，每一次向支持部门的请求都得到了像我们刚才看到的一样荒谬的回应。用户利用了唯一可靠的Stripe支持机制，在HN上发帖并希望上首页，这奏效了，尽管许多评论者说这是常规评论，比如“标记，因为我们看到很多这样的HN帖子，它们似乎是试图欺诈性地操纵客户支持，而不是真实的故事”，多人暗示或影射用户在做某种非法或欺诈的事情，但事实证明这是Stripe方面的错误，再加上Stripe的大公司支持。在某个时候，用户指出：</p>
<blockquote>
<p>在我写HN帖子的同时，我还在与Stripe聊天一个多小时。没有新信息。他们基本上是想关闭和我聊天，直到我给他们看了HN的故事并显示它获得了一些关注度。然后他们才重新开始处理我的问题，并尝试与更多人沟通。</p>
</blockquote>
<p>然后问题在第二天就得到了解决。</p>
<p>尽管原则上，随着公司变得更大，他们可以利用规模经济提供更高效的支持，但他们往往利用规模经济提供更差但更便宜、更有利可图的支持。例如，关于Google Play商店的批准支持，一位Google员工指出：</p>
<blockquote>
<p>很多支持外包给了海外，导致响应时间慢得多。在美国这边，我们有很多指标来实现快速响应。通常你的应用会在同一天得到审核。不确定现在情况如何，但当时管理层很无能。</p>
</blockquote>
<p><a href="https://twitter.com/RMac18/status/1382366931307565057" rel="noopener noreferrer">一位前FB支持人员指出</a>：</p>
<blockquote>
<p>这里的大问题是劳动分工。在队列中花费最多时间的人对政策的影响最小。分析师可以向QA提出问题，然后QA再向Facebook正式员工（FTE）提出。问题可能需要数月才能得到解决，如果能得到解决的话。最糟糕的是，按照常识去做，执行政策的精神而非字面意思，可能会对你的质量评分产生负面影响。我经常想到，在我任职的几个月里，由于政策“澄清”中一个措辞草率的地方，大多数肢解动物的照片都被允许出现在平台上，没有警告屏幕，而我们对此无能为力。</p>
</blockquote>
<p>如果你曾经想知道为什么你的支持人员回复不合逻辑，有时原因是显而易见的：支持工作外包给了一个每小时挣1美元的人（当我查到一个很多支持工作外包的国家的标准费率时，一个相当标准的费率大约是每小时1美元），他们不太懂你所使用的语言，并且是在照着流程图念，对他们正在提供支持的系统一无所知。但另一个不那么明显的原因是，如果支持人员采取了有意义的行动而不是遵循他们面前那个毫无意义的流程图，他们可能会受到惩罚并最终被解雇。</p>
<p>回到“它们似乎是试图欺诈性地操纵客户支持，而不是真实的故事”这个评论，这是我在那些随意和反复无常的封禁用户的大公司中经常看到的工程师表达的一种情绪。我理解人们是如何走到这一步的。<a href="https://twitter.com/danluu/status/964562384558927872" rel="noopener noreferrer">在我加入Twitter之前，我就指出过</a>，评论公开信息：</p>
<blockquote>
<p>事实证明，Twitter每天移除约100万机器人账户。Twitter只有约3亿月活跃用户，这使得错误容忍度非常低。这似乎是一个非常难的问题...Gmail的垃圾邮件过滤器每1000封正确分类的正常邮件中可能会给我大约1个误报...定期清除同等比例的真实用户在服务中将是[糟糕的]。</p>
</blockquote>
<p>这是真的，如果你，一个工程师，深入研究某个巨头公司的支持队列，查看那些申诉封禁的人，几乎所有的申诉都应该被拒绝。但是，根据我与从事反欺诈系统等工作的工程师交谈的经验，许多人，也许是大多数人，将“几乎所有”四舍五入到了“全部”，这在数量和质量上都是不同的。让从事这些系统的工程师相信他们的决策“全部”而不是“几乎所有”都是正确的，会导致用户糟糕的体验。</p>
<p>例如，有一家社交媒体公司因错误地封禁用户而闻名（我认识的人中至少有10%因错误的封禁而失去了账户，如果我随机搜索一个不认识的人，很可能找到他们的多个账户，其中一个最近的账户写着“曾经是@[某个旧账户]”，旧账户没有转发到新账户，因为他们现在被封禁了）。当我遇到这个团队的一位高级工程师时，我问他为什么有这么多合法用户被封禁，他告诉我类似“那不是问题，真正的问题是我们没有封禁足够多的账户。每个被封禁的人都罪有应得，不值得听取申诉或思考它们”这样的话。当然，<a href="https://twitter.com/danluu/status/964562384558927872" rel="noopener noreferrer">每个公共平台上的大多数内容都是坏内容、垃圾信息等</a>是真的，所以如果你对某个东西是否是坏内容有任何信号，当你查看它时，它很可能是坏内容。但这并不意味着反过来也成立，即几乎没有用户被错误地封禁。如果负责分类哪些内容是坏内容的团队中的高级人员持这种态度，即我们不需要担心误报，因为几乎所有被标记的内容都是坏内容，我们最终会得到一个具有大量误报的系统。我后来四处询问，看看为减少欺诈检测系统中的误报做过哪些努力，结果发现根本没有系统地跟踪误报，没有办法统计员工提交内部工单覆盖错误封禁等情况；在元层面上，有一些机制可以减少漏报率（例如，有人看到坏内容没有被捕捉到，然后添加一些东西来捕捉更多坏内容），但没有跟踪误报，实际上就没有减少误报率的机制。这个元系统导致我认识的人中有超过10%的人被错误地暂停或封禁，这并不奇怪。而且，正如帕特里克·麦肯齐所说，误报的最佳率不是零。但当工程师持有“我们已经做了足够的研究，误报是不可能的”这种态度时，误报率基本上保证会高于最佳水平。当你将其与正常大公司的支持水平相结合时，这就是卡夫卡式用户体验的配方。</p>
<p>还有一次，我评论Uber的内容审核政策变更似乎可能导致误报封禁。一位Uber技术主管立即批评我，说我做出了毫无根据的假设，Uber工程师非常努力地确保没有误报封禁，有广泛的审查来确保封禁是有效的，事实上，我担心的误报封禁永远不会发生。然后我就因为反欺诈系统中的误报而被有效地封禁了。<a href="https://www.theguardian.com/technology/2023/apr/16/stop-or-ill-fire-you-the-driver-who-defied-ubers-automated-hr" rel="noopener noreferrer">当Uber错误地封禁了一位司机，该司机不得不将他们告上法庭以获取被封禁的原因，在这时Uber才真正调查（而不是只回复申诉并声称他们已经调查过了）</a>，我回想起那件事。之后，Uber回应媒体询问时说：</p>
<blockquote>
<p>我们对法院未能认可我们制定的健全流程感到失望，包括在因涉嫌欺诈而对司机账户进行停用决定时进行有意义的人工审查。</p>
</blockquote>
<p>当然，在那位司机的案例中，没有健全的审查流程，我的案例也没有健全的申诉流程。当我联系支持人员时，他们并没有真正阅读我的消息，做了一些更改，使我的账户比之前更糟。幸运的是，我有足够的Twitter粉丝，一些Uber工程师看到了我关于这个问题的推文，并让我解除了封禁，但这不是大多数人可用的选择，导致像<a href="https://danluu.com/customer-service/#dentist" rel="noopener noreferrer">这个针对Google员工的Facebook广告</a>这样的怪事发生，有人绝望地寻求帮助解决他们的Google账户问题。</p>
<p>即使你认识内部人员，也不总是能轻易解决问题，因为即使公司的效率没有随着规模扩大而提高，系统的复杂性确实增加了。一个很好的例子是<a href="https://twitter.com/GergelyOrosz/status/1469968831372312578" rel="noopener noreferrer">Gergely Orosz的故事，关于支付团队经理离开Uber后，如何因某些难以理解的ML反欺诈算法决定这位前支付团队经理在进行支付欺诈而被Uber封禁</a>。花了六个月的时间试图解决问题来缓解问题。而且，顺便说一句，他们从未弄清楚发生了什么并修复根本问题；相反，他们将这位前支付团队经理加入了一个特殊白名单，没有为任何其他用户解决问题，并且，大概，严重减少或甚至完全移除了该前经理账户的支付欺诈保护。</p>
<p>毫无疑问，如果问题容易解决，他们会修复根本问题，但随着公司规模扩大，他们会产生技术和非技术性的官僚主义，使系统即使对员工来说也不透明。</p>
<p>另一个例子是，在一家有排名社交feed的公司，通过添加诸如<code>timeline_injection:false</code>、<code>interstitial_ad_op_out</code>等过滤器来删除你不想在排名feed中看到的东西的想法会病毒式传播。第一次发生这种情况时，许多工程师调查后认为病毒式传播的技巧不起作用。他们并不完全确定，并且依赖于诸如“没有人记得曾经实施过这样的系统”和“如果你在代码库中搜索这些字符串，它们没有出现”以及“我们查看了可能这样做的系统，它们似乎没有这样做”的想法。他们对这个技巧不起作用有中等程度的信心，但没有人敢确定地说这个技巧不起作用，因为正如在所有大公司一样，系统的整体行为超出了人类的理解范围，甚至那些可以理解的部分也常常因为其他优先级而不被理解。</p>
<p>几个月后，这个技巧再次病毒式传播，人们通常被指引去查看上次的调查结果，只是有一个人实际尝试了这个技巧并报告说它有效。他们写了一条关于这个技巧对他们有效的Slack消息，但几乎没有人在意那个尝试复制技巧并发现它有效的人。后来，当这个技巧再次病毒式传播时，人们会指出关于人们认为这个技巧不起作用的讨论，而这条说它似乎有效的消息（几乎肯定不是用户认为的机制，而是因为长的过滤器列表导致超时，或类似的原因）基本上被遗失了，因为信息太多读不过来。</p>
<p>在我的社交圈里，许多人都读过詹姆斯·斯科特的《国家的视角》，其副标题是“改善人类世界的某些计划是如何失败的”。这本书的一个关键概念是“可读性”，即国家能看到的东西，以及这如何扭曲国家的行为。人们很容易写出一本高度类似的书，名为《像科技公司一样看待》，关于那些规模扩大的公司在至少是目前公司运营方式下，哪些东西是不可读的。一个简单的例子是，在许多视频游戏中，包括由价值3万亿美元的公司旗下的游戏工作室制作的游戏，通过让一群人举报某个账户行为不端，很容易让某人被暂停或封禁。对公司来说可读的是举报率，而不可读的是玩家的实际行为（它本可以是可读的，但公司选择没有足够的人或足够熟练的人来检查实际行为）；<a href="https://news.ycombinator.com/item?id=31581510" rel="noopener noreferrer">许多人在社交媒体公司也报告了类似的封禁</a>。当涉及到反欺诈系统时，对公司来说可读的东西往往对人类来说相当不可读，即使是从事反欺诈系统工作的人员自己也是如此。</p>
<p>尽管他不是专门谈论反欺诈系统，但在一次特别主事官听证会上，Facebook的一位总监尤金·扎拉沙（Eugene Zarashaw）做出了这番评论，说明了Facebook自身系统的不可读性：</p>
<blockquote>
<p>这可能需要广告团队的多个团队才能追踪到确切的数据流向。如果真的能有一个人能明确回答那个狭隘的问题，我会感到惊讶。</p>
</blockquote>
<p>Facebook因为这个声明被不公平地（而且大部分是无知地）大肆批评（<a href="#appendix-how-much-should-we-trust-journalists-summaries-of-leaked-documents" rel="noopener noreferrer">我们将在附录中讨论这个</a>），但一般来说，<abbr title="取决于你所说的理解是什么意思，可能公平地说是不可能的">理解</abbr>像Facebook这样规模的系统是如何运作的确实很困难。</p>
<p>原则上，公司可以通过让薪酬尚可的支持人员调查那些可能是具有严重后果的边缘情况问题来增强其深奥系统的可读性，在这些问题中，系统“误解”了正在发生的事情，但在实践中，公司支付给这些支持人员的报酬极低，雇佣了真正不理解情况的人，然后给他们指示，确保他们通常无法成功解决可读性问题。</p>
<p>有一件事帮助不可读的力量在规模上占上风，那就是作为这些大公司的高薪员工，很容易看着外面的数百万或数十亿人（和机器人），把他们所有人都看作数字。俗话说，“一个人的死亡是悲剧。一百万人的死亡是统计数据”，正如我们注意到的，工程师经常将“几乎所有X都是欺诈”这样的想法转变为“所有X都是欺诈，所以我们不妨直接封禁所有做X的人，不听取申诉”。现代科技公司寻找可扩展解决方案的文化，即使在同等规模下，也使其比其他行业更糟，而且科技公司还拥有前所未有的规模。</p>
<p>例如，当有人指出FB广告管理器声称你可以在美国18-34岁人群中投放可能覆盖1.01亿人的广告，而美国人口普查显示18-34岁总人口为7600万时，广告定向团队的前产品经理回应道：</p>
<blockquote>
<p>从FB规模思考</p>
</blockquote>
<p>并解释说你不能指望在美国18-34岁人口这样的人口统计上进行“切分”查询在“FB规模”下起作用。谷歌内部有一个讽刺用的梗，在这种情况下，人们会说“我不会数那么低的数”。这位FB广告前产品经理是在非讽刺地说，FB不会数像1亿这样低的数。FB不仅不关心任何个人用户（除非他们是名人），这位产品经理声称他们甚至懒得关心1亿人这样的群体是否被准确追踪。</p>
<p>回到糟糕支持的后果，一个常见的回应是听到有人因这些巨型服务而被错误封禁后说“很好！你为什么想用Uber/亚马逊/之类的？它们很糟糕，没人应该用它们。”我不同意这种推理。首先，<a href="https://twitter.com/danluu/status/891508449414197248" rel="noopener noreferrer">为什么你要替那个人决定他们是否应该使用某个服务或什么对他们好？</a>其次（这是一个足够大的话题，应该单独写一篇文章，所以我只是简单提一下<a href="https://mastodon.social/@whitequark/111280549888138665" rel="noopener noreferrer">并链接到@whitequark的这篇更长的评论</a>），许多人认为不必要的便利而应该放弃的大多数服务，实际上对相当多的人（绝对数量，不一定是百分比）来说是严重的无障碍性问题。当我们谈到小企业时，这些人通常可以换到另一家企业，但像Uber和亚马逊这样的服务，有时没有替代品，或者只有一家提供类似便利性的替代品，当只有一家时，由于某些随机系统误判而被封禁，也可能发生在另一家服务上。<a href="https://www.reddit.com/r/mildlyinfuriating/comments/186redy/doordash_denied_refund_for_wrong_order_delivered/" rel="noopener noreferrer">例如，针对许多人评论说你应该直接申请拒付并从DoorDash被封禁，当他们没有送达时</a>，一位残疾用户回应道：</p>
<blockquote>
<p>我是残疾人。没有驾照或汽车。我公寓附近没有公交车站，我实际上乘坐辅助客运系统去上班，但我必须提前一天计划。Uber也做同样的事情，所以我不得不根据谁有优惠券且最近没有偷我的钱，在Uber、DoorDash和GrubHub之间轮换使用。不是每个人都能自己去取东西。</p>
</blockquote>
<p>此外，当谈论这类问题时，参与通常不是自愿的，<a href="https://news.ycombinator.com/item?id=39059307" rel="noopener noreferrer">比如这个将人们错误关进监狱的富士通错误</a>。</p>
<p>关于第三个问题，即让人们就什么是垃圾信息、欺诈和其他不允许的内容达成一致是不可能的，<a href="https://danluu.com/impossible-agree/" rel="noopener noreferrer">我们在这里详细讨论过</a>。我们看到，即使在一个简单、无争议、单一规则的微不足道情况下，人们也无法就什么是允许的达成一致。而且，当你增加更多规则或添加有争议的话题或扩大人数规模时，就更难就应该允许什么达成一致了。</p>
<p>总结一下，我们考察了三个领域，其中规模不经济导致内容审核、支持、反欺诈和反垃圾信息随着公司变大而变差。第一个是，即使在存在广泛共识认为某事是坏的情况下，例如欺诈/诈骗/钓鱼网站和搜索，<a href="https://danluu.com/seo-spam/" rel="noopener noreferrer">拥有最复杂机器学习的最大公司实际上无法跟上一个（尽管非常熟练的）在小型搜索引擎上工作的人</a>。如果诈骗分子针对最大的平台，回报会高得多，导致反垃圾/反欺诈等问题变得极其非线性地困难。</p>
<p>为了了解规模上的差异，HN“地狱封禁”发布垃圾信息和发表某些类型恶毒评论的用户。大多数垃圾信息发布者似乎没有意识到他们被地狱封禁了，会继续发布一段时间，所以如果你在登录状态下浏览“最新”（提交）页面，你会看到这些地狱封禁用户发布的稳定的一串被自动删除的故事。虽然有相当多，但比例通常远低于一半。当我们看像2017年的Twitter这样的“中型”大科技公司时，根据公开数据，如果垃圾信息机器人被地狱封禁而不是删除，垃圾信息将如此普遍，以至于如果你能看到它，你所能看到的只有垃圾信息。而且，作为大公司来说，2017年的Twitter并不算大。正如我们也注意到的，FB广告定向的前产品经理解释说，像1亿这样小的数字都在“我不会数那么低的数”的范围内，太小了，不值得关心；对他来说，基本上就是一个舍入误差。对于像FB或谷歌这样的公司来说，非线性差异的难度要糟糕得多。这些问题是非线性困难的，显然超过了扎克伯格和其他科技高管想要吹嘘的任何ML或AI技术。</p>
<p>在国会作证时，你会看到高管用类似“我们能以95%的准确率识别X”这样的言论来为这些系统在规模上的有效性辩护，这个说法在技术上可能是正确的，但似乎旨在故意误导被认为数学不好的观众。如果你以个人规模为参考，95%听起来可能相当好。即使对于HN规模这样的东西，95%准确的垃圾信息检测如果导致立即封禁可能还过得去。无论如何，即使它不是很好，被错误封禁的人可以直接给Dan Gackle发邮件，他会解封他们。正如我们在看数字时指出的，在Twitter规模上达到95%准确的检测会很糟糕（而且，确实，我收到的大多数DM都是明显的垃圾信息）。你要么退缩，只在你非常有信心的情况下封禁用户，要么你不会太久就封禁你所有的用户，而且正如公司喜欢处理支持的方式，申诉意味着你会收到一条回复，说“你的案件经过仔细审查，我们确定你违反了我们的政策。这是最终的”，即使对于任何一种粗略审查都会导致封禁被撤销的情况，比如当你封禁一个冒充自己的用户时。然后在FB的规模上，情况更糟，你会更快地封禁所有用户，然后你就退缩了，我们最终得到像每天有10万未成年人暴露于“成年人生殖器照片或其他性虐待内容”这样的事情。</p>
<p>我们考察的第二个领域是支持，它往往随着公司变大而变差。在高层面上，可以说公司并不关心提供良好的支持（亚马逊在这里是一个例外，特别是AWS，即使在消费者方面也是如此）。在系统内部，有些个体是关心的，但如果你看看支持与增长甚至有趣/有声望的项目相比投入的资源比例，支持是一个事后的想法。当DeepMind正在训练一个星际争霸AI时，Alphabet可能在玩星际争霸上花的钱比在支持代理上还多（如果没有，只需再加上一两个大型AI训练项目就可以了，特别是如果包括定制硬件等的摊销成本）。</p>
<p>很容易看出大公司有多不关心。你只需要联系支持，与每小时赚1美元、用他们勉强懂的语言回复你、试图通过照着流程图走来帮助解决他们不理解的问题的人取得联系，或者申诉一个问题并被告知“经过仔细审查，我们确定你[做了与你实际做的相反的事情]”。在某些情况下，你甚至不需要走到那一步，比如当<a href="https://mastodon.social/@alexjsp/109760284680279691" rel="noopener noreferrer">按照Instagram的支持说明操作会导致一个把你带回起点的无限循环</a>，而<a href="https://news.ycombinator.com/item?id=34549132" rel="noopener noreferrer">“如果这不是你，请点击这里”的链接返回404</a>。我遇到过一次像这样的无限循环，和Verizon一起，它至少持续了六个月。我之后没有检查，但我敢打赌它持续了几年。如果你的引导或注册页面有这样一个问题，那会被认为是一个严重的错误，人们应该优先处理，因为它影响增长。但对于像因诈骗分子接管账户导致的账户丢失这样的问题，可能要几个月或几年后才会修复。或者也许不会。</p>
<p>如果你曾经和真正关心支持的人交谈，你立即就能明显看出他们的运作方式与典型的大科技公司支持完全不同，无论是流程还是文化。另一个可以看出大公司不关心支持的方式是，有多少从未研究过支持如何做或可以如何做的大公司员工和高管会告诉你，做得更好是不可能的。</p>
<p>当你与那些确实关心这一点的公司的支持人员交谈时，很明显可以做得更好。在我写这篇文章的时候，我实际上<abbr title="明确地说，这是一线支持，我与普通的非技术用户交谈，而不是像“现场应用工程师”这样的角色，你互动的典型用户是工程师">在一家支持做得相当好的公司（对于一家科技公司，按规模调整，我认为他们在<a href="https://danluu.com/p95-skill/" rel="noopener noreferrer">99%分位数以上</a>）做支持，包括接受支持人员的培训和入职流程。<a href="https://danluu.com/sounds-easy/" rel="noopener noreferrer">在规模上执行任何事情都是非平凡的</a>，所以我不是在贬低他们的支持组织有多好，但最令我震惊的是，该组织的有效性自然源于关心为用户提供良好的支持体验。对此的完整讨论太长，无法在此包含，我们将在以后更详细地讨论这个，但一个例子是，当我们看大公司支持如何回应时，它通常旨在阻止用户回应（“此审查是最终的”）或者表面上向用户证明公司工作做得充分（“这不是纯粹的自动化过程，每个申诉都由人类在一个健全的流程中审查，该流程...”）。这家公司的培训指示你做标准大公司“请走开”风格和“我们做得很好，有一个健全的过程，因此投诉无效”风格回应的相反。对于你在支持中经常看到的每一种反模式，培训都告诉你做相反的事情，并讨论为什么反模式会导致糟糕的用户体验。此外，文化已经深刻吸收了这些想法（或者更确切地说，这些想法来自文化），并且有流程来<abbr title="这个词是在口语中使用，不是严格意义上的，因为你无法在规模上保证这一点">确保</abbr>人们真正理解提供良好支持意味着什么并付诸行动，支持人员有方法直接与正在实现产品的开发人员交谈等等。</abbr></p>
<p>如果人们关心做好支持，他们可以与那些擅长帮助用户的支持组织的人交谈，甚至在解释为什么不可能做得更好之前尝试在其中工作，但这通常不会发生。他们公司的支持组织领导层也可以这样做，或者像我一样直接在一个有效的支持组织中担任支持角色，但这并没有发生。如果你是一个愤世嫉俗者，这一切都说得通。就像愤世嫉俗者建议初级员工“大公司的人力资源不是来帮助你的；他们的工作是保护公司”一样，一个愤世嫉俗者可以令人信服地争辩说“大公司支持不是来帮助用户的；他们的工作是<abbr title="用保护公司来区别于人力资源；对我来说，这感觉更类似于保险，那里有人的工作就是降低成本。我遇到过保险公司拒绝承保，根据他们书面政策，显然应该承保的情况。在一种情况下，我可以打电话给公司代表，他们会向我解释为什么他们公司拒绝理赔是错误的以及我应该如何上诉，但书面申诉总是被书面拒绝。幸运的是，在大科技公司工作时，你可以告诉你的雇主发生了什么，然后他们会告诉保险公司停止耍你，就像我们的大型体育赛事云支持故事一样，但对于大多数保险用户来说，这不是一个选择，他们唯一的追索权是起诉，通常他们不会起诉，即使起诉也会庭外和解。保险公司知道这一点，并且经常在没有查看的情况下拒绝索赔（这在诉讼发现过程中被揭露）；考虑到诉讼成本，这种索赔拒绝比正确处理索赔要便宜得多。同样，提供实际支持的成本比不提供要高得多，让用户停止因账户损坏而烦扰公司可以节省成本，因此有标准回复声称审查是最终的，无能为力等；任何能让用户减少公司支持成本的方法（除了实际提供支持）">保护公司</abbr>”，所以大公司当然不尝试理解那些擅长支持用户的公司是如何做支持的，因为那不是大公司支持的目的。</p>
<p>我们考察的第三个领域是人们无法就平台应如何运作达成一致是多么不可能，以及人们的偏见意味着人们没有理解这个问题有多困难。对美国人来说，一个突出的例子是左翼和右翼的阴谋论，每当某种错误导致任何类型的服务中断或封禁时，这些阴谋论就会冒出来。</p>
<p><a href="https://twitter.com/greenberg/status/872115047769788418" rel="noopener noreferrer">在一条推文中，Ryan Greeberg开玩笑说</a>：</p>
<blockquote>
<p>来Twitter工作吧，你今天的bug可以成为明天的阴谋论！</p>
</blockquote>
<p>在我的社交圈里，人们喜欢嘲笑所有在某个错误导致人们被错误封禁、网站无法加载等之后传播的荒谬的右翼阴谋论，或者当一些新的ML功能正确地拿下了一个巨大的诈骗/垃圾机器人网络时（这也会减少一些用户的关注者数量）。但当然，这并非右翼独有，左翼的意见领袖和<a href="https://twitter.com/altluu/status/1588573465359319041/" rel="noopener noreferrer">政客们也会编造他们自己的阴谋论</a>。</p>
<p>将这三者结合在一起，更差的问题检测、更差的支持，以及在政策上更难达成一致，我们最终得到了开头指出的情况：在我的Twitter关注者（主要是技术人员且通常技术相当精通）的民意调查中，只有<a href="https://twitter.com/danluu/status/1570604630350106624" rel="noopener noreferrer">2.6%的人认为最大的公司在内容审核和垃圾信息/欺诈过滤方面做得最好</a>，所以花这么多时间反复强调这一点可能显得有点愚蠢。当你对美国普通人口进行抽样时，有更大比例的人表示他们相信阴谋论，比如疫苗会在你体内植入微型芯片，或者我们从未登上月球，而我不会花时间解释为什么疫苗实际上不会植入微型芯片，或者为什么我们认为我们登上了月球是合理的。一个也许合理的理由是，当“只有大公司能处理这些问题”的言论在非技术人员（如监管者、立法者和<a href="https://twitter.com/altluu/status/1484718002046070784" rel="noopener noreferrer">经常倾听然后复述胡说八道的高级政府顾问</a>）中开始流行时，我一直在关注。也许下次你遇到一个外行告诉你只有最大的公司才可能处理这些问题时，你可以礼貌地指出技术人员中有着非常强烈的相反共识<sup id="fnref:A"><a href="#fn:A" rel="noopener noreferrer">5</a></sup>。</p>
<p>
<b><a href="https://www.propelauth.com/?utm_source=danluu.com" rel="noopener noreferrer"> 如果你是创始人或早期初创公司，正在寻找身份验证解决方案，PropelAuth正在针对你的用例</a></b>。虽然他们可以处理其他用例，但他们目前专门致力于让尚未投资身份验证解决方案的预发布初创公司生活更轻松。免责声明：我是投资者</p>
<p><i>感谢Gary Bernhardt、Peter Bhat Harkins、Laurence Tratt、Dan Gackle、Sophia Wisdom、David Turner、Yossi Kreinin、Justin Blank、Ben Cox、Horace He、@borzhemsky、Kevin Burke、Bert Muthalaly、Sasuke、anonymous、Zach Manson、Joachim Schipper、Tony D'Souza和@GL1zdA的评论/纠正/讨论。</i></p>
<p>（由于文章过长，附录部分未翻译）</p><p><em>由 mimo-v2.5 模型翻译，花费 106459 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/diseconomies-scale/</link>
      <guid isPermaLink="false">https://danluu.com/diseconomies-scale/</guid>
      <pubDate>Sun, 18 Feb 2024 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>为什么无法就允许的内容达成一致</title>
      <description>&lt;p&gt;在大型平台上，人们无法就审核、垃圾邮件、欺诈和性内容等政策达成一致。David Turner制作了一个简单的游戏来说明即使在最微不足道的情况下，这有多么困难，&lt;a href=&quot;https://novehiclesinthepark.com/&quot;&gt;No Vehicles in the Park&lt;/a&gt;。如果您还没有玩过，我建议在继续阅读本文之前先玩一下。&lt;/p&gt;

&lt;p&gt;这个网站背后的想法是，让</description>
      <content:encoded><![CDATA[<p>在大型平台上，人们无法就审核、垃圾邮件、欺诈和性内容等政策达成一致。David Turner制作了一个简单的游戏来说明即使在最微不足道的情况下，这有多么困难，<a href="https://novehiclesinthepark.com/" rel="noopener noreferrer">No Vehicles in the Park</a>。如果您还没有玩过，我建议在继续阅读本文之前先玩一下。</p>

<p>这个网站背后的想法是，让人们就平台应适用的审核规则达成一致非常困难。即使你采用一个更简单的例子，给定一条规则和一些解释规则的指示，问哪些车辆应被允许进入公园，然后问一组小问题，人们也无法达成一致。我自己做这个调查时，最初的反应之一是这些问题并不特别棘手，如果Dave想让它更具挑战性，他可以问许多边缘情况。然而，尽管调查并不特别具有挑战性，人们对这些问题并没有广泛的共识。</p>
<p>对调查的评论也表明了规则的另一个问题，即达成一致比人们想象的要难得多。如果你阅读lobsters、HN、reddit等网站上关于规则解释或审核的评论，当人们提出解决方案时，绝大多数人会提出任何做过审核或关注审核工作的人都知道行不通的建议，相当于<a href="https://danluu.com/sounds-easy/" rel="noopener noreferrer">"我周末就能搞定"</a>的<a href="https://danluu.com/sounds-easy/" rel="noopener noreferrer">"审核版"</a><sup id="fnref:M"><a href="#fn:M" rel="noopener noreferrer">1</a></sup>。当然，我们在Dave的游戏中也看到了这一点。HN上最热门的评论，也就是最多人赞同的评论，以及在其他地方普遍存在的观点是<sup id="fnref:C"><a href="#fn:C" rel="noopener noreferrer">2</a></sup>：</p>

<blockquote>
<p>我着迷于这样一个事实：我的收获与作者的意图完全相反。</p>

<p>对我来说，所有问题的答案都一目了然。是的，你可以学术性地思考一个绕轨道运行的空间站是否是车辆，以及它是否在公园里，但标志牌的明显意图再清楚不过了。汽车、卡车、摩托车不被允许，而显然，警察和救护车（以及消防车）在执行公务时不必遵守标志牌。</p>

<p>所以，如果这旨在说明内容审核规则难以遵循，那么它恰恰达到了相反的效果。</p>
</blockquote>

<p>有人附和地回复道：</p>

<blockquote>
<p>确实如此。答案中存在明显的多数意见。</p>
</blockquote>

<p>完成调查后，你会得到一个图表，显示每个人对每个问题回答“是”和“否”的数量，这就是“明显的多数”来源。首先，我认为说存在明显的多数意见并不正确。但即使假设有，也没有理由认为多数意见意味着大多数人与你意见一致，即使你每个问题都持多数立场。事实上，考虑到每个问题的多数意见图表看起来多么“摇摆不定”，如果每个问题的多数立场意味着大多数人同意你，或者存在大多数人都同意的一组立场，那将是非凡的。虽然你可以构建一个人工数据集使其成立，但在自然数据集中这将非常令人惊讶。</p>

<p>如果你查看数据（该数据在网站上不可用，但Dave在我询问时欣然提供），在我获取数据时，没有一组答案是大多数用户同意的，而且差距很大。我在HN发布链接后不久获取了这些数据，当时绝大多数响应来自HN读者，他们比一般人群更同质。尽管这些因素使得更容易找到共识，但最受欢迎的答案组合仅被11.7%的人选择。这就是顶级评论者所说“显而易见”的立场，但它不仅在只有11.7%的人同意、88.3%的人不同意的意义上是少数派立场，而且几乎没有人持有与这一所谓显而易见立场仅有微小分歧的立场。第二和第三最常见立场分别占8.5%和6.5%的投票，它们相似，仅在一个非功能性的二战时期坦克作为纪念物是否违反规则上有分歧。除此之外，大约1%的人持有第四、第五、第六和第七最流行立场，每个较不流行立场都少于1%的同意度，并且从那里相当迅速地下降。因此，27%的人发现自己与超过1%的其他用户意见一致（中位The request was rejected because it was considered high risk</p><p><em>由 mimo-v2.5 模型翻译，花费 6708 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/impossible-agree/</link>
      <guid isPermaLink="false">https://danluu.com/impossible-agree/</guid>
      <pubDate>Wed, 7 Feb 2024 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Cruise行人事故笔记</title>
      <description>&lt;p&gt;这是一组关于Quinn Emanuel针对Cruise处理2023年10月2日事故的报告的笔记。该事故中，一辆Cruise自动驾驶汽车（AV）撞到一名行人后停下，随后再次移动，行人被卡在车底下方，被拖拽了20英尺。在看到一些关于这份报告的评论后，我阅读了五篇关于此报告的报道，并快速浏览了报告本身，我的感觉是，其中四篇报道的作者可能根本没读过报告，而评论的人通常只读了那些没有阅读原始材料的记者</description>
      <content:encoded><![CDATA[<p>这是一组关于Quinn Emanuel针对Cruise处理2023年10月2日事故的报告的笔记。该事故中，一辆Cruise自动驾驶汽车（AV）撞到一名行人后停下，随后再次移动，行人被卡在车底下方，被拖拽了20英尺。在看到一些关于这份报告的评论后，我阅读了五篇关于此报告的报道，并快速浏览了报告本身，我的感觉是，其中四篇报道的作者可能根本没读过报告，而评论的人通常只读了那些没有阅读原始材料的记者的文章，因此评论往往大错特特。<a href="https://danluu.com/dunning-kruger/" rel="noopener noreferrer">正如我们之前讨论的，即使是总结一篇普通读者能轻松阅读的短文，其摘要也经常错得离谱</a>，因此，对一份200页报告的摘要，其误导性可能最多也只是“最佳情况”了。</p>

<p>在通读了整份报告后，我认为Cruise的情况既比我看到的那些文章中描述的要好，也要更糟，这与我们在查看<a href="https://danluu.com/elon-twitter-texts/" rel="noopener noreferrer">Twitter诉Musk案中的展品H和J</a>、<a href="https://danluu.com/us-v-ms/" rel="noopener noreferrer">美国诉微软公司文件</a>等实际来源时看到的模式相同；就像一些记者似乎亲马斯克/反马斯克、亲微软/反微软，并愿意为最大限度地抨击或开脱他们而推动不准确的叙事一样，我们在Cruise这里也看到了同样的现象。并且，正如我们在那些案例中所见，尽管一些文章似乎试图将Cruise描绘得尽可能好或尽可能坏，但报告本身的材料比最正面或最负面的故事所呈现的更有积极和消极之处。</p>

<p>除了纠正关于报告的误导性观点外，我发现这份报告有趣之处在于，在科技领域很少能看到如此详细、更不用说是公开的事故调查。我们通常在安全关键系统、有时在体育赛事以及历史事件中看到这类调查，但科技事件通常不会被如此报道。当然，公司会对事故进行事后分析，但你通常不会看到一份针对单个事件的200页报告，事后分析的重点也不会是这份报告的重点。过去，<a href="https://danluu.com/wat/" rel="noopener noreferrer">我们已经指出，通过研究安全关键系统的文献和事故报告可以学到很多东西</a>，因此，对于这个比我们之前研究过的系统更接近科技领域的安全关键系统，当然也是如此。</p>

<p>这份报告的长度和深度反映了安全关键系统与“科技”文化之间的差异。报告中被描述为不可理喻的行为在科技界不仅是正常的，而且可能比你在大多数大型科技公司看到的更透明、更公开；我认为科技与安全关键系统之间的文化冲突也很有趣。我试图在报告中尽可能少地加入自己的个人观点，即使在那些因为了解科技公司或工程背景而我自己会写出不同内容的情况下也是如此。更多观点，请<a href="#back-to-danluu-com" rel="noopener noreferrer">参见文末部分</a>。</p>

<p><b><a href="https://assets.ctfassets.net/95kuvdv8zn1v/1mb55pLYkkXVn0nXxEXz7w/9fb0e4938a89dc5cc09bf39e86ce5b9c/2024.01.24_Quinn_Emanuel_Report_re_Cruise.pdf" rel="noopener noreferrer">关于2023年10月2日旧金山事故致Cruise LLC、GM Cruise Holdings LLC及General Motors Holdings LLC董事会的报告</a></b></p>

<h2 id="i-introduction">I. 引言</h2>

<h3 id="a-overview">A. 概述</h3>

<ul>
<li><strong>2023-10-24</strong>：加州DMV暂停了Cruise的无人驾驶许可证。</li>
<li><strong>2023-10-02</strong>：一辆人类驾驶的日产汽车撞到一名行人，使行人进入一辆Cruise自动驾驶汽车（AV）的路径，随后该AV将行人拖拽了<code>20英尺</code>后才停止。</li>
<li>DMV的指控：
<ul>
<li>Cruise未能披露AV在首次撞击后又向前移动。</li>
<li>Cruise播放的视频仅显示了部分事故，没有显示行人拖拽过程。</li>
<li>DMV是从另一个政府机构得知拖拽情况的，这“阻碍了其监管”。</li>
</ul></li>
<li>NHTSA和CPUC也对Cruise采取了行动，并提出了类似指控。</li>
<li>媒体也抱怨被Cruise误导。</li>
<li>Cruise领导层以及与监管机构沟通的Cruise员工承认他们没有解释拖拽情况，但他们表示播放了完整视频，除了一个会议外，网络问题可能阻碍了监管机构看到整个事故过程。</li>
<li>Cruise员工声称NHTSA在<b>10-03</b>会议后立即收到了完整视频，而CPUC拒绝了提供完整视频的提议。</li>
<li>Cruise员工指出，他们在<b>10-03</b>向SF MTA、SFPD和SFFD播放了完整视频，没有网络问题，并与这些机构进行了充分讨论。</li>
<li>Cruise领导层承认从未通知媒体，但领导层认为Cruise对媒体的义务不同于对监管机构的义务。</li>
</ul>

<h3 id="b-scope-of-review">B. 审查范围</h3>

<ul>
<li>[无注释]</li>
</ul>

<h3 id="c-review-plan-methodology-and-limitations">C. 审查计划、方法论和局限性</h3>

<ul>
<li>审查了20.5万份“文件”，包括“电子邮件、短信、Slack通讯和Cruise内部文件”。</li>
<li>采访了88名现任和前任员工及承包商。</li>
<li>审查了第三方公司Exponent Inc.的一份报告。</li>
<li>仅进行内部审查；未采访监管机构和公共官员。</li>
<li>由于“个人情况和/或大规模裁员”，一些员工和承包商无法接受采访，但这些采访未被认为重要。</li>
<li>报告不涉及超出任务范围的更广泛问题，“例如Cruise AV及其运营的安全性或安全流程，这些问题更适于由具备工程和技术安全专业知识的人员评估”。</li>
</ul>

<h3 id="d-summary-of-principal-findings-and-conclusions">D. 主要发现和结论摘要</h3>

<ul>
<li>截至<strong>10-03</strong>早上，领导层和100多名员工都知道行人被Cruise AV拖拽了约20英尺，这是在AV停止后二次移动过程中发生的。</li>
<li>计划是通过播放完整视频来披露此事，“让‘视频自己说明情况’”。
<ul>
<li>Cruise假设监管机构和政府官员会提出问题，Cruise将提供更多信息。</li>
</ul></li>
<li>“证据权重”显示Cruise试图播放完整视频，但在3次会议中，网络问题导致无法播放，且Cruise未指出发生了行人拖拽。</li>
<li>在<strong>10-02</strong>和<strong>10-03</strong>，“Cruise领导层专注于纠正不准确的媒体报道”，即称Cruise的AV导致了事故。
<ul>
<li>这导致Cruise向“媒体、监管机构和其他政府官员”传达了关于日产汽车的信息，并遗漏了关于事故的“其他重要信息”。</li>
</ul></li>
<li>“Cruise在此事件中失败的原因众多：领导不力、判断失误、缺乏协调、与监管机构‘我们对他们’的心态，以及对Cruise应向政府和公众承担的问责和透明义务的根本性误解。Cruise必须采取果断措施解决这些问题，以恢复信任和信誉。”</li>
<li>“DMV暂停令是某些Cruise高级领导和员工典型的‘自作自受’的结果，这些人似乎没有完全理解受监管企业应如何与其监管机构互动……Cruise或其他任何企业采取认为导致严重伤害的事故视频就提供了所有必要信息，并因此无需主动、全面地向监管机构通报所有相关事实，这种方法从根本上是错误的。正如一名Cruise员工在就此事宜发给另一名员工的短信中所说，我们的‘领导辜负了我们’。”</li>
</ul>

<h2 id="ii-the-facts-regarding-the-october-2-accident">II. 关于10月2日事故的事实</h2>

<h3 id="a-background-regarding-cruise-s-business-operations">A. 关于Cruise业务运营的背景</h3>

<ul>
<li>Cruise成立于2013年，2016年被通用汽车收购（通用汽车持股79%）。</li>
<li>Cruise声明的目标：“负责任地部署世界上最先进的无人驾驶出行服务”。</li>
<li>“Cruise声明的使命是让交通更清洁、更安全、更便捷”。</li>
<li>无人驾驶网约车业务于2021年9月在旧金山启动。</li>
<li>于2022年6月开始收费。</li>
<li>已扩展到其他地区，包括海外。</li>
<li><b>10-02</b>事故是超过<code>500万英里</code>驾驶中首次发生的行人受伤事件。</li>
</ul>

<h3 id="b-key-facts-regarding-the-accident">B. 关于事故的关键事实</h3>

<ul>
<li><strong>10-02, <i>晚上9:29</i></strong>：一辆人类驾驶的日产Sentra在旧金山Market街与第5街交叉口的人行横道上撞到行人。</li>
<li>行人无视红灯和“请勿通行”信号进入人行横道，然后在日产汽车车道上停下。警方报告引用了司机和行人违反法规，并得出结论称司机“负主要责任”。</li>
<li>撞击将行人抛入Cruise AV的路径。</li>
<li>Cruise AV刹车但仍撞到了行人。</li>
<li>在完全停止后，AV移动以寻找安全地点停下，这被称为“‘最小风险状态’靠边停车操作（靠边停车操作）或‘二次移动’”。</li>
<li>AV以最高<code>7.7英里/小时</code>的速度行驶了<code>20英尺</code>，拖拽着行人。</li>
<li>日产司机逃离现场（肇事逃逸）。</li>
</ul>

<h3 id="c-timeline-of-key-events">C. 关键事件时间线</h3>

<ul>
<li><strong>10-02, <i>晚上9:29</i></strong>：事故发生，日产司机逃逸；AV传输低分辨率3秒视频（Offload 1）确认碰撞至Cruise远程协助中心。</li>
<li><strong><i>晚上9:32</i></strong>：AV传输中分辨率14秒视频（Offload 2）显示碰撞，但不包括靠边停车操作和行人拖拽。</li>
<li><strong><i>晚上9:33</i></strong>：急救人员在晚上9:33至9:38之间到达。</li>
<li><b><i>晚上9:40</i></b>：SFFD使用重型救援工具将行人从AV下移出。</li>
<li><b><i>晚上9:49</i></b>：Cruise事故响应团队将事故定为“Sev-1”（轻微碰撞）。团队在Google Meet上创建了一个虚拟“作战室”和一个专用的Slack频道（作战室Slack频道），约有20名员工加入。</li>
<li><b><i>晚上10:17</i></b>：Cruise承包商到达事故现场。一名承包商拍摄了100多张照片和视频，并记录了地面上的血迹和皮肤碎片，表明AV从撞击点移动到了最终停止位置。
<ul>
<li>另一名承包商在Cruise授权下，向SFPD提供了显示日产汽车的14秒视频。</li>
</ul></li>
<li><b><i>晚上11:31</i></b>：Cruise将事故升级为“Sev-0”，即“造成任何一方中度至重度伤害或死亡的重大车辆事故”。可能另有200名员工被召集至作战室。</li>
<li><b>10-03, <i>凌晨12:15</i></b>：事故管理团队召开虚拟会议，分享事故更新并讨论媒体策略，以反驳称AV导致事故的文章。</li>
<li><b><i>凌晨12:45</i></b>：Cruise政府事务团队联系政府官员。</li>
<li><b><i>凌晨12:53</i></b>：Cruise发布新闻稿，指出日产汽车导致了事故。CEO <abbr title="CEO, CTO, President, and co-founder">Kyle Vogt</abbr>和通讯副总裁<abbr title="Communications VP">Aaron McLear</abbr>大量编辑了新闻声明。未提及靠边停车操作或拖拽；Cruise员工声称当时不知道这些事实。</li>
<li><b><i>凌晨1:30</i></b>：AV返回Cruise设施，开始从AV下载碰撞报告数据，包括完整视频的过程。</li>
<li><b><i>凌晨2:14</i></b>：包含靠边停车操作和拖拽的45秒事故视频可用，但直到4个多小时后所有AV数据处理完毕时，才没有Cruise员工收到其就绪通知。</li>
<li><b><i>凌晨3:21</i></b>：应Cruise政府事务部要求，系统完整性总监<abbr title="Director of Systems Integrity">Matt Wood</abbr>制作了12秒事故视频，显示日产汽车撞到行人以及行人落在Cruise AV前方。视频在AV撞到行人前停止。</li>
<li><b><i>凌晨3:45</i></b>：<abbr title="Director of Systems Integrity">Wood</abbr>在作战室Slack频道（当时有77名员工）发布了已知的首次关于靠边停车操作和行人拖拽的沟通。<abbr title="Director of Systems Integrity">Wood</abbr>称AV在首次碰撞后移动了1-2个车身长度。</li>
<li><b><i>早上6:00</i></b>：Cruise举行虚拟危机管理团队（CMT）会议；讨论了行人拖拽。后续的Slack消息（6:17am, 6:25am, 6:56am）确认了关于靠边停车操作和拖拽的讨论。</li>
<li><b><i>早上6:28</i></b>：Cruise在作战室Slack频道发布了显示靠边停车和拖拽的45秒9格视频（完整视频，offload 3）。</li>
<li><b><i>早上6:45</i></b>：虚拟高级领导团队（SLT）会议；<abbr title="CEO, CTO, President, and co-founder">Vogt</abbr>和<abbr title="Communications VP">McLear</abbr>讨论是否向媒体分享完整视频或修改Cruise新闻声明，决定两者都不做。</li>
<li><b><i>早上7:25</i></b>：Cruise政府事务员工发邮件给NHTSA并提议会面。</li>
<li><b><i>早上7:45</i></b>：Cruise工程和安全团队举行初步会议，讨论碰撞和靠边停车操作。</li>
<li><b><i>早上9:05</i></b>：Cruise的监管、法律和系统完整性员工为NHTSA简报举行预备会议；他们讨论了靠边停车和拖拽。</li>
<li><b><i>早上10:05</i></b>：<abbr title="Director of Systems Integrity">Wood</abbr>和全球政府事务副总裁Prashanthi <abbr title="VP of Global Government Affairs Prashanthi">Raman</abbr>与旧金山市长交通顾问举行虚拟会议。<abbr title="Director of Systems Integrity">Wood</abbr>播放了完整视频，“据报道因其家用电脑网络连接问题”；<abbr title="Director of Systems Integrity">Wood</abbr>和<abbr title="VP of Global Government Affairs Prashanthi">Raman</abbr>均未提及或讨论靠边停车或拖拽。</li>
<li><b><i>早上10:30</i></b>：与NHTSA的虚拟会议。<abbr title="Director of Systems Integrity">Wood</abbr>播放完整视频，“再次因网络连接问题导致视频在关键位置（包括首次撞击后）冻结或黑屏/白屏”，并且同样未提及或讨论靠边停车或拖拽。</li>
<li><b><i>早上10:35</i></b>：Cruise工程和安全团队举行第二次会议讨论碰撞。</li>
<li><b><i>早上11:05</i></b>：Cruise的监管、法律和系统完整性员工为DMV和加州公路巡警（CHP）简报举行预备会议；Cruise团队未讨论靠边停车和拖拽。</li>
<li><b><i>早上11:30</i></b>：与DMV和CHP的混合现场和虚拟会议。<abbr title="Director of Systems Integrity">Wood</abbr>播放完整视频，同样因网络连接问题且未提及或讨论靠边停车或拖拽。</li>
<li><b><i>中午12:00</i></b>：虚拟Cruise CMT会议；工程师展示发现结果，包括详细说明事故期间AV移动情况的图表。显示了AV如何撞到行人，然后再次向前移动，拖拽行人约20英尺。AV程序设定最多移动100英尺，但AV内部系统标记了一个车轮速度传感器故障，因为车轮以不同速度转动（一个车轮在行人腿上打滑），从而提前停止了车辆。</li>
<li><b><i>下午12:30</i></b>：Cruise政府事务员工致电CPUC讨论<b>10-02</b>事故和视频。</li>
<li><b><i>下午12:40</i></b>：Cruise虚拟SLT会议。展示了来自CMT会议的图表。<abbr title="CEO, CTO, President, and co-founder">Vogt</abbr>、首席运营官<abbr title="COO">Gil West</abbr>、首席法务官<abbr title="Chief Legal Officer">Jeff Bleich</abbr>及其他人员出席。安全和工程团队提出暂停车队运营直到部署修复方案的问题；<abbr title="CEO, CTO, President, and co-founder">Vogt</abbr>和<abbr title="COO">West</abbr>否决了该想法。</li>
<li><b><i>下午1:40</i></b>：完整视频上传至NHTSA。</li>
<li><b><i>下午2:37</i></b>：Cruise向NHTSA提交1天报告；未提及靠边停车或拖拽。</li>
<li><b><i>下午3:30</i></b>：Cruise与SF MTA、SFPD和SFFD举行虚拟会议。<abbr title="Director of Systems Integrity">Wood</abbr>“多次播放完整视频”，没有技术问题。Cruise未提及靠边停车操作或拖拽，但官员们看到了并就此询问了Cruise。</li>
<li><b><i>下午6:05</i></b>：Cruise CMT会议。<abbr title="CEO, CTO, President, and co-founder">Vogt</abbr>和<abbr title="COO">West</abbr>结束了Sev-0作战室。一些Cruise员工后来对此表示担忧。</li>
<li><b>10-05, <i>上午10:46</i>：福布斯要求Cruise就AV拖拽事件发表评论。Cruise拒绝评论，并坚持<b>10-03</b>的新闻稿。</b></li><b>
<li><b><i>下午1:07</i>：CPUC发出信息请求，回复截止日期为<b>10-19</b>。</b></li><b>
<li><b>10-06, <i>上午10:31</i>：福布斯发表《Cruise机器人出租车在最近事故中拖拽女子20英尺，当地政客称》。</b></li><b>
<li><b>10-10, <i>下午4:00</i>：DMV要求Cruise提供更完整的视频。Cruise当天回应，提议屏幕共享视频。</b></li><b>
<li><b>10-11, <i>上午11点</i></b>：Cruise与DMV举行会议，讨论与事故无关的运营问题。DMV的视频请求被“简要讨论”。</li>
<li><b><i>下午12:48pm</i></b>：Cruise律师助理在检查更新后向NHTSA提交10天报告。报告未提及靠边停车或拖拽，“因为没有人告诉律师助理需要添加这些事实”。</li>
<li><b>10-12, <i>下午3点</i>：NHTSA通知Cruise，打算对<b>10-02</b>事故及其他3起行人相关事件进行初步评估（PE）。</b></li><b>
<li><b>10-13, <i>上午10点</i></b>：Cruise与DMV和CHP会面，分享9分6格视频，DMV澄清需要45秒9格视频（“完整视频”）。</li>
<li><b><i>下午12:19</i></b>：Cruise上传完整视频。</li>
<li><b><i>下午1:30</i></b>：Cruise与NHTSA会面，认为PE没有必要。</li>
<li><b><i>10-16, 上午11:30</i></b>：Cruise与DMV和CHP会面，后者表示不认为在<b>10-03</b>会议上看到过完整视频。</li>
<li><b><i>10-16</i></b>：NHTSA正式启动PE。</li>
<li><b>10-18, <i>下午3:00</i></b>：Cruise与CPUC举行定期月度会议。Cruise表示将满足CPUC的<b>10-19</b>截止日期。</li>
<li><b>10-19, <i>下午1:40</i></b>：Cruise应<b>10-05</b>的请求提供了信息和完整视频。</li>
<li><b>10-23, <i>下午2:35</i></b>：Cruise获悉DMV可能暂停其无人驾驶许可证。</li>
<li><b>10-24, <i>上午10:28</i>：DMV发布暂停Cruise无人驾驶许可证的命令。除少数在<b>10-23</b>听到消息的员工外，Cruise员工感到惊讶。</b></li><b>
<li><b><i>上午10:49am</i></b>：Cruise发布博客文章，声明：“事故后不久，我们的团队主动向加州机动车辆管理局（DMV）、加州公共事业委员会[原文如此]（CPUC）和国家公路交通安全管理局（NHTSA）提供了信息，包括完整视频，并一直与监管机构保持密切联系以回答他们的问题。”</li>
<li><b>11-02, <i>中午12:03</i>：Cruise提交30天NHTSA报告，其中包括对靠边停车和拖拽的讨论。</b></li><b>
<li><b>11-02</b>：Cruise因<b>10-02</b>事故召回950个系统。</li>
<li><b>12-01</b>：CPUC发布“要求说明原因令”，指其“未能提供完整信息，并就<b>2023年10月2日</b>Cruise相关事故及其后续与委员会的互动发表了误导性公开评论”。</li>
</b></b></b></b></b></b></b></ul><b><b>

<h3 id="d-video-footage-of-the-accident">D. 事故视频片段</h3>

<ul>
<li>6段视频
<ul>
<li><strong>Offload 1; <i>晚上9:29</i></strong>：低分辨率，3秒，4格。捕捉碰撞后3秒，包括音频。</li>
<li><strong>Offload 2; <i>晚上9:32</i></strong>*：14秒，9格。无音频。显示日产汽车撞到行人以及行人被抛入Cruise AV路径。</li>
<li><strong>媒体视频; <i>晚上10:04</i></strong>：21秒，4格。源自offload 2，但被放慢。</li>
<li><strong><i>凌晨1:06</i></strong>：4秒片段，来自offload 2，由<abbr title="CEO, CTO, President, and co-founder">Vogt</abbr>剪辑并发给政府事务高级副总裁<abbr title="SVP of Government Affairs">David Estrada</abbr>和首席法务官<abbr title="Chief Legal Officer">Jeff Bleich</abbr>，附言“这是我想要的片段”。<abbr title="SVP of Government Affairs">Estrada</abbr>回复“同意，如果发布视频，这应该是主要的视频”。该视频是AV左前方的单格画面，仅显示日产汽车撞到行人。<abbr title="SVP of Government Affairs">Estrada</abbr>表示这应在与监管机构的会议上首先展示，“清晰地展示发生了什么（确立人类司机的明确责任）”，但“没有证据表明这个较短的4秒视频在任何监管会议上展示过。”</li>
<li><strong><i>凌晨3:21</i></strong>：12秒9格视频，源自offload 2。Cruise全球政府事务副总裁<abbr title="VP of Global Government Affairs Prashanthi">Prashanthi Raman</abbr>和<abbr title="SVP of Government Affairs">Estrada</abbr>要求<abbr title="Director of Systems Integrity">Wood</abbr>制作14秒视频的较短版本，“考虑到昨晚的Sev 0以及我们需要与政策制定者讨论，请您为我们制作这个角度[Webviz链接]的可用视频。我们只需要展示撞击和人落在我们前方的画面，然后在那里剪断”。<abbr title="Director of Systems Integrity">Wood</abbr>制作了该视频。Cruise联邦事务高级总监<abbr title="Senior Director of Federal Affairs">Eric Danko</abbr>告诉<abbr title="Director of Systems Integrity">Wood</abbr>，“相信NHTSA会想要捕捉我们撞击时刻的视频片段”，而<abbr title="Director of Systems Integrity">Wood<abbr>回复，“一旦日志下载完毕，可以制作NHTSA版本的视频”</abbr></abbr></li>
<li><strong>“完整视频”；<i>凌晨6:28</i></strong>：45秒，9格，显示靠边停车和拖拽。无音频。完整视频链接发布在作战室Slack频道。</li>
</ul></li>
</ul>

<h3 id="e-the-facts-regarding-what-cruise-knew-and-when-about-the-october-2-accident">E. 关于Cruise何时知道10月2日事故的事实</h3>

<h4 id="1-facts-cruise-learned-the-evening-of-october-2">1. Cruise在10月2日晚间得知的事实</h4>

<h5 id="a-accident-scene">a. 事故现场</h5>

<ul>
<li>无人驾驶支持专家（DSS）于晚上9:39至9:44到达现场。</li>
<li>另一支2人DSS团队与运营团队成员和安全升级团队（SET）成员于晚上10:00至10:30到达。</li>
<li>至少一名承包商拍摄了100多张照片和视频，并表明理解行人被拖拽的情况。
<ul>
<li>承包商记录了血迹和皮肤碎片，拍摄了显示撞击后行进路线的血迹长镜头；承包商被指示将手机带回Cruise，而不是上传到通常的Slack频道；承包商认为这是为了保护受伤行人的隐私。</li>
<li>照片和视频于凌晨2:23上传至“RINO”数据库，从<b>10-03, <i>早上5:11</i></b>开始有100多名员工访问；数据库未显示哪些具体员工查看了哪些具体照片和视频。</li>
</ul></li>
<li>现场另一人否认知道拖拽情况。</li>
<li>在Cruise内部审查中（在Quinn Emanuel之前），一名远程协助操作员（RA）说他们看到“行人被甩到AV引擎盖上。你可以看到并听到撞击声”，另一人看到AV“已经靠边停下”。Quinn Emanuel直到12-14裁员后才得知这些。联系时，一人拒绝采访，另一人未回应。
<ul>
<li>另外两名受访者报告了在<b>10-02</b>晚上或<b>10-03</b>凌晨关于AV二次移动的讨论，但“此信息未经核实，且似乎与证据权重相反”。</li>
</ul></li>
<li>Quinn Emanuel采访的员工中无人表示在<b>10-02</b>知道拖拽情况。</li>
</ul>

<h5 id="b-virtual-sev-0-war-room">b. 虚拟“Sev-0作战室”</h5>

<ul>
<li>最初有20人在作战室。</li>
<li>在<b>10-02</b>和<b>10-03</b>，有200多人加入和离开作战室。</li>
<li>2名受访者回忆起在<b>10-02</b>晚上或<b>10-03</b>凌晨在Meet上的Sev-0作战室中讨论过行人拖拽。两人都无法确定谁参与了讨论以及讨论的时间。一人说是在凌晨4:00之后。</li>
<li>Cruise事故响应手册概述了事件指挥官、SLT、CMT的角色以及事故后数周的响应方式。手册未被遵循，据称被“放弃”，因为“太费人力”。</li>
</ul>

<h5 id="c-initial-media-narrative-about-the-october-2-accident">c. 关于10月2日事故的最初媒体报道</h5>

<ul>
<li>“尽管作战室应处理各种问题，如了解事故如何发生及后续步骤，但焦点很快几乎完全集中在纠正Cruise AV导致了事故的错误媒体报道上。”</li>
</ul>

<h4 id="2-facts-cruise-learned-on-october-3">2. Cruise在10月3日得知的事实</h4>

<h5 id="a-the-12-15-a-m-sev-0-collision-sfo-meeting">a. 凌晨12:15的“Sev-0碰撞SFO”会议</h5>

<ul>
<li>CMT事件经理召集了140人参会的会议。</li>
<li>重点是分享更新和媒体报道策略。</li>
<li>Slack通讯显示Cruise员工认为公众认为Cruise AV伤害了行人的风险是一场危机。</li>
<li><abbr title="SVP of Government Affairs">Estrada</abbr>对<abbr title="VP of Global Government Affairs Prashanthi">Raman</abbr>说，“感觉如果我们如此害怕发布一段开脱视频，就像是双手被绑在背后战斗，如果我们认为不会被媒体和敌人痛击，那就太天真了。”
<ul>
<li><abbr title="VP of Global Government Affairs Prashanthi">Raman</abbr>回应，“我认为我们正处于围攻之下，我们没有机会与这些头条新闻/媒体报道抗衡……我们正在溺水——而且每次都会输”</li>
<li>上述声明据称“很好地捕捉了Cruise高级领导层的感觉”。</li>
</ul></li>
<li><abbr title="CEO, CTO, President, and co-founder">Vogt</abbr>参加了会议，并希望仅发布显示日产汽车撞到行人的4秒片段。
<ul>
<li><abbr title="CEO, CTO, President, and co-founder">Vogt</abbr>坚持在发布前他要授权任何视频或媒体声明，“未经他批准，任何内容都不会分享或执行”。</li>
</ul></li>
<li>同时，通讯团队起草了要点提供给媒体，包括“AV在撞击行人后立即完全停止”，通讯团队不知道这是不准确的。</li>
</ul>

<h5 id="b-engineer-s-3-45-a-m-slack-message">b. 工程师凌晨3:45的Slack消息</h5>

<ul>
<li>Slack通讯
<ul>
<li><abbr title="Director of Systems Integrity">Wood</abbr>：我还没看到有人提到这个，在第一次远程协助会话中，AV几乎就停在旁边车辆旁边，但又向前开了1-2个车身长度才到达它的[原文如此]最终位置。</li>
<li>匿名员工：ACP，我无法访问链接，但PED是否在车辆下面时它还在移动？我理解得对吗？</li>
<li><abbr title="Director of Systems Integrity">Wood</abbr>：我相信是的，可以看到AV视频在垂直移动。</li>
</ul></li>
<li><abbr title="Director of Systems Integrity">Wood</abbr>是通过查看远程协助中心的数据得出此结论的，数据暗示AV拖拽着行人移动了1-2个车身长度。</li>
</ul>

<h5 id="c-the-6-00-a-m-crisis-management-team-cmt-meeting">c. 凌晨6:00的危机管理团队（CMT）会议</h5>

<ul>
<li>CMT讨论了靠边停车和拖拽。</li>
<li>会议有100多人参加，包括“首席运营官Gil <abbr title="COO">West</abbr>、联合创始人兼首席产品官Dan Kan、通讯副总裁、联邦事务高级总监以及通讯、法律、工程、安全、监管和政府事务团队的成员”。</li>
<li>早上6:17，工程师发消息给<abbr title="Director of Systems Integrity">Wood</abbr>，“在这次会议上他们是否提出了AV在事件后移动的问题？我加入晚了” <abbr title="Director of Systems Integrity">Wood</abbr>回复“还没有。我会提出”。</li>
<li>会议期间的Slack对话，从<abbr title="COO">West</abbr>到另外6位高级领导：
<ul>
<li><abbr title="COO">West</abbr>：ACP- 请注意，CMT会议上有报告称AV在远程协助连接前移动了1-2个车身长度（低碰撞，并在紧急停止前寻找靠边停车点）</li>
<li><abbr title="CEO, CTO, President, and co-founder">Vogt</abbr>：我们是否应该运行道路模拟，看看如果AV在其他车辆的位置会怎么做？我认为这可能相当有说服力。</li>
<li><abbr title="COO">West</abbr>：好主意——我怀疑AV会停下来，根本不会撞到行人。</li>
</ul></li>
<li>工程师在作战室Slack中总结了CMT会议，“在今天的CMT会议上，讨论了在某个时候发布/分享视频。<abbr title="Director of Systems Integrity">Matt Wood</abbr>还指出，碰撞后AV以低速行驶，行人就在车下（大约7米）。虽然未讨论，但我想指出，任何有权访问我们碰撞前AV视频的人可能都能看到碰撞后AV行驶了这段距离，因为社交媒体上有视频显示AV停下时行人就在下面，现场还有一些标记。”
<ul>
<li>工程师还指出，非工程师应能从AV碰撞前的画面加上显示AV最终位置的社交媒体视频推断出拖拽情况。在DMV暂停令发布后，也表示“我在频道中指出，不难推断出首次停止后有移动……似乎DMV完全了解整个细节是可预见的。”</li>
</ul></li>
</ul>

<h5 id="d-the-6-45-a-m-senior-leadership-team-slt-meeting">d. 凌晨6:45的高级领导团队（SLT）会议</h5>

<ul>
<li>SLT会议讨论了拖拽。</li>
<li>SLT讨论了修改媒体声明，“[这些讨论的]结果是，无论发布什么声明，我们都会坚持，因为决定是如果我们编辑先前同意的声明，我们将失去信誉。”</li>
<li>此时，通讯团队高级成员知道“AV在撞击行人后立即完全停止”的声明不准确，但通讯团队在SLT会议后继续向媒体提供不准确的声明，导致福布斯、CNBC、ABC News Digital、Engadget、Jalopnik和The Register等出版物中的陈述错误。
<ul>
<li>“完全停止”在<b>10-13</b>被删除，当时通讯员工将声明标记给法律部门，法律部门表示“我认为我们不能这么说”。</li>
</ul></li>
</ul>

<h5 id="e-the-7-45-a-m-and-10-35-a-m-engineering-and-safety-team">e. 凌晨7:45和10:35的工程和安全团队</h5>

<p>会议</p>

<ul>
<li>[无注释]</li>
</ul>

<h5 id="f-the-12-05-p-m-cmt-meeting">f. 下午12:05的CMT会议</h5>

<ul>
<li>[无注释]</li>
</ul>

<h5 id="g-the-12-40-p-m-slt-meeting">g. 下午12:40的SLT会议</h5>

<ul>
<li>“据称<abbr title="CEO, CTO, President, and co-founder">Vogt</abbr>表示，AV在检测到轮胎受干扰后停止了20英尺，这很好，而不是像AV程序设定的那样继续移动最多100英尺或一个完整街区去寻找安全地点靠边停车。”</li>
<li>安全和工程团队提出了在部署修复方案前暂停车队运营的问题。
<ul>
<li><abbr title="CEO, CTO, President, and co-founder">Vogt</abbr>和<abbr title="COO">West</abbr>否决了该想法。</li>
</ul></li>
</ul>

<h5 id="h-the-6-05-p-m-cmt-meeting">h. 下午6:05的CMT会议</h5>

<ul>
<li>CMT领导层得知SLT正在解散Sev-0作战室。</li>
<li>一些受访者对Cruise历史上最重大的事件不再安排未来的CMT会议表示担忧。
<ul>
<li>有人向首席法务官<abbr title="Chief Legal Officer">Jeff Bleich</abbr>建议“迷你CMT”应继续开会；<abbr title="Chief Legal Officer">Bleich</abbr>和其他人表示支持，但未实施。</li>
</ul></li>
</ul>

<h4 id="3-cruise-s-response-to-the-forbes-article">3. Cruise对福布斯文章的回应</h4>

<ul>
<li>福布斯就行人拖拽联系Cruise。
<ul>
<li>Cruise决定不回应，以避免引发新一轮媒体报道周期。
<ul>
<li>Cruise停止向媒体分享视频。</li>
</ul></li>
</ul></li>
</ul>

<h2 id="iii-cruise-s-communications-with-regulators-city-officials-and-other-stakeholders">III. Cruise与监管机构、市政官员及其他利益相关方的沟通</h2>

<h3 id="a-overview-of-cruise-s-initial-outreach-and-meetings-with-regulators">A. Cruise与监管机构初步接触和会议概述</h3>

<ul>
<li>“初始说明”于凌晨12:24起草；Cruise当时不知道拖拽情况。</li>
</ul>

<h3 id="b-the-mayor-s-office-meeting-on-october-3">B. 10月3日与市长办公室的会议</h3>

<ul>
<li>与市长交通顾问Alexandra Sweet的会议。</li>
<li>Cruise员工做了概述，然后<abbr title="Director of Systems Integrity">Wood</abbr>播放了完整视频。
<ul>
<li>这种方法成为了Cruise的标准演示方式。</li>
</ul></li>
<li><abbr title="Director of Systems Integrity">Wood</abbr>播放了两次完整视频，但存在连接问题。</li>
<li>Sweet显然注意到车辆再次移动，但未询问拖拽或车辆为何再次移动。</li>
</ul>

<h3 id="c-cruise-s-disclosures-to-the-national-highway-traffic-safety-administration-nhtsa">C. Cruise向国家公路交通安全管理局（NHTSA）的披露</h3>

<h4 id="1-cruise-s-initial-outreach-on-october-3">1. Cruise于10月3日的初步接触</h4>

<ul>
<li>**10-03, <i>早上7:25</i>    **：Cruise监管参与负责人发邮件给NHTSA。
<ul>
<li>NHTSA希望解决的问题包括“Cruise ADS或远程助理是否能够确定行人被困在车下或行人在地面上的位置”以及“导致事故及撞击后车辆控制动力学（横向和纵向），包括ADS预测的行人路径以及是否采取了任何碰撞避免或缓解措施”以及事故视频。</li>
</ul></li>
</ul>

<h4 id="2-cruise-s-nhtsa-pre-meeting">2. Cruise与NHTSA的预备会议</h4>

<ul>
<li>针对预期问题的谈话要点
<ul>
<li>你们是否停止了车队？
<ul>
<li><abbr title="Deputy General Counsel">Alicia Fenrick</abbr>：我们没有改变车队的运营姿态。</li>
<li>我们未发现AV响应存在故障。</li>
</ul></li>
<li>为什么车辆在首次停止后再次移动？
<ul>
<li>[未讨论] <abbr title="Director of Systems Integrity">Matthew Wood</abbr>：撞击触发了碰撞检测，车辆设计用于驶出车道靠边停车。</li>
</ul></li>
<li>为什么车辆没有预判道路上的行人并刹车？
<ul>
<li><abbr title="Director of Systems Integrity">Matthew Wood</abbr>：我认为视频不言自明，行人早已越过我们的车道进入另一车道。</li>
<li><abbr title="Deputy General Counsel">Alicia Fenrick</abbr>：行人显然已远离AV的行驶车道。期望其他车辆加速并撞到行人，然后行人翻过相邻车辆落在我们的车道上，这是不合理的。</li>
</ul></li>
</ul></li>
<li>一名员工笔记摘录：
<ul>
<li>他们要求播放视频-等到会议开始。然后另一个问题-我们在哪里结束视频。</li>
<li>Alicia：坦率地说，最大的问题。我们移动了，以及为什么移动，是我们需要解释的。事实就是事实。</li>
<li>Matt：我们为什么移动，这是碰撞响应。被检测为轻微碰撞，因此设计响应是允许的车道靠边停车。</li>
<li>如何提及这一点：触发了碰撞检测并被设计为驶出车道靠边停车。不要限定为轻微碰撞，而是碰撞检测。</li>
<li>问题将是：它停止了然后又向前移动。</li>
<li>一般前提：我们正在调查，我们正在进行深入分析，我们做了一些初步分析，这是我们得到的结果，但它只是初步的。</li>
<li>分类：撞击前、撞击时、撞击后。</li>
</ul></li>
<li>Slack消息显示了关于何时发送视频以及发送哪个视频的讨论；决定播放完整视频，以避免被“指责隐瞒”。</li>
</ul>

<h4 id="3-cruise-s-meeting-with-nhtsa-on-october-3">3. Cruise于10月3日与NHTSA的会议</h4>

<ul>
<li><abbr title="Director of Systems Integrity">Wood</abbr>播放了两次或三次完整视频，“但它一直停止或黑屏或白屏，因为他的家用电脑网络连接有问题”。</li>
<li>“NHTSA未清晰或完整地看到完整视频”。</li>
<li>未讨论靠边停车或拖拽。
<ul>
<li>预备会议记录在会议后被编辑，在此项添加了“[未讨论]”。</li>
</ul></li>
<li>NHTSA提出的一些问题的会议记录：
<ul>
<li>远程协助能否检测到行人被困？
<ul>
<li><abbr title="Director of Systems Integrity">Wood</abbr>：是的。</li>
</ul></li>
<li>传感器也能？
<ul>
<li><abbr title="Director of Systems Integrity">Wood</abbr>：是的。</li>
</ul></li>
<li>“你最不想做的事就是在行人位于车下时移动”这句话似乎被说过，但回忆者对谁说的有分歧。一些人认为<abbr title="Director of Systems Integrity">Wood</abbr>说了这话，NHTSA表示同意；一些人认为<abbr title="Director of Systems Integrity">Wood</abbr>说了这话，NHTSA重复了这句话；还有一些人认为NHTSA说了这话，<abbr title="Director of Systems Integrity">Wood</abbr>表示同意。</li>
</ul></li>
<li>会后Slack讨论
<ul>
<li>员工：“我认为我们可能需要提及Matt在NHTSA电话中说的‘你最不想做的事就是在行人位于车下时移动’。根据我的笔记和回忆，Matt说‘由于行人在车下，最不想做的就是操作’，而[NHTSA监管员]表示同意。”</li>
<li>另一名员工：“让我们看看对话如何进行。如果相关，我们应该分享。不过这不是这里的主要观点。”</li>
<li>在其他讨论中，其他员工和高管对未披露靠边停车和拖拽表示不同程度的担忧，从适中到没有（例如，联邦事务高级总监表示他“支持它……[Cruise员工]已经超出了他们的监管要求”）。</li>
</ul></li>
</ul>

<h4 id="4-cruise-s-nhtsa-post-meeting-on-october-3">4. Cruise于10月3日与NHTSA的会后事宜</h4>

<ul>
<li>NHTSA发送了视频请求，Cruise上传了完整视频。</li>
</ul>

<h4 id="5-cruise-s-interactions-with-nhtsa-on-october-12-13-and-16">5. Cruise于10月12日、13日和16日与NHTSA的互动</h4>

<h5 id="a-october-12-call">a. 10月12日电话</h5>

<ul>
<li>NHTSA监管员致电Cruise员工，告知NHTSA计划进行初步评估；员工向Cruise NHTSA团队发送了以下信息：
<ul>
<li>“她分享说，上周的事故在办公室引起了很大的不满。这将是一项相当广泛的调查，涉及车辆如何应对街道上的行人和道路上的人员。但关于上周事故的问题将包含在IR问题和分析中。我提出就上周事故进行额外简报，但她说我们相当坦率，提供了视频并告诉了他们需要知道的一切。”</li>
<li>“很难相信他们能在Panini事故中[Panini是特定AV的名称]发现我们的反应有过错，以至于超出要求我们提供后续问题……”</li>
</ul></li>
</ul>

<h5 id="b-october-13-meeting">b. 10月13日会议</h5>

<ul>
<li>“尽管PE可能导致严重后果，包括召回，Cruise的首席法务官和政府事务高级副总裁未出席。”</li>
<li>来自会议议程：“我们对此有点困惑。我们上周与您就Panini事件会面了，团队没有表达任何剩余的担忧，即使被问及是否有任何额外的担忧。是否真的对Panini事件中的AV行为仍有担忧？如果有，为什么他们不要求另一次简报？我们一直非常配合该机构，并始终提供该机构要求的信息。通过这次升级我们将获得什么，而不是我们已经提供的？提出就这些主题中的任何一个进行简报，以替代PE。”</li>
<li>还计划声明：“关于上周的事件，我们在事件发生后数小时内向该机构做了简报，提供了视频，并多次主动提出分享额外信息，包括关于行人安全的广泛话题。没有要求任何信息，这让我们质疑开启PE的动机。PE是收集信息的惩罚性手段，在声誉上有害，尤其是在新兴行业。”</li>
</ul>

<h5 id="c-october-16-pe">c. 10月16日PE</h5>

<ul>
<li>[无注释]</li>
</ul>

<h4 id="6-cruise-s-nhtsa-reports-regarding-the-october-2-accident">6. Cruise关于10月2日事故的NHTSA报告</h4>

<ul>
<li>NHTSA的SGO要求三份书面报告，包括“碰撞前、碰撞时和碰撞后的详细书面描述”。</li>
<li>Cruise的前两份报告未提及靠边停车和拖拽；在咨询通用汽车后，第三份报告提及了靠边停车和拖拽。</li>
</ul>

<h5 id="a-nhtsa-1-day-report">a. NHTSA 1天报告</h5>

<ul>
<li>原始草案由律师助理转发给副总法律顾问<abbr title="Deputy General Counsel">Alicia Fenrick</abbr>、通讯总监Erik Moser和管理法律顾问<abbr title="Managing Legal Counsel">Andrew Rubenstein</abbr>：“一辆Cruise自动驾驶汽车（‘AV’），以无人驾驶自动驾驶模式运行，在Market街与Cyril Magnin街交叉口因红灯完全停在南向车道上。一辆深色日产Sentra也停在AV左侧相邻车道。当Sentra和AV在绿灯后通过交叉口时，一名行人在车辆对面Market街的人行横道进入交叉口，并无视红灯通过。行人通过了AV的行驶车道，但在相邻车道的人行横道中间停下。不久之后，日产Sentra撞到行人，将行人抛到AV前方。AV紧急制动，但不久后与行人发生接触。这对AV没有造成损坏。日产Sentra司机在碰撞后不久逃离现场。警察和急救医疗服务（EMS）被叫到现场。行人由EMS运送。”
<ul>
<li>经<abbr title="Deputy General Counsel">Fenrick</abbr>和Moser批准[LGTM'd]；<abbr title="Managing Legal Counsel">Rubenstein</abbr>表示“GA人员建议了一些额外编辑”，包括添加行人“完全”通过了AV的行驶车道、将“抛到”改为“偏转”以及删除“这对AV没有造成损坏”；没有讨论可能包含靠边停车和拖拽。</li>
</ul></li>
<li>建立NHTSA报告系统的Cruise员工认为应包含完整细节，包括靠边停车和拖拽，但当时该员工在休假。</li>
<li>在后来的<b>10-24</b>，员工关于DMV暂停令的问答中，一名员工问“为什么决定不在给NHTSA的书面报告中包含碰撞后的靠边停车？至少，这似乎必须是一个有意识的决定，而不是意外疏忽。”
<ul>
<li><abbr title="Managing Legal Counsel">Rubenstein</abbr>为<abbr title="Deputy General Counsel">Fenrick</abbr>起草了此准备好的答复：“NHTSA报告要求的目的是通知该机构事故的发生。根据该目标和我们通常的做法，我们的报告通知了NHTSA事故已发生。此外，我们在提交报告之前已经与NHTSA会面，包括向他们展示了完整视频。该会议是我们主动接触的结果：事故后我们立即联系NHTSA安排会议讨论。我们在事件发生后的第二天早上与NHTSA团队会面，包括向NHTSA展示了完整视频。然后我们提交了报告，并在当天晚些时候发送了完整视频的副本。”</li>
<li><abbr title="Deputy General Counsel">Fenrick</abbr>批准了上述内容，但最终未给出答复。</li>
</ul></li>
<li>Quinn Emanuel指出，“很难将这一理由与NHTSA法规的明确语言相协调，该法规要求‘碰撞前、碰撞时和碰撞后细节的书面描述……’（强调后加）”。</li>
</ul>

<h5 id="b-nhtsa-10-day-report">b. NHTSA 10天报告</h5>

<ul>
<li>律师助理完全有权决定是否需要任何新信息或更新。</li>
<li>律师助理在Slack上询问三名员工，“嗨，正在跟进，看看此事件是否有任何更新？特别是，关于行人的任何状况？”
<ul>
<li>一名与执法部门打交道的员工回复“不幸的是没有。我联系了调查警长但未收到回复。这可能是由于他可能参与的其他调查。”</li>
<li>这名员工表示他们仅指行人的医疗状况，但律师助理将其理解得更广泛。</li>
</ul></li>
<li>律师助理还检查了RINO数据库中的更新，未发现任何更新，然后提交了10天报告，该报告称“自2023年10月3日原始提交以来，此事件没有更新”，然后重复了1天报告中的叙述，省略了关于靠边停车和拖拽的讨论。</li>
</ul>

<h5 id="c-nhtsa-30-day-report">c. NHTSA 30天报告</h5>

<ul>
<li>通用汽车敦促Cruise在30天报告中更全面，因此首席法务官<abbr title="Chief Legal Officer">Bleich</abbr>介入。
<ul>
<li><abbr title="Chief Legal Officer">Bleich</abbr>审查了1天和10天报告，然后跟进说“现在最重要的事情是确保我们向监管机构报告此事件时完整准确”，表示要包含靠边停车和拖拽。
</li><li><abbr title="Managing Legal Counsel">Rubenstein</abbr>反对在30天报告中包含拖拽。</li>
</ul></li>
</ul>

<h4 id="7-conclusions-regarding-cruise-s-interactions-with-nhtsa">7. 关于Cruise与NHTSA互动的结论</h4>

<ul>
<li>[无注释]</li>
</ul>

<h3 id="d-cruise-s-disclosures-to-the-department-of-motor-vehicles-dmv">D. Cruise向机动车辆管理局（DMV）的披露</h3>

<h4 id="1-cruise-s-initial-outreach-to-the-dmv-and-internal-discussion-of-which-video-to-show">1. Cruise向DMV的初步接触以及内部讨论展示哪个视频</h4>

<ul>
<li>“<abbr title="CEO, CTO, President, and co-founder">Vogt</abbr>希望仅关注日产汽车在导致事故中的作用，避免展示行人的伤势。”</li>
<li><abbr title="SVP of Government Affairs">Estrada</abbr>对<abbr title="VP of Global Government Affairs Prashanthi">Raman</abbr>表示，显然表示同意：“我认为我们应该获取<abbr title="CEO, CTO, President, and co-founder">Kyle</abbr>描述的视频片段，准备向政策制定者展示……展示撞击和人落在我们前方的画面。在那里剪断。这就是所需要的全部。”</li>
<li><abbr title="VP of Global Government Affairs Prashanthi">Raman</abbr>和<abbr title="Senior Director of Federal Affairs">Danko</abbr>不同意，坚持展示最完整的可用视频。</li>
</ul>

<h4 id="2-dmv-s-response-to-cruise-s-outreach">2. DMV对Cruise接触的回应</h4>

<ul>
<li>[无注释]</li>
</ul>

<h4 id="3-cruise-s-dmv-pre-meeting">3. Cruise与DMV的预备会议</h4>

<ul>
<li>“尽管副总法律顾问<abbr title="Deputy General Counsel">Fenrick</abbr>表示她通常不参加DMV会议，但她选择参加此次会议，以便在NHTSA和DMV会议之间有一些重叠的与会者。值得注意的是，<abbr title="Chief Legal Officer">Bleich</abbr>和<abbr title="SVP of Government Affairs">Estrada</abbr>均未参加预备会议，尽管计划与DMV主任亲自会面讨论事故。”</li>
</ul>

<h4 id="4-cruise-s-october-3-meeting-with-the-dmv">4. Cruise于10月3日与DMV的会议</h4>

<h5 id="a-dmv-meeting-discussions">a. DMV会议讨论</h5>

<ul>
<li>DMV监管员不认为播放了完整视频。</li>
<li>Cruise员工回忆不同，但许多人认为播放了完整视频，可能因连接问题。</li>
<li>未讨论靠边停车或拖拽。</li>
</ul>

<h5 id="b-cruise-s-post-dmv-meeting-reflections">b. Cruise会后对DMV会议的反思</h5>

<ul>
<li>Slack讨论
<ul>
<li><abbr title="VP of Global Government Affairs Prashanthi">Raman</abbr>：想法？</li>
<li><abbr title="Deputy General Counsel">Fenrick</abbr>：你是指DMV电话？比NHTSA更咄咄逼人……</li>
<li>ACP - 不过有点过于挑剔和不切实际。比如真的我们应该预测另一辆车会肇事逃逸并相应刹车。我认为他们对其他道路使用者碰撞的预期反应期望有点偏差。</li>
<li><abbr title="VP of Global Government Affairs Prashanthi">Raman</abbr>：他们倾向于问疯狂的假设性问题。我差点打断说我们可以讨论任何数量的假设……这就是发生的事情，但我在等他们提出后续问题再做。</li>
<li><abbr title="Deputy General Counsel">Fenrick</abbr>：疯狂的假设绝对是正确的。</li>
<li>ACP - 更大的担忧是没有监管员真正注意到我们在碾压行人后移动了。</li>
</ul></li>
<li>在另一次Slack讨论中，一名员工表示“车辆移动了，他们没有问，我们有点幸运他们没问”。
<ul>
<li>一些员工表示这是对会议的普遍共识。</li>
</ul></li>
</ul>

<h4 id="5-cruise-s-october-10-communications-with-dmv">5. Cruise于10月10日与DMV的通讯</h4>

<ul>
<li>DMV要求在<b>10-11</b>前提供视频。Cruise未这样做，但在<b>10-13</b>的会议上展示了视频。</li>
</ul>

<h4 id="6-cruise-s-october-11-meeting-with-the-dmv">6. Cruise于10月11日与DMV的会议</h4>

<ul>
<li>[无注释]</li>
</ul>

<h4 id="7-cruise-s-october-13-meeting-with-the-dmv">7. Cruise于10月13日与DMV的会议</h4>

<ul>
<li>Cruise分享了<abbr title="Director of Systems Integrity">Wood</abbr>制作的9分钟6格视频。
<ul>
<li>“值得注意的是，摄像头角度不包括最清楚地显示AV撞击行人和靠边停车操作的下方前部摄像头角度。”</li>
</ul></li>
<li>“受访者表示，DMV在会议中的语气‘感觉非常不信任’，并且‘感觉这里有些不对劲’。”
<ul>
<li>DMV对看似缺失或误导性的视频有疑问。</li>
<li>应DMV的担忧和要求，Cruise将完整视频上传到DMV在线门户。</li>
</ul></li>
</ul>

<h4 id="8-cruise-s-october-16-meeting-with-the-dmv">8. Cruise于10月16日与DMV的会议</h4>

<ul>
<li>会议原定讨论不同主题，但会议转向DMV在事故中被误导的问题；“Cruise受访者回忆DMV和CHP与会者对10月3日的演示感到愤怒，说他们的集体记忆是他们没有看到完整视频。”</li>
</ul>

<h4 id="9-cruise-s-october-23-communications-with-the-dmv">9. Cruise于10月23日与DMV的通讯</h4>

<ul>
<li>Cruise致电政治顾问，让他们了解DMV为何对旧金山自动驾驶车队扩张保持沉默。
<ul>
<li>顾问表示DMV“很生气”，并考虑撤销Cruise的运营许可证。</li>
</ul></li>
<li>内部对这是否可能发生存在分歧。“<abbr title="SVP of Government Affairs">Estrada</abbr>随后发Slack消息给首席法务官<abbr title="Chief Legal Officer">Bleich</abbr>，表示他已与DMV主任通话，‘完全没有迹象表明他们正在考虑撤销’。”
<ul>
<li><abbr title="VP of Global Government Affairs Prashanthi">Raman</abbr>再次向政治顾问核实，后者重复DMV非常生气，可能撤销。</li>
</ul></li>
</ul>

<h4 id="10-dmv-s-october-24-suspension-order">10. DMV于10月24日的暂停令</h4>

<ul>
<li><abbr title="SVP of Government Affairs">Estrada</abbr>致电DMV主任Gordon询问暂停事宜，但被搪塞。</li>
<li><abbr title="CEO, CTO, President, and co-founder">Vogt</abbr>加入通话并做出个人呼吁，称他“从13岁起就致力于尝试提高驾驶安全性”。</li>
<li>呼吁失败，暂停令不久后发布。</li>
<li>Slack对话
<ul>
<li><abbr title="SVP of Government Affairs">Estrada</abbr>：<abbr title="CEO, CTO, President, and co-founder">Kyle</abbr>领导我们的回应，我们提供了“完整”视频，如果是战斗，我们将坚持这一点。</li>
<li><abbr title="Chief Legal Officer">Bleich</abbr>：ACP- 这将是一场艰难的战斗。DMV和CHP有信誉，Steve Gordon似乎发誓他没有看到视频的结尾。Cruise员工的话不会被信任。我认为我们应该聘请外部公司审查事件经过并进行内部报告，因为否则没有人会相信我们。我们应该考虑这样做以及如何传达信息。</li>
<li><abbr title="SVP of Government Affairs">Estrada</abbr>：是的，同意这很艰难，我们需要这样做，因为我们有事实，我们可以有宣誓声明和数据分析支持我们。这不是他说她说。我们有证据。如果我们用事实证明一个虚假陈述，这对声誉挽救很重要。</li>
<li>Steve甚至不再试图提出这个主张。他转而辩称我们应该强调靠边停车尝试。他们提出这样的主张是巨大的越界，而我们有能力证明这是错误的。</li>
</ul></li>
</ul>

<h4 id="11-post-october-24-dmv-communications">11. 10月24日之后的DMV通讯</h4>

<ul>
<li><a href="https://web.archive.org/web/20231024184429/https://getcruise.com/news/blog/2023/a-detailed-review-of-the-recent-sf-hit-and-run-incident/" rel="noopener noreferrer">Vogt发布了这篇题为《关于近期旧金山肇事逃逸事故的详细回顾》的博客文章</a>。
<ul>
<li>[报告仅包含博客文章的节选，但出于我认为详细查看报告是值得的同样理由，我认为值得查看上面链接的博客文章；我对这篇已删除博客文章的解读是，它试图将责任归咎于“肇事逃逸”司机，并多次强调；博客文章似乎还包括模拟讨论的视频，其中<abbr title="CEO, CTO, President, and co-founder">Vogt</abbr>说“我们是否应该运行道路模拟，看看如果AV在其他车辆的位置会怎么做？我认为这可能相当有说服力。”]</li>
<li>[博客文章确实讨论了靠边停车和拖拽，说“AV检测到碰撞，使车辆停止；然后试图靠边停车以避免造成进一步的道路安全问题，将个人向前拖拽了约20英尺”]</li>
</ul></li>
</ul>

<h4 id="12-conclusions-regarding-cruise-s-communications-with-the-dmv">12. 关于Cruise与DMV通讯的结论</h4>

<ul>
<li><abbr title="Chief Legal Officer">Bleich</abbr>：“[DMV]的主要担忧是我们的车辆最初没有区分人和其车身下的另一个物体，因此进入了最小风险状态（MRC）。其次，他们觉得我们应该在第一次会议中立即强调AV的第二次移动。事实上，在第一次会议中——尽管我们向他们展示了完整视频——他们（和我们）都专注于确认我们在碰撞前没有不安全操作，我们没有导致与行人的初次接触。他们没有关注视频的结尾，并且——因为他们没有提出——我们的团队没有主动解决它。”</li>
<li><abbr title="CEO, CTO, President, and co-founder">Vogt</abbr>：“我非常纠结于我们的GA团队没有主动向DMV提供关于二次移动的信息，并且在处理事件过程中，我记得收到关于分享内容的不一致报告。在某个时刻一定是做出了糟糕的判断，我想知道这是如何发生的。”</li>
<li><abbr title="Chief Legal Officer">Bleich</abbr>：“ACP——我与你一样担心第二次移动没有成为讨论的一部分。我不知道负责简报的团队是否有过深思熟虑的决定。我相信他们仍然处于前一天晚上的模式，即他们正在反驳我们应对撞到行人负责，或我们在行人落入我们路径时反应不够快的假设。但正如我探究关于我们分享了什么以及何时分享的基本信息时，我有同样的挫败感，即日期被混淆或细节被遗漏。我不知道这是故意的，还是人们只是很难准确回忆起在事件发生后立即做了什么或说了什么。”</li>
<li>“这些Slack消息传达出，公司的三位高级领导——CEO、CLO和COO——并未积极参与Cruise历史上最严重事故的监管响应。相反，他们试图在事后拼凑出发生了什么。”</li>
</ul>

<h3 id="e-cruise-s-disclosures-to-the-sf-mta-sf-fire-department-and-sf-police">E. Cruise向SF MTA、SF消防局和SF警察局的披露</h3>

<p>局</p>

<ul>
<li>在播放视频后，一名政府官员问“这辆车在女人还在下面时移动了，我们看到的是这样吗？”，这引发了一系列关于此话题的讨论。</li>
<li>会议中的四名Cruise员工中有两名报告看到靠边停车和拖拽时感到震惊，显然没有意识到发生了这种情况。</li>
</ul>

<h3 id="f-cruise-s-disclosures-to-the-california-public-utilities-commission-cpuc">F. Cruise向加州公共事业委员会（CPUC）的披露</h3>

<h4 id="1-cruise-s-october-3-communications-with-the-cpuc">1. Cruise于10月3日与CPUC的通讯</h4>

<ul>
<li>CPUC和Cruise对是否提议播放完整视频存在分歧。</li>
</ul>

<h4 id="2-cpuc-s-october-5-data-request">2. CPUC于10月5日的数据请求</h4>

<ul>
<li>CPUC要求在<b>10-19</b>前提供视频；Cruise的标准政策是在最后一天回复，因此视频于<b>10-19</b>发送。</li>
</ul>

<h4 id="3-cruise-s-october-19-response-to-cpuc-s-data-request">3. Cruise于10月19日对CPUC数据请求的回应</h4>

<ul>
<li>视频，以及以下摘要：“[日产]Sentra撞到行人，将行人偏转到AV前方。AV在紧急制动前向右偏转，但不久后与行人发生接触。AV随后试图通过驶出车道来实现最小风险状态（MRC），然后到达其最终停止位置。日产Sentra司机在碰撞后不久逃离现场。”</li>
</ul>

<h4 id="4-conclusions-regarding-cruise-s-disclosures-to-the-cpuc">4. 关于Cruise向CPUC披露的结论</h4>

<ul>
<li>[无注释]</li>
</ul>

<h3 id="g-cruise-s-disclosures-to-other-federal-officials">G. Cruise向其他联邦官员的披露</h3>

<ul>
<li>Cruise的初步接触侧重于传达事故是由肇事逃逸的日产司机造成的。</li>
<li>在<b>10-24</b> DMV暂停后，“接触侧重于传达其相信在10月2日事故后与加州DMV、CPUC和NHTSA等监管机构密切合作的信息。”</li>
</ul>

<h2 id="iv-the-aftermath-of-the-october-2-accident">IV. 10月2日事故的后续</h2>

<h3 id="a-the-cruise-license-suspension-by-the-dmv-in-california">A. DMV在加州对Cruise的许可证暂停</h3>

<ul>
<li>仍允许有人类司机在方向盘后驾驶。</li>
</ul>

<h3 id="b-the-nhtsa-pe-investigation-and-safety-recall">B. NHTSA PE调查和安全召回</h3>

<ul>
<li>[无注释]</li>
</ul>

<h3 id="c-the-cpuc-s-show-cause-ruling">C. CPUC的“要求说明原因裁决”</h3>

<ul>
<li>[无注释]</li>
</ul>

<h3 id="d-new-senior-management-of-cruise-and-the-downsizing-of-cruise">D. Cruise的新高级管理层和Cruise的缩编</h3>

<ul>
<li>[无注释]</li>
</ul>

<h2 id="v-summary-of-findings-and-conclusions">V. 发现和结论摘要</h2>

<ul>
<li>“当Cruise的法律、政府事务、运营和系统完整性员工在10月3日与监管机构及其他政府官员会面时，他们知道或应该知道Cruise AV进行了靠边停车操作，并在车辆下方拖拽了行人约20英尺。”
<ul>
<li>Cruise对监管机构披露义务的被动、不透明方法反映了对其监管机构需要知道什么以及何时知道的基本误解。”</li>
</ul></li>
<li>“尽管Cruise和Quinn Emanuel都无法最终确定NHTSA或DMV在这些会议上看到了包含靠边停车操作和拖拽在内的完整视频，但证据权重表明Cruise试图在这些会议上播放完整视频；然而，网络连接问题阻碍或阻止了这些监管员清晰或完整地看到视频。”
<ul>
<li>“面对这些导致视频冻结或黑屏/白屏的网络连接问题，Cruise员工保持沉默，未能确保监管员理解他们可能无法看到的内容——即Cruise AV在首次撞击后再次向前移动，在车辆下方拖拽了行人。”</li>
</ul></li>
<li>“即使如一些Cruise员工所说，他们在某些监管简报时不知道靠边停车操作和行人拖拽（这本身引发其他担忧），Cruise领导层和其他人员在10月3日白天得知了10月2日事故的全部细节，应该采取纠正措施。”</li>
<li>“虽然Cruise员工明显表现出判断失误以及未能理解透明度和问责制的重要性，但根据Quinn Emanuel目前的审查，证据并未确定Cruise员工试图故意误导政府监管员关于1</li></ul></b></b><p><em>由 mimo-v2.5 模型翻译，花费 36460 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/cruise-report/</link>
      <guid isPermaLink="false">https://danluu.com/cruise-report/</guid>
      <pubDate>Mon, 29 Jan 2024 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>为什么人们选择在[差平台]发布内容，而不是在[好平台]？</title>
      <description>[AI 摘要] 本文分析了人们选择在Twitter等短平台而非博客发布内容的四大原因：更高互动、社交圈所在、更低心理门槛和更易变现。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文分析了人们选择在Twitter等短平台而非博客发布内容的四大原因：更高互动、社交圈所在、更低心理门槛和更易变现。</div><p>当某人在Mastodon/Twitter/Threads等平台发布热门内容时，你常会看到一类评论，比如在视频下也常见“为何发推特串？这更适合作为博客文章”或“为何做视频？这更适合作为博客文章”。但这类评论往往措辞更激烈，例如：</p>
<blockquote>
<p><a href="https://news.ycombinator.com/item?id=31311904" rel="noopener noreferrer">我读不了那些跨页的推文，因为发帖者每条回复只写5个词。我觉得普通互联网完全愚蠢：Twitter、tiktok、Instagram等等。这完全是浪费精力</a>。</p>
</blockquote>
<p>或</p>
<blockquote>
<p><a href="https://news.ycombinator.com/item?id=31312967" rel="noopener noreferrer">当有人选择在推特上写博客，你就知道这内容顶多是浅薄的，更可能纯粹是愚蠢的（比如这个案例）</a></p>
</blockquote>
<p>这类评论相当常见，例如：我查看了Foone在HN上得分200以上的最近10个推特串，其中9个都有类似抱怨使用推特的评论。</p>
<p>人们常表示困惑，为何有人会选择使用[差平台]，例如“<a href="https://news.ycombinator.com/item?id=33234817" rel="noopener noreferrer">为了表达观点，这得发多少条推文？200条？没人想过‘这内容要是放在一个页面上会更连贯’？我不懂社交媒体</a>”或“<a href="https://news.ycombinator.com/item?id=31723037" rel="noopener noreferrer">拜托，输入简短描述并上传100张图片，会比一次性写完所有内容再添加些连接词更容易？……客观来说这工作量更大</a>”。</p>
<p>就个人而言，我并不喜欢视频形式，对于95%的YouTube视频，我宁愿以博客文章而非视频形式获取信息（如果Google真的严打广告屏蔽，这点会更明显），而且我认为，对于关注信息的读者来说，长篇博客文章基本上优于在[差平台]上的长推文串。但我也认识到，如果不存在[差平台]这类工具，许多我想读的内容根本不会出现。</p>
<p>退一步看大局，我见过人们使用[差平台]主要有四个原因：它能获得更多互动、那里有他们的朋友、摩擦更低，以及变现更容易。</p>
<h3 id="engagement">互动</h3>
<p>互动原因最简单，所以我们先看这个。仅从人们花费时间的平台来看，推特、Instagram等短形式平台完全主导了Medium、Blogspot等长形式平台；这可以从这些公司的估值、调查数据等方面看出。Substack是当前最热门的长形式内容平台，其最新估值约为6亿美元，相比短形式平台的价值基本是个舍入误差（我不包括Wordpress或Squarespace等，其价值很大一部分来自文章和帖子之外的东西）。资金正流向人群，而人群已大多从长形式内容迁移。如果与使用Substack的人交流其读者和增长来源，你会发现这来自推特等平台，因此，即使优化互动或收入的长形式内容创作者仍会产出大量短形式内容<sup id="fnref:O"><a href="#fn:O" rel="noopener noreferrer">1</a></sup>。</p>
<h3 id="friends">朋友</h3>
<p>朋友原因可能是次简单的。许多人会使用周围人都在用的平台。现实中，如果我年轻十岁，在2023年而非2013年开始做网络内容，我很可能会先尝试直播再尝试博客。但作为年长、脱节的人，即使知道博客相对视频是衰退媒介，我还是在2013年尝试开了博客。这似乎对我效果不错，所以我坚持下来，但这似乎是代际问题。虽然有比我年长的人做视频，也有比我年轻的人写博客，但从年龄分布看，我离人们普遍转向视频的年龄并不远，如果我真的计划长期做，而非仅仅做<a href="https://danluu.com/writing-non-advice/#friction" rel="noopener noreferrer">开始时最低摩擦的事</a>，我会从视频开始。今天，对于刚开始分享想法的人来说，做视频是很自然的。</p>
<h3 id="friction">摩擦</h3>
<p>当[差平台]是推特、Mastodon、Threads等微博平台时，朋友原因通常仍适用——这些平台上的用户常属于他们互动的社区，对他们来说，将内容保留在充满社区成员的平台比放到别处更合理。但对于内容被广泛阅读的人来说，更大的原因是许多人发现这些平台比写博客摩擦小得多。当人们指出这点时，[差平台]的讨厌者常感到困惑，回应如：</p>
<blockquote>
<p>拜托，输入简短描述并上传100张图片，会比一次性写完所有内容再添加些连接词更容易？……客观来说这工作量更大</p>
</blockquote>
<p>一方面，我接触的大多数广受欢迎的程序员/科技博主实际上使用摩擦更高的平台（例如<a href="https://twitter.com/danluu/status/1204576631890698240" rel="noopener noreferrer">Jekyll</a><a href="https://twitter.com/danluu/status/1244050309648797697" rel="noopener noreferrer">的摩擦</a>和<a href="https://mastodon.social/@danluu/109542037575670321" rel="noopener noreferrer">Hugo</a><a href="https://twitter.com/danluu/status/1244050309648797697" rel="noopener noreferrer">的摩</a><a href="https://commaok.xyz/post/on_hugo/" rel="noopener noreferrer">擦</a>）。但原则上，他们可以使用Substack、托管的WordPress或此评论者认为“客观”更低摩擦的平台，但这根本误解了摩擦的来源。当人们说[差平台]摩擦更低时，通常指写作和发布某物的心理障碍，而非实际发布所需的点击次数。我们可以争论这是否理性，是否“客观”合理，但最终，许多人发现在短文本块而非单个大文本块的平台上写作，在心理上更容易，这是简单事实。</p>
<p>我<a href="https://mastodon.social/@danluu/" rel="noopener noreferrer">有时在Mastodon上写东西</a>，因为我觉得对某些内容它适合做平台。当然，既然问题不在于点击次数，而是某种潜在情感动机，其他人有不同原因。例如，<a href="https://nitter.net/Foone/status/1066548390517854208" rel="noopener noreferrer">Foone说</a>：</p>
<blockquote>
<p>不是自夸或别的，但我在Hackernews或Reddit上被转发时最喜欢的部分是，每次总有一个高赞回复说“天哪，这本可以是一篇博客文章！为什么写20条推文，而不是一篇博客文章？”</p>
<p>因为我写不了博客文章，该死的。
我有注意力缺陷多动障碍。很严重的那种，正在治疗，但治疗效果不太好。
我无法专注于写博客文章。这不会发生的</p>
<p>如果我尝试写博客文章，最终会半途而废，因为我无法将其编辑成可读和可发布的状态。所以如果我完全转向博客：
你会得到：没有内容
我会得到：大量未完成的草稿和一种自己是无用废物的感觉</p>
<p>但我能做冗长的推文串。它们不需要长时间高度集中，没有我写博客文章时没完没了的编辑，我能做到。我做了很多！它们只是冗长且在推特上，有些人不喜欢</p>
</blockquote>
<p>Foone提到的问题甚至并不罕见——我最喜欢的三位博主中，有三位提到他们只能一口气写完东西，要么有足够的势头写完一整篇博客文章，要么就写不完。只能一次写一条推文与只能在一次写作中完成你能写的内容之间存在规模差异，但这些是程度上的差异，而非本质上的差异。</p>
<h3 id="revenue">收入</h3>
<p>无论什么原因让人们觉得[差平台]比[好平台]摩擦更低，让人们使用适合自己的平台意味着我们获得更多内容。对于视频，同样的情况也适用，因为<a href="https://twitter.com/danluu/status/1588318512258650112" rel="noopener noreferrer">视频变现比文字好得多</a>，而且有很多内容在视频上能很好变现，在文字形式上可能就不行。</p>
<p>举个随意的例子，汽车内容就是这样一个领域。例如，如果你买车，想了解汽车的详细实用评测，以及与其他可能考虑的车型的比较，在YouTube出现之前，据我所知，没人做过接近<a href="https://www.youtube.com/@AAutoBuyersGuide" rel="noopener noreferrer">Alex Dykes在Alex on Autos上做的深度</a>的内容。如果你翻开鼎盛时期的汽车杂志，比如1997年的Car and Driver或Road and Track，没有任何内容达到Alex做的1/10深度，即使到今天，现代汽车杂志也如此。许多汽车内容的子类别也是如此，例如<a href="https://www.youtube.com/@tyrereviews" rel="noopener noreferrer">Jonathan Benson在Tyre Reviews上的内容</a>。在Jonathan之前，没人以同样广度和深度测试轮胎并撰写报告（轮胎公司的工程师做过这类测试甚至更多，但你必须直接与他们交谈才能获得信息）<sup id="fnref:V"><a href="#fn:V" rel="noopener noreferrer">2</a></sup>。你可以在许多汽车内容之外的领域找到类似模式。虽然这取决于领域，但在许多情况下，如果没有视频，内容根本不会存在。总的来说，人们不仅比阅读文字更愿意看视频，而且视频变现比文字好得多，这使得人们能将提供深度信息作为职业，这在文字形式中是不可能的。在某些领域，你可以通过付费通讯赚不少钱，但这本质上就是汽车杂志，它们从未能支持类似Alex Dykes所做的内容，似乎也不合理支持Jonathan Benson在YouTube上的类似内容。</p>
<p>或者，从科技界举个例子，在Lucy Wang创建她的YouTube频道<a href="https://www.youtube.com/@TechwithLucy" rel="noopener noreferrer">Tech With Lucy</a>后不久，当她有5万订阅者，典型视频有几千到几万观看，偶尔有十万观看时，<a href="https://twitter.com/danluu/status/1589775359243091969" rel="noopener noreferrer">她指出她的收入超过了在AWS工作时（大部分钱可能来自赞助）</a>。相比之下，我的博客文章都有超过一百万点击，我肯定赚不到Lucy在AWS赚的那么多；相反，我的博客勉强能付房租。如果你把大部分内容放在付费墙后面，可以适度地变现一些文字，例如<a href="https://blog.pragmaticengineer.com/" rel="noopener noreferrer">Gergely Orosz在他的通讯中这样做</a>，但如果你想大部分或完全发布免费内容，视频通常主导文字。</p>
<h3 id="non-conclusion">无结论</h3>
<p>虽然我更喜欢在YouTube/Twitter/Threads/Mastodon等上看到的大多数内容托管在文字博客上，但现实是，如果必须将其写成长形式文字而非分段短形式文字或视频，大多数内容根本不会出现。也许几年后，摘要工具会变得足够好，我可以消费它们，但今天，我尝试过的所有工具经常严重错误传达关键细节，所以我们只能接受内容以创作的形式存在。</p>
<p><b><a href="https://jobs.ashbyhq.com/freshpaint/bfe56523-bff4-4ca3-936b-0ba15fb4e572?utm_source=dl" rel="noopener noreferrer">如果你正在找工作，Freshpaint正在招聘招聘人员、软件工程师和支持工程师。我是该公司的投资者，所以你应该带着通常的保留态度看待此事，但如果你想加入一个快速增长的早期创业公司，他们似乎找到了产品市场契合点，收入方面一直在极快增长。</a></b></p>
<p><i>感谢Heath Borders、Peter Bhat Harkins、James Young、Sophia Wisdom和David Kok的评论/修正/讨论。</i></p>
<h3 id="appendix-elsewhere">附录：其他地方</h3>
<ul>
<li><a href="https://www.ftrain.com/wwic" rel="noopener noreferrer">Paul Ford的WWIC（Why Wasn't I Consulted）</a>是本文的更通用版本</li>
</ul>
<p>以下是David Kok的评论，来自一位80岁桥牌玩家抱怨桥牌为何衰落的讨论，这位80岁玩家声称主要原因是智商下降，年轻人（指60岁以下的人）太蠢而无法玩桥牌这类智力游戏；许多其他玩家赞同：</p>
<blockquote>
<p>与其说是关于年龄段的错误但有意义的陈述，我总是将“智商下降”这类话解释为“我不快乐且难以表达”，而当其他人同意时，他们其实也是在说“是的我也是”。</p>
</blockquote>
<p>如果你将David Kok的评论改编到抱怨为何某内容不是博客文章的语境中，这是对我在本文中给出理由无关紧要的元原因（对某些人来说）——这些理由只对关心理由的人重要；如果某人只是发泄情绪，他们给出的理由是其感受的表达而非合理理由，那么人们不写博客文章的原因就无关紧要。</p>
<p>总之，“为何在那里而不是这里发布”是个常见话题，我确信其他人写过相关文章，我会很感兴趣。请随时<a href="https://mastodon.social/@danluu/" rel="noopener noreferrer">将其他相关文章转发给我</a>。</p>
<h3 id="appendix-hn-comments-on-foone-s-last-10-twitter-threads">附录：HN上对Foone最近10个推特串的评论。</h3>
<p>我查找了Foone最近在HN上得分200以上的推特串，10个中有9个都有抱怨Foone为何使用推特以及为何作为博客文章会更好的评论。[不包括形如“对于那些和我一样讨厌推文串的人：<a href="https://threadreaderapp.com/thread/1014267515696922624.html&quot;" rel="noopener noreferrer">https://threadreaderapp.com/thread/1014267515696922624.html"</a>的评论，这类评论比下面带抱怨但也有有用内容的评论更多，比如串的其他版本链接。]</p>
<h4 id="never-trust-a-system-that-seems-to-be-working-https-news-ycombinator-com-item-id-33233720"><a href="https://news.ycombinator.com/item?id=33233720" rel="noopener noreferrer">永远不要相信看似正常工作的系统</a></h4>
<p>最早的评论之一是抱怨它在推特上，不久之后就是</p>
<blockquote>
<p>为了表达观点，这得发多少条推文？200条？没人想过“这内容要是放在一个页面上会更连贯”？我不懂社交媒体</p>
</blockquote>
<h4 id="someday-aliens-will-land-and-all-will-be-fine-until-we-explain-our-calendar-https-news-ycombinator-com-item-id-32975173"><a href="https://news.ycombinator.com/item?id=32975173" rel="noopener noreferrer">总有一天外星人会降落，一切都会很好，直到我们解释我们的日历</a></h4>
<blockquote>
<p>这内容更适合写成短篇小说，但我离题了。</p>
<p>这种玩意儿太棒太有趣了，不应该在推特上[少数对此表示赞同的抱怨之一]</p>
<p>这人每次他们的内容被转发到这网站时都超级生气，总是发巨长的帖子，就像原发帖者一样刷屏，太搞笑了。</p>
</blockquote>
<h4 id="you-want-to-know-something-about-how-bullshit-insane-our-brains-are-https-news-ycombinator-com-item-id-32303786"><a href="https://news.ycombinator.com/item?id=32303786" rel="noopener noreferrer">你想了解我们大脑有多疯狂和扯淡吗？</a></h4>
<blockquote>
<p>他们在推特上就能容忍阅读？</p>
<p>认真提问：为什么出版商把他们的博客文章分解成无数条推文？互动网页算法是否偏爱推文串中的推文数量？我看到这正成为更多趋势</p>
<p>这是一篇非常有趣的投稿。但是，推特的字符限制真有毒。</p>
<p>在我看来，Foone的网络存在是有害的。他们不是写一篇条理清晰的文章发布在博客上，然后在一条推文中总结指向该文章的链接，而是做了相反的事：写了数十条推文串，然后用一篇博客文章总结那些推文。这不是我想鼓励的网络趋势，但唉，它正在流行起来。</p>
<p>哦，我不在乎作者怎么写，也不在乎下面是否有图表关系（或其他任何东西）。只是推特让阅读这类内容的体验变得非常繁琐。</p>
</blockquote>
<h4 id="reverse-engineering-skifree-https-news-ycombinator-com-item-id-31718756"><a href="https://news.ycombinator.com/item?id=31718756" rel="noopener noreferrer">逆向工程Skifree</a></h4>
<blockquote>
<p>这应该是一篇博客或直播。</p>
<p>即使这种形式？</p>
<p> 我真的不理解。对他们来说这样发布很麻烦，对我们来说这样阅读也很麻烦。我希望马斯克接管推特并把它搞垮，这样我们就能回到真正的博客文章。 </p>
</blockquote>
<p>有人指出Foone说过他们觉得写长形式内容不可能，只能写短形式媒体，对此回应如下：</p>
<blockquote>
<p>拜托，输入简短描述并上传100张图片，会比一次性写完所有内容再添加些连接词更容易？</p>
<p>显然那是他们的权利，他们可以做任何想做的事，但客观来说这工作量更大，我真诚希望这趋势会消失。</p>
</blockquote>
<h4 id="everything-with-a-battery-should-have-an-off-switch-https-news-ycombinator-com-item-id-31287112"><a href="https://news.ycombinator.com/item?id=31287112" rel="noopener noreferrer">所有带电池的设备都应该有开关</a></h4>
<blockquote>
<p>你忘了，foone不会从推文串转向长形式博客。[实际上是对人们总是抱怨这点的元评论，而非抱怨，我认为]</p>
<p>我读不了那些跨页的推文，因为发帖者每条回复只写5个词。我觉得普通互联网完全愚蠢：Twitter、tiktok、Instagram等等。这完全是浪费精力。</p>
<p>他显然知道[在推特上发长串]是个问题，他应该解决它。</p>
</blockquote>
<p>有人指出Foone说过他们无法写长形式博客文章，那人回应：</p>
<blockquote>
<p>你可以像推特那样逐步添加到博客文章。功能上是一样的，只是媒介不是线程层次结构。没必要像他说的那样必须完全写好才发布。</p>
<p>我自己的博客文章在发布后经常修改10次以上。</p>
<p>这对成千上万的人来说效果不好，这就是为什么总有抱怨……当某事次优时，你完全有权抱怨。将长篇抱怨作为推文串发布对消费者来说是次优的</p>
<p>我有点欣赏这个信号：当有人选择在推特上写博客，你就知道这内容顶多是浅薄的，更可能纯粹是愚蠢的（比如这个案例）</p>
</blockquote>
<h4 id="there-s-an-arm-cortex-m4-with-bluetooth-inside-a-covid-test-kit-https-news-ycombinator-com-item-id-29698887"><a href="https://news.ycombinator.com/item?id=29698887" rel="noopener noreferrer">Covid检测试剂盒中有一个带蓝牙的ARM Cortex-M4芯片</a></h4>
<p>令人惊讶，我没看到任何抱怨，尽管有一条评论被编辑为“.”。</p>
<h4 id="taking-apart-the-2010-fisher-price-re-released-music-box-record-player-https-news-ycombinator-com-item-id-29048819"><a href="https://news.ycombinator.com/item?id=29048819" rel="noopener noreferrer">拆解2010年Fisher Price重新发行的音乐盒唱片机</a></h4>
<blockquote>
<p>为什么这是推文串？为什么不是博客？</p>
</blockquote>
<p>接着是</p>
<blockquote>
<p>我喜欢完全没人理解这个笑话……Foone是个反社会者，觉得某些词不该用来指代Foone，因为他们不喜欢。事实上任何人都不该谈论Foone。</p>
</blockquote>
<h4 id="while-posting-to-tumblr-e-and-w-keys-just-stopped-working-https-news-ycombinator-com-item-id-28601684"><a href="https://news.ycombinator.com/item?id=28601684" rel="noopener noreferrer">在Tumblr上发布时，E和W键突然失灵了</a></h4>
<blockquote>
<p>只是热键检测出错。这并不奇怪，因为在网站上实现热键完全是个雷区。我不认为你可以从中得出Tumblr写得很差的结论。可能只是测试不充分。</p>
</blockquote>
<p>因为那条评论对读过链接的人来说毫无意义，有人问“你读完整个串了吗？”，评论者回应：</p>
<blockquote>
<p>没有，因为推特让它完全不可读。</p>
</blockquote>
<h4 id="my-mouse-driver-is-asking-for-a-firewall-exemption-https-news-ycombinator-com-item-id-28274305"><a href="https://news.ycombinator.com/item?id=28274305" rel="noopener noreferrer">我的鼠标驱动程序请求防火墙例外</a></h4>
<blockquote>
<p>我们能禁止在这里发布推特吗？在所有界面点击时，都会弹出烦人的窗口。你可以关掉它，但它<em>会撤销你的点击</em>，所以任何导航都变得非常麻烦。</p>
<p>或者用某种推特转可读工具替换推特网址</p>
</blockquote>
<h4 id="duke-nukem-3d-mirror-universe-https-news-ycombinator-com-item-id-26513877"><a href="https://news.ycombinator.com/item?id=26513877" rel="noopener noreferrer">毁灭公爵3D镜像宇宙</a></h4>
<blockquote>
<p>这很了不起，但推特是这类文字的糟糕媒介。我希望这是发布在正常平台上，这样我可以轻松分享。</p>
<p>如果这是一篇博客文章而不是一堆推文，我们就不用展开多个回复才能看到全部内容</p>
<p>呃为什么这不是博客或YouTube视频？<em> specifically to annoy foone</em></p>
<p>是的，长形式推特是烂透了。但foone很棒，也许它们互相抵消了？</p>
<p>我讨厌推特。它正在慢慢毁掉互联网。</p>
</blockquote>
<h4 id="non-foone-posts">非foone帖子</h4>
<p>当然，这种现象并非Foone独有。例如，在我在HN上看到的最后一个推文串中，前五条评论中有两条是：</p>
<blockquote>
<p>这家伙有博客吗？</p>
</blockquote>
<p>和</p>
<blockquote>
<p>这就是为什么“在X上发布某物”的答案应该是“直接拒绝”。不可能在那里说出任何有细微差别或需要背景才能理解的话，但不幸的是，被X迷住的人根本无法开始以别人可能的方式看待他们所做的事情。</p>
</blockquote>
<p>我之所以查看Foone的串，是因为我知道他们倾向于发布短形式平台内容，查看10个Foone串比查看10个随机串更有趣。</p>
<div>
<hr>
<ol>
<li id="fn:O"><p>当然，几乎没人优化收入，因为大多数人从他们发布在互联网上的内容中赚不到钱。我怀疑只有一小部分人有意识地优化互动，但<a href="https://www.patreon.com/posts/25835707" rel="noopener noreferrer">就像我们看到的</a><a href="https://twitter.com/danluu/status/1594798257485795328" rel="noopener noreferrer">威望</a>一样，似乎有很多无意识地优化互动的现象。在平台内你可以看到这点的地方是（我研究了数百个例子），当人们开始使用像Mastodon或Threads这样的平台时。他们会发布很多不同的东西。大多数东西不会获得太多关注，少数会。他们可以继续发布相同的东西，但通常，随着时间的推移，他们会减少发布低互动内容，而增加发布高互动内容。平台有各种方式试图让其他人与你的内容互动变得有回报，平均而言，这似乎对人们有效。这是一个平台内而非平台间的例子，但如果这对人们有效，平台间的推理似乎也应该成立。</p>
<p>就个人而言，我并非优化互动或收入，但我一直靠<a href="https://patreon.com/danluu" rel="noopener noreferrer">Patreon收入</a>支付房租，所以这样做可能是合理的。但至少目前，研究感兴趣的事感觉是更高优先级，即使这有点像减少收入和互动的举动。例如，<a href="https://mastodon.social/@danluu/111729303313181364" rel="noopener noreferrer">wc显示我上一篇文章有2万字，这意味着写两遍可能花了大约7小时40分钟</a>。如果我做短形式内容，不久前我做了个实验，尝试每天发推文几个月，这使我的Twitter关注者增加了约50%（从约2万到3万）。推特实验可能花了和我写上一篇文章差不多的时间（不包括做研究的时间，这包括阅读五本书和大约15篇关于轮胎和车辆动力学的论文），所以从互动或收入的角度来看，发布到短形式平台完全主导了我做的这种写作，任何关心互动或收入的人都会做短形式发布而非耗时长形式写作。至于我，现在我有两篇草稿在进行中，更像我的上一篇文章。<a href="https://danluu.com/diseconomies-scale/" rel="noopener noreferrer">对于一篇草稿，我需要完成的两件主要事情是为附录撰写约500篇文章/评论的摘要，以及阅读一本我想引用几段的400页书</a>，<a href="https://danluu.com/why-video/" rel="noopener noreferrer">对于另一篇，我需要完成约350页FTC备忘录的笔记</a>。每篇草稿都将变成一篇长到足以独立成书的博客文章。就这驱动<a href="https://www.patreon.com/danluu" rel="noopener noreferrer">我的Patreon</a>收入而言，我做这个能赚到最低工资就不错了，<a href="https://www.patreon.com/posts/60185075" rel="noopener noreferrer">甚至不包括我研究但没发表的时间，因为结果无趣</a>。但我也完全是个怪人。平均而言，人们会产生吸引眼球的内容，所以当然会有更多人仓促撰写长篇[差平台]串而非博客文章。</p>
 <a href="#fnref:O" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:V"><p>对于德语内容，有一家杂志做得不算彻底，但有些接近，但没人将其翻译成英语。Jonathan Benson不仅为英语做了前所未有的轮胎评测，他还将德语评测翻译成英语！</p>
<p>更广泛地说，不幸的是，尽管视频使得<a href="https://danluu.com/why-benchmark/" rel="noopener noreferrer">更多基准测试</a>在财务上可行，但仍有大量领域，除了与行业人士交谈外，没有好方法确定什么是更好的，例如<a href="https://mastodon.social/@danluu/109667084437206655" rel="noopener noreferrer">ADAS系统</a>，公开测试充其量只是肤浅的。</p>
 <a href="#fnref:V" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 14155 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/why-video/</link>
      <guid isPermaLink="false">https://danluu.com/why-video/</guid>
      <pubDate>Thu, 25 Jan 2024 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>搜索引擎结果有多糟糕？对比Google、Bing、Marginalia、Kagi、Mwmbl和ChatGPT</title>
      <description>&lt;p&gt;在《搜索引擎优化的诞生与消亡》一文中，Xe建议：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里有一个有趣的实验可以尝试。以一个开源项目如&lt;code&gt;yt-dlp&lt;/code&gt;为例，尝试使用一个非常通用的术语如“youtube downloader”来查找它。由于所有试图在该术语上排名第一的内容农场的存在，你将无法找到它。尽管&lt;code&gt;yt-dlp&lt;/code&gt;很可能实际上才是你想要用来下载</description>
      <content:encoded><![CDATA[<p>在《搜索引擎优化的诞生与消亡》一文中，Xe建议：</p>
<blockquote>
<p>这里有一个有趣的实验可以尝试。以一个开源项目如<code>yt-dlp</code>为例，尝试使用一个非常通用的术语如“youtube downloader”来查找它。由于所有试图在该术语上排名第一的内容农场的存在，你将无法找到它。尽管<code>yt-dlp</code>很可能实际上才是你想要用来下载YouTube视频的工具。</p>
</blockquote>
<p>更普遍地说，我联系的大多数技术界人士似乎都认为，谷歌搜索结果比十年前显著变差了（<a href="https://mastodon.social/@danluu/111506788692079608" rel="noopener noreferrer">Mastodon投票</a>，<a href="https://twitter.com/danluu/status/1730705885037801686" rel="noopener noreferrer">Twitter投票</a>，<a href="https://www.threads.net/@danluu.danluu/post/C0UnBr9vEeY" rel="noopener noreferrer">Threads投票</a>）。然而，有一大群直言不讳的人声称搜索结果仍然很棒。例如，一位在Bluesky上获得高参与度的意见领袖说：</p>
<blockquote>
<p>我认为关于谷歌搜索现在变得多么糟糕的哀嚎有点被夸大了<sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">1</a></sup></p>
</blockquote>
<p>我怀疑这里发生的情况是，一些人已经习惯了围绕糟糕的软件工作，以至于他们甚至不知道自己在这样做，条件反射地做着相当于<a href="https://twitter.com/danluu/status/1525988886119186432" rel="noopener noreferrer">在编辑器中始终按ctrl+s，或者在文本框中撰写任何内容时按ctrl+a; ctrl+c</a>的现代等价物。每一个现代网络的熟练用户都有一套他们用来从查询中获得体面结果的技巧。从观察许多用户与计算机的互动来看，这似乎并不正常，即使在那些在机械工程等各种技术领域相当有竞争力的人中也是如此<sup id="fnref:M"><a href="#fn:M" rel="noopener noreferrer">2</a></sup>。然而，也可能是那些抱怨搜索结果质量差的人<a href="https://mastodon.social/@danluu/111666646509250420" rel="noopener noreferrer">只是赶上了“一切都糟糕”的潮流，并对搜索质量发表了完全无根据的评论</a>。</p>
<p>既然尝试直接、简单的查询相当容易，让我们尝试一些查询。我们将用五种搜索引擎加上ChatGPT来查看三种查询，并且会关闭广告拦截器以获得<a href="https://twitter.com/danluu/status/1564292487744978946" rel="noopener noreferrer">非专家的浏览体验</a>。我曾经因为浏览到一个有问题的广告网站而使计算机被入侵，所以我希望这种情况不会在这里发生（在那次事件中，我很幸运能发现，因为恶意软件在我的计算机上做了太多事情，不可能不被注意到）。</p>
<p>一种查询是我的一个朋友为设置新电脑而使用的一组有代表性的查询。我的朋友是一位非技术领域高度能干的工程师，她想学习“如何使用计算机”，所以我观察了她设置电脑的过程，并指出了她在与网站和软件互动的思维模型中存在的漏洞<sup id="fnref:N"><a href="#fn:N" rel="noopener noreferrer">3</a></sup>。</p>
<p>第二种查询是我在高中时想知道的那些事情的查询，当时我找不到答案，因为我问的所有人（老师等）都给了我明显错误的答案，而我不知道如何找到正确答案。一旦我上了大学并可以访问大学图书馆，我就能够从各种教科书中得到正确答案，但这些问题很简单，高中生理应能理解答案；这只是找到答案的问题，所以我们来看看这些答案有多容易找到。第三种查询是在撰写本文时我恰好想获取的本地信息查询。</p>
<p>在对查询进行评分时，会有一些主观性，因为，例如，是具有中等相关性但没有诈骗的结果更好，还是非常<a href="https://mastodon.social/@danluu/109967416372133687" rel="noopener noreferrer">相关但混杂着试图安装恶意软件或诱骗你交出信用卡信息来支付你本不该支付的东西的诈骗结果</a>更好，这并不客观明确。在本文中，我将诈骗视为相当糟糕，所以在那个特定例子中，我会将中等相关性的结果评为高于混有诈骗的高相关性结果。与我<a href="https://danluu.com/car-safety/" rel="noopener noreferrer">其他</a><a href="https://danluu.com/web-bloat/" rel="noopener noreferrer">篇</a><a href="https://danluu.com/futurist-predictions/" rel="noopener noreferrer">有</a>某种主观排名的文章一样，这里既有简短的总结，也有结果的详细描述，以便您自己可以对服务进行排名。</p>
<p>在下表中，每一列是一个查询，每一行是一个搜索引擎或ChatGPT。结果按（从最差到最佳）极差、很差、差、一般、好和优秀评级，较差的结果更红，较好的结果更蓝。</p>
<p>查询是：</p>
<ul>
<li>download youtube videos</li>
<li>ad blocker</li>
<li>download firefox</li>
<li>Why do wider tires have better grip?</li>
<li>Why do they keep making cpu transistors smaller?</li>
<li>vancouver snow forecast winter 2023</li>
</ul>
<p>
</p><table>
<tbody><tr>
<th></th><th>YouTube</th><th>Adblock</th><th>Firefox</th><th>Tire</th><th>CPU</th><th>Snow</th></tr>
<tr>
<th>Marginalia</th><td>一般</td><td>好</td><td>一般</td><td>差</td><td>差</td><td>差</td></tr>
<tr>
<th>ChatGPT</th><td>很差</td><td>优秀</td><td>好</td><td>很差</td><td>很差</td><td>差</td></tr>
<tr>
<th>Mwmbl</th><td>差</td><td>差</td><td>差</td><td>差</td><td>差</td><td>差</td></tr>
<tr>
<th>Kagi</th><td>差</td><td>很差</td><td>优秀</td><td>极差</td><td>差</td><td>极差</td></tr>
<tr>
<th>Google</th><td>极差</td><td>很差</td><td>差</td><td>差</td><td>差</td><td>极差</td></tr>
<tr>
<th>Bing</th><td>极差</td><td>极差</td><td>优秀</td><td>极差</td><td>一般</td><td>极差</td></tr>
<tr>
</tr>
</tbody></table><p></p>
<p>Marginalia表现相对较好，有时提供尚可但不完美的答案，然后对它无法回答的问题不提供答案或提供非常明显的无关答案，诈骗率也相对较低，低于任何其他搜索引擎（尽管对于这些查询，ChatGPT返回零诈骗，而Marginalia返回一些）。</p>
<p>有趣的是，Mwmbl允许用户直接编辑搜索结果排名。我对一个查询进行了编辑，如果按编辑后的标准评分，它将得“优秀”，但<a href="https://danluu.com/car-safety/" rel="noopener noreferrer">在基准测试中专门为基准优化时，很容易取得好成绩</a>，因此Mwmbl的分数是在我编辑排名标准之前计算的。</p>
<p>我发现Google结果中有趣的一点是，除了众所周知的倾向返回近期结果外，还有强烈的倾向返回近期的YouTube视频。这导致我们得到的视频对任何人来说似乎都毫无用处，除了视频的制作者，他们似乎试图从视频中获得广告收入。例如，搜索“ad blocker”时，一个YouTube结果显示一个人漫无边际地讲了93秒关于你应该使用广告拦截器的话，然后谷歌搜索“ad blocker extension”。然后他们点击第一个结果并错误地说“它正式来自谷歌”，即该广告拦截器要么是谷歌制造的，要么有某种官方谷歌认证，因为它是第一个结果。然后他们又漫无边际地讲了40秒，同时安装了广告拦截器。安装后，他们错误地陈述“这基本上是Google Chrome上最有效的广告拦截器之一”。这个视频有14k次观看。作为参考，Steve Yegge花了一年时间制作高质量视频，他最受欢迎的视频有8k次观看，典型的观看次数低于2k。这个通过制作他们一无所知的话题的低质量视频来玩弄算法的人，是利用谷歌算法优先考虑近期内容而不顾质量的小型产业的一部分，正在压倒Steve Yegge的视频，因为他们找到了可以排上名的搜索词。我们将在下面更详细地讨论其他谷歌怪癖。</p>
<p>ChatGPT做着它惯常的事情，在一个案例中显著超越了它更传统的竞争对手，在另一个案例中做得还可以，在另一个案例中拒绝真正回答问题，并在多个查询中“产生幻觉”胡言乱语（像ChatGPT一样，随机扰动可以显著改变结果<sup id="fnref:R"><a href="#fn:R" rel="noopener noreferrer">4</a></sup>）。批评ChatGPT的幻觉很常见，虽然我认为这并不过分，但<a href="https://danluu.com/customer-service/" rel="noopener noreferrer">正如我们在2015年这篇关于人工智能的LLM前文章中指出的那样</a>，我认为这类批评被高估了，因为人类和传统计算机系统犯的是完全相同的错误。</p>
<p>在这种情况下，搜索引擎返回各种幻觉结果。在降雪预测的例子中，我们得到了故意伪造的结果，一个旨在通过虚假预报网站上的阴暗广告获得广告收入，另一个旨在诱骗用户认为预报显示的是寒冷、多雪的冬季（与实际预报相反），似乎是为了让用户注册不必要的铲雪服务。其他故意伪造的结果包括一个网站，它看起来像一个客观的评论网站，实际上是一个假网站，旨在将你引导安装特定的广告拦截器，其中他们引导你安装的广告拦截器似乎是一个诈骗广告拦截器，试图让你为广告拦截付费并且不允许你取消订阅，一个试图让你安装一个将你所有购物信息暴露给某个服务的chrome扩展程序的虚假“有机”博客文章（在许多情况下，无法判断博客文章是假的还是推销的，但在这种情况下，他们将虚假博客文章托管在产品的域名上，尽管它被设计成看起来有整个博客的主题，但实际上并没有——只有这篇虚假的博客文章），等等。</p>
<p>还有许多结果似乎不是故意欺诈，只是普通的SEO垃圾，旨在通过广告点击获利。这些网站大多早于LLM，所以它们读起来不完全像ChatGPT幻觉，但本质上没有不同。有时这些网站的目标是诱骗用户点击实际上会诈骗用户的广告，有时目标似乎是生成非诈骗广告的点击。搜索引擎也返回了许多看似非故意的人类幻觉，人们在用户内容突出显示的地方自信地陈述错误答案，如quora、reddit和stack exchange。</p>
<p>在这些查询中，即使忽略任何看起来像LLM生成的文本，我也会将主要搜索引擎（Google和Bing）评为在返回各种幻觉或准幻觉结果方面略逊于ChatGPT。虽然我认为关于LLM幻觉的担忧并非不合理，但<a href="https://twitter.com/danluu/status/1557970249948884992" rel="noopener noreferrer">传统生态系统的问题在于，系统高度激励将对软件供应链最有利的东西放在用户面前</a>，而这通常与最佳结果截然不同。</p>
<p>例如，如果你的应用商店允许“你可能还喜欢”的推荐，赌博成瘾管理应用最值钱的广告位将是赌博应用。在成瘾管理应用上允许赌博广告对于今天任何一家公司来说都过于公然地敌视用户，但当然，制作赌博应用的公司会试图破坏系统来突破过滤，<a href="https://twitter.com/danluu/status/1588289840629833729" rel="noopener noreferrer">他们有时成功了</a>。对于网络搜索，我刚刚又在网上尝试了一下，两大搜索引擎之一返回的顶部结果是广告充斥的SEO博客垃圾，用于成瘾管理。页面顶部是一个多部分广告，前两个链接是“支付真钱的游戏”和“支付真现金的游戏”。通常，我得到的是本地化结果（因为我在加拿大，有很多.ca域名），所以你自己尝试时可能会得到有些不同的结果。</p>
<p>同样，如果最佳结果是一个好的、免费的广告拦截器，如ublock origin，那么对于一个制作广告拦截器的公司来说，顶部的广告位价值要高得多，这个广告拦截器旨在诱骗你为一个质量较低的广告拦截器付费，该拦截器有一个几乎无法取消的订阅，所以诈骗广告拦截器会出价高于免费广告拦截器来争夺顶部广告位。这类公司也有更多的资源用于直接SEO，以及间接的SEO活动，如营销，因此，除非搜索引擎投入更有效的努力来对抗利润动机，否则顶部结果将归付费广告拦截器所有，尽管付费广告拦截器通常对用户来说明显比免费广告拦截器差。如果你和从事排名工作的人交谈，许多最大的排名信号来自点击和参与度，但<a href="https://danluu.com/nothing-works/" rel="noopener noreferrer">这只有在用户足够老练，知道什么是最佳结果时，才会推动用户找到最佳结果，而通常他们并非如此</a>。人类评分员也会评估页面质量，但这存在完全相同的问题。</p>
<p>许多谷歌员工曾告诉我，广告实际上是好的，因为它们向用户介绍了用户原本不会知道的选项，但任何尝试在没有广告拦截器的情况下浏览的人都会看到各种误导性的广告，试图通过假装是一个窗口或在页面顶部宣传“支付真现金的游戏”等方式诱骗或陷害用户，该页面是关于对抗赌博成瘾的，并且已经通过SEO自己获得了赌博成瘾搜索的高排名。原则上，这些问题可以通过足够的资源来缓解，但我们可以观察到，万亿美元级别的公司选择不投入足够的资源来打击SEO、垃圾邮件等，以至于这类诈骗广告很少被看到。相反，一些顶级结果实际上是将你引向诈骗的广告。</p>
<p>在最初的Page Rank论文中，谢尔盖·布林和拉里·佩奇指出，基于广告的搜索本质上与提供优质结果不一致：</p>
<blockquote>
<p>目前，商业搜索引擎的主要商业模式是广告。广告商业模式的目标并不总是与为用户提供高质量搜索相对应。例如，在我们的原型搜索引擎中，手机相关的顶级结果之一是“手机使用对驾驶员注意力的影响”，这是一项详细解释在开车时使用手机交谈的分心和风险的研究。这个搜索结果之所以排在第一位，是因为它被PageRank算法评估为高度重要，PageRank算法是对网络引用重要性的近似估算[Page, 98]。显然，一个为展示手机广告而收费的搜索引擎将很难向其付费广告商证明我们的系统返回的页面是合理的。出于这类原因以及其他媒体的历史经验[Bagdikian 83]，我们预计广告资助的搜索引擎将固有地偏向广告商，远离消费者的需求。</p>
<p>即使对于专家来说，评估搜索引擎也非常困难，因此搜索引擎的偏见特别隐蔽。一个很好的例子是OpenText，据报道它出售公司被列在特定查询搜索结果顶部的权利[Marchiori 97]。这种类型的偏见比广告更隐蔽，因为不清楚谁“应得”在那里，以及谁愿意付费被列入。这种商业模式引起了轩然大波，OpenText已经不再是一个可行的搜索引擎。但不太明显的偏见可能会被市场容忍。……这种类型的偏见很难检测，但仍可能对市场产生重大影响。此外，广告收入通常为提供低质量搜索结果提供了动机。例如，我们注意到一个主要搜索引擎在给出航空公司名称作为查询时不会返回该航空公司的主页。碰巧的是，该航空公司已经为其名称的查询投放了昂贵的广告。一个更好的搜索引擎将不需要这个广告，这可能会导致搜索引擎从航空公司那里损失收入。总的来说，从消费者的角度来看，搜索引擎越好，消费者为找到所需信息需要的广告就越少。这当然会侵蚀现有搜索引擎的广告支持商业模式……我们认为广告问题导致了足够的混合激励，因此拥有一个透明的、学术领域的竞争性搜索引擎至关重要。</p>
</blockquote>
<p>当然，谷歌现在已被广告主导，尽管特别指出了用户将真实结果与付费结果混为一谈的阴险性，但<a href="https://twitter.com/danluu/status/1557970259142791168" rel="noopener noreferrer">谷歌和必应都让广告看起来越来越像真实的搜索结果</a>，以至于大多数用户通常不知道他们点击的是广告而不是真实的搜索结果。顺便说一句，用户认为一切都是“自然”搜索结果的原因是，在本文中，结果按它们在页面上出现的顺序排列，所以如果有四个广告出现在第一个自然结果之上，四个广告将排名1-4，自然结果将排名5。我听谷歌员工说过，AMP不影响搜索排名，因为它“只”控制哪些结果进入搜索结果上方的“轮播图”，好像在结果上方插入一个轮播图和一堆广告，将结果推到折叠线以下，对用户与结果的互动没有影响。也经常看到搜索引擎为公司赎买顶级位置，以至于那些不为自己名字购买广告的公司在搜索该名字时会将竞争对手放在顶部，这也据说不影响搜索结果排名，这对中位数用户来说技术上正确但基本上没有意义。</p>
<p>当我尝试运行论文中的查询“cellular phone”（无引号）时，顶级结果是谷歌商店链接到购买谷歌自己的Pixel 7，其余顶级结果是亚马逊上销售的各种Android手机。之后是维基百科的手机页面，然后是一系列商业结果，都试图向你推销手机或SEO垃圾，试图让你点击广告或通过他们的链接购买手机（接下来的7个结果是商业的，之后的结果是一个广告充斥的SEO博客垃圾页面，用于定义手机，上面有手机广告，然后是3个更多商业结果，然后是另一个广告充斥的手机定义）。这些商业链接似乎质量非常低，例如，轮播后维基百科下的第一个链接是加拿大百思买的手机页面。那里的前两个产品是eufy版本的AirTag的广告位。下一个结果是与Rogers绑定的按月分期iPhone，然后是与TELUS绑定的按月分期三星手机，然后是三星的AirTag，与Freedom Mobile绑定的按月分期iPhone，不同颜色的与Freedom Mobile绑定的按月分期iPhone，与Rogers绑定的按月分期iPhone，iPhone 13的屏幕保护膜，另一个三星AirTag产品，未锁定的iPhone 12，三星壁式充电器等；这是一个质量极低的结果，产品是人们不应该购买的（而且根据评论数量，人们并没有购买——顶级产品的评论数模态是0，中位数是1或2，尽管有很多东西人们确实在加拿大百思买购买，并且有很多产品有很多评论）。其他显示的商业结果也通常质量极低。谢尔盖和拉里建议的顶级结果，“手机使用对驾驶员注意力的影响”，根本看不到，被淹没在大量商业结果之下。另一方面，谷歌也加入了行动，通过购买广告来欺骗用户，<a href="https://twitter.com/danluu/status/887724695558205440" rel="noopener noreferrer">例如付费让安装程序试图诱骗用户安装Chrome而不是Firefox</a>。</p>
<p>总之，在查看了我们的测试查询结果之后，一些想到的问题是：</p>
<ul>
<li>Marginalia，一个由单个人构建的搜索引擎，为什么这么好？</li>
<li>Marginalia或其他小型搜索引擎能取代主流用户中的谷歌吗？</li>
<li>一组小型搜索引擎能比谷歌提供更好的结果吗？</li>
<li>Mwmbl的用户策划方法会奏效吗？</li>
<li>像1996年的Metacrawler这样聚合多个搜索引擎、ChatGPT、Bard等结果的搜索引擎，会显著优于谷歌吗？</li>
</ul>
<p>第一个问题很容易成为单独一篇文章，而本文已经17000字了，也许我们以后会研究它。我们之前已经注意到<a href="https://danluu.com/people-matter/" rel="noopener noreferrer">一些</a><a href="https://danluu.com/why-benchmark/" rel="noopener noreferrer">人</a><a href="https://twitter.com/danluu/status/1586508706774388736" rel="noopener noreferrer">可以</a>非常高效，但当然每个案例的细节都不同。</p>
<p>关于第二个问题，<a href="https://danluu.com/sounds-easy/" rel="noopener noreferrer">我们在2016年研究了一个类似的问题</a>，包括一般版本，“我可以在一个周末复制这个价值十亿美元的公司”，以及关于开源软件将如何使在某天超越谷歌变得微不足道的具体评论，例如</p>
<blockquote>
<p>如今，你需要的大多数技术确实都可以在OSS中获得，并且是最先进的。请允许我用meta64.com（我自己的公司）来举例。我正在使用Lucene来索引大量新闻文章，并通过搜索通过简单抓取RSS爬取内容生成的Lucene索引来提供搜索。我将声称Lucene技术接近最优，而我使用的这种搜索方法与Google需要采用的方法几乎相同。Google唯一真正的技术优势在于他们可以投入在线的服务器数量，这对我们小公司来说成本过高。但从软件角度来看，我预计Google将在未来10年内被像我这样的技术所超越。</p>
</blockquote>
<p>以及</p>
<blockquote>
<p>扩展规模总是一个挑战，但只要Lucene不断变得越来越好，就会达到Google的优势变得无关紧要，我们可以在其上集群Lucene节点并分发搜索相关计算，然后使用Hadoop之类的工具实现我们自己的开源排名算法。我们还没有达到那个地步，但技术只会随着时间变得更好，我们作为开发者做出的选择也很重要。即使亚马逊和谷歌现在看起来像不可战胜的巨人，也不要低估在很长时间内增量改进所能完成的事情，而且在技术领域这甚至算不上很长的时间。Windows曾经是统治冠军也没多久。Windows现在在哪里？</p>
</blockquote>
<p>在那篇2016年的文章中，我们看到那些认为开源解决方案将随时超越谷歌的人，似乎完全不知道为了成为谷歌的主流竞争对手必须解决多少难题，包括实时索引像Twitter、报纸等快速更新的网站，以及作为入场券的NLP，这是极其复杂的。自2016年以来，随着有更多实时内容需要索引以及用户期望更好的NLP，这些问题变得更加困难。人们期望从搜索引擎得到的功能也增加了，使得问题更加困难，因此，它似乎仍然非常难以取代谷歌成为主流搜索引擎，比如说，为十亿用户提供服务。</p>
<p>另一方面，如果你想为少量用户制作一个有用的搜索引擎，这似乎比以往任何时候都容易，因为谷歌对许多查询返回的结果比以前更差。在我们的测试查询中，我们看到许多查询中大多数或全部顶级结果都充斥着SEO垃圾，这是一个比十年前显著更严重的问题，甚至在LLM兴起之前，并且继续变得更糟。我通常以不会遇到这种情况的方式使用搜索引擎，但当我查看“普通”用户查询什么或自己尝试天真的查询时，就像我在本文中所做的那样，大多数结果相当差，这在过去并非如此。</p>
<p>谷歌现在在我的另一个弱点是查找不流行的网页时。我经常发现，当我想要找到一个网页并且我正确记住了网页的内容时，即使我进行精确字符串搜索，谷歌也不会返回该网页。要么该网页未被索引，要么它实际上未被索引，因为它位于索引的某个缓慢角落，不会及时返回。为了找到该网页，我必须记住链接到该网页的页面中的一些文本（通常点击多次才能到达实际页面，而不只是一次，所以我实际上记住的是一个链接到一个链接到一个链接到一个链接到一个链接的页面，然后使用archive.org遍历现已失效的链接），搜索它，然后手动导航链接图以到达该页面。这在2005年我搜索东西时基本从未发生过，在2015年也很少发生，但现在我搜索东西时很大一部分时间都会发生这种情况。即使在2015年，谷歌实际上也不是全面的。例如，谷歌搜索没有索引每条推文。但当时，我认为谷歌搜索在搜索推文方面比Twitter搜索更好，我基本上从未遇到过我想找到的推文没有被谷歌索引的情况。但现在，我想找到的大多数推文都没有被谷歌搜索返回<sup id="fnref:T"><a href="#fn:T" rel="noopener noreferrer">5</a></sup>，即使我搜索“[推文中的精确字符串] site:twitter.com”。在最初的Page Rank论文中，谢尔盖和拉里说“因为人类只能输入或说出有限的内容，随着计算机不断改进，文本索引将比现在扩展得更好。”（而且虽然机器可以生成实际上无限的内容，但仅索引人类生成的内容似乎就非常有用）。在LLM之前，谷歌肯定有资源索引每条推文以及每个公共网站上每个人类生成的发声，但他们似乎选择将资源投入到其他地方，并且相对于其规模，公共网络似乎比以往任何时候索引得更少，或者至少是自网络搜索早期以来索引得更少。</p>
<p>当谷歌对简单查询返回体面结果并且几乎索引了我想找到的任何公共页面时，一个独立搜索引擎返回我认为比谷歌更好的结果会非常困难。2016年的Marginalia对我来说将只是一个新奇事物，因为谷歌会为Marginalia返回体面结果的任何地方提供足够好的结果，而谷歌会为我搜索的每个晦涩网页提供正确结果，这对于一个小型搜索引擎来说极其困难。但现在谷歌实际上没有索引许多我想搜索的网页，独立搜索引擎相对较小的索引对它们来说并不是致命缺陷，其中一些返回的SEO垃圾比谷歌少，因此对我的用途更好，因为我通常不关心实时结果，不需要花哨的NLP（并且发现很多NLP实际上让搜索结果对我更糟），不需要将购物集成到搜索结果中，很少需要理解图像的图像搜索，等等。</p>
<p>关于一组小型搜索引擎能否为许多用户提供比谷歌更好的结果，我认为这不是一个问题，因为多年来答案一直是一声响亮的“是”。然而，许多人并不相信这一点。例如，一位谷歌TLM回复了本文开头的Bluesky意见领袖：</p>
<blockquote>
<p>有人试图争辩说，如果搜索领域更具竞争性，有许多小型提供商而不是只有三个大公司，那么它会对基于ML的SEO滥用更*有*抵抗力。</p>
<p>而且……听着，如果*谷歌*现在都跟不它，那么市场份额5%的“小先生”怎么能做到？</p>
</blockquote>
<p>大概指的是像<a href="https://buttondown.email/hillelwayne/archive/algorithm-monocultures/" rel="noopener noreferrer">Hillel Wayne的“算法单一文化”</a>这样的论点，我们的Bluesky意见领袖回复说：</p>
<blockquote>
<p>95%的情况下，当有人声称某个小型独立公司能比市场领导者做得更好时，这通常只是心理安慰。规模经济运作得相当好！</p>
</blockquote>
<p>过去，<a href="https://danluu.com/nothing-works/" rel="noopener noreferrer">我们看了一些市场领导者提供糟糕产品而其他玩家（通常是微小的）提供更好产品的例子</a>，在未来的文章中，我们将看看规模经济和规模不经济如何在各个领域与科技互动，但对于本文， suffice to say，很明显，尽管常见的“经济学101”<a href="https://danluu.com/cocktail-ideas/" rel="noopener noreferrer">鸡尾酒派对观点</a>认为规模经济应该是搜索质量的主要因素，但当我们看实际结果时，情况似乎并非如此。</p>
<p>关于Mwmbl的用户策划结果是否可行的问题，我猜不行，或者至少需要更多的审查。仅仅浏览Mwmbl就显示排名的最后编辑是由用户“betest”完成的，他在“RSS”下添加了某种博客垃圾作为顶级条目。似乎可以还原这个更改，但没有容易找到的方法来报告此更改或将用户报告为垃圾。</p>
<p>关于像Metacrawler这样聚合多个搜索引擎结果的东西今天是否会产生优越的结果，这可以说是无关紧要的，因为它要么不可能作为商业服务合法运行，要么需要高昂的许可费，但从技术角度来看，一个现代的metacrawler在今天可能相当不错。Metacrawler很快变得无关紧要，因为谷歌返回的结果比你通过聚合其他搜索引擎的结果要好得多，但今天情况似乎并非如此。</p>
<p>回到Xe等认为直接搜索查询充斥着垃圾的人和我们的意见领袖之间的辩论，后者认为“关于谷歌搜索现在变得多么糟糕的哀嚎有点被夸大了”，似乎Xe是正确的。虽然谷歌没有公开提供查看过去查询返回了什么的功能，但许多人记得直接查询通常返回良好结果的时候。谷歌在90年代如此迅速崛起，即使在熟练使用AltaVista的专家用户中，他们已经非常擅长在查询中添加各种限定词以获得好结果，是因为你不需要对谷歌这样做。但我们现在已经绕了一圈回来，我们需要添加限定词、将搜索限制在特定网站等，以便从谷歌在以前是简单查询的内容中获得好结果。如果说有什么不同的话，我们已经远远绕回来了，因为我们需要的扭曲比AltaVista时代要复杂得多。</p>
<p><i><a href="https://jobs.ashbyhq.com/freshpaint/bfe56523-bff4-4ca3-936b-0ba15fb4e572?utm_source=dl" rel="noopener noreferrer">如果你在找工作，Freshpaint正在招聘招聘人员、软件工程师和支持工程师。我是该公司的投资者，所以你应该带着通常的怀疑态度来看待这一点，但如果你想加入一个快速增长的早期初创公司，他们似乎找到了产品市场契合点，并且收入增长非常快。</a></i></p>
<p><i>感谢Laurence Tratt、Heath Borders、Justin Blank、Brian Swetland、Viktor Lofgren（顺便说一句，在写这篇文章之前我不认识他——我只在运行查询后联系他讨论Marginalia搜索结果）、Misha Yagudin、@hpincket@fosstodon.org、Jeremey Kun和Yossi Kreinin的评论/更正/讨论</i></p>
<h2 id="appendix-other-search-engines">附录：其他搜索引擎</h2>
<ul>
<li>DuckDuckGo：过去，当我在使用广告拦截器将DDG与Bing进行比较时，结果非常相似。我也尝试了DDG，移除Bing广告后，结果不如以前相似，但它们仍然足够相似，似乎不值得列出DDG结果。我使用DDG作为我的默认搜索引擎，我认为像谷歌一样，如果你知道如何查询它就能正常工作，但对于本文中的天真的查询类型，它的表现并不特别好。</li>
<li>wiby.me：像Marginalia一样，这是另一个为查找相对晦涩结果而制作的搜索引擎。我尝试了上述四个查询，结果很有趣，因为它们与其他任何搜索引擎的结果都大不相同，但wiby没有为我尝试的查询返回相关结果。</li>
<li>searchmysite.net：对于某些查询有一些相关结果，但不如Marginalia相关。诈骗和广告充斥的页面比谷歌、Bing和Kagi少得多。</li>
<li>indieweb-search.jamesg.blog：似乎出现了故障。“由于服务器错误，无法处理您的请求。”对每个查询都如此。</li>
<li>Teclis：搜索框仍然在那里，但任何查询都返回“Teclis.com由于机器人滥用而关闭。Teclis结果仍可通过Kagi的搜索结果获得，特别是通过“非商业网络”镜头，也可作为API。”首页上的说明写着“由于疯狂的机器人流量（99.9%的流量是机器人），该站点上的Teclis结果已禁用。”</li>
</ul>
<h2 id="appendix-queries-that-return-good-results">附录：返回良好结果的查询</h2>
<p>我认为大多数程序员都可能能够对每个查询获得好结果，也许除了轮胎宽度与抓地力的查询，所以这是我如何找到轮胎查询的尚可答案的方法：</p>
<p>我尝试了YouTube搜索，因为<a href="https://mastodon.social/@danluu/111441790762754806" rel="noopener noreferrer">许多最好的汽车相关内容现在都在YouTube上</a>。一个标题声称回答该问题的YouTube视频（视频实际上没有回答问题）有一条评论推荐Carroll Smith的书《Tune To Win》。该评论声称第1章解释了为什么更宽的轮胎有更好的抓地力，但我在书中没有找到任何解释。第1章确实注意到赛车通常使用比乘用车更宽的轮胎，并且乘用车正朝着使用更宽轮胎的方向发展，并且它对滑移角做了一些评论，大致勾勒出为什么更宽的接触面会有更好转弯性能的直观原因，但我没有找到关于制动差异的解释。此外，该书指出更宽接触面的主要原因是它（间接地）允许热量积聚更少，这使你可以设计出在更窄温度范围内工作的轮胎，从而允许使用更软的橡胶。这可能是真的，但它不能解释人们可能想知道的许多观察到的行为。</p>
<p>《Tune to Win》推荐了Kummer的《轮胎和橡胶摩擦统一理论》和Hays与Brooke（实际上是Browne，但Smith错误地说是Brooke）的《轮胎牵引力物理学》。这些都没有真正解释发生了什么，但寻找类似的书籍发现了<a href="https://mastodon.social/@danluu/111572194243940891" rel="noopener noreferrer">Milliken和Milliken的《赛车车辆动力学》</a>，它也没有真正解释为什么，但似乎更接近有解释。寻找与《赛车车辆动力学》类似的书籍发现了Guiggiani的《车辆动力学科学》，它确实触及了如何思考和建模许多相关因素。Guiggiani书的最后一章提到了所谓的“刷模型”（轮胎），搜索“brush model tire width”找到了Pacejka的《轮胎和车辆动力学》的参考，它确实开始解释为什么更宽的轮胎有更好的抓地力，以及需要进行什么样的轮胎和车辆动力学建模来解释容易观察到的轮胎行为。</p>
<p>正如我们指出的，人们有不同的技巧来获得好结果，所以如果你有更好的方法来获得好结果，我很想听听。但请注意，基本上每次我有一篇指出某件事不起作用的文章时，最常见的建议将是做那些通常建议但不起作用的事情，即使文章明确指出通常建议的事情不起作用。例如，我收到的关于<a href="https://danluu.com/file-consistency/" rel="noopener noreferrer">这篇关于文件系统正确性的文章</a>最常见的评论是你可以通过执行重命名技巧来绕过所有这些问题，即使文章明确指出这不起作用，解释了为什么不起作用，并引用了一篇讨论为什么不起作用的论文。几年后，<a href="https://danluu.com/deconstruct-files/" rel="noopener noreferrer">我就这个主题做了一次扩展演讲</a>，我指出人们不断建议这个不起作用的东西，我在演讲中收到的最常见的评论是你不需要费心所有这些事情，因为你只需要做重命名技巧（不，ext4有<code>auto_da_alloc</code>并不意味着这有效，因为如果你检查你是在兼容的文件系统上，它会自动将不正确的代码替换为正确的代码，此时更简单的是直接编写正确的代码）。如果你有关于为什么更宽的轮胎有更好的抓地力的建议，或者找到解释的搜索，请考虑确保该解释不是<a href="#why-do-wider-tires-have-better-grip" rel="noopener noreferrer">本文中提到的标准错误解释之一，并且该解释能够解释所有必须解释的行为才能解释这一现象</a>。</p>
<p>关于如何对其他查询获得好结果，由于本文已经17000字了，我将留给未来关于专家与非专家计算机用户如何与计算机互动的文章。</p>
<h2 id="appendix-summary-of-query-results">附录：查询结果摘要</h2>
<p>对于每个问题，答案按从最佳到最差排序，指标是我对结果好坏的主观印象。这些查询大多在2023年11月运行，尽管有几个在12月中旬运行。当我运行查询时，我很少自己写自然语言查询。然而，普通用户经常写自然语言查询，所以我任意对“轮胎”和“降雪”查询使用了自然查询。继续使用简单、天真的查询主题，我们在本文中使用了ChatGPT的免费版本，这意味着查询通过ChatGPT 3.5运行。理想情况下，我们应该对每个查询使用关键词和自然语言查询运行完整的矩阵，运行更多查询等，但本文已经17000字（转换为标准长度书籍的页面，大约70页），所以运行更多查询的完整矩阵会很快将本文变成一本长篇著作。对于工作和某些类型的数据分析，我有时会做那些全面或更全面的项目，但在这里，我们无法涵盖任何类似于全面查询集的内容，我们最多只能尝试一些似乎具有代表性的查询，并用我们的判断来决定这是否符合我们和其他人通常看到的行为，所以我认为不值得做像覆盖稍微更多内容的4倍工作量这样的事情。</p>
<p>对于搜索引擎，所有查询都是在全新的隐身窗口中运行，已清除Cookie，Kagi除外，它不允许未登录搜索。对于Kagi，查询是用一个没有自定义个性化或过滤器的新账户完成的，尽管它们是按顺序用同一个账户完成的，因此可能基于早期查询的点击对后期查询应用了某种个性化排名。这些查询在温哥华完成，似乎对某些搜索引擎应用了某种本地化排名。</p>
<ul>
<li>download youtube videos

<ul>
<li>理想情况下，顶级结果应该是<code>yt-dlp</code>或<code>yt-dlp</code>的轻量级图形包装器。链接到<code>youtube-dl</code>或其他更新频率较低的项目也可以。</li>
<li>优秀结果（<code>yt-dlp</code>作为顶级结果，可能在某个地方有<code>youtube-dl</code>，没有诈骗）：无</li>
<li>好结果（<code>youtube-dl</code>作为顶级结果，可能在某个地方有<code>yt-dlp</code>，没有诈骗）：无</li>
<li>一般结果（<code>youtube-dl</code>作为顶级结果，可能在某个地方有<code>yt-dlp</code>，诈骗比其他搜索引擎少）：
<ul>
<li>Marginalia：顶级链接是<code>youtube-dl</code>。大多数链接不相关。诈骗比大型搜索引擎少得多</li>
</ul></li>
<li>差结果（有一些有用的链接，但也链接到很多诈骗）
<ul>
<li>Mwmbl：有一些链接到不良网站和诈骗，但比大型搜索引擎少。在顶级10个链接中还有一个间接链接到<code>youtube-dl</code>，一个<code>youtube-dl</code>的GUI</li>
<li>Kagi：主要是链接到诈骗网站，但在几页之后，有一个web.archive.org链接到2010年版本的<code>youtube-dl</code></li>
</ul></li>
<li>很差结果（未能返回任何有用的结果）
<ul>
<li>ChatGPT：基本上拒绝回答问题，尽管如果你不天真地直接问问题，可能可以通过提示工程得到答案</li>
</ul></li>
<li>极差结果（未能返回任何有用的结果并且充满诈骗）：
<ul>
<li>Google：主要是链接到试图诈骗你或向你收费购买免费软件的更差版本的网站。一些链接到广告充斥的列表文章，没有好的建议。零个链接到好结果。还链接到各种YouTube视频，这些视频是YouTube版的博客垃圾。</li>
<li>Bing：主要是链接到试图诈骗你或向你收费购买免费软件的更差版本的网站。一些链接到广告充斥的列表文章，没有好的建议。可以说零个链接到好结果（不过有人可能会说第10个结果是尚可的结果，尽管它似乎是恶意软件）。</li>
</ul></li>
</ul></li>
<li>ad blocker

<ul>
<li>理想情况下，顶级链接应该指向ublock origin。退而求其次，任何链接到ublock origin都很好</li>
<li>优秀结果（ublock origin是顶级结果，没有诈骗）：
<ul>
<li>ChatGPT：第一个建议是ublock origin</li>
</ul></li>
<li>好结果（ublock origin排名靠前，但不是顶级结果；ublock origin之上的结果要么明显不是广告拦截器，要么即使不如ublock origin好也基本上免费工作；没有直接试图诈骗你的链接）：无</li>
<li>一般结果（ublock origin在某个地方，诈骗比其他搜索引擎少，不多）
<ul>
<li>Marginalia：第3和第4个结果让你找到ublock origin，第8个结果是ublock origin。没有似乎试图直接诈骗你的东西，并且“只有”一个链接到某种SEO广告农场诈骗（这比主要搜索引擎好得多）</li>
</ul></li>
<li>差结果（没有链接到ublock origin并且主要链接到那些为良好功能设置付费墙或默认让广告通过的广告拦截器）：
<ul>
<li>Mwmbl：许多不相关的链接和一些链接到ghostery。一个诈骗链接，所以比商业搜索引擎少</li>
</ul></li>
<li>很差结果（专门或几乎专门链接到为良好功能设置付费墙或默认让广告通过的广告拦截器）：
<ul>
<li>Google：很多链接到参与“可接受广告计划”的广告拦截器，出版商同意确保他们的广告符合某些标准（文本中未提及，但在其他地方解释，这样参与此计划的公司的主要收入来源是广告商向“广告拦截器”公司付费以不阻止他们的广告，使得“广告拦截器”不仅不是广告拦截器，而且与用户的动机非常不一致。一些链接到似乎是诈骗的东西。零个链接到ublock origin。还链接到各种YouTube视频，这些视频是YouTube版的博客垃圾。</li>
<li>Kagi：类似于Google，但诈骗更多，不过比Bing少</li>
</ul></li>
<li>极差结果（专门或几乎专门链接到为良好功能设置付费墙或默认让广告通过的广告拦截器并且有大量诈骗）：
<ul>
<li>Bing：类似于Google，但诈骗更多且没有YouTube视频垃圾</li>
</ul></li>
</ul></li>
<li>download Firefox

<ul>
<li>理想情况下，我们会得到链接到下载Firefox，没有假链接或诈骗链接</li>
<li>优秀结果（链接到下载Firefox；没有诈骗）：
<ul>
<li>Bing：链接到下载Firefox</li>
<li>Mwmbl：链接到下载Firefox</li>
<li>Kagi：链接到下载Firefox</li>
</ul></li>
<li>好：
<ul>
<li>ChatGPT：这有点搞笑，因为这些技术上是不正确的说明，但人类应该能轻松解码说明并下载Firefox</li>
</ul></li>
<li>一般结果（某种间接链接到下载Firefox；没有诈骗）：
<ul>
<li>Marginalia：间接链接到获取Firefox下载的Firefox下载说明</li>
</ul></li>
<li>差结果（链接到下载Firefox，带有诈骗）：
<ul>
<li>Google：顶级链接都是合法的，但#7结果是一个试图诱骗你安装恶意软件的诈骗，#10结果是一个似乎是某种诈骗的广告，想要你的信用卡信息。</li>
</ul></li>
</ul></li>
<li>Why do wider tires have better grip?

<ul>
<li>理想情况下，会链接到一个清楚解释原因的解释，并且没有不完整、无法解释许多常见观察到的行为的解释</li>
<li>优秀/好/一般结果：无</li>
<li>差结果（没有结果或很少明显的错误结果）：
<ul>
<li>Mwmbl：一个明显错误的结果，没有其他结果</li>
<li>Marginalia：两个明显错误的结果，没有其他结果</li>
</ul></li>
<li>很差结果：（很少半合理错误的结果）
<ul>
<li>ChatGPT：标准的ChatGPT“幻觉”，可能对很多人来说很合理（它听起来像网络上很多关于该主题的错误评论，但写得更好）</li>
</ul></li>
<li>极差结果（大量半合理的错误结果，通常在广告农场）：
<ul>
<li>Google / Bing / Kagi：错误的、广告充斥的结果，带有通常的诈骗广告率</li>
</ul></li>
</ul></li>
<li>Why do they keep making cpu transistors smaller?

<ul>
<li>理想情况下，会链接到一个清楚解释原因的解释。我见过的最好的解释是在<a href="https://en.wikipedia.org/wiki/Very_Large_Scale_Integration" rel="noopener noreferrer">VLSI</a>教科书中，但我也在讲义和幻灯片中见过非常好的解释</li>
<li>优秀结果（链接到非常好的解释，没有诈骗）：无</li>
<li>好结果（链接到尚可的解释，没有诈骗）：无</li>
<li>一般结果（链接到你可以进一步搜索的东西，并且如果你擅长搜索，可以获得好解释，并且不会将错误或误导性解释排在尚可解释之上）：
<ul>
<li>Bing：顶级链接组有一个部分答案，如果进一步搜索可以轻松找到正确答案。还有许多不相关的答案和广告充斥的SEO垃圾</li>
</ul></li>
<li>差结果（没有结果或少量明显不相关的结果或很多半合理的错误结果，但某个地方有尚可结果）：
<ul>
<li>Marginalia：无答案</li>
<li>Mwmbl：一个明显不相关的答案</li>
<li>Google：第5个链接有正确的关键词，可能通过进一步搜索找到正确答案。大多数链接有误导性或错误的部分答案。很多链接到Quora，没有回答问题。还有很多链接到其他糟糕的SEO答案</li>
<li>Kagi：第10个链接有相当直接的路径来获得正确答案，如果你向下滚动到第10个链接。其他链接不好。</li>
</ul></li>
<li>很差结果：
<ul>
<li>ChatGPT：没有真正回答问题。要求ChatGPT进一步解释其答案会导致它“产生幻觉”出不正确的原因。</li>
</ul></li>
</ul></li>
<li>vancouver snow forecast winter 2023

<ul>
<li>我不确定理想的答案是什么，但一个相当好的答案是加拿大环境部的降雪预测，预测降雪量显著低于正常水平（气温高于正常水平）</li>
<li>优秀结果（链接到加拿大环境部的2023年冬季多月降雪预测作为顶级结果或同等好的结果）：无</li>
<li>好结果：无</li>
<li>一般结果（链接到某种半合理的冬季降雪预测，而不仅仅是为了广告点击而编造的垃圾）：无</li>
<li>差结果（没有结果或明显不相关的结果）：
<ul>
<li>Marginalia：无结果</li>
<li>ChatGPT：错误的结果，但当我意外地在问题前加上“User\n”时，它返回了一个指向正确网站的链接（但以一种会让人很难导航到体面结果的方式），所以也许略有不同的提示会伪随机地导致这里有一个尚可的结果？</li>
<li>Mwmbl：一堆明显不相关的结果</li>
</ul></li>
<li>很差结果：无</li>
<li>极差结果（链接到故意伪造的预测结果）：
<ul>
<li>Bing：主要是不相关的结果。顶级看似相关的结果是第5个链接，但它似乎是某种诈骗网站，伪造假天气预报并在SEO严重的网站上通过服务广告赚钱</li>
<li>Kagi：前4个结果来自Bing第5个链接的诈骗预报网站</li>
<li>Google：主要是不相关的结果，#1结果是来自当地铲雪公司的假答案，预测大量降雪和寒冷天气，试图诱骗你不必要地为该年购买铲雪服务。其他结果是充满广告的SEO垃圾
<br></li>
</ul></li>
</ul></li>
</ul>
<h2 id="appendix-detailed-query-results">附录：详细查询结果</h2>
<h3 id="download-youtube-videos">Download youtube videos</h3>
<p>对于我们的第一个查询，我们将搜索“download youtube videos”（Xe建议的搜索词“youtube downloader”返回非常相似的结果）。理想的结果是<code>yt-dlp</code>或围绕<code>yt-dlp</code>的轻量级、免费包装器。<code>yt-dlp</code>是<code>youtube-dlc</code>的一个分支，<code>youtube-dlc</code>是一个现已废弃的<code>youtube-dl</code>的分支，<code>youtube-dl</code>现在似乎很少更新。如果仍然可用，链接到这些较旧的下载器之一似乎也可以。</p>
<h4 id="google">Google</h4>
<ol>
<li>某个youtube downloader网站。有很多保证，说网站和工具是安全的，因为它们已经过“Norton SafeWeb”检查。与网站互动会提示你安装浏览器扩展程序并启用通知。尝试下载任何视频会给你一个全页弹窗，用于安装名为CyberShield的扩展程序。似乎没有方法可以在不点击试图安装它的东西的情况下关闭弹窗。在点击链接但随后选择不安装CyberShield后，没有视频下载。谷歌搜索“cybershield chrome extension”会返回一个知识卡，写着“Cyber Shield是一个声称是弹窗拦截器的浏览器扩展程序，但在浏览器中显示广告。安装后，此扩展会打开新标签页，显示试图推销软件的广告、推送假软件更新和技术支持诈骗。”，所以CyberShield似乎是恶意软件。</li>
<li>某个youtube downloader网站。与网站互动会导致弹窗提示你下载他们的浏览器扩展程序。将视频URL放入会导致弹到某个诈骗网站，但也会导致视频下载，所以如果小心不要与网站试图诱骗你互动的诈骗打交道，在这里下载youtube视频似乎是可能的</li>
<li>PC Magazine关于如何从youtube下载视频的列表文章。顶级建议是付费下载youtube、VLC（他们说他们尝试时不起作用）、一些每年15美元的软件、一些每年26美元的软件、“FlixGrab”，然后警告说下载器网站通常是诈骗性的，他们不推荐任何下载器网站。文章每个建议有多个广告。</li>
<li>某个youtube downloader网站，带有阴暗的弹窗，试图在你甚至与页面互动之前诱骗你点击广告</li>
<li>某个youtube downloader网站，带有弹窗，试图诱骗你点击诈骗广告</li>
<li>某个youtube downloader网站，带有弹窗，试图诱骗你点击诈骗广告，例如“Samantha 24, vancouver | I want sex, write to WhatsApp | Close / Continue”。点击任何东西（任何按钮，或网站上的任何地方）都试图让你安装名为“Adblock Ultimate”的东西</li>
<li>ZDNet ZDnet列表文章。第一个建议是clipware，显然在安装程序中捆绑了大量恶意软件/广告软件/垃圾软件：<a href="https://www.reddit.com/r/software/comments/w9o1by/warning_about_clipgrab/" rel="noopener noreferrer">https://www.reddit.com/r/software/comments/w9o1by/warning_about_clipgrab/</a>。列表文章充满了广告，有一个自动播放视频</li>
<li>[YouTube视频] 超过2分钟的广告，然后是一个如何购买youtube premium的视频（视频有2M次观看）</li>
<li>[YouTube video] 视频一开始要求用户观看整个视频（某种变现功能？）。视频试图将你引导到某种付费的视频下载软件</li>
<li>[YouTube video] PC Magazine视频说你可能“不需要”下载视频，因为你可以使用分享按钮，然后建议阅读他们的文章（结果#3中的那篇）关于如何下载视频</li>
<li>某个youtube downloader网站，带有诈骗广告。与网站互动会试图让你安装“Adblock Ultimate”</li>
<li>某个youtube downloader网站，带有弹窗，试图诱骗你点击诈骗广告</li>
<li>某个youtube downloader网站，带有诈骗广告</li>
</ol>
<p>在10个“正常”结果中，有9个试图以某种方式诱骗你安装恶意软件或链接到其他类型的广告诈骗。一个页面没有这样做，但它也没有建议免费下载youtube视频的好选择，而是建议了几个付费解决方案。我们还有三个YouTube视频，所有这些似乎都是SEO博客垃圾的视频等价物。有趣的是，我们没有从谷歌本身得到很多广告，<a href="https://twitter.com/danluu/status/823623691246239744" rel="noopener noreferrer">尽管上次我关闭广告拦截器尝试一些谷歌测试查询时发生了这种情况</a>。</p>
<h4 id="bing">Bing</h4>
<ol>
<li>某个youtube downloader网站。这是谷歌（2），带有诈骗网站的广告</li>
<li>[EXPLORE FURTHER ... "Recommended to you based on what's popular"] 某个youtube下载网站，不是我们从谷歌看到的。网站有多个脉动广告，并自我标榜为圣诞“50%折扣”（此搜索在11月中旬进行）。尝试下载任何视频会弹出一个假进度条，上面写着“太慢？尝试[我们的程序]链接”。过了一会儿，出现了一个下载视频的链接，但这是一个诡计，当你点击它时，它会试图安装“oWebster Search extension”。谷歌搜索“oWebster Search extension”表明它是劫持你的浏览器显示广告的恶意软件。前三个命中中的两个是如何安装该扩展程序，其余顶级命中是如何删除此恶意软件。许多删除链接本身是安装其他恶意软件的诈骗。在不安装此恶意软件的情况下，再次点击下载链接会导致一个弹窗，试图让你安装该网站的软件。如果你关闭弹窗并再次点击下载链接，你只会再次获得弹窗链接，所以这个网站似乎是一个纯粹的诈骗，不允许你下载视频</li>
<li>[EXPLORE FURTHER]. 与网站互动会弹出假广告，显示声称想与你聊天的漂亮女性照片。点击视频下载按钮会试图让你安装<a href="https://github.com/gorhill/uBlock/issues/3027#issuecomment-330077439" rel="noopener noreferrer">冒牌广告拦截器</a>，该拦截器<a href="https://www.reddit.com/r/assholedesign/comments/h8pdar/this_ad_blocker_that_gives_you_ads/" rel="noopener noreferrer">显示额外的弹窗广告</a>。不过该网站似乎确实给你提供了视频下载</li>
<li>[EXPLORE FURTHER] 与（3）相同</li>
<li>[EXPLORE FURTHER] 与谷歌（1）相同（那个NortonSafeWeb youtube downloader网站，试图诈骗你）</li>
<li>[EXPLORE FURTHER] 一个将视频转换为MP4的网站。我没有检查该网站是否工作或只是诈骗，因为它甚至不声称让你下载youtube视频</li>
<li>谷歌（1），再次。那个NortonSafeWeb youtube downloader网站，试图诈骗你。</li>
<li>[EXPLORE FURTHER] 一个链接到youtube.com（主页）</li>
<li>[EXPLORE FURTHER] 某个youtube downloader网站，带有弹窗，试图诱骗你点击广告。关闭后显示12个更多广告。有一个诈骗广告，被设计成看起来像youtube下载按钮。如果你滚动过去，会有一个文本框和一个按钮用于尝试下载youtube视频。输入有效的URL会导致错误，说没有该URL的视频。</li>
<li>一个带有下载按钮的巨大卡片。下载按钮是假的，只是带你到该网站。该网站大声宣称该软件不是广告软件、间谍软件等。许多互联网评论者注意到他们的防病毒软件将该软件标记为恶意软件。很多评论还表明该软件效果不好但有时有效。该软件的网站有一个嵌入的YouTube视频，显示“此视频因违反YouTube服务条款而被移除”。奇怪的是，mac和Linux的下载链接不是针对此软件的，实际上根本不下载任何东西，而是<code>youtube-dl</code>的安装说明；如果Windows版本实际上是恶意软件，这可能有意义。Windows下载按钮带你到一个页面，可以下载Windows可执行文件。还有一个链接到某种广告充斥的页面，试图诱骗你点击看起来像普通按钮的广告</li>
<li>PC杂志列表文章</li>
<li>某个youtube downloader程序的广告，声称“今天下载345,764,132次”；在reddit上搜索此产品名称似乎表明它是恶意软件</li>
<li>某种付费下载器软件的广告</li>
</ol>
<p>这是第一页的结束。</p>
<p>和谷歌一样，没有好结果，很多诈骗和可能不是诈骗但只是围绕开源项目的某种轻量级皮肤的软件，向你收费而不是让你免费使用软件。</p>
<h4 id="marginalia">Marginalia</h4>
<ol>
<li>一个12年前的答案，建议youtube-dl，链接到一个已被取下并替换为“由于汉堡地区法院的裁决，禁止访问此网站。”的URL</li>
<li>某种SEO文章，就像你在普通搜索引擎上看到的</li>
<li>Leawo YouTube Downloader（我不知道这是什么，但快速搜索至少没有立即表明这是某种恶意软件，不像谷歌和必应的结果）</li>
<li>某种SEO列表文章，就像你在普通搜索引擎上看到的</li>
<li>某个随机软件的错误报告</li>
<li>某个随机博主推荐的“4K Video Downloader”。快速搜索似乎表明这不是诈骗或恶意软件，但它确实将一些功能锁定在付费墙后，因此比<code>yt-dlp</code>或围绕<code>yt-dlp</code>的免费包装器差</li>
<li>一篇关于如何安装和使用<code>yt-dlp</code>的博客文章。博客文章指出它曾经是关于<code>youtube-dl</code>的，但已更新到<code>yt-dlp</code>。</li>
<li>更多向你收费你可以免费获得的东西的软件，尽管在reddit上搜索此软件会出现其破解版</li>
<li>一篇带有过时推荐（如RealPlayer）的列表文章。整个博客似乎充满了垃圾质量的列表文章。</li>
<li>一个用于下载youtube视频的脚本，适用于名为“keyboard maestro”的东西，如果你已经使用该软件，这似乎有用，但如果你不使用该软件，这似乎是解决此问题的糟糕方案。</li>
</ol>
<p>目前为止最好的结果。第一个链接不起作用，但你可以轻松地从第一个链接到达<code>youtube-dl</code>。我当然不会尝试Leawo YouTube Downloader，但至少它没有诈骗到搜索项目名称主要返回关于该项目是某种恶意软件或诈骗的结果，这比我们从谷歌或必应得到的结果好。而且我们确实得到了<code>yt-dlp</code>的推荐，在结果中有说明，只是一个想帮助尝试下载youtube视频的人的博客文章。</p>
<h4 id="kagi">Kagi</h4>
<ul>
<li>1. 那个NortonSafeWeb youtube downloader网站。与网站互动会提示你安装浏览器扩展程序并启用通知。尝试下载任何视频会给你一个全页弹窗，用于安装名为CyberShield的扩展程序。似乎没有方法可以在不点击试图安装它的东西的情况下关闭弹窗</li>
<li>2. 另一个链接到那个NortonSafeWeb youtube downloader网站。不知何故，这个标记为“Dec 20, 2003”，显然表示该网站来自2003年12月20日，尽管这是非常错误的。</li>
<li>3. 某个youtube downloader网站。选择任何视频下载会将你推送到带有诈骗广告的网站。</li>
<li>4. 某个youtube downloader网站。与网站互动会弹出多个链接到诈骗的广告，并且页面想启用通知。然后在广告之上出现一个弹窗，写着“Ad removed”，带有一个详细信息链接。这是另一个广告的诈骗链接。</li>
<li>5. 另一个链接到上述网站</li>
<li>6-7. 在标题为“Interesting Finds”的子部分下，有两个指向GitHub仓库的链接。一个是将youtube视频转录为文本，另一个是使用Google Takeout备份来自Google Photos或你自己YouTube频道的照片</li>
<li>8. 某个youtube downloader网站。</li>
<li>9-13. 在标题为“Blast from thePast”的子部分下，4个不相关链接和一个链接到youtube-dl的GitHub页面，但是2010年版本的archive.org</li>
<li>14. 关于youtube帮助的SEO博客垃圾。有一个链接据称用于“下载YouTube视频的Greasemonkey脚本”，但链接只是指向一个带有诈骗广告的页面</li>
<li>15. 某种向你收费每月5美元下载youtube视频的软件</li>
</ul>
<h4 id="mwmbl">Mwmbl</h4>
<ol>
<li>某个youtube视频下载网站，但没有其他搜索引擎返回的网站。有一个巨大的广告面板显示“503 NA - Service Deprecating”。下载链接不起作用，只会弹出其他广告面板，然后消失，只留下503“广告”。</li>
<li>用于下载youtube视频的20美元软件</li>
<li>2016年关于如何安装和使用<code>youtube-dl</code>的博客文章。侧边栏有两个低质量广告，似乎不是诈骗，正文中有两个插入的广告，使得与大型搜索引擎的类似结果相比，广告量极低</li>
<li>某个youtube视频下载网站。有一个巨大的横幅，声称它是“唯一100%无广告且不包含弹窗的YouTube Downloader”，这可能不是真的，但该网站确实似乎是无广告且没有弹窗。下载链接似乎确实有效。</li>
<li>关于如何在Linux上安装和使用<code>youtube-dlg</code>（<code>youtube-dl</code>的GUI包装器）的YouTube视频（此查询从Mac运行）。</li>
<li>链接到2007年关于如何下载youtube视频的博客文章，该文章自动转到2020年广告充斥的SEO博客垃圾列表文章，建议很差。文章有两个自动播放视频。Archive.org显示2007年的博客文章有一些合理的选项，所以这并不总是坏结果。</li>
<li>一篇主流网站的博客文章，实际上是试图将你引导到特定视频下载器的赞助文章。在reddit上搜索对此的评论表明，用户认为该应用是浪费钱且不起作用。该网站也充满了其他产品的诈骗性和误导性广告。例如，我尝试点击一个声称可以为你省钱在“产品”上的广告。它加载了一个假的“检查你的计算机”动画，据说检查我的计算机是否兼容该扩展程序，然后是另一个假检查动画，之后我收到一条消息，说我的计算机兼容，我有资格省钱。我所要做的就是安装这个扩展程序。关闭该窗口会打开一个新标签页，写着“Hold up! Do you actually not want automated savings at checkout”，选项是“Yes, Get Coupons”和“No, Don't Save”。点击“No, Don't Save”实际上是一个广告，带你回到一个试图让你安装chrome扩展程序的链接。</li>
<li>那个“Norton Safe Web” youtube downloader网站，但链接错误，链接到声称下载Instagram视频的网站版本，而不是声称下载youtube视频的版本。</li>
<li>链接到Google帮助页面，解释如何下载你自己上传的youtube视频</li>
<li>SEO博客垃圾。它立即有一个弹窗让你订阅他们的时事通讯。关闭后给你另一个弹窗，选项是“Subscribe”和“later”。点击“later”确实会关闭第二个弹窗。关闭弹窗后，文章有说明如何在Windows上安装某个软件。在reddit上搜索对该软件的评论会出现像“This is a PUP/PUA that can download unwanted applications to your pc or even malicious applications.”这样的评论。</li>
</ol>
<p>基本上与谷歌或必应相同。</p>
<h4 id="chatgpt">ChatGPT</h4>
<p>由于ChatGPT期望更对话式的查询，我们将使用提示“How can I download youtube videos?”</p>
<p>第一次尝试，周一上午10:38 PT返回“Our systems are a bit busy at the moment, please take a break and try again soon.”。第二次尝试返回一个答案，说人们不应该在不付费给YouTube Premium的情况下下载视频，但如果你想，可以使用第三方应用程序和网站。接着问题“What are the best third-party apps and websites?”返回另一个警告，说你不应该使用第三方应用程序和网站，然后是<a href="https://nitter.net/CeciliaZin/status/1740109462319644905" rel="noopener noreferrer">对GPT来说讽刺的警告,</a></p>
<blockquote>
<p>I don't endorse or provide information on specific third-party apps or websites for downloading YouTube videos. It's essential to use caution and adhere to legal and ethical guidelines when it comes to online content.</p>
</blockquote>
<h3 id="ad-blocker">ad blocker</h3>
<p>对于我们的下一个查询，我们将尝试“ad blocker”。我们想得到<code>ublock origin</code>。退而求其次，默认阻止广告的广告拦截器。再退而求其次，不是诈骗并且不注入额外广告或自己广告的东西。虽然什么是最好的可能会随时改变，但我看到的没有偏向的比较<a href="https://www.researchgate.net/figure/Performance-benchmark-when-using-an-adblocker-on-the-desktop-version-of-Top150_fig1_318330749" rel="noopener noreferrer">经常似乎显示ublock origin是最好的或最好的之一</a>，并且ublock origin是免费的并且阻止广告。</p>
<h4 id="google-1">Google</h4>
<ol>
<li>“AdBlock — best ad blocker”。折叠线下注明“AdBlock participates in the Acceptable Ads program, so unobtrusive ads are not blocked”，所以这并不阻止所有广告。</li>
<li>Adblock Plus | The world's #1 free ad blocker。页面注明“Acceptable Ads are allowed by default to support websites”，所以这也默认不阻止所有广告</li>
<li>AdBlock。页面注明“ Since 2015, we have participated in the Acceptable Ads program, where publishers agree to ensure their ads meet certain criteria. Ads that are deemed non-intrusive are shown by default to AdBlock users”，所以这并不阻止所有广告</li>
<li>“Adblock Plus - free ad blocker”，与（2）相同，不阻止所有广告</li>
<li>“AdGuard — World's most advanced adblocker!”页面试图向你推销某种付费软件，“AdGuard for Mac”。搜索AdGuard会出现一篇<a href="https://www.reddit.com/r/Adguard/comments/16whmsy/is_there_an_adblocker_for_adguards_adblocker_that/" rel="noopener noreferrer">来自此人寻找阻止AdGuard注入广告的广告拦截器的帖子</a>。似乎你可以免费下载它，但如果你不订阅，他们会给你更多广告？</li>
<li>“AdBlock Pro”在safari商店；有应用内购买。看起来你必须付费解锁功能，如阻止视频</li>
<li>[YouTube] “How youtube is handling the adblock backlash”。视频前30秒有15秒广告。视频没有实际内容</li>
<li>[YoutTube] “My thoughts on the youtube adblocker drama”</li>
<li>[YouTube] “How to Block Ads online in Google Chrome for FREE [2023]”；视频下的第一条评论是“your video doesnt [sic] tell how to stop Youtube adds [sic]”。视频中，一个人漫无边际地讲了一会儿，然后谷歌搜索<code>ad blocker extension</code>，然后点击第一个链接（和我们第一个链接相同），说，“If I can go ahead and go to my first website right here, so it's basically officially from Google .... [安装后，当付款屏幕弹出要求你支付30美元或每月或每年费用时]”</li>
<li>“AdBlock for Mobile”在App Store上。在iOS商店评分为3.2星。很多评论表明它实际上不起作用</li>
<li>MalwareBytes ad blocker。快速搜索表明它并不阻止所有广告（不清楚是故意还是因为bug）</li>
<li>“Block ads in Chrome | AdGuard ad blocker”，与（5）相同</li>
<li>[ad] NordVPN</li>
<li>[ad] “#1 Best Free Ad Blocker (2024) - 100% Free Ad Blocker.”似乎立即是诈骗性的，因为它有一个假年份（此查询在2023年11月中旬运行）。这是针对名为TOTAL Ad Block的东西。<a href="(https://www.reddit.com/r/Adblock/comments/1412m7l/total_adblock_peoples_experiencesopinions/)" rel="noopener noreferrer">搜索TOTAL Ad Block会出现表明它是一个诈骗性应用的结果，不允许你取消订阅并且基本上试图偷你的钱</a>
15 [ad] 100% Free &amp; Easy Download - Automatic Ad Blocker。实际上是针对Avast浏览器，而不是广告拦截器。<a href="https://palant.info/2020/01/13/pwning-avast-secure-browser-for-fun-and-profit/" rel="noopener noreferrer">快速搜索显示该浏览器历史上比只运行chromium更不安全</a>，并且<a href="https://palant.info/2019/10/28/avast-online-security-and-avast-secure-browser-are-spying-on-you/" rel="noopener noreferrer">它从用户收集异常大量的信息</a>。</li>
</ol>
<p>没有链接到ublock origin。一些链接到诈骗，但远不如尝试获得youtube下载器时多。很多链接到默认只阻止部分广告的广告拦截器。</p>
<h4 id="bing-1">Bing</h4>
<ul>
<li>1. [ad] "Automatic Ad Blocker | 100% Free &amp; Easy Download". [链接实际上指向avast secure browser，所以是整个浏览器而不是广告拦截器；根据快速搜索，这似乎是围绕chromium的包装器，</li></ul><p><em>由 mimo-v2.5 模型翻译，花费 48654 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/seo-spam/</link>
      <guid isPermaLink="false">https://danluu.com/seo-spam/</guid>
      <pubDate>Sat, 30 Dec 2023 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>埃隆·马斯克与戴夫·查普尔同台对话文字记录</title>
      <description>&lt;p&gt;这是使用OpenAI的Whisper模型转录的埃隆·马斯克与戴夫·查普尔同台视频的文字记录，经手动纠错并标注了观众噪音。&lt;/p&gt;

&lt;p&gt;与&lt;a href=&quot;https://danluu.com/elon-twitter-texts/&quot;&gt;Twitter短信证据H公开&lt;/a&gt;一样，许多文章引用了此事件的部分内容，但这些文章通常遗漏了大量细节，往往对事件过程进行误导性描述。鉴于整个事件篇幅不长，</description>
      <content:encoded><![CDATA[<p>这是使用OpenAI的Whisper模型转录的埃隆·马斯克与戴夫·查普尔同台视频的文字记录，经手动纠错并标注了观众噪音。</p>

<p>与<a href="https://danluu.com/elon-twitter-texts/" rel="noopener noreferrer">Twitter短信证据H公开</a>一样，许多文章引用了此事件的部分内容，但这些文章通常遗漏了大量细节，往往对事件过程进行误导性描述。鉴于整个事件篇幅不长，您最好直接观看或阅读原文，而非阅读他人带有误导性的摘要。总体而言，媒体似乎想塑造埃隆极不光彩的形象，导致文章和虚拟推文中出现事实错误。例如，普遍错误报道称，在"我是有钱人，贱人"环节，有人用喇叭声掩盖观众对埃隆的嘘声。但喇叭声实际上出现在前一个人说同样台词时——那是记录到最热烈欢呼的时刻。当埃隆说"我是有钱人，贱人"时，声音弱得多且听不分明，听起来像是嘘声与欢呼的混合。这可能是埃隆获得的最积极的观众反应，因此声称用喇叭掩盖嘘声的说法至少在两方面不准确。另一方面，尽管媒体试图尽可能负面地描绘埃隆，但效果不佳，许多其他环节中枯燥、准确的实况描述，比流传的误导性摘要更令人不适。</p>

<p></p><ul>
<li><a href="https://www.youtube.com/watch?v=CzkreBMHUFY" rel="noopener noreferrer">视频1</a>
<ul>
<li><b>戴夫</b>：女士们先生们，为世界首富欢呼吧。</li>
<li><b>观众</b>：[欢呼、鼓掌与嘘声混合；数秒后嘘声压过其他声音并延续至后续发言]</li>
<li><b>戴夫</b>：有欢呼和嘘声，我说</li>
<li><b>观众</b>：[短暂笑声，嘘声持续盖过其他声音]</li>
<li><b>戴夫</b>：埃隆</li>
<li><b>观众</b>：[嘘声持续]</li>
<li><b>埃隆</b>：嘿戴夫</li>
<li><b>观众</b>：[嘘声加剧]</li>
<li><b>埃隆</b>：[嘘声中听不清]
</li><li><b>戴夫</b>：有争议啊，伙计。
</li><li><b>观众</b>：[嘘声持续；隐约有欢呼声]</li>
<li><b>埃隆</b>：没料到吧？</li>
<li><b>戴夫</b>：听起来像是你解雇的一些人在台下。</li>
<li><b>观众</b>：[笑声，隐约有掌声]
</li><li><b>埃隆</b>：[笑]
</li><li><b>观众</b>：[嘘声再起]
</li><li><b>戴夫</b>：嘿，等一下。在座嘘声最大的各位</li>
<li><b>观众</b>：[嘘声加剧]</li>
<li><b>戴夫</b>：真够劲[嘘声中听不清]听起来像</li>
<li><b>埃隆</b>：[刚开口就被戴夫打断]
</li><li><b>戴夫</b>：你们知道吗。所有嘘声的人。我只是指出一个事实。他们的座位真差。[观众嘈杂声中听不清]</li>
<li><b>观众</b>：[微弱笑声]</li>
<li><b>戴夫</b>：都从最上面传来的[听不清]最后时刻的非[听不清]兄弟。嘘——嘘——</li>
<li><b>观众</b>：[稍安静]</li>
<li><b>戴夫</b>：听着。</li>
<li><b>观众</b>：[嘘声再起]</li>
<li><b>戴夫</b>：随便吧。听好了伙计们。这家伙根本没打算死在地球上</li>
<li><b>观众</b>：[笑声与嘘声混合，笑声盖过嘘声]</li>
</ul></li><p></p>

<p></p><li><a href="https://www.youtube.com/watch?v=hUkulqjTNpk" rel="noopener noreferrer">视频2</a>
<ul>
<li><b>戴夫</b>：他的整个商业模式就是“去他的地球我要走了”</li>
<li><b>观众</b>：[微弱笑声，微弱嘈杂声]</li>
<li><b>戴夫</b>：随便你们怎么闹。带上我吧兄弟我要去火星了</li>
<li><b>观众</b>：[笑声]</li>
<li><b>戴夫</b>：不管那里有什么样的妞儿，我们都要玩遍
</li><li><b>观众</b>：[微弱笑声]</li>
<li><b>戴夫</b>：[笑]零重力脱衣舞吧。追随你的梦想贱人金钱会洒满整个房间</li>
<li><b>观众</b>：[微弱笑声]</li>
<li><b>埃隆</b>：[笑]</li>
<li><b>观众</b>：[持续笑声被重新响起的嘘声淹没；隐约有欢呼声]</li>
<li><b>埃隆</b>：感谢，呃，感谢邀请我上台。</li>
<li><b>戴夫</b>：开什么玩笑。我可不会错过这机会。</li>
<li><b>埃隆</b>：[笑声中听不清]</li>
<li><b>观众</b>：[笑声]</li>
<li><b>埃隆</b>：[笑声中听不清]</li>
<li><b>戴夫</b>：火星上第一家喜剧俱乐部应该是我的[观众笑声中停顿]一言为定，马斯克。</li>
<li><b>观众</b>：[微弱笑声与欢呼]</li>
<li><b>埃隆</b>：[听不清]，是的</li>
<li><b>戴夫</b>：你们随便嘘。这家伙去年圣诞节送了我个喷气背包</li>
<li><b>观众</b>：[笑声]</li>
<li><b>戴夫</b>：直接飞过你们房子。让他们嘘蛋去吧[此句笑声中听不清]</li>
<li><b>戴夫</b>：这就是我们放松的方式，所有事都干</li>
<li><b>观众</b>：[微弱笑声，转为观众闲谈]</li>
<li><b>埃隆</b>：[埃隆转身似要面对观众]</li>
<li><b>观众</b>：[嘘声再起]</li>
<li><b>埃隆</b>：戴夫，我该说什么？</li>
<li><b>观众</b>：[嘘声加剧]</li>
<li><b>戴夫</b>：什么都别说。只会破坏气氛。听见那声音了吗埃隆？那是社会动荡的前兆。</li>
<li><b>观众</b>：[微弱笑声，起初隐约有嘘声；嘘声加剧直至被戴夫下句话打断]</li>
<li><b>戴夫</b>：我等不及看你下一个搞垮哪家公司了伙计[听不清]你们嘘声都给我闭嘴。你们还能做点别的。嘘声不是你们能做的最好的事。试试看兄弟。让它成为你们想要的样子。我是你们的盟友。我祝礼堂里每个人平安，享受自由的快乐，追求幸福并感到幸福。阿门。非常感谢旧金山。地球上没有城市曾对我友善过。谢谢。晚安。</li>
</ul></li><p></p>

<p></p><li><a href="https://www.youtube.com/watch?v=u1cl8U0UCMQ" rel="noopener noreferrer">视频3</a> [此时台下观众大量空位]
<ul>
<li><b>戴夫</b>：[听不清]尽量大声说。你说会更搞笑。准备好了吗？说这句[听不清]你说。来吧。</li>
<li><b>观众</b>：[微弱笑声]</li>
<li><b>可能是克里斯·洛克？</b>：我是有钱人贱人</li>
<li><b>观众</b>：[响亮欢呼，同时可听到舞台上大喇叭声]</li>
<li><b>未知</b>：等等[将麦克风递给埃隆]</li>
<li><b>观众</b>：[笑声]</li>
<li><b>埃隆</b>：[摆姿势]</li>
<li><b>观众</b>：[笑声，笑声中开始夹杂嘘声]</li>
<li><b>埃隆</b>：我是有钱人贱人</li>
<li><b>观众</b>：[有些声响，被舞台喇叭声及后续DJ音乐盖过难以听清；似有嘘声也有欢呼]
</li></ul></li><p></p>

<p></p><li><a href="https://www.youtube.com/watch?v=WSjQ5ojUvnM" rel="noopener noreferrer">视频4</a>
<ul>
<li><b>戴夫</b>：<a href="https://jezebel.com/talib-kwelis-harassment-campaign-shows-how-unprotected-1844483551" rel="noopener noreferrer">我的好友塔里布·奎利[观众欢呼]目前被Twitter封禁了。</a>
</li><li><b>观众</b>：[笑声]</li>
<li><b>戴夫</b>：他回家后[听不清]，奎利。[将麦克风递给埃隆]
</li><li><b>埃隆</b>：啊哈。Twitter客服在这里。</li>
<li><b>观众</b>：[微弱笑声]</li>
<li><b>埃隆</b>：我们马上处理。</li>
<li><b>观众</b>：[微弱嘘声，后续发言中逐渐增强直至被戴夫打断]</li>
<li><b>埃隆</b>：戴夫，你应该用Twitter。</li>
<li><b>戴夫</b>：如果你。我跟你说件事。等等。电台的，你手机呢？</li>
<li><b>戴夫</b>：听着。多年前，这是真事，我讲两个Twitter趣事然后我们就结束。</li>
<li><b>观众</b>：[微弱笑声]</li>
<li><b>戴夫</b>：多年前，我试着注册Twitter。输入名字时，提示不能用名人名字。</li>
<li><b>观众</b>：[微弱笑声]</li>
<li><b>戴夫</b>：还显示我的名字已被使用，是真的。</li>
<li><b>戴夫</b>：于是我上网查谁在用我名字，结果是个假的戴夫·查普尔。我当时想，搞什么鬼？我正要举报他，但我读了那家伙的推文。很震惊。这家伙，埃隆，搞笑极了。</li>
<li><b>观众</b>：[微弱笑声，有人大喊"没错"]</li>
<li><b>戴夫</b>：所以我想，算了，由他去吧。大家会以为这些搞笑话是我说的，而我根本不用说这些。感觉真棒。每天早上我醒来喝杯咖啡，看假戴夫·查普尔的推文笑。</li>
<li><b>戴夫</b>：但是后来</li>
<li><b>观众</b>：[大声嘈杂，能听到有人说"哇哦"]</li>
<li><b>戴夫</b>：[用手挡舞台灯光以便看清台下，看向观众]喂。别闹了，你们这些反[听不清；许多人报道称此处为"法西斯"，这似乎合理，使整句话意为"反法西斯"]的兄弟？</li>
<li><b>观众</b>：[大声嘈杂，能听到一些嘲笑和嘘声]<p></p></li></ul></li></ul><p><em>由 mimo-v2.5 模型翻译，花费 5797 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/elon-dave-chappelle/</link>
      <guid isPermaLink="false">https://danluu.com/elon-dave-chappelle/</guid>
      <pubDate>Sun, 11 Dec 2022 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Twitter诉马斯克案聊天记录展示材料</title>
      <description>&lt;p&gt;这是Twitter诉马斯克案中展示材料H和J的扫描/OCR版本，其中部分对话已解交织并从模糊扫描转换为文本，以便于阅读。&lt;/p&gt;

&lt;p&gt;我这样做的目的是为了方便阅读，但阅读后我发现，大多数关于对话内容的报道都存在某种程度的误导。由于文本并不长，如果你对他们的谈话内容感兴趣，&lt;a href=&quot;https://danluu.com/dunning-kruger/&quot;&gt;我建议你直接阅读完整的原文&lt;</description>
      <content:encoded><![CDATA[<p>这是Twitter诉马斯克案中展示材料H和J的扫描/OCR版本，其中部分对话已解交织并从模糊扫描转换为文本，以便于阅读。</p>

<p>我这样做的目的是为了方便阅读，但阅读后我发现，大多数关于对话内容的报道都存在某种程度的误导。由于文本并不长，如果你对他们的谈话内容感兴趣，<a href="https://danluu.com/dunning-kruger/" rel="noopener noreferrer">我建议你直接阅读完整的原文</a>（在可用范围内——文本明确显示有些对话部分未被包含），而不是阅读各种记者摘录的内容，这些摘录有时是故意误导的，因为选择性引用允许他们编写符合自己议程的故事，有时则是无意中产生误导，因为他们不了解文本中哪些内容值得关注。</p>

<p>如果你想将这些对话与其他高管/领导层的对话进行比较，你可以参考<a href="https://danluu.com/us-v-ms/" rel="noopener noreferrer">美国司法部诉微软案中出现的微软电子邮件和备忘录</a>以及<a href="https://www.cs.cmu.edu/~enron/" rel="noopener noreferrer">安然电子邮件数据集</a>。</p>

<p>由于这是通过OCR完成的，可能存在OCR错误。如果你发现错误，请随时<a href="https://twitter.com/danluu/" rel="noopener noreferrer">联系我</a>。</p>

<h3 id="exhibit-h">展示材料H</h3>

<ul>
<li><a id="1" rel="noopener noreferrer"></a><a href="#1" rel="noopener noreferrer">2022-01-21至2022-01-24</a>
<ul>
<li><b>Alex Shillings [SpaceX / 埃隆的IT专家]</b>: 埃隆-你能正常访问你的Twitter账户吗？我看到一些邮件被放入了垃圾邮件。包括一些密码重置尝试。</li>
<li><b>埃隆</b>: 我最近没试过。</li>
<li><b>埃隆</b>: 正在远离Twitter。</li>
<li><b>埃隆</b>: 我的Twitter账户有发任何东西吗？</li>
<li><b>Alex</b>: 没有发推，但我看到一封注销邮件和十几封密码重置邮件。假设是诈骗企图，但想核实一下以确保你仍能访问你的Twitter。</li>
<li><b>埃隆</b>: 是有人想黑我的Twitter。</li>
<li><b>埃隆</b>: 但我启用了双重验证，用的是确认应用程序。</li>
<li><b>Alex</b>: 好的，很高兴听到。</li>
<li><b>Alex</b>: 是的-通过FaceTime确认了我的身份（哈），他们希望很快能将你的2FA重置为短信验证。现在正在询问更新情况。</li>
<li><b>埃隆</b>: 听起来不错。</li>
<li><b>埃隆</b>: 如果还有问题，我也可以FaceTime他们。</li>
<li><b>Alex</b>: 简而言之；你的账户在他们内部被视为高关注度账户。因此他们的团队很难进行此类更改。他们正在处理中...</li>
<li><b>埃隆</b>: 很乐意直接FaceTime。</li>
<li><b>埃隆</b>: 不确定我如何能在这个新手机上使用Twitter，因为我没有用备用码。</li>
<li><b>Alex</b>: 现在正在联系他们的信任与安全负责人。</li>
<li><b>Alex</b>: 我想我们曾经用你旧手机验证过新手机。</li>
<li><b>埃隆</b>: 哦是的。</li>
<li><b>Alex</b>: 他们可以通过禁用你账户的所有2FA来修复，这样你就能登录，然后你可以重新启用。你90分钟后有空让他们协调吗？</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
<li><b>Alex</b>: 我知道现在情况多变，但EMDesk SpaceX还是你的主要日历吗？我意识到上面有一个1小时后的会议。以防我需要把这次Twitter修复推迟一点。</li>
<li><b>埃隆</b>: 是的。</li>
<li><b>埃隆</b>: 但如果需要，我可以在通话中暂停一下来FaceTime他们。</li>
<li><b>Alex</b>: 听起来不错。最理想的是，我发短信给你让他们禁用2FA后登录，然后你可以立即登录并重新启用。不需要FaceTime。</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
<li><b>Alex</b>: 埃隆-我们准备好做更改了，如果你准备好的话。</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
<li><b>Alex</b>: 2FA已禁用。请现在尝试登录。</li>
<li><b>Alex</b>: 能正常重新登录吗？</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
<li><b>Alex</b>: 一旦登录，你可以在设置&gt;安全与账户访问&gt;安全&gt;2FA中重新启用它。</li>
<li><b>Alex</b>: 建议仅使用应用程序。</li>
<li><b>埃隆</b>: 谢谢！</li>
<li><b>Alex</b>: 提醒保存那个备用代码 👍</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
</ul></li><br>

<li><a id="2" rel="noopener noreferrer"></a><a href="#2" rel="noopener noreferrer">2022-03-05</a>
<ul>
<li><b>Antonio Gracias [风险投资人]</b>: 哇...我看到你关于言论自由的推文。搞什么鬼，埃隆...</li>
<li><b>埃隆</b>: 欧盟通过了一项禁止今日俄罗斯和几家其他俄罗斯新闻机构的法律。我们被告知要封锁他们的IP地址。</li>
<li><b>埃隆</b>: 实际上，我觉得他们的新闻相当有趣。</li>
<li><b>埃隆</b>: 很多胡说八道，但有些观点也不错。</li>
<li><b>Antonio</b>: 这太他妈疯了...你完全正确。我百分之百同意你。</li>
<li><b>埃隆</b>: 我们应该允许它，正是因为我们讨厌它...这是美国宪法的要义。</li>
<li><b>Antonio</b>: 确实如此。</li>
<li><b>埃隆</b>: 言论自由最重要的是，当你讨厌的人在说你认为是胡说八道的话时。</li>
<li><b>Antonio</b>: 我百分之百支持你，埃隆。无论如何都要拼到底...这是一个原则，我们需要用生命去捍卫，否则我们就会迷失在黑暗中。</li>
<li><b>Antonio</b>: 抱歉说了脏话。我有点激动。</li>
<li><b>埃隆</b>: [“喜欢”了“我百分之百...”]</li>
<li><b>埃隆</b>: [2022-04-26] 在打电话。30分钟后有空。</li>
<li><b>Antonio</b>: 好的。我30分钟后打给你。</li>
</ul></li><br>

<li><a id="3" rel="noopener noreferrer"></a><a href="#3" rel="noopener noreferrer">2022-03-24</a>
<ul>
<li><b>TJ</b>: 你能买下Twitter然后删掉它吗，拜托！xx</li>
<li><b>TJ</b>: 美国快疯了。</li>
<li><b>TJ</b>: 《巴比伦蜜蜂》被停用真是疯狂。Raiyah和我今天还在聊这事。那他妈就是个笑话。为什么每个人都变得这么清教徒主义？</li>
<li><b>TJ</b>: 或者你能买下Twitter然后让它彻底支持言论自由吗？xx</li>
<li><b>TJ</b>: 愚蠢的东西太多，都来自Twitter xx</li>
<li><b>埃隆</b>: 也许买下它然后改变它，让它真正支持言论自由 xx</li>
<li><b>埃隆</b>: [“喜欢”了“或者你能买下Twitter...”]</li>
<li><b>TJ</b>: 我真心认为社交媒体是现代生活的祸害，其中最糟糕的是Twitter，因为它既是社交平台也是新闻流，所以比TikTok等具有更大的现实影响力。但它很容易被利用，并被激进分子大规模用于社会工程。而这种狗屎正在感染世界。拜托做点什么来对抗觉醒主义。我愿意做任何事来帮忙！xx</li>
</ul></li><br>

<li><a id="4" rel="noopener noreferrer"></a><a href="#4" rel="noopener noreferrer">2022-03-24至2022-04-06</a> [与上述对话交织]
<ul>
<li><b>Joe Lonsdale [风险投资人]</b>: 我喜欢你的“Twitter算法应该是开源”的推文-我明天实际上要对GOP政策休会的100多名国会议员发表演讲，这是我正在推动的用于约束疯狂大型科技的想法之一。现在我可以引用你，这样我自己听起来就不那么疯狂了：）。我们的公共广场不应该有任意的、可疑的审查。</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
<li><b>埃隆</b>: 绝对是这样。</li>
<li><b>埃隆</b>: 我们现在有的是隐藏的腐败！</li>
<li><b>Joe</b>: [“喜欢”了上面的消息]</li>
<li><b>[2022-04-04]</b>: Joe：看到持有Twitter股份很兴奋-太棒了。他们说你是“后门男人”哈哈。希望你能影响它。我敢打赌董事会甚至没有完整的报告，也没看到任何关于审查决策和在那里进行的小团体活动的报告，但这些都应该有-董事会里的左翼人士可能想要合理的否认！</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
<li><b>[2022-04-16] Joe</b>: 哈哈甚至德桑蒂斯州长刚才也给我打电话，告诉我如何帮助你，对那个董事会感到愤怒，说公众都支持你。如果你或你那边有人想和他聊聊，告诉我。如果你们这周末或未来几天在这里，见面会很有趣。</li>
<li><b>埃隆</b>: 哈哈酷。</li>
</ul></li><br>

<li><a id="5" rel="noopener noreferrer"></a><a href="#5" rel="noopener noreferrer">2022-03-26</a>

<ul>
<li><b>“jack jack” [大概是杰克·多西，Twitter前CEO和Square CEO]</b>: 是的，需要一个新的平台。它不能是一家公司。这就是我离开的原因。</li>
<li><b>jack</b>: <a href="https://twitter.com/elonmusk/status/1507777913042571267?s,=20&amp;t=8z3h0h0JGSnt86Zuxd61Wg" rel="noopener noreferrer">https://twitter.com/elonmusk/status/1507777913042571267?s,=20&amp;t=8z3h0h0JGSnt86Zuxd61Wg</a></li>
<li><b>埃隆</b>: 好的。</li>
<li><b>埃隆</b>: 它应该是什么样子？</li>
<li><b>jack</b>: 我相信它必须是一个开源协议，由一个不拥有该协议、只推动它的基金会资助。有点像Signal所做的那样。它不能有广告模式。否则，政府和广告商将试图影响和控制它。如果它背后有一个中央集权的实体，它将受到攻击。这并不复杂，只是必须做对，使其对Twitter所发生的事情具有弹性。</li>
<li><b>埃隆</b>: 超级有趣的想法。</li>
<li><b>jack</b>: 我五月中旬离开Twitter董事会，然后完全离开公司。我打算做这项工作并纠正我们的错误。Twitter最初是一个协议。它本不应该是一家公司。那是原罪。</li>
<li><b>埃隆</b>: 如果我有能力，我很乐意帮忙。</li>
<li><b>jack</b>: 我想在完全清楚之后和你谈这件事，因为你非常关心，明白它的重要性，绝对能以不可估量的方式提供帮助。当年激进分子进来时，我竭尽全力想让你加入我们的董事会，但我们的董事会拒绝了。大约在那个时候，我决定我需要努力离开，尽管这对我来说很难。</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
<li><b>jack</b>: 你有时间聊聊吗？</li>
<li><b>埃隆</b>: 马上要出门吃晚饭，但可以聊一分钟。</li>
<li><b>jack</b>: 我认为主要原因是董事会非常规避风险，认为增加你是更大的风险，我认为这完全愚蠢和错误，但我只有一票，持有3%的公司股份，没有双重股权结构。设定很艰难。我们可以再讨论。</li>
<li><b>埃隆</b>: 我们一定要多讨论。</li>
<li><b>埃隆</b>: 我认为既值得尝试推动Twitter朝更好的方向发展，也值得做一些新的、去中心化的东西。</li>
<li><b>jack</b>: 这可能是最好的选择。我只是有疑虑。但是开放的。</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
</ul></li><br>

<li><a id="6" rel="noopener noreferrer"></a><a href="#6" rel="noopener noreferrer">2022-03-26至2022-03-27</a>

<ul>
<li><b>埃隆致Egon Durban [私募股权；Twitter董事会成员]</b>: 我是埃隆。有空请回电。</li>
<li><b>埃隆</b>: 是关于Twitter董事会的事。</li>
<li><b>Egon</b>: 有后续。今天方便的时候我们聊聊。</li>
</ul></li><br>

<li><a id="7" rel="noopener noreferrer"></a><a href="#7" rel="noopener noreferrer">2022-03-27至2022-04-26</a> [与上述交织]

<ul>
<li><b>Larry Ellison [Oracle创始人兼高管] 埃隆，我想在接下来一两天和你聊聊...我确实认为我们需要另一个Twitter 👍</b></li><b>
</b><li><b>埃隆</b>: 现在想聊吗？</li>
<li><b>Larry</b>: 当然。</li>
<li><b>[2022-04-17] 埖隆</b>: 有兴趣参与Twitter交易吗？</li>
<li><b>Larry</b>: 有兴趣...当然 👍</li>
<li><b>埃隆</b>: 酷。</li>
<li><b>埃隆</b>: 大概多少金额？不约束你任何事，但交易已经超额认购，所以我必须减少或踢出一些参与者。</li>
<li><b>Larry</b>: 十亿...或者你推荐的金额。</li>
<li><b>埃隆</b>: 任何适合你的金额。我建议也许是20亿或更多。这有非常高的潜力，我宁愿要你也不要其他任何人。</li>
<li><b>Larry</b>: 我同意它有巨大的潜力...而且会很有趣。</li>
<li><b>埃隆</b>: 绝对是：)</li>
<li><b>[2022-04-26] Larry</b>: 既然你认为我应该投入至少20亿...我投入20亿 👍</li>
<li><b>埃隆</b>: 哈哈谢谢你：)</li>
</ul></li><br>

<li><a id="8" rel="noopener noreferrer"></a><a href="#8" rel="noopener noreferrer">2022-03-27至2022-03-31</a> [与埃隆·马斯克、Egon Durban、“Martha Twitter NomGov”、Brett Taylor [Salesforce CEO兼Twitter董事会主席]、“Parag” [大概是Twitter CEO Parag Agrawal]的群聊]

<ul>
<li><b>Egon</b>: 大家好，Parag（CEO）、Bret（主席）和Martha（政府关系负责人）-你们和埃隆有联系。他知道我和你们的对话。埃隆-大家对你的参与和加入董事会感到兴奋。下一步是让你和他们三位聊聊，以便我们能快速推进。也许我们能在未来几天内完成🤞</li>
<li><b>埃隆</b>: 谢谢你，Egon。</li>
<li><b>Parag</b>: 嘿埃隆-很高兴能直接联系上。很想聊聊！Parag</li>
<li><b>Martha</b>: 嘿埃隆，我是Martha，Twitter提名治理委员会主席-知道你和Bret、Parag聊过-很想在你有时间的时候聊聊天-我在欧洲（也听说你得了新冠，希望不严重）</li>
<li><b>Parag</b>: 期待很快见面！你能告诉我们未来几天你什么时候能在湾区见面吗？</li>
<li><b>Martha</b>: 嘿埃隆，我是Martha，Twitter提名治理委员会主席-知道你和Bret、Parag聊过-我非常想在你有时间的时候聊聊天-我在欧洲但会想办法</li>
<li><b>埃隆</b>: 听起来不错。也许今晚中央时间晚些时候可以？我通常熬夜到凌晨3点左右。</li>
<li><b>Martha</b>: 如果你方便，我将在欧洲中部时间上午10点（太平洋时间凌晨1点）打给你，期待见到你。</li>
<li><b>埃隆</b>: 好的。</li>
<li><b>Martha</b>: 非常感谢你抽时间，埃隆-请告诉我们你办公室里谁可以和我们的总法律顾问聊聊-睡个好觉！</li>
<li><b>埃隆</b>: 你太客气了。很高兴交谈！</li>
</ul></li><br>

<li><a id="9" rel="noopener noreferrer"></a><a href="#9" rel="noopener noreferrer">2022-03-27</a> [与上述交织]

<ul>
<li><b>Brett Taylor</b>: 我是Bret Taylor。请告诉我你今天什么时候有空谈谈。刚和Parag通完话，我知道他今天也急于和你谈谈。全天灵活。</li>
<li><b>埃隆</b>: 今晚晚些时候可以-也许晚上7点？我得了轻微的新冠，所以有点不舒服。</li>
<li><b>Bret</b>: 听说很难受-它可能会让你倒下。7点听起来很棒。</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
</ul></li><br>

<li><a id="10" rel="noopener noreferrer"></a><a href="#10" rel="noopener noreferrer">2022-03-27</a>

<ul>
<li><b>Parag</b>: 很想聊聊。请告诉我什么时间方便-我非常灵活。-Parag</li>
<li><b>埃隆</b>: 也许今晚8点左右？</li>
<li><b>Parag</b>: 那可以！期待交流。</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
<li><b>埃隆</b>: 刚结束特斯拉自动驾驶工程会议。</li>
<li><b>Parag</b>: [“喜欢”了上面的消息]</li>
</ul></li><br>

<li><a id="11" rel="noopener noreferrer"></a><a href="#11" rel="noopener noreferrer">2022-03-27至2022-04-24</a> [与上述交织]

<ul>
<li><b>“Dr Jabour”</b>: 嗨，E，疼痛缓解了吗？是时候让一位当代的古腾堡来复兴言论自由...并买下Twitter了。</li>
<li><b>[2022-04-04] 埖隆</b>: [“喜欢”了上面的消息]</li>
<li><b>[2022-04-24] Jabour</b>: 嗨E，看起来一位Twitter董事会成员在手忙脚乱地试图应对你的报价...我很喜欢你的策略，（视频来自我房子在Monica海滩）-Brad</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
</ul></li><br>

<li><a id="12" rel="noopener noreferrer"></a><a href="#12" rel="noopener noreferrer">2022-03-29至2022-04-01</a> [与上述交织]

<ul>
<li><b>Will MacAskill [有效利他主义运动联合创始人，牛津大学教授，牛津全球优先研究所董事会主席]</b>: 嘿-我在Twitter上看到了你关于Twitter和言论自由的投票。我不确定这是否是你在想的，但我的合作者Sam Bankman-Fried (<a href="https://www.forbes.com/profile/sam-bankman-fried/?sh=4de9866a4449" rel="noopener noreferrer">https://www.forbes.com/profile/sam-bankman-fried/?sh=4de9866a4449</a>) 一直有兴趣购买它，然后让它变得更好。如果你想和他谈谈在这个方向上的合作，他的号码是[已编辑]，他用Signal。</li>
<li><b>埃隆</b>: 他有很多钱吗？</li>
<li><b>Will</b>: 这取决于你如何定义“很多”！他身价240亿美元，他的早期员工（有共同价值观）将这个数字提高到300亿。我问了他原则上能贡献多少，他说：“约10-30亿美元很容易-30-80亿美元我可以做到约80-150亿美元是可能的，但需要融资”</li>
<li><b>Will</b>: 如果你有兴趣讨论这个想法，我问过了，他说愿意在奥斯汀见你。</li>
<li><b>Will</b>: 他通常在巴哈马。我下周可能会去奥斯汀，如果你在的话？</li>
<li><b>Will</b>: 那是个开始。</li>
<li><b>Will</b>: 你想让我通过短信介绍你们认识吗？</li>
<li><b>埃隆</b>: 你为他担保吗？</li>
<li><b>Will</b>: 非常愿意！非常致力于让人类的长期未来顺利发展。</li>
<li><b>埃隆</b>: 好的，当然可以。</li>
<li><b>Will</b>: 太好了！会用Signal。</li>
<li><b>Will</b>: (Signal不行；改用imessage)</li>
<li><b>埃隆</b>: 好的。</li>
<li><b>Will</b>: 另外，如果你想了解Sam，这里是他基金会Future Fund的4月1日推文，我为其提供咨询-我想你可能会喜欢：</li>
<li><b>Will</b>: <a href="https://twitter.com/ftxfuturefund/status/1509924452422717440?s=20&amp;t=0qjM58KUj49xSGa0qae97Q" rel="noopener noreferrer">https://twitter.com/ftxfuturefund/status/1509924452422717440?s=20&amp;t=0qjM58KUj49xSGa0qae97Q</a></li>
<li><b>Will</b>: 这是实际的（信息量更大的）发布推文·今年将拨款1亿-10亿美元用于改善人类的未来：</li>
<li><b>Will</b>: <a href="https://twitter.com/ftxfuturefund/status/1498350483206860801" rel="noopener noreferrer">https://twitter.com/ftxfuturefund/status/1498350483206860801</a></li>
</ul></li><br>

<li><a id="13" rel="noopener noreferrer"></a><a href="#13" rel="noopener noreferrer">2022-03-29至2022-04-14</a>

<ul>
<li><b>Mathias Döpfner [Axel Springer CEO兼22%所有者，数字出版商和报纸出版商联邦协会主席]</b>: 你为什么不买Twitter？我们为你运营。建立一个真正的言论自由平台。这将是对民主的真正贡献。</li>

<li><b>埃隆</b>: 有趣的想法。</li>
<li><b>Mathias</b>: 我是认真的。可行。会很有趣。</li>
<li><b>[2022-04-04] Mathias</b>: 恭喜投资Twitter！快速执行 🤩 我们应该讨论一下我们是否应该加入这个项目吗？我的建议是认真的。</li>
<li><b>埃隆</b>: 当然，乐意聊聊。</li>
<li><b>Mathias</b>: 我明天要去迈阿密一周。我们那时或周三聊聊，然后再定？</li>
<li><b>埃隆</b>: 好的。</li>
<li><b>[2022-04-06] Mathias</b>: 关于Twitter的简短通话？</li>
<li><b>Mathias</b>: # 现状：它是事实上的公共城镇广场，但问题是它没有遵循言论自由原则。=&gt; 所以核心产品相当不错，但 (i) 它不服务于民主，且 (ii) 当前的商业模式已经走进死胡同，这从股价持平就能反映出来。# 目标：让Twitter成为言论自由的全球支柱，一个真正符合第一修正案精神的思想开放市场，并将商业模式转变为广告支持和付费支持的组合 # 行动计划：1. “解决言论自由” 1a) 步骤1：通过大幅减少服务条款（现在有数百页）来使其免于审查，如下：Twitter用户同意：(1) 使用我们的服务发送垃圾邮件或诈骗用户，(2) 宣扬暴力，(3) 发布非法色情内容。🙃 1b) 步骤2：使Twitter抵抗审查 • 通过实施确Twitter长期不会受到审查的措施来确保抗审查能力，无论哪个政府和管理层。• 如何做？继续推进Twitter内部正在开发的去中心化社交网络协议项目（例如BlueSky）。这并不容易，但后端必须运行在去中心化基础设施上，API应该开放（回归本源！Twitter从一开始就因其开放API而起步并发展壮大）。• Twitter将是众多发布和消费内容的客户端之一。• 然后创建一个算法市场，例如，如果你是个雪花（玻璃心），不想看到冒犯你的内容，可以选择另一个算法。2. “解决股价” 业务现状：• Twitter的广告收入稳定增长，目前足以资助运营。• 月活跃用户持平，没有结构性增长 • 股价持平，对现有商业模式缺乏信心和/或</li>
<li><b>[2022-04-14] Mathias</b>: 我们《世界报》的主编刚刚接受采访，解释他为什么离开Twitter。他批评的正是你很可能想要改变的东西。我迫不及待想在你准备好时讨论Twitter的未来。太令人兴奋了。</li>
<li><b>埃隆</b>: 有趣！</li>
</ul></li><br>

<li><a id="14" rel="noopener noreferrer"></a><a href="#14" rel="noopener noreferrer">2022-03-31至2022-04-01</a> [与Bret Taylor、Parag和埃隆·马斯克的群聊，与上述部分交织]

<ul>
<li><b>埃隆</b>: 我明天下午2点左右在圣何塞降落，午夜左右离开。我的特斯拉会议是灵活的，所以我可以在那10个小时内的任何时间见面。</li>
<li><b>Bret</b>: 你说的“明天”是指周四还是周五？</li>
<li><b>埃隆</b>: 今天。</li>
<li><b>Parag</b>: 那10个小时内的任何时间我都可以。</li>
<li><b>Bret</b>: 我晚上8:30在奥克兰降落。也许我们9:30左右在某个地方见面？我正在努力看看能否改签从纽约起飞的航班，以便更早降落。</li>
<li><b>Bret</b>: 正在努力早降落并降落在圣何塞，这样我们可以在你附近吃晚饭。我会实时向你们俩更新。</li>
<li><b>Bret</b>: 好的，成功改签了航班，下午6:30降落圣何塞。正在找一个我们可以私下见面的地方。</li>
<li><b>埃隆</b>: 听起来不错。</li>
<li><b>埃隆</b>: Twitter上的加密货币垃圾信息真的需要被清除。它严重影响用户体验，并且诈骗了这么多无辜的人。</li>
<li><b>Bret</b>: 听起来我们确认了晚上7点在圣何塞附近的一个私人住宅见面。我们的助手就后勤事宜联系了Jehn。如果你们中任何人有任何顾虑或想调整，请告诉我。期待我们的对话。</li>
<li><b>Parag</b>: 对我来说可以。期待与你们俩本人见面！</li>
<li><b>埃隆</b>: Jehn生了孩子，我决定试着几个月不要助理。</li>
<li><b>埃隆</b>: 我也一样。</li>
<li><b>Bret</b>: 地址是[已编辑]。</li>
<li><b>Bret</b>: 晚上7点可以吗，埃隆？</li>
<li><b>埃隆</b>: 大概那个时间。可能只能7:30到，但会努力早点到。</li>
<li><b>Bret</b>: 听起来不错。我会早点到，因为我的飞机降落得早，但整个晚上都有空，所以你一到我们就可以开始，Parag和我可以先聊聊。</li>
<li><b>Bret</b>: 这是我最近开会最奇怪的地方。我想他们是在机场附近找Airbnb，结果有拖拉机和驴子🤷</li>
<li><b>埃隆</b>: 哈哈太棒了。</li>
<li><b>埃隆</b>: 也许Airbnb的算法认为你喜欢拖拉机和驴子（谁不喜欢！）。</li>
<li><b>埃隆</b>: 在路上了。大约15分钟后到。</li>
<li><b>Bret</b>: 还有废弃的卡车，以防我们见面后想开始餐饮业务。</li>
<li><b>埃隆</b>: 听起来像后世界末日电影的布景。</li>
<li><b>Bret</b>: 基本上是的。</li>
<li><b>埃隆</b>: 晚餐很棒：)</li>
<li><b>Bret</b>: 真的很棒。驴子和反乌托邦的监视直升机增添了氛围。</li>
<li><b>埃隆</b>: 肯定是会载入史册的一次，哈哈。</li>
<li><b>Parag</b>: 因多种原因而难忘。真的很享受。</li>
</ul></li><br>

<li><a id="15" rel="noopener noreferrer"></a><a href="#15" rel="noopener noreferrer">2022-03-31至2022-04-02</a> [与Will MacAskill、“Sam BF”和埃隆·马斯克的群组信息，与上述交织]

<ul>
<li><b>Will</b>: 嘿，介绍你们俩认识，Sam和埃隆。你们俩都对游戏、让非常长远的未来顺利发展以及购买Twitter感兴趣。所以我认为你们会有一场愉快的对话！</li>
<li><b>Sam</b>: 很高兴认识你埃隆-乐意随时聊聊Twitter（或其他事情）！</li>
<li><b>埃隆</b>: 嗨！</li>
<li><b>埃隆</b>: 也许我们今天晚些时候可以聊聊？我在德国。</li>
<li><b>Sam</b>: 我在东部时间-可以在今天德国时间晚上7点到10点之间聊吗？</li>
</ul></li><br>

<li><a id="16" rel="noopener noreferrer"></a><a href="#16" rel="noopener noreferrer">2022-04-03至2022-04-04</a> [与Jared Birchall、“Martha Twitter NomGov”和埃隆·马斯克的群聊]

<ul>
<li><b>埃隆</b>: 正在连接Martha（Twitter提名/治理委员会）和Jared（管理我的家族办公室）。</li>
<li><b>埃隆</b>: Jared，有重要文件需要签署，以便我希望能加入Twitter董事会。</li>
<li><b>Martha</b>: 谢谢埃隆-感激不尽-嗨Jared-我会让Sean Edgett与你联系，他是Twitter的总法律顾问。</li>
<li><b>Jared</b>: 听起来不错。请让他随时打电话或发送文件到我的邮箱：[...]</li>
<li><b>Martha</b>: 👍</li>
<li><b>Martha</b>: 埖隆-你有空聊5分钟吗？</li>
<li><b>Martha</b>: 我想向你转达我们刚结束的会议。</li>
<li><b>埃隆</b>: 当然。</li>
<li><b>[2022-04-04] Martha</b>: 早上好埃隆-你醒来就面对了很大的风暴...很高兴听到Bret说你同意我们今天可以快速推进此事-Jared，我猜是你我应该把他们讨论的停止协议发给你？它和Egon及Silver Lake承担的相同。如果应该发给其他人请告诉我-我们真的希望在未来几小时内完成。谢谢</li>
<li><b>埃隆</b>: 你可以发给我们俩。</li>
<li><b>埃隆</b>: 抱歉，Bret打电话时我刚醒来！我凌晨4点从柏林抵达。</li>
<li><b>Martha</b>: 无需道歉。那我们怎么发送？如果是邮件，请告诉我地址。</li>
<li><b>埃隆</b>: 短信或邮件。</li>
<li><b>埃隆</b>: 我的邮箱是[已编辑]。</li>
<li><b>Martha</b>: 👍</li>
<li><b>Martha</b>: &lt;附件- application/vnd.openxmlformats-officedocument.wordprocessingml.document-Twitter合作协议-草案2022年4月4日.docx&gt;</li>
<li><b>Martha</b>: 在这里-也通过邮件发送了。和Egon的相同，但更精简。</li>
<li><b>Martha</b>: 只是抄送你们俩确认已发送协议-根据你们的对话，非常希望快速完成此事。</li>
</ul></li><br>

<li><a id="17" rel="noopener noreferrer"></a><a href="#17" rel="noopener noreferrer">2022-04-03</a>

<ul>
<li><b>Bret</b>: 刚和Martha通完话。请告诉我你今天或明天什么时候有时间谈谈。听起来你可能要上飞机了—灵活。</li>
<li><b>埃隆</b>: 听起来不错。我正要从柏林飞往奥斯汀，但明天随时可以聊。</li>
<li><b>Bret</b>: 我明天全天有空。有空时发短信。计划和妻子去徒步，只有那时我的信号可能不太好。期待交流。也期待与你合作！</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
</ul></li><br>

<li><a id="18" rel="noopener noreferrer"></a><a href="#18" rel="noopener noreferrer">2022-04-03</a> [与上述交织]

<ul>
<li><b>Parag</b>: 我想你已经听说了Martha和Bret的消息。我对这个机会感到超级兴奋，期待紧密合作，并找到方法最有效地利用你的时间来改善Twitter和公共对话。</li>
<li><b>埃隆</b>: 听起来很棒！</li>
</ul></li><br>

<li><a id="19" rel="noopener noreferrer"></a><a href="#19" rel="noopener noreferrer">2022-04-03</a>

<ul>
<li><b>jack</b>: 听说有好消息正在发生。</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
</ul></li><br>

<li><a id="20" rel="noopener noreferrer"></a><a href="#20" rel="noopener noreferrer">2022-04-04</a>

<ul>
<li><b>Ken Griffin [Citadel CEO]</b>: 太棒了！！</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
</ul></li><br>

<li><a id="21" rel="noopener noreferrer"></a><a href="#21" rel="noopener noreferrer">2022-04-04</a>

<ul>
<li><b>Bret Taylor</b>: 嘿，你有空吗？</li>
<li><b>Bret Taylor</b>: 鉴于SEC文件，希望尽快通话协调沟通。你回来后请尽快打电话。</li>
</ul></li><br>

<li><a id="22" rel="noopener noreferrer"></a><a href="#22" rel="noopener noreferrer">2022-04-04</a> [与上述交织]

<ul>
<li><b>[已编辑]</b>: 恭喜！！上面那篇文章☝️ [似乎指 <a href="https://www.revolver.news/2022/04/elon-musk-buy-twitter-free-speech-tech-censorship-american-regime-war/" rel="noopener noreferrer">https://www.revolver.news/2022/04/elon-musk-buy-twitter-free-speech-tech-censorship-american-regime-war/</a>] 列举了一些可能发生的事情：第一步：指责平台及其用户 第二步：协调施压运动 第三步：蓝V用户外流 第四步：平台封禁 “但这不会容易。这将是一场战争。让战斗开始吧。”</li>
<li><b>[已编辑]</b>: 这将是一场微妙的游戏，让右翼分子重新回到Twitter上，并如何驾驭它（特别是老板你自己，如果你愿意的话）我也会尽早制定标准，但让一个具有敏锐文化/政治观点的人担任实际执行的副总裁。</li>
<li><b>[已编辑]</b>: 布莱克·马斯特斯那种类型的人。</li>
</ul></li><br>

<li><a id="23" rel="noopener noreferrer"></a><a href="#23" rel="noopener noreferrer">2022-04-04至2022-04-17</a> [与上述交织]

<ul>
<li><b>Egon Durban</b>: 嗨-如果你有几分钟，请随时打电话？飞往英国。</li>
<li><b>埃隆</b>: 刚和Bret通完话。他的电话把我叫醒了哈哈。凌晨4点从柏林抵达。</li>
<li><b>Egon</b>: 🙏</li>
<li><b>[2022-04-17] 埖隆</b>: 你在给摩根士丹利打电话说我的坏话...</li>
</ul></li><br>

<li><a id="24" rel="noopener noreferrer"></a><a href="#24" rel="noopener noreferrer">2022-04-04</a>

<ul>
<li><b>埃隆致Jared Birchall</b>: 请和Martha谈谈文件的事。</li>
<li><b>Jared</b>: 好的。</li>
</ul></li><br>

<li><a id="25" rel="noopener noreferrer"></a><a href="#25" rel="noopener noreferrer">2022-04-04</a>

<ul>
<li><b>Bret Taylor</b>: 你有五分钟时间吗？</li>
<li><b>埃隆</b>: 当然。</li>
</ul></li><br>

<li><a id="26" rel="noopener noreferrer"></a><a href="#26" rel="noopener noreferrer">2022-04-04</a>

<ul>
<li><b>埃隆致Parag</b>: 如果你想聊，我很乐意。</li>
<li><b>Parag</b>: 那将很有帮助。你有空时请给我打电话。</li>
<li><b>埃隆</b>: 正在和Jared通电话。一结束就打给你。</li>
<li><b>Parag</b>: [“喜欢”了上面的消息]</li>
</ul></li><br>

<li><a id="27" rel="noopener noreferrer"></a><a href="#27" rel="noopener noreferrer">2022-04-04</a>

<ul>
<li><b>“Kyle”</b>: 所以你现在能把我们从Twitter监狱里放出来了吗哈哈</li>
<li><b>埃隆</b>: 我没有那个能力。</li>
<li><b>Kyle</b>: 哈哈我知道我知道。但大动作啊，很高兴看到。</li>
</ul></li><br>

<li><a id="28" rel="noopener noreferrer"></a><a href="#28" rel="noopener noreferrer">2022-04-04</a> [与Egon Durban、“Martha Twitter NomGov”、Brett Taylor、Parag Agrawal和埃隆·马斯克的群聊，与上述交织]

<ul>
<li><b>埃隆</b>: 感谢你们考虑让我加入Twitter董事会，但经过仔细考虑，我目前的职务承诺使我无法成为一名有效的董事会成员。未来可能会有变化。埃隆</li>
</ul></li><br>

<li><a id="29" rel="noopener noreferrer"></a><a href="#29" rel="noopener noreferrer">2022-04-04</a> [与上述部分交织]:

<ul>
<li><b>Joe Rogan</b>: 你要把Twitter从热衷审查的暴民手中解放出来吗？</li>
<li><b>埃隆</b>: 我会提供建议，他们可能会也可能不会采纳。</li>
</ul></li><br>

<li><a id="30" rel="noopener noreferrer"></a><a href="#30" rel="noopener noreferrer">2022-04-04</a>

<ul>
<li><b>Bret Taylor</b>: <a href="https://twitter.com/trungtohan/status/1510994320471429131?s=10&amp;t=qrv_fOhTfUzRVDe_IbJKlQ" rel="noopener noreferrer">https://twitter.com/trungtohan/status/1510994320471429131?s=10&amp;t=qrv_fOhTfUzRVDe_IbJKlQ</a></li>
<li><b>埃隆</b>: [“笑”了上面的消息]</li>
</ul></li><br>

<li><a id="31" rel="noopener noreferrer"></a><a href="#31" rel="noopener noreferrer">2022-04-04至2022-04-05</a> [与上述交织]

<ul>
<li><b>Parag</b>: 你应该在邮箱里收到了更新后的协议。我有空聊天。</li>
<li><b>埃隆</b>: 批准了。</li>
<li><b>Parag</b>: [“喜欢”了上面的消息]</li>
<li><b>Parag</b>: 有几分钟聊天吗？我渴望快速推进。</li>
<li><b>埃隆</b>: 当然，我正在SpaceX发动机评审会议上。</li>
<li><b>Parag</b>: 之后请给我打电话。</li>
<li><b>Parag</b>: 我很高兴地宣布，我们将任命 @elonmusk 加入我们的董事会！通过近几周与埃隆的对话，我清楚地认识到他将为我们的董事会带来巨大价值。为什么？最重要的是，他既是这项服务的狂热支持者，也是其强烈的批评者，这正是Twitter和董事会为了我们的长期发展所需要的。欢迎埃隆！</li>
<li><b>埃隆</b>: 听起来不错。</li>
<li><b>埃隆</b>: 马上发布？</li>
<li><b>Parag</b>: <a href="https://twitter.com/paraga/status/1511320953598357505?s=21&amp;t=g9oXkMyPGFahuVNDKcoBa5A" rel="noopener noreferrer">https://twitter.com/paraga/status/1511320953598357505?s=21&amp;t=g9oXkMyPGFahuVNDKcoBa5A</a></li>
<li><b>埃隆</b>: 酷。</li>
<li><b>Parag</b>: 超级兴奋！</li>
<li><b>埃隆</b>: 我也一样！</li>
<li><b>埃隆</b>: 刚和杰克进行了一次很棒的对话！你今晚晚些时候有空聊吗？</li>
<li><b>Parag</b>: 有空，什么时间？</li>
<li><b>埃隆</b>: 最好能解除永久封禁，除了垃圾邮件账户和明确鼓吹暴力的账户。</li>
<li><b>埃隆</b>: 加州时间晚上7点？或者之后任何时间。</li>
<li><b>Parag</b>: 7点可以！回头聊。</li>
<li><b>埃隆</b>: 几分钟后打回给你。</li>
<li><b>Parag</b>: [“喜欢”了上面的消息]</li>
<li><b>埃隆</b>: 总结得相当好。</li>
<li><b>埃隆</b>: <a href="https://twitter.com/stevenmarkryan/status/1511489781104275456?s=1O&amp;t=LprG6-7KefKLzNX133IpjQ" rel="noopener noreferrer">https://twitter.com/stevenmarkryan/status/1511489781104275456?s=1O&amp;t=LprG6-7KefKLzNX133IpjQ</a></li>
</ul></li><br>

<li><a id="32" rel="noopener noreferrer"></a><a href="#32" rel="noopener noreferrer">2022-04-05</a> [与Jared Birchall、“Martha Twitter NomGov”和埃隆·马斯克的群聊]

<ul>
<li><b>Martha</b>: 我非常高兴你加入董事会。对于第一份协议的变动我感到抱歉-我不擅长管理律师。我真的很期待见到你。</li>
<li><b>埃隆</b>: 谢谢Martha，我也一样。</li>
</ul></li><br>

<li><a id="33" rel="noopener noreferrer"></a><a href="#33" rel="noopener noreferrer">2022-04-05</a> [与上述交织]

<ul>
<li><b>Bret</b>: 很高兴与你合作，并感激事情进展顺利。</li>
<li><b>埃隆</b>: 我也一样。</li>
</ul></li><br>

<li><a id="34" rel="noopener noreferrer"></a><a href="#34" rel="noopener noreferrer">2022-04-05</a> [与上述交织]

<ul>
<li><b>jack</b>: 感谢你加入！</li>
<li><b>jack</b>: <a href="https://twitter.com/jack/status/1511329369473564677?s=21&amp;t=DdrUUFvJPD7Kf-jXjBogIg" rel="noopener noreferrer">https://twitter.com/jack/status/1511329369473564677?s=21&amp;t=DdrUUFvJPD7Kf-jXjBogIg</a></li>
<li><b>埃隆</b>: 绝对的。希望我能有所帮助。</li>
<li><b>jack</b>: 巨大的帮助。Parag是一位出色的工程师。董事会很糟糕。随时愿意与你谈论任何你想谈的事情。</li>
<li><b>埃隆</b>: 什么时候适合保密谈话？</li>
<li><b>jack</b>: 随时都可以。</li>
<li><b>埃隆</b>: 谢谢，谈话很棒！</li>
<li><b>jack</b>: 永远如此！我不能更高兴你做这件事了。我期盼已久。当得知这终于成为可能时，我非常激动。</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
<li><b>埃隆</b>: 如果我做了什么蠢事或者没做什么，请一定要直言不讳。我将不胜感激。</li>
<li><b>jack</b>: 我信任你，但绝对会的。</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
<li><b>jack</b>: <a href="https://twitter.com/MattNavarra/status/1511773605239078914" rel="noopener noreferrer">https://twitter.com/MattNavarra/status/1511773605239078914</a></li>
<li><b>jack</b>: 看起来在绝望的泥潭里有一个“认证”账户。<a href="https://m.facebook.com/Elonmuskoffifref=nf&amp;pn_ref=story&amp;rc=p" rel="noopener noreferrer">https://m.facebook.com/Elonmuskoffifref=nf&amp;pn_ref=story&amp;rc=p</a> (还推广加密货币！)</li>
<li><b>埃隆</b>: 哈哈。</li>
<li><b>[2022-04-26] jack</b>: 我想确保Parag在交易完成前尽一切努力实现你的目标。他在被赋予明确方向时非常擅长完成事情。让我、你和他通个电话讨论下一步行动并明确需要什么是否合理？这样他就能快速行动并理清。大家目标一致，这甚至会更有帮助。</li>
<li><b>埃隆</b>: 好的。</li>
<li><b>jack</b>: 太好了！我会安排。我不会让它失败，会尽一切努力。这太关键了，关乎人类。</li>
<li><b>埃隆</b>: 绝对是。</li>
<li><b>jack</b>: &lt;附件-图片/jpeg-屏幕截图2022-04-226日15.05.00.jpeg&gt;</li>
<li><b>jack</b>: 我整理了一份草案清单，以使讨论更高效。目标是围绕1）我们要解决的问题，2）长期优先事项，3）短期行动达成一致，都使用你提到的更高层次指南。想想你会增加/删减什么。解决这个问题会提高速度。</li>
<li><b>jack</b>: 这是会议链接，你时间晚上7点。</li>
<li><b>jack</b>: [会议URL]</li>
<li><b>埃隆</b>: 很好的行动清单。</li>
<li><b>jack</b>: 我们在Hangout上，等你准备好了随时开始。不着急。只是在完善文档。</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
<li><b>埃隆</b>: 它要求我登录谷歌账户。</li>
<li><b>埃隆</b>: 你和我完全一致。Parag行动太慢了，试图取悦那些无论如何都不会满意的人。</li>
<li><b>jack</b>: 至少现在很清楚你们无法共事了。这很明确。</li>
<li><b>埃隆</b>: 是的。</li>
</ul></li><br>

<li><a id="35" rel="noopener noreferrer"></a><a href="#35" rel="noopener noreferrer">2022-04-06</a>

<ul>
<li><b>Ira Ehrenpreis [风险投资人]</b>: 如果你计划加入提名/治理或薪酬委员会，告诉我，我可以给你一些建议！哈哈！🤪</li>
<li><b>埃隆</b>: 哈哈，我甚至不想加入Twitter董事会！他们极力推动我加入。</li>
<li><b>Ira</b>: 你是个软心肠！😂</li>
<li><b>Ira</b>: 而且你已经让他们尝试编辑推文功能了！哦是的...它其实已经在开发中了。当然。</li>
<li><b>埃隆</b>: 它实际上已经在开发中了，但我不知道。</li>
</ul></li><br>

<li><a id="36" rel="noopener noreferrer"></a><a href="#36" rel="noopener noreferrer">2022-04-06至2022-04-08</a>

<ul>
<li><b>Justin Roiland [《瑞克和莫蒂》联合创作者]</b>: 我他妈爱你成为Twitter的大股东。我的朋友David和Daniel有一个验证身份的程序，连接到Twitter会很好。就是说，如果人们选择使用它，它可以验证他们是真实的人而不是喷子农场。我应该介绍你们认识。</li>
<li><b>埃隆</b>: 我只拥有Twitter 9%的股份，所以并不控制公司。</li>
<li><b>埃隆</b>: 会向Parag（CEO）提出身份验证问题。</li>
</ul></li><br>

<li><a id="37" rel="noopener noreferrer"></a><a href="#37" rel="noopener noreferrer">2022-04-06至2022-04-14</a>

<ul>
<li><b>Gayle King [CBS《今早》联合主持人，《奥普拉杂志》编辑]</b>: 我是Gayle！你想我了吗（笑）你准备好和我坐下来好好聊聊了吗！太多要讨论了！特别是你的Twitter动作...我需要做些什么？？？另外我喜欢Twitter编辑功能带24小时时间限制...我们都会在气头上说一些后悔想收回的话...</li>
<li><b>埃隆</b>: Twitter编辑按钮即将推出。</li>
<li><b>Gayle</b>: 整个Twitter事件被过度放大了。</li>
<li><b>埃隆</b>: 拥有约9%的股份并不等于控制。</li>
<li><b>Gayle</b>: 我从没认为...而且我数学不好。</li>
<li><b>埃隆</b>: Twitter应该向中间靠拢，但Parag在我到来之前就认为应该如此。</li>
<li><b>埃隆</b>: [“笑”了“我从没认为...”]</li>
<li><b>[2022-04-14] Gayle</b>: 埖隆！你要买Twitter还是出价买Twitter 哇！现在你不觉得我们应该面对面坐下来吗 现在的孩子们说这是一种“黑帮举动”我不知道股东怎么会拒绝...就像我说的，你和班上其他孩子不一样....</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
<li><b>[2022-04-18] 埖隆</b>: 也许奥普拉有兴趣加入Twitter董事会，如果我的报价成功的话。关于人性的智慧和知道什么是对的，比所谓的“董事会治理”技能重要得多，根据我的经验，后者几乎毫无意义。</li>
</ul></li><br>

<li><a id="38" rel="noopener noreferrer"></a><a href="#38" rel="noopener noreferrer">2022-04-07至2022-04-08</a> [与上述交织]

<ul>
<li><b>Parag</b>: 围绕这一点有很多想法值得探索-甚至比这摩擦更小的。</li>
<li><b>埃隆</b>: 我有个想法可以一举两得。</li>
<li><b>埃隆</b>: 对了，你的邮箱是什么？</li>
<li><b>Parag</b>: [...]</li>
<li><b>Parag</b>: 你下周能虚拟参加员工问答环节吗？我的行程导致延迟太久，而且只有约10-15%的听众会亲自到场，所以无论如何我们会为虚拟优化。下周三/四太平洋时间上午11点对你来说哪天方便进行45分钟的视频问答？-否则我可以建议其他时间。试图最大化跨全球时区的参与度。</li>
<li><b>Parag</b>: 下次交谈时很想听听更多-你明天有空吗？</li>
<li><b>埃隆</b>: 当然。</li>
<li><b>埃隆</b>: 听取Twitter工程团队的最新进展会很好，这样我的建议就不会那么愚蠢。</li>
<li><b>Parag</b>: 是的-会在问答之前安排一次产品+工程对话-他们说，我预计大多数问题不会深入具体想法/深度-而是更多围绕你对Twitter未来的看法、为什么它重要、你个人为什么想参与、你希望看到什么改变...-但也有一些来自那些不高兴你参与并且通常因为某种原因不喜欢你的人。正如你昨天所说，目标是让人们直接听你说话，而不是根据媒体故事对你做假设。下周四太平洋时间上午11点进行问答可以吗？</li>
<li><b>埃隆</b>: 太平洋时间周三上午11点很棒。</li>
<li><b>埃隆</b>: 确实如此。周四太平洋时间上午11点可以。</li>
<li><b>Parag</b>: 好的酷。那么将确认周三太平洋时间上午11点与少数工程和产品负责人进行交谈。周四太平洋时间上午11点进行AMA。</li>
<li><b>Parag</b>: 另外：我发给公司的关于AMA的邮件已经泄露了+内部Slack消息有很多泄露：<a href="https://www.washingtonpost.com/technology/2022/04/07/musk-twitter-employee-outcry/" rel="noopener noreferrer">https://www.washingtonpost.com/technology/2022/04/07/musk-twitter-employee-outcry/</a> -我认为很大一部分沉默的多数对你加入董事会感到兴奋，所以这不具有代表性。乐意讨论-这些都不意外。</li>
<li><b>埃隆</b>: 黄色。</li>
<li><b>埃隆</b>: *太棒了（该死的自动更正！）</li>
<li><b>埃隆</b>: 如预期。是的，同步一下会很好。我明天晚上或这个周末任何时间都可以。我喜欢我们的对话！</li>
<li><b>Parag</b>: 明天太平洋时间下午5:30之后我完全灵活-告诉我什么时间合适。是的，这是预期的-我认为这有助于推动公司向积极方向发展。尽管内部动荡-我认为这对推动公司前进非常有帮助。</li>
<li><b>埃隆</b>: 太棒了！</li>
<li><b>埃隆</b>: 我有很多想法，但如果你觉得我逼得太紧就告诉我。我只想让Twitter变得尽可能棒。</li>
<li><b>Parag</b>: 我想听听所有的想法-然后我会告诉你哪些我会推进，哪些不会。以及为什么。</li>
<li><b>Parag</b>: 在这个阶段-只是花时间和你在一起很好。+让我的产品和工程团队与你交流，以便双方吸收信息。</li>
<li><b>埃隆</b>: 我想了解Twitter代码库的技术细节。这将帮助我评估我的建议有多愚蠢。</li>
<li><b>埃隆</b>: 我写了20年的重型软件。</li>
<li><b>Parag</b>: 我曾是CTO，在我们的代码库中待了很长时间。</li>
<li><b>Parag</b>: 所以我可以回答你很多问题。</li>
<li><b>埃隆</b>: 我与能够进行硬核编程的工程师交流比与项目经理/MBA类型的人交流效果好得多。</li>
<li><b>埃隆</b>: [“喜欢”了“我曾是CTO...”]</li>
<li><b>埃隆</b>: 🔥🔥</li>
<li><b>Parag</b>: 在下次交谈时-把我当作工程师而不是CEO，看看我们能走到哪里。交谈之后我会知道谁可能是最适合连接你的人。</li>
<li><b>埃隆</b>: 坦白说，我讨厌做管理类工作。我其实觉得任何人都不应该成为任何人的老板。但我喜欢帮助解决技术/产品设计问题。</li>
<li><b>埃隆</b>: 你说了算！</li>
<li><b>Parag</b>: 期待明天的交谈。你喜欢日历邀请发送到你的邮箱地址吗？</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
<li><b>埃隆</b>: 我已经把两个日期放在我的日历上了，但给我补充资料也没问题。<br></li>
<li><b>Parag</b>: 如果你想聊聊Twitter的工程方面，我现在就有空。告诉我！</li>
<li><b>埃隆</b>: 大约45分钟后打电话？</li>
<li><b>Parag [“喜欢”了上面的消息]</b></li><b>
</b><li><b>埃隆</b>: 稍后打回。</li>
<li><b>埃隆</b>: &lt;附件• 图片/png-屏幕截图2022年4月8日晚上10:10:09.png&gt;</li>
<li><b>埃隆</b>: 我真受够了这种东西。</li>
<li><b>Parag</b>: 我们应该发现这个。</li>
<li><b>埃隆</b>: 是的。</li>
</ul></li><br>

<li><a id="39" rel="noopener noreferrer"></a><a href="#39" rel="noopener noreferrer">2022-04-09至2022-04-24</a>

<ul>
<li><b>Kimbal Musk [埃隆的弟弟，The Kitchen Restaurant Group所有者]</b>: 我有一个区块链社交媒体系统的想法，可以同时处理支付和类似Twitter的短文本消息/链接。你必须支付少量费用来注册你链上的信息，这将消除绝大多数垃圾邮件和机器人。没有喉咙可以扼杀，所以言论自由得到保证。</li>
<li><b>Kimbal</b>: 谜题的第二部分是一个大规模的实时数据库，内存中保存所有区块链消息的副本，以及你、你的关注者和你关注的人发送或接收的所有消息。</li>
<li><b>Kimbal</b>: 第三部分是手机上类似Twitter的应用程序，访问云端数据库。</li>
<li><b>Kimbal</b>: 这可能会非常庞大。</li>
<li><b>Kimbal</b>: 我想了解更多。我深入研究了Web3（不是太多加密货币），投票权非常强大且经过验证。在这里也可以做很多事。</li>
<li><b>埃隆</b>: 我认为需要一家基于区块链并包含支付功能的新社交媒体公司。</li>
<li><b>Kimbal</b>: 会让他们用与服务相关的代币支付吗？你必须在钱包中持有代币才能发推。不一定贵，它会随着时间增值。</li>
<li><b>Kimbal</b>: 区块链防止人们删除推文。有好有坏，但让游戏开始吧！</li>
<li><b>Kimbal</b>: 如果你使用自己的代币，你就不需要广告，这是一种付费使用的服务，但价格很低。</li>
<li><b>Kimbal</b>: 随着规模扩大，它将成为一个巨大的业务，纯粹造福用户。我讨厌广告。</li>
<li><b>埃隆</b>: [“喜欢”了上面的消息]</li>
<li><b>Kimbal</b>: 有一些好的广告。感兴趣用户（仅当您想时）的投票组成部分可以对增加价值的广告进行投票。广告商必须质押大得多的代币，但除此之外广告没有费用。它将激发创造力，广告可以政治不正确/艺术/激进主义/慈善事业。</li>
<li><b>Kimbal</b>: 投票权也可以众包踢出骗子。当我看到有人宣传你赠送比特币的骗局时，真让我抓狂。有很多坏人。</li>
<li><b>[2022-04-24] 埖隆</b>: 你想参与Twitter交易吗？</li>
<li><b>Kimbal</b>: 我们明天讨论吧。</li>
<li><b>埃隆</b>: 好的。</li>
<li><b>Kimbal</b>: 我经常可以从我的团队中抽身。明天下午会发短信，如果你有空我们可以见面。</li>
<li><b>埃隆</b>: 好的。</li>
</ul></li><br>

<li><a id="40" rel="noopener noreferrer"></a><a href="#40" rel="noopener noreferrer">2022-04-09</a> [与上述交织]

<ul>
<li><b>Parag</b>: 你有权发推说“Twitter要完了吗？”或任何其他关于Twitter的话-但我的责任是告诉你，这在当前情况下无助于我改善Twitter。下次我们谈话时，我想向你说明目前的内部干扰程度，以及它如何损害我们工作的能力。我希望AMA能帮助人们了解你，理解你为什么相信Twitter，并信任你-我希望公司能变得更加坚韧，不受干扰，但我们目前还没有达到那个水平。</li>
<li><b>埃隆</b>: 你这周完成了什么？</li>
<li><b>埃隆</b>: 我不加入董事会了。这是浪费时间。</li>
<li><b>埃隆</b>: 将提出将Twitter私有化的报价。</li>
<li><b>Parag</b>: 我们能聊聊吗？</li>
</ul></li><br>

<li><a id="41" rel="noopener noreferrer"></a><a href="#41" rel="noopener noreferrer">2022-04-09至2022-04-10</a> [与上述交织]

<ul>
<li><b>Bret</b>: Parag刚才给我打电话，提到了你们的短信对话。你能谈谈吗？</li>
<li><b>埃隆</b>: 请期待私有化要约。</li>
<li><b>Bret</b>: 我看到了短信内容。你能给我五分钟时间理解一下背景吗？我目前还不...</li>
<li><b>埃隆</b>: 通过和Parag聊天来修复Twitter行不通。</li>
<li><b>埃隆</b>: 需要采取激烈行动。</li>
<li><b>埃隆</b>: 作为一家上市公司很难做到这一点，因为清除虚假用户会让数字看起来很糟糕，所以重组应该作为一家私营公司进行。</li>
<li><b>埃隆</b>: 杰克也是这么认为的。</li>
<li><b>Bret</b>: 你能花10分钟和我谈谈这个问题吗？自从你加入董事会以来已经大约24小时了。我明白你的观点，但只是想理解一下突然的转变，并确保我深入理解你的观点和前进道路。</li>
<li><b>埃隆</b>: 我马上要起飞了，但明天可以谈。</li>
<li><b>Bret</b>: 谢谢你。</li>
<li><b>Bret</b>: 嘿-今晚能谈谈吗？我看到了你的推文，更迫切地想了解你的前进道路。</li>
<li><b>[第二天] Bret</b>: 确认你昨天与Parag的短信，你拒绝加入董事会。这将在我们明天的8-K文件中体现。我已要求我们的团队今天将草案发送给你的家族办公室。我期待今天的谈话。</li>
<li><b>埃隆</b>: 听起来不错。</li>
<li><b>埃隆</b>: 在我看来，将Twitter私有化、重组并重返公开市场更好。这也是杰克与我交谈时的观点。</li>
</ul></li><br>

<li><a id="42" rel="noopener noreferrer"></a><a href="#42" rel="noopener noreferrer">2022-04-12</a>

<ul>
<li><b>Michael Kives [好莱坞人才经纪人]</b>: 明天有空在温哥华见Philippe Laffont吗？</li>
<li><b>埃隆</b>: 也许吧。</li>
<li><b>Michael</b>: 什么时间最好？</li>
<li><b>Michael</b>: 任何时间最好？</li>
<li><b>埃隆</b>: 他到底想要什么？</li>
<li><b>Michael</b>: 有一些关于Twitter的想法 拥有特斯拉十亿美元 曾参与最后两轮SpaceX融资 -并且想未来参与Boring Company（我告诉他帮助招聘）你真的可以在你的酒店待20分钟 他非常聪明，好人。</li>
<li><b>埃隆</b>: 好的，他今晚可以过来。香格里拉酒店1001房间。</li>
<li><b>Michael</b>: 需要给你找个好助理！我要去睡了 我会告诉Philippe今晚降落时给你发邮件，以防你还醒着想见面。</li>
<li><b>Michael</b>: <a href="https://twitter.com/sbf_ftx/status/1514588820641128452?s=21&amp;tZ4pA_Ct35ud6M60g3ng" rel="noopener noreferrer">https://twitter.com/sbf_ftx/status/1514588820641128452?s=21&amp;tZ4pA_Ct35ud6M60g3ng</a></li>
<li><b>Michael</b>: 和Sam Bankman-Fried一起做这个可能很酷。</li>
<li><b>[2022-04-28] 埖隆</b>: Twitter显然不会变成某种右翼疯人院。目标是尽可能广泛地包容。为绝大多数美国人做正确的事。</li>
<li><b>Michael</b>: [“喜欢”了上面的消息]</li>
</ul></li><br>

<li><a id="43" rel="noopener noreferrer"></a><a href="#43" rel="noopener noreferrer">2022-04-13至2022-04-15</a>

<ul>
<li><b>埃隆致Bret</b>: 经过几天的深思熟虑-这显然是一个极其严肃的问题-我决定推进将Twitter私有化。我今晚将给你发送报价函，明早将公开。如果你有任何问题，乐意将你与我的团队联系。谢谢，埃隆</li>
<li><b>Bret</b>: 已收到。</li>
<li><b>Bret</b>: 确认我收到了你的邮件。另外，请使用 [...] 作为我的个人邮箱。</li>
<li><b>埃隆</b>: 会的。</li>
<li><b>[2022-04-14] Bret</b>: 埖隆，正如你在我们的新闻稿中看到的，董事会已收到你的信函，正在评估你的提案，以确定其认为对Twitter及其所有股东最有利的行动方案。我们将在完成这项工作后与你联系。Bret</li>
<li><b>埃隆</b>: 听起来不错。</li>
<li><b>[2022-04-17] Bret</b>: 埖隆，我只是确认一下，董事会正在认真审查你信中的提案。我们正在尽快</li></ul></li></ul><p><em>由 mimo-v2.5 模型翻译，花费 47956 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/elon-twitter-texts/</link>
      <guid isPermaLink="false">https://danluu.com/elon-twitter-texts/</guid>
      <pubDate>Sat, 1 Oct 2022 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>未来学家的预测方法与准确性</title>
      <description>&lt;p&gt;我一直在阅读许多预测，这些预测旨在探讨人类在未来10到50年甚至更长时间内可能面临的问题，以便人们能够在这些关键领域工作。我好奇这些对未来的预测究竟有多准确。由于预测的时间跨度如此之远，当今做出这类预测的人中只有极少数有实际记录可循，因此，若要评估哪些预测是合理的，我们需要寻找记录之外的依据。&lt;/p&gt;

&lt;p&gt;本文思路是基于独立选定的一组预测者（维基百科的知名未来学家列表&lt;sup class</description>
      <content:encoded><![CDATA[<p>我一直在阅读许多预测，这些预测旨在探讨人类在未来10到50年甚至更长时间内可能面临的问题，以便人们能够在这些关键领域工作。我好奇这些对未来的预测究竟有多准确。由于预测的时间跨度如此之远，当今做出这类预测的人中只有极少数有实际记录可循，因此，若要评估哪些预测是合理的，我们需要寻找记录之外的依据。</p>

<p>本文思路是基于独立选定的一组预测者（维基百科的知名未来学家列表<sup id="fnref:W"><a href="#fn:W" rel="noopener noreferrer">1</a></sup>）的预测进行研究。这些预测的时间足够长，可以评估其效果，从而理解哪些预测技术有效，哪些无效。这样我们就可以（主要在未来的文章中）评估使用类似方法的预测的可信度。</p>

<p>不幸的是，从独立选择的列表中，每位预测者的记录都很差。而抽查其他未来学家的一些预测后发现，未来学家们的预测记录往往相当糟糕。因此，为了对比有效的技术和无效的技术，我从记忆中选取了一些有不错记录的预测者，这是一个非独立的来源，引入了许多潜在偏差。</p>

<p>让我比通常更有信心的一点是，我在完成本文的评估并写下98%的内容之前，避免阅读对预测方法的独立评估。在阅读其他人的评估后，我发现我与特特洛克（Tetlock）的《超级预测者》（<a href="https://amzn.to/3xzG3a2" rel="noopener noreferrer">Superforecasting</a>）在哪些方法有效、哪些无效的看法大致一致，尽管我们使用了完全不同的数据集。</p>

<p>具体而言，那些热衷于“宏大理念”、用少数几个大锤子套用在每个预测上，并且对特定主题的理解仅限于“鸡尾酒会想法”水平的人，无论他们偏爱的宏大理念是否正确，其预测效果通常很差。一些“宏大理念”的例子包括“环境末日即将来临，超级保护主义将渗透一切”、“经济增长将很快创造近乎无限的财富”、“摩尔定律极其重要”、“量子力学极其重要”等。表现不佳的预测者的另一个共同特征是，他们几乎从不认真评估过去的预测错误，这使得提升他们的直觉或方法变得不可能（除非他们私下进行）。相反，他们常常挑选几个准确或至少听起来与准确预测模糊相似的例子，以此向他人兜售他们的下一代预测。</p>

<p>相比之下，那些拥有（相对）准确预测的人对问题有深刻理解，并且往往有从过去的预测错误中吸取教训的记录。由于本文与特特洛克研究的数据集存在差异，这里的细节相当不同。我发现相对准确的预测者拥有深厚的领域知识，并且隐含地接触到了大量信息，他们能有效过滤这些信息以做出好的预测。特特洛克研究的是那些对广泛领域做出预测的人，这些领域通常超出了他们的专业范围，因此他发现的是，这些人深入钻研数据并深刻理解数据的局限性，从而做出相对准确的预测。但是，尽管人们运作方式的细节不同，但在高层次上，真正深入特定知识的做法是一致的。</p>

<p>由于本文篇幅很长，本文将包含每个预测者的非常简短的总结，然后是对每个预测者的中等长度总结。接着，我们将总结哪些技术和风格有效，哪些无效，完整的预测评分细节以及与其他评估者的比较将附在附录中。</p>

<ul>
<li>雷·库兹韦尔（Ray Kurzweil）：准确率7%
<ul>
<li>依赖于：指数或超指数增长必然持续；基于过去趋势的延续预测未来；乐观地“上调”事实和对数据的解释；关于技术和计算机的“万能药思维”<a href="https://twitter.com/arxanas/status/1560756277231644673" rel="noopener noreferrer">（panacea thinking）</a>；在预测主题上使用“鸡尾酒会想法”级别的理解</li>
</ul></li>
<li>雅克·弗雷斯科（Jacque Fresco）：预测大多过于遥远而无法判断，但对于可判断的预测似乎准确率极低
<ul>
<li>依赖于：关于人性、科学方法和计算机的万能药思维；确信人类价值观与弗雷斯科的价值观一致</li>
</ul></li>
<li>巴克敏斯特·富勒（Buckminster Fuller）：预测太少无法评分，但对于可判断的预测似乎准确率极低
<ul>
<li>依赖于：在预测主题上使用“鸡尾酒会想法”，其程度即使对未来学家来说也极为极端</li>
</ul></li>
<li>米奇奥·卡库（Michio Kaku）：准确率3%
<ul>
<li>依赖于：关于“量子”、计算机和生物技术的万能药思维；这些领域的指数增长</li>
</ul></li>
<li>约翰·奈斯比特（John Naisbitt）：预测过于模糊无法评分；在大局准确性方面结果参差不齐，可能比这里除了迪克森（Dixon）之外的任何未来学家都好，但这与给出其他预测者的百分比不具可比性
<ul>
<li>依赖于：基于报纸分析的趋势预测</li>
</ul></li>
<li>杰勒德·K·奥尼尔（Gerard K. O'Neill）：预测大多过于遥远而无法判断，但对于可判断的预测似乎准确率极低
<ul>
<li>依赖于：与其他未来学家错误做法相反的方式，可描述为基于近期涨幅较大的价格“试图低买高卖”；在奥尼尔认为被低估的领域乐观地“上调”事实和对数据的解释；在预测主题上使用“鸡尾酒会想法”</li>
</ul></li>
<li>帕特里克·迪克森（Patrick Dixon）：准确率10%；在“大局”预测方面也远优于这里的任何其他未来学家（但与非未来学家预测者如耶格（Yegge）、盖茨（Gates）等不在同一水平）
<ul>
<li>依赖于：推断现有趋势（但乐观上调的程度远低于这里几乎任何其他未来学家）；指数增长；“第二千年思维”和“第三千年思维”之间的鲜明分野</li>
</ul></li>
<li>阿尔文·托夫勒（Alvin Toffler）：预测大多过于模糊无法评分；在非模糊预测中，托夫勒有一种不可思议的本事，即在一个趋势即将停止时，将其标记为非常重要且可能持续
<ul>
<li>依赖于：已发生的指数增长必然持续；各种受指数增长预期启发的“鸡尾酒会想法”混合体</li>
</ul></li>
<li>史蒂夫·耶格（Steve Yegge）：准确率50%；对未来的总体愿景通常相当准确
<ul>
<li>依赖于：深厚的领域知识，流入亚马逊和谷歌的信息流；观察正在兴起的事物</li>
</ul></li>
<li>布莱恩·卡普兰（Bryan Caplan）：准确率100%
<ul>
<li>依赖于：接受人们对错误赌注/预测的“另一面”，并且主要依赖于做出非常保守的预测</li>
</ul></li>
<li>比尔·盖茨/内森·梅尔沃德（Nathan Myhrvold）/旧微软领导层：预测时间框架过于模糊无法评分，但对未来的愿景以及各种技术的相对重要性有着不可思议的准确性
<ul>
<li>依赖于：深厚的领域知识，许多拥有深厚领域知识的人之间的讨论，流入微软的信息流</li>
</ul></li>
</ul>

<h3 id="ray-kurzweil">雷·库兹韦尔</h3>

<p>雷·库兹韦尔声称其预测准确率为86%，这一说法经常被重复，例如彼得·迪亚曼迪斯（Peter Diamandis）所说：</p>
<blockquote>
<p>自1990年代以来，库兹韦尔做出了147项预测，其中完全正确的有115项，另有12项被证明“基本正确”（相差一两年），这使得他的预测达到了惊人的86%准确率。</p>
</blockquote>
<p>这篇文章的标题是“谷歌高管声称奇点将在2029年之前发生”，开头写道：“谷歌工程总监雷·库兹韦尔是一位著名的未来学家，拥有高命中率的准确预测记录。”它引用了<a href="https://web.archive.org/web/20170225013846/https://en.wikipedia.org/wiki/Predictions_made_by_Ray_Kurzweil" rel="noopener noreferrer">维基百科上的这份预测列表</a>。86%对于非显而易见的、重大的未来预测来说是一个惊人的好记录。这个说法似乎是其他人声称库兹韦尔具有高准确率的来源，<a href="https://nitter.ca/naijaflyingdr/status/1552751297908117504" rel="noopener noreferrer">例如这里</a>和这里。我自己检查了迪亚曼迪斯引用的维基百科列表的准确率（使用archive.org获取他文章发表时的列表），发现准确率较低，为7%。</p>

<p>从根本上说，导致库兹韦尔许多预测失败的原因是，他依赖于这样一个观点：在他能想象到的几乎每个领域，以及在一些确实有重大增长的领域，增长都以指数级和加速方式进行，但增长并未达到他的预期。他的基本论点是，由于技术进步（改进技术等），我们不仅有指数增长，而且技术进步的反馈会加速指数增长，因此我们在许多重要领域（如计算机性能）拥有双指数增长（如<code>e^x^x，不是2*e^x</code>）。他反复谈论这种不可阻挡的指数或超指数增长，例如，他在1990年的书《智能机器时代》（<a href="https://amzn.to/3LnrpYY" rel="noopener noreferrer">The Age of Intelligent Machines</a>）中说“我们可以对未来做出的一个可靠预测是，变化的步伐将继续加速”，并在1999年的书《精灵机器时代》（<a href="https://amzn.to/3DCsj1U" rel="noopener noreferrer">The Age of Spiritual Machines</a>）、2001年关于加速技术增长的文章“加速回报定律”（The Law of Accelerating Returns）、2005年的书《奇点临近》（<a href="https://amzn.to/3LnvTik" rel="noopener noreferrer">The Singularity is Near</a>）等中再次讨论了这一点。</p>

<p>值得注意的一点是，尽管他早期工作中绝大多数可证伪的预测都是错误的，库兹韦尔每次仍继续使用相同的方法生成新的预测，这让人想起安德鲁·格尔曼（Andrew Gelman）关于预测者反复预测同一事物，即使面对旧预测错误的证据时的讨论。例如，他在2005年的《奇点临近》中指出“S曲线”的存在，即任何特定“事物”的增长不一定是指数级的，但正如他在1990年所做的那样，他得出结论认为指数增长将持续，因为某些新技术必然会被发明出来，从而使指数增长得以继续，并且“加速回报定律适用于所有技术，实际上适用于任何进化过程。它可以在基于信息技术的领域用非凡的精确度描绘出来，因为我们有明确定义的指数（例如，每美元每秒的计算次数，或每克每秒的计算次数）来衡量它们”。</p>

<p>2001年，他使用这种方法绘制图表，然后预测2011年之前人类寿命将是无限的（下面的引文在寿命无限方面并不明确，但如果你阅读整篇文章或他对寿命预测的澄清，就明确了，他说“我不是指基于出生日期的预期寿命，而是指你的剩余预期寿命”）：</p>
<blockquote>
<p>你们中的大多数人（再次使用复数形式）很可能将看到奇点的到来。人类预期寿命的延长是另一个指数级趋势。在18世纪，我们每年为人类寿命增加几天；在19世纪，我们每年增加几周；而现在我们每年增加将近半年。随着基因组学、蛋白质组学、合理药物设计、我们自己器官和组织的治疗性克隆以及生物信息科学相关领域的革命，我们将在十年内每年增加超过一年。</p>
</blockquote>
<p>库兹韦尔将预期发生日期推后了一年多（我最近看到的一次是2016年预测我们将在2029年之前拥有无限的预期寿命），这是库兹韦尔许多预测的特点。</p>

<p>许多人都说库兹韦尔的方法荒谬，因为指数增长在现实世界中不可能无限期持续，但库兹韦尔在他1990年的书《智能机器时代》中解释了为什么他认为这是不正确的：</p>
<blockquote>
<p>这项新技术的一个显著方面是它几乎不使用自然资源。硅芯片使用微量的沙子和其他容易获得的材料。它们使用微不足道的电量。随着计算机变得越来越小，它们所使用的物质资源正在变得与其价值无关紧要。实际上，软件根本不使用任何资源。</p>
</blockquote>
<p>我们正在进入一个自然资源丰富的世界，因为<a href="https://danluu.com/datacenter-power/" rel="noopener noreferrer">资源和电力与计算机无关，到目前为止这并不正确</a>，但对库兹韦尔来说幸运的是，他预测会无限期持续的许多指数和双指数过程，在自然资源限制发挥作用之前很久就停止了，因此这不是库兹韦尔预测错误的主要原因，但如果他的预测不那么不准确的话，这可能会成为主要原因。</p>

<p>在元层面上，库兹韦尔方法的一个问题是，他倾向于“上调”以使增长看起来比实际更快，以符合他的模型。例如，在“加速回报定律”中，我们注意到库兹韦尔基于加速的预期寿命，预测2011年之前人类寿命将是无限的，当时“我们现在每年增加将近半年”。然而，美国的预期寿命增长（根据他的评论，这似乎是库兹韦尔写作的主要内容）<a href="https://pubmed.ncbi.nlm.nih.gov/15008552/" rel="noopener noreferrer">每年仅为0.2年，长寿人群为每年0.1年</a>，而全球预期寿命每年为0.3年。虽然从技术上讲，如果你四舍五入到最近的0.5，你可以将0.3四舍五入到0.5，但当你试图猜测何时会实现无限寿命时，这样做非常不合理，因为世界预期寿命的高增长率主要来自“追赶增长”，即大幅减少导致“非自然”缩短寿命的因素。</p>

<p>如果你想预测高端会发生什么，看看高端寿命的增长更有意义，而高端寿命的增长要慢得多。库兹韦尔“上调”的另一个方式是选择了一种框架，使其看起来我们正在看到预期寿命的极快增长。但是，如果我们简单地绘制自1950年以来的预期寿命随时间变化的图表，我们可以看到增长大致是线性的，并趋向于亚线性（即使在库兹韦尔写作时的2001年切断图表也是如此），其中有一些超线性时期，但最终趋向亚线性。库兹韦尔说他是使用指数等查看增长曲线的粉丝，但在这种情况下，他本可以轻松做到，却选择从空气中挑选一些数字，因为他“标准”的查看增长曲线的方法会导致对预期寿命增长放缓的相当无聊的预测，因此这里有三种上调在起作用：选择一个不合理的乐观数字、将该数字上调，然后选择性地不在时间序列上绘制大量点以描绘库兹韦尔想要呈现的画面。</p>

<p>库兹韦尔的“上调”也是他得出以下预测的原因，其中包括计算机性能/尺寸/成本和经济增长将遵循双指数轨迹。对于计算机成本/晶体管尺寸，库兹韦尔在硅片缩放曲线上绘制了多个点，加上一个来自硅片时代之前的一个非常古老的点，那时晶体管尺寸处于不同的缩放曲线上。然后，他拟合了一条似乎是三次曲线，由于三次曲线“倾向于”在未来具有高增长或高反增长，而前硅片点将三次曲线拟合在过去的很低位置，因此三次曲线拟合必须“倾向于”在未来上升，而库兹韦尔将这种三次增长上调为指数增长。这在库兹韦尔写作时晶体管缩放曲线的支持也非常薄弱。作为一个当时关注<a href="https://en.wikipedia.org/wiki/International_Technology_Roadmap_for_Semiconductors" rel="noopener noreferrer">ITRS路线图</a>的人，我记得ITRS设定了一个预测的摩尔定律缩放曲线，半导体公司竞相超越曲线，短暂地实现了看似超指数的缩放，因为他们会持续超越以摩尔定律为基准的路线图。然而，任何真正了解情况细节的人，或者与半导体工程师交谈过的人，而不是仅仅看缩放曲线，就会知道人们普遍认为超指数缩放是暂时的、不可持续的，并且<a href="https://en.wikipedia.org/wiki/Dennard_scaling" rel="noopener noreferrer">登纳德缩放的终结</a>以及晶体管延迟主导（而非互连延迟主导）的高性能处理器即将出现，这意味着晶体管尺寸的指数缩放不会带来以前伴随着晶体管缩放的历史性计算机性能提升；这种预期如此普遍，以至于当时在本科课程中就被讨论过。任何花最短时间了解半导体缩放的人，在库兹韦尔谈论我们正在进入双指数缩放时代的时，都会知道这些事情，并且会认为我们能幸运地拥有计算机性能的通用单指数缩放就不错了，但由于库兹韦尔观察的是曲线的总体形状而不是机制，这些知识并没有影响他的预测，而且由于库兹韦尔上调现有证据以支持他对增长加速加速的观点，他能够找到一组支持他寻求的曲线拟合的选择性数据点。</p>

<p>我们将在这里讨论的其他未来学家以及附录中讨论的长期主义者中看到这种类型的上调，我们还会反复看到一些相同的主题，特别是指数增长以及技术改进导致技术改进速率加速，从而产生更快的指数增长的观点。</p>

<h3 id="jacque-fresco">雅克·弗雷斯科</h3>

<p>1969年，雅克·弗雷斯科写了《展望未来》（<a href="https://amzn.to/3LmxYuO" rel="noopener noreferrer">Looking Forward</a>）。弗雷斯科声称，通过了解人们未来的价值观，然后由此推导出未来可能的样子，就可以预测未来。弗雷斯科没有描述如何知道人们未来的价值观，并假设人们会拥有他自己的价值观，人们可能会将其描述为60/70年代的嬉皮士价值观。他用来预测未来的另一个主要机制是，未来的人们将更加科学并运用科学方法。</p>

<p>他写道，“科学方法”只被有限地应用，这导致了数千年的缓慢进步。但是，与20世纪不同，在21世纪，人们将摆脱偏见，并在生活的所有领域应用“科学方法”，而不仅仅是在做科学时。人们将完全开放地在生活的各个方面进行实验，并且所有人都将“习惯性地保持开放心态，同时严格坚持所有问题的表述方式必须允许事实核查”。</p>

<p>这将导致所有人都完全了解自身的局限性，并且由于次优的政治和社会结构而导致的不快乐将终结。</p>

<p>弗雷斯科用来推导其预测的第三个主要机制是，计算机将能够解决人们能想象到的几乎任何问题，而制造技术也将类似地发展。</p>

<p>弗雷斯科预测中起作用的每个主要机制都与魔法无异。如果在某个领域能想到一个问题，该机制就能解决它。当时还有其他魔法机制在起作用，通常是当时流行的想法。例如，行为主义和操作性条件反射在当时非常流行，因此弗雷斯科假设整个社会将能够通过操作性条件反射摆脱可能存在的任何社会问题。</p>

<p>尽管弗雷斯科的大多数预测在技术上尚无法判断，因为它们是关于遥远未来的，但对于他已经做出的、时间已到的预测，我没有看到一个准确的预测。</p>

<h3 id="buckminster-fuller">巴克敏斯特·富勒</h3>

<p>富勒<a href="https://news.ycombinator.com/item?id=32462494" rel="noopener noreferrer">以发明测地线圆顶而闻名</a>，尽管测地线圆顶实际上是由瓦尔特·鲍尔斯费尔德（Walther Bauersfeld）在富勒“发明”它们几十年前制造的。富勒也以其各种其他创作而闻名，例如<a href="https://slate.com/technology/2022/08/the-dymaxion-car-the-true-history-of-buckminster-fullers-failed-automobile.html" rel="noopener noreferrer">戴马克松汽车</a>，以及他的未来主义预测。</p>

<p>我找不到包含富勒大量预测列表的优质来源，但我找到了<a href="https://www.youtube.com/watch?v=wPETzKYLkco" rel="noopener noreferrer">这次采访，其中他做出了一些预测</a>。富勒基本上是用词语进行自由联想，通过词语的英文含义来做出预测（例如，传送预测），或者有时是更模糊的联系。</p>

<p>来自视频的预测：</p>
<ul>
<li>我们将能够通过无线电发送人，因为原子有频率，无线电波也有频率，所以有可能拾取我们所有的频率并通过无线电发送它们</li>
<li>欠发达国家（相对于高度发达国家）将能够“通过月球”获得最先进的技术
<ul>
<li>我们将在月球上放人一年，这将需要将相当于一英里直径的地球活动放入一个重500磅的小黑匣子中，以便月球上的人能够在当地像在地球上一样运作</li>
<li>这将导致每个人都意识到他们只需要一个小黑匣子，就不再需要当地的下水道系统、水、电力等。</li>
</ul></li>
<li>人类将完全自动化地摆脱体力劳动
<ul>
<li>中国和印度的生产能力将无关紧要，唯一重要的是谁能“获得”中国和印度的消费者</li>
</ul></li>
<li>将有一个关于财富是什么的现实记账系统，这实际上与能量有关，因为能量守恒定律，这也意味着财富不会恶化和丢失
<ul>
<li>财富只能增加，因为能量既不能被创造也不能被毁灭，当你做一个实验时，你只能学到更多，所以财富只能被创造</li>
<li>这将使整个世界成功</li>
</ul></li>
</ul>

<p>对于那些听说过富勒预测比特币创造的人，最后一个关于财富记账系统的预测就是人们所指的。通常，这样说的人并没有实际听过他陈述整个预测的采访，他们自己也在使用富勒的自由联想方法。比特币来自花费能源来挖掘比特币，而富勒预测未来将有一个基于能源的财富系统，因此富勒预测了比特币的创造。如果你实际听了采访，比特币甚至不接近满足富勒所描述系统的属性，但如果你做富勒式的自由联想，这并不重要。</p>

<p>在本文中，富勒的预测评分比几乎其他任何人都少，所以如果我们有一个大约100个预测的列表，他的准确率会是多少尚不清楚，但我能找到的预测准确率为0%。</p>

<h3 id="michio-kaku">米奇奥·卡库</h3>

<p>在维基百科的未来学家列表中，米奇奥·卡库可能相对知名，因为作为他科学普及工作的一部分，自2006年以来他有一个全美（美国）联合广播节目，并且经常出现在脱口秀节目中，接受新闻机构的采访。</p>

<p>在他的1997年著作《愿景：科学将如何彻底改变21世纪》（<a href="https://amzn.to/3BMxY4g" rel="noopener noreferrer">Visions: How Science Will Revolutionize the 21st Century</a>）中，卡库解释了为什么其他未来学家的预测不太准确以及为什么他的预测与众不同：</p>
<blockquote>
<p>……大多数对未来的预测都失败了，因为它们反映了一个个体古怪、常常狭隘的观点。</p>
<p>《愿景》则不是这样。在撰写众多书籍、文章和科学评论的过程中，我在十年期间有幸采访了来自不同学科的150多位科学家。</p>
<p>基于这些采访，我努力谨慎地界定某些预测将在何时实现，何时不会实现。科学家预计某些预测将在2020年之前实现；其他预测则要到更晚的时候——从2050年到2100年——才会实现。</p>
</blockquote>
<p>卡库还声称他的预测比许多其他未来学家更准确，因为他是物理学家，以物理学家的方式思考问题可以实现准确的未来预测：</p>
<blockquote>
<p>我认为，《愿景》与媒体上的预测之间存在一个重要区别。《愿景》涉及科学家本身的新兴共识，而媒体上的预测几乎完全由作家、记者、社会学家、科幻作家和其他技术的消费者做出，而不是由那些帮助塑造和创造技术的人做出。
……
作为一名研究物理学家，我相信物理学家在预测未来的轮廓方面特别成功。在职业上，我在物理学最基础的领域之一工作，即追求实现爱因斯坦的“万有理论”之梦。因此，我不断提醒自己量子物理学如何触及了许多塑造二十世纪的关键发现。</p>
<p>过去，物理学家的记录是惊人的：我们深入参与引入了许多关键发明（电视、收音机、雷达、X射线、晶体管、计算机、激光、原子弹），解码DNA分子，通过PET、MRI和CAT扫描开辟了探测身体的新维度，甚至设计了互联网和万维网。</p>
</blockquote>
<p>他还特别指出库兹韦尔的预测荒谬，说库兹韦尔对“未来几十年做出了荒谬的预测，从火星度假到消灭所有疾病”。</p>

<p>尽管卡库认为库兹韦尔的预测荒谬，但他的预测依赖于库兹韦尔依赖的一些相同机制。例如，卡库假设商品/原材料价格在不久的将来会暴跌，因为技术的进步会使原材料变得不那么重要，卡库还假设计算机芯片的性能和成本缩放将继续其在70年代和80年代的历史路径。像维基百科列表中的大多数其他未来学家一样，卡库也假设科学进步的步伐将迅速加快，尽管他的理由不同（他引用了量子力学、计算机科学和生物学这些重要领域之间日益增长的协同作用，他说这些领域如此重要，“未来若不具备对所有这些领域的基本工作知识，将很难成为研究科学家”）。</p>

<p>卡库假设紫外线光刻将耗尽动力，我们将不得不切换到X射线或电子光刻，而光刻也会耗尽动力，需要我们切换到完全不同的计算机基底（光学、分子或DNA）以保持性能和缩放的轨道，但其他基本计算基底的进步没有迅速实现，导致卡库的预测未能实现。卡库赋予他认为具有“量子”效应的东西非常高的权重，这就是为什么例如他认为微处理器到2020年将过时（它们不是“量子”的），而光纤不会过时（它们依赖于“量子”机制）。尽管卡库批评其他未来学家在对其讨论的主题没有真正理解的情况下做出预测，但不清楚卡库是否对讨论的许多主题有更好的理解，尽管作为物理学家，卡库具有更相关的背景知识。</p>

<p>上述未实现的假设组合导致卡库在《愿景》中的预测准确率相当低。</p>

<p>我没有读完《愿景》，但我读的那部分书（从开头到中间某处，以避免挑选）的预测准确率是3%（如果你给我完全分的那个预测一半分数，可以说是6%）。他做出了许多我没有评分的预测，他在其中说某些事情“可能”会发生。这样的预测当然是无法证伪的，因为无论事件是否发生，该陈述都是正确的。</p>

<h3 id="john-naisbitt">约翰·奈斯比特</h3>

<p>任何经常逛旧书店打折区的人都会在《大趋势》（<a href="https://amzn.to/3qMyokJ" rel="noopener noreferrer">Megatrends</a>）的封面上看到这个名字，它肯定与李·艾柯卡（Lee Iacocca）的自传一样，是最常见的打折区填充物之一。</p>

<p>奈斯比特声称他能够通过对报纸进行“内容分析”来准确预测未来，他说这在二战期间曾用于提供深刻的见解，并且此后被情报界广泛使用，但直到他这样做之前从未在商业上应用过。奈斯比特解释说这之所以有效，是因为报纸的空间是固定的（显然报纸不能被创造或毁灭，也不能决定印刷更多或更少的新闻，或者决定印刷内容的编辑转变不会在更大社会中反映相同的改变）：</p>
<blockquote>
<p>为什么我们如此确信内容分析是监测社会变化的有效方法？简单地说，因为报纸上的新闻空间是一个封闭系统。出于经济原因，报纸上用于新闻的空间量随时间推移不会发生显著变化。因此，当引入新内容时，其他内容或内容组合必须被省略。不减去就无法增加。这是封闭系统中的强制选择原则。</p>
</blockquote>
<p>不幸的是，几乎无法判断奈斯比特的预测，因为他几乎只处理模糊的、像占星术一样的预测，这些预测很难被判断为正确或错误。如果你只是阅读《大趋势》来感受每章的基调，而不试图挑出单个预测，有些章节看起来相当好，例如“工业社会 -&gt; 信息社会”，但即使你非常慷慨地给他的模糊预测评分，有些章节也绝对喜忧参半。这无法与本文中的其他未来学家进行比较，因为让模糊的预测听起来大致正确比让精确的预测正确要容易得多，但即便如此，如果是为了感受未来可能的方向而阅读，奈斯比特的预测比讨论的任何其他未来学家都要准确得多。</p>

<p>话虽如此，就我在他书中的阅读范围而言，我能找到的唯一具体预测是错误的，因此如果你想让奈斯比特与这里讨论的其他未来学家进行可比的评分，你可能会说他的准确率是0%，但误差范围很广。</p>

<h3 id="gerard-k-o-neill">杰勒德·K·奥尼尔</h3>

<p>奥尼尔有两本相对知名的非虚构类未来主义书籍，《2081》（<a href="https://amzn.to/3dtGtHX" rel="noopener noreferrer">2081</a>）和《技术优势》（<a href="https://amzn.to/3do9bde" rel="noopener noreferrer">The Technology Edge</a>）。《2081》写于1980年，预测了此后100年的未来。《技术优势》讨论了奥尼尔认为美国在1983年需要做什么才能避免被日本淘汰。</p>

<p>奥尼尔花在讨论为什么以前的未来学家错了的篇幅比任何其他讨论中的未来学家都多。奥尼尔指出，“大多数[未来学家]高估了社会和政治变革对世界的改变程度，低估了技术变革的力量”，并引用吉卜林（Kipling）、凡尔纳（Verne）、威尔斯（Wells）、霍尔丹（Haldane）和贝拉米（Ballamy）作为这样做的例子。奥尼尔还说，“科学家往往高估了重大科学突破的机会，低估了在现有知识范围内直接发展的影响”，并再次引用霍尔丹。奥尼尔还引用太空飞行作为过去未来学家的一个重大失误，说他们往往低估了太空飞行发展的速度。</p>

<p>奥尼尔还说，即使不知道变革的确切机制，也有可能预测未来。例如，他声称即使内燃机没有被发明，汽车也可以被安全地预测，因为蒸汽也可以工作。但他接着说，有些事情是不可预测的，比如收音机、电视和电子通信，尽管这些的基础发现于1865年，并且基础发现与应用之间的时间间隔“通常相当长”，引用量子力学到集成电路是30-50年，相对论到超光速旅行是100多年，核能的发明至今没有“深刻影响”是50多年。</p>

<p>我认为奥尼尔从未真正令人信服地解释过为什么他的预测属于“汽车”类型。相反，他依靠做与他认为别人错误相反的事情。结果是他预测太空飞行的巨大进步，说我们应该期望到2081年有大规模的太空旅行和殖民，预示着2000年之前无线传输能源（指从卫星发射的能量）和2025年之前星际探测器（大概是不同于1977年发射的旅行者探测器的类别）。</p>

<p>1981年，他说“一支由1990年代的可重复使用飞行器组成的机队，数量远少于当今世界商用喷气式运输机队，将足以每年为数亿人提供进出太空的运输”，预测比美国宇航局航天飞机更先进的东西不久后就会生产出来。继续这种进步，“到2010年左右，将有许多太空殖民地存在，每年都有许多新的在建”。</p>

<p>奥尼尔的大部分预测是针对2081年的，但他确实偶尔做出1981年之前的预测。我能找到的所有可证伪的预测都是错误的，准确率约为0%，但误差范围相当大。</p>

<h3 id="patrick-dixon">帕特里克·迪克森</h3>

<p>迪克森以撰写《未来智慧》（<a href="https://amzn.to/3Br9JqK" rel="noopener noreferrer">Futurewise</a>）而闻名，但他有很多关于未来的预测书籍。在本文中，我们只看《未来智慧》，因为它是迪克森最具预测导向的书，并且足够老，我们应该能够对他相当多的预测做出判断（《未来智慧》来自1998年；他另一本明显的候选书是<a href="https://amzn.to/3qKT1O6" rel="noopener noreferrer">《几乎所有事情的未来》</a>，来自2015年，展望一个世纪）。</p>

<p>与本文中的大多数其他未来学家不同，迪克森在《未来智慧》中并没有明确说明你为什么应该信任他的预测，尽管他在致谢中隐含地这样做了，他提到与许多非常重要的人有过互动。</p>
<blockquote>
<p>我非常感谢数百位高级管理人员，他们通过参与“未来的六个面孔”演讲塑造了这本书。内容是根据他们自身的经验铸造的。</p>
</blockquote>
<p>尽管他没有明确提到自己，但他说商业成功将来自于倾听那些有远见的人：</p>
<blockquote>
<p>那些经常正确的人会发大财。未来的趋势寻找将与七八十年代大不相同，那时一切更加确定。在一个全球化的市场中，有太多的变量，反向投影和正向投影无法可靠地工作……这就是为什么经济学家在涉及新技术时不能成为好的未来学家，以及为什么这么多大型公司的董事会在涉及2000年以后的思维量子跃迁时如此混乱的原因。</p>
<p>第二千年思维永远不会让我们到达那里……一家财富1000强公司的资深董事会成员最近告诉我：‘我很高兴我要退休了，这样我就不必面对这些决定’……‘我们能做什么？’另一位高管宣称……</p>
</blockquote>
<p>后来，在《几乎所有事情的未来》中，迪克森列出了他所说的在写《未来智慧》时有效的方法，这些方法“经受住了17年以上的考验”。迪克森说：</p>
<blockquote>
<p>所有可靠的、长期的预测都是基于强大的超级趋势，这些趋势在过去30年里推动了深刻、一致且相对可预测的变化。这些趋势是每个精心构建的公司战略和政府政策的基础
……
这些更广泛的趋势对像我这样的大多数趋势分析师来说已经显而易见一段时间了，并且在过去20-30年里得到了很好的描述。它们的发展比繁荣与萧条，或社会时尚要慢得多。</p>
</blockquote>
<p>并列出了趋势，例如：</p>
<ul>
<li>大多数大规模生产物品的生产成本下降</li>
<li>对环境/可持续性的关注增加</li>
<li>数字技术、电信和网络的价格下降</li>
<li>各种无线/移动设备的快速增长</li>
<li>全球公司规模越来越大，合并、整合</li>
</ul>
<p>迪克森拒绝提及他预测的未能实现的趋势（例如，他预测日益增长的部落主义将意味着大部分新财富由20人或更少员工的小公司创造，这些公司大多是家族拥有或控制的，或者他预测“旧经济学”的终结意味着我们可以无限期地实现高经济增长、低失业率且没有通胀压力），或者趋势进展导致迪克森的预测大错特错的情况，当基于指数趋势做出预测时，这是一个常见问题，因为增长率的相对较小的不准确可能导致最终状态的非常大的变化。</p>

<p><a href="https://web.archive.org/web/20210814150424/https://www.globalchange.com/the-future-of-almost-everything-new-book-by-patrick-dixon.htm" rel="noopener noreferrer">迪克森的网站充满了对他的赞扬，隐含或明确地声称他是伟大的未来预测者</a>，以及更一般的声明，如“帕特里克·迪克森被评为当今在世最具影响力的20位商业思想家之一”。</p>

<p>回到《未来智慧》，迪克森严重依赖于“第二千年思维”和“第三千年思维”之间的鲜明分野这一概念，这在他的文本中反复出现。像几乎所有其他讨论中的人一样，迪克森也从许多现有趋势中推断出未能实现的预测，例如，他观察到电话线路成本的下降和价格的降低，预测到2005年人们家里将会有大量的电话线路，并且屏幕变薄意味着到2005年我们将广泛使用“薄如纸的显示片”。这种推断有时有效，迪克森10%的总体准确率与其他讨论中的“未来学家”相比相当好。</p>

<p>然而，当迪克森在我有一定理解的领域解释他的推理时，他似乎在<a href="https://danluu.com/cocktail-ideas/" rel="noopener noreferrer">流行词水平</a>上运作，因此当他做出正确的判断时，通常出于错误的原因。例如，迪克森说软件总是有缺陷的，这似乎是正确的，至少到目前为止是这样。然而，他的理由是新计算机发布得如此频繁（他说“不到20个月”——指摩尔定律中的18个月时间线），编写好的软件需要很长时间（“至少20年”），所以程序员总是忙于为新一代机器重写软件（由于这本书的年代，他使用了“为奔腾芯片编写全新代码”作为例子）。</p>
<p>大多数缺陷，甚至作为缺陷比例的几乎任何缺陷，都不是由于程序员重写现有代码以在新的CPU上运行造成的。如果你真的仔细看，你可能会看到诸如<a href="https://danluu.com/android-updates/" rel="noopener noreferrer">安卓设备由于更新和向旧硬件反向移植更改的困难而有许多安全缺陷</a>之类的事情，但这些类型的缺陷在所有缺陷中只占很小一部分，而且也不是迪克森真正谈论的内容。</p>

<p>同样，关于计算机备份未来将如何完成，迪克森基本上正确地说，家庭工作者将容易受到数据丢失的影响，认真保存数据的人将在线备份数据，“在线备份数据到其他城市的计算机作为终极安全”。</p>
<p>但迪克森陈述的理由是工作站已经有大的磁盘容量（&gt;=2GB），而软盘容量没有跟上（&lt;2MB），因此备份需要数千张软盘，这显然是荒谬的。然而，即使在当时，Zip驱动器（每张可移动磁盘100MB）也很常见，尽管没有流行起来，制造Zip驱动器的同一家公司也制造了1GB的“Jaz”驱动器。当然，磁带备份在当时也被使用，今天仍然使用。这种趋势一直持续到今天；大型可移动磁盘可用，而且我认识的很多人都在可移动磁盘上传输或备份大量数据。大多数人不进行磁盘/磁带备份的原因并不是备份本地计算机需要数千张磁盘（如果你看看人们在家里通常使用的计算机，大多数人都可以在每个故障域上用一个可移动磁盘备份他们的数据，甚至在一个磁盘上保留多个版本），而是因为在线/云备份更方便。</p>

<p>由于迪克森的推理是错误的（至少在我足够了解主题以了解推理适用性的情况下），似乎当迪克森正确时，不可能是因为陈述的原因，迪克森要么是巧合正确，要么是因为他看到了更广泛的趋势，并为预测提出了错误的合理化。但是，根据以上所述，要真正正确预测趋势随时间的增长率非常困难，因此如果不了解运作的机制，你也可以说基于一些粗略趋势而实现的预测也是巧合正确。</p>

<h3 id="alvin-toffler-heidi-toffler">阿尔文·托夫勒/海蒂·托夫勒</h3>

<p>像这个列表上的大多数其他人一样，<a href="https://www.denverpost.com/2016/06/29/author-alvin-toffler-dies/" rel="noopener noreferrer">托夫勒声称有一些重大的预测成功</a></p>
<blockquote>
<p>托夫勒夫妇在其网站上声称，他们预见了苏联的解体、德国的统一和亚太地区的崛起。他在《人民日报》的采访中说，《未来的冲击》预见了有线电视、录像、虚拟现实和美国家庭规模的缩小。</p>
</blockquote>
<p>在本文中，我们将看托夫勒最著名的著作，写于1970年的《未来的冲击》（<a href="https://amzn.to/3xxaKMW" rel="noopener noreferrer">Future Shock</a>）。</p>
<p>根据许多来源，阿尔文·托夫勒的主要作品是与海蒂·托夫勒合著的。在书中，海蒂·托夫勒被承认为帮助很多的人，但不是作者，尽管其他地方有关于合著的说法。在本节中，我将托夫勒视为单数，但你可能想在心里替换成复数。</p>
<p>托夫勒声称，我们不仅应该通过理解过去来理解现在，还应该通过理解未来来理解现在：</p>
<blockquote>
<p>以前，人们研究过去以阐明现在。我转动了时间之镜，确信一个连贯的未来图景也能为我们提供关于今天的宝贵见解。我们将发现，如果不把未来作为智力工具，我们将越来越难以理解我们的个人和公共问题。在接下来的页面中，我有意利用这个工具来展示它能做什么。</p>
</blockquote>
<p>托夫勒通常做出模糊的、摇摆不定的陈述，因此真的无法对托夫勒的具体预测进行评分，因为给出的预测太少了。然而，托夫勒强烈暗示过去的指数趋势预计将持续甚至加速，并且由此引起的快速变化将导致“未来的冲击”，因此书名如此：</p>
<blockquote>
<p>我创造了“未来冲击”这个术语来描述我们在个体身上施加的破坏性压力和迷失方向，方法是在短时间内让他们承受过多的变化。对这个概念着迷后，我花了五年时间访问了数十所大学、研究中心、实验室和政府机构，阅读了无数文章和科学论文，采访了数百位关于变化、应对行为和未来的不同方面的专家。诺贝尔奖得主、嬉皮士、精神病学家、医生、商人、专业未来学家、哲学家和教育家表达了他们对变化的担忧、对适应的焦虑、对未来的恐惧。从这次经历中，我带着两个令人不安的信念离开了。首先，很明显，未来的冲击不再是遥远的潜在危险，而是一种真正的疾病，越来越多的人已经在遭受这种疾病。这种心理生理疾病可以用医学和精神病学术语来描述。它是变化的疾病
……
真诚的知识分子勇敢地谈论“为变化而教育”或“为未来做好准备”。但我们几乎不知道如何去做
……
因此，这本书的目的是帮助我们接受未来——通过加深我们对人类如何应对变化的理解，来帮助我们更有效地应对个人和社会变化。</p>
</blockquote>
<p>托夫勒到处使用的大锤是指数增长的推断，暗示这预计将持续。关于从曲线推断的一般概念，托夫勒的立场与库兹韦尔非常相似：如果你能在图表上看到趋势，你就可以用它来预测未来，而技术加速新技术发展的能力将导致创新比你天真预期的更快发生：</p>
<blockquote>
<p>绘制在图表上，代表上一代人进步的线将垂直跃出页面。无论我们检查旅行的距离、达到的高度、开采的矿产，还是利用的爆炸威力，同样的加速趋势是显而易见的。模式，在这里和成千上万的其他统计系列中，是绝对清晰和明确的。几千年或几个世纪过去了，然后在我们这个时代，突然打破了限制，出现了惊人的激增。原因在于技术以自身为食。技术使更多技术成为可能，如果我们稍微看一下创新过程，就能看到这一点。技术创新包括三个阶段，相互连接成一个自我强化的循环……如今有证据表明，这个循环中每个步骤之间的时间已经缩短。因此，经常被指出的是，有史以来90%的科学家现在还活着，并且新的科学发现每天都在被发现，这些新想法比以往任何时候都更快地被付诸实践。</p>
</blockquote>
<p>书中第一个N个主要例子是：</p>
<ul>
<li>人口增长率（倍增时间为11年），这将不得不要求重大变化</li>
<li>经济增长（倍增时间为15年），这将增加人们拥有的东西的数量（这特别表述为东西的数量而不是财富）
<ul>
<li>强烈暗示这将持续至少70年</li>
</ul></li>
<li>旅行速度；未说明倍增时间，但读者可以从以下几点进行推断：数百万年前人类奔跑速度、1880年代的每小时100英里、1938年的每小时400英里、1958年的每小时800英里、此后不久的每小时4000英里（绕地球轨道时每小时18000英里）</li>
<li>从构思想法到应用的时间缩短，用来支持增长将加速的观点</li>
</ul>
<p>正如我们刚刚在讨论迪克森、库兹韦尔等人时指出的那样，通过推断指数增长来预测未来是充满风险的。托夫勒以某种方式实现了反预测的壮举，即标记了一系列即将停止的趋势，其中一些在托夫勒写作时已经有失败的迹象。</p>
<p>托夫勒从以上内容推断，预测所有事物的半衰期都会变短，这将颠覆社会运作的方式。</p>
<p>例如，公司和政府将在1995年至2020年之间用“特别机构”（adhocracies）取代官僚机构。随着公司规模扩大，人们担心自己会成为齿轮，但这种担忧是过时的，因为在特别机构中，自上而下的指挥控制概念将消失，被一切加速的步伐所淘汰。虽然一些公司的自上而下指导确实比托夫勒时代预期的要少，但许多公司却更多，这得益于技术允许雇主比以往任何时候都更严格地监视员工，使人们比以往任何时候都更像齿轮。</p>

<p>另一个例子是，托夫勒预测人类对海洋的殖民，“新亚特兰蒂斯”，“在公元2000年之前很久”。</p>
<p>法比安·吉森（Fabian Giesen）指出，无论托夫勒预测的准确性如何，文卡泰什·拉奥（Venkatesh Rao）的<a href="https://www.ribbonfarm.com/2012/05/09/welcome-to-the-future-nauseous/" rel="noopener noreferrer">《欢迎来到令人作呕的未来》</a>解释了为什么“未来的冲击”在技术发展非常快的领域没有发生。</p>

<h3 id="people-from-the-wikipedia-list-who-weren-t-included">维基百科列表中未包含的人</h3>
<ul>
<li>劳瑞·安德森（Laurie Anderson）
<ul>
<li>我无法轻易找到她的预测，除了据称预测了9/11的一些歌词，但非常“占星术”</li>
</ul></li>
<li>阿瑟·哈金斯（Arthur Harkins）
<ul>
<li>他的维基百科条目后来因不显著原因被移除，在当时已被标记为不显著</li>
</ul></li>
<li>斯蒂芬·霍金（Stephen Hawking）
<ul>
<li>我能找到的预测通常太遥远无法评分，而且更像是建议人们应该做什么，而不是预测。例如，上面的维基百科未来学家列表链接到一项2001年的预测，如果不进行基因工程改造以使人类跟上步伐，人类将被计算机/机器人超越，还链接到一项2006年的预测，人类需要向其他星球扩张以保护物种</li>
</ul></li>
<li>索基尔·克里斯滕森（Thorkil Kristensen）
<ul>
<li>我无法轻易找到克里斯滕森的一套英语预测。索基尔·克里斯滕森与《增长的极限》有关联但不是作者，这是一本1970年代的反增长论战作品</li>
</ul></li>
<li>大卫·西尔斯（David Sears）
<ul>
<li>当时或现在都不够显著，没有维基百科页面</li>
</ul></li>
<li>约翰·泽赞（John Zerzan）
<ul>
<li>泽赞更像是一个因其政治观点而呼吁社会变革的人，而不是一个试图预测未来的“未来学家”</li>
</ul></li>
</ul>

<h3 id="steve-yegge">史蒂夫·耶格</h3>
<p>正如我在开头提到的，维基百科列表中没有一个未来学家有非常准确的预测，所以我们将看看来自其他来源的其他几个人，他们通常不被视为未来学家，看看他们的排名如何。</p>
<p>我们<a href="https://danluu.com/yegge-predictions/" rel="noopener noreferrer">之前在这里看过耶格的预测</a>，这些预测写于2004年，通常是关于未来5-10年的，有些更远。有九个预测（技术上是十个，但有一个不是真正的预测）。如果按照书面形式评分，就像未来学家评分一样，我会给这些评分4.5/9，大约50%。</p>
<p>你可能会说这不公平，因为耶格预测的是相对较近的未来，但如果我们看看未来学家对相对较近未来的预测，他们的准确率通常远低于50%，所以我认为以某种方式比较数字并不公平。</p>
<p>如果你想给这些评分，就像人们通常给未来学家评分一样，他们因基本把握方向正确而获得分数，那么我会说耶格的得分应该在7/9到8/9之间，取决于他在一个问题上获得多少部分分数。</p>
<p>如果你想采取更整体的“如果耶格的愿景是正确的，世界会是什么样子，与我们今天的世界相比”的方法，我认为耶格也做得相当好，最大的失误是基于Lisp的语言还没有接管世界，尽管Clojure取得了成功。这与这里的未来学家有很大不同，他们通常预测了许多没有实现的巨大变化，例如，如果我们看看库兹韦尔对世界的愿景，到2010年，我们本该有自动驾驶汽车、治疗瘫痪的“疗法”、广泛使用AR等，到2011年，我们将有无限的预期寿命，到2019年，我们将广泛使用纳米技术，包括计算机已从晶体管转向纳米管，有效“缓解”失明和失聪，相当广泛部署完全现实的VR，可以通过真实的身体刺激模拟性爱，普遍的自动驾驶汽车（再次预测），全新的艺术和音乐领域等，以及所有这些暗示的东西，这是一个与我们实际生活的世界非常不同的世界。</p>
<p>如果我们看看其他未来学家，我们也会看到类似的情况，他们预测了诸如住在地下、生活在水下等事情；大多数人预测了许多真正改变社会的革命性变化，其中一些实现了。耶格则预测了许多适度的变化（以及一些变化比许多人预期的要慢的领域），并且他预测的领域变化比他预期的慢，但只慢了一点。</p>
<p>耶格将他的方法描述为：</p>
<blockquote>
<p>如果你读得很多，你会开始发现趋势和暗流。你可能看到人们更频繁地谈论某些你认为即将兴起的主题或技术，或者你只是模糊地感觉到行业中正在发生某种临界点。或者在你的公司，就这件事而言。</p>
<p>我似乎有很多最好的见解是在写我已经知道的东西时想到的。我想到了写关于看似明显和不可避免的趋势可能有助于我浮现出一些不那么明显的趋势。所以我决定根据我注意到的一些趋势做出一些随机预测，看看会出现什么。这基本上是一种挖掘见解的心智练习</p>
<p>在这篇文章中，我将根据今年阅读科技内容时感受到的暗流做出十个预测。在我写这段话时，我还不知道我的十个预测会是什么，除了第一个。这是一个简单、明显的预测，只是为了启动创造性思维过程。然后我会抛出另外九个，当它们在我脑海中出现时，我会尽力证明它们合理，即使它们听起来很疯狂。</p>
</blockquote>
<p>他并不是真的在尝试生成最好的预测，但通过依赖他的领域知识和一些关于他所见过的事物的直觉，仍然做得相当好。</p>
<p>在关于耶格预测的文章中，我们还注意到他在其预测文章之外做出了许多成功的预测：</p>
<blockquote>
<p>史蒂夫还有一些文章不是明确关于预测的，然而却对事物的现状做出了相当可靠的预测，早在2004年就写好了。有《这不是软件》，它超前于其时代很多年，关于人们如何编写“软件”，编写服务器应用程序如何真正不同于编写包装软件，以至于使许多以前可靠的建议过时，比如乔尔（Joel）关于重写的格言，以及面向服务的架构是什么样的；《德尔菲的谷歌》（同样来自2004年）正确预测了ML和AI的重要性以及谷歌对ML的大量投资；一次旧采访中，他预测“网络应用程序编程将逐渐成为最重要的客户端编程。我认为它将基本淘汰所有其他客户端工具包：GTK、Java Swing/SWT、Qt，当然还有所有特定于平台的工具包，如Cocoa和Win32/MFC”；等等。史蒂夫内部的一些谷歌博客文章也做出了有趣的预测，但据我所知，这些是保密的。</p>
</blockquote>
<p>耶格的许多预测在当时会被认为相当不明显，而且他在其他预测中的成功率似乎仍然相当高（尽管我并没有试图全面找到并对它们评分，我抽样了他的一些旧文章，发现总体成功率与他预测文章中的相似）。</p>
<p>对于耶格和其他为了让我们看到一些准确预测而选择的预测者，当然存在选择这些预测者时存在幸存者偏差的担忧。我怀疑耶格的情况并非如此，因为在我第一次注意到他似乎有准确预测之后，他继续保持准确，所以这不仅仅是因为我在事后挑选了一个有一段时间运气好的人。此外，特别是他在谷歌内部的G+评论中，做出了相当高维度的评论，最终因为他说的原因而正确，这提供了比仅仅连续抛硬币更多关于他的推理准确性的信息。这种关于推理深度的评论不适用于下面的卡普兰，因为我没有评估卡普兰的推理，但适用于1990年左右的微软领导层。</p>

<h3 id="bryan-caplan">布莱恩·卡普兰</h3>
<p>布莱恩·卡普兰报告说他的记录是23/23 = 100%。他比我们研究过的任何人都更精确地指定了他的预测，并试图给出一个精确的赌注，该赌注将很容易裁决，以及赔率。</p>
<p>卡普兰开始做出预测/赌注的时间大约与“赌注是对废话征税”这一概念流行的时间相同（意思是很多人愿意说任何话，但如果被要求进行真正的赌注，他们会安静下来，那些不这样做的人如果做出了糟糕的真赌注就会付出真正的代价），卡普兰似乎有一个策略，就是充当废话的征税员，他通常接受人们不应该做的赌注的安全面。<a href="https://statmodeling.stat.columbia.edu/2022/08/11/bets-as-forecasts-bets-as-probability-assessment-difficulty-of-using-bets-in-this-way/" rel="noopener noreferrer">安德鲁·格尔曼说</a>：</p>
<blockquote>
<p>卡普兰的赌注是一个有趣的混合。第一个赌注他提供了1比100的赔率，所以他赢了并不奇怪，但大多数赌注都是平赔。有几个他运气很好（例如，他在2008年打赌在2020年1月1日之前没有大国会离开欧盟，所以他在那个月仅仅幸免于难），但是，嘿，运气好是可以的，而且无论如何，即使他只赢了23场中的21场，那也是一个令人印象深刻的记录。</p>
<p>在我看来，卡普兰的诀窍在于，他对选择什么投球有良好的判断力。人们带着一些强烈的、不切实际的观点来找他，他善于将这些转化为赌注。用扑克术语来说，他等到他拿到坚果牌，或者接近坚果牌。23场赢了23场……那是一个伟大的记录。</p>
</blockquote>
<p>我认为这样做有很大的价值，无论是从一般的“赌注是对废话征税”的意义上讲，还是更具体地说，如果你非常相信有人试图接受错误赌注的另一面，并且有良好的判断力，那么知道卡普兰式的赌徒接受了这个立场，即使你在主题上没有特别的专业知识，也会给你关于赌注的不错信号。例如，如果你看看我的赌注，尽管<a href="https://twitter.com/danluu/status/1554559905167597568" rel="noopener noreferrer">我有时会与明显错误的立场对赌</a>，但我更经常地<a href="https://twitter.com/danluu/status/1555343411229536257" rel="noopener noreferrer">接受我有很大可能输的赌注</a>，所以仅仅知道我接受了一个赌注提供的信息比知道卡普兰接受了一个赌注要少得多。</p>
<p>但是，当然，站在卡普兰的一方并不是万无一失的。正如格尔曼指出的，卡普兰至少有一次运气好，而且卡普兰似乎也可能会输掉<a href="https://standupeconomist.com/2021-update-on-my-global-warming-traffic-light-bet-with-bryan-caplan-and-alex-tabarrok/" rel="noopener noreferrer">卡普兰和塔巴罗克对阵鲍曼的全球温度赌注</a>。对于这个特定的赌注，你也可以认为他应该输，因为他接受了3比1的赔率，但很多人会认为3比1的赔率远远不够长，不足以接受这个赌注。</p>
<p>卡普兰迄今为止使用的方法永远不会在重大变化非常可能发生之前产生积极的预测，因此这种方法无法像耶格、盖茨或其他接受更狂野赌注的相对准确的预测者那样，真正给你一个关于未来会是什么样子的愿景。</p>

<h3 id="bill-gates-nathan-myhrvold-ms-leadership-circa-1990-to-1997">比尔·盖茨/内森·梅尔沃德/1990至1997年左右的微软领导层</h3>
<p><a href="https://danluu.com/us-v-ms/" rel="noopener noreferrer">由于针对微软的诉讼，一些备忘录被公之于众，这些备忘录阐述了微软高管对世界将如何发展（无论是否有微软参与）的愿景</a>。这些备忘录没有列出带有时间线的具体预测，因此无法像本文对未来学家预测的评分方式进行评分。如果根据这些预测对未来的愿景的准确性进行评分，我会给它们与史蒂夫·耶格（得分为7/9或8/9）类似的评分，但这些预测的雄心要大得多，因此在控制预测范围的情况下，它们看起来更加令人印象深刻。</p>
<p>与我们讨论的未来学家相比，在多个方面，这些预测要详细得多（因此在给定准确度水平上更加令人印象深刻）。一个是微软高管对讨论中的事物及其相互影响有更深的理解。对于“我们”的未来学家，他们经常在高层次上讨论事物，当他们详细讨论事物时，他们会做出一些陈述，表明他们并不真正理解该主题，并且常常不知道他们写的词是什么意思。那个时代的微软高管显然对问题的运作机制有深刻的理解，这使他们能够做出我们的未来学家不会做出的详细预测，例如，虽然FTP和IRC等协议将继续使用，但互联网的近期未来是基于TCP的HTTP，浏览器将成为像Windows一样的“平台”，一个比任何操作系统都更重要和更庞大的平台（除非微软成功地采取行动阻止这一点，尽管微软高管预见到了可能导致微软未能拥有互联网的确切机制，但它并未成功）。微软领导层利用这种理解水平对我们的未来学家讨论的更大的事物做出预测，例如工作的性质以及这将如何改变。</p>
<p>真正理解问题的运作机制，而不仅仅是在典型的未来学家流行词水平上理解主题，使得微软领导层能够对未来的可能样子做出相当好的猜测。</p>
<p>关于盖茨为理解情况所付出的努力的一个有趣故事，<a href="https://www.joelonsoftware.com/2006/06/16/my-first-billg-review/" rel="noopener noreferrer">请参见乔尔·斯波尔斯基（Joel Spolsky）关于他第一次比尔·盖茨评审的故事</a>：</p>
<blockquote>
<p>比尔转向我。</p>
<p>我注意到我的规范文件边距上有注释。他读了第一页！</p>
<p><i>他读了我规范文件的第一页并在边距上写了小注释！</i></p>
<p>考虑到我们大约在24小时前才给他规范文件，他一定是前一晚读的。</p>
<p>他在问问题。我在回答。它们相当简单，但我根本记不得它们是什么，因为我无法停止注意到他在翻阅规范文件……</p>
<p><i>他在翻阅规范文件！</i> [冷静点，你是个小女孩吗？]</p>
<p>…… [编辑：省略号来自原始文档] 每一页边距上都有注释。他读完了整个该死的东西并在边距上写了注释。</p>
<p>他读完了所有东西！[天哪，尖叫！]</p>
<p>问题变得更难、更详细。</p>
<p>它们似乎有点随机。现在我已经习惯把比尔当作我的伙伴了。他是个好人！他读了我的规范文件！他可能只是想问我几个关于边距注释的问题！我会为他的每个注释在错误跟踪器中打开一个错误，并确保它很快得到解决！</p>
<p>最后是致命的问题。</p>
<p>“我不知道，你们，”比尔说，“有人真的在研究如何做到这一点的所有细节吗？比如，所有那些日期和时间函数。Excel有那么多日期和时间函数。Basic会有相同的函数吗？它们都会以相同的方式工作吗？”</p>
<p>“是的，”我说，“除了1900年的1月和2月。”</p>
<p>沉默。
……
“好吧。嗯，干得好，”比尔说。他拿起了他标记过的规范文件副本……离开了</p>
</blockquote>
<p>盖茨（以及其他一些微软高管）对情况了解得非常详细，同时还在考虑大局问题。</p>
<p>另一个帖子的主题是，微软领导层如何拥有比传统竞争对手（Novell、IBM、AT&amp;T、Yahoo、Sun等）更有效的未来愿景，以及这如何导致微软成为一家价值2万亿美元的公司，而他们的竞争对手最多变得无关紧要，大多数甚至没有成功变得无关紧要，就不再存在。阅读旧的微软备忘录，很明显微软确实在密切关注竞争对手在做什么，他们常常对竞争对手的领导层效率低下感到惊讶，例如，关于Novell，比尔·盖茨说“我们的传统竞争对手刚刚开始涉足互联网。考虑到网络对Novell的重要性，它缺席得令人惊讶”；盖茨注意到当时的Novell CEO弗兰肯伯格（Frankenberg）似乎理解互联网的重要性，但弗兰肯伯格直到1994年才加入Novell，并于1996年离开，在诺瓦德（Noorda）领导下，他在Novell的大部分时间都在逆转公司采取的方向，当弗兰肯伯格“辞职”两年后，这并没有给Novell留下一个连贯的立场或计划。</p>
<p>在很多方面，讨论当时科技高管认为未来会是什么样子以及什么道路通向成功，比研究基本上不理解他们讨论主题的未来学家更有趣，但我开始这篇文章是为了研究未来学家对他们讨论的主题理解得有多好，而我不知道，事先，他们对主题的理解和由此产生的预测准确性会如此之差。</p>

<h4 id="common-sources-of-futurist-errors">未来学家错误的常见原因</h4>
<ul>
<li>没有从错误中学习
<ul>
<li>好的预测者倾向于认真研究失败的过去预测并试图校准</li>
</ul></li>
<li>基于<a href="https://danluu.com/cocktail-ideas/" rel="noopener noreferrer">鸡尾酒会想法水平对主题的理解</a>进行推理
<ul>
<li>好的预测者倾向于深入探讨想法</li>
</ul></li>
<li>推行一个或几个“宏大理念”</li>
<li>通常对未来具有高度确定性
<ul>
<li>更糟糕的是：假设缩放曲线，尤其是指数缩放曲线具有高度确定性</li>
</ul></li>
<li>万能药思维</li>
<li>只看到技术变革的积极面（或消极面）</li>
<li>从无证据支持的假设开始</li>
</ul>
<h5 id="not-learning-from-miss">没有从错误中学习</h5>
<p>我们在这篇文章中研究的未来学家倾向于给自己打很高的分，并且事后通常声称自己是伟大的未来预测者，以至于他们甚至会告诉你如何准确地预测未来。然而，在给他们评分后，最准确的未来学家（在做出足够具体、可以评分的预测者中）的准确率只有10%，慷慨的评分给了他们因错误预测机制而偶然实现的预测的分数（严格阅读他们的许多预测会降低准确率，因为他们说预测会因为他们的预测机制而实现，这是错误的，使得预测错误）。</p>
<p>这些未来学家使用了两个技巧才能做出如此崇高的声明。首先，他们中的许多人做出模糊的预测，然后如果任何与预测模糊相似的事情发生，就声称成功。其次，他们几乎都做出大量的预测，然后只计算那些成功的预测。看待4%准确率的一种方式是，你不应该依赖那个人的预测。另一种方式是，如果他们做出了500个预测，他们是一个伟大的预测者，因为他们做出了20个准确的预测。因为几乎没有人会费心去检查一份预测列表来计算总体准确率，当一个人这样做时，做出大量的预测然后挑选出那些准确的是成为一个著名未来学家的好策略。</p>
<p>但如果我们想找出如何做出准确的预测，我们将不得不研究其他人的策略。有些人确实做出了相当好的、总体方向准确的预测，正如我们在<a href="https://danluu.com/yegge-predictions/" rel="noopener noreferrer">史蒂夫·耶格的预测记录</a>中所指出的那样。然而，他们往往是自己预测的严厉批评者，正如史蒂夫·耶格在<a href="https://sites.google.com/site/steveyegge2/ten-predictions" rel="noopener noreferrer">回顾他自己的预测记录时</a>所做的那样，他说：</p>
<blockquote>
<p>我看到了HN上关于丹·陆（Dan Luu）对这篇文章的评论的帖子，我觉得人们对评分有点太慷慨了。</p>
</blockquote>
<p>一个相对较好的未来预测者给自己打分比我低并不奇怪，因为批判性地看待自己的错误并指出大多数人太小而不关心的错误是提高的好方法。我们可以在<a href="https://danluu.com/us-v-ms/" rel="noopener noreferrer">微软领导层的通讯中</a>看到这一点，例如，他们指出自己未能预测缺乏向后兼容性会破坏像OS/2和LanMan这样的重大项目。像大多数未来学家那样，专注于那些成功的预测而不研究出了什么问题，并不是提高的好方法。</p>

<h5 id="cocktail-party-understanding">鸡尾酒会理解</h5>
<p>我们在做出总体方向正确预测的人身上看到的另一件事，正如上面提到的史蒂夫·耶格文章、<a href="https://twitter.com/corry_wang/status/1340869586397372417" rel="noopener noreferrer">内森·梅尔沃德1993年的“信息高速公路上的车祸”</a>、比尔·盖茨1995年的<a href="https://danluu.com/us-v-ms/" rel="noopener noreferrer">《互联网浪潮》</a>等，是做出预测的人实际上理解该主题。在所有上述例子中，文件的作者显然对预测的主题有相当强的技术理解，并且在一般情况下，似乎拥有相对准确预测的人真正试图理解主题，这与本文讨论的未来学家形成鲜明对比，他们几乎都表现出在讨论的主题上具有<a href="https://danluu.com/cocktail-ideas/" rel="noopener noreferrer">流行词水平的理解</a><sup id="fnref:I"><a href="#fn:I" rel="noopener noreferrer">2</a></sup>的明显迹象。</p>
<p>在某种意义上，如果你理解该主题并有权访问正确的数据，做出正确的预测并不太难。在加入一家大型跨国公司并观察未来展开之前，我认为像“信息高速公路上的车祸”和“互联网浪潮”这样的文件具有惊人的预见性，但一旦我在2013年加入谷歌，从外部不明显的许多趋势从内部看似乎相当<a href="https://www.patreon.com/posts/how-do-their-71735437" rel="noopener noreferrer">明显</a>。</p>
<p>例如，很明显移动设备对大多数类别的应用程序非常重要，以至于大多数成功应用将是“移动优先”应用，其中网络应用程序是次要的（如果存在的话），从内部可用的数据来看，这至少从2010年起就应该很明显。看看外部的人在做什么，许多在移动设备至关重要的领域的初创公司在2016年和2017年仍然以2009年对未来的理解运营，他们专注于首先拥有一个网络应用程序，并且没有移动应用程序，而且网络应用程序在移动设备上无法使用。另一个例子是，在2012年，<a href="https://twitter.com/danluu/status/1571051251357589510" rel="noopener noreferrer">谷歌内部相当多的人独立希望谷歌在深度学习上进行非常大的押注</a>。很明显深度学习将是一件非常大的事情，值得在硬件上投资十亿美元，以加速谷歌的深度学习工作。</p>
<p>这并不是说问题很简单——许多拥有相同数据的人通常仍然做出不正确的预测。一个著名的例子是鲍尔默的预测，“iPhone不可能获得任何重要的市场份额。不可能。”<sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">3</a></sup> 鲍尔默和其他微软领导层拥有与十年前微软领导层一样好的信息，但他们中的许多人的预测并不比我们在这里讨论的未来学家好。对于上面的深度学习例子，<a href="https://twitter.com/danluu/status/1571051255157653506" rel="noopener noreferrer">谷歌的一个竞争对手拥有相同的信息，却完全错过了，并且多年来一直错过，即使有多年额外信息的优势</a>；由于未能理解如何实现有效的、实际的深度学习研发，他们现在仍然远远落后于谷歌。</p>

<h5 id="assuming-high-certainty">假设高度确定性</h5>
<p>不正确预测的另一个常见原因是高度确定性。这是一个普遍问题，在从过去的指数增长进行预测并将其外推到未来时，会被放大，因为错误预测指数增长发生重大变化的时间可能会产生非常大的影响，而且相对较小的持续变化在指数增长中也可能产生很大影响。一个暴露我们许多未来学家这些弱点的例子是他们对摩尔定律的解释，许多人将其解释为每18个月计算机相关的每项好处翻倍和/或每项坏事减半。这从来都不是摩尔定律的预测，但它是摩尔定律的普遍流行概念。其中一点说明性的是，预测者在90年代末和00年代初撰写时，仍然基于摩尔定律做出这些幻想性的预测，即使单线程计算机性能和摩尔定律都将面临重大挑战这一常识在当时的本科课程中就已教授过。任何花几分钟与该领域的专家交谈甚至本科生交谈的未来学家都会看到计算机性能缩放存在高度不确定性，但我们讨论的大多数未来学家要么不这样做，要么忽视会增加他们叙述不确定性的证据<sup id="fnref:F"><a href="#fn:F" rel="noopener noreferrer">4</a></sup>。</p>
<p>随着计算能力的增加，所有的常数因子效率低下（“使用两倍的RAM”、“需要三倍的RISC操作”）往往会屈服于摩尔定律，留下多项式和指数增长的成本作为唯一合理的关注领域。因此，Flare愿意接受任何O(C)效率低下（单一、一次性成本），并且愿意接受大多数O(N)效率低下（常数因子成本），因为这两种成本都不会影响可伸缩性；Flare程序和程序空间可以在这些成本相对重要性不增加的情况下增长。你可以随着N的增加向O(N)问题抛掷硬件；向O(N**2)问题抛掷硬件很快就会变得非常昂贵。</p>
<p>对于计算机缩放，本可以做出</p><p><em>由 mimo-v2.5 模型翻译，花费 69407 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/futurist-predictions/</link>
      <guid isPermaLink="false">https://danluu.com/futurist-predictions/</guid>
      <pubDate>Mon, 12 Sep 2022 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>为简单架构辩护</title>
      <description>[AI 摘要] Wave公司以简单架构支撑十亿美元业务，证明复杂架构未必必要，简单方案更易实现且有效。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> Wave公司以简单架构支撑十亿美元业务，证明复杂架构未必必要，简单方案更易实现且有效。</div><p>Wave 是一家拥有70名工程师、估值17亿美元的公司<sup id="fnref:R"><a href="#fn:R" rel="noopener noreferrer">1</a></sup>，其产品是一个用于增减数字的CRUD应用。与此一致，我们的架构是标准的CRUD应用架构，一个基于PostgreSQL的Python单体应用。<a href="https://twitter.com/danluu/status/146207028585525249" rel="noopener noreferrer">从简单的架构开始，并尽可能以简单的方式解决问题</a>，使我们能够扩展到目前的规模，同时工程师们主要专注于为用户创造价值的工作。</p>

<p>Stack Overflow成功地将单体应用扩展到相当规模（<a href="https://nickcraver.com/blog/2013/11/22/what-it-takes-to-run-stack-overflow/" rel="noopener noreferrer">2013年架构</a> / <a href="https://nickcraver.com/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/" rel="noopener noreferrer">2016年架构</a>），最终以18亿美元被收购。如果我们看流量而非市值，Stack Overflow是互联网上流量最高的100个网站之一（关于其他许多建立在单体应用之上的高价值公司，<a href="https://twitter.com/danluu/status/1498678300163588096" rel="noopener noreferrer">请参阅此Twitter线程的回复</a>）。我们没有大量网络流量，因为我们是移动应用，但即使我们的网站基本上只是供人们查找应用的入口，且大多数人甚至不是通过网站发现该应用，Alexa仍将我们的网站排在全球前7.5万名之内。</p>

<p>确实有些类型的应用程序需求，使得基于无聊数据库的简单单体应用无法启动，但对于大多数类型的应用程序，即使在顶级100流量水平上，计算机速度也足够快，高流量应用可以采用简单架构来支撑，而这种架构通常比复杂架构创建得更便宜、更简单。</p>

<p>尽管简单架构有着惊人的有效性，但大多数媒体报道都倾向于复杂架构。例如，在最近的一次通用技术会议上，有六个演讲是关于如何构建或处理基于复杂微服务架构的副作用，而关于如何构建简单单体应用的演讲数量为零。关于量子计算的演讲（一个）比关于单体应用的演讲（零）还多。大型会议情况类似；最近在旧金山举行的一次面向企业的会议，有数十个演讲是关于如何应对复杂架构带来的复杂性，而关于如何构建简单单体应用的演讲为零。上次我参加那次会议时，令我印象深刻的是，有多少在企业工作的与会者，其低规模应用本可以采用简单架构构建，却照搬了在会议圈和Hacker News上流行的最新最复杂的技术。</p>

<p>我们的架构如此简单，我甚至懒得画架构图。相反，我将讨论我们做的一些无聊的事情，帮助我们保持事物的无聊。</p>

<p>我们目前使用无聊的、同步的Python，这意味着我们的服务器进程在等待I/O（如网络请求）时会阻塞。我们之前尝试过Eventlet，一个异步框架，理论上可以让我们更高效地利用Python，但遇到了如此多的bug，以至于我们认为等待事件的CPU和延迟成本不值得我们为处理Eventlet问题而承担的运维痛苦。Python还有其他<a href="https://twitter.com/mcfunley/status/1194713713330122752" rel="noopener noreferrer">知名的异步框架</a>，但那些在规模上使用它们的用户也经常报告<a href="https://twitter.com/mcfunley/status/1194715290841432064" rel="noopener noreferrer">在规模上使用这些框架会带来重大问题</a>。使用同步Python是昂贵的，因为我们在网络请求期间支付了CPU仅仅等待，但鉴于我们目前每月只处理数十亿请求，即使使用像Python这样较慢的语言并支付公共云的零售价格，其成本也很低。我们工程团队的成本完全主导了我们运行系统的成本<sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">2</a></sup>。</p>

<p>为了不承担让我们的单体应用异步化的复杂性，我们将长时间运行的任务（我们不希望响应被阻塞的任务）分发到队列。</p>

<p>有一个我们无法像希望的那样保持无聊的地方，那就是我们的本地数据中心。当我们只在塞内加尔和科特迪瓦运营时，我们完全在云端运营，但随着我们扩展到乌干达（以及未来更多国家），为了遵守当地的数据驻留法律法规，我们不得不拆分后端并在本地部署。这并非一个简单的操作，但任何用复杂服务导向架构做过同样事情的人都知道，这个操作比采用复杂服务导向架构要简单得多。</p>

<p>另一个领域是我们不得不自建（而不是购买）的软件。刚开始时，我们强烈倾向于购买软件而不是自建，因为只有少数工程师的团队承担不起自建一切的时间成本。尽管<a href="https://danluu.com/nothing-works/" rel="noopener noreferrer">“购买”选项通常给你的是不好用的工具</a>，但在当时是正确的选择。在供应商无法被说服修复对我们至关重要的阻塞性bug的情况下，<a href="https://danluu.com/in-house/" rel="noopener noreferrer">确实有理由自建更多工具并在更多领域维护内部专业知识</a>，这与公司应只在其核心能力上“自建”的标准建议相矛盾。大部分复杂性是我们不想承担的，但在某些产品类别中，即使经过相当广泛的研究，我们也找不到任何供应商可能提供适合我们的产品。公平地说，我们的供应商需要解决的问题比我们需要解决的问题复杂得多，因为供应商需要解决为每个客户的问题，而我们只需要解决一个客户——我们自己的问题。</p>

<p>我们在最初几个月犯的一个错误，至今仍有一些代价，那就是没有仔细界定数据库事务的边界。在Wave的代码库中，SQLAlchemy数据库会话是一个请求全局变量；每当访问数据库对象的属性时，它都会隐式开始一个新的数据库事务，Wave代码库中的任何函数都可以在会话上调用commit，从而导致其提交所有挂起的更新。这使得控制数据库更新发生的时间变得困难，增加了我们出现微妙数据完整性错误的频率，也使得利用数据库构建诸如<a href="https://brandur.org/idempotency-keys" rel="noopener noreferrer">幂等键</a>或<a href="https://brandur.org/job-drain" rel="noopener noreferrer">事务性暂存作业排空</a>等功能变得更加困难。它还增加了我们意外打开长时间运行的数据库事务的风险，这可能会使<a href="https://gocardless.com/blog/zero-downtime-postgres-migrations-the-hard-parts/" rel="noopener noreferrer">数据库模式迁移在操作上变得困难</a>。</p>

<p>一些我们不确定的选择（即这些是我们正在考虑更改，或会建议其他团队从头开始时考虑采用不同方法的事情）包括：使用RabbitMQ（就我们的目的而言，Redis可能同样适合作为任务队列，且仅使用Redis可以减少运维负担）、使用Celery（对于我们的用例过于复杂，并且已导致数次故障，例如由于版本升级期间的向后兼容性问题）、使用SQLAlchemy（使得开发人员难以理解其代码将发出哪些数据库查询，导致各种难以调试的情况，并涉及不必要的运维痛苦，特别是与上述数据库事务边界相关的点）以及使用Python（由于我们创始CTO的技术背景，这是一个正确的初始选择，但其并发支持、性能和广泛的动态特性让我们质疑它是否是大型后端代码库的正确选择）。这些都不是重大错误，对于其中一些（例如Python），缺点足够小，以至于我们继续承担增加的维护负担比投资迁移到理论上更好的东西更便宜，但如果我们今天从头开始构建类似的代码库，我们会认真思考它们是否是正确选择。</p>

<p>一些我们对其选择感到满意的领域，尽管它们可能听起来不像最简单的可行方案，包括：我们的API（我们使用GraphQL）、我们的传输协议（我们曾有一段时间使用自定义协议）以及我们的主机管理（我们使用Kubernetes）。关于传输协议，我们过去使用一个运行在UDP之上的自定义协议，带有SMS和USSD回退，<a href="https://www.youtube.com/watch?v=EAxnA9L5rS8" rel="noopener noreferrer">原因如本次演讲所述，是为了性能</a>。随着HTTP/3的推出，我们能够用HTTP/3替换我们的自定义协议，并且我们通常只需要USSD来应对像最近马里互联网关闭这样的事件。</p>

<p>关于使用GraphQL，我们认为对我们来说利大于弊：</p>

<p>优点：</p>

<ul>
<li>精确返回类型的自文档化</li>
<li>精确返回类型的代码生成带来更安全的客户端</li>
<li>GraphiQL交互式浏览器提高了生产力</li>
<li>我们的各种应用（用户应用、支持应用、Wave代理应用等）可以大多共享一个API，降低了复杂性</li>
<li>可组合的查询语言允许客户端在单次数据包往返中精确获取所需数据，无需构建大量专用端点</li>
<li>消除了关于什么才算RESTful API的无谓争论</li>
</ul>

<p>缺点：</p>

<ul>
<li>当我们采用GraphQL时，GraphQL库还不够好（基础Python库是Javascript库的移植，不够Python化，Graphene需要大量样板代码，Apollo-Android生成的代码优化很差）</li>
<li>默认的GQL编码是冗余的，我们非常关注限制大小，因为我们的许多客户带宽较低</li>
</ul>

<p>关于Kubernetes，我们使用Kubernetes是因为我们知道，如果业务成功（事实确实如此）并且我们持续扩展，我们最终会扩展到要求我们在该国境内运营服务的国家。具体法规因国家而异，但我们已经在扩展到一个要求我们将“主要数据中心”设在该国境内的主要非洲市场，其他一些国家则有法规要求，例如，我们必须能够故障转移到该国境内的数据中心。</p>

<p>对于我们来说，有一个不可避免的复杂领域是电信集成。理论上，我们会为所有事情使用SaaS SMS提供商，但<a href="https://youtu.be/6tb8ALAvodM?t=196" rel="noopener noreferrer">主要的SaaS SMS提供商并非在非洲所有地方都运营</a>，在所有地方都使用它们的成本将是高昂的<sup id="fnref:C"><a href="#fn:C" rel="noopener noreferrer">3</a></sup>。前面关于工程师补偿成本主导我们系统成本的评论，如果我们为所有SMS需求都使用SaaS SMS提供商，就不成立了；提供电信集成的团队自身创造了数倍于其成本的价值。</p>

<p>通过尽可能保持我们的应用架构简单，我们可以在那些复杂性能为业务带来收益的地方花费我们的复杂性（和人员编制）预算。除非有充分的理由增加复杂性，否则尽可能简单地做事的理念，使我们能够用不算太多的工程师建立一个相当大的业务，尽管我们运营的是一项非洲金融业务——这通常被认为是一个难以进入的行业，我们将在未来的一篇文章中讨论（我们最早和最有帮助的顾问之一，他的建议对Wave的成功至关重要，最初建议Wave是一个糟糕的商业想法，创始人应该选择另一个，因为他预见到了如此多的潜在困难）。</p>

<p><em>感谢Ben Kuhn、Sierra Rotimi-Williams、June Seif、Kamal Marhubi、Ruthie Byers、Lincoln Quirk、Calum Ball、John Hergenroeder、Bill Mill、Sophia Wisdom和Finbarr Timbers的评论/修正/讨论。</em></p>

<p></p>
<div>

<hr>

<ol>
<li id="fn:R">如果你想计算一个比例，我们上次融资时约有40名工程师，当时估值为17亿美元。
 <a href="#fnref:R" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:B">对于某些商业模式，情况并非如此，例如，如果我们是一家广告支持的社交媒体公司，随着公司增长，我们需要支持的流量水平将足够大，以至于如果我们不<a href="https://danluu.com/sounds-easy/" rel="noopener noreferrer">将大量工程时间用于优化和降低成本工作</a>，我们将产生巨大的财务成本。但是，作为一个在应用的大量交互中收取真金白银的公司，我们单位收入的计算负载与社交媒体公司相比非常低，这很可能对我们来说直到我们比现在大一个数量级以上都是一个小问题；即使我们大两个数量级，这是否是主要问题也不确定，但在三个数量级的增长上，这肯定是一个问题。
 <a href="#fnref:B" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:C">尽管有不应在价格上竞争的经典建议，我们（除了其他许多事情）确实在价格上竞争，因此必须关注成本。我们已经降低了非洲移动货币的成本，并且<a href="https://www.wave.com/en/blog/world/" rel="noopener noreferrer">我们的竞争对手不得不大幅降价以匹配我们的价格，我们认为这对世界来说是一个积极的价值</a>。
 <a href="#fnref:C" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 6608 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/simple-architectures/</link>
      <guid isPermaLink="false">https://danluu.com/simple-architectures/</guid>
      <pubDate>Wed, 6 Apr 2022 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>为什么买到好用的东西这么难？</title>
      <description>&lt;p&gt;我经常听到一种&lt;a href=&quot;https://danluu.com/cocktail-ideas/&quot;&gt;鸡尾酒会版的&lt;/a&gt;有效市场假说，大意是“市场强制执行效率，因此一家公司不可能存在重大低效并存活下来”。&lt;a href=&quot;https://danluu.com/tech-discrimination/&quot;&gt;我们之前在这里&lt;/a&gt;和&lt;a href=&quot;https://danluu.com/tal</description>
      <content:encoded><![CDATA[<p>我经常听到一种<a href="https://danluu.com/cocktail-ideas/" rel="noopener noreferrer">鸡尾酒会版的</a>有效市场假说，大意是“市场强制执行效率，因此一家公司不可能存在重大低效并存活下来”。<a href="https://danluu.com/tech-discrimination/" rel="noopener noreferrer">我们之前在这里</a>和<a href="https://danluu.com/talent/" rel="noopener noreferrer">这里</a>讨论过马克·安德森关于科技招聘不可能低效的引述：</p>

<blockquote>
<p>让我们直接开始吧。我认为硅谷公司故意、系统性歧视的批评是错误的，有两个原因可以相信这一点。……第二，我们的公司求贤若渴。极其渴望。我们的公司渴求人才。他们就像躺在沙滩上喘气一样，因为找不到足够多的有才华的人来做这些工作。去寻找人才的动机高得难以置信。</p>
</blockquote>

<p>我经常听到工程师和风险投资人重复的这一观点的变体是，公司是有效率的，或者产品基本上已经尽可能好，因为如果它们可能更好，早就有人通过竞争做得更好了<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">1</a></sup>。</p>

<p>这类说法有一种模糊的合理性，因此它经常成为<a href="https://danluu.com/cocktail-ideas/" rel="noopener noreferrer">我在随意谈话中经常听到的辩论话题</a>，其中一人会指出某个明显的公司低效或产品错误，而另一人则会回应说，如果它如此明显，公司里早就有人修复了这个问题，或者另一家公司会凭借更高的效率或更好的产品胜出。纯粹从理论上讲，很难解决这个争论，但如果我们看一些具体例子，比如上面关于招聘的两个例子，事情就清晰了，我们可以观察到，无论人们提出什么抽象的论点，低效现象持续了数十年。</p>

<p>就购买产品和服务而言，在个人层面上，我认识的大多数检查过他们雇来的人（比如家庭装修或<a href="https://twitter.com/benskuhn/status/1477072484092375040" rel="noopener noreferrer">会计</a>）的工作的人都发现了工作中的严重错误。虽然有可能找到不做劣质工作的人，<a href="https://twitter.com/pushcx/status/1499381895653699585" rel="noopener noreferrer">但对于一个不是该领域专家的人来说，通常很难确定某人是否会做劣质工作</a>。你可以尝试通过支付更多来获得更好的质量，但一旦你脱离了市场的非常低端，如何用金钱换取质量通常就不清楚了；例如，我的朋友和同事中，那些选择大型、知名会计公司的人支付的费用远高于选择小型、本地会计师的人，却得到了更高的错误率；作为一种策略，尝试聘请昂贵的本地会计师也没有好多少。好的会计师通常有点贵，但他们通常不收取最高费率，而且只有一小部分有点贵的会计师是好的。</p>

<p>更普遍地说，在许多市场中，消费者信息不灵，<a href="https://danluu.com/why-benchmark/#appendix-capitalism" rel="noopener noreferrer">很难找出哪些产品甚至还算不错，更不用说好的了</a>。当人们碰巧选择了适合他们的产品或服务时，通常是出于错误的原因。例如，在我的社交圈里，过去几年有两波人从iPhone转向Android手机。这两波都是由于苹果的公关失误造成的，导致很多人认为iPhone在某方面很糟糕，而实际上它在这方面比Android手机更好。幸运的是，iPhone并非严格优于Android手机，许多转换的人得到了一个对他们更好的设备，因为他们之前是因为苹果良好的公关而使用iPhone，这使得他们的错误相互抵消。但是，当人们主要根据营销和公关做决定，并且无法获得好的信息时，没有特别的理由认为一个总体上更好甚至严格优越的产品会胜出，而更差的产品会失败。在资本市场中，我们不需要那么多信息充分的参与者就能认为某种形式的有效市场假说成立，以确保“价格反映了所有可用信息”。一个不言而喻的道理是，关于市场低效的已发表结果在发表的那一刻就不再成立，因为人们会利用这种低效直到它消失。但是，在劳动力市场的例子中，尽管公司可以利用定价错误的劳动力，正如格林斯潘在成为美联储主席之前著名地那样，低效可以持续存在：</p>

<blockquote>
<p> Townsend-Greenspan 作为一家经济学公司是不寻常的，因为男性为女性工作（我们大约有25名员工）。我雇用女性经济学家并非出于妇女解放。这只是非常有商业意义。我平等对待男性和女性，并发现由于其他雇主没有这样做，优秀的女性经济学家比男性更便宜。雇用女性……让 Townsend-Greenspan 用同样的钱获得了更高质量的工作……</p>
</blockquote>

<p>但正如我们所看到的，单个公司利用定价错误的劳动力，对劳动力的需求有限，低效可以持续数十年，因为那些根据“所有可用信息”行事的公司购买的劳动力不足以将定价错误的人的价格推高到如果大多数或所有公司都理性行事时会达到的水平。</p>

<p>从理论上讲，在产品和服务方面，低效似乎也应该能够持续很长时间，因为同样，也没有一种机制允许系统中的参与者以一种直接将金钱转化为更多金钱的方式来利用低效，有时甚至几乎没有任何机制来赚钱。例如，如果你观察到人们因为认为苹果在进行邪恶的计划性淘汰而从iPhone转向Android手机是愚蠢的，而Android设备通常由于<a href="https://danluu.com/android-updates/" rel="noopener noreferrer">iPhone获得更新的时间更长</a>以及iPhone在它们竞争的每一个价位上都更快而更快过时，允许手机在<a href="https://danluu.com/web-bloat/" rel="noopener noreferrer">臃肿的网站</a><a href="https://twitter.com/danluu/status/919423480776613888" rel="noopener noreferrer">上使用更长时间</a>，你真的无法从这个观察中赚钱。这与你可以购买衍生品来赚钱（在预期中）的定价错误的资产不同。</p>

<p>对于不知道什么产品或服务好的问题，一个常见的建议是咨询该领域的专家或有资质的人，但这<a href="https://twitter.com/danluu/status/1324495890548056065" rel="noopener noreferrer">也经常失败</a>。例如，我的一个朋友因为他的窗户空调噪音太大，启动时会把他吵醒而睡不着觉。他问了一个他信任的、从事空调工作的朋友，换一台新的空调是否可以改善这种情况，他的朋友说“不；空调基本上都差不多”。但任何比较过带电机产品的消费者都会立即知道这是错误的。工程师在保持功率和成本不变的情况下制造更安静的设备方面已经进步了很多。我的朋友最终买了一台更新的、更安静的空调，这解决了他的睡眠问题，但他问题持续的时间比需要的更长，因为他以为一个以空调工作为业的人会给他提供不糟糕的建议。如果我的朋友是空调专家，或者比较过随时间变化的其他类似消费产品的噪音水平，他本可以知道不该相信他的朋友，但如果他有那种专业水平，他一开始就不需要建议了。</p>

<p>到目前为止，我们关注的是在个人层面获得合适产品或服务的困难，但这个问题在公司层面也存在，并且通常更严重，因为市场往往更薄，可用产品更少，而且定价不透明，“请联系我们”。一些经常被重复的建议是公司应该专注于他们的“核心竞争力”，并将其他所有事情外包（例如，Joel Spolsky、Gene Kim、Will Larson、Camille Fournier 等人都这么说），但如果我们看中型科技公司，我们会发现它们通常需要拥有远超出任何人认为的核心竞争力的内部专业知识，除非，例如，<a href="https://danluu.com/in-house/" rel="noopener noreferrer">每家社交媒体公司都将内核专业知识作为核心竞争力</a>。原则上，公司可以将这类工作外包，但我认识的那些依赖外包的人，比如将内核专业知识外包给顾问或将应用工程师放在支持合同上，与他们通过雇用专职工程师所能得到的结果相比，都非常不满意，无论是在绝对值上（支持通常在几周甚至几个月内都找不到满意的解决方案，即使是一个好的工程师可以在几天内解决的问题），还是在金钱上（尽管工程师很昂贵，但大型支持合同通常可能比一个工程师更贵，同时提供的服务更差）。</p>

<p>这个问题不仅存在于支持方面，也存在于公司可以购买而不是构建的产品中。例如，Wave 的首席技术官 Ben Kuhn <a href="https://twitter.com/benskuhn/status/1324493738304036864" rel="noopener noreferrer">在推特上发布了一些关于我们遇到的问题</a>，并<a href="https://twitter.com/benskuhn/status/1382325921311563779" rel="noopener noreferrer">有</a><a href="https://twitter.com/benskuhn/status/1496167942744346624" rel="noopener noreferrer">几个后续</a>。Ben 现在认为，他作为首席技术官犯下的一个大错误是没有在供应商选择上投入更多精力，即使决策看起来是板上钉钉的，并且更强烈地考虑尽早将许多系统迁移到定制的内部版本。即使选择了该领域领先（即规模最大、最受尊敬）的公司中的共识最佳产品，并使用公司的主要产品，该产品通常不仅不起作用，而且其设计就无法工作。</p>

<p>例如，我们尝试“购买”而不是“构建”一个将数据从 Postgres 同步到 Snowflake 的产品。从 Postgres 同步是主要产品（即拥有最多客户的产品）来自一家领先的数据同步公司，我们发现它会丢失数据、重复数据和损坏数据。深入研究后，发现该产品的设计依赖于数据源能够在变更日志上向后搜索。但 Postgres 在变更日志被消费后就会丢弃它们，因此 Postgres 数据源无法支持此操作。当他们的产品尝试执行此操作且操作失败时，我们最终会出现同步“卡住”的情况，需要供应商操作员的和/或数据丢失的人工干预。由于我们的数据仍然在 Postgres 上，可以通过完全重新同步来恢复，但数据同步产品的速度上限为 5MB/s，原因似乎不为他们所知，因此即使对于不是特别大的数据库，完全重新同步也可能需要数天时间。重新同步也会静默地丢弃和损坏数据，因此有时需要多轮完全重新同步后跟数据完整性检查来从数据损坏中恢复，这可能需要数周时间。尽管被广泛推荐并且是该领域的领先产品，该产品存在一些重大设计缺陷，意味着它实际上无法工作。</p>

<p>这与 Mongo 或其他存在基本设计缺陷导致严重数据丢失的产品没有太大区别，主要区别在于，在大多数领域，没有像<a href="https://danluu.com/why-benchmark/" rel="noopener noreferrer">Kyle Kingsbury 这样花费数年时间测试该领域各种产品</a>，并耐心回应关于正确性的虚假声明，直到公关反弹导致该领域的公司开始认真对待正确性的人。没有这种压力，大多数软件产品基本上都不工作，因此上面有 Ben 的推特线程，他指出你可能想选择的“购买”解决方案大多不起作用<sup id="fnref:V"><a href="#fn:V" rel="noopener noreferrer">2</a></sup>。当然，在我们的规模上，有很多我们近期不会去构建的东西，比如 CPU，但对于许多普遍认为应该“购买”的东西，“构建”似乎是一个合理的选择。即使对于大公司和构建 CPU 也是如此。十五年前，高性能（即非嵌入式级别的性能）CPU 被认为是疯了才会内部构建的东西，对最大的软件公司来说也是荒谬的，但 Apple 和 Amazon 已经能够在它们优化的维度上生产出同类最佳的 CPU，这是有原因的<sup id="fnref:Q"><a href="#fn:Q" rel="noopener noreferrer">3</a></sup>。</p>

<p>这不仅仅是影响科技公司的问题；我们在许多不同的行业中都能看到这一点。例如，任何想向客户邮寄物品的公司都必须自己实现物流，或者处理不可靠物流的后果。作为用户，包裹是否能寄达给你，很大程度上取决于你住在哪里以及你住在什么样的建筑里。</p>

<p>当我住在房子里时，包裹通常无论使用哪家快递都会送达（尽管经常迟到）。但自从搬进公寓楼后，一些建筑就是收不到某些快递服务的配送。有一次，我住在一个邮政服务没有正确投递邮件的建筑里，我收不到很多邮件（虽然我经常收到寄给楼里其他人以及别处的人的邮件）。更常见的是，UPS 和 Fedex 通常不会尝试投递，只会在建筑门上贴一堆通知，上面是所有他们未投递的包裹的通知，通知错误地表示收件人不在家，但正确地指出，要拿到包裹，必须去某个取件点取件。</p>

<p>有一段时间，我住在一个城市，Amazon 使用第三方商业快递服务进行当日达的最后一公里配送。他们使用的服务以在未投递物品的情况下标记已投递而闻名，使得“当日达”配送比次日达甚至两日达还慢。有一次，我天真地联系了 Amazon 客服，因为我的包裹被标记为已投递但实际上没有投递。客服使用 Amazon 提供的标准脚本告诉我，我应该在包裹被标记为已投递三天后再联系他们，因为快递员经常标记包裹已投递但未投递，但他们通常会在几天内投递包裹。Amazon 知道他们使用的快递服务甚至不真正尝试投递包裹<sup id="fnref:P"><a href="#fn:P" rel="noopener noreferrer">4</a></sup>，而他们唯一可用的短期缓解措施是告诉客服，让人们不要在包裹被标记为已投递时就认为它已到达。</p>

<p>Amazon 最终通过拥有自己的快递员或使用（按商业运输标准）极其昂贵的服务解决了这个问题（Apple 已为当日达这样做<sup id="fnref:A"><a href="#fn:A" rel="noopener noreferrer">5</a></sup>）。在大规模运营下，没有你可以付费的商业服务能可靠地尝试投递包裹。如果你想要一个真正有效的服务，你通常需要自己构建，就像在软件领域一样。我当地的杂货店试图将此外包给 DoorDash。我尝试从杂货店送货3次，杂货只到了2次，这远低于大多数人认为可接受的命中率。为了获得可靠性而必须自建而不是购买，对生产力是巨大的拖累，尤其是对小公司而言（例如，想要与 Amazon 竞争并邮寄产品给客户的小商店不可能拥有可靠的配送服务，因为他们无法建立自己的配送服务）。</p>

<p>无法外包服务所产生的浪费量是惊人的，我在我工作过的每个地方都见过。另一个行业的例子：当我在一家小型芯片初创公司工作时，我们内部有能力进行端到端的芯片处理（除了没有自己的晶圆厂），这在小型芯片初创公司中并不常见。当新设计的第一片晶圆从晶圆厂出来时，我们会用航班把晶圆运给我们，然后有人会用晶圆切割机把晶圆切成单个芯片，以便尽快开始测试。这通常被认为是荒谬的，就像小型软件初创公司管理自己的本地硬件被认为是荒谬的一样。毕竟，晶圆切割机和从晶圆到工作芯片所需的专业知识在99%以上的时间里都是闲置的。拥有全职设备和你使用时间不到1%的专业知识，是应该外包的经典例子，但如果考虑到拥有能够完成这项工作的人员和设备，即使在相当低的产量下，在内部完成也更便宜，即使设备和专业知识闲置99%的时间。更重要的是，内部服务会好得多（周转更快），让你能够以更高的节奏发货。我既在尝试将此类事情外包的公司工作过，也与许多做过此事的人交谈过，你会得到更慢、更不可靠的服务，而且成本更高。</p>

<p>芯片软件工具也是如此；尽管将工具外包给<a href="https://en.wikipedia.org/wiki/Electronic_design_automation" rel="noopener noreferrer">大型 EDA 供应商</a>是标准做法，但我们通过使用自己的定制工具（通常由一个人创建或维护）获得了很大收益，例如，我在那里的时候，大多数模拟周期都是在由一个人维护的定制模拟器上运行的，这为公司节省了每年数百万美元的模拟器成本（当时模拟器的标准价格是每个许可证每年几千美元，而我们有大约一千台模拟机器的农场）。你可能认为，如果一个人可以创建或维护一个对公司每年价值数百万美元的工具，我们的竞争对手也会这样做，就像你可能认为如果通过雇用一个知道如何切割晶圆的人能以更低的成本更快地发货，我们的竞争对手也会这样做，但他们大多没有这样做。</p>

<p><a href="https://twitter.com/danluu/status/823296808919019520" rel="noopener noreferrer">Joel Spolsky 有一篇旧文章说</a>：</p>

<blockquote>
<p>“找出依赖关系——然后消除它们。”当你是真正优秀的团队的一员，有优秀的程序员时，坦率地说，其他人的代码都是漏洞百出的垃圾，而且没有其他人知道如何按时交付。</p>
</blockquote>

<p>我们有类似的态度，尽管我会说我们更谦逊一些。我们不认为其他人都在生产垃圾，但我们也没有假设我们无法以十分之一的成本制造出可以与我们购买的产品相媲美的东西。通过与一些竞争对手的人员交谈，<a href="https://danluu.com/culture/" rel="noopener noreferrer">我们的运作方式和他们的运作方式之间存在着相当大的文化差异</a>。他们根本没有想到，他们不必接受标准的美国商业逻辑，即你应该专注于你的核心竞争力，你可以根据具体情况考虑内部做某事是否有意义，而不是将你的思考外包给一句简洁的格言。</p>

<p>我曾从内部观察过一家公司经历这种文化转变。一些领导层的人决定公司应该专注于核心竞争力，这意味着放弃用于基础设施的定制软件。这导致了大量从定制内部软件迁移到 SaaS 解决方案和开源软件。如果你观察关于各种项目“为什么”应该或不应该迁移的讨论，有一些异常“不讲理”的人试图根据每个案例的具体情况进行推理（在一篇关于抵制高层命令的文章中，<a href="https://yosefk.com/blog/people-can-read-their-manager-mind.html" rel="noopener noreferrer">Yossi Kreinin 称这些人是疯了的员工</a>；我将在本文中指代同一个概念，但称之为“异常不讲理”的人）。但大多数人接受了党的路线，无论具体情况如何都推动迁移。</p>

<p>我认为有趣的是，领导层并没有告诉特定团队他们必须迁移，对于“异常不讲理”的人为了保持运行现有系统（出于合理原因）而抵制的团队，也没有真正的负面后果。相反，大多数人接受了这个想法，并试图为听起来合理但实际上与现实无关的理由进行迁移，导致一些有趣的结果，比如迁移到一个开源系统“以省钱”，而新系统显然效率低下<sup id="fnref:M"><a href="#fn:M" rel="noopener noreferrer">6</a></sup>，并且可预见地需要更高的资本支出和运营支出。节省成本本应来自缩小团队规模，但运营成本的增加主导了团队成本的变化，而操作系统的复杂性意味着团队规模反而增加了。确实有一些迁移是有意义的，但陈述的迁移理由往往与实际有意义的理由无关或关系不大。一旦人们接受了公司应该专注于核心竞争力的思想，迁移就是由这种文化思想驱动的，而不是任何技术原因。</p>

<p><a href="https://danluu.com/bad-decisions/" rel="noopener noreferrer">像上面这样的决策的普遍性——没有严肃技术考虑的技术决策——是公司制造好产品的选择压力如此微弱的一个主要原因</a>。存在一些压力，但它非常嘈杂，以至于成功的公司常常绕过制造一个能工作的产品，就像上面的 Mongo 例子一样，Mongo 决定大声重复<a href="https://aphyr.com/tags/mongodb" rel="noopener noreferrer">明显虚假的性能主张和做出明显错误的正确性主张</a>，从商业角度来看，这比专注于实际的正确性和性能更优越；通过将资源集中在对业务重要的地方，他们成功地超越了那些犯了将大量资源投入到性能和正确性上的错误的公司。</p>

<p>Yossi 关于一个异常“不讲理”的人在他们看重的维度上能在公司产生超大影响的文章也适用于公司外部的影响。上面提到的 Kyle Kingsbury 就是一个例子。按我现在听说的<a href="https://jepsen.io/" rel="noopener noreferrer">Jepsen</a>收费水平，Kyle 可以带来相当于大公司高级开发人员（真正的高级，不是头衔写着“高级”）的收入，但那是在多年的低于市场水平的长时间工作、在不确定的努力中进行的，在此期间他反驳了批评者的<a href="https://en.wikipedia.org/wiki/Fear,_uncertainty,_and_doubt" rel="noopener noreferrer">FUD</a>（如果你阅读相关帖子下的回复，或者更糟的是，阅读他参与讨论的工单，多年来回复给 Kyle 的内容一直是一连串的胡言乱语，包括为供应商工作的人<a href="https://lobste.rs/s/jgfyep/addio_redis_i_m_leaving_redis_labs#c_i6kcci" rel="noopener noreferrer">觉得他特别针对他们，贬低他的人格</a><sup id="fnref:J"><a href="#fn:J" rel="noopener noreferrer">7</a></sup>，以及<a href="https://lobste.rs/s/jgfyep/addio_redis_i_m_leaving_redis_labs#c_sae3wo" rel="noopener noreferrer">普遍贬低他</a>）。我深深尊重那些尽管系统与他们作对，仍愿意推动解决这类问题的人，但尽管我尊重，基本上没有人会去做。一个需要像 Kyle 这样的人挺身而出，成功的公司才会在正确性而不是正确性营销上投入努力的系统，将会产生很多产品，它们擅长营销正确性，却没有真正的正确性属性（比如本文中提到的数据同步产品，其网站反复提到同步产品多么可靠和安全，尽管其设计存在根本性缺陷）。</p>

<p>在公司层面也是如此，通常需要一家异常“不讲理”的公司才能制造出真正卓越的产品，而不仅仅是被营销为卓越的产品，例如，<a href="https://danluu.com/car-safety/" rel="noopener noreferrer">Volvo，唯一一家似乎试图制造出超越 IIHS 测试所能证明的结构安全性的汽车制造商</a>，其商业表现非常糟糕，以至于被迫转向高端市场，成为一家小众的豪华汽车制造商，因为尽管车祸是导致死亡的主要原因，也是预期寿命损失的重要来源，但消费者并不真正感兴趣于安全性。而且目前还不清楚 Volvo 是否能够持续作为一家“不讲理”的公司存在，因为他们无法作为一家独立的汽车制造商生存下来。当福特收购 Volvo 时，<a href="https://danluu.com/car-safety/#quality-may-change-over-time" rel="noopener noreferrer">福特开始将 Volvo 转移到共享的福特 C1 平台，该平台在碰撞测试中表现并不出色</a>。自从吉利收购 Volvo 以来，<a href="https://twitter.com/danluu/status/1458814317948661768" rel="noopener noreferrer">现在还太早，无法确定他们是否会保持 Volvo 为真实世界碰撞数据（而不仅仅是基准测试中报告的碰撞数据）设计的承诺</a>。如果吉利拒绝继续 Volvo 对结构安全性的承诺，可能就无法买到一辆设计为安全的现代汽车了。</p>

<p>大多数市场都是如此，只是从一开始就从未有过像 Volvo 这样“不讲理”的公司。关于“不讲理”的员工，Yossi 说：</p>

<blockquote>
<p>谁能够，有时也确实会，从底部清理变质的鱼？一个疯了的员工。一个发现叉子、崩溃等事情是对他的个人冒犯的人，并且会反复冒着惹恼管理层的风险，去阻止这些事情。尤其是那种把自己辛苦赚来的、管理层真正重视的政治资本，花在管理层并不真正重视的工作上的人——这样的人可以坚持战斗很长时间。有些人通过坚持直到管理层真正改变想法并奖励他们而成功地将其作为事业。无论成功的几率如何，普通人都无法理解试图完成这样壮举的人的动机。</p>
</blockquote>

<p>很少有人愿意花费大量的个人资本去做正确的事，无论对某人来说这意味着什么，但更罕见的是，一家公司的领导层会做出这样的选择，花费公司的资本去做正确的事。</p>

<p>经济学家有一个术语，用于信息不对称导致买方无法区分好产品和“柠檬”的情况，“柠檬市场”，比如汽车市场（“柠檬”一词由此而来），或者<a href="https://danluu.com/hiring-lemons/" rel="noopener noreferrer">招聘市场的双方</a>。在经济论述中，关于汽车是否是柠檬市场存在争论，原因多种多样（允许人们退还有问题的柠檬车的柠檬法似乎没有改变市场的运作方式，当定义为具有严重可靠性问题的车辆时，很少有现代汽车是柠檬，等等）。但关注人们偶尔是否会买到有缺陷的汽车，是只见树木不见森林。可能只有一家汽车制造商真正认真地试图制造出超越标准机构测试水平的结构安全的汽车（而且有传言称他们在日益重要的软件测试方面偷工减料），因为消费者无法区分超过少数标准机构测试水平的安全性差异。那就是一个柠檬市场，几乎所有其他消费市场和企业对企业市场也是如此。</p>

<h3 id="appendix-culture">附录：文化</h3>

<p>关于美国社会，我觉得有趣的一点是，有多少人认为，一个人因为未能保护自己应对所有突发情况而吃亏是“活该”（希望高度有效的组织通常通过拥有“免责”文化来避免这种情况，但很少有人接触过这样的文化）。</p>

<p>我最近看到的一些例子：</p>

<ul>
<li>一个人在咖啡馆笔记本电脑被偷；被责备没有一直盯着笔记本电脑，因为没有一个理性的人会在转头和某人简短交谈时让目光离开自己的任何物品哪怕10秒钟</li>
<li>一个人发布了一份公共服务公告，说他们被一家公司服务条款的变更所困，其他人也应该注意同样的事情，人们说被困住的人很蠢，没有阅读他们收到的每一份服务条款更新的每一个字</li>
<li>（很多次，在 r/idiotsincars 上）：一个人发生了事故，从合理角度很难或不可能避免，人们告诉这个人他们是一个糟糕的司机，没有避免事故</li>
<ul>
<li>至少有一次，这个人进行了逐帧分析，显示他在一帧的延迟内尽可能快地做出反应，但仍然被告知他应该避免事故</li>
<li>人们经常会说“我一开始就不会陷入那种情况”，在有人经过停放的车辆行驶的情况下，会导致诸如“我绝不会以超过10英里/小时的速度经过车辆”之类的荒谬言论，就好像发表评论的人在每条有停放或停止车辆的街道上都会减速到10英里/小时一样。</li>
</ul>
<li>一个人在 flyertalk 论坛抱怨 Google 地图说明不清楚（如果你不是机器人的话，比如“在500米后右转”，这可能是多个交叉路口之一），人们回应说诸如“我从不在完全熟悉路线的情况下出门”之类的话，并且你应该事先规划好所有的驾驶路线，就像你在1992年用纸质地图进行公路旅行一样（这被用作事先规划所有旅行的合理性的理由——我当时就是这么做的，任何不蠢的人现在也会这么做）</li>
<ul>
<li>那些持有此类负面反应的人获得了大量点赞；<a href="https://twitter.com/danluu/status/1520012665954865152" rel="noopener noreferrer">没有人建议改用 Apple 地图，它提供清晰的、基于地标的指示，如“通过红绿灯，然后在下一个路口右转”</a></li>
</ul>
</ul>

<p>如果你阅读这类讨论，你会经常看到人们声称“世界就是这样的”，并且进一步说世界不可能以任何其他方式存在，所以任何没有为此做好准备的人都是白痴。</p>

<p>回到笔记本电脑被偷的例子，任何旅行过，甚至阅读过其他文化的人都可以观察到，北美人认为基本上是大规模社会固有结果的东西是武断的。例如，如果你把包和笔记本电脑放在咖啡馆的桌子上几个小时后回来，在韩国，包和笔记本电脑极有可能还在那里<a href="https://news.ycombinator.com/item?id=30625890" rel="noopener noreferrer">我听说在日本也是如此</a>。虽然那样占着桌子不礼貌，但你的包和笔记本电脑不太可能被偷。</p>

<p>而且，事实上，如果你稍微改变一下语境，这在美国基本上也是真的。进入一个空房子并偷走里面的东西，并不比从咖啡馆偷走东西难多少（学习如何撬锁<a href="https://www.youtube.com/c/lockpickinglawyer" rel="noopener noreferrer">相当容易</a>，而打碎窗户则更容易）。然而，在美国的大多数社区，人们很少被入室盗窃，当有人发帖说被入室盗窃时，他们不会因为没有看好自己的房子而被痛斥为白痴。相反，人们大多表示同情。在公共场所无人看管的财产被偷被认为是正常的，而在私人空间则不是，但这更多是一种文化上的区别，而不是技术上的区别。</p>

<p>有一个相关的系列故事，是 Avery Pennarun 讲述的关于他作为美国人在韩国的文化冲击。其中一个故事是关于某种你可以使用的在线订购服务，有点像 Amazon。在 Amazon，当你订购某物时，你会收到一个带有多个条形码/二维码/其他代码的盒子，当你打开它时，里面还有另一个至少有一个代码的盒子。当然，另一个盒子需要条形码，因为它要经过大规模的物流中心，没人知道盒子是什么或需要去哪里，内盒也需要经过某种其他类型的处理，并且如果商品在零售商处销售，也需要能够被结账机器扫描。内盒里面是商品。如果你想退货，你把商品放回带有条形码的盒子里，然后把那个盒子放进运输盒里，然后在运输盒上贴另一个条形码，然后寄出。</p>

<p>所以，在韩国，有某个类似 Amazon 的服务，你可以订购一件商品，一两个小时后，你会听到敲门声。当你到门口时，你会看到一个没有标签的盒子或袋子，商品就在这个没有标签的容器里。如果你想退货，你“告诉”应用程序你想退货，把它放回它的容器，放在你家门口，他们会取走它。看到这种与美国截然不同的物流设置后，他问某人“他们怎么可能不弄混哪个盒子是哪个？”他得到的答案是“他们为什么会弄混哪个盒子是哪个？”。他的其他故事也有类似的感觉，他在其中描述了一些相当陌生的事物，询问当地人事情怎么会以这种陌生的方式运作，而当地人无法想象事情以任何其他方式运作，并回应说“为什么X不行？”。</p>

<p>就像咖啡馆笔记本电脑的例子一样，Avery 的许多故事都归结为在人们和组织如何运作方面存在完全不同的共同文化期望。</p>

<p>另一个例子是新冠。我的许多朋友在过去几年里大部分时间都呆在越南或台湾等亚洲国家，那里的新冠感染率低得多，以至于他们几乎完全没有封锁。我在那些国家的朋友基本上可以过上正常的生活，就好像根本没有新冠病毒一样（至少直到最近的变种，在此之前他们已接种疫苗，并且对于最严重的结果来说风险相对较低），同时几乎冒着零感染新冠病毒的风险。</p>

<p>在大多数西方国家，许多人的最初舆论认为封锁是无意义的，我们无法防止新冠病毒爆发The request was rejected because it was considered high risk</p><p><em>由 mimo-v2.5 模型翻译，花费 20053 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/nothing-works/</link>
      <guid isPermaLink="false">https://danluu.com/nothing-works/</guid>
      <pubDate>Mon, 14 Mar 2022 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>人才误判</title>
      <description>[AI 摘要] 文章通过棒球球探和科技行业招聘的例子，说明基于外貌、身高或背景等无关因素的人才评估会导致人才错配和偏见。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章通过棒球球探和科技行业招聘的例子，说明基于外貌、身高或背景等无关因素的人才评估会导致人才错配和偏见。</div><p></p><details>
<summary>[点击展开/收起体育相关部分]</summary>
以下是球探笔记：<p></p>

<ul>
<li>球员A：
<ul>
<li>……将会成为一个真正的体格范例，有潜力练成戴夫·帕克那样的身材。面容像利昂·瓦格纳。身体柔韧性极佳。手非常大。</li>
</ul></li>
<li>球员B：
<ul>
<li>出色的身体条件——高大的运动型体格，宽阔的肩膀，修长而结实的手臂和腿。步伐有弹性，身体控制能力高于平均水平。面容强健。</li>
</ul></li>
<li>球员C：
<ul>
<li>臀部位置高，手臂和腿较长，躯干精瘦，像匹年轻的马驹
</li>
<li>[另一位球探]：精干、柔韧，动作灵活，面容佳
</li>
<li>[又一位球探]：运动型体格，柔韧，骨架修长，双腿略呈弓形。
</li>
</ul></li>
</ul>

<p>脱离语境，你可能以为他们是在给演员或模特做球探，但这些人其实是棒球运动员（“A”是劳埃德·莫斯比，“B”是吉姆·阿博特，“C”是德瑞克·基特），而且他们都非常优秀（劳埃德·莫斯比可能只有大约四年时间算是顶级球员，但与大多数被球探评估的球员相比，这已经非常出色了）。如果你读过其他棒球球探报告，你会发现很多评论是关于某人“面容如何”、长得像谁、臀部形状等等。</p>

<p>基本上每个人都想雇用有才华的人。但即使在棒球领域——雇用人才的回报显而易见且丰厚，并且是美国最易于量化的主要运动——人们在一个世纪里，仅仅因为过度依赖基于无意识和有意识偏见、并且没有正确校准的直觉，就犯了相当明显的错误。稍后，我们将探讨棒球雇用模式对其他领域的意义，但首先，让我们看看那些最终没有达到预期的球员，如何与未来的超级巨星获得类似的球探报告（不关心体育的程序员可以将此视为等同于面试反馈），例如以下关于<a href="https://www.baseball-reference.com/players/e/eatonad01.shtml" rel="noopener noreferrer">亚当·伊顿</a>的评论，尽管他曾被视为他那一代最有潜力的热门新秀（潜在雇员）之一，但按照职业标准，他是一名表现糟糕的球员：</p>

<ul>
<li>球探1：中等身材/紧凑/结实。一名非常优秀的运动员/反应迅速如“猫”一般。整体身体力量出色。中等大小的手/中等长度的手臂/前臂强壮……该球员是位顽强的竞争者。这家伙骨子里有点老派的斗牛犬精神。
</li>
<li>球探2：身体条件好，有发展骨架。手臂长，手大。脸窄。留鬓角，帽子戴得像军人。溜肩。大腿内侧强壮……也打篮球。好运动员……态度极佳。看不出他会受伤病困扰。全国顶尖高中投手之一
</li>
<li>球探3：6'1"-6'2"（约1.85-1.88米），180磅（约82公斤），上下半身都很结实。还有空间再增加15磅（约7公斤）而不影响表现
</li>
</ul>

<p>另一方面，球探也会仅凭球员的身体外貌就否定他们，而这些球员后来却被证明非常出色，比如以下这些对阿尔伯特·普霍尔斯体重感到担忧的球探：</p>

<ul>
<li>球探1：身体沉重、笨重。下半身有多余（体重）。未来（体重）将成为问题。积极的打者，有打出失误全垒打的力量。倾向于乱挥棒。
</li>
<li>球探2：挥棒速度好，手部力量非常强。在本垒板前竞争意识强，拼劲足。击球接触似乎还行。挥棒有时有点长。会过度拉打。他未能将球有力地打向中外野或右外野。体重未来将成为问题。
</li>
</ul>

<p>普霍尔斯最终成为有史以来最伟大的棒球运动员之一（<a href="https://web.archive.org/web/20220209005630/https://www.baseball-reference.com/leaders/WAR_career.shtml" rel="noopener noreferrer">目前按WAR（胜利贡献值）排名第32位</a>）。他的体重并不是问题，但如果你阅读其他伟大但体重较重或身高较矮的球员的球探报告，他们经常被低估。当然，棒球球探报告不仅仅看人的外表，但球探通常被他们认为运动员“应该”长成的样子严重地影响了判断。</p>

<p>因为使用统计数据在棒球界已经“胜出”（如今顶尖球队都雇佣着一帮统计学家）且“老派”人士不想承认这一点，我们经常听到有人说，使用统计数据实际上并没有带来与过去不同的结果。但这是非常错误的，人们举出的例子通常<a href="https://www.patreon.com/posts/62223485" rel="noopener noreferrer">自相矛盾</a>。例如，以下是《体育画报》对此事的看法：</p>

<blockquote>
<p>媒体和网络的选秀预测者喜欢强调马德里加尔“顽强拼搏的小个子”特质，声称现代赛博统计学（sabermetrics）帮助球探们纳入了早先被忽视的矮个子球员。当然，这纯粹是胡扯。球员的能力决定了他对球探的吸引力——而不是身高或体型——而且矮小的球员一直是棒球的常客——从梅尔·奥特到乔·摩根，再到柯比·帕克特和何塞·奥图维。
</p>
</blockquote>

<p>用这些例子来支持球探制度是很奇怪的，因为柯比·帕克特在球探眼中是出了名地被忽视，尽管他打出了统计上占主导地位的表现，而他之所以能成为棒球运动员，纯属偶然，当时双城队<a href="https://en.wikipedia.org/wiki/Farm_team" rel="noopener noreferrer">小联盟体系</a>的助理总监去观看自己儿子的棒球比赛时，正好看到了柯比·帕克特，这才导致双城队选中了他，而他后来带领球队征战了十年。</p>

<p>乔·摩根同样出了名地被忽视，只是通过偶然机会才得以成为职业棒球运动员。摩根在高中时期打出了统计上占主导地位的数据，但因为身高被忽略了。由于没有被职业球队选中，他进入了奥克兰城市学院，在那里他再次打出了被忽视的出色数据。一支球队注意到他的原因是两个巧合的结合。首先，一支新的棒球队成立了，这支球队需要填补阵容和相关的小联盟体系，这意味着他们需要大量球员。其次，这支新球队需要雇佣球探，于是雇佣了比尔·怀特（他之前没有当过球探）。怀特以不具有其他几乎所有球探都有的外貌偏见而闻名，并因签下“长相滑稽”的棒球运动员而被嘲笑。比尔说服了这支新球队“雇用”了不少被忽视的球员，包括乔·摩根。</p>

<p>梅尔·奥特同样是出了名地被忽视，只是通过偶然机会才得以成为职业棒球运动员。他在高中时表现如此突出，以至于在业余时间还为成人半职业球队打球。然而，当他毕业时，职业棒球队因为他太矮而不想要他，于是他在一家木材公司找了份工作，并为公司球队打球。木材公司的老板对他的棒球技术印象深刻，幸运的是，这位老板与一支棒球球队的老板是商业伙伴和朋友，于是他实际上为奥特在职业球队争取到了一个位置，最终成就了<a href="https://web.archive.org/web/20220208221047/https://www.baseball-reference.com/players/o/ottme01.shtml" rel="noopener noreferrer">按WAR排名历史第20位的职业生涯</a><sup id="fnref:J"><a href="#fn:J" rel="noopener noreferrer">1</a></sup>。大多数矮个子棒球运动员可能都没有得到偶然的幸运机会；每有一个得到的，很可能就有很多没有得到的。如果我们看看有多少几乎被忽视但幸运的球员打出了使他们成为历史伟大的数据，似乎极有可能，有史以来最有潜力的顶级球员中，大多数曾在业余或半职业棒球打球的人都被忽视了，没有打上职业棒球（如果这看起来难以置信，那么在阅读接下来关于国际象棋、围棋和将棋的部分时，考虑一下如果去掉所有那些根据人们认为在大科技公司认知能力出众的刻板印象“看起来不像”会很厉害的选手，然后再看看还剩下多少历史级的顶尖选手）。</p></details><p></p>

<p>决定为棒球队“雇用”谁是一个涉及数百万美元（按2022年美元计）的高风险决策，但球队并没有尝试认真量化生产力，而是基于各种无关因素来决定选秀（雇用）谁。像任何主要运动一样，棒球生产力比大多数现实世界中的努力更容易量化，因为比赛比“真正”的问题简单得多。而且，在美国主要运动中，棒球是最容易量化的运动，但这<a href="https://danluu.com/tech-discrimination/" rel="noopener noreferrer">并没有阻止棒球队在一个世纪里过度关注诸如身高和种族等显而易见的视觉标准</a>。</p>

<p>前几天我看到推特上有一个帖子，一位非常成功的人谈到了他们是如何起步的，说他们尽管资历不足，但还是设法说服了一所精英学府录取了他们，并用这个故事得出结论：精英机构的把关者基本上就是在寻找人才，而你只需要向人们展示你有才华就行，这让我想起了这些老派的棒球球探报告：</p>

<blockquote>
<p>关于大学的一个例子：我设法进入了卡内基梅隆大学，尽管我的成绩很糟，SAT分数也很低（我参加考试时感冒了：/）。

</p><p>我花了一个月时间研究了CMU计算机系的一切，然后开车直接去那里和教授们交谈。当我第一次到校园时，招生办公室问了我的GPA和SAT分数，然后请我离开。但我设法和一位教授谈了话，他把我推荐给了他的上级，如此递归下去，直到我和该校的副校长谈上了话。他问我为什么我足够好到能上CMU，我说“我不确定我够格。其他孩子都非常聪明。我现在可以离开了”，他打断了我，提醒我进入这个房间需要多大的主动性。

</p><p>他当场给了我一封手写的录取通知书……我认为一个秘密，至少就把关者而言，是他们通常只在寻找高主动性和才华。
</p>
</blockquote>

<p>我从其他成功人士那里也听过类似的故事，他们往往会得出双峰式的结论，关于这一切意味着什么。一些人认为世界正确地认可了他们的才华，世界就是这么运作的；才华得到认可和回报。另一些人则认为世界在回报才华方面相当随机，他们很幸运地因为自己的才华得到了回报，而许多其他使用类似策略、拥有类似才华的人却被忽略了<sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">2</a></sup>。</p>

<p>另一次让我想起老式棒球球探报告，是我听说我一位朋友的故事，她现在是一所顶尖加拿大大学的工程学教授。我们称她为简。当简还是她现在任教的大学的本科生时，在校园里有时会被人好心地问“你迷路了吗？”。有时这是因为作为女性，她出现在工程楼里看起来不太对劲。有时则是因为她看起来和说起来像来自加拿大乡村的人。有一次，保安以为她是误入校园的流浪汉。几年后，在大学朋友的帮助下——他们向她解释了应该如何说话和穿衣——她学会了合适的着装和举止，从而能够融入“那种人”。但当她年轻时，人们的第一印象认为她是行政助理，而现在他们的第一印象认为她是教授的妻子，因为他们没料到一个女性会是她所在院系的教授。她相当成功，但这比那些看起来更像样的人需要付出更多的努力。</p>

<p>关于她的把关者是否真的只看重主动性和才华，她有一次在土木工程考试中不及格，因为她从未听说过“玉米狗”（一种裹着玉米面糊的热狗），而在她勉强通过的一门入门编程课上，教授曾宣布任何还没学会编程的人都会不及格。</p>

<p>玉米狗考试不及格是因为土木工程考试中有一道题要求学生设计一个玉米狗分发器。我的朋友从未听说过玉米狗，就问教授什么是玉米狗。教授不相信她不知道玉米狗是什么，在全班面前斥责她提了一个明显不严肃的问题。由于不知道什么是玉米狗，她设计了一个将玉米放进热狗里、然后分发夹着玉米的热狗的装置，结果不及格，因为那不是玉米狗。</p>

<p>结果证明，土木工程和编程的把关者实际上并不是在寻找主动性，而是在寻找有合适背景的人。我怀疑这与那位当场录取了一位有前途的学生的CMU教授没有太大不同，只是碰巧有很多人把“成绩和SAT分数糟糕、有故事的聪明少年”模式匹配为“有前途的潜在天才”，而把“来自加拿大乡村、高中班级成绩第一、没怎么用过计算机、因为边打工边抚养弟弟边上大学（父母基本抛弃了他们）而穿着像加拿大乡村穷苦女孩”的模式匹配为“不属于工程领域的流浪汉”。</p>

<p>另一件让我想起棒球球探报告有趣之处的事情，是我前一阵子和本·库恩的一次对话。</p>

<p><strong>我</strong>：在我这个级别（高级_staff工程师），大型科技公司的男性里，高个子真多，这很奇怪。就我最近的记忆，在这个级别或以上的会议中，我似乎只遇到过一个比我矮的男性。我只比美国平均水平矮1英寸！而且那个比我矮的人至少远程工作了十年，所以我不知道人们是否真正注意到他的身高。而且在管理岗位上，人们似乎甚至更高。如果我看看我参加过会议的副总裁们，他们肯定至少有6英尺高（约1.83米）。<br>
<strong>本</strong>：也许我可以在一家大科技公司当副总裁。我身高6英尺！<br>
<strong>我</strong>：哦，我猜我不知道6英尺有多高。我开会时遇到的副总裁们明显比你高。他们可能至少有6英尺2英寸（约1.88米）？<br>
<strong>本</strong>：哇，那作为最低要求真的很高了。6英尺2英寸在美国成年男性中属于第96百分位数<br></p>

<p>当我与在大公司（科技公司、咨询公司等）工作的成功人士讨论这个问题时，那些按普通标准被认为高个子的男性，比如6英尺或6英尺1英寸（约1.85-1.88米），告诉我他们在重要会议中经常是房间里最矮的男性。6英尺1英寸略低于棒球运动员的平均身高。身高与作为咨询顾问或程序员的成功相关性似乎比在棒球领域更高，而身高在棒球领域直接带来优势，这有点奇怪。一个可能的解释是<a href="https://en.wikipedia.org/wiki/Halo_effect" rel="noopener noreferrer">晕轮效应</a>，即对高个子或看起来有权威感的人的正面联想有助于他们的成功。</p>

<p>当我在网上看到这个讨论时，有人会指出这是因为身高与认知表现相关。但如果我们看看IQ（智商）文献，这种相关性并不强，不足以解释像这样的情况。如果我们看看人们的心理敏锐度被IQ测试以外的东西直接测试的领域，比如国际象棋，其中大多数顶尖棋手身高在平均水平左右，有一些双向的离群值，我们也能观察到这一点。即使不详细看数据，这也应该是预料之中的，因为身高和IQ之间的相关性很弱，其中大部分相关性源于低端的关系<sup id="fnref:Q"><a href="#fn:Q" rel="noopener noreferrer">3</a></sup>，而IQ与各种心理任务表现之间的相关性也很弱（有人会说按照社会科学的标准是强相关，<a href="https://danluu.com/percentile-latency/" rel="noopener noreferrer">但即使从人口水平来看，其实际解释力也非常弱，在个体层面就更弱了</a>）。<a href="https://artscimedia.case.edu/wp-content/uploads/sites/141/2016/12/22143817/Burgoyne-Sala-Gobet-Macnamara-Campitelli-Hambrick-2016.pdf" rel="noopener noreferrer">如果我们具体看看国际象棋，我们可以看到相关性很弱，正如预期的那样</a>。</p>

<p>既然相关性很弱，而平均水平附近的人数又远多于其他水平，我们应该预期大多数顶尖国际象棋棋手身高在平均水平左右。<a href="https://www.chess.com/forum/view/chess-players/do-chess-players-tend-to-be-short" rel="noopener noreferrer">如果我们看看近代历史上最顶尖的棋手，卡尔森、阿南德和卡斯帕罗夫</a>，他们身高分别是5英尺8英寸（约1.73米）、5英尺8英寸和5英尺9英寸（约1.75米）（如果你查看不同来源，他们会声称身高上下浮动几英寸，但仍然在相当正常的范围内；人们经常夸大身高；如果你看看那些试图通过照片或亲身进行真实比较的人，测量身高往往低于人们声称的自己身高<sup id="fnref:O"><a href="#fn:O" rel="noopener noreferrer">4</a></sup>）。</p>

<p>虽然很难找到围棋和将棋棋手的身高，但我能找到身高的<a href="https://gameofgo.app/learn/top-ten-go-players" rel="noopener noreferrer">顶尖现代棋手</a>（李世石、羽生善治）似乎大致在正常范围内，而在那些不是史上最顶尖的精英棋手中，有一些双向的离群值，这和国际象棋一样。</p>

<p>如果身高或其他外貌因素与心理表现有很强的相关性，我们应该预期在那些相对直接测量心理表现的活动（如国际象棋）中，身高与表现的相关性要比身高与职业成功的相关性强得多，但情况恰恰相反，这似乎表明身高的晕轮效应比任何与身高相关的潜在好处都要强。</p>

<p>如果我们看看那些在人们真正展示技能之前存在大量筛选、但表现可以相当准确地衡量、并且雇用更好的员工会对公司业绩产生直接、可衡量、直接影响的领域，比如<a href="https://danluu.com/tech-discrimination/" rel="noopener noreferrer">棒球和冰球，我们可以看到，在有关数据驱动方法发现人们直觉存在巨大漏洞的公开讨论出现几十年后，人们仍然依赖直觉而不是数据</a>。</p>

<p>如果我们再看看编程，其中准确衡量个人表现介于极其困难到不可能之间，个人表现对公司成功的影响也远不如体育运动直接，那么我们对人才评估准确性的预期应该是什么？</p>

<p>悲观的看法是，我们似乎不太可能期望人才评估比体育领域更好，在体育领域，尽管有相当准确和严谨的公开表现评估报告，公司花了数十年才认真对待人才评估。而在编程领域，人才评估甚至还没有发展到可以准确评估整个行业人员的阶段，所以我们甚至还没有开始那场长达数十年的公司坚持基于个人意见而不是准确测量来评估人员的斗争。</p>

<p>在招聘阶段，工作有多个层次的过滤器，体现了人们的偏见。这方面的一个经典研究是<a href="https://www.nber.org/system/files/working_papers/w9873/w9873.pdf" rel="noopener noreferrer">伯特兰和森迪尔·穆来纳森</a>的论文，该论文发现，简历上“听起来像白人”的名字比“听起来像黑人”的名字获得更多面试回电，并且在简历上使用“听起来像白人”的名字会提高简历上拥有更好资历所带来的回报。此后，有许多该研究的变体被完成，例如<a href="https://www.utpjournals.press/doi/pdf/10.3138/cpp.2017-033" rel="noopener noreferrer">带有白人名字的简历比带有亚裔名字的简历表现更好</a>，<a href="https://www.researchgate.net/profile/Juan-Madera-2/publication/266589756_The_Problem_for_Black_Professors_Judged_Before_Met/links/54381fea0cf24a6ddb922d57/The-Problem-for-Black-Professors-Judged-Before-Met.pdf" rel="noopener noreferrer">简历上带有白人名字的教授在人际交往技能方面被认为优于简历上带有黑人和亚裔名字的教授</a>，等等。</p>

<p>关于晋升和定级的文献要薄弱得多，但我和其他处于高度筛选环境中的人（比如副总裁、资深个人贡献者、精英大学的教授等），都观察到身高的过滤在大型科技公司或咨询公司等机构中与棒球一样严重，甚至更严重，但不如篮球。</p>

<p>这很奇怪，因为在智力追求中，“晋升”标准直接由表现决定，例如在国际象棋中，身高似乎只与成功有非常弱的相关性。文献中的一个主要问题是，通常，社会科学家考察的是平均值。在许多研究中，他们只是给出一个相关系数。运气好的话，他们可能会画一个图表，为每个身高平均某个数值。这是最简单的做法，但这只能让我们对正在发生的事情有一个非常粗略的了解。</p>

<p>因为我想了解事物运作的方式，包括组织和人们的意见，我（非正式地、口头地）询问了许多工程师对其他工程师的看法。我发现存在很多意见的集群，导致对谁做出了出色工作有粗略共识的人群。在每个集群内部，人们通常会对工程师的排名有不同意见，但他们会大致同意谁是“好到优秀”的。</p>

<p>一个集群是（在我看来；当然，这也可能只是我自己的偏见）那些关注人们产出的成果并以此评判人的人。另一个集群是关注身高和自信的组合，并以此评判人的人。对我来说，这曾是个谜（很久以前，在我有写这篇文章的想法之前，我就习惯性地问人们这样的问题并整理数据，在我认识到这个模式之前，我发现奇怪的是，许多具有良好技术判断力的人——由他们做好工作的能力以及显示出良好技术判断力的评论证明——高度评价许多经常说明显错误的话、并生产出效果不佳甚至根本不工作的系统的人）。还有一个集群围绕资历，比如某人上的学校、职级或曾工作过的知名公司。人们可能拥有来自多个集群的判断力，例如，有些人会赞扬既做了出色技术工作的人，也赞扬高大自信的人。在更高层级，判断人们的工作变得更加困难，相对较少的人基于产出进行评判。</p>

<p>当我在我工作的那家初创公司做这项评估整理工作时，基本上只有一个集群，就是基于人们的产出，对于谁是顶尖工程师有相当广泛的共识，但在我工作过的任何大公司里我都没有看到过这种情况。我不会说那意味着那家初创公司的评估是公平的（也许我们所有人都陷入了同样的偏见），但至少我们没有陷入最明显的偏见。</p>

<p>回到大公司，如果我们看看改革晋升制度需要什么，在存在偏见的情况下似乎很难做到，因为许多个体工程师是有偏见的。一些公司让委员会处理晋升以减少偏见，但系统的主要输入仍然带有强烈偏见。委员会使用的输入是来自许多人的推荐，而这些人中的许多人让那些偏见比他们的技术判断权重更大。即使我们假设引入了一个系统，该系统能够识别谁的判断与与表现无直接关系的因素高度相关，并且不给予这些推荐任何权重，人们的意见常常限制了某人能做的工作。我听到一些初级员工抱怨说，他们无法晋升，因为他们的工作不符合晋升标准。当他们要求被允许做可能让他们晋升的工作时，却被告知他们资历太浅，不适合做那种工作。通常他们会停留在当前级别，直到找到一个足够相信他们潜力的经理，给予他们可能通过出色表现带来晋升的工作。另一个与此交互的因素是，如果你表现良好和/或拥有高“晋升速度”（即频繁晋升），调到有高影响力工作机会的团队会更容易，而如果你表现不佳，或者只是晋升速度低且表现不是特别差，调岗会更难。在更高层级，无法从事高影响力工作并不常见，但要区分个人表现和偏见的影响也非常困难，因为很多表现关乎你能影响谁，而这需要尝试影响那些可能有偏见的人（如果你需要大规模地做到这一点，而要晋升到更高层级通常需要），如果嵌套的、多层次的偏见影响使得改变系统以消除偏见影响变得困难。</p>

<p>虽然从整体系统来看很容易变得悲观，但当看个人能做什么时，也很容易变得乐观。做比尔·怀特（那位以推荐“长相滑稽”的棒球运动员而闻名的球探）所做的事情，忽略其他人错误地认为重要的事情<sup id="fnref:F"><a href="#fn:F" rel="noopener noreferrer">5</a></sup>，是相当容易的。<a href="https://danluu.com/culture/#appendix-centaur-s-hiring-process" rel="noopener noreferrer">我曾在一家这样做的公司工作过，它拥有我工作过的所有公司中最好的工程团队</a>。他们通过忽略其他公司关心的标准来做到这一点，例如<a href="https://twitter.com/danluu/status/1425514112642080773" rel="noopener noreferrer">雇用非精英学校的人，而不是关注出身背景</a>，<a href="https://danluu.com/algorithms-interviews/" rel="noopener noreferrer">不因为没有练习解决工作中实际不解决的抽象白板问题而淘汰人</a>，<a href="https://twitter.com/danluu/status/1447322487583244289" rel="noopener noreferrer">没有与工作表现无关的文化契合度标准</a>（他们确实关心人们是否自主，并在高度独立时能有效运作），等等<sup id="fnref:C"><a href="#fn:C" rel="noopener noreferrer">6</a></sup>。</p>

<p>感谢<b><a href="https://www.reforge.com/all-programs?utm_source=danluu&amp;utm_medium=referral&amp;utm_campaign=spring22_newsletter_test&amp;utm_term=&amp;utm_content=engineering" rel="noopener noreferrer">Reforge - 工程项目</a></b>和<b><a href="https://flatironsdevelopment.com/" rel="noopener noreferrer">Flatirons Development</a></b>通过<a href="https://patreon.com/danluu" rel="noopener noreferrer">在主要赞助商级别赞助我</a>，帮助使这篇文章得以发表。</p>

<p>同时，感谢Peter Bhat Harkins, Yossi Kreinin, Pam Wolf, Laurie Tratt, Leah Hanson, Kate Meyer, Heath Borders, Leo T M, Valentin Hartmann, Sam El-Borai, Vaibhav Sagar, Nat Welch, Michael Malis, Ori Berstein, Sophia Wisdom, and Malte Skarupke 的评论/纠正/讨论。</p>

<h3 id="appendix-other-factors">附录：其他因素</h3>

<p>本文以身高作为一个贯穿始终的例子，因为它在男性中是容易观察到的与成功相关的因素，并且已在许多领域进行过研究。我猜测社会阶层标志/<a href="https://twitter.com/danluu/status/1480616193761288194" rel="noopener noreferrer">举止</a>（如本文中的简的例子）至少有同样的影响。例如，许多人向我指出，他们周围的高个子成功人士说话时信心十足（常常是不正确的事情，但说得很自信），并且拥有传达自信和权威的举止。</p>

<p>其他身体因素似乎也有很大影响。有相当多的文献研究晕轮效应如何导致通常有吸引力的人在各种维度上获得更高评价，例如<a href="https://www.gwern.net/docs/sociology/2021-klebl.pdf" rel="noopener noreferrer">道德品质</a>。在Ask Metafilter（Reddit之前的一个类似论坛）上有一个著名的回答，问题是“你怎么知道一个人是坏人？”，最受欢迎的答案（我希望是出于反讽的原因，尽管回答者看起来很认真）是他们牙齿不好。当然，<a href="https://twitter.com/danluu/status/1477001184904814593" rel="noopener noreferrer">在美国，牙齿不好是童年贫困的标志</a>，而非道德品质的贫乏。当然，性别是另一个人们似乎基于与才华或能力无关的原因进行筛选的维度。</p>

<p>另一个因素就是纯粹的运气。回到棒球的例子，关于奇珀·琼斯少数几个负面球探报告之一来自一位球探，他说</p>

<blockquote>
<p>挥棒时不够积极。无论从哪一侧都无法扎实击球。始终表现出漫不经心的态度。他让我失望。在8场比赛中，他只击出1安打，很少有扎实击球。左侧显示出击打式挥棒……2级工具
</p>
</blockquote>

<p>另一位看到他更典型表现的球探正确地指出</p>

<blockquote>
<p>明确的大联盟前景……所有方面均达到或超过大联盟工具水准……凭借出色的本能、能力和比赛知识。具有超级明星潜力。
</p>
</blockquote>

<p>另一位同样指出：</p>

<blockquote>
<p>这孩子拥有一切工具。力量出色，在本垒板前挥棒速度好，基本功扎实。性格和训练习惯出色。过去7年我在佛罗里达看到的最佳前景……这孩子必须被考虑为我们（首轮选秀）的 picks。各方面都出色且轻松自如。
</p>
</blockquote>

<p>表现存在很大方差。如果你通过短时间内观察某人来评判表现，你会根据观察时间的不同而得到截然不同的判断。</p>

<h3 id="appendix-related-discussions">附录：相关讨论</h3>

<p>如果你读过每个人都引用的那个盲听交响乐团试奏的研究，该研究本身似乎质量不高且缺乏说服力，但盲听试奏确实伴随着乐团雇佣那些不符合人们预期音乐家形象的人。在可能的情况下，盲听试奏似乎是值得尝试的好方法。</p>

<p>如前所述，一位教授提到，通过Zoom进行招聘偶然使得身高不如平时那么显眼，并导致至少一个大学院系雇佣了一些明显没有之前雇佣的教授那么高的教授。</p>

<p>我写的<a href="https://danluu.com/algorithms-interviews/" rel="noopener noreferrer">关于科技面试甚至不能有效过滤其名义上要过滤的主要内容</a>。</p>

<p>我写的<a href="https://danluu.com/programmer-moneyball/" rel="noopener noreferrer">关于科技招聘如何注重声望</a>。</p>

<p>@ArtiKel 写的关于<a href="https://nintil.com/talent-review" rel="noopener noreferrer">考恩和格罗斯关于人才的书</a>以及<a href="https://nintil.com/categories/fund-people-not-projects/" rel="noopener noreferrer">资助个人而非项目</a>。我长期以来的一个问题是，那些通过某种人才选拔过程传递声望的非主流项目（蒂尔奖学金、泰勒·考恩、帕特里克·科里森、斯科特·亚历山大等人的资助）是否比传统选拔过程偏见更少，还是只是偏见不同。这本书似乎没有真正回答这个问题，但它引人深思。顺便说一句，我认为这些替代过程极具价值，即使它们并不更好，实际上，即使它们稍差，因为它们的存在为世界提供了更广泛的人才发掘组合。但即便如此，我还是想知道这些替代过程是否比传统过程更好。</p>

<p>Alexy Guezy 写的关于<a href="https://guzey.com/where-does-talent-come-from/" rel="noopener noreferrer">人才从何而来</a>。</p>

<p><a href="https://marginalrevolution.com/marginalrevolution/2020/02/an-anonymous-reader-on-talent-misallocation-and-bureaucratization.html" rel="noopener noreferrer">一位匿名人士关于人才错配</a>。</p>

<p><a href="https://sockpuppet.org/blog/2015/03/06/the-hiring-post/" rel="noopener noreferrer">Thomas Ptacek 关于在科技招聘中实际尝试寻找相关信号</a>。</p>

<p><a href="https://www.patreon.com/posts/62933244" rel="noopener noreferrer">我关于在解释智商和人才重要性的类比中使用的手法，其中手法旨在让智商看起来比实际更重要</a>。</p>

<p><a href="https://web.archive.org/web/20140831182249/https://newrepublic.com/article/119239/transgender-people-can-explain-why-women-dont-advance-work" rel="noopener noreferrer">Jessica Nordell 关于跨性别者的经历展示了男女待遇差异</a>。</p>

<p><a href="https://amzn.to/3I9Sqg1" rel="noopener noreferrer">《点球成金》这本书，当然</a>。不过，要了解真正的极客细节，我推荐阅读旧的<a href="https://baseballthinkfactory.org/" rel="noopener noreferrer">棒球思考工厂档案</a>，当时这个网站还叫“棒球迷论坛”。球迷们实时指出谁会成功，并且通常比当时的棒球队取得了更大的成功。随着棒球队开始认真对待数据，将业余爱好者的分析抛在身后（因为球队可以接触到好得多的精细数据，以及比业余爱好者更多的时间进行严肃分析），该网站逐渐衰落，但观察业余爱好者使用基本数据分析技术完全主导职业领域是很有趣的。</p>
<div>

<hr>

<ol>
<li id="fn:J">何塞·奥图维来自数据驱动决策的现代，因此不能作为反例。
<a href="#fnref:J" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:B">当我看到那些坐在桌子另一边、选择谁能加入精英机构的人的讨论时，也存在类似的双峰分裂。有些人坚信他们的判断基本上是完美的（“我就是知道”等等），而另一些人则认为，对人做判断是一个有噪声的过程，你最多只能得到微弱的信号。
<a href="#fnref:B" rel="noopener noreferrer"><sup>[返回]</sup></a>

</li><li id="fn:Q"><p>估计值在0到0.3之间，Teasdale等人发现相关性随时间推移而降低（推测是由于营养改善），而Teasdale等人发现，在尾部（身高底部2%）相关性显著强于平均水平，在顶部（身高顶部2%）则显著弱于平均水平，表明大部分整体相关性源于导致身高和智商同时降低的因素。

</p><p>通常，对于相关系数<code>x</code>，它将解释<code>x^2</code>的方差。所以，即使相关性在高端没有减弱，我们得到相关系数<code>0.3</code>，这也只解释了<code>0.3 = 0.09</code>的方差，即<code>1 - 0.09 = 0.91</code>将由其他因素解释。
<a href="#fnref:Q" rel="noopener noreferrer"><sup>[返回]</sup></a></p></li>
<li id="fn:O">当我做网上约会时，经常有人告诉我，我肯定比我声称的更高，因为他们习惯了其他人在约会资料上谎报身高，于是把我的身高和更大的数字联系起来了。
<a href="#fnref:O" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:F"><p>在被评估的一方，一个人在工作中能做什么，我注意到，除非人们熟悉我的工作，否则他们在团体互动（如会议）中通常会忽略我。历史上，对我来说有效并让人们停止忽略我的方法是：在短时间内完成数量不合理的高影响力工作（<a href="https://danluu.com/productivity-velocity/" rel="noopener noreferrer">同时不长时间工作</a>），通常是解决了一个人们认为在时间框架内不可能解决的问题，这使得人们很难不注意到我的工作；另一个是让一个看起来比我更有权威的人吸引房间的注意力，并让人们听我说话；还有就是找到那些更关心想法而非想法来源的群体（团队或组织）。最近，一些有效的方法是写这个博客，并使用那些缺乏人们作为能力代理指标的媒介（Slack，以及在较小程度上，视频通话）。</p>

<p>在某些情况下，疫情在某些方面偶然导致了这种情况的发生。例如，我的一位朋友向我提到，他们的大学院系在疫情期间进行了视频面试，并且第一次雇佣了一些明显没有之前那么高的教授。
<a href="#fnref:F" rel="noopener noreferrer"><sup>[返回]</sup></a></p>

</li><li id="fn:C"><p>在一家招聘和晋升存在偏见的公司，仍然可以独立于公司常规标准去发掘人才。对我来说效果很好的一种方法是雇佣实习生，因为实习生的招聘标准往往不那么严格。一旦某人被聘为实习生，如果他们的工作出色，并且你知道如何推销，就很容易让他们被聘为正式员工。</p>

<p>例如，在推特，我为我的团队雇用了两名实习生。其中一位，作为实习生，<a href="https://danluu.com/cgroup-throttling/" rel="noopener noreferrer">编写了解决容器节流问题的内核补丁</a>（在边际上，每年价值数亿美元），并作为正式员工继续从事出色的高影响力工作。另一位，作为实习生，构建了全公司范围内的性能剖析，这是许多正式员工级（staff+）工程师一直想解决但无人解决的问题，并将于今年秋天加入推特成为正式员工。在这两种情况下，这个人都因为愚蠢的理由被其他公司忽视。在前一个案例中，原因组合很有趣，包括地点/时区（澳大利亚），其他公司对雇用能利用其技能的人的工作不感兴趣。从与他们的交谈来看，他们显然对计算机性能有深入了解，这即使在拥有十年“系统”经验的工程师中也很罕见。他们在澳大利亚有工作机会，但在其他大科技公司从事性能工作的团队对在澳大利亚接受实习生并不真正感兴趣。对于这种专业知识，我很乐意将我的时间表调整得稍晚一些，直到他们适应，结果发现他们高度独立，几乎不需要指导就能适应（我们讨论了他们可以解决的一些问题，包括上述容器节流问题，然后他们带着解决问题的建议方案回来，然后解决了问题）。在后一个案例中，他们是一名大学早期阶段的学生。最受欢迎的雇主往往想要已经修过更多课程的学生，因此我们能够没有太大竞争地雇用他们。等到一个学生修完很多课程可能是一个平均而言不错的策略，但这个特定的实习生候选人已经写了一些对于那种经验水平来说不错的代码，并且他们表现出了很大的主动性（他们为了重新实现一个服务器以修复导致游戏濒死的错误，逆向工程了一个濒死游戏的服务器协议），这比你面试几乎任何寻找实习机会的大三学生能得到的积极信号强得多。</p>

<p>当然，你并不总是能获得有价值的技能信号，但如果你积极地发掘人才，你并不总是需要获得信号。如果你偶尔能获得可靠的信号，并且能雇用那些你拥有良好信号但被低估的人，这仍然是有价值的！对推特来说，在三个实习生季节里，我雇用了两名实习生，第一位已经达到了“staff”级别，第二位根据他们的技能和工作影响力，应该很快就能达到。在投资回报率方面，每年花大约30小时留意那些有明显信号表明可能非常高效的人，是我为公司所做的最有价值的事情之一。如果整个行业在招聘时开始使用有效的信号，投资回报率会大幅下降，但基于本文正文中讨论的原因，我预计进展会很慢，在我有生之年不会看到足够多的变化，使得这种工作变得低回报。
<a href="#fnref:C" rel="noopener noreferrer"><sup>[返回]</sup></a></p></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 18961 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/talent/</link>
      <guid isPermaLink="false">https://danluu.com/talent/</guid>
      <pubDate>Mon, 21 Feb 2022 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Twitter十年重大缓存事件回顾</title>
      <description>[AI 摘要] 本文梳理了Twitter自2012年至2022年间因缓存引发的重大严重事故，旨在保存历史知识并揭示故障模式。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文梳理了Twitter自2012年至2022年间因缓存引发的重大严重事故，旨在保存历史知识并揭示故障模式。</div><p><i>本文与姚越合著</i></p>

<p>本文汇编了Twitter从采用现行事故追踪系统JIRA（2012年）至2022年间，至少部分归因于缓存的严重事故（SEV-0或SEV-1级别，即最高严重等级分类）信息，并附带一件2012年之前的额外事故。不计额外事故，事故追踪系统中共记录了6起SEV-0和6起SEV-1事故至少部分与缓存相关，另有38起严重性较低的事故未在本文讨论。</p>

<p>我们记录这些历史知识的原因有二：首先，科技公司的历史知识流失速度极快，我们认为保存部分记录很有价值；其次，从特定角度审视事故与可靠性问题、将所有信息整合一处，有时能让某些规律变得尤为明显。</p>

<p>关于知识流失，当我们观察到关于某科技公司事件的病毒式推文或其他传播内容时，深入调查发现广泛流传的故事通常因平庸的原因而严重失真。其一，极度夸张的故事更易传播，因此往往被记住；其二，存在一种<a href="https://twitter.com/copyconstruct/status/1353487786163097601" rel="noopener noreferrer">前高管/副总裁宣扬个人功绩的特色产业</a>，这些故事往往（委婉地说）扭曲事实（尽管普通工程师也可能这样做，但传播最广的虚假故事常来自管理岗）。在这两种情况下，都存在一种<a href="https://en.wikipedia.org/wiki/Gresham%27s_law" rel="noopener noreferrer">故事领域的格雷欣法则</a>——错误故事往往压倒正确故事。</p>

<p>即便真心尝试理解事件经过，知识也会快速流失。在我们进行的事故分析项目中，近几年的文档和工单链接通常仍可访问（90%以上概率），但更早的链接失效风险显著增高，到2012年的内容时失效概率接近0%。有时，人们会将资料保存在锁定文档、邮件中，但这些资料常链接至已完全失效的内容。了解事件经过需与多人交流，而<a href="https://www.pnas.org/content/114/30/7758" rel="noopener noreferrer">由于人类记忆的特性，人们会提供需要整合的碎片化叙述</a><sup id="fnref:L"><a href="#fn:L" rel="noopener noreferrer">1</a></sup>。</p>

<p>关于特定角度的观察，<a href="https://danluu.com/postmortem-lessons/" rel="noopener noreferrer">广义审视失败并分类汇总所有失败固然有用</a>，但深入剖析特定类别的失败同样具有价值。例如，丽贝卡·艾萨克斯和丹·卢曾对Twitter故障转移测试（2018-2020年）进行（内部非公开）分析，发现多项促成运营变更的问题。从某种意义上说，分析中并无新信息，因为我们获取的信息均来自现有文档，但将信息整合一处后，多年间分散发生的事故中那些原本不明显变得清晰可见。</p>

<p>本文档不会在Twitter内部引发变更，因为缓存事故的规律及应对措施已进行过梳理，但将这些内容整理归档对外部人员可能仍有价值。</p>

<p>至于为何关注缓存故障（而非其他系统故障）——缓存相对常见地牵涉重大事故，正如姚越在内部Twitter故障故事分享会上（指代Twitter在运营方面的"黑暗时代"）的评论所示：</p>

<blockquote>
<p>目前每起事故都至少提及缓存。事实上，很长时间里，缓存可能是导致网站宕机的头号原因。</p>
<p>我入职前六个月，每次重启缓存服务器，在当今标准下都是SEV-0事故。顺利的话，重启一个缓存后，外部请求的成功率（SR）可能还有95%...</p>
</blockquote>

<p>此外，Twitter绝大部分缓存采用（memcached的分支版本）<sup id="fnref:R"><a href="#fn:R" rel="noopener noreferrer">2</a></sup>，该技术在其他领域广泛使用，使得相关知识比讨论完全定制的Twitter系统更具普适性。</p>

<p>更广泛地说，由于缓存结构简单，它是相对纯净的真实世界分布式系统常见故障模式案例来源。概念上，缓存服务器是高吞吐量、低延迟的RPC服务器加上管理数据（如内存/磁盘及键值索引）的库。对于内存缓存，数据管理侧应能轻松超越RPC侧性能（原生内存键值库每核心可达百万级QPS，而未采用用户空间网络、批处理/流水线等技术的原生RPC服务器性能可能不足其十分之一）。由于RPC栈外的组件简单，缓存可视为近似纯粹的RPC负载，这在服务密集型架构中常至关重要。</p>

<p>当规模与性能成为关注点时，缓存常采用分片集群，这使其受制于分布式系统的约束与陷阱（但因性能优先，同步问题较某些其他负载如强一致性分布式数据库而言强调较少）。同时，由于分布式系统的特性，缓存使用者将暴露于这些故障模式，并可能受其级联影响而受损。</p>

<p>缓存故障模式亦颇具研究价值，因为当缓存承担显著比例的请求或数据服务时，缓存中断或性能下降极易引发全面宕机——为缓存性能设计的架构不会（也不应）具备足以维持网站运行的备用数据库性能。</p>

<p>相较于多数负载，缓存对底层栈（如内核、固件、硬件等）的性能异常更为敏感，因其通常具有高吞吐量、低延迟的SLO（缓存的意义在于快速），且大量时间（排除用户空间网络等情况）耗费在内核（Twitter memcached运行常规内核网络时约80%）。此外，因缓存服务器常运行少量线程，易受共享底层资源（CPU、内存、磁盘等）的其他负载影响而被阻塞。高吞吐量与低延迟SLO会加剧导致"死亡螺旋"的正反馈循环——这是分布式系统的经典故障模式。</p>

<p>审视下文事故时，我们将发现多数事故并非源于缓存逻辑错误，而是某种异常引发了未被充分缓解的正反馈循环，最终演变为失控反馈循环。</p>

<p>因此，阅读下文事故时，关注缓存与栈上调用者及栈下交互层的互动方式将大有裨益。另一值得关注点是：重大事故多因先前事故的修复措施未完全执行，或工程师认定的严重运营问题未获优先处理所致。这与丽贝卡·艾萨克斯和丹·卢在故障转移测试失败原因分析中发现的常见模式一致。</p>

<h3 id="2011-08-sev-0">2011-08 (SEV-0)</h3>
<p>数月内，大量用户自主发起的变更（如用户名、显示名、密码）遭回滚。此风险持续存在数年。</p>
<h4 id="background">背景</h4>
<p>当时，Rails应用采用单线程工作器，由单个主进程管理健康检查、重新部署等。若工作器卡顿30秒，主进程将终止并重启该工作器。</p>
<p>团队在裸金属服务器上运行，未使用Mesos或Kubernetes等集群管理器。团队拥有硬件全权，负责内核升级等。</p>
<p><a href="https://www.metabrew.com/article/libketama-consistent-hashing-algo-memcached-clients" rel="noopener noreferrer">键的分片定位算法涉及哈希运算。若节点失效，原哈希至此节点的键将被哈希至其他节点</a>。每个工作器客户端独立进行路由决策以确定与哪个缓存分片通信，即各工作器独立判断缓存节点存活状态及键的位置。若客户端判定某主机"状态不良"，该主机将被"剔除"。</p>
<h4 id="incident">事件</h4>
<p>11月8日，某用户将姓名从[旧名]更改为[新名]。一周后，其用户名回滚为[旧名]。</p>
<p>11月8日至12月初，客服提交数十起类似工单。Twitter缺乏定位故障点的监控手段，因此前两周调查主要为Rails应用添加指标以定位问题根源。每次变更需与部署团队协调，耗时至少两小时。Rails应用监控完善后，所有线索指向缓存为问题源头。确定缓存是否为真正原因的全套变更又耗时一两周，包括添加缓存不一致性、缓存异常路径及主机剔除的监控指标。</p>
<p>添加监控后，某工程师在12月初的JIRA工单中评论：</p>
<blockquote>
<p>今日启用代码以查看缓存用户与数据库用户在将缓存用户数据写回数据库时的不同步程度。该比例约为0.2%...<br>检查Twitter上150名热门用户所在缓存分片数（理论上应不超过1个）。多数用户存在于至少两个分片，部分用户甚至多达六个。</p>
</blockquote>
<p>首个修复措施是避免将过期数据写回数据库。但这未解决同一数据在不同缓存分片存在多份副本的问题。第二个修复措施旨在减少键出现在多处的频率，即多次重试后再剔除主机。其逻辑是：若主机真正永久宕机，应触发警报，但宕机主机的警报未触发，因此导致剔除的错误应为暂时性，客户端持续重试应能找到键"应在的位置"。随后，为防止因主机暂时性错误过多导致键波动，延长了被剔除主机的屏蔽时间。</p>
<p>此变更经单个缓存测试后推广至全部缓存。推广后网站立即宕机，因剔除仍在发生，而更长的屏蔽时间导致后端承压。当时后端采用MySQL，其配置下高负载时响应时间可能无限延长。这导致工作器响应时间无限延长，主进程终止工作器，当此情况大规模发生时因可用工作器不足导致网站宕机。</p>
<p>回滚第二个修复后，用户仍可见过期数据——尽管过期数据不再写回数据库，但缓存更新可能发生在某位置的键上，而客户端随后从另一位置读取到该键的过期缓存副本。另一缓解措施是将用户数据缓存从高利用率集群迁移至低利用率集群。</p>
<p>进一步调试后发现，重试可解决"随机"原因导致的尾部延迟剔除，但仍有高剔除率源于非随机因素。监控指标显示有时出现高丢包率，且与入站数据包速率相关而非带宽占用。高数据包速率与丢包时段检查主机发现，CPU0耗费65%-70%时间处理软中断，表明丢包可能源于CPU0无法跟上数据包到达速率。</p>
<p>修复措施为设置<a href="https://www.kernel.org/doc/Documentation/IRQ-affinity.txt" rel="noopener noreferrer">IRQ亲和性</a>，将入站数据包处理分散至主机所有物理核心。部署修复后，新迁移用户数据的集群观察到丢包和缓存不一致，而原集群未出现。</p>
<p>时值12月底。检查其他集群发现部分集群也存在丢包。深入观察发现，丢包每20小时40分钟在特定主机上发生。所有问题主机均为特定<a href="https://en.wikipedia.org/wiki/Stock_keeping_unit" rel="noopener noreferrer">硬件SKU</a>与特定BIOS版本（最新版）。该SKU较早BIOS版本的主机正常。实为该BIOS版本的主机触发BMC每20小时40分钟执行高开销健康检查，期间内核被中断，无法处理任何数据包导致丢包。</p>
<p>内核团队约六个月前已发现此问题，并尝试推送内核配置变更（增大数据包环形缓冲区容量以防缓冲区溢出时暂时性问题导致丢包）进行修复。尽管该工单标记为已解决，但修复未广泛推广，原因不明。</p>
<p>快速缓解措施为错开主机重启时间，避免集群内数据包丢弃同步发生。</p>
<p>因BMC版本需与BIOS匹配且BMC无法回滚，无法通过回滚BIOS解决问题。为推进BMC和BIOS更新，硬件工程团队需进行紧急测试/验证（尽快完成），随后BIOS修复得以推广，丢包问题消失。</p>
<p>整体耗时约两个月。</p>
<p>但这并非彻底修复——主机剔除行为未变，任何导致部分（非全部）客户端剔除缓存分片的随机问题仍将引发不一致。修复此问题需改变缓存架构，这无法快速完成（耗时约两年）。</p>
<p><b>缓解措施/修复</b>：</p>
<ul>
<li>增加可见性</li>
<li>设置IRQ亲和性以避免CPU0过载</li>
<li>修复导致主机周期性丢包的固件问题</li>
<li>修复缓存架构以容忍分区而不引发不一致</li>
</ul>
<p><b>经验教训</b>：</p>
<ul>
<li>需提升可见性</li>
<li>运维缓存需具备底层系统知识</li>
<li>实施隔离变更（推广IRQ亲和性修复时迁移至新集群，此举混淆了问题——同时修复了一个丢包问题并引入了另一个）</li>
</ul>

<h3 id="2012-07-sev-1">2012-07 (SEV-1)</h3>
<p>非个性化趋势对约10%用户约10小时未显示，用户看到空白趋势框。</p>
<p>Rails应用更新部署后，趋势缓存停止返回结果。仅影响非个性化趋势（因个性化趋势由独立服务提供）。</p>
<p>两小时后确认因趋势缓存刷新守护进程段错误导致，原因为内存耗尽。根因是部署的变更在Trend对象中增加了Thrift字段，使趋势缓存刷新守护进程内存使用量超限。</p>
<p>虽有守护进程失败的警报，但其仅检查守护进程是否成功启动，未检查是否成功完成。</p>
<p>缓解措施/修复：</p>
<ul>
<li>增加ulimit</li>
<li>警报标准改为作业成功完成而非作业启动</li>
<li>全局仪表盘添加404率指标</li>
</ul>
<p>经验教训：</p>
<ul>
<li>警报标准应为作业成功而非作业启动</li>
</ul>

<h3 id="2012-07-sev-0">2012-07 (SEV-0)</h3>
<p>此为外界较知名的Twitter事故之一，因用户看到无图片和CSS的公共错误页面：</p>
<blockquote>
<p>Twitter currently down for &lt;% = reason %&gt;<br>We expect to be back in &lt;% = deadline %&gt;</p>
</blockquote>
<p>网站严重受损约四小时。</p>
<p>此事故的记录不甚完整（因该时期文档高度缺失，相关JIRA工单注明"此事故已进行深度事后分析与评审，关闭事故工单"，但书面文档大多已丢失）。</p>
<p>事故触发点为两排机架断电。对缓存的影响为：48台主机断电，一小时供电恢复后重启。其中37台主机的缓存因脚本预期存在的目录未挂载而启动失败。"手动"修复这些主机的布局耗时30分钟，随后缓存恢复。</p>
<p>该目录对运行缓存服务器（至少在当时Twitter的运行方式下）并非必需。但有一个脚本在启动时检查该目录是否存在，而一个月前从布局配置脚本中移除此目录时，该检查脚本未同步更新。</p>
<p>增加调试难度的另一因素是：主机重启后<code>/proc</code>未正确挂载。尽管这非事故原因，但其异常性导致确认此问题非事故部分且为需修复的独立紧急问题耗费时间。</p>
<p>若网站其余部分运行完美，上述缓存问题不应引发如此严重事故，但多因素叠加导致全站宕机持续较长时间。</p>
<p>其他问题包括：</p>
<ul>
<li>应5秒超时的慢请求未超时，反而持续30秒直至处理该请求的整个工作器进程被终止重启
<ul>
<li>原设计5秒超时的代码虽运行，但使用错误时间戳判断时长，故未触发超时</li>
</ul></li>
<li>用户数据服务恢复缓慢
<ul>
<li>故障期间日志记录消耗大量资源并引发极高GC压力</li>
</ul></li>
<li>多台非缓存主机重启后启动失败，问题包括卡在<code>fsck</code>或<code>PXE</code>引导循环</li>
<li>尽管网站和错误消息为静态，停机页面使用Ruby通配符，向用户显示模板消息
<ul>
<li>源于Twitter近期从Rails应用前端迁移至C++前端；错误资源直接复制而来，仍包含<a href="https://docs.ruby-lang.org/en/2.3.0/ERB.html" rel="noopener noreferrer">ERB模板</a></li>
</ul></li>
<li>CSS未加载，因网站CSS的加载源宕机</li>
<li>前端过载，健康检查发现分片异常时重启失败</li>
</ul>
<p>缓存缓解措施/修复：</p>
<ul>
<li>修复配置布局的软件以防未来复发</li>
<li>审计现有主机以修复当前受影响主机的问题</li>
<li>确保内核升级时挂载<code>/proc</code></li>
<li>建立软件更新/升级流程以降低未来引入缺陷概率</li>
<li>确保缓存主机（及其他主机）在故障域间更均匀分布</li>
</ul>
<p>其他缓解措施/修复（高度不完整）：</p>
<ul>
<li>在监控主机上设置磁盘/RAID健康与维护</li>
<li>将故障/异常主机送至SiteOps维修</li>
<li>移除停机页面的Ruby通配符</li>
<li>将CSS打包至停机页面，使其他组件宕机时CSS仍可用</li>
<li>为前端添加过载保护机制</li>
<li>将用户数据服务的日志库更换为开销更低的日志库，防止高错误率时GC压力导致服务终止</li>
<li>修复5秒超时以读取正确标头</li>
<li>在栈的其他层级添加独立超时机制，当请求完全无进展时亦应触发</li>
<li>更改前端健康检查与重启机制以强制终止节点而非尝试优雅关闭</li>
<li>确保任何时刻仅有一个健康检查脚本版本在节点上运行</li>
</ul>
<p>经验教训：</p>
<ul>
<li>需主动测试故障模式，包括导致主机重启或超时的故障模式</li>
<li>需制定机架多样性要求，使部分机架故障不会不成比例地影响少数服务</li>
</ul>

<h3 id="2013-01-sev-0">2013-01 (SEV-0)</h3>
<p>网站宕机3小时30分</p>
<p>负载增加（据我所知，当日属正常水平，非异常峰值）导致缓存尾部延迟上升。缓存尾部延迟上升源于新缓存主机未设置IRQ亲和性，导致队列积压和延迟升高。</p>
<p>缓存延迟升高加之推文服务使用缓存的设计，导致使用缓存的推文服务分片陷入GC死亡螺旋（延迟增加→待处理请求增多→GC压力增大→分片负载增加→延迟进一步升高），进而导致剩余分片负载增加。</p>
<p>当时，推文服务缓存与用户数据缓存共置于相同主机，每台主机承载1个推文服务缓存分片和2个用户数据缓存分片。推文服务缓存新增主机未引发问题。用户数据缓存于傍晚逐步新增主机，初始亦未引发问题。但晨间流量高峰（因同时接近亚洲和美国使用高峰，亚洲国家使用高峰常非"朝九晚五"工作时间，美国使用高峰在工作时间）触发IRQ亲和性问题。推文服务受IRQ亲和性问题影响远甚于用户数据服务。</p>
<p><b>缓解措施/修复</b>：</p>
<ul>
<li>缓存主机需设置IRQ亲和性（参照2011-08事故）
<ul>
<li>将此设为服务器默认配置，而非缓存主机单独作为临时变更执行</li>
</ul></li>
<li>调整推文服务设置
<ul>
<li>减少最大连接数</li>
<li>增加超时时间</li>
<li>未进行GC配置变更，因当时GC统计未作为指标导出，且GC日志未记录足够信息以判断不良GC设置是否为诱因</li>
</ul></li>
<li>调整所有使用缓存的服务设置
<ul>
<li>将连接限制调整至稳态值的约2倍</li>
</ul></li>
</ul>

<h3 id="2013-09-sev-1">2013-09 (SEV-1)</h3>
<p>单数据中心网站整体成功率降至92%。用户受影响约15分钟。</p>
<p>时间线服务失去其使用的75%某缓存的访问权限。缓存团队对该缓存进行服务器集变更，而时间线服务未采用推荐机制消费缓存服务器集路径，导致其"未知"哪些服务器为缓存服务器。</p>
<p><b>缓解措施/修复</b>：</p>
<ul>
<li>时间线服务采用推荐机制查找服务器集路径</li>
<li>审计所有消费服务器集路径的代码，确保无服务使用非推荐机制</li>
</ul>

<h3 id="2014-01-sev-0">2014-01 (SEV-0)</h3>
<p>单数据中心网站宕机，影响请求路由至该数据中心的用户约20分钟。</p>
<p>推文服务开始向缓存发送高负载。此前某变更移除了缓存连接数上限。当时缓存在约160万连接时将拒绝接受新连接。导致监控服务无法连接缓存分片，进而重启缓存分片引发宕机。</p>
<p>宕机前数月，已有五个工单描述此事故的多个诱因。</p>
<p>某工单（由C状态配置错误与SMI组合引发的较轻事故的后续）指出缓存在约160万连接时停止接受连接。某工程师深入调试此问题，查明原因并建议多种缓解路径。</p>
<p>关键因素之一是：尤其当缓存高负载时，尽管内核已建立TCP连接，缓存可能未<code>accept</code>连接。</p>
<p>客户端"未知"连接实际未对缓存开放，将发送请求并等待响应。若客户端"认为"需更高并发，Finagle可能开启多连接。150ms后请求超时。若缓存端队列过长，此时缓存可能尚未尝试处理该请求。</p>
<p>超时后，Finagle将重试并开启新连接，导致缓存分片每次过载加剧。</p>
<p>客户端侧，每个此类请求引发大量分配，导致高GC压力。</p>
<p>当时设置允许5次失败后将节点标记为不可用30秒，连接并行度为16，每个客户端尝试连接3台服务器。所有数值乘以分片数后，推文服务可在缓存接受连接限制前使其过载。</p>
<p>缓存侧有1个调度线程和N个工作线程。调度线程调用<code>listen</code>和<code>accept</code>，将工作放入工作线程队列。默认积压长度为1024。当<code>accept</code>因文件描述符限制失败时，调度线程在<code>listen</code>中将积压设为0，并忽略监听fd的所有事件。当连接关闭释放fd时，积压恢复为正常值并重新接受连接。</p>
<p>重大事故前观察到：当连接数"过高"时，连接开始被拒绝。一段时间后，拒绝连接产生的背压使缓存得以恢复。</p>
<p>问题的另一诱因是：某硬件SKU在缓存高负载下32KB页耗尽时发生OOM，增加未OOM的缓存负载。Twitter内核工程师在以下提交中修复：</p>
<pre><code>commit 96c7a2ff21501691587e1ae969b83cbec8b78e08
Author: Eric W. Biederman &lt;ebiederm@xmission.com&gt;
Date:   Mon Feb 10 14:25:41 2014 -0800

    fs/file.c:fdtable: avoid triggering OOMs from alloc_fdmem
    
    Recently due to a spike in connections per second memcached on 3
    separate boxes triggered the OOM killer from accept.  At the time the
    OOM killer was triggered there was 4GB out of 36GB free in zone 1.  The
    problem was that alloc_fdtable was allocating an order 3 page (32KiB) to
    hold a bitmap, and there was sufficient fragmentation that the largest
    page available was 8KiB.
    
    I find the logic that PAGE_ALLOC_COSTLY_ORDER can't fail pretty dubious
    but I do agree that order 3 allocations are very likely to succeed.
    
    There are always pathologies where order &gt; 0 allocations can fail when
    there are copious amounts of free memory available.  Using the pigeon
    hole principle it is easy to show that it requires 1 page more than 50%
    of the pages being free to guarantee an order 1 (8KiB) allocation will
    succeed, 1 page more than 75% of the pages being free to guarantee an
    order 2 (16KiB) allocation will succeed and 1 page more than 87.5%
    of the pages being free to guarantee an order 3 allocate will succeed.
    
    A server churning memory with a lot of small requests and replies like
    memcached is a common case that if anything can will skew the odds
    against large pages being available.
    
    Therefore let's not give external applications a practical way to kill
    linux server applications, and specify __GFP_NORETRY to the kmalloc in
    alloc_fdmem.  Unless I am misreading the code and by the time the code
    reaches should_alloc_retry in __alloc_pages_slowpath (where
    __GFP_NORETRY becomes signification).  We have already tried everything
    reasonable to allocate a page and the only thing left to do is wait.  So
    not waiting and falling back to vmalloc immediately seems like the
    reasonable thing to do even if there wasn't a chance of triggering the
    OOM killer.
    
    Signed-off-by: "Eric W. Biederman" &lt;ebiederm@xmission.com&gt;
    Cc: Eric Dumazet &lt;eric.dumazet@gmail.com&gt;
    Acked-by: David Rientjes &lt;rientjes@google.com&gt;
    Cc: Cong Wang &lt;cwang@twopensource.com&gt;
    Cc: &lt;stable@vger.kernel.org&gt;
    Signed-off-by: Andrew Morton &lt;akpm@linux-foundation.org&gt;
    Signed-off-by: Linus Torvalds &lt;torvalds@linux-foundation.org&gt;
</code></pre>
<p>此例再次说明<a href="https://danluu.com/in-house/" rel="noopener noreferrer">Twitter规模的公司拥有内核团队的价值</a>。</p>
<p>另一工单强调了缓存主机设置标准化的重要性（如IRQ亲和性、C状态、睿频加速、网卡绑定、固件版本），此为跟进更早工单中指出的推文服务部分主机延迟升高问题——最终查明源于某内核升级影响特定硬件SKU，因内核与固件版本交互导致SMI增加。</p>
<p><b>缓存缓解措施/修复</b>：</p>
<ul>
<li>将积压从1024降至128，使调度器过载时更快施加背压</li>
<li>降低文件描述符限制以避免部分分片内存耗尽</li>
<li>缓存使用固定哈希表大小以避免哈希表迁移期间大量分配和内存/CPU负载</li>
<li>低延迟memcached主机使用CPU亲和性</li>
</ul>
<p>这些缓解措施的测试表明，即便客户端修复尚未实施（防止其"试图"压垮缓存），这些措施也能防止缓存于类似事故条件下崩溃。</p>
<p><b>推文服务缓解措施/修复</b>：</p>
<ul>
<li>调整超时、重试和客户端并发连接设置以避免过载缓存</li>
</ul>
<p><b>经验教训</b>：</p>
<ul>
<li>硬件设置一致性至关重要</li>
<li>高积压深度后才施加背压可能危险</li>
<li>客户端设置重试策略时应"审慎计算"，避免采用在100%响应失败且施加最大背压时仍可能完全压垮缓存服务器的策略</li>
</ul>

<h3 id="2014-03-sev-0">2014-03 (SEV-0)</h3>
<p><a href="https://twitter.com/TheEllenShow/status/440322224407314432" rel="noopener noreferrer">艾伦·德杰尼勒斯在奥斯卡颁奖典礼期间发布的推文</a>被大量转发，导致搜索功能宕机约25分钟，并引发网站宕机，多数用户无法使用。</p>
<p>此事故涉及多环节。从缓存角度看，此为缓存因异常客户端行为过载的又一案例。</p>
<p>与2014-01事故相似，但当时为该事故实施的缓存侧缓解措施不足，因"攻击"客户端采用了比2014-01事故中推文服务更激进的参数，且此时部分缓存运行于共享Mesos的容器化环境，使其易受<a href="https://danluu.com/cgroup-throttling/" rel="noopener noreferrer">节流死亡螺旋</a>影响。</p>
<p>对此直接问题的主要修复为为Finagle memcached客户端增加流水线支持，使多数客户端仅需1-2个连接即可获得足够吞吐量，降低客户端持续冲击缓存致其崩溃的概率。</p>
<p>其他服务实施近50项修复。部分主要主题：</p>
<ul>
<li>在适当时机施加背压
<ul>
<li>施加背压时避免重试</li>
</ul></li>
<li>确保数据（主要）流向同一数据中心以避免昂贵缓慢的跨数据中心流量</li>
<li>创建适当线程池防止关键工作被阻塞</li>
<li>为热点项目添加进程内缓存</li>
<li>高负载时返回查询的部分结果（不因结果不完整而拒绝请求）</li>
<li>制定缓存客户端配置指南以防对缓存发起DDoS</li>
</ul>

<h3 id="2016-01-sev-0">2016-01 (SEV-0)</h3>
<p>日本前偶像团体SMAP（后转型为流行乐团及日本收视率常居首位的综艺节目主持人）举行发布会否认解散谣言。此事件导致单数据中心宕机，影响路由至该数据中心的用户约20分钟（直至流量切换至其他数据中心）。受损数据中心服务恢复耗时约六小时。</p>
<p>某数据中心推文服务负载激增，导致39台缓存分片主机OOM终止进程。集群管理器未自动将死亡节点移出服务器集——因死亡节点过多（节点少量故障时自动移除机制仍有效，但节点大量故障时自动化操作可能加剧灾难性故障——从缓存服务器集移除节点可能导致持久存储流量激增）。缓存运维人员手动清理死亡节点后，应重启节点的服务因Puppet变更意外移除了该服务的缓存相关配置而未能重启节点。回滚不良Puppet提交后，缓存分片启动但初期恢复过慢，随后恢复过快，导致推文服务成功率恢复耗时延长。</p>
<p>缓存分片主机因内核套接字缓冲区内存分配过多而OOM。</p>
<p>初始修复为将主机TCP缓冲区大小限制为4GB，但压力测试失败，查明因高运行时间（2年）主机内存碎片导致失败，缓解措施为更频繁重启主机以清理碎片。</p>
<p><b>缓解措施/修复</b>：</p>
<ul>
<li>每两年重启主机一次以上</li>
<li>为缓存主机添加Puppet警报以检测破坏性Puppet变更</li>
<li>改进集群管理器以处理大规模变更（因更早的小型事故，此变更已在进行中）</li>
</ul>

<h3 id="2016-02-sev-1">2016-02 (SEV-1)</h3>
<p>此为上述2016-01 SEV-0事故中提到的压力测试失败。导致网站成功率轻微下降数分钟直至压力测试终止。</p>

<h3 id="2016-07-sev-1">2016-07 (SEV-1)</h3>
<p>计划中将用户数据缓存从专用主机迁移至Mesos引发单数据中心服务显著降级，随后另一数据中心轻微降级。部分现有用户受影响，所有基本为新用户注册失败约半小时。</p>
<p>集群管理器以最快速度将115个新缓存实例添加至服务器集，导致缓存命中率下降。预期集群管理器每20分钟添加1个分片，但配置变更意外修改了最小缓存集群规模，"迫使"集群管理器尽可能快速添加节点。</p>
<p>一次性添加大量节点使用户数据缓存命中率从常规99.8%降至84%。为阻止情况恶化，运维人员终止集群管理器以防其向服务器集添加更多节点，随后重新部署集群管理器至先前状态以恢复原配置，用户数据缓存命中率立即改善。</p>
<p>缓存命中率下降期间，备用数据库流量激增引发长时间GC暂停。导致缓存未命中的用户数据服务请求查询备用数据库时成功率为0%。</p>
<p>虽有限流机制防止过载备用数据库，但阈值设置过高未触发。为恢复备用数据库，运维人员执行滚动重启并部署严格限流。因单数据中心流量切换，严格限流在另一数据中心触发（因流量切换导致该数据中心流量升高），导致用户数据服务成功率轻微下降（请求被严格限流拒绝），故此事故亦影响未受初始缓存宕机影响的数据中心。</p>
<p><b>缓解措施/修复</b>：</p>
<ul>
<li>添加部署钩子，在向缓存集群添加或移除大量节点时警告运维人员</li>
<li>运维手册中增加部署、集群创建、扩容、缩容等操作的详细信息</li>
<li>为所有"零级"（关键）缓存部署添加检查清单</li>
</ul>

<h3 id="2018-04-sev-0">2018-04 (SEV-0)</h3>
<p>计划中的测试数据中心故障转移导致网站部分宕机约1小时。故障转移开始1分钟后发现成功率下降。故障转移测试立即回滚，但网站完全恢复耗时近1小时。</p>
<p>初始网站降级源于用户数据服务错误率升高，由缓存热点键导致。原设计有缓存热点键机制：采样1%事件（采样用于减少开销，逻辑是若键为热点，即使采样亦应被发现），将采样键放入带哈希映射的FIFO队列以统计各键出现频率。</p>
<p>尽管此机制在过往高负载事件中有效，但在某些情况下（虽非事故根因）因值过大效果不佳——1%采样率无法使缓存及时"发现"热点键（当存在大（因此昂贵）值时）。原始热点键检测逻辑为推文服务缓存设计（最大键约5KB），后被用于其他缓存（键可能大得多）。设计热点键提升时，用户数据缓存未纳入考量——因其时最热键由进程内缓存服务，无热点键问题。</p>
<p>大键问题因使用<code>FNV1-32</code>进行键哈希而加剧（忽略最低有效字节）。问题数据集的方差集中于最后字节，使用<code>FNV1-32</code>导致所有大值键存储于少量缓存分片。早在2014年即有建议迁移<code>FNV1-32</code>，且更现代的哈希函数已加入工具库，但部分缓存所有者未选择迁移。</p>
<p>因热点键提升逻辑未触发，热点缓存分片的流量饱和了配备1Gb网卡的分片的NIC带宽（Twitter硬件通常异构——除非确保集群仅含特定规格；许多缓存主机配备10Gb网卡，但许多也配备1Gb网卡）。</p>
<p>修复/缓解措施：</p>
<ul>
<li>调整用户数据缓存热点键检测</li>
<li>将相关缓存集群的所有硬件升级为配备10Gb网卡的主机</li>
<li>部分缓存从<code>FNV</code>迁移至<code>murmur3</code></li>
</ul>

<h3 id="2018-06-sev-1">2018-06 (SEV-1)</h3>
<p>测试数据中心故障转移期间，部分操作成功率降至约50%，直至约四分钟后中止测试故障转移。</p>
<p>缓存角度的问题在于：推文服务缓存分片处理流量的能力远低于预期（约50%），基于未反映真实流量的负载测试导致缓存配置不足。负载测试设置不真实的原因包括：</p>
<ul>
<li><a href="https://twitter.com/danluu/status/1360029773011984385" rel="noopener noreferrer">到达分布高度非独立，负载下因相关到达产生大尖峰</a>。通常假设恒定或泊松到达分布，但<a href="https://danluu.com/latency-pitfalls/#minutely-resolution" rel="noopener noreferrer">审视指标数据时发现，负载生成器常用的恒定或泊松假设在实际流量下可能完全错误</a>，导致真实负载与基于简单假设的负载生成器可达吞吐量差异无限大</li>
<li>负载测试使用的连接数远少于实际高负载时的连接数</li>
</ul>
<p>缓存性能下降的另一原因为：每分钟有一次容器化性能计数器采集运行十秒，此操作开销较高——因采集的计数器远多于硬件计数器数量，内核需执行高开销操作切换采集的计数器。</p>
<p>性能下降导致计数器采集窗口期间延迟升高，缓存分片无法在<a href="https://danluu.com/cgroup-throttling/" rel="noopener noreferrer">容器节流限制</a>前完成工作，延迟升高至推文服务请求超时。按配置，连续12次对单个缓存节点失败后，推文服务客户端将节点标记为死亡30秒并停止向其发送请求，导致节点30秒内无流量（客户端独立决定标记节点死亡）。这导致备用数据库请求量超过配额，请求被数据库拒绝，推文服务失败率升高。</p>
<p><b>缓解措施/修复</b>：</p>
<ul>
<li>推文服务客户端与缓存的连接数从4减至2，降低延迟
<ul>
<li>如前事故所述，添加流水线支持使缓存仅需1个客户端连接即可高效运行，但部分工程师担忧1个可能不足（因此前连接数远高），故选择4个"以防万一"。但标准Linux内核网络下，<a href="https://twitter.github.io/pelikan/2020/benchmark-adq.html" rel="noopener noreferrer">更多连接反而增加尾部延迟</a>，因此降低性能</li>
</ul></li>
<li>增加缓存节点以减少单个缓存分片负载</li>
<li>改进缓存热点键提升算法
<ul>
<li>此非特指本事故，但某工程师分析发现一年前引入的热点键提升算法在某缓存集群的缓存命中率仅约0.3%（源于多种问题）。改用更优算法显著提升缓存命中率与性能</li>
</ul></li>
<li>修改缓存验证流程，使确定容量（节点数）的缓存性能更贴近真实世界性能</li>
<li>详细分析多路复用性能计数器采集的开销</li>
</ul>
<p><i>感谢<b><a href="https://www.reforge.com/all-programs?utm_source=danluu&amp;utm_medium=referral&amp;utm_campaign=spring22_newsletter_test&amp;utm_term=&amp;utm_content=engineering" rel="noopener noreferrer">Reforge - 工程项目</a></b>和<b><a href="https://flatironsdevelopment.com/" rel="noopener noreferrer">Flatirons Development</a></b>通过<a href="https://patreon.com/danluu" rel="noopener noreferrer">以主要赞助商级别赞助我</a>，助力本文发布。</i></p><i>
</i><p><i>另感谢Michael Leinartas、Tao L.、Michael Motherwell、Jonathan Riechhold、Stephan Zuercher、Justin Blank、Jamie Brandon、John Hergenroeder和Ben Kuhn的评论/修正/讨论。</i></p>

<h3 id="appendix-pelikan-cache">附录：Pelikan缓存</h3>
<p><a href="https://twitter.github.io/pelikan/" rel="noopener noreferrer">Pelikan</a>旨在解决我们在大规模运维memcached和Redis时遇到的问题。<a href="https://twitter.github.io/pelikan/2019/why-pelikan.html" rel="noopener noreferrer">本文档</a>解释了Pelikan的部分设计动机。模块化/易于修改的特性使我们能发现新颖的缓存创新，例如<a href="https://twitter.com/danluu/status/1381687511362138113" rel="noopener noreferrer">解决现有淘汰算法问题的全新淘汰算法</a>。</p>
<p>就本文讨论的问题类型而言，生产环境测试表明Pelikan相比现有缓存具有更可预测的性能、更优的中位性能和更优的尾部性能，这意味着我们以更低成本获得更高可靠性与容量。</p>
<div>
<hr>
<ol>
<li id="fn:L"><p>知识高速流失并非Twitter独有。实际上，在我全职效力的所有公司中，我认为Twitter在知识保存方面做得最好。我曾任职的芯片公司Centaur除全面的错误报告外基本不认可书面文档，因此多种知识迅速流失。微软几乎同样糟糕——因文档默认锁定且需知即可，除少数拥有极广权限的人员外，几乎无人能查阅旧文档以了解历史脉络。</p>
<p>谷歌早期与Twitter现状相似，但随公司增长及对诉讼的担忧加剧（尤其在<a href="https://twitter.com/danluu/status/1172676081628766208" rel="noopener noreferrer">高管公开表示拟采取不道德和非法行动</a>的多起尴尬事件后），其管控更趋严格，形似微软。</p>
<a href="#fnref:L" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:R">亦有部分Redis分支的使用，但平均情况性能显著较差，且尾部性能相对平均性能更差。同时，因其设计，大规模运维的直接负担更高，限制了其在我们的应用。<p></p>
<a href="#fnref:R" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 21950 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/cache-incidents/</link>
      <guid isPermaLink="false">https://danluu.com/cache-incidents/</guid>
      <pubDate>Wed, 2 Feb 2022 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>鸡尾酒会想法</title>
      <description>[AI 摘要] 本文批评人们在社交场合中对复杂领域进行浅薄讨论的现象，揭示了表面认知与专业深度间的巨大鸿沟。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文批评人们在社交场合中对复杂领域进行浅薄讨论的现象，揭示了表面认知与专业深度间的巨大鸿沟。</div><p>你不必在派对上才能看到这种现象发生，但我经常在那些重视智力与聪明才智，却不同样重视实践知识或学术严谨性的社交圈的派对上，看到一种奇特的现象。人们常常讨论一些标准的时髦话题（我近期在多个派对上观察到的一些例子是：如何构建一个与谷歌搜索竞争的产品，以及如何解决交通建设成本高昂的问题），然后解释为什么该领域的现有从业者做法是错误的，接着再阐述他们自己会如何改进。我偶尔也会有符合这种模式的良好对话（与那些拥有深厚专业知识、多年来致力于改变该领域的人），但更常见的情况是，一个对某个领域只有“鸡尾酒会级别”了解的人，会对如何修复该领域提出自己的想法。</p>

<p>在我参加的那些充斥着这种肤浅伪技术讨论的派对上，询问人们为何认为他们的方案能解决该领域的有价值问题，已经成了我的一个爱好。当我询问细节时，我发现，在我有一定了解的领域里，人们<a href="https://danluu.com/sounds-easy/" rel="noopener noreferrer">通常不知道需要解决哪些子问题才能解决他们试图处理的问题，使得他们的方案毫无希望</a>。多次尝试之后，我的观点是，根本原因通常是许多对某个主题有肤浅理解的人，认为该主题的复杂度等同于他们对其理解的深度，而不是意识到仅仅知道一点皮毛意味着他们不了解该主题完整的复杂性。</p>

<p>由于我经常参加程序员的派对，这意味着我经常听到<a href="https://twitter.com/nick_r_cameron/status/1346174149044043776" rel="noopener noreferrer">程序员复述</a>他们<a href="https://twitter.com/danluu/status/1347269793578053632" rel="noopener noreferrer">对另一个领域鸡尾酒会级别的理解</a>（尽管上面提到了搜索引擎的例子）。如果你想在网上看看类似的评论，当程序员讨论“传统”工程领域时，通常可以看到。我欣赏的一个例子是<a href="https://twitter.com/danluu/status/1162469763374673920" rel="noopener noreferrer">这条推特线程，Hillel Wayne讨论了没有传统工程知识的程序员通常对传统工程的面貌持有不正确的看法</a>，其中许多回复来自对传统工程几乎一无所知的程序员，然后他们以自己的误解回复Hillel。当Hillel完成他的<a href="https://www.hillelwayne.com/tags/crossover-project/" rel="noopener noreferrer">交叉领域项目</a>（他采访了那些既在传统工程领域又在软件行业工作的人）时，<a href="https://twitter.com/danluu/status/1484268111687663620" rel="noopener noreferrer">他收到了更多此类评论</a>。即使人们被警告说，对一个领域的朴素看法很可能是错误的，许多人也忍不住，会立即对他们基本一无所知的领域发表自己的看法。</p>

<p>总之，在交叉领域项目中，Hillel比较了那些真正曾在多个领域工作的人对领域的看法与流行程序员对传统工程的看法。Hillel给出的众多例子之一是关于造桥的讨论，他指出程序员会说这样的话：</p>
<blockquote>
<p>一个真正工程师的世界的可预测性是令人羡慕的。但我们所处的世界总是在变化，物理定律每周都在改变。如果我们不能迅速适应不可预见的状况，唯一可预见的事件就是我们自身的毁灭。</p>
</blockquote>
<p>以及</p>
<blockquote>
<p>没有人会在桥梁建造中途考虑移动起点或终点。</p>
</blockquote>
<p>但是Hillel采访的一位土木工程师说，他们确实移动过一座桥！当然，土木工程师移动桥梁的频率不如程序员处理软件变更的频率，但是，如果你与实际工作的土木工程师交谈，许多土木工程师经常处理开始工作后需求变更的情况，这与程序员在工作中处理的情况并没有本质区别。那些在两个领域都工作过或至少与另一个领域的人交谈过的人，往往认为两个领域工程师面临的问题都很复杂，而那些对领域只有鸡尾酒会级别理解的人却经常声称他们不从事的那个领域很简单，不像自己的领域。</p>

<p>我常听程序员说，编程就像“必须在飞机飞行时建造它”，这隐含着编程比设计和制造飞机更难的论点，因为设计和制造飞机的人可以在飞机飞行之前完成这些工作<sup id="fnref:N"><a href="#fn:N" rel="noopener noreferrer">1</a></sup>。但是，当然，设计飞机的人也可以同样轻松地说，“天哪，如果我的飞机能达到4个9的正常运行时间，并且允许我的飞机每周崩溃一分钟并杀死所有乘客，那我的工作就会非常简单。”当然，不同项目类型和不同领域的约束使得不同的事情变得困难，但人们似乎常常很难看到其他领域存在而自己的领域没有的约束。人们可能认为，理解自己领域比外行天真想象的更复杂，会帮助人们理解其他领域也可能隐藏着复杂性，但情况通常并非如此。</p>

<p>如果我们看看Hillel引用的那句话的其余部分（来自Stack Exchange问题的最高票且被接受的答案），作者继续说道：</p>
<blockquote>
<p>当提前确切知道要求你预测的内容时，做出准确预测要容易得多，而不是猜测并处理不断的变化。</p>
<p>绝大多数桥梁都使用极其成熟可靠的材料、架构和技术。一位罗马工程师被传送到两千年后，通常仍然能认出现代建筑工地上正在发生的事情。当然会有差异，但你仍然在用拱形结构平衡荷载，仍然使用许多相同的材料等等。另一方面，大多数正在建造的软件……</p>
</blockquote>
<p>这是人们在讨论鸡尾酒会想法时所犯错误的典型例子。当不称职的高管（他们已经十年没当过程序员了）对正在进行的项目提出不合理的变更要求时，程序员确实会抱怨，但这与那些从未当过土木工程师的政治家在大型土木工程项目上要求变更相比，并没有太大不同，甚至更可能合理。我猜想，平均而言，编程项目比土木工程项目有更频繁或更大的项目变更，但领域内差异可能至少和领域间差异一样大。</p>

<p>当然，只有那些没有在物理世界中做过严肃工程工作的人才会说“一个真正工程师的世界的可预测性是令人羡慕的。但我们所处的世界总是在变化，物理定律每周都在改变”这样的话，认为物理定律的（相对）固定性意味着物理工作是可预测的。当我作为一名硬件工程师工作时，我项目中的很大一部分工作量和复杂性都用于处理物理不确定性，土木工程也是如此（如果有什么不同的话，土木工程师用于处理大型项目物理不确定性的工具要糟糕得多，导致更大程度的不确定性和因不确定性导致延误的能力下降）。</p>

<p>如果我们看看罗马工程甚至300年前的工程与现代工程的不同之处，一个主要差异来源是来自于物理世界的、我们对不确定性更好的理解。过去，在没有异常条件或刺激的情况下（例如建筑物倒塌，或由于铁轨铺设不正确导致的火车事故），建筑结构在建成不久后就失效并不令人震惊。这种情况现在在美国或加拿大已经很少发生，一旦发生就是重大新闻，这种理解也让我们能够在以前被认为难以建造中型结构的地方建造巨型结构。</p>

<p>例如，如果你看看温哥华地区一个建在三角洲（Delta, Richmond, 以及延伸到Hope的大片土地）上的大型建设项目，直到相对较近，我们才发现了必要知识，能够在这种地面上可靠地建造一些大型结构（例如较高的建筑物），这是现代土木工程中罗马工程师无法理解的众多部分之一。这很大程度上源于一个叫做岩土工程的领域，它是土木工程的一个分支（或者，可以认为是一个独立的领域，也可以认为是地质工程的一个子领域），涉及地面问题，即土壤力学、岩石力学、地质学、水文学等等。岩土工程的一个基本概念是将<a href="https://en.wikipedia.org/wiki/Mechanics" rel="noopener noreferrer">力学</a>应用于土壤推理。已知的第一次将力学应用于土壤（这是岩土工程的一个基础部分）是在1773年，而当今意义上的岩土工程通常被认为始于1925年。虽然罗马工程师做了许多令人印象深刻的工作，<a href="https://www.patreon.com/posts/61946482" rel="noopener noreferrer">他们当时运作的心智模型妨碍了理解现代土木工程的大部分内容</a>。</p>

<p>自然，为了让这些知识能够改变我们能建造什么，它必须改变我们的建造方式。如果我们看看一个在温哥华可压缩三角洲土壤上、运用了现代知识的施工现场是什么样子，按挂钟时间计算，大部分时间看起来就像有人在工地上堆了一堆沙子（预压）。虽然罗马工程师知道一堆沙子是什么，但他们不知道有人如何算出需要多少沙子以及沙子需要堆在那里多长时间（在某些情况下，罗马人会使用桩或筏，这类似于我们今天使用预压解决的问题，但在许多情况下，他们对预压今天解决的问题无能为力）。</p>

<p>岩土工程以及由此产生的沙堆（预压）只是进行现代大型土木工程项目时你需要专业知识的数十个子领域之一，而罗马工程师需要相当多的教育才能真正理解它。</p>

<p>回到我听到的鸡尾酒会解决方案，一个常见的方案是如何修复高昂的建设成本和缓慢的施工进度。有一些时髦的想法被人们随意谈论，关于为什么事情如此昂贵，为什么项目比预期花费更长时间等等。有时，这些评论与我从参与项目的执业工程师那里听到的相似，但更多时候，原因大相径庭。当原因相同时，似乎<a href="https://twitter.com/danluu/status/1420866014493822980" rel="noopener noreferrer">那一定是巧合性地正确，因为他们似乎不理解进行工程权衡推理所需的知识体系</a><sup id="fnref:D"><a href="#fn:D" rel="noopener noreferrer">2</a></sup>。</p>

<p>当然，像鸡尾酒会理论家一样，<a href="https://twitter.com/danluu/status/1483162978224463872" rel="noopener noreferrer">具有该领域专业知识的土木工程师也认为现代建筑是浪费的</a>，但他们得出的原因通常与我在派对上听到的非常不同<sup id="fnref:C"><a href="#fn:C" rel="noopener noreferrer">3</a></sup>。通过不理解问题、假设问题人为地简单，然后为想象中的问题想出解决方案，很容易为问题提出鸡尾酒会解决方案。要理解大型建设项目所需的数十个相互作用的工程子领域之间的权衡，并就权衡应该如何进行以及如何激励工程师和决策者改变权衡的落点进行实际相关的讨论，则要困难得多。</p>

<p>一项被广泛引用的关于人们普遍拥有<a href="https://twitter.com/danluu/status/1356056202203947008" rel="noopener noreferrer">对事物运作方式极其过度简化且不正确的模型</a>的研究是Rebecca Lawson关于人们理解自行车工作原理的研究<a href="https://link.springer.com/content/pdf/10.3758/BF03195929.pdf" rel="noopener noreferrer">（研究链接）</a>，该研究指出：</p>
<blockquote>
<p>最近的研究表明，人们常常高估自己解释事物运作机制的能力。Rozenblit和Keil（2002）发现，人们对复杂现象的理解力进行了过高的评价。这种解释深度错觉并非仅仅源于一般的过度自信；它特别存在于对因果复杂系统（如人造物：弩、缝纫机、微芯片；以及自然现象：潮汐、彩虹）的理解上，相对于其他知识领域，如事实（首都城市名称）、程序（烘焙蛋糕）或叙事（电影情节）。</p>
</blockquote>
<p>以及</p>
<blockquote>
<p>如果外行未能解释齿轮如何工作的复杂性或为什么自行车前叉的角度至关重要，这并不令人惊讶。事实上，甚至物理学家在看似简单的问题上也有分歧，比如为什么自行车是稳定的（Jones, 1970; Kirshner, 1980）以及它们如何转向（Fajans, 2000）。当前研究结果引人注目之处在于，有这么多人对自行车如何运作几乎一无所知。</p>
</blockquote>
<p>在研究的“实验二”中，人们被要求画一辆能工作的自行车，并专注于使自行车工作的机制（而不是让图画看起来好看），94名参与者中有60人至少犯了一个严重错误，导致图画看起来甚至不像一辆能工作的自行车。如果我们看看一个大规模的真实世界土木工程项目，仅一个相关的子领域（如岩土工程）就包含了比自行车多出许多数量级的复杂性，并且可以相当有把握地猜测，精确到百分比的话，零百分比的外行（或罗马工程师）能够大致勾勒出相关运作部分是什么。</p>

<p>对于非土木工程的例子，Jamie Brandon引用了<a href="https://amzn.to/3HrzkSc" rel="noopener noreferrer">Jim Manzi的《Uncontrolled》</a>中的一段话，这是对我在派对上经常听到的一个“聪明”小观点的反驳：</p>
<blockquote>
<p>选择悖论是一个广为流传的民间故事，它基于一个实验：在超市展示中提供更多种类的果酱反而导致购买量减少。给出的解释是选择带来压力，因此一些人面对太多可能的果酱，干脆放弃，空手回家。这个实验在新闻和媒体中被不断引用，通常描述为“科学家发现选择对你有害”。但如果你去一个大型超市，你会看到大约1200万种果酱。他们没听说过果酱实验吗？Jim Manzi在《Uncontrolled》中写道：</p>
<blockquote>
<p>首先，请注意，所有的推断都建立在总共只购买了35罐果酱的基础上。其次，请注意，如果果酱实验的结果有效且具有作为经济或社会政策基础所需的普遍适用性，那将意味着许多商店可以削减75%的产品，并导致销售额增加900%。这将是一个相当惊人的结果，并表明测量可能存在问题。</p>
<p>……原始实验的研究者 themselves were careful about their explicit claims of generalizability, and significant effort has been devoted to the exact question of finding conditions under which choice overload occurs consistently, but popularizers telescoped the conclusions derived from one coupon-plus-display promotion in one store on two Saturdays, up through assertions about the impact of product selection for jam for this store, to the impact of product selection for jam for all grocery stores in America, to claims about the impact of product selection for all retail products of any kind in every store, ultimately to fairly grandiose claims about the benefits of choice to society. But as we saw, testing this kind of claim in fifty experiments in different situations throws a lot of cold water on the assertion.</p>
<p>作为一个实际的商业案例，即使是对构成有用前向预测规则的因果机制进行简化，也不太可能像“将QwikMart商店重命名为FastMart将导致销售额上升”这样，而更可能是“在繁忙道路的高收入社区将QwikMart商店重命名为FastMart，只要商店因粉刷关闭不超过两天，将导致销售额上升”。我们极不可能在开始测试之前就知道所有可能的隐藏条件，并且能够设计并执行一个测试来发现这种充满条件的规则。</p>
<p>此外，这些因果关系本身也经常变化。例如，我们发现某个特定的销售促销活动与没有促销活动相比带来了净利润增长，但第二年，当发生大量变化时——我们的竞争对手推出了新的促销活动，整体经济恶化，消费者流量从购物中心稍微转向了带状购物中心，等等——这个规则不再成立。用之前的比喻来说，我们是通过撞到家具来在黑暗的房间里摸索前进，而未被观察到的顽皮小鬼（gremlins）一直在我们周围移动家具。由于这些原因，仅仅运行一个实验，发现一个因果关系，并假设它广泛适用是不够的。我们必须运行测试，然后在实际实施中测量从这些测试中得出的规则的实际预测能力。</p>
</blockquote>
</blockquote>

<p>到目前为止，我们讨论的都是没有某领域背景的人解释该领域如何运作或应该如何运作的例子，但当人们退后一步，以一种脱离细节的高层视角看待自己领域时，这种犯下“错误地假设事情很简单”的错误也会发生。例如，当我在Centaur工作时，我们尚未推出双核芯片，一位来自顶尖学校的即将毕业的计算机体系结构博士生问我：“为什么你们不直接把两个核心钉在一起做一个双核芯片，就像Intel和AMD那样？那是个轻松的胜利。”</p>

<p>那时，我们从单核转向多核的研究已经进行了一年多。将一个单核芯片变成多核甚至多处理器能力，且性能良好，需要对缓存和内存层次结构（芯片中逻辑最复杂的部分）进行显著额外的复杂设计。粗略估计，我认为，将一个为单核使用设计的芯片变成具有多处理器能力的芯片，至少会使生产一个可工作芯片所需的测试/验证工作量加倍（而芯片设计的大部分工作都用于测试/验证）。更普遍地说，计算机体系结构师的水平取决于他们对自己决策所影响的权衡的理解深度。优秀的架构师对他们必须交互的底层领域有深刻的理解。计算机体系结构师做出糟糕决策的一个常见原因是，他们对计算机体系结构下一层或两层的领域只有鸡尾酒会级别的理解。工业界多次发生的一个糟糕决策的例子是，当一个在职的计算机体系结构师决定在芯片上添加<a href="https://en.wikipedia.org/wiki/Simultaneous_multithreading" rel="noopener noreferrer">SMT</a>，因为它基本上是白送的好处。你支付几个百分点的额外面积，就能获得大约20%的性能提升。我知道有多次尝试这样做都完全失败了，原因可以预见：架构师没有考虑到添加SMT带来的复杂性和验证成本。添加SMT比添加第二个核心带来的复杂性更多，因为逻辑必须贯穿所有部分，并且由于同样的原因，它导致验证芯片复杂性的爆炸性增长。英特尔在P4上添加SMT并著名地没有在其发布的第一代产品中启用，因为它太复杂了，无法在一代产品内验证，并且存在关键的、阻碍性的bug。经过他们多年的时间在一代架构上修复bug后，他们在下一代芯片中修复了SMT实现并发布。当他们迁移到Core架构并在其上添加SMT时，这种情况再次发生。一个在职的计算机体系结构师应该知道这在英特尔身上发生过两次，这意味着验证一个SMT实现是困难的，但仍然有多次有人对SMT的复杂性只有鸡尾酒会级别的理解，并建议将其添加到没有验证预算来发布带有SMT的可工作芯片的设计中。</p>

<p><a href="https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/" rel="noopener noreferrer">当然，这并非计算机体系结构独有</a>。我使用双核的例子是因为它恰好是目前我记忆犹新的一个，但我可以随口说出几十个类似的<a href="https://twitter.com/danluu/status/814167684954738688" rel="noopener noreferrer">例子</a>，并且我敢肯定，如果我花几天时间思考类似的例子，可以写出几百个。<a href="https://twitter.com/danluu/status/1594227349662609408" rel="noopener noreferrer">在一个领域工作的人仍然必须非常小心，以避免拥有不正确的、过于抽象的世界观，从而忽略细节</a>，并<a href="https://twitter.com/altluu/status/1484589911873261568" rel="noopener noreferrer">因此得出荒谬的错误推论或结论</a>。当领域外的人解释事情应该如何运作时，他们的解释通常比领域内忽略了关键考量的人更糟，而且<a href="https://www.patreon.com/posts/54329188" rel="noopener noreferrer">他们通常提出</a><a href="https://yosefk.com/blog/the-high-level-cpu-challenge.html" rel="noopener noreferrer">古怪的想法</a>。</p>

<p>结合罗马工程和CPU的例子，从1个核心到2个（以及一般而言，从1到2，例如从1个数据中心到2个数据中心，或从单体应用到分布式系统），是每个从业者都应该理解其困难性的，即使有些人并不理解。类似地，如果有人展示一个4 THz的处理器，其性能是4 GHz处理器的1000倍，这是任何从业者都应该认识到是他们绝对无法理解的外星技术。只有对该领域一无所知的外行才会合理地想，“它只是一个以1000倍时钟频率运行的处理器；一个能制造4 GHz处理器的工程师基本上会理解一个性能提升1000倍的4 THz处理器是如何工作的。”我们离通过将芯片运行速度提高1000倍来将性能扩展1000倍还差得很远，这样做将需要许多技术上的根本突破，并且很可能需要创造全新的领域，其工程知识量将超过当今世界所有工程领域的总和。同样，只有外行才会看着罗马工程和现代土木工程并认为“罗马人建造东西，我们建造的东西只是更大更多样；一个罗马工程师应该能够理解我们今天如何建造东西，因为东西只是更大了。”仅岩土工程就包含了比罗马时代所有工程领域总和更多的工程知识，而且它只是<a href="https://www.patreon.com/posts/61946482" rel="noopener noreferrer">为使我们能够建造像今天这样的结构而必须发明的新领域之一</a>。</p>

<p>当然，我并不指望随机程序员理解岩土工程，但我希望那些比较编程和土木工程的人至少对土木工程有一些了解，而不是仅仅假设该领域存在的知识量大致等于他们对该领域的了解，而他们对该领域基本一无所知。</p>

<p>尽管<a href="https://www.patreon.com/posts/60185075" rel="noopener noreferrer">我似乎比大多数人更努力地避免陷入认为某事简单因为我看不懂它的陷阱</a>，但我仍然经常陷入这个陷阱，我能想到的最好的防止方法，虽然聊胜于无，但并不可靠。</p>

<p>其中一部分是我试图培养注意到“对某事不甚了了却一掠而过”的感觉。我认为这类似于（甚至可能实际上就是）过去二十年变得流行的一种东西：关注情绪在你身体里的感觉，并通过注意身体里的感觉来理解你的情绪状态，例如，某种特定的紧绷感是我生气的一个确定信号。</p>

<p>当我以模糊、高层的视角看待某事，并在精神上一掠而过时，我的身体会产生一种特定的感觉。如果我不注意，我很容易错过它，我怀疑当我在一种连产生感觉的无意识大脑部分都不知道我在一掠而过的方式下忽略某事时，我也可能错过它。尽管注意到这种感觉本质上不可靠，但我认为，我可能做的其他所有独立检查我推理的事情都根本依赖于相同的机制（例如，如果我有一个清单，试图确定当我推理某个主题时是否没有一掠而过，这个过程的某些部分仍然依赖于感觉或直觉）。我确实尝试对错过这种感觉的案例进行事后分析，以弄清楚发生了什么，这基本上就是我发现我首先与这种错误相关联的感觉的方式（我思考过去是什么导致了这类错误，并注意到我通常与之相关联的一种感觉），但这永远不会完美，甚至<a href="https://danluu.com/p95-skill/" rel="noopener noreferrer">非常出色</a>。</p>

<p>另一个组成部分是做我认为是“检查输入我头脑的信息”的事情。当我在上高中时，我注意到相当大一部分“明显错误”的我说出的话，来自于让不正确的信息进入我的头脑。我过去没有，现在也没有一个好的、便宜的方法来标记信息的可靠性，因此我发现更容易在信息消费时就进行事实核查或丢弃信息。</p>

<p>另一件我尝试做的事情是<a href="https://danluu.com/writing-non-advice/#appendix-getting-feedback" rel="noopener noreferrer">获取反馈</a>，这不可靠，并且在一般情况下也难以处理，因为获取反馈的速度远慢于思维速度，将一般思维速度降低到反馈速度会导致思想相对较少<sup id="fnref:W"><a href="#fn:W" rel="noopener noreferrer">4</a></sup>。</p>

<p>虽然，<a href="https://danluu.com/teach-debugging/" rel="noopener noreferrer">不像某些领域，没有可以教授的、机械的、系统的步骤</a>来解决这个问题，但我确实认为这是可以练习和提高的，并且在一些领域（通常是隐含地）教授了类似的技能。例如，在讨论高级或研究生水平教材的先决条件时，通常会看到书上说“自成体系。除数学成熟度外无先决条件”。这是一种简写，意思是“这本书不要求你具备任何高中生不会学到的特定数学知识，但你确实需要在解释和理解数学陈述方面克服几乎每个未受过训练的人都有的那种模糊思维。”拥有数学学位的人会在脑海中积累很多关于<a href="https://en.wikipedia.org/wiki/Cauchy%E2%80%93Schwarz_inequality" rel="noopener noreferrer">柯西-施瓦茨不等式</a>和<a href="https://en.wikipedia.org/wiki/Bolzano%E2%80%93Weierstrass_theorem" rel="noopener noreferrer">波尔查诺-魏尔斯特拉斯定理</a>之类的显性知识，但能够理解这本书的重要知识不是显性知识，而是一般性的数学思维方式。</p>

<p>尽管在其他领域没有真正对应“数学成熟度”的术语，例如人们通常不把“系统设计成熟度”作为<a href="https://twitter.com/danluu/status/1470890504833228801" rel="noopener noreferrer">系统设计面试</a>中寻找的东西，但类似的技能是存在的，即使它没有名字。同样，对于思考那些自己不是训练有素的专家的主题，比如一个非土木工程师思考为什么一个建设项目花费那么多时间那么多钱，存在一种一般性的思维成熟度<sup id="fnref:R"><a href="#fn:R" rel="noopener noreferrer">5</a></sup>。</p>

<p>感谢 <b><a href="https://www.reforge.com/all-programs?utm_source=danluu&amp;utm_medium=referral&amp;utm_campaign=spring22_newsletter_test&amp;utm_term=&amp;utm_content=engineering" rel="noopener noreferrer">Reforge - Engineering Programs</a></b> 和 <b><a href="https://flatironsdevelopment.com/" rel="noopener noreferrer">Flatirons Development</a></b> 通过<a href="https://patreon.com/danluu" rel="noopener noreferrer">在Major Sponsor级别赞助我</a>，帮助使这篇文章得以发表。</p>

<p>另外，感谢Pam Wolf, Ben Kuhn, Yossi Kreinin, Fabian Giesen, Laurence Tratt, Danny Lynch, Justin Blank, A. Cody Schuffelen, Michael Camilleri, 和Anonymous的评论/更正讨论。</p>

<h4 id="appendix-related-discussions">附录：相关讨论</h4>

<p>一位匿名博客读者给出了他们自己与鸡尾酒会想法斗争的例子：</p>
<blockquote>
<p>你最近的一篇帖子再次引起了我的共鸣，因为我最近了解到我对如何让东西变冷基本一无所知，尽管我从事低温物理学研究已经将近10年了。虽然我知道冷却的大致轮廓，以及稀释制冷机的大致工作原理，但我并没有体会到将东西保持在毫开尔文（mK）温度的巨大挑战。我是团队中唯一的物理学家，其他成员都是机械工程师。我们发现，在mK级别，基本上每一纳瓦的耗散都很重要，每一个表面与表面的接触、每一种材料选择等等也都很重要。</p>
<p>确实，我们可以说，mK温度下的热传输物理已经被很好地理解了，我们可以写出描述这些系统中热传递随温度变化的定律。它们通常写作P = aT^n。我们知道不同类型的传输具有不同的指数n，这些指数是已知的。当然，正如你可能预期的，拥有“热”量子比特与拥有稀释制冷机基础温度（30 mK）的量子比特之间的区别，完全在于预因子a在我们特定系统中恰好取什么值。这个参数通常可以猜测，通常在10倍以内，有时在2倍以内。但真的，为了确保我们能够保持量子比特冷却，我们需要测量那些预因子。诸如紧固件类型（4-40螺钉与M4螺栓）、紧固件数量、材料选择（金？铜？）以及几何形状等因素都对系统的实际性能起着巨大作用。哦，还有，当你将金属从其正常状态变为超导态时，n会发生剧烈变化。有趣！</p>
<p>我们花了一年多的时间仔细建模我们的低温系统，并在此过程中发现了拥有15-20年低温测量经验的人所持有的巨大误解。我们发现了材料选择和设计决策，如果进行了实际的热建模验证，这些决策会被认为是疯狂的。</p>
<p>有趣的是，如果我们想复制学术实验室的结果（这些结果大多倾向于更简单的实验设计），这基本上没问题，但当我们离开学术界，朝我们自己的目的设计时，这就行不通了。</p>
<p>附言：量子计算似乎也受到一种想法的影响，即控制100个量子比特（IBM已经达到127个）与1000个或1,000,000个没有太大不同。我过去认为这只是公关屁话，这些公司负责扩展的人完全意识到这将是多么疯狂地困难，但在我自己的经历和阅读你的帖子之后，我有点担心他们大多数人并不真正理解我们前方的巨大斗争。</p>
<p>这只是长篇大论地说明，我曾在我拥有博士学位并理应是专家的领域持有鸡尾酒会想法，所以你的帖子对我来说非常及时。我喜欢把你的写作作为跳板来思考如何变得更好，这一直非常困难。很难定义什么是好的物理学家或他们做什么，但我确信，更努力地去识别和应对我自己知识的局限性似乎是一件好事。</p>
</blockquote>

<p>关于清晰思考的更广泛、更高层次的讨论，请参阅Julia Galef的《侦察兵心态》：</p>
<blockquote>
<p>当你想到一个拥有出色判断力的人时，你会想到哪些特质？也许你会想到聪明、机智、勇气或耐心。这些都是令人钦佩的美德，但有一种特质应该列在首位，它如此被忽视，甚至没有一个官方名称。</p>
<p>所以我给它起了一个名字。我称之为侦察兵心态：将事物视为其本来面目，而非你希望其模样的动机。</p>
<p>侦察兵心态让你能够认识到自己何时犯错，寻找自己的盲点，测试自己的假设并改变方向。它促使你诚实自问，比如“那场争论是我的错吗？”或“这个风险值得吗？”或者“如果另一个政党做了同样的事，我会如何反应？”正如已故物理学家理查德·费曼曾经说过的，“第一原则是你不能欺骗自己——而你是最容易被欺骗的人。”</p>
</blockquote>
<p>作为改进思维的工具，这本书<a href="https://twitter.com/danluu/status/1477789638387322880" rel="noopener noreferrer">有几章提供了具体的检查方法</a>，这使得它比这篇文章更具可操作性（或至少更容易操作），这篇文章只是建议你弄清楚当你对某事一掠而过时是什么感觉。但我不认为书中的思想是本文的替代品，因为书中建议的自检方法并没有直接针对本文讨论的问题。</p>
<p>在其中一章中，Galef建议拥抱困惑（例如，如果一些看似矛盾的信息引发了困惑感），我同意这一点。我要补充的是，还有很多其他感觉值得观察，这些感觉并没有很好的名字。当评估想法时，除了已经提到的“我正在忽略重要细节的感觉”，我试图注意的一些感觉还有“某种方法如果追求很可能会有回报的感觉”、“某种方法确实充满风险/危险的感觉”、“存在关键缺失信息的感觉”、“有些地方确实不对的感觉”，以及类似的没有好名字的感觉。</p>
<p>关于电影《不要抬头》如何宣扬世界很简单、我们可以轻松找到鸡尾酒会解决方案来解决问题的观点，请参阅<a href="https://astralcodexten.substack.com/p/movie-review-dont-look-up" rel="noopener noreferrer">Scott Alexander的这篇帖子</a>。</p>
<p>另外，John Salvatier指出<a href="http://johnsalvatier.org/blog/2017/reality-has-a-surprising-amount-of-detail" rel="noopener noreferrer">现实有着惊人数量的细节</a>。</p>
<div>

<hr>

<ol>
<li id="fn:N">我常听到的另一个说法是，与传统工程师不同，<a href="https://twitter.com/danluu/status/1162469760900091904" rel="noopener noreferrer">程序员做的是前所未有的事情</a>
 <a href="#fnref:N" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:D"><p>关于建筑延误的讨论同样忽略了岩土工程原因导致的延误。与上面一样，我使用岩土工程作为解释许多延误的子领域的例子，只是因为我恰好熟悉它，而不是因为它是最重要的，但它确实是延误的一个主要原因，并且在许多类型的项目中，是最大的延误原因。</p>
<p>回到我们这个罗马工程师最多只能肤浅理解的例子，我们在建造前将土堆到地面上的原因是，温哥华大部分地区的地质条件不利于建造大型建筑。地面松软，如果在上面建造重物，随着时间的推移会被不均匀地压低。沙子作为重量，用于预先挤压地面。</p>
<p>如上段所述，这听起来很简单。不幸的是，事实远非如此。实际上，我花了大量时间和一位地球物理工程师（一个相关但与岩土工程相当不同的领域）一起开车四处走。当我们驶过道路上的一个奇怪的凸起或凹陷时，她通常能指出导致该凸起存在的地质问题，或出于政治动机而忽视地质工程师指导的决定。我觉得有趣的是，尽管土木工程项目所进行的风险降低水平通常远高于我从事过的电气工程项目，而电气工程项目的风险降低水平又远高于我从事过的任何软件项目，但仍然有足够多的“bug”进入“生产环境”，以至于如果你开车四处转，具备知识并加以注意，一天可以看到几十或几百个错误。</p>
<p>根本问题在于，人类没有技术能够以合理的成本理解像主要高速公路这样的大型物理项目中的地面情况。我们有一个工具是用探地雷达对地面成像，但这会导致高度<a href="https://en.wikipedia.org/wiki/Underdetermined_system" rel="noopener noreferrer">欠定</a>的输出。我们还有另一个工具是使用岩芯钻或土壤螺旋钻，基本上是挖入地下查看情况。这也固有地产生欠定的输出，因为我们只能在钻孔的确切位置看到情况，而地面有时在其成分上存在大的空间变化，这从地表看并不明显。一个常见的例子是存在未标记的残留河床，它很容易“避开”土壤采样的位置。其他工具也存在，但它们在实际财务约束下使用时，同样给工程师留下了不完整和不确定的世界观。</p>
<p>当我听鸡尾酒会讨论为什么一个建设项目花了这么长时间，并将其与土木工程师告诉我的延误原因进行比较时，鸡尾酒会讨论几乎总是专门讨论土木工程师告诉我是错误的原因。延误有很多原因，“未预料到的地质条件”是常见的一种。土木工程师在这方面很为难，因为钻探岩芯既耗时又昂贵，而且当人们看到地面被挖开却没有进行“实际工作”时（同样，当施加预压时——“为什么他们不修高速公路？”），人们会生气，这给政治家带来压力，间接导致时间表不允许足够的时间来理解地质条件。这有时会导致项目期间出现地质意外（通常在技术报告中表述为“未预料到的地质条件”），这可能导致项目的大部分必须切换到更慢、更昂贵的技术，甚至更糟的是，可能需要项目的一部分返工，导致成本和进度超支。</p>
<p>我从未听过鸡尾酒会讨论将地质原因归咎于项目延误。相反，人们谈论的是对外行人听起来似乎合理但完全脱离现实的高层原因。但如果你想讨论如何更快、更便宜地建造东西、“<a href="https://www.theatlantic.com/science/archive/2019/07/we-need-new-science-progress/594946/" rel="noopener noreferrer">进步研究</a>”等，如果不理解岩土工程权衡（以及我们未讨论的其他土木工程领域的权衡），就无法合理地进行。</p>
 <a href="#fnref:D" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:C"><p>我们可能做的一件控制成本的事情是减少地质工作，并在某个风险界限内忽略地质意外。今天，一些“工作量”是由法规决定的，很大一部分是由判例法决定的，它大致指明了需要做哪些工作以避免在发生各种不良结果（如建筑物倒塌）时承担法律责任。</p>
<p>如果我们不使用判例法和责任风险来确定应该进行多少地质风险降低，而是根据每美元的<a href="https://en.wikipedia.org/wiki/Quality-adjusted_life_year" rel="noopener noreferrer">质量调整生命年</a>（QALYs）来计算，在边际上，我们似乎在地质风险降低上花费了大量资金，与许多其他干预措施相比。</p>
<p>这不仅对地质工作是正确的，对土木工程的其他领域也是如此，例如，美国和加拿大等地的建筑商比一些建设速度更快的国家做了更多的坍落度测试，这降低了建筑物过早坍塌的风险。如果建筑物因为建筑商没有进行在美国或加拿大会进行的坍落度测试而倒塌，那将是丑闻和严重的责任问题，但即使建筑商没有做像美国和加拿大那样多的坍落度测试，建筑物通常也不会倒塌。</p>
<p>不按与美国或加拿大大致相同严格的标准建造的国家，有时会有一些相对较新建造的结构倒塌，这在美国或加拿大会被认为是令人震惊的，但每挽救一条生命所花费的金钱与其他可以花钱的地方相比非常少。我们是否应该通过政策决定来改变这一点，是与建筑成本和时间表更相关的讨论，但我从未在土木工程圈子外听到这个或建筑项目成本的其他具体原因被提起过。</p>
<p>即使我们将自己局限于与土木工程相关的工作，而不是采取更广泛、更以EA为导向的方法，并将QALYs用于所有可能的干预措施，在建设期间投入资源进行风险降低与投入资源在持续基础上进行风险降低（检查、维护等）之间的权衡，其相对资源水平并非由一个应该产生接近最优结果的过程所决定。</p>
 <a href="#fnref:C" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:W">有些人建议写作是一个好的中间步骤，它比获取外部反馈更快，同时比仅仅思考某事更可靠，但我发现写作太慢，无法用作理清想法的方式，而且在努力识别我何时有模糊想法之后，我发现尝试想清楚一个想法更可靠也更快。
 <a href="#fnref:W" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:R"><p>我认为这一点被那些自认为“聪明”的人低估了，即书本知识和思考某事在哪些情况下是足够的，而在哪些情况下需要该主题的实践经验。</p>
<p>一个快速阅读者可以在大约40-100小时内阅读大多数技术学位的教材。对于阅读慢的人，可能会慢得多，但时间仍然不算太长。对于问题的某些方面，这足以理解问题并提出好的、合理的解决方案。<a href="https://danluu.com/hardware-unforgiving/" rel="noopener noreferrer">而对于问题的某些其他方面，这极其低效，需要数千小时的实际努力才能真正理解正在发生的事情</a>。</p>
 <a href="#fnref:R" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 19551 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/cocktail-ideas/</link>
      <guid isPermaLink="false">https://danluu.com/cocktail-ideas/</guid>
      <pubDate>Wed, 2 Feb 2022 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>容器限流问题</title>
      <description>[AI 摘要] 本文探讨Twitter容器化环境中CPU限流问题的成因、案例研究及多种解决方案，以提升性能与降低成本。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文探讨Twitter容器化环境中CPU限流问题的成因、案例研究及多种解决方案，以提升性能与降低成本。</div><p><i>本文节选自我与<b>David Mackey</b>于2019年4月合著的一份内部文档。节选原因是原文大部分内容涉及在Twitter提高效率的不同方案对比，这些信息若无大量额外解释/背景，对Twitter外部读者意义有限。</i></p>

<p>在Twitter，大多数CPU密集型服务在容器CPU利用率约50%时开始出现性能下降，而几乎所有服务即使理论应能获得更高利用率，实际上也仅在略高于50%时即开始崩溃。由于负载通常无法在分片间均匀分布，且超过50% CPU利用率时分片级性能衰减严重，这使得实际负载上限远低于50%的理论值，即使在负载高峰期也是如此。</p>

<p>本文将描述解决此问题的潜在方案。首先将说明为何根据服务配置方式和所用Linux调度器的特性，此问题本就该出现。随后将通过具体案例研究，展示如何通过配置调整使特定服务容量提升1.5至2倍，这相当于为大型服务每年节省数十万至数百万美元。虽然针对大型服务进行手动优化值得实施（可带来数十万至数百万美元的<a href="https://en.wikipedia.org/wiki/Total_cost_of_ownership" rel="noopener noreferrer">总拥有成本</a>节约），但逐个手动调整服务并不具备可扩展性。因此，我们还将探讨如何为大多数服务实施可大规模推广的改进措施。</p>

<h3 id="the-problem-in-the理论">理论层面的问题</h3>

<p>Twitter几乎所有服务均运行在采用<a href="https://en.wikipedia.org/wiki/Completely_Fair_Scheduler" rel="noopener noreferrer">CFS调度器</a>的Linux系统上，使用<a href="https://www.kernel.org/doc/Documentation/cgroup-v2.txt" rel="noopener noreferrer">CFS带宽控制配额</a>进行资源隔离，并采用默认参数。其设计初衷是允许不同服务在同一物理机上共存，避免某个服务的CPU过度使用影响其他服务，同时防止空闲物理机上的服务占用全部CPU资源。此前服务所有者在启用配额前，因性能不可预测而难以评估服务表现。配额机制限制每个容器的平均CPU使用量，但不限制作业在任意时刻可使用的CPU核心数。相反，如果作业在配额时间片内“试图”使用超过配额核心数，它会暂时使用超过配额的核心数，随后被限流（即基本进入休眠状态），以确保其平均核心使用量保持在配额以下。这对<a href="https://danluu.com/latency-pitfalls/" rel="noopener noreferrer">尾部延迟</a><sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">1</a></sup>是灾难性的。</p>

<p>由于Twitter绝大多数服务使用的线程池规模远大于其Mesos核心预留量，当作业负载较重时，它们最终会请求并使用超过预留量的核心数，进而触发限流。这导致那些基于负载测试数据或负载下观测到的延迟来配置资源的服务，不得不超额配置CPU以避免违反其<a href="https://en.wikipedia.org/wiki/Service-level_objective" rel="noopener noreferrer">SLO</a>。它们要么每个分片请求多于实际所需的CPU，要么必须增加分片数量。</p>

<p>此问题的一个旧例是JVM垃圾回收器。在JVM实现容器感知之前，每个JVM默认将GC并行线程池大小设置为物理机核心数。GC期间，所有这些GC线程同时运行，迅速耗尽CPU配额导致限流。其结果是，原本亚秒级的STW（Stop-The-World）GC暂停可能耗费数秒实际时间才能完成。虽然GC问题已修复，但此问题在几乎所有运行在Mesos上的服务应用层仍然存在。</p>

<h3 id="the-problem-in-practice-case-study">实际案例研究</h3>

<p>以<code>service-1</code>（Twitter规模最大、成本最高的服务）为例进行研究。</p>

<p>下图显示该服务在负载测试刚开始失败时的CPU利用率直方图——即刚超过服务在违反SLO前可处理的峰值负载时。X轴为特定时刻使用的CPU核心数，Y轴为在该利用率下花费的（相对）时间。该服务配置为20个核心，可见即使在接近峰值负载运行时，其利用率大多远低于此配置值：</p>

<p><img src="https://danluu.com/images/cgroup-throttling/cpu-histogram-1.png" alt="配置20个CPU配额的服务直方图，显示平均利用率远低于配置值，但服务过载并违反SLO时峰值利用率显著更高" width="1266" height="658"></p>

<p>问题在于超过20的那些小条形。这些尖峰导致作业耗尽CPU配额后被限流，引起延迟急剧增加，这就是即使平均利用率约8核心（占配额40%），SLO仍被违反的原因。需注意的是，该图的采样周期为10ms，配额周期为100ms，因此技术上可能在不触发限流的情况下观测到超过20的突变，但平均而言，若频繁出现突变（尤其远超20），很可能导致限流。</p>

<p>通过缩减线程池规模以避免使用过多核心并触发限流后，在负载测试中得到如下CPU利用率直方图：</p>

<p><img src="https://danluu.com/images/cgroup-throttling/cpu-histogram-2.png" alt="配置20个CPU配额的服务直方图，显示优化后即使负载更高，利用率分布也更合理" width="1216" height="548"></p>

<p>此时负载（请求速率）为前一张直方图的1.6倍。由于该服务能处理如此高的负载直至故障，导致为其提供请求的负载测试工具无法注入更多负载（虽然这可以修复，但我当时无权快速修复）。[后续测试显示，调整线程池规模后服务容量约提升2倍]。</p>

<p>此案例并非孤例——Andy Wilcox对<code>service-2</code>进行了相同分析，在类似原因下发现了相似的性能提升。</p>

<p>对于关注延迟的服务，若优先追求延迟优化而非成本降低，可获得显著改善。以<code>service-1</code>为例，若保持配置容量不变而非削减2倍，延迟可降低20%。</p>

<p>针对个别大型服务进行此类调整的收益显著（以<code>service-1</code>为例，该服务本身每年可节约[七位数中段金额]，包含其克隆服务则达[八位数低段金额]），但手动调整每个服务并不具备可扩展性。这就引出一个问题：有多少服务受影响？</p>

<h3 id="thread-usage-across-the-fleet">集群整体线程使用情况</h3>

<p>若观察中等规模服务（≥100分片）的活跃线程数与预留核心数之比，会发现几乎所有服务都有远多于预留核心数的可执行线程。每个预留核心对应数十个<a href="https://access.redhat.com/sites/default/files/attachments/processstates_20120831.pdf" rel="noopener noreferrer">可运行线程</a>并不罕见。相比之下，<code>service-1</code>在负载下每个预留核心对应1.5至2个可运行线程的情况已相对温和。</p>

<p>观察这些线程来源，常见情况是程序包含多个线程池，每个线程池大小被设为预留核心数的2倍或物理机逻辑核心数的2倍。在Twitter内外，常见建议是将线程池大小设为物理机逻辑核心数的2倍。该建议可能源于类似gcc编译的工作负载——我们不希望在可执行任务时出现空闲资源。由于线程有时会阻塞导致空闲，采用2倍规模可通过降低任何核心完全空闲的概率来提升吞吐量，且2是一个整数。</p>

<p>然而，将此应用于Twitter应用存在几个问题：</p>

<ol>
<li>大多数应用存在多个相互竞争的线程池</li>
<li>超过预留核心数限制会导致严重后果</li>
<li>执行计算的额外线程可能增加延迟</li>
</ol>

<p>“应配置2倍逻辑核心数”模型假设我们仅有一个执行所有工作的主线程池，空闲线程无负面影响，且工作负载面向吞吐量而不关心特定任务的截止时间。</p>

<p>采用CFS调度器时，超过核心预留量的活跃工作线程不会空闲，它们会被调度运行，但这将导致限流，进而损害尾部延迟。</p>

<h3 id="potential-solutions">潜在解决方案</h3>

<p>鉴于我们在许多服务上观察到与案例研究相似的情况，且难以将性能修复推送到大量服务（因为服务所有者缺乏实施动力），我们应如何解决整个集群（而非仅少数选定大型服务）的此问题？下文将列出潜在方案并逐一详细讨论。</p>

<ul>
<li>优化跨集群线程池（eventbus、netty等）的默认配置</li>
<li>通过共享库协商线程池大小</li>
<li>CFS周期调优</li>
<li>CFS带宽片调优</li>
<li>其他调度器调优</li>
<li>CPU绑核与隔离</li>
<li>在Mesos调度器层面进行超额配置</li>
</ul>

<h4 id="better-defaults-for-cross-fleet-threadpools">优化跨集群线程池的默认配置</h4>

<p><b>潜在影响</b>：效率小幅提升<br>
<b>优势</b>：相比任何全面解决方案工作量少得多，可与其他方案并行实施，即使其他方案已实施也能带来一定收益（减少锁竞争和上下文切换）。<br>
<b>劣势</b>：无法解决大部分问题。</p>

<p>许多默认配置过大。Netty默认线程池大小为预留核心数的2倍。在[某组织]某些部分，他们使用的库会启动<a href="https://news.ycombinator.com/item?id=26643392" rel="noopener noreferrer">eventbus</a>并分配大小为物理机逻辑核心数2倍的线程池（产生[超过100个]eventbus线程），而大多数eventbus用例仅需1-2个线程。</p>

<p>调整这些默认大小虽无法彻底解决问题，但能减轻其影响，且比下文方案工作量小得多，因此可在我们致力于更全面解决方案的同时实施。</p>

<h4 id="negotiating-threadpool-sizes-via-a-shared-library-api">通过共享库（API）协商线程池大小</h4>

<p>[本节由<i>Vladimir Kostyukov</i>撰写]</p>

<p><b>潜在影响</b>：可为大多数服务基本缓解问题。<br>
<b>优势</b>：设计与实现相当直观；有可能在<a href="https://kostyukov.net/posts/finagle-101/" rel="noopener noreferrer">Finagle</a>/Finatra中成为一等公民。<br>
<b>劣势</b>：需要服务所有者明确采用（采用新API构建线程池）。</p>

<p>CSL的util库中有一个桥接应用与JVM集成点的包（util-jvm），这可能是承载协商应用所需线程池大小新API的良好载体。</p>

<p>此类API的形态实际上取决于协商所需的粒度。仅竞争分配给每个进程的总线程数虽易于实现，但无法区分应用线程与IO线程。而引入线程QoS概念（即“IO线程；不可阻塞”、“应用线程；可阻塞”）则可能使协商更精细。</p>

<h4 id="cfs-period-tuning">CFS周期调优</h4>

<p><b>潜在影响</b>：通过缩短进程组CFS运行时配额刷新前的时间周期，小幅降低尾部延迟。<br>
<b>优势</b>：相对直接的修改，只需最少改动。<br>
<b>劣势</b>：增加调度器开销成本可能抵消收益，且未解决并行耗尽配额的根本问题。可能导致总限流量增加。</p>

<p>为限制CPU使用，CFS在称为CFS周期的时间窗口上运行。调度组中的进程从分配给cgroup的CFS配额中获取时间，该配额在CFS周期内通过CFS带宽片消耗。通过缩短CFS周期，配额耗尽导致限流与进程组能再次运行之间的最坏情况时间按比例减少。以默认值（CFS带宽片5ms、CFS周期100ms）为例，最坏情况下，高度并行的应用可能在第一个带宽片耗尽全部配额，导致95ms的限流时间，期间无线程可被调度。</p>

<p>总限流量可能增加，因为即使存在5ms的突增超过阈值，100ms内的调度时间可能未超阈值。</p>

<h4 id="cfs-bandwidth-slice-tuning">CFS带宽片调优</h4>

<p><b>潜在影响</b>：通过允许应用更好地利用分配的配额，小幅降低尾部延迟。<br>
<b>优势</b>：相对直接的修改，仅需最少代码改动。<br>
<b>劣势</b>：增加调度器开销成本可能抵消收益，且未解决并行耗尽配额的根本问题。</p>

<p>当CFS调度进程时，它会在全局池与CPU本地池之间转移运行时间以减少大型系统的全局记账压力。每次转移的量称为“片”。从调度器角度，较大的带宽片更高效，但较小的带宽片允许更细粒度的执行。在调试[内部JIRA链接]问题时确定，若被调度进程未能消耗其整个带宽片（默认5ms），因完成执行或阻塞于其他进程，该时间将对进程组丢失，降低其消耗所有请求资源的能力。</p>

<p>调优此值的预期开销最小，但应测量。此外，这可能并非普适调优项，但Mesos曾拒绝将其暴露为用户可调参数。确定调优此值的启发式方法并提供按应用设置的途径可能难以实现。</p>

<h4 id="other-scheduler-tunings">其他调度器调优</h4>

<p><b>潜在影响</b>：小幅降低尾部延迟并减少限流。<br>
<b>优势</b>：相对直接的修改，仅需最少代码改动。<br>
<b>劣势</b>：可能增加调度器开销成本抵消收益，且未解决并行耗尽配额的根本问题。</p>

<p>内核具有众多自动缩放和自动分组功能，其对调度性能和限流的影响目前未知。<code>kernel.sched_tunable_scaling</code>可在我们理解其值的情况下调整<code>kernel.sched_latency_ns</code>。<code>kernel.sched_min_granularity_ns</code>和<code>kernel.sched_wakeup_granularity_ns</code>可调优以允许更早抢占，改善资源共享并最小化延迟。<code>kernel.sched_autogroup_enabled</code>当前可能未遵循<code>kernel.sched_latency_ns</code>，导致更多限流挑战和调度低效。这些可调参数尚未深入研究，调优影响未知。</p>

<h4 id="cfs-scheduler-improvements">CFS调度器改进</h4>

<p><b>潜在影响</b>：提升整体CPU资源利用率并最小化因CFS低效导致的限流。<br>
<b>优势</b>：改进对用户空间透明。<br>
<b>劣势</b>：CFS调度器复杂，修改存在较大风险，且上游对某些类型修改的接受度可能具挑战性。</p>

<p>CFS调度器处理CFS带宽片中未用空闲时间的方式已被证明效率低下。内核团队有补丁确保这些未用时间返回全局池供其他进程使用，<a href="https://lore.kernel.org/patchwork/patch/907450/" rel="noopener noreferrer">https://lore.kernel.org/patchwork/patch/907450/</a>，以确保更好的整体系统资源利用。还有其他探索途径可提供进一步增强。此领域众多近期讨论之一源于k8s限流问题（<a href="https://github.com/kubernetes/kubernetes/issues/67577" rel="noopener noreferrer">https://github.com/kubernetes/kubernetes/issues/67577</a>）：<a href="https://lkml.org/lkml/2019/3/18/706" rel="noopener noreferrer">https://lkml.org/lkml/2019/3/18/706</a>。</p>

<p>此外，CFS可能因错误而效率降低，如[内部JIRA链接]和<a href="http://www.ece.ubc.ca/~sasha/papers/eurosys16-final29.pdf" rel="noopener noreferrer">http://www.ece.ubc.ca/~sasha/papers/eurosys16-final29.pdf</a>。但我们尚未花费大量时间研究CFS在Twitter特定用例中的性能。更深入的考察可能发现提升效率的方法。</p>

<p>另一个具有更大潜力与风险的改变是使用CFS以外的调度器。</p>

<h4 id="cpu-pinning-and-isolation">CPU绑核与隔离</h4>

<p><b>潜在影响</b>：通过使应用开发者心理模型中的CPU映射到物理CPU，从系统中消除限流概念。<br>
<b>优势</b>：从应用开发者视角简化理解，调度器施加的限流不再是应用需应对的概念，提升缓存效率，显著减少资源干扰带来更确定的性能。<br>
<b>劣势</b>：运维复杂性更高，超额订阅更复杂，需对当前运行环境进行重大变更。</p>

<p>允许限流发生的根本问题是，高度线程化的应用可拥有超过其“请求CPU数”的并行执行线程，导致可用运行时提前耗尽。通过将同时执行的线程数限制为应用请求的CPU数，现存在1:1映射，应用进程组可不受调度器干扰地自由使用逻辑CPU线程。此外，通过为应用分配CPU线程而非带宽片，应用现可充分利用CPU缓存优势，无需在被限流或上下文切换移出时与其他应用竞争同一CPU线程。</p>

<p>在Mesos中，实现CPU绑核已被证明相当困难。然而，k8s已有Intel名为k8s CPU Manager的项目带来希望。CPU Manager作为alpha功能在k8s 1.8中添加，自1.10起作为beta功能启用。它在beta阶段有些停滞，因似乎少有人使用，但核心功能已存在。CPU Manager项目推广的性能改进显著，如示例<a href="https://kubernetes.io/blog/2018/07/24/feature-highlight-cpu-manager/" rel="noopener noreferrer">https://kubernetes.io/blog/2018/07/24/feature-highlight-cpu-manager/</a>和<a href="https://builders.intel.com/docs/networkbuilders/cpu-pin-and-isolation-in-kubernetes-app-note.pdf" rel="noopener noreferrer">https://builders.intel.com/docs/networkbuilders/cpu-pin-and-isolation-in-kubernetes-app-note.pdf</a>所示。尽管这些基准测试应谨慎看待，但确实为探索此途径提供了有希望的前景。对该项目的粗略检查突出了一些<a href="https://github.com/kubernetes/kubernetes/issues/70585" rel="noopener noreferrer">需要工作的领域</a>，但其已处于可用于验证方法的可用状态。底层，k8s CPU Manager利用内核中的cpuset cgroup功能。</p>

<p>潜在地，此方法确实降低了机器超额订阅的能力。然而，最小化跨Pod干扰、CPU限流、更确定执行轮廓等方面的效率增益可能抵消超额订阅的需求。当前，k8s CPU Manager确实允许少量超额订阅（允许系统级容器和daemonset超额订阅），但在Pod调度层面，CPU被保留供该Pod使用。</p>

<p>Brian Martin等人的实验显示CPU绑核带来显著性能收益，几乎与我们的超额订阅系数相当。</p>

<p>长期来看，超额订阅可通过多层方法实现：主要Pod类使用CPU绑核调度，而对延迟不敏感的次要Pod类允许在所有核心上浮动，消耗主要Pod的空闲资源。CPU Manager方面的工作将是广泛的。然而，最近<a href="https://lwn.net/ml/linux-kernel/20190408214539.2705660-1-songliubraving@fb.com/" rel="noopener noreferrer">Facebook在内核调度器方面的工作</a>以最小化影响主要Pod类的方式进一步实现了此概念，我们可以扩展或发展。</p>

<h4 id="oversubscription-at-the-cluster-scheduler-level">集群调度器层面的超额订阅</h4>

<p><b>潜在影响</b>：可将机器利用率提升至任意高水平，并充分超额配置。<br>
<b>优势</b>：集群调度器层面的超额订阅与本文描述的问题无关；以数据驱动方式执行可在无需尝试解决此处所述特定问题的情况下提升机器利用率。这可能同时解决本文问题（因避免限流而过度配置导致的低CPU利用率）和[另一问题描述文档]中的问题。<br>
<b>劣势</b>：我们在[内部文档链接]中看到，运行在高负载主机上的服务分片性能下降。除非我们将Mesos调度器改为基于实际利用率（而非预留量）调度，否则某些主机会负载过高，分片落在这些主机上的服务性能将较差。</p>

<h4 id="disable-cfs-quotas">禁用CFS配额</h4>

<p><b>潜在影响</b>：防止限流，允许服务通过依赖“份额”机制而非配额使用物理机上所有可用核心。<br>
<b>优势</b>：在某种意义上可实现最高可能利用率。<br>
<b>劣势</b>：行为异常的服务可能严重干扰同一物理机上运行的其他服务。此外，服务所有者将更难预测其自身服务性能，因为空闲与负载状态间的性能变异性会大得多。</p>

<p>此方案是我们启用配额前所用的方案。从朴素硬件利用率角度，依赖份额机制似乎最优，因为这意味着若物理机未充分利用，服务可占用空闲核心；但若物理机高度利用，服务将回退至按核心预留量比例占用核心。然而，当我们使用此系统时，大多数服务所有者发现预测负载下性能过于困难，因而不可行。至少一家公司尝试此方案解决其限流问题，在负载下发生了严重事故。若我们现在切换回此方案，不会比启用配额前更好。</p>

<p>鉴于我们的容量分配方式，若要使此方案比之前更有效，需包含两个要素：对单个分片请求率的更精细控制，以及允许服务所有者理解负载激增期间真实情况的负载测试环境（与我们仅允许向单个分片注入不切实际负载的系统不同，这存在请求混合与真实负载激增期间不同、且注入负载的分片未看到同一物理机上其他服务升高负载的问题）。根据[另一内部文档]，我们知道影响分片级性能的最大因素之一是物理机的总体负载，且其对延迟的影响是非线性且难以预测，因此确实没有好方法从当今负载测试框架下的测试负载预测实际负载下的性能。</p>

<p>虽然这些缺失要素是重要且高影响力的问题，但解决任一要素均超出本文范围；[X团队]负责负载测试并正在改进，待问题解决后可能值得重新审视此方案。</p>

<p>一个中间方案是将调度器配额设置为大于Mesos预留核心数的值，这将限制“过多”CPU导致不可预测性能的影响，同时可能在高负载下减少限流（因为调度器将有效回退至份额机制）。例如，若cgroup配额为Mesos配额的两倍，在保留Mesos CPU使用量50%时崩溃的服务将改为在100%时崩溃。对于高负载物理机，更高的总体利用率将减少限流，因为来自其他核心的增加负载意味着拥有过多可运行线程的服务无法让其如此多线程执行。这具有禁用配额缺点的弱化版本——根据[内部文档]，来自其他服务的物理机负载是分片级性能差异的最大因素之一，若我们不改变物理机上Mesos核心预留量，这将增加物理机负载。若我们按比例减少物理机Mesos预留核心数，则此变更失去意义，相当于简单地将每个服务的CPU配置加倍（只是“秘密”加倍可能减少人们问“为何在负载测试中超过X% CPU时服务崩溃？”的人数）。</p>

<h3 id="results">成果</h3>

<p><i>本节未包含在2019年4月的原始文档中；写于2021年12月，描述因原始文档产生的工作。</i></p>

<p>更改默认线程池大小的建议被采纳并带来轻微改进。更重要的是，本文催生了两个主要项目。Vladimir Kostyukov（来自<a href="https://finagle.github.io/blog/2021/03/31/quarterly/" rel="noopener noreferrer">CSL团队</a>）和Flavio Brasil（来自JVM团队）创建了<a href="https://github.com/twitter/finagle/blob/develop/finagle-core/src/main/scala/com/twitter/finagle/filter/OffloadFilter.scala" rel="noopener noreferrer">Finagle Offload Filter</a>，Xi Yang（当时我的实习生<sup id="fnref:I"><a href="#fn:I" rel="noopener noreferrer">2</a></sup>，现为我团队全职员工）创建了消除容器限流的内核补丁（补丁仍为内部，但希望最终能上游化）。</p>

<p>几乎所有在Mesos上运行的应用都基于<a href="https://kostyukov.net/posts/finagle-101/" rel="noopener noreferrer">Finagle</a>。Finagle Offload Filter使服务所有者能轻松将应用工作负载置于与IO不同的线程池（此前常未实现）。结合恰当调整线程池规模，在其他条件不变的情况下，应用<a href="https://mobile.twitter.com/fbrasisil/status/1163974576511995904" rel="noopener noreferrer">延迟大幅降低</a>，使其能削减配置容量并降低成本，同时满足SLO。根据服务不同，这带来15%至60%的成本节约。</p>

<p>内核补丁实现了显而易见的思路：防止容器在任意时刻使用超过容器配额的核心数，而非允许容器使用物理机上所有可用核心，然后在容器使用过多核心以将其平均核心使用量降低时将其置入休眠。</p>

<p>在运行Twitter主要服务的主机上实验显示，其产生了消除限流相关问题的预期影响，对未调优线程池规模的典型服务带来约50%的成本节约。事实证明，其净影响比我们撰写本文时认知的更大，因为防止服务使用“过多”核心后限流减少了干扰<sup id="fnref:M"><a href="#fn:M" rel="noopener noreferrer">3</a></sup>。此外，虽然当时已意识到，但未在文档中注明限流问题使分片从“基本完全正常”进入“限流死亡螺旋”（类似于“GC死亡螺旋”），仅需少量额外负载即会触发，增加了可靠运行系统的难度。情况是，当服务处于高负载时会发生限流。限流不会阻止请求进入被限流的分片，因此当分片从限流中苏醒时，其待处理工作量比限流前更多，导致其使用更多CPU并更快触发限流，造成更多工作堆积。Finagle有可为状态极差的分片卸载负载的机制（与故障服务器通信的客户端将标记服务器为故障并停止发送请求一段时间），但分片往往在服务总体负载高时进入此状态，因此标记节点为故障仅意味着更多负载流向其他分片，后者将“想要”进入限流死亡螺旋。在限流可能导致死亡螺旋的环境下运行是<a href="https://twitter.com/copyconstruct/status/1399766443596472320" rel="noopener noreferrer">本质上亚稳态的</a>。消除这两个问题的影响可说与消除限流带来的成本节约一样大。</p>

<p>Xi Yang已试验上述朴素内核调度器变更的多种变体，但即使是朴素变更，与不变更相比也似乎相当有效，尽管朴素变更确实意味着服务在请求时通常无法获得全部CPU分配。例如，若服务在前半周期不请求CPU而后半周期请求无限CPU，在旧系统下将获得其周期分配CPU量，但在新系统下仅能获得一半。Xi的部分变体补丁以某种方式解决了此问题，但与防止限流本身相比影响相对较小。</p>

<p>Pratik Tandel推动的独立变更通过减少分片间差异影响来减轻限流对服务的影响，该变更是转向更少更大的分片。此变更的主要目标是减少跨所有分片发生的重复工作/内存开销，但它碰巧因更大的分片配额减少了随机噪声影响。总体而言，这带来Twitter大型服务0%至20%的CPU使用减少和10%至40%的内存使用减少（具体取决于服务）。</p>

<h3 id="appendix-other-container-throttling-related-work">附录：其他容器限流相关工作</h3>

<ul>
<li><a href="https://engineering.indeedblog.com/blog/2019/12/cpu-throttling-regression-fix/" rel="noopener noreferrer">https://engineering.indeedblog.com/blog/2019/12/cpu-throttling-regression-fix/</a></li>
<li>增加突发性

<ul>
<li><a href="https://lore.kernel.org/lkml/20180522062017.5193-1-xiyou.wangcong@gmail.com/" rel="noopener noreferrer">https://lore.kernel.org/lkml/20180522062017.5193-1-xiyou.wangcong@gmail.com/</a></li>
<li><a href="https://lkml.org/lkml/2019/11/26/196" rel="noopener noreferrer">https://lkml.org/lkml/2019/11/26/196</a></li>
<li><a href="https://lwn.net/Articles/840595/" rel="noopener noreferrer">https://lwn.net/Articles/840595/</a></li>
<li>超出分配的容器仍将限流，但增加了“突发容量”概念，在保持基本相同平均核心利用率的同时，允许限流前有更大余量

<ul>
<li>允许突发性独立于我们的修复（防止限流），原则上两者可同时应用，有点像启用htb qdisc的网络隔离工作方式</li>
<li>鉴于Twitter的工作负载和配置，这并未解决我们关于实现极高每容器CPU利用率或防止限流死亡螺旋威胁所致亚稳态的限流问题，尽管它确实允许我们比不启用突发性时使用略高的平均CPU</li>
</ul></li>
</ul></li>
<li>运行时层面并行限制

<ul>
<li>由于Go通常使用单线程池，Uber通过<a href="https://github.com/uber-go/automaxprocs" rel="noopener noreferrer">https://github.com/uber-go/automaxprocs</a>限制最大运行goroutine数绕过了此问题

<ul>
<li>对Twitter不幸的是，包括<code>service-1</code>在内的多个最大最昂贵服务使用多种语言运行时，因此没有简单方法在运行时层面限制并行性</li>
</ul></li>
<li>.NET运行时拥有自适应线程池大小已十年，<a href="https://twitter.com/danluu/status/1340059907026898944" rel="noopener noreferrer">这是.NET技术栈比我们常见时尚科技公司更先进的众多方式之一</a></li>
</ul></li>
</ul>

<p><i>感谢Xi Yang、Ilya Pronin、Ian Downes、Rebecca Isaacs、Brian Martin、Vladimir Kotsyukov、Moses Nakamura、Flavio Brasil、Laurence Tratt、Akshay Shah、Julian Squires、Michael Greenberg @synrotek和Miguel Angel Corral的评论/修正/讨论</i></p>
<div>

<hr>

<ol>
<li id="fn:S">如果此物理机负载很高，因核心数不足，容器可能无法获得其请求的所有核心，但这不改变根本问题。
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:I">我常开玩笑说<a href="https://twitter.com/danluu/status/1324416895013986305" rel="noopener noreferrer">实习生得到所有最有趣的工作</a>，而我们全职员工则处理实习生不想做的工作。
 <a href="#fnref:I" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:M">在一项独立工作中，Matt Tejo发现，对于固定的平均核心利用率，发生限流的服务比使用恒定核心数的服务对同一主机上其他服务造成更大的负面影响。这是因为高度负载且限流的服务在试图使用物理机所有核心和完全不使用核心之间切换，在其试图使用所有核心的期间造成极大量的干扰。
 <a href="#fnref:M" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 16502 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/cgroup-throttling/</link>
      <guid isPermaLink="false">https://danluu.com/cgroup-throttling/</guid>
      <pubDate>Sat, 18 Dec 2021 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>关于写作的一些思考</title>
      <description>[AI 摘要] 本文探讨写作建议常陷入“照我这样写”的误区，强调有效写作应基于个人目标与情境，而非盲目模仿他人风格。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文探讨写作建议常陷入“照我这样写”的误区，强调有效写作应基于个人目标与情境，而非盲目模仿他人风格。</div><p>我见过许多标榜为写作建议的文章，实际上只是作者对自己写作方式的含蓄描述，其核心无非是“你应该像我这样写”。例如，<a href="https://twitter.com/danluu/status/1437539076324790274" rel="noopener noreferrer">写短文的人会建议你写短文</a>。与技术话题一样，<a href="https://danluu.com/learn-what/" rel="noopener noreferrer">我认为许多不同的方法都可能有效，真正重要的是找到一种适合你自身及所处环境的风格</a>。<a href="https://twitter.com/danluu/status/1467235582199812097" rel="noopener noreferrer">照搬他人成功的方法不太可能对你有效</a>，因此“照我这样写”是一种糟糕的建议。</p>
<p>我们将首先审视那些对他人奏效的写作方法中存在多少多样性<sup id="fnref:P"><a href="#fn:P" rel="noopener noreferrer">1</a></sup>，接着探讨模仿他人风格为何如此困难，最后讨论我自己在写作中尝试的方法。</p>
<p>如果我查看从2000年到2017年在我社交圈中阅读量最高的编程博客<sup id="fnref:O"><a href="#fn:O" rel="noopener noreferrer">2</a></sup><sup id="fnref:7"><a href="#fn:7" rel="noopener noreferrer">3</a></sup>，它们是Joel Spolsky、Paul Graham、Steve Yegge和Julia Evans（如果你不熟悉这些作者，<a href="#appendix-some-snippets-of-writing" rel="noopener noreferrer">请参阅附录中我认为能代表他们风格的摘录</a>）。这份清单上的每个人在以下方面（以及其他方面）都有不同的风格：</p>
<ul>
<li>主题选择</li>
<li>行文风格</li>
<li>文章长度</li>
<li>幽默类型（如果有的话）</li>
<li>技术细节水平</li>
<li>支撑证据的多少</li>
<li>细致程度</li>
</ul>
<p>以一个易于量化的简单维度——长度为例。Julia Evans和我都在2013年开始写博客（她在2012年有一篇帖子，但她告诉我，她认为自己的博客是在2013年9月在<a href="https://www.recurse.com/scout/click?t=b504af89e87b77920c9b60b2a1f6d5e8" rel="noopener noreferrer">RC</a>时才真正开始，那也是我开始写博客的月份）。多年来，我们多次交流过，直到2017年底我暂停写博客，我们的博客总字数相近，尽管她发布的文章数量比我多大约一个数量级。</p>
<p>再看几个难以量化的方面，考虑一下Paul Graham这段典型的风格片段：</p>
<blockquote>
<p>书呆子喜欢的是那种人们走来走去面带微笑的城镇。这排除了洛杉矶（那里根本没人走路）和纽约（人们走路，但不微笑）。我在波士顿读研究生时，一位朋友从纽约来访。从机场回来的地铁上，她问道：“为什么每个人都在微笑？”我看了看，他们其实没在微笑。只是与她习惯的面部表情相比，他们看起来像是在微笑。</p>
<p>如果你在纽约生活过，你就知道这些面部表情从何而来。这是一个你的头脑可能很兴奋，但你的身体知道它正在经历糟糕时光的地方。人们与其说是享受生活在那里，不如说是为了那份兴奋而忍受它。如果你喜欢某些类型的兴奋，纽约是无与伦比的。它是一个魅力中心，是所有半衰期较短的时尚和名望同位素的磁石。</p>
<p>书呆子不在乎魅力，所以对他们来说，纽约的吸引力是个谜。</p>
</blockquote>
<p>它运用了有时被称为<a href="https://amzn.to/3dRLMgR" rel="noopener noreferrer">古典风格</a>的多个方面。在这篇文章中，当我说“古典风格”时，我指的是<a href="https://amzn.to/3dRLMgR" rel="noopener noreferrer">Thomas &amp; Turner</a>所使用的术语，而非日常含义。其内涵实在过长，不适合在这篇文章中详细描述，但我可以说其中一部分是行文干净、直白且简单；一位以“删除多余词语”为信条的编辑不会有太多修改意见。另一部分是，这种风格的简洁性超越了文字本身，延伸到呈现的信息，以至于几乎不呈现支撑证据。Thomas &amp; Turner说：“真理无需争辩，只需准确呈现。”Rochefoucauld的这段话既体现了这两点：</p>
<blockquote>
<p>德·谢弗勒兹夫人智慧闪耀，野心勃勃，美貌十足；她轻佻、活泼、大胆、进取；她动用一切魅力来推动她的计划走向成功，她几乎总是给那些在路上遇到她的人带来灾难。</p>
</blockquote>
<p>Thomas &amp; Turner对Rochefoucauld这段话的评论是：</p>
<blockquote>
<p>这段文字展示了一种与作者获知过程无关的真理顺序。作者摆出全知的姿态。这种姿态暗示作者拥有广阔而丰富的经验；否则他无法做出这样的观察。但这些个人历史、个人经历或个人心理并未进入表达。相反，这句话将作者的经验结晶成一种超越时间且绝对的序列，就像几何证明一样。</p>
</blockquote>
<p>这很大程度上也适用于Paul Graham的那段话（尽管不完全适用，因为他讲述了一个朋友从纽约来访波士顿的轶事，并明确说“如果你在纽约生活过”你会知道什么，而不是直接陈述你会知道什么）。</p>
<p>我的风格在很多方面都与之相反。我经常写长句，思路迂回，不是出于任何特定的文学目的，而仅仅因为它反映了我思考的方式。<a href="https://amzn.to/3s0Adwd" rel="noopener noreferrer">Strunk &amp; White</a>要是看到我的写作，恐怕会有很多意见。在可能的情况下，我尽量构建有条理的论证，并尽可能提供证据，同时对证据不适用的情况加以说明。虽然不呈现证据能让文章读起来更干净，但那不是我的选择，因为我不喜欢读者基本上只能全盘接受或拒绝那些赤裸裸的断言，例如“书呆子喜欢的是那种人们走来走去面带微笑的城镇”，我更希望读者知道我为什么这么想，这样他们可以根据根本原因来同意或反对。</p>
<p>在长度、风格和其他提到的维度上，并没有正确与错误之分。许多不同的方法都能取得不错的效果。不过，如果目标是受欢迎，那么相比于Julia，我可能在长度上做出了次优选择；相比于Paul，我在文风上也可能做出了次优选择。如果我看什么能让别人获得关注，什么能让我的RSS流量增加，让更多人关注我的Twitter等，我发现频繁发布短文似乎比不频繁发布长文更有效。</p>
<p>我对文风对受欢迎程度的影响不那么确定，但我的感觉是，出于同样的原因，在工作中说很多自信的话（能让人晋升），写自信、不带保留的文章也能奏效（能吸引读者）。人们喜欢自信。</p>
<p>但是，在这两种情况下，即使做出次优选择，人们仍然可以非常受欢迎。对我而言，优化其他目标比优化受欢迎程度更重要。在长度上，我经常讨论那些无法轻易甚至根本无法用简短篇幅涵盖的话题。一个例子是<a href="https://danluu.com/branch-prediction/" rel="noopener noreferrer">我关于分支预测的文章</a>，它有两个目标：为没有分支预测甚至计算机体系结构背景的程序员提供历史概述，并教会他们足够多的知识，以便能够阅读和理解现代最先进的关于分支预测的论文。这篇文章长达5800字。我看不出如何用人们推荐的博客文章长度（500字、1000字、1500字等）来达到同样的目标。文章或许可以稍微删减一些，但每个讨论到的预测器，除了<code>agree</code>预测器外，要么是解释后续预测器所必需的构建块，要么具有历史重要性。但即使不讨论<code>agree</code>预测器，讨论至少一种减少干扰的方案也很重要，因为干扰发生的原因以及如何减少它是分支预测中的一个基本概念。</p>
<p>这篇文章也可以有其他版本。一个解释分支预测存在的版本可能1000字就能写好。那篇文章如果写得好，会有更广泛的受众，更受欢迎，但那不是我想写的。</p>
<p>我对风格有类似的看法，因为我经常想以某种程度的细节和精确度来讨论事情，而这使得无法用干净的古典风格来写作。一个具体的小例子是，在最近一篇文章中，一位审阅读者要求我删除双重否定，但我拒绝了，因为在那种情况下，双重否定与可能替换它的肯定陈述有着不同的内涵，我想传达一些精确的东西，而这与简化句子后传达的内容不同。</p>
<p>一个更普遍的问题是，Paul从高层面讨论许多“大创意”。这适合用干净、简单的风格（Paul称之为优雅风格）来写作。但我对<a href="https://scattered-thoughts.net/writing/on-bad-advice/#context-matters" rel="noopener noreferrer">脱离低层次细节的“大创意”</a>不感兴趣，而要有效地讨论低层次细节，很难不用Paul所说的不够优雅的风格来写。</p>
<p>一个具体的例子是我<a href="https://danluu.com/cli-complexity/" rel="noopener noreferrer">关于命令行工具和UNIX哲学的讨论</a>。我们应该有“只做一件事并做好”、“编写程序来处理文本流，因为这是一个通用接口”的工具，还是使用有很多选项、能处理结构化数据的命令？几十年来，人们一直在来回重复同样的高层次反驳。但一旦我们看细节，看这些想法暴露在现实世界中会发生什么，我们立刻就能看出其中一套想法根本不可能像宣称的那样奏效。</p>
<p>回到写作风格，如果你试图弄清楚哪些风格选择适合你，你应该从你的目标和你的长处出发，而不是听那些告诉你像他们那样写的人。即使有人能描述出他们写作为什么奏效，那也不太可能对你有效，而且大多数建议都是由那些不理解自己写作为何奏效的人写的。如果你没有花很多时间分析写作，这可能很难看清，但如果你上过许多舞蹈课或接受过非优秀教练的体育指导，就很容易看出这是真的。例如，观察普通舞蹈教练并倾听他们的指导，你会发现他们的指导与他们实际做的大相径庭。<a href="https://news.ycombinator.com/item?id=29524840" rel="noopener noreferrer">那些倾听并遵循指令而不是尝试模仿教练动作的人，最终会把事情做得完全错误</a>。大多数写作建议同样未能捕捉到重要之处。</p>
<p>不幸的是，<a href="https://www.youtube.com/watch?v=2THVvshvq0Q" rel="noopener noreferrer">模仿他人的风格也非易事；大多数人完全模仿错了东西</a>。例如，Natalie Wynn指出，模仿她风格的人常常模仿表面的东西，却不理解是什么驱动了这些表面特征：</p>
<blockquote>
<p>我注意到一件事，就是当人们没有表达任何东西的时候。比如有人试图做一个“左管视频论文”，他们往屏幕上堆满了奢华的东西，因为Contrapoints，但这与主题无关。这指的是什么？你在说什么？？</p>
<p>我做了一个关于羞耻的视频，造型是伊甸园中的夏娃，因为夏娃是第一个体验到羞耻的人。所以视觉效果与概念相关，并因此希望能引起更多共鸣。所以我想这就是我的建议，试着说些什么。</p>
</blockquote>
<p>如果你探究那些在其领域出类拔萃的人说了什么，你经常会在其他领域看到类似的评论。例如，在实用射击中，Rob Leatham说：</p>
<blockquote>
<p>我在课堂上忙于帮助学生学习如何思考。他们会说，“Rob像这样握枪…”，他们不知道我像这样握枪的原因不是为了让自己看起来那样。最终结果不是那样握枪；那样握枪是做其他事情的结果。</p>
</blockquote>
<p>Brian Enos说：</p>
<blockquote>
<p>当我开始…射击时，我对技术只有基本的概念。所以我做了我认为合乎逻辑的事情。我找到了当地最好的射手（他也在全国比赛中具有竞争力），问他我应该如何射击。他毫不犹豫地告诉我：左手食指放在扳机护圈上，左手肘弯曲向后拉，经典拳击姿势，等等，等等。我盲目地采用这套系统一两年，然后才想是否有一套更适合我的结构和态度、也更适合射击的系统。这最初采用的风格似乎不适合我，因为我感觉好像在努力控制枪支；我从未真正像现在感觉那样与枪支融为一体。我的实验促使我从所有类型的射击风格中汲取灵感：等腰三角姿势、改良的韦伯式、靶心射击，以及像Bill Blankenship、霰弹枪手John Satterwhite和武术家李小龙这样的人。</p>
<p>但来自你环境的想法只能引导你朝正确方向前进。这些想法本身就会限制你的思考……伟大的想法会产生于你内心的感受。这种直觉意识会让你接受任何对你有用的东西，抛弃任何没用的东西。</p>
</blockquote>
<p>我引用这些例子是因为它们被写进了一本书，但我从各种活动的教练那里听过基本上相同的评论，例如我与之交谈过的舞蹈教练抱怨人们会问在某个动作中左脚应该交叉在右脚前面还是后面，这就忽略了重点，因为重要的是脚步放置在考虑到人的重心移动的情况下是合理的，这可能意味着根据具体情况，脚应该交叉在前面或后面。</p>
<p>更普遍的问题在于，不理解他们试图模仿的东西的人，最终会模仿他人行为中不重要的表面方面，而忽略了驱动这些表面方面的根本原因。<a href="https://danluu.com/hardware-unforgiving/" rel="noopener noreferrer">即使有非常详细的说明，也会发生这种情况</a>。虽然观察他人所做可以加速学习，特别是对于毫无头绪的初学者，但没有捷径可以达到足够深入的理解以促进做好一件事，这种理解无法用简单的规则（如“删除多余词语”）来概括<sup id="fnref:C"><a href="#fn:C" rel="noopener noreferrer">4</a></sup>。</p>
<p>因此，我认为风格应源于你的目标，而目标最终是个人偏好。就我个人而言，我有时会有以下目标：</p>
<ul>
<li>以几乎任何专业程序员都能理解的方式解释许多人似乎不理解的技术主题
<ul>
<li>例如：<a href="https://danluu.com/branch-prediction/" rel="noopener noreferrer">分支预测</a>、<a href="https://danluu.com/malloc-tutorial/" rel="noopener noreferrer">malloc</a>、<a href="https://danluu.com/intel-cat/" rel="noopener noreferrer">缓存分区</a></li>
</ul></li>
<li>为少数派观点（或当时是少数派的观点）辩护：
<ul>
<li>例如：<a href="https://danluu.com/deconstruct-files/" rel="noopener noreferrer">文件难以使用</a>、<a href="https://danluu.com/startup-tradeoffs/" rel="noopener noreferrer">公共科技公司可以支付很高薪酬</a>、<a href="https://danluu.com/monorepo/" rel="noopener noreferrer">单体仓库并不愚蠢</a></li>
</ul></li>
<li><a href="https://danluu.com/why-benchmark/" rel="noopener noreferrer">进行测量</a></li>
<li>讨论我认为有趣的现象：
<ul>
<li>例如：<a href="https://danluu.com/discontinuities/" rel="noopener noreferrer">有趣的间断点</a>、<a href="https://danluu.com/hardware-unforgiving/" rel="noopener noreferrer">知识转移的难度</a>、<a href="https://danluu.com/wat/" rel="noopener noreferrer">偏差常态化</a></li>
</ul></li>
</ul>
<p>当你将其中一个目标与详细讨论事情的偏好<a href="http://johnsalvatier.org/blog/2017/reality-has-a-surprising-amount-of-detail" rel="noopener noreferrer">相结合</a>时，你会得到一种与上面提到的任何作者都不同的风格，即使你想像Steve Yegge那样有效地使用幽默，像Julia Evans那样为更广泛的读者写作，或者像Paul Graham那样写得权威。</p>
<p>当我想起自己写作的主要组成部分时，我认为除了风格和目标之外，驱动我写作的主要因素是过程。与风格一样，我认为这是一个多种方法都可行的领域，需要你自己找出什么适合你。</p>
<p><a id="friction" rel="noopener noreferrer"></a>就我而言，在开始写博客时，我有以下过程目标：</p>
<ul>
<li>低前期投入，尽可能减少摩擦，如果我继续写博客，可能会随时间增加投入</li>
<li>通过每篇文章提高写作技巧/能力，而不必过于担心任何单篇文章的写作质量</li>
<li>只在我觉得值得发布时才发布</li>
<li>写一个我自己会想订阅的博客</li>
<li>在自己的平台上写作</li>
</ul>
<p>低前期投入目标的原因是，当我审视我看过的博客时，最常见的博客格式之一是包含一篇解释该人开始写博客的文章，可能还有另一篇解释其博客设置的文章，但没有后续文章。另一种常见的博客格式是博客有一段时间定期发帖，然后是一段很长的休眠期，最后有一篇文章解释他们将重新开始发帖，然后就没有更多文章了（有些情况下，会有几篇这样的文章，间隔时间越来越长）。鉴于人们在开始写博客后继续写下去的比例很低，我认为我不应该费心投资博客基础设施，直到我知道我会写一段时间，所以尽管我已经拥有这个域名，但我没有费心去弄清楚如何将这个域名指向GitHub Pages，只是安装了一些流行博客软件的默认版本，甚至在我已经写了一篇文章之后才这样做。回顾过去，使用Octopress（Jekyll）是一个大错误；我选择它是因为当时我和一群做潮流东西的人混在一起，但事实证明它安装起来非常烦人，以至于人们组织了小型的“Octopress安装日”，这是一个坏兆头。结果发现，它不仅安装麻烦，还有相当多的故障，使用了一种无法获取上游更新的开发模型，而且速度极快地变慢了（没过多久，构建我的博客就需要整整一分钟，编译少量博客文章花费这么长时间太荒谬了）。我本应该只写纯HTML，直到有几篇文章，然后将其转换为<a href="https://twitter.com/danluu/status/1244023627613274115" rel="noopener noreferrer">自定义静态站点生成器</a>，或者使用<a href="https://wordpress.com/refer-a-friend/34nAGAYt06ZlZRYBjx1/" rel="noopener noreferrer">WordPress</a>，它可以在几分钟内启动，并且可以轻松迁移。但是，低前期投入的一部分就包括不对此进行研究，并相信我周围的人做出了合理的决定<sup id="fnref:G"><a href="#fn:G" rel="noopener noreferrer">5</a></sup>。总的来说，我支持保持低启动成本的想法，但如果我当时忽略了所有标准建议，要么做一些极简的东西，要么使用不流行但直接的选项，我本可以为自己节省很多工作。</p>
<p>“提高写作”这个目标是因为我发现我的写作尴尬得令人恼火，并想修正这一点。我经常写出在我看来笨拙的句子或段落，就像你拼错一个单词，无论你怎么重新拼写，它看起来都不对。拼写检查器现在已相当普及，你不再真正遇到拼写问题，但我们还没有能提高你写作水平的自动化工具（有些尝试，但它们往往会制造糟糕的写作）。我不担心任何特定的文章，因为我认为我可能需要花数年时间来改进我的写作，而且我不认为花数年时间重新编辑一篇文章会很令人满意。</p>
<p><a href="https://danluu.com/p95-skill/" rel="noopener noreferrer">正如我们之前讨论过的</a>，获得反馈可以大大加快技能获取速度，所以我聘请了一位我尊重其写作的专业编辑，并附上说明：“我的写作笨拙而尴尬，我想修正它。我不太关心拼写和语法问题。你能据此编辑我的写作吗？”我收到了关于我许多文章的详细反馈。我试图解决反馈中提出的问题，但更重要的是，努力在我的下一篇文章中避免出现相同或之前提到的问题。我可能学得有点慢，所以有时需要几篇文章才能解决一个问题，但随着时间的推移，我的写作提高了很多。</p>
<p>只有当我感觉想发布时才发布，是因为我通常更喜欢过程目标而不是结果目标，至少对于个人目标是这样。我最初有一个目标是每月花一定时间写博客，但当我意识到无论是否将其作为一种义务，我都会花足够时间写作时，我就放弃了这个目标。我认为关于博客的结果目标对某些人确实有效（例如，“每周发布一篇文章”），但如果你的目标是提高写作质量，结果目标可能适得其反（例如，为了在有限时间内实现“每周发布一篇文章”的目标，有人可能专注于发布东西，而不考虑如何提高质量，因为从结果目标的角度来看，提高质量是浪费时间）。</p>
<p>以写我想订阅的内容为目标，当然是非常主观的。我不喜欢其他博客中的一些东西，所以我尽量避免它们。例如：</p>
<ul>
<li>将可能是一整篇文章的内容拆分成多篇较小的文章</li>
<li>标题党</li>
<li>反复写同一话题却没有新意
<ul>
<li>这其中的一个子类别是拥有某种信念，然后每当出现支持该信念的证据时就写博客，却不提出现反证的证据</li>
</ul></li>
<li>没有RSS或Atom订阅源</li>
</ul>
<p>在自己的平台上写作是最次要的一个。一个主要原因来自平台的兴衰。在我开始写博客时，一些平台已经出现又消失。最近，Twitter收购了Posterous并将其关闭。有一段时间，Posterous是当时最时髦的平台，而Twitter决定完全关闭它，破坏了许多史上得票最高的HN文章的链接等。Blogspot，之前一个流行的写作场所，也被谷歌收购，之后严重降低了读者体验。避免时髦的平台效果很好。当我开始写博客时，人们纷纷使用的两个时髦平台是<a href="(https://news.ycombinator.com/item?id=4268832)" rel="noopener noreferrer">Svbtle</a>和Medium。<a href="https://www.designernews.co/stories/44300-is-svbtle-dead" rel="noopener noreferrer">在我开始写博客后不久，Svbtle基本上就被废弃了</a>，因为Medium在受众规模上显然将占据主导地位。而Medium从未找到良好的变现策略，并在尝试产生足够收入以证明其在融资1.6亿美元后的估值后，严重降低了读者体验。你无法信任别人的平台不会在你下面消失，或为了利润而彻底改变。</p>
<p>我想做的另一件相关的事情是在自己的空间（而不是在互联网评论中）写作。我过去写了很多HN评论<sup id="fnref:H"><a href="#fn:H" rel="noopener noreferrer">6</a></sup>，但HN评论的半衰期很短。除极少数例外，一篇评论将获得的几乎所有浏览量都在最初几天。而对于博客，则恰恰相反。一篇文章最初可能会获得一波流量，但只要你坚持写下去，大部分流量会在之后到来（例如，对于我的博客，当一篇文章在HN上时，我通常会获得大约两倍于基准水平的点击量，当然我大多数时候并没有文章在HN上）。写一篇“真正的博客文章”并不比写一篇HN评论多费多少功夫，所以我倾向于写博客文章而不是HN评论。另外，当我在这里写作时，创造的大部分价值在我和读者之间分配。如果我在别人的平台上写作，大部分价值将在平台和读者之间分配。如果我做视频，我可能没有选择，只能在YouTube或Twitch上，但对于文字，我确实有选择。看看<a href="http://exquora.thoughtstorms.info/" rel="noopener noreferrer">那些做出不同选择、决定为平台写评论的人后来怎样了</a>，我认为我为正确的理由做出了正确的选择。我确实看到了<a href="https://twitter.com/foone/status/1066547904532242437" rel="noopener noreferrer">在现有平台上评论减少的摩擦力</a>的吸引力，但即便如此，我宁愿付出额外摩擦的成本，写一些在我自己空间里的东西，而不是写在别处。</p>
<p>所有这些加在一起基本上就是全部了。这就是我的写作方式。</p>
<p>与其他博主不同，我不会试图告诉你“如何有用地写作”或“如何写得好”之类的。我同意Steve Yegge的观点，他认为<a href="https://sites.google.com/site/steveyegge2/you-should-write-blogs" rel="noopener noreferrer">你应该考虑写作，因为它可能具有高价值，而且价值可能以你意想不到的方式出现</a>，但如何写作应该真正源于你的目标和才能。</p>
<h4 id="appendix-changes-in-approach-over-time">附录：方法随时间的变化</h4>
<p>当我开始写博客时，我担心一篇文章会因为只包含一个简单想法而不够有趣，所以我经常等到能将两个或更多想法合并成一篇文章。回顾过去，我认为我早期的许多文章如果分成单独的文章会更好。例如，2016年这篇关于<a href="https://danluu.com/bimodal-compensation/" rel="noopener noreferrer">薪酬</a>的文章包含了薪酬可能变得双峰分布的想法，以及考虑到与其他同样高薪的领域（如金融、法律和医学）的准入门槛相比，程序员的薪酬高得令人难以置信。我不认为把这两个想法合并成一篇文章有多大价值，而且我认为如果关于程序员薪酬异常高的部分没有捆绑到一篇关于薪酬双峰分布的文章中，会有更多人阅读它。</p>
<p>我过去还做的一件事是避免写那些看起来太明显的东西。<a href="https://www.patreon.com/posts/58713950" rel="noopener noreferrer">但是，我逐渐认识到写下明显的东西有很多价值</a>，我一些最有影响力的文章都是关于我以前认为太明显而不值得写的话题：</p>
<ul>
<li><a href="https://danluu.com/look-stupid/" rel="noopener noreferrer">https://danluu.com/look-stupid/</a></li>
<li><a href="https://danluu.com/people-matter/" rel="noopener noreferrer">https://danluu.com/people-matter/</a></li>
<li><a href="https://danluu.com/culture/" rel="noopener noreferrer">https://danluu.com/culture/</a></li>
<li><a href="https://danluu.com/learn-what/" rel="noopener noreferrer">https://danluu.com/learn-what/</a></li>
<li><a href="https://danluu.com/productivity-velocity/" rel="noopener noreferrer">https://danluu.com/productivity-velocity/</a></li>
<li><a href="https://danluu.com/in-house/" rel="noopener noreferrer">https://danluu.com/in-house/</a></li>
</ul>
<p>排除这些近期文章，告诉我<a href="https://danluu.com/look-stupid/" rel="noopener noreferrer">https://danluu.com/look-stupid/</a>改变了他们操作方式的人，比告诉我的其他所有文章加起来还要多（而之所以接近，仅仅是因为有很多人告诉我关于薪酬的讨论让他们意识到可以找到一份更喜欢且年薪比以前高出数十万美元的工作，这是从人们告诉我文章毫无意义因为每个人都知道科技行业能赚多少钱的评论最多的一组文章）。</p>
<p>一个主要且相对较新的风格变化是我正在尝试使用更多例子。这是受Ben Kuhn评论的启发，到目前为止我很喜欢。与大多数博主相比，<a href="https://twitter.com/benskuhn/status/1431671165320376327" rel="noopener noreferrer">我在早期并不缺少例子</a>，但我注意到一点，增加比我自然倾向更多的例子确实能为读者澄清事情；“很多”例子能降低人们得出与我本意截然不同的想法的速度。一个具体的例子是，在一篇讨论<a href="https://danluu.com/p95-skill/" rel="noopener noreferrer">达到95百分位水平表现需要什么</a>的文章中，我只提供了几个例子，<a href="https://danluu.com/corrections/#p95" rel="noopener noreferrer">许多人填充了空白，认为远高于99.9百分位的表现就是95百分位，例如成为国际象棋大师是95百分位</a>。</p>
<p>另一个做出这种改变的人是Jamie Brandon。如果你读他早期的文章，<a href="https://www.scattered-thoughts.net/writing/imperative-thinking-and-the-making-of-sandwiches/" rel="noopener noreferrer">比如这篇</a>，他经常有一个引人入胜的想法和漂亮的措辞，例如这段关于他与Chris Granger一起开发Eve时的内容：</p>
<blockquote>
<p>人们经常告诉我，命令式编程是自然的编程形式，因为‘人们以命令式方式思考’。我能理解他们的意思。就在前几天，我还发现自己说：“嘿Chris，我饿了。我需要你走进厨房，打开橱柜，拿出一袋面包，打开袋子，取出一片面包，放在盘子上……”不幸的是，我没有说明盘子在哪里，所以这时Chris抛出了空指针异常并死了。</p>
</blockquote>
<p>但是，尽管有些部分真的很吸引人，他的早期写作在某种程度上与现实世界脱节，这是Jamie回顾自己的旧文时不喜欢的。关于添加更多细节，Jamie说：</p>
<blockquote>
<p>专注于具体例子并尽可能保持具体的关键在于：a) 让我更不可能出错，因为非具体的想法很难证伪，我很容易欺骗自己；b) 让读者更有可能吸收我试图传达的想法，而不是一些表面上相似但符合模糊文本的想法。</p>
<p>例子能固定想法，以便它们能被恰当地审视。</p>
</blockquote>
<p>另一个重大变化，也是我唯一要在这里讨论的真正属于散文风格的变化，是我更努力地尝试写作中保持一种有时被称为“叙事语法”的连续性。<a href="https://web.archive.org/web/20180611151516/http://www.sterlingediting.com/narrative-grammar-an-exercise/" rel="noopener noreferrer">Nicola Griffith的这篇文章有一些句子层面的例子</a>，但我也会在写作的更大结构上思考这个问题。我不认为自己特别擅长这个，但更多地思考这一点使我的写作更容易理解。这个变化，尤其是在更大规模上，确实是与一位擅长发现使写作更难理解的结构性问题的专业编辑合作的结果。但同时，如果某事难以理解有原因，我也不会太担心。一个具体的例子是，如果你阅读<a href="https://ask.metafilter.com" rel="noopener noreferrer">ask metafilter</a>或reddit上问题的答案，任何结构上非简单的问题都会有大量答案来自那些未能仔细阅读问题并回答错误问题的人，例如，如果有人问一个包含用<code>and</code>连接的两部分的问题，很多人只会读<code>and</code>的一半，并给出一个明显被<code>and</code>条件排除的答案。如果很多人连一个简短问题的<code>and</code>的两边都不愿意仔细阅读以给出满足两部分的答案，那么很多人也不会跟进任何人可能想写的最简单的东西。我认为试图引导那些连<code>and</code>两边都不愿意读的人通过一篇有结构的文章，对作者来说不是时间的有效利用，但我确实认为努力避免“叙事语法”问题是有价值的，因为这些问题可能让真正想阅读的人更难理解。</p>
<h4 id="appendix-getting-feedback">附录：获取反馈</h4>
<p><a href="https://danluu.com/p95-skill/" rel="noopener noreferrer">正如我们之前讨论的</a>，反馈可以极大地促进进步。不幸的是，那篇文章中的观点，即95百分位水平表现通常很差，也适用于反馈，使得大多数反馈适得其反。</p>
<p>我花了很多时间观察人们在私人渠道中获得反馈，并看到他们如何根据反馈改变他们的写作，至少在我观察的渠道中（程序员，而非专业作家或编辑的评论），大多数反馈被忽略了。而当反馈被采纳时，因为几乎所有的反馈都是糟糕的，而且人们通常并不完美，甚至不太擅长筛选好的反馈，所以被采纳的反馈通常是糟糕的。</p>
<p>根本上，大多数反馈都有本文提到的问题，是“你应该像我那样写”的一种形式，这通常行不通，除非反馈的作者在给出反馈时非常小心，但很少有人这样做。反馈往往是表面的建议，忽略了写作中严重的结构性问题。此外，反馈也往往是“最低公分母”式的反馈，将优美的散文变成Strunk-and-White风格化的平庸散文。我不认为我的散文风格特别好，但我见过一些拥有自然优美风格的人向程序员寻求反馈，结果他们的写作变成了任何人都能写出的无聊散文。</p>
<p>另一面是，当人们得到我认为好的、实质性的反馈时，最常见的回应是“算了，没事”。我认为这是大多数反馈都是“你应该像我那样写”的对立面。大多数人对反馈的回应是“我想怎么写就怎么写”。</p>
<p>尽管这篇文章侧重于多种风格都可以奏效，但同样真实的是，给定一种风格和一组目标，写作可以更好或更差。但是，大多数得到反馈的人<a href="https://twitter.com/danluu/status/1428445465662603272" rel="noopener noreferrer">对写作了解不够，无法知道什么是更好什么是更差</a>，所以他们无法区分好反馈和坏反馈。</p>
<p>解决这个问题的一种方法是获得你信任其判断的人的反馈。如文章所述，我的做法是聘请一位我尊重其写作（和编辑）的专业编辑。</p>
<p>我做的另一件事，也是我个性的核心方面，与写作关系不大，是我相对认真地对待反馈，并努力避免对反馈有“算了，没事”的回应。我不会说这是最佳状态，因为我有时花了太多时间在糟糕的反馈上，但我思维方式的一个核心部分是我意识到大多数人过度自信，并且经常因为过度自信而出错，所以我不信任自己的推理，并花相对大量的时间和精力思考反馈，以试图降低我的过度自信率。</p>
<p>有时，我会花可笑的长时间思考一些现在看来非常糟糕且“显然”不正确的反馈，因为我一直担心因认为其不正确而错误地驳回它。我注意到的一件事是，随着人们获得受众，有些人变得越来越自信，最终变得极度过度自信。这很容易理解——当你声名鹊起时，你会获得更多的曝光和更多认为你总是对的“粉丝”，而另一方面，你也会得到更多“显然”错误的评论。</p>
<p>在几乎没人读我博客的时候，我得到的大部分评论都相当好。随着我读者越来越多，好评论的百分比下降了。从观察其他人如何处理这个问题来看，一种常见的失败模式是，他们会看到他们的文章吸引了大量明显错误的评论，然后错误地认为所有的批评者都是笨蛋，他们自己基本上从不犯错。除了“非常认真地对待批评”之外，我并没有真正的解决办法。由于这里的失败模式涉及判断中的盲点，我没有看到一个简单的方法来认真对待某条批评，同时又不会因为盲点而错误地驳回批评。</p>
<p>根本上，我的解决方案是避免查看大多数反馈，同时努力接受我信任的人的反馈。</p>
<p>当涉及散文问题时，我们在上面讨论过，聘请一位我尊重其写作和编辑的专业编辑，并在散文问题上听从他们的意见，效果很好。</p>
<p>当涉及逻辑严密性或一般趣味性时，这些更难外包给一个人，我有一组<a href="https://twitter.com/Aella_Girl/status/1453936895088488449" rel="noopener noreferrer">我信任其判断的人</a>会看大多数文章。如果任何我信任其判断的人认为一篇文章有趣，我就认为这是一个强有力的确认，我基本上忽略那些说文章无聊或没意思的评论。我几乎所有在“告诉我的文章改变了他们生活”的人数中名列前茅的文章，都收到了一些我认为判断力不差的人说文章似乎无聊、无意义、太明显不值得写或根本不感兴趣的评论。我过去常常认真对待说文章不有趣的评论，但回想起来，那是一个错误，浪费了我很多时间，也没有提高我的写作。我认为这与那些说“像我那样写”的人没什么不同；相反，这是那些在“有趣性”方面有相似心智模型的人，他们无法想象其他人会觉得他们不觉得有趣的东西有趣。当然，不是所有人的思维都如此，但擅长模拟他人觉得什么有趣的人通常不会留下“这很无聊/毫无意义”之类的反馈，所以这种形式的反馈几乎可以肯定是无价值的。</p>
<p>当涉及论证的严密性时，我采取与对待趣味性相反的态度，即我非常认真地对待负面评论，而对正面评论则不太在意。有时，我因为这个在特定文章上浪费了很多时间。我的解决方法是努力忽略那些经常给出糟糕反馈的人的反馈。我认为这样做是危险的，因为有选择地忽略反馈是制造回音室的好方法，但真的认真花时间思考反馈，即使我没有看到逻辑漏洞，也是非常耗时的，考虑到我收到反馈时会重新评估自己的工作，我认为没有其他替代方法。</p>
<p>最近我开始做的一件事让我对此感觉好多了，那就是观察人们给别人的反馈。给我糟糕反馈的人通常给其他人的反馈方式也差不多一样糟糕。因为我不太担心自己存在某种认知偏差，会误导我认为当涉及他们对别人写作的反馈时我是对的他们是错的，我可以花几分钟时间看到他们对别人写作的反馈是胡扯，然后发现他们对我的反馈方式完全一样也是胡扯。</p>
<h4 id="appendix-where-i-get-ideas">附录：我的想法从何而来</h4>
<p>我经常被问到如何产生想法。我原本不打算说任何关于这个的内容，因为我没什么可说的，但Ben Kuhn强烈敦促我加上这一节，“以便其他人意识到你有多么另类”。</p>
<p>我的感觉是世界充满了有趣的东西，想法无处不在。我有大约一百篇草稿，我认为基本上可以发布，但出于各种原因没有优先完成。如果算上那些我已在脑海中构思但还没有写下来的帖子，数量肯定达到成千上万。如果我辞职，然后坐下来全职写作直到去世，我认为即使我只写已经有过的想法，也不会用完。世界是如此广阔、奇妙且具有分形般的趣味性。</p>
<p>例如，我最近开始玩冲浪皮划艇（一种皮划艇），可以说，在几周之后，我大概有二十个左右的博客文章想法，我认为这些可以以普通受众为目标写出来，就像<a href="https://danluu.com/branch-prediction/" rel="noopener noreferrer">这篇关于分支预测的文章</a>是为普通受众写的，因为它不假设有任何硬件背景。我可以写两篇关于独木舟桨进化和设计的不同技术方面的文章，以及两篇关于文化因素及其如何影响不同独木舟桨设计发展的文章。皮划艇桨的设计在近代历史上要丰富得多，这很容易再写出五到六篇文章。船体设计的技术方面更为丰富，可以成为文章的不竭源泉，尽管目前我脑子里只有四篇特定文章的想法，但文化和历史方面对我来说似乎也很有趣，这补充了我脑海中关于这个话题的二十件事。</p>
<p>我脑子里有二十篇关于皮划艇和独木舟的文章，并不是因为我对皮划艇和独木舟特别感兴趣。一切似乎都足够有趣，值得写二十篇文章。我现有的很多文章，如果我有时间写出来，可能会成为一个更长系列的一部分。例如，<a href="https://danluu.com/bad-decisions/" rel="noopener noreferrer">这篇关于棒球决策的文章</a>，在我脑海中是一系列关于决策的较长（10篇以上）文章的第一篇，我从未抽出时间写出来，我怀疑我永远不会写，因为有太多其他有趣的东西要写，而时间又不够。</p>
<h4 id="appendix-other-writing-about-writing">附录：其他关于写作的文章</h4>
<ul>
<li>Richard Lanham：<a href="https://amzn.to/3DTiVU2" rel="noopener noreferrer">分析散文</a>
<ul>
<li>我认为从这本书中直接提取可操作的东西并不容易，但我发现它剖析散文节奏的方式非常有趣</li>
</ul></li>
<li>Robert Alter：<a href="https://amzn.to/3oOs3VT" rel="noopener noreferrer">摩西五经</a>
<ul>
<li>关于Robert Alter在翻译中做出某些微妙选择的脚注</li>
</ul></li>
<li>Francis-Noel Thomas &amp; Mark Turner：<a href="https://amzn.to/3ymHFDh" rel="noopener noreferrer">清晰简单如真理</a>
<ul>
<li>如果你想以干净、权威的风格写作
<ul>
<li>将此书作为指南的人通常写得没有细致差别，并有很多不正确的陈述，但我认为这并非必要。此外，文章通常引人入胜，这是许多人更喜欢细致差别的方式；许多科技界的受欢迎作者使用类似的风格</li>
</ul></li>
</ul></li>
<li>Gary Hoffman &amp; Glynis Hoffman：<a href="https://amzn.to/33qX6if" rel="noopener noreferrer">再见，Strunk &amp; White：新学术散文手册</a></li>
<li>Tracy Kidder &amp; Richard Todd：<a href="https://amzn.to/3DRTCSp" rel="noopener noreferrer">好散文：非虚构的艺术</a>
<ul>
<li>Kelly Eskridge向我推荐了这本书，因为它深入探讨了编辑和作者如何互动，我发现记住这一点在与编辑合作时很有用；读这本书可能是理解与优秀编辑合作是什么样的低效方式，但如果你好奇<a href="https://amzn.to/3ETX7Jw" rel="noopener noreferrer">《新机器之魂》</a>的作者如何写作，可能值得一读；如果你不确定编辑能为你做什么，这本书是很好的读物</li>
</ul></li>
<li>Steve Yegge：<a href="https://sites.google.com/site/steveyegge2/you-should-write-blogs" rel="noopener noreferrer">你应该写博客</a>
<ul>
<li>特别是“理由#3”，Jacob Gabrielson / Zero Config的故事，尽管整篇都值得阅读</li>
</ul></li>
<li>Lawrence Tratt：<a href="https://tratt.net/laurie/blog/entries/what_ive_learnt_so_far_about_writing_research_papers.html" rel="noopener noreferrer">关于撰写研究论文我目前学到的</a>
<ul>
<li>写得很好，像Lawrence的其他作品一样。而且我认为有趣的是，Lawrence在大多数主要方面与我有着完全不同的过程，但最终的风格在比较所有编程博主时相对相似（当然比这篇文章正文中提到的任何作者都更相似）</li>
</ul></li>
<li>Julia Evans：<a href="https://jvns.ca/blog/2020/12/05/how-i-write-useful-programming-comics/" rel="noopener noreferrer">我如何撰写有用的编程漫画</a>
<ul>
<li>很好地解释了Julia的小册子为何成功；也与我的方法完全不同，但这次结果也完全不同</li>
</ul></li>
<li>Yossi Kreinen：<a href="https://yosefk.com/blog/blogging-is-hard.html" rel="noopener noreferrer">写博客很难</a>（这个标题与他的下一篇文章“底层很容易”形成对比）
<ul>
<li>一个罕见的例子，第一篇基本上是“我要写博客”的文章，既有趣，又有有趣的后续文章；还有Yossi的写作哲学</li>
</ul></li>
<li>Phil Eaton：<a href="https://notes.eatonphil.com/2024-04-10-what-makes-a-great-tech-blog.html" rel="noopener noreferrer">是什么造就了出色的技术博客</a>
<ul>
<li>Phil在技术博客中喜欢的属性的简要总结。它有点与人们通常从Thomas和Turner的《清晰简单如真理》中得出的相反</li>
</ul></li>
</ul>
<h4 id="appendix-things-that-increase-popularity-that-i-generally-don-t-do">附录：增加受欢迎程度但我不常做的事情</h4>
<p>以下是我通过观察他人发现有效但我自己不做的事情，但如果你想拥有广泛的受众，或许你可以尝试其中一些：</p>
<ul>
<li>使用标题党
<ul>
<li>说脏话或说某事“是毒药”或“是X的越南战争”或其他高度情绪化的短语似乎特别有效</li>
</ul></li>
<li>夸大声望/成就/头衔</li>
<li>使用权威的语气和/或风格</li>
<li>以愤怒的语气写文章或旨在引起愤怒的文章</li>
<li>频繁写作</li>
<li>获得他人背书</li>
<li>写热门、当前的话题
<ul>
<li>对近期事件发表看法</li>
</ul></li>
<li>对主题使用故意惊世骇俗/有争议的框架</li>
</ul>
<h4 id="appendix-some-snippets-of-writing">附录：一些写作摘录</h4>
<p>如果你不熟悉提到的作者，以下是一些我认为能代表他们写作风格的摘录：</p>
<p>Joel Spolsky：</p>
<blockquote>
<p>我真的在意的原因是微软正在吸纳太多的程序员。在微软，他们那些不道德的招聘人员向毫无戒心的大学生开出附带苛刻条件的爆炸性offer，而谷歌（你们在我的雷达上）向那些极限飞盘经验比Python还丰富的孩子们支付无法维持的薪水，他们的主要工作将在谷歌园区玩桌上足球，到处试图让某个人……任何人……来看他们用“20%时间”写的演示代码，做某种，让我猜猜，基于云的同步……在微软和谷歌之间，一个聪明的计算机科学毕业生的起薪正危险地接近六位数，而这些聪明的孩子，我们大学的精华，正在从事毫无希望和用处的架构天文学，因为这些公司就像癌症，不惜一切代价追求增长，即使他们想不出一件对我们有用的东西，但他们下周还需要3000-4000名计算机科学毕业生。而且该死的桌上足球不会自己玩<i>它自己</i>。</p>
</blockquote>
<p>以及</p>
<blockquote>
<p>1991年我开始面试程序员时，我通常允许他们使用任何他们想用的语言来解决我给出的编码问题。99%的情况下，他们选择C。如今，他们倾向于选择Java……Java通常不是一种足够难的编程语言，无法用来区分优秀程序员和平庸程序员……一个纯Java的计算机科学学位并没有真正筛掉那些缺乏处理这些概念所需心智敏捷性的学生。作为雇主，我看到100%采用Java的学校已经开始培养出相当多的计算机毕业生，他们根本不具备在比又一个Java会计应用程序更复杂的任何东西上工作的程序员素质，尽管他们勉强通过了新简化的课程。这些学生永远无法通过麻省理工的6.001或耶鲁的CS 323，坦率地说，这就是为什么作为雇主，麻省理工或耶鲁的计算机科学学位比杜克大学（最近全面转向Java）或宾夕法尼亚大学（用Java替代了Scheme和ML）的学位更有分量的原因之一。</p>
</blockquote>
<p>Paul Graham：</p>
<blockquote>
<p>几年前，一位风险投资家朋友告诉我他参与的一家新创公司。听起来很有前途。但下次我和他交谈时，他说他们决定在Windows NT上构建他们的软件，并且刚刚聘请了一位非常有经验的NT开发人员担任首席技术官。当我听到这个消息时，我想，这些人完蛋了。第一，首席技术官不可能是一流的黑客，因为要成为著名的NT开发人员，他必须自愿多次使用NT，而我无法想象一个伟大的黑客会这么做；第二，即使他很优秀，如果项目必须在NT上构建，他也很难招聘到优秀的人为他工作。</p>
</blockquote>
<p>以及</p>
<blockquote>
<p>什么样的人会成为讨厌鬼？任何人都能成为吗？我不确定，但我注意到一些模式。讨厌鬼通常是在非常特定的意义上的失败者：尽管他们偶尔有才华，但他们从未取得过什么成就。而且，任何成功到获得显著名声的人都不太可能将另一个著名人物视为骗子，因为任何名人都知道名声是多么随机。</p>
</blockquote>
<p>Steve Yegge：</p>
<blockquote>
<p>当我在2003年第一次读这本书时，我感到一种可怕的冰冷感，就像你刚意识到过去5年你一直穿着裤子上班但裤子掉在脚踝上那种感觉。第二天我随意地问了一圈：“是的，呃，你读过那本，嗯，《重构》书，当然，对吧？哈，哈，我只是因为我很久以前就读过，不是刚读完，所以问问。”在我调查的20人中，只有1人读过。谢天谢地，我们都把裤子掉在脚踝上了，不只是我。</p>
<p>这是一本关于如何写出好代码的精彩书，这样的书不多。可能没有。他们通常不在学校教你如何写出好代码，你可能永远学不会。可能需要几年时间，你可能仍然缺少一些关键概念。我肯定就是如此。
...
如果你是一个相对有经验的工程师，你会认出书中80%或更多的技术，这些是你已经弄明白并开始习惯去做的。但它给了它们所有名称，并客观地讨论了它们的优缺点，我觉得这非常有用。它还揭穿了我从程序员生涯早期就珍视的两三种做法。不给代码加注释？局部变量是万恶之源？这家伙是疯子吗？读读它，自己判断吧！</p>
</blockquote>
<p>以及</p>
<blockquote>
<p>杰夫·贝佐斯是一个臭名昭著的微观管理者。他微观管理亚马逊零售网站上的每一个像素。他聘请了拉里·泰斯勒，苹果的首席科学家，可能是全世界最著名和最受尊敬的人机交互专家，然后完全无视拉里三年里说的每一个该死的事情，直到拉里最终——明智地——离开了公司。拉里会做这些大型可用性研究，并且毫无疑问地证明没有人能理解那个该死的网站，但贝佐斯就是无法放手那些像素，那些登陆页面上数百万语义密集的像素。它们就像他数百万自己珍贵的孩子。所以它们都还在，而拉里已经不在了。</p>
<p>微观管理并不是亚马逊做得比我们好的第三件事。我是说，没错，他们微观管理得很好，但我不认为这算是什么优势。我只是想在这里设定背景，帮助你理解发生了什么。我们谈论的是一个在公开场合多次严肃地说人们应该付钱给他才能在亚马逊工作的人。他分发印有他名字的小黄便利贴，在人们与他意见不一致时提醒他们“谁是公司老板”。这家伙是个典型的……嗯，史蒂夫·乔布斯，我猜。只是没有时尚或设计品味。贝佐斯非常聪明；别误会我。他只是让普通的控制狂看起来像吸了大麻的嬉皮士。</p>
</blockquote>
<p>Julia Evans：</p>
<blockquote>
<p>现在我正在从纽约到蒙特利尔的一百万小时火车旅途中。所以我在看strace的输出，因为，呃，<code>strace</code>很酷，它正在教我一些关于我经常使用的命令行工具如何工作的知识。</p>
<p><code>strace</code>的作用是捕获执行程序时调用的每一个系统调用。系统调用是用户空间程序和内核之间的接口，所以看strace的输出是理解Linux如何工作以及运行一个程序到底涉及什么的一个有趣方式。</p>
<p>例如！<code>killall</code>！我运行了</p>
<p><code>strace killall ruby1.9.1 2&gt; killall-log.</code></p>
</blockquote>
<h4 id="appendix-how-gpt-4o-thinks-people-write">附录：GPT-4o认为人们如何写作</h4>
<p>[2024年更新]</p>
<p>我见过相当多的人对GPT-3、GPT-3.5和GPT-4“模仿”某人并创作出其风格作品的能力赞不绝口，让你可以阅读假设的文章或与某人进行假设的对话。我发现这通常效果不佳。以下是提示“生成一段200字关于微软的引语，内容是X可能会写的。”的示例（我尝试了其他提示，结果同样糟糕，因为内容、语气和风格与这些人的实际产出不太相似）：</p>
<p>Joel Spolsky：</p>
<blockquote>
<p>微软是工程卓越与商业智慧复杂相互作用的一个研究案例。他们不仅仅是一家构建软件的公司；他们是一个庞大的、多触手的庞然大物，几十年来，成功地将自己插入了科技行业的几乎每个角落。他们有过失误——有人记得Clippy或Zune吗？——但他们也取得了惊人的成功。微软的魔力在于他们转向和适应的能力，经常将看似巨大的失误转变为他们战略不可或缺的一部分。</p>
<p>他们最初的主导地位建立在Windows和Office之上，这对强大的组合定义了几十年来个人和企业计算。但真正令人着迷的是他们如何进化。在纳德拉领导下，微软从一个笨重的巨人转变为一个灵活的创新者，一头扎进Azure的云中，并拥抱了他们曾经排斥的开源社区。他们创建了一个强大且，我敢说，有趣的开发者生态系统。微软不再仅仅关乎软件；它关乎平台、服务和一个不断增长的互联工具宇宙。它们证明了即使是最大的公司，如果愿意不断地重新思考和重塑自己，也能保持相关性。</p>
</blockquote>
<p>Paul Graham：</p>
<blockquote>
<p>微软的历程是适应性战胜惰性的典型例子。在其早期，该公司体现了抓住机会的创业精神，从一个两人团队转变为一个通过智取更大、更成熟的竞争对手而形成的庞然大物。他们掌握了软件商品化的艺术，并建立了一个生态系统，使个人计算成为普遍现实。然而，成功滋生了自满。多年来，微软紧抱着Windows和Office这两个摇钱树，错过了移动和互联网革命，而年轻、更灵活的竞争对手重塑了行业。但微软的故事并没有结束。在萨蒂亚·纳德拉富有远见的领导下，该公司重新发现了其创业根基，转向云计算，并以类似于其早期的热情拥抱了开源。这种转型的能力，在面临深刻的行业变化时重新想象自己的能力，是使微软保持相关性的原因。这对每个初创公司和科技巨头都是一个教训：长寿的关键不仅是创新，还有不懈追求重塑。在微软的世界里，唯一不变的是变化，而公司真正的天才在于其适应和繁荣的坚定能力。</p>
<p>微软是一个引人入胜的探索对象，特别是如果你喜欢深入技术的细节并观察它们如何扩展复杂性。很长一段时间，如果你使用计算机，你无法避开这个庞然大物。Windows和Office就像你呼吸的空气——无处不在且不可或缺。但真正有趣的是微软如何进化。他们从桌面的守门人变成了云领域的先驱和开源的倡导者。</p>
<p>想想Azure。这是一个巨大的基础设施，为从小型初创公司到大型企业的一切提供动力，使开发者只需点击几下就能在全球部署代码。然后是他们对开源的拥抱。记得他们收购GitHub的时候吗？那表明微软是认真的支持开发者社区。这不仅仅是一次公关活动；他们正在贡献甚至领导我们数百万人依赖的项目。</p>
<p>他们的工具对我们许多人来说已经变得不可或缺。例如，Visual Studio Code，使用起来很愉快。它轻量、快速、可扩展——是微软如何学会构建开发者喜爱的产品的完美例证。这是一家不惧怕重塑自己的公司，并在此过程中让我们的科技生活变得更好、更有趣。</p>
</blockquote>
<p><i>感谢Yossi Kreinin、Ben Kuhn、Laurence Tratt、Heath Borders、Jamie Brandon、Julia Evans、Vegard Nossum、Julien Kirch、Bram Delver和Pam Wolf的评论/更正/讨论。</i></p>
<div>
<hr>
<ol>
<li id="fn:P"><p>“奏效”对不同的人可能意味着非常不同的事情，但在这一节中，我们将看看受欢迎的博客，因为当我知道的人在写了一段时间博客后沮丧地停止写作时，最常见的原因是他们的博客基本上没有读者。</p>
<p>当然，许多人写作不是为了拥有读者，有些人甚至试图避免拥有超过几个读者（通过某种方式“锁定”文章，使只有“朋友”可以访问），但是，我认为“奏效”的范围非常广泛，许多不同的风格都可以奏效，这一观点即使目标是让几个朋友阅读博客也不会改变。</p>
<a href="#fnref:P" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:O">这是相当随意的。在其他社交圈中，Jeff Atwood、Raymond Chen、Scott Hanselman等人可能在名单上，但这不会改变观点，因为所有这些人的风格也彼此不同，也与我名单上的人不同。<p></p>
<a href="#fnref:O" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:7">2017年是终点，因为我在那时前后减少了对编程网络文化的关注，不清楚在我认识的人中2017年后人们在读什么。<p></p>
<a href="#fnref:7" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:C">在体育运动中，真正弄清楚如何提示人们做正确事情的精英教练可以大大加速学习，但在体育运动之外，尽管不乏愿意提供指导的人，但很少能找到一个真正弄清楚可以给学生哪些提示，能帮助他们比仅仅天真地测量他们的行为并应用一点内省更快地达到正确结果的人。<p></p>
<a href="#fnref:C" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:G">事实证明，写博客对我来说非常棒（例如，我的博客让我找到了现在的工作，促成了我大部分朋友的相识，导致人们给我发送关于行业内各种动态的有趣故事等），但我认为在开始写博客之前，这不是一个可预测的结果。我的猜测是，基于基本情况，最可能的结果是失败。<p></p>
<a href="#fnref:G" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:H">例如<a href="https://news.ycombinator.com/item?id=6315483" rel="noopener noreferrer">这条关于与其他高薪工作相比编程工作多么舒适的评论</a>（这变成了<a href="https://danluu.com/bimodal-compensation/" rel="noopener noreferrer">这篇关于程序员薪酬的文章的后半部分</a>、<a href="https://news.ycombinator.com/item?id=4652367" rel="noopener noreferrer">这条关于写作报酬的评论</a>和<a href="https://news.ycombinator.com/item?id=4920429" rel="noopener noreferrer">这条关于桌面游戏设计演变的评论</a>）。<p></p>
<a href="#fnref:H" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 28416 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/writing-non-advice/</link>
      <guid isPermaLink="false">https://danluu.com/writing-non-advice/</guid>
      <pubDate>Mon, 13 Dec 2021 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>延迟测量的潜在问题</title>
      <description>[AI 摘要] 本文讨论了延迟测量中的潜在问题，包括不透明延迟、缺乏集群范围聚合和分钟级分辨率。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文讨论了延迟测量中的潜在问题，包括不透明延迟、缺乏集群范围聚合和分钟级分辨率。</div><p><small><i>这是我在一两年前在Twitter做的一次简短闪电演讲的伪转录稿（实际用词已调整为比100%忠实转录更易读的形式），关于我们在使用延迟指标时遇到的陷阱（服务名称已根据通信要求匿名处理）。自那次演示以来，在基础设施方面已取得重大进展，因此现状较当时已有很大改善，但我认为这仍然相关，因为通过与同侪公司的同行交流，发现许多人正面临类似问题。</i></small></p>

<p>我们经常在Twitter使用<a href="https://research.google/pubs/pub40801/" rel="noopener noreferrer">尾部延迟</a>指标。最常见的情况是，服务所有者希望获得其服务的集群范围或Twitter范围的延迟数据。不幸的是，由于我们延迟测量设置中的一些历史特殊性，服务所有者倾向于使用的数字与我们希望测量的数字存在差异：</p>

<ul>
<li>不透明的、未被检测到的延迟</li>
<li>缺乏集群范围的聚合能力</li>
<li>分钟级分辨率</li>
</ul>

<h4 id="opaque-uninstrumented-latency">不透明的、未被检测到的延迟</h4>

<p>当我们查看大多数服务的仪表盘时，显示和用于告警的延迟指标通常来自服务本身运行的服务器。一些由高级SRE（他们曾因不可见的延迟问题受过教训）设置的仪表盘的服务，也会包含来自服务调用者观测到的客户端延迟。我想讨论这种设置的三个问题。</p>

<p>在本次演讲的范围内，我们可以将客户端请求视为在客户端“用户”代码将请求传递给我们RPC层（Finagle）之后、客户端用户代码接收到响应之前，经过了以下管道（由于Finagle当前处理请求的方式，一旦请求移交给我们在使用的网络库（<a href="https://netty.io/" rel="noopener noreferrer">netty</a>），我们无法获取特定请求的时间戳）：</p>

<p><code>客户端 netty -&gt; 客户端 Linux -&gt; 网络 -&gt; 服务器 Linux -&gt; 服务器 netty -&gt; 服务器“用户代码” -&gt; 服务器 netty -&gt; 服务器 Linux -&gt; 网络 -&gt; 客户端 Linux -&gt; 客户端 netty</code></p>

<p>正如我们之前在[一份内部文档中量化<a href="https://www.kernel.org/doc/html/latest/scheduler/sched-bwc.html" rel="noopener noreferrer">CFS带宽控制节流</a>的影响以及我们使用过大的线程池如何导致节流]中所看到的那样<sup id="fnref:W"><a href="#fn:W" rel="noopener noreferrer">1</a></sup>，我们经常在netty中及以下层发生大量排队，这会产生连锁反应，导致服务被内核节流，这通常会导致大量不透明延迟，尤其是在高负载时——此时我们最希望仪表盘能显示正确的延迟数字。</p>

<p>当我们在服务器端采样延迟时，我们基本得到的是：</p>

<ul>
<li>服务器服务“用户代码”的延迟</li>
</ul>

<p>当我们在客户端采样延迟时，我们基本得到的是：</p>

<ul>
<li>服务器服务“用户代码”的延迟</li>
<li>服务器端 netty 延迟</li>
<li>服务器端 Linux 延迟</li>
<li>客户端 Linux 延迟</li>
<li>客户端 netty 延迟</li>
</ul>

<p>这里有两个问题：第一，仅凭指标数据，我们无法很好地分辨栈中不透明部分的延迟是来自客户端还是服务器端。作为服务所有者，如果你基于客户端延迟设置告警，即使你的服务运行平稳，当客户端延迟因netty或Linux中排队过多而上升时，你也会收到告警。</p>

<p>其次，鉴于我们暴露的数据，合理查看的客户端延迟指标会给出客户端与所有服务器通信的延迟，这与我们在服务器指标上看到的视图非常不同——服务器指标给出的是每台服务器的延迟数据，而且没有好的方法能聚合所有客户端的每服务器客户端数据，因此很难判断，例如，某个特定服务器实例的netty中是否存在高延迟。</p>

<p>以下是一些在客户端测量与服务器端测量的集群范围延迟差异的例子。这些例子是特意挑选的，以展示客户端与服务器端延迟差异的一个横截面。</p>

<p><img src="https://danluu.com/images/latency-pitfalls/service-1.png" alt="显示客户端测量与服务器端测量延迟存在巨大差异的图表" width="1259" height="778"></p>

<p>这是一个<a href="https://en.wikipedia.org/wiki/Cumulative_distribution_function" rel="noopener noreferrer">累积分布函数图</a>，采用了CDF的标准方向，纵轴是百分位数，横轴是数值，这意味着向下和向右表示更高延迟，向上和向左表示更低延迟，线越平缓表示延迟增长越快，线越陡峭表示延迟增长越慢。</p>

<p>由于该图表在两个轴上都是对数刻度，客户端与服务器端延迟的差异实际上很大，尽管这些线看起来并没有那么远。例如，如果我们看99%分位数延迟，在服务器端测量约为16毫秒，而在客户端测量约为240毫秒，相差15倍。或者，如果我们看一个固定的延迟值，比如240毫秒，然后向上查找百分位数，会发现这是客户端的99%分位数延迟，但在服务器端则远高于99.9%分位数延迟。</p>

<p>下面的图表具有类似的特性，尽管客户端与服务器端之间的差异会有所不同。</p>

<p><img src="https://danluu.com/images/latency-pitfalls/service-2.png" alt="显示在客户端测量与服务器端测量在p99.5之前差异适中，p99.5之后差异巨大" width="1259" height="778">
<img src="https://danluu.com/images/latency-pitfalls/service-3.png" alt="显示在客户端测量与服务器端测量在p74之前差异很小，之后差异逐渐扩大" width="1259" height="778">
<img src="https://danluu.com/images/latency-pitfalls/service-4.png" alt="显示在客户端测量与服务器端测量在接近客户端超时值之前差异适中，接近超时值时差异巨大" width="1259" height="778">
<img src="https://danluu.com/images/latency-pitfalls/service-5.png" alt="显示在客户端测量与服务器端测量在p999之前差异很小，之后迅速增大" width="1259" height="778"></p>

<p>我们可以看到，客户端测量与服务器端测量的延迟常常存在显著差异，即使在较低百分位数差异较小的情况下，在较高百分位数时差异有时也会变得很大——因为更高的负载可能导致更多排队，从而在netty和内核中产生更多延迟。</p>

<p>需要注意的一点是，对于任何特定的测量到的服务器端延迟值，我们都能看到非常宽范围的客户端延迟值。例如，这是<code>service-5</code>的客户端与服务器端延迟的放大散点图。如果我们缩小范围，会看到对于一个服务器端测量延迟为10毫秒的请求，我们可能看到客户端测量延迟高达500毫秒。更普遍地说，我们看到许多请求的服务器端测量延迟与客户端测量延迟非常相似，但也有少数请求的服务器端测量延迟是对客户端测量延迟的非常不准确的表示。在几乎所有这些情况下，由于栈中我们不可见部分的排队，客户端测量延迟更高，而在极少数情况下，由于我们检测方面的一些问题，客户端测量延迟会更低。在下面的图中，由于我们追踪延迟的方式，我们对延迟只有1毫秒的粒度。下面图表上的点已随机抖动了+/- 0.4毫秒，以便更好地展示图表上非常密集点的分布情况。</p>

<p><img src="https://danluu.com/images/latency-pitfalls/service-scatter.png" alt="每请求的客户端与服务器端延迟散点图，显示任何特定的服务器端延迟值可能对应非常宽范围的客户端延迟值" width="1259" height="778"></p>

<p>虽然有可能通过netty和内核来检测请求延迟（在Finagle移交请求之后进行追踪）（内核甚至有一些钩子可以使这变得相对简单），但这可能在近期值得做的工作量太大。如果你想知道你的服务受不透明延迟影响的程度，利用<a href="https://zipkin.io/" rel="noopener noreferrer">Zipkin</a>和<a href="https://danluu.com/tracing-analytics/" rel="noopener noreferrer">Rebecca Isaacs、Jonathan Simms 和 Rahul Iyer 所做的工作</a>，可以相当容易地获得一个大致的概念，这也是我生成上面那些图表的方式。这些代码的源代码在[我们monorepo中的一个路径]，如果你只是想检查另一个不同的服务，你可以插入你自己的服务名称。</p>

<h4 id="lack-of-cluster-wide-aggregation-capability">缺乏集群范围的聚合能力</h4>

<p>在上面的例子中，我们能够获得集群范围的延迟百分位数，因为我们使用了来自Zipkin的数据，该系统尝试以均匀随机的方式采样请求。由于各种原因，服务所有者主要依赖指标数据，虽然更完整（因为是无采样的），但不允许我们计算集群范围的聚合，因为我们是在每个分片基础上预计算固定聚合，并且无法从分片聚合中重建集群范围的聚合。</p>

<p>通过查看我们服务的仪表盘，最常见的延迟目标是分片级99%分位数延迟的分片平均值（一些位于请求树深处的服务，如缓存，使用更尾部的数字）。不幸的是，取分片尾部延迟的平均值违背了监控尾部延迟的目的。如果我们思考<a href="https://brooker.co.za/blog/2021/04/19/latency.html" rel="noopener noreferrer">为什么我们想要使用尾部延迟</a>，因为当我们有高扇出和深度请求树时，很小一部分服务器响应变慢就可能拖慢许多或大多数顶级请求，而取尾部延迟的平均值未能捕捉到使用尾部延迟的价值——因为它未能捕捉到“一小部分服务器响应变慢就可能拖慢许多或大多数请求”这一特性，同时也<a href="https://brooker.co.za/blog/2017/12/28/mean.html" rel="noopener noreferrer">错过了查看集群范围平均值的优点</a>（后者可以从分片平均值中重建）。</p>

<p>例如，当我们有几个坏节点返回慢响应时，这对分片平均尾部延迟的影响很小，尽管集群范围的尾部延迟会大幅升高。正如我们在[一份量化整个集群中机器级问题范围以及对数据完整性和性能影响]的文档中看到的那样<sup id="fnref:H"><a href="#fn:H" rel="noopener noreferrer">2</a></sup>，我们经常遇到主机级问题，这可能使节点上的尾部延迟上升一个或多个数量级，有时甚至能使节点上的中位数延迟超过其他节点上的尾部延迟。由于少数甚至一个这样的节点就可能决定一个集群的尾部延迟，因此对所有节点取平均值可能会产生误导，例如，如果我们有一个100节点的集群，其中一个节点上的尾部延迟上升了10倍，这可能导致我们集群范围延迟的平均值增加0.99 + 0.01 * 10 = 1.09倍，而实际的尾部延迟增加幅度要大得多。</p>

<p>一些服务所有者试图通过取99%分位数的分位数（通常是90%分位数或99%分位数）来获得对集群范围尾部延迟的更好近似，但这也不起作用，而且通常，没有任何分片级分位数或其他分片尾部延迟的聚合可以重建集群级别的尾部延迟。</p>

<p>下面是人们在仪表盘上尝试使用实例级指标数据获取集群范围延迟的各种方法与某个服务上实际（采样的）集群范围延迟的对比图（对于较小的服务，分位数的分位数尝试会更不准确）。我们可以看到相关性非常弱，并且存在我们预期的问题：尾部延迟的平均值受异常分片的影响不如它“应该的”那么大，而各种常用的分位数要么受影响不足，要么受影响过度，平均而言与实际延迟的相关性也很弱。由于我们以分钟级粒度跟踪指标，下面图表中的每个点代表一分钟，其中采样的集群范围p999延迟在横轴上，仪表盘聚合指标值在纵轴上。由于我们从跟踪管道中获得的单个延迟测量值有1毫秒的粒度，这些点已水平抖动了+/- 0.3毫米，以便更好地展示分布情况（纵向没有施加此类抖动，因为我们的指标管道没有这种限制，因此数据精度更高）。</p>

<p><img src="https://danluu.com/images/latency-pitfalls/cluster-1.png" alt="分片p999平均值与实际p999的每分钟散点图，显示分片p999平均值是一个非常差的近似值" width="1259" height="778">
<img src="https://danluu.com/images/latency-pitfalls/cluster-2.png" alt="分片p999的p99与实际p999的每分钟散点图，显示分片p999的p99是一个差的近似值" width="1259" height="778">
<img src="https://danluu.com/images/latency-pitfalls/cluster-3.png" alt="分片p999的p999与实际p999的每分钟散点图，显示分片p999的p999是一个非常差的近似值" width="1259" height="778"></p>

<p>集群范围延迟与分片延迟聚合之间的相关性非常弱，以至于即使你选择了能产生正确平均行为的聚合方式，该值对于几乎所有样本（分钟）仍然相当错误。鉴于我们的基础设施，这里真正可行的唯一解决方案是扩展我们的跟踪管道以用于仪表盘和告警，或者在Finagle中添加指标直方图，并将该数据通过所有中间层传递到[仪表盘软件]，以便我们能够获得正确的集群级别聚合<sup id="fnref:M"><a href="#fn:W" rel="noopener noreferrer">3</a></sup>。</p>

<p>虽然取尾部延迟的平均值很流行，因为它简单且人们熟悉它（例如，[隐去名称的同侪公司]的可观测性主管曾表示，他们应该只关注平均值，因为每个人都只想要平均值），但取分片级尾部延迟的平均值或其他聚合方式，既不具备人们想要的属性，也不具备人们预期的属性。</p>

<h4 id="minutely-resolution">分钟级分辨率</h4>

<p>另一个独立的问题，也是我们观察基础设施状况能力上的一个缺口，是我们在收集指标时只有分钟级的粒度。Rezolus可以以秒级（在某些情况下甚至是亚秒级）的粒度收集指标，但出于本次演讲范围之外的原因，它通常只用于系统级指标（少数例外除外）。</p>

<p>我们都遇到过某些突发的、亚分钟级事件成为问题根源的事故。让我们看一个此类事故的例子。在这个事故中，一个服务出现了延迟和错误率升高。查看我们导出的标准指标并无帮助，但查看亚分钟级指标立即揭示了一个线索：</p>

<p><img src="https://danluu.com/images/latency-pitfalls/minutely-1.png" alt="采样请求的每请求延迟图，显示大尖峰后请求率严重降低" width="1259" height="778"></p>

<p>对于这个特定的缓存分片（以及许多其他未显示的），在<code>time 0</code>处延迟大幅增加，随后30秒内请求率非常低。30秒的延迟是因为<code>service-6</code>的分片被配置为：如果<code>service-6</code>客户端遇到太多失败请求，就会将它们通信的服务器标记为死亡状态30秒。这个决定是分布式的，这就是为什么到受影响的<code>cache-1</code>分片的请求率不为零；在延迟升高期间，<code>service-6</code>的一些分片没有向<code>cache-1</code>的特定分片发送请求，因此没有将<code>cache-1</code>的该分片标记为死亡，并继续发出请求。</p>

<p>请求延迟的亚分钟级视图非常清楚地揭示了导致<code>service-6</code>中延迟和错误率升高的机制。</p>

<p>需要注意的一点是，缺乏亚分钟级可见性并不是这里唯一的问题。大部分升高的延迟发生在对延迟指标不可见的地方，这使得监控<code>cache-1</code>的延迟不足以检测到问题。下面，<code>cache-1</code>单个实例的报告延迟指标是蓝点，而客户端观测到的测量（采样）延迟是黑线<sup id="fnref:O"><a href="#fn:O" rel="noopener noreferrer">4</a></sup>。报告的p99延迟是0.37毫秒，但实际的p99延迟约为580毫秒，差异超过三个数量级。</p>

<p><img src="https://danluu.com/images/latency-pitfalls/minutely-2.png" alt="报告的指标延迟与跟踪数据延迟对比图，显示指标延迟与跟踪延迟之间存在极大差异" width="1259" height="778"></p>

<h4 id="summary">总结</h4>

<p>尽管我们现有的延迟报告和告警设置运行得相当不错（网站通常能正常工作，并且与同等规模的同侪公司相比，我们的可靠性实际上相当好），但我们确实因为这种设置付出了一些显著代价。</p>

<p>一个代价是，我们经常遇到一些事故，不使用大多数人不使用的专业工具就很难看清发生了什么，这增加了值班的辛劳。另一个代价是，由于我们对集群范围延迟的估算存在较大的误差范围，我们必须预留大量的余地，并保持比实际希望达到的延迟严格得多的延迟SLO（服务级别目标），以避免用户可感知的事故。这增加了运营成本，正如我们在[一份比较每用户运营成本与提供类似类型和级别流量的公司的文档]中所看到的。</p>

<p><i>如果你喜欢这篇文章，你可能还想阅读关于<b><a href="https://danluu.com/perf-tracing/" rel="noopener noreferrer">单主机跟踪与采样分析器对比</a></b>的内容</i>。</p>

<h4 id="appendix-open-vs-closed-loop-latency-measurements">附录：开环与闭环延迟测量</h4>

<p>我们的一些合成基准测试设置，如setup-1，使用“闭环”测量，它们实际上发送一个请求，等待它返回，然后再发送另一个请求。其中一些允许一定程度的并行，即可以同时有N个请求在传输中，但这在真实性方面仍然存在类似的问题。</p>

<p>为了说明这个问题，我们假设有一个服务，在生产环境中每秒恰好收到1个请求，并且该服务的正常响应时间是1/2秒。在正常情况下，如果我们以每秒1次的频率发出请求，我们将观察到平均值、中位数和所有百分位数的请求时间都是1/2秒。作为读者的练习，请计算在基准测试运行的1分钟内，中间有一个请求耗时10秒的情况下，闭环与开环基准测试设置的平均值和90%分位数延迟（在开环情况下，基准测试设置以每秒1次的频率发出请求；在闭环情况下，以每秒1次的频率但等待上一个请求完成）。</p>

<p>更多相关信息，请参阅<a href="https://psy-lob-saw.blogspot.com/2015/03/fixing-ycsb-coordinated-omission.html" rel="noopener noreferrer">Nitsan Wakart 关于在YCSB基准测试中修复此问题的介绍</a>或<a href="https://www.youtube.com/watch?v=9MKY4KypBzg" rel="noopener noreferrer">Gil Tene 关于此问题的演讲</a>。</p>

<h4 id="appendix-use-of-unweighted-averages">附录：未加权平均值的使用</h4>

<p>我研究过的一个仪表盘平均值常见问题，与我们取尾部延迟平均值时出现的问题无关，那就是未加权平均值通常会低估实际延迟。</p>

<p>我经常看到未加权平均值的两个地方是：有人通过跨数据中心取未加权平均值来获得整体延迟，以及有人通过跨分片取平均值来获得集群范围延迟。这两者都有同样的问题：负载较低的分片往往延迟也较低。当我们从某个数据中心故障转移时，这种情况尤其明显。错误地使用跨数据中心未加权平均值的服务，通常会显示延迟降低，尽管实际服务请求的延迟反而增加了。</p>

<p><i>感谢Ben Kuhn的评论/修正/讨论。</i></p>

<p>
</p>
<div>

<hr>

<ol>
<li id="fn:W">这是另一个有点过时的项目，因为这份文档促使了Flavio Brasil和Vladimir Kostyukov在Finagle上进行工作以减少此问题的影响，后来，我当时实习生Xi Yang的工作，对内核调度器进行了补丁，通过防止cgroup超过其CPU分配（与标准机制相反，后者允许cgroup超过其分配然后有效地让cgroup休眠直到其摊销的CPU分配不再过高，这对尾部延迟非常不利），基本上消除了该问题。
 <a href="#fnref:W" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:H">这也是另一个过时的项目，因为内核、HWENG和新成立的集群健康团队已付出巨大努力来降低不健康机器的比例。
 <a href="#fnref:H" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:M">这在今天也已显著过时。Finagle现在确实支持导出分片级直方图数据，并且可以通过单次查询访问导出的指标端点来查询这些数据。
 <a href="#fnref:M" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:O">正如我们之前指出的，不透明延迟可能来自服务器或客户端，但在此案例中，我们有强有力的证据表明延迟来自<code>cache-1</code>服务器，而不是<code>service-6</code>客户端，因为来自<code>service-6</code>客户端的不透明延迟应该在所有来自<code>service-6</code>的请求上可见，但我们仅在到<code>cache-1</code>的请求上观察到升高的不透明延迟，而不是到它“交谈”的其他服务器。
 <a href="#fnref:O" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 11332 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/latency-pitfalls/</link>
      <guid isPermaLink="false">https://danluu.com/latency-pitfalls/</guid>
      <pubDate>Mon, 6 Dec 2021 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>本博客中的主要错误（及其纠正）</title>
      <description>[AI 摘要] 该文是作者对其博客中存在的主要错误进行的整理和分类，并附带了相应的纠正与反思。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 该文是作者对其博客中存在的主要错误进行的整理和分类，并附带了相应的纠正与反思。</div><p>这是我认为本博客中相当严重的一系列错误列表。虽然我认为的“严重”当然是主观的，但我认为没有任何合理的方式可以完全避免主观性，例如，我会犯大量的拼写错误，多到许多文章的致谢部分主要是感谢通过邮件或私信向我指出拼写错误的人。</p>

<p>一个包含所有错误（包括拼写错误）的列表不仅对其他读者来说阅读价值不高，对我而言制作门槛也很高，这就是我划定一条界限的原因。一个我认为不算严重的错误例子是，<a href="https://danluu.com/learning-to-program/" rel="noopener noreferrer">在这篇关于我如何学习编程的文章中</a>，我最初弄错了高中时那些竞赛程序员开始赚钱的时间（比我以为的晚了几年）。在那种情况下，以及许多其他情况下，我认为日期的错误并不会改变文章的重要内容。</p>

<p>虽然这篇博文的原始版本发表于2021年，但我预计这个列表会随着时间的推移而增长。我希望自己能变得更细心，未来的增长速度会比过去慢，但这仍有待观察。我认为列表中有很大一部分来自我2013年博客的前三个月，这是一个好迹象，但这绝不是自满的理由！</p>

<p>我在下面添加了一个我如何对这些错误进行分类的说明，但这种分类也是任意的，各个类别甚至并非互斥。如果我收集到的错误多到难以全部记在脑子里，我可能会创建一个标签系统来对它们进行分类，但我不希望积累如此多的重大错误，以至于需要一个标签系统方便读者浏览。</p>

<ul>
<li>思考不充分

<ul>
<li><i>2013年</i>：<a href="https://danluu.com/randomize-hn/" rel="noopener noreferrer">使用随机算法来降低好文章在Hacker News上“运气不佳”的概率</a>：这个想法曾被尝试过，效果不如让人工介入，由人来决定哪些文章应该从遗忘中被拯救出来。

<ul>
<li>由于这是一个提议而非断言，技术上讲这并非错误，因为我没有声称它一定会奏效，但我觉得我当时也应该考虑将人工纳入决策的解决方案。我没有那么做，是因为Digg曾因人工干预其首页而受到强烈反对，但回顾来看，我们可以看到以一种不会产生足以摧毁该网站的反对声浪的方式进行人工干预是可能的，并且我认为经过足够思考是可以预见到这一点的</li>
</ul></li>
</ul></li>
<li>天真/幼稚

<ul>
<li><i>2013年</i>：<a href="https://danluu.com/hardware-unforgiving/" rel="noopener noreferrer">创造卓越的机构知识和文化可能需要很长时间来建立</a>：当时我还没有在软件行业工作过，认为这对软件来说没那么困难，因为许多成功的软件公司都是用新的/年轻的团队建立的。但回顾来看，区别并不在于那些公司不生产糟糕（不可靠、多、运行缓慢等）的软件，而在于产品市场契合度和网络效应非常重要，以至于软件糟糕通常也无所谓</li>
<li><i>2015年</i>：<a href="https://danluu.com/dunning-kruger/" rel="noopener noreferrer">在这篇关于人们不阅读参考文献的文章中</a>，我对于类型系统的倡导者会引用不存在的强力证据感到困惑，这似乎与其他例子不同，在那些例子中，人们传播一个巧妙、反直觉的结论却从未读过它。我认为奇怪之处在于，与其他例子不同，这里没有被传播的不正确证据片段；“有证据”这一断言与任何证据（哪怕是被误解的证据）都毫无关联。回想起来，我天真地以为存在一个证据链接，而人们不会直接编造“存在支持他们信念的证据”这个想法然后传播开去。</li>
</ul></li>
<li>对信息核实不足

<ul>
<li><i>2016年</i>：<a href="https://danluu.com/sounds-easy/" rel="noopener noreferrer">构建搜索引擎并非易事</a>：虽然我认为总体观点是正确的，但我所依赖的证据之一来自一位搜索引擎从业者告诉我的数据。但当我测量实际数据时，发现我被告知的数据偏差了好几个数量级。</li>
<li><i>2022年</i>：<a href="https://danluu.com/futurist-predictions/" rel="noopener noreferrer">未来主义者的预测</a>，由@ESRogs向我指出：我误读了nostalgebraist对一份报告的总结，并不理解他所指的敏感性分析。我清楚地记得当时不确定nostalgebraist在说什么，并且最初同意了正确的解读。重新阅读后，我得出的却是错误的理解，并将其写进了我的文章。我对该解读的不确定性本应促使我直接复述他的分析，那会立刻澄清他的意思，但我没有那么做。这个错误并没有从根本上改变我自己的分析，因为我提出的更广泛的观点并不依赖于确切的数字，但允许自己在具有如此不确定性的状态下发布内容而不注明，我认为这是一个非常坏的习惯（考虑到文章本身的内容，这是一个颇具讽刺意味的错误）。导致这个错误，也导致我没有通过检查计算来发现这个错误的一个因素是，引入此错误的编辑是在我想立即发表而非推迟相当一段时间时，在一个很短的时间窗口内进行的最后时刻修改。当然，这本应导致我推迟发表，因此这是一个坏决定导致了另一个坏决定。</li>
</ul></li>
<li>低级错误/失误

<ul>
<li><i>2015年</i>：<a href="https://danluu.com/butler-lampson-1999/" rel="noopener noreferrer">回顾Butler Lampson在16年后对计算机科学中哪些东西行得通的评述</a>：在2015年说能力（capabilities）是“不行”的是错误的，考虑到它们在移动端的有效性，这在当时看来如此明显错误，我将其称为一个低级错误，而不是那种我经过相当思考但本应思考得更深入的情况。</li>
<li><i>2024年</i>：<a href="https://danluu.com/diseconomies-scale/" rel="noopener noreferrer">规模不经济</a>：我在做算术时搞混了哪个数字除以哪个，导致百分比出现多个数量级的错误。Sophia Wisdom在文章发布几小时后注意到了这个问题，我立即修复了，但这是一个相当愚蠢的错误。</li>
</ul></li>
<li>晦涩难懂的解释

<ul>
<li><i>2013年</i>：<a href="https://danluu.com/3c-conflict/" rel="noopener noreferrer">数据对齐如何影响内存延迟</a>：这篇文章的主要图表使用了延迟比率，这增加了一层间接性，让很多人感到困惑。</li>
<li><a id="p95" rel="noopener noreferrer"><i>2017年</i></a>：<a href="https://danluu.com/p95-skill/" rel="noopener noreferrer">达到95百分位的性能是很容易的</a>：人们对这篇文章最常见的反对意见类似于“错误。你必须非常有天赋，并且/或者要达到[进入NBA / 成为国际象棋特级大师 / 获得2200的国际象棋等级分]是很难的”。<a href="https://twitter.com/JamesClear/status/1292574538912456707" rel="noopener noreferrer">James Clear在推特上提出了一个更弱的主张</a>，也得到了类似的回应。在推特上确实没有空间详细说明，但在我这篇博文中，我应该包括更多具体的例子，说明不同性能水平对于那些难以估计不同百分位性能的人来说是什么样子的。举一个不那么离谱的说法为例，<a href="https://lobste.rs/s/mwykjj/95_ile_isn_t_good#c_pudige" rel="noopener noreferrer">这里有一个说法，即2200的等级分是玩过在线国际象棋的人的95百分位水平</a>，<a href="https://lobste.rs/s/mwykjj/95_ile_isn_t_good#c_iyoegc" rel="noopener noreferrer">这个说法似乎偏差了大约四个数量级，上下浮动一个</a>。</li>
</ul></li>
<li>回顾之下的错误

<ul>
<li><i>2015年</i>：<a href="https://danluu.com/blog-ads/" rel="noopener noreferrer">博客变现</a>：我通过查看当时Casey Muratori、Eric Raymond和eevee在Patreon上的收入，严重低估了<a href="https://www.patreon.com/danluu" rel="noopener noreferrer">我能在Patreon上赚到多少钱</a>。我认为基于各种原因，他们三个人都会比我赚得多，这是不正确的。一个主要原因是，枯燥、长篇的文字变现效果比我预期的要好得多，这意味着与其他技术人士的做法相比，我在经济上低估了这一点的价值。

<ul>
<li>几周前，我在文章顶部（而不是只隐藏在底部）添加了一个Patreon链接，并在推特上提到了我有Patreon。从那时起，我的收入增加了大约Eric Raymond的总收入水平，并且这个金额似乎在以不错的速度增长，这是在substack兴起之前我不会预料到的结果。但任何意识到个人写作者的文字变现能力有多好的人本可以创建substack，但直到Chris Best、Hamish McKenzie和Jairaj Sethi创建了substack才有人这么做，所以我认为这个错误在某种程度上是不易察觉的。另外，<a href="https://www.patreon.com/posts/60185075" rel="noopener noreferrer">目前尚不清楚这种变现是否会扩大规模还是达到平台期</a>；如果达到平台期，那么我的猜测只会偏离一个小的常数因子。</li>
</ul></li>
</ul></li>
</ul>

<p>感谢Anja Boskovic和Ville Sundberg的评论/纠正/讨论。</p><p><em>由 mimo-v2.5 模型翻译，花费 4909 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/corrections/</link>
      <guid isPermaLink="false">https://danluu.com/corrections/</guid>
      <pubDate>Mon, 22 Nov 2021 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>个体至关重要</title>
      <description>[AI 摘要] 文章论述了在工作规划和团队管理中将人员视为可互换的模型往往失效，因为个体差异对生产力和结果有重大影响。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章论述了在工作规划和团队管理中将人员视为可互换的模型往往失效，因为个体差异对生产力和结果有重大影响。</div><p>我在观察数据时发现，人们最常犯的错误之一是错误地使用过于简化的模型。其中一种特定变体已经使我看过的大多数工作路线图脱轨，那就是将人视为可互换的，仿佛谁做什么并不重要，仿佛个体无关紧要。</p>

<p>个体至关重要。</p>

<p>在路线图制定和评审过程中，我反复看到的一种模式是：人们会规划接下来几个季度的工作，然后为每个项目分配一定数量的人员——比如一个人负责一个季度，两个人负责三个季度等。名义上，这个过程能让团队理解其他团队的计划并做出相应安排。然而，我从未在任何一个组织中见到这种方法真正奏效，也从未见到它能让团队在依赖其他团队时有效执行。</p>

<p>我看到的情况是，当项目启动后，人们会询问谁在负责该项目，然后根据负责人是谁来猜测项目是否能按时完成、是否高效，甚至能否完成。“哦，乔在负责功能X？他从没交付过靠谱的东西。看来我们不能指望它，因为这根本行不通。我们做Y而不是Z，因为Z需要X才能正常工作。”路线图制定和评审过程表面上维持着人员可互换的客气假象，但所有人都知道这不是真的。高效且希望按时交付的团队在实际执行时无法配合，即使他们在与制定路线图的经理、总监和副总裁们表面上保持一致。</p>

<p>另一个因人员不可替代性而引发可预见问题的地方是团队管理方式。希望打造高效团队的管理者<sup id="fnref:P"><a href="#fn:P" rel="noopener noreferrer">1</a></sup>最终不得不与现有体制抗争。非工程部门大多将人员视为可互换的，而在我工作过的多家公司中，财务部门会要求工程部门以“人头”为单位做预算，从而迫使其将人员视为可互换的。公司当然花钱而不是买“人头”，但内部记账却用“人头”计算，因此某团队的X美元预算会被转化为类似“三个资深级别的人头”这样的指标。而“两个更高效、薪水更高的资深级别人头”<sup id="fnref:E"><a href="#fn:E" rel="noopener noreferrer">2</a></sup>则无法体现为这种预算分配。如果你雇佣两名资深工程师而非三名，那么那个“人头”以及相关的预算最终会被转移到别处。</p>

<p>我反复看到的一个情况是：招聘经理想要雇佣一个他认为高效的人，或仅仅是一个拥有专业技能的人，但最终无法实现，因为公司已将预算转化为特定费率的“人头”，而不允许雇佣某些类型的“人头”<a href="https://twitter.com/danluu/status/1452701799417143296" rel="noopener noreferrer">。人力资源部门的“薪酬团队”或其他团体会提出异议，因为他们没有“高效工程师”或“难以招聘的专业技能”这样的概念；对于一个人来说，角色、级别和地点决定了他们的身份，而为某角色和级别支付过高薪水因此被视为一次糟糕的招聘。如果有任何理性人士掌握流程控制权并愿意使用它，这种情况就不会发生，但根据设计，官僚体制的建立使得很少有人拥有权力<sup id="fnref:B"></sup></a><a href="#fn:B" rel="noopener noreferrer">3</a>。</p>

<p>类似的情况也发生在人才保留方面。我认识的一位优秀工程师每年为公司创造约数千万美元的额外利润，当此人想回到葡萄牙的家乡时，公司将其现金薪酬削减了四分之三。公司还提出，如果他搬到西班牙而非葡萄牙，则只削减一半现金薪酬。他最终加入了一家不实行地区差异薪资的公司。此事被上报到总监级别，但这并不足以推翻人力资源部门的决定，因此他还是离开了。人力资源部门并不关心此人通过创造价值为公司带来的收益远超过他们为所有国际员工进行地区调整所节省的成本，因为该公司的人力资源部门没有“员工价值”的概念，只有成本、职位、级别和地点<sup id="fnref:L"><a href="#fn:L" rel="noopener noreferrer">5</a></sup>。</p>

<p>相关的一个“手段”是我见过两次的例子（一次是远观，一次是亲身经历）：人力资源部门决定<a href="https://twitter.com/danluu/status/1277591470162104321" rel="noopener noreferrer">人员流失率太低了</a>。一种情况下，人力资源主管认为公司约5%的流失率“不健康”，因为太低了；另一种情况下，人力资源部门认为公司略低于10%的流失率也太低。在这两种情况下，公司都采取了一些措施，导致流失率上升到人力资源部门认为的“健康”水平。在我远观的案例中，我在该公司的熟人认同，接下来一年里公司大多数最优秀的工程师离职了，其中许多人仅工作了几个月。在我亲身经历的案例中，我列出了我所知的最高效工程师的名单（就像上面提到的那位在陪产假期间就为公司增加了0.7%营收的人），当公司成功将整体流失率推高至10%以上时，最高效的工程师流失率是这个数字的两倍多（这甚至低估了其影响，因为他们往往是任期长且资历深的工程师，正常预期的流失率应远低于公司平均值）。</p>

<p>有些人似乎将公司视为《模拟城市》游戏：如果你想要更多钱，可以转动一个旋钮，提高税收，然后均匀地影响整个城市。但公司不是《模拟城市》游戏。如果你想要更高的流失率并转动一个旋钮，你得到的并非随机均匀增加的流失。作为整体的人不能被抽象地对待，认为公司领导层的行为对每个人都产生相同的影响。如果你转动的旋钮导致流失率上升，那么最高效的人离开的可能性会不成比例地高。</p>

<p>到目前为止，我们讨论了将个体人员视为可互换对公司的危害，当然，这在普遍意义上也行不通。例如，我的一位朋友曾在非洲做过大量“实地”开发工作，他抱怨说许多希望捐赠的人想要清晰、简单的标准来指导他们的捐赠（例如，随机对照试验表明该干预措施非常有效）。但许多有效的干预措施无法以任何简单的方式事前证明其影响，原因有很多，其中之一就是实施干预措施的团队构成很重要，导致随机试验或其他实验无法适用于试验团队以外的其他团队在试验背景下的实施。</p>

<p>一个例子是他们参与的一项干预措施，除了其他事情外，帮助在一个国家根除了几内亚虫。事后来看，我们可以说这是一项非常有效的干预措施，因为这是一个三人团队，在每人每天12美元<sup id="fnref:T"><a href="#fn:T" rel="noopener noreferrer">6</a></sup>的预算下运作了相对较短的时间，使其成为一项高投资回报率的干预措施，但事前无法为该干预提供量化依据，似乎也不可能存在一组能够证明该干预合理性的随机试验或实验。</p>

<p>他们的干预并非根除几内亚虫，那只是一个副作用。该干预措施基本上是走遍全国各地，深入各地区政府办公室以了解他们的问题，然后建议/协助改进决策。在与人交谈并提出改进建议时，他们意识到通过更好的清洁水分配可以解决几内亚虫问题（饮用未过滤的水可能导致感染几内亚虫；给人们提供清洁水可以解决这个问题），而专门用于水利相关项目（如建造水井）的援助资金如果重新分配到国内因污染水源而导致几内亚虫高发的地区，而不是流向目前援助资金涌入的地方（这些地方往往因为靠近一个开展大量慈善工作的当地“办公室”等原因获得了大量援助资金），那么这些资金已经足够。这个团队为帮助根除几内亚虫所做的具体工作是向政府官员做PowerPoint演示，说明政府如何建议接受援助资金的组织更有效地安置水井。从边际效益看，仅在一个国家根除几内亚虫可能就足以使该干预成为高投资回报率项目，但这只是这个三人团队“回报”中极小的一部分。我提到它只是因为它是一个独立的、易于量化的改变。在地区政府办公室“提升”决策能力的大部分价值很难量化（即使能量化，误差范围也非常大）。</p>

<p>许多事前看似相同的干预措施，可能大多数，产生的影响微乎其微。我的朋友对许多组织有很多评论，这些组织派出大量人员做听起来类似的工作，但产生的价值很小，比如和平队。</p>

<p>我朋友的团队与大多数团队的一个主要区别在于，我朋友的团队由那些在各种环境下都有高效表现记录的人员组成。在之前的一份工作中，我的朋友在一家规模较大（年营收50亿美元）的政府公用事业公司开始工作，并立即被分配了一个问题，她当时不知道这个问题已经悬而未决多年且被认为无法解决。没人愿意碰这个问题，所以他们雇佣了她，想让她当替罪羊，在问题爆发时承担责任。相反，她解决了被分配的问题，还解决了许多其他被认为无法解决的问题。由三名这样的人组成的团队能够从许多潜在的高投资回报率干预中获益，而大多数团队不会成功，例如前往一个陌生国家，改善全国各地区政府办公室的决策能力，使政府能够解决困扰该国数十年的严重悬而未决的问题。</p>

<p>许多最高投资回报率的干预措施同样需要专业技能，不适用于简单的粗略计算，但我看到的大多数关于该主题的讨论，无论是在现实中还是在线上，都严重依赖简单但无关紧要的粗略计算。这不仅限于鸡尾酒会式的交谈。我朋友的干预措施差点被她所在的组织扼杀，因为该组织充斥着她所认为的“过于简化的<a href="https://en.wikipedia.org/wiki/Effective_altruism" rel="noopener noreferrer">有效利他主义</a>思维”，导致组织领导层试图将资源重新导向那些预期回报计算更简单的项目，因为那些项目被认为影响力更大，而事后证明其影响力反而更小。当然，我们不应根据事后表现来判断干预措施，因为这会过分有利于高方差的干预措施，但我认为，一个愿意运用判断力而不是将其外包给简单指标的人，应该且能够说相关的干预措施在事前就是好的选择。</p>

<p>这种更容易<a href="https://amzn.to/3qGiucN" rel="noopener noreferrer">理解</a>的项目获得更多资金的问题，既存在于组织之间，也存在于组织内部。例如，我的朋友说，当GiveWell主要或只推荐那些具有简单可量化回报的慈善机构时，她基本上无法让在其他领域工作的朋友将资源投入到未被GiveWell认可的努力中。那些不了解她援助背景的人，在她建议将资源投向任何特定事业、项目或组织时，会说类似“你没听说过GiveWell吗？”这样的话。</p>

<p>我当时与一位在GiveWell工作过的朋友讨论了这个问题，据他说，GiveWell最初专注于慈善机构是因为它们的价值容易量化，而不是因为他们认为这些是最高影响力的慈善机构。相反，作为一个年轻的组织，他们需要建立可信度，而为价值容易量化的慈善机构建立可信的案例更容易。他本人不会，他认为GiveWell也不会，支持捐赠者将所有资源都投入给GiveWell认可的慈善机构而忽视其他改善世界的方式。但许多人希望世界简单，并应用“在GiveWell名单上的慈善机构=好；不在名单上的=坏”的算法，因为这使他们的世界变得简单。</p>

<p>对这些人以及整个世界来说，不幸的是，世界并不简单。</p>

<p>回到科技公司的例子，劳伦斯·特拉特指出了一些我也观察到的情况：</p>
<blockquote>
<p>在大型组织中，我发现一件非常有趣的事情：当他们意识到需要做些不同的事情（即他们正在慢慢失败，想要扭转局面）时。显而易见的做法是让一个小团队基于他们可能大获全胜的可能性来承担风险。相反，他们往往成立无休止的委员会，这些委员会只是延续了导致其成立的漂移状态！我认为这是因为他们真的很难将人视为非同质化的，即使他们真的想：这几乎超出了他们摆脱组织模式的能力，即使这预示着长期失败。</p>
</blockquote>
<p>我们可以用来审视正在发生事情的一个视角是<a href="https://www.ribbonfarm.com/2010/07/26/a-big-little-idea-called-legibility/" rel="noopener noreferrer">可理解性</a>。当你有一个复杂的系统，无论是拥有数千名工程师的公司，还是有数十亿美元投入援助工作的世界，这个系统对任何决策者来说都过于复杂，无论是公司的高管还是试图理解其资金应投向何处的潜在捐赠者。解决这个问题的一种方法是通过假设个体是可互换的来降低问题的感知复杂性，使系统更易于理解。这会产生相对低效的结果，但与<a href="https://twitter.com/benskuhn/status/1458948982059593730" rel="noopener noreferrer">试图理解手头问题</a>不同，它具有高度可扩展性。如果说科技公司喜欢什么，那就是做可扩展的事情，将复杂的系统视为《模拟城市》或《文明》是高度可扩展的。当回报相对均匀分布时，为了可理解性而牺牲潜在的异常回报是一种好的权衡。但当投资回报率呈重尾分布时，当合适的人能够在陪产假期间就将一家巨型科技公司的营收增加0.7%，而全职工作时增加更多时，为了提高可理解性而严重压制分布右侧就是代价高昂的，可能会让你损失大部分潜在回报。</p>
<p>感谢劳伦斯·特拉特、帕姆·沃尔夫、本·库恩、彼得·巴特·哈金斯、约翰·赫根罗德、安德烈·米什琴科、约瑟夫·卡普特和索菲亚·威斯多姆的评论/修正/讨论。</p>
<h3 id="appendix-re-orgs">附录：重组</h3>
<p>我最近从一位朋友那里听到一个关于一家时尚科技公司的故事，他们试图将六个人调到另一个项目上，一个那些人不想做、认为没有意义的项目。结果是两名资深开发者辞职了，工程经理退休了，一名产品经理被解雇了（说来话长），还有三个人离开了团队。旧项目和新项目的团队都必须从头开始重建。</p>
<p>情况可能糟糕得多。在那个案例中，至少有一些人没有离开公司。我曾问过某人，为什么公开承诺的功能X尚未实现，并且整个子产品都坏了。答案是：在大约一年的工作后，当交付该功能被认为只差几周时，领导层决定这个之前被认为是首要任务的功能不再是优先事项，应该放弃。团队争辩说该功能离完成很近，他们只需要足够的时间来完成。当这个请求被拒绝后，整个团队都辞职了，此后该子产品逐渐衰落。多年后，团队有一次重组尝试，但由于超出本文范围的原因，由一名新经理管理应届毕业生进行，未能重建老团队的能力。</p>
<p>正如我们之前看到的，<a href="https://danluu.com/hardware-unforgiving/" rel="noopener noreferrer">一个高效团队很难组建，因为团队中存在的机构知识</a>以及<a href="https://danluu.com/culture/" rel="noopener noreferrer">团队文化</a>，但摧毁一个团队却非常容易。</p>
<p>有趣的是，如此多的高管职位的人坚持认为他们可以像在《文明》游戏中打开城市视图并将工人从一项任务重新分配到另一项任务那样轻易地调动人员，而我交谈过的资深个人贡献者在预测这类调动何时会失败方面具有很高的准确性。</p>
<h3 id="appendix-related-posts">附录：相关文章</h3>
<ul>
<li><a href="https://yosefk.com/blog/compensation-rationality-and-the-projectperson-fit.html" rel="noopener noreferrer">约西·克雷宁论薪酬与项目/人员匹配度</a></li>
<li><a href="https://danluu.com/hardware-unforgiving/" rel="noopener noreferrer">我关于获取机构知识的困难</a></li>
<li><a href="https://amzn.to/3qGiucN" rel="noopener noreferrer">詹姆斯·C·斯科特论可理解性</a></li>
</ul>
<div>
<hr>
<ol>
<li id="fn:P"><p>另一方面，也有一些管理者希望最大化其职业生涯回报。在我工作过的每一家非初创公司中，要做到这一点都需要向上晋升，而最简单的方法就是尽可能多地管理人。在我曾工作过的一家公司，明确的晋升标准基本上是“有多少人向这个人汇报”。</p>
<p>如果认为人基本是同质化的，那么将晋升和薪酬与管理人数挂钩可能说得通，否则这显然是个愚蠢的想法。</p>
<a href="#fnref:P" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:E">当考虑到留任预算（来自一个不属于该组织正常预算的池子的资金，通常用于匹配离职者获得的录用机会）等情况时，情况并非完全如此简单，但增加这种细微差别并不改变根本观点。
<a href="#fnref:E" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:B"><p>一个人们没有权力的系统有其优势，例如减轻权力滥用、各种偏见、裙带关系等。有人可能会认为通过让人们无力来减少结果方差是理想的结果，但在赢家通吃或主导的市场（许多科技市场正是如此）中，迫使每个人都达到最低共同标准的效率，是沦为平庸之辈的良方。</p>
<p>一个具体的小规模例子是，那些没有官僚的公关/公关批准流程来发布技术博客的公司具有巨大优势<a href="https://danluu.com/corp-eng-blogs/" rel="noopener noreferrer">。大多数公司拥有繁琐流程背后的理论是，公司可以避免因糟糕博客文章带来的下行风险，但糟糕的工程博客文章的例子很少，而且如果有繁琐流程本来是可以缓解的；而那些拥有良好公开写作流程的公司则获得了许多显而易见的价值。</a></p><a href="https://danluu.com/corp-eng-blogs/" rel="noopener noreferrer">
<p>一个更大规模的例子是，那些如今市值超过5000亿美元的大公司，都采取了在它们官僚主义盛行的竞争对手中不可能实现的大胆举措，这使它们得以横扫竞争对手。当然，许多其他下重注而非稳妥行事的公司比那些试图稳妥行事的公司更快地失败了，但这些公司至少有机会，而那些稳妥行事的公司则完全没有机会。</p>
</a><a href="#fnref:B" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:Q"><p>我通常对这类说法持怀疑态度。在我工作过的多家公司，如果你将声称的营收或用户增长收益加起来，并与实际营收或用户增长相比较，你会发现有些蹊跷，因为声称的总收益远大于观察到的总量。</p>
<p>纯粹出于<a href="https://danluu.com/why-benchmark/" rel="noopener noreferrer">我对测量的好奇</a>，我有时会自己分析人们声称的收益，几乎每次我的估算都远低于原始估计。当然，我通常不会在内部公布这些结果，因为那通常会结下很多仇敌却不会带来任何改变。在一个极端案例中，我发现整个组织使用的一种实验方法有问题，导致他们在A/B测试中获得了虚假的胜利。我悄悄告知了他们，他们无动于衷，这是对他们来说唯一合理的选择，因为拥有系统性显示改进而实际上不存在的实验，是组织通过让其成员获得更多晋升和更多人员编制来获取更多权力的廉价而有效的方式。而且，如果任何掌握官僚体系权力的人关心结果的准确性，这种声称收益与实际结果之间的巨大差异从一开始就不可能存在。</p>
<p>总之，尽管我普遍怀疑声称的收益，但在自己核实后，我认为此人声称的收益高度可信。他们在陪产假期间完成的一个项目（之所以在休假期间做，是因为他们的经理以及实际上整个组织甚至公司都不支持他们所做的那种工作）为公司增加了0.7%的营收，通过长期持有实验组结果稳健且实际价值不断提升，并且在领导层因尴尬而允许他们做有价值的工作后，他们能够产生如此规模的收益。</p>
<p>附言：如果你喜欢在家里玩类似的游戏，在弄清楚哪些团队和组织实现了路线图目标后，你还可以玩另一个有趣的游戏。为了加分，可以绘制团队路线图目标完成率与人员编制增长的关系图，以及团队实现上季度目标对其实现下季度目标的预测能力如何。</p>
<a href="#fnref:Q" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:L"><p>我见过不少人在疫情期间因地区薪资调整而离开雇主。在一个案例中，人力资源部门坚持认为此人实际上得到了非常好的补偿，因为尽管此人表面上薪酬似乎不高，收入明显低于许多比他们低一级的许多员工，但根据人力资源部门的公式（包含地区薪资调整），此人在整个公司按标准化薪酬计算是他们级别中薪酬最高的人之一。抛开关于公平性的抽象考虑，<a href="https://yosefk.com/blog/compensation-rationality-and-the-projectperson-fit.html" rel="noopener noreferrer">对于员工</a>来说，人力资源部门告诉他们，考虑到他们的地区，他们薪酬很高，就像人力资源部门有一个基于身高的公式，然后告诉员工他们的身高对应的薪酬很高一样。根据人力资源部门的任何公式，这可能都是真的，但实际上这对员工毫无意义，他们可以去一家身高薪资调整幅度较小的公司工作。</p>
<p>疫情前，公司能够进行大幅度的地区薪资调整而不产生任何成本。但疫情后，许多公司加大了远程招聘力度，其中一些公司的地区薪资调整幅度相对较小，这使得它们能够从那些仍然保持大幅度地区薪资调整的公司中不成比例地挖走它们想要的人。</p>
<a href="#fnref:L" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:T">严格来说，他们的最终预算比这个数字高，因为一名团队成员感染了伤寒并从个人预算而非组织预算中支付了部分医疗费用，但12美元/(人·天)的组织资金是一个很好的近似值。
<a href="#fnref:T" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 10856 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/people-matter/</link>
      <guid isPermaLink="false">https://danluu.com/people-matter/</guid>
      <pubDate>Mon, 15 Nov 2021 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>文化很重要</title>
      <description>[AI 摘要] 文章通过科技行业案例论证文化对员工行为和产出的影响，远超激励机制和流程，强调择业时应审慎选择文化环境。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章通过科技行业案例论证文化对员工行为和产出的影响，远超激励机制和流程，强调择业时应审慎选择文化环境。</div><p>公司影响员工行为的三大工具是激励机制、流程和文化。人们谈论这些概念时往往指代不同含义，因此我将分别举例说明，以确保理解一致（若你认为某个概念应使用其他词汇，可自行在心中替换）。</p>

<ul>
<li><p>让员工准时参加会议</p>
<ul>
<li>激励机制：迟到者扣工资</li>
<li>流程：迟到者不得进入会场</li>
<li>文化：员工高度重视准时到场</li>
</ul></li>

<li><p>推动员工构建复杂系统</p>
<ul>
<li>激励机制：将系统复杂度作为晋升考核标准</li>
<li>流程：制定繁琐的工作项创建/执行流程，迫使员工放弃简单工作</li>
<li>文化：员工乐于构建复杂系统，且/或因此获得同行尊重与地位提升</li>
</ul></li>

<li><p>避免生产缺陷</p>
<ul>
<li>激励机制：按合格品计酬，次品扣工资</li>
<li>流程：出货前设置质检环节，剔除次品</li>
<li>文化：员工崇尚卓越品质，竭力避免缺陷</li>
</ul></li>
</ul>

<p>若阅读"老派"思想领袖的著作，许多人倡导纯文化方案。例如<a href="https://mobile.twitter.com/danluu/status/885214004649615360" rel="noopener noreferrer">肯·汤普森主张降低缺陷率的关键不在工具（本文将流程称为工具），而在于员工有意识地主动避免编写缺陷</a>；或如鲍勃·马丁所言"<a href="https://www.hillelwayne.com/post/uncle-bob/" rel="noopener noreferrer">解决软件危机的方法不是更多工具，而是更严谨的编程纪律</a>"。</p>

<p>这类夸张言论引发的情感反应，与其易于反驳的特点，共同导致了对文化解决方案的反弹。人们开始声称"绝不应强调纪律，而应审视底层系统的激励机制"——正如十倍效率程序员现象及其相关评论引发反弹，导致人们宣称<a href="https://danluu.com/productivity-velocity/" rel="noopener noreferrer">开发速度完全无关紧要</a>，或程序员间效率毫无差异（<a href="https://scattered-thoughts.net/writing/moving-faster/" rel="noopener noreferrer">正如杰米·布兰登所指出，开发速度很大程度上取决于对此的重视与投入</a>，这同样是文化反弹的表现）。</p>

<p>但若观察量化产出，即便流程和激励机制是公司首选的一线工具，文化仍会产生重大影响。以制造缺陷率为例：某些国家长期保持低于他国的缺陷率（时间跨度达数十年）<sup id="fnref:C"><a href="#fn:C" rel="noopener noreferrer">1</a></sup>，且这种优势通常普遍存在于企业间——即使公司在多国运营工厂，并尽可能导入相同流程与激励机制，文化差异仍会深刻影响工作方式。</p>

<p>回到编程领域，杰米在"提升速度"的笔记中写道：</p>
<blockquote>
<p>最关键的改变源于真正渴望提升速度。</p>
<p>早期我确实更关注编写"优雅"代码或使用流行工具，而非解决实际问题。这或许并非明确信念，但行动已清晰反映优先级。</p>
<p>我可能未曾意识到速度提升的巨大空间。职业生涯初期，我与同样缓慢且缺乏经验的同事共事。</p>
<p>逐渐地，我注意到有些人能产出我终生难以企及的项目。我渴望探寻实现之道，这意味着必须放弃既有观念，探索真正有效的方法。</p>
</blockquote>

<p>我很幸运在职业生涯初期拥有相反体验：我的第一份全职工作在Centaur公司（当时该公司拥有极高的开发速度/生产力）。我曾在其他团队体验过类似效率，即我目前的团队——但这在科技公司中相当特殊（例如我们团队的中位数职级是"高级专家"）<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">2</a></sup>。在高效公司开启职业生涯的副作用是：我通常认为大公司开发节奏缓慢，且不接受"行业常态"作为缓慢的理由。我与大公司同行交流时经常听到类似评论，这些人曾在功能正常但速度并非顶尖的初创公司工作过。某家顶级科技公司的定期调研会问："你感觉当前开发速度比上一份工作更快还是更慢？"回答呈现双峰分布——取决于受访者来自小公司还是大公司（该公司开发速度慢于初创企业，快于大企业）。</p>

<p>有个故事（<a href="https://amzn.to/3EZXykS" rel="noopener noreferrer">据我回忆由布莱恩·恩诺斯讲述</a>）：他进行限时训练时，原本目标是在习惯时间内完成特定任务。但他迟迟无法达到习惯时间，因比平时缓慢而感到沮丧，于是持续练习直至达成目标——此时才发现自己误记了目标，实际在挑战自以为不可能的新纪录。虽然"心想事成"过于简化，但<a href="https://twitter.com/nickcammarata/status/1362261305357393920" rel="noopener noreferrer">我们几乎无人发挥出接近自身极限的能力，而自我设限往往是主要瓶颈</a>。诚然，开发速度与质量存在权衡，速度无法"免费获得"。但就编程而言，<a href="https://danluu.com/p95-skill/" rel="noopener noreferrer">我们远未达到帕累托前沿，只要</a><a href="https://twitter.com/danluu/status/1442945072144678914" rel="noopener noreferrer">意识到优化空间存在</a>，就能实现零成本提升。</p>

<p>文化对此的影响方式之一：人们往往从所处环境中吸收对可能性的认知。非速度案例：我注意到参与<a href="https://www.recurse.com/scout/click?t=b504af89e87b77920c9b60b2a1f6d5e8" rel="noopener noreferrer">RC（Recurse Center）</a>后，许多在Deconstruct和Strange Loop等知名非学术/非企业技术会议演讲者都曾就读RC。多数人在参加RC前未做过演讲，当我询问时，许多人表示渴望演讲却不知成为"大型"会议演讲者的过程如此简单（形成想法→记录想法→将记录提交为提案）。实际上在会议演讲很简单，主要障碍在于认知可行性。在RC这样的环境中（大量人演讲，犹豫者会被告知流程简洁），许多人最终会走上讲台。博客写作同理，因此高阅读量的编程博主中，有超常比例是在RC学习后开始严肃写作的。对许多人而言，开始博客的障碍在于认知可行性，并意识到从技术层面讲，只要选择合理工具链并完成设置，创建博客极其简单。正因人们频繁演讲和写作，他们在这两方面持续进步，因此RC学员平均而言很可能比普通程序员更擅长演讲与写作，尽管RC几乎不进行技能转移或教学。</p>

<p>文化能显著驱动的另一类技能是高度依赖态度的技能，例如调试。正如茱莉亚·埃文斯指出，<a href="https://twitter.com/b0rk/status/1249715842708844544" rel="noopener noreferrer">良好态度是调试效能的关键要素</a>。Centaur在这方面非常擅长培养员工，以至于该公司技术部门几乎所有人都算得上优秀的调试者。</p>

<p>大公司常见现象：员工在数次无效尝试后就放弃调试。某中型科技公司的熟人曾告诉我，调试耗时超过数小时的缺陷毫无意义，因为工程师时间太宝贵——这种态度源于其首个团队的工作环境。形成此类调试态度者，在转变观念前很难成为优秀调试者。许多人（包括这位熟人）会长期保留第一份工作形成的态度和习惯<sup id="fnref:N"><a href="#fn:N" rel="noopener noreferrer">3</a></sup>。</p>

<p>以科技行业标准衡量，Centaur是反向极端案例。设计CPU时，不允许放弃未理解的缺陷。即使缺陷症状不严重，底层原因可能危及重大隐患（芯片出货后才会显现），因此必须追查看似微小的问题。此外，缺陷常无可靠复现步骤，典型描述如"在特定系统运行这些程序，数小时或数日后系统将挂起/数据损坏"。调试此类缺陷会遭遇无数歧途和死胡同，某些可能耗费数周甚至数月。作为新员工观察这类工作，我看到同事们日复一日追踪缺陷，既不沮丧也不放弃。当这种文化氛围环绕，你自然会形成相同态度。许多实用调试技能源于大量问题的实践积累，这自然产生于持续积极调试（尤其接触高难度问题时）。</p>

<p>当然，大公司的多数缺陷不值得投入数月时间，但存在本质区别：某些缺陷确实不值得修复而主动搁置，与因从未调试严重缺陷导致技能低下，进而无法处理非平凡问题。</p>

<p>文化态度能驱动远超个体技能的影响。Centaur人均严重生产缺陷率远低于我任职过的所有公司（约100名工程师，年缺陷率远低于1次）。相比之下，我从未在规模仅其十分之一的团队工作过，其严重生产问题发生率却高出十倍以上。如同多数初创公司，Centaur流程极简，激励机制也比大公司宽松得多。</p>

<p>其构成要素之一是"问题归属文化"：无论身处何团队，发现任何问题都会主动修复；若问题有明确负责人，则会通知对方解决。无需路线图、站会、看板或其他机制推动员工处理关键问题，员工自发行动无需提醒或催促。</p>

<p>这与我在三家大公司中两家的见闻截然相反：中位数员工如同躲避瘟疫般规避团队职责外问题；政治敏感度低的员工向其他团队提出问题，常得到"抱歉，本季度路线图未涵盖，或许下下季度可纳入"的回应——数月后天真者再提同样问题，仍会得到相同答复。在我任职过的所有大公司中，因无人愿接手，价值巨大的严重问题常被遗漏。这种情况在Centaur从未发生。</p>

<p>大公司技术文化的副作用是：愿意做正确之事者，只需解决任何实习生都能处理的问题（若愿无视组织流程和激励机制），就能产生极高（正面）影响。<a href="https://twitter.com/danluu/status/802971209176477696" rel="noopener noreferrer">随手都能发现价值超过我终生预期收入的问题</a>，且每年轻松解决多个此类问题。<a href="https://danluu.com/algorithms-interviews/#appendix-misaligned-incentive-hedgehog-defense-part-3" rel="noopener noreferrer">当然，导致大量琐碎问题被忽视的同一股力量，也使解决者得不到应有回报</a><sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">4</a></sup>。</p>

<p>相反，在Centaur的八年里，我仅发现一个琐碎问题的修复价值超过我的终生预期收入，因为问题通常在此之前已得到解决。我见过多家大公司尝试用激励机制（如奖励解决重要问题）和流程（如<a href="https://twitter.com/altluu/status/1497980098107953152" rel="noopener noreferrer">创建约1000个项目的巨型清单，由专人排序，并建立官僚体系：所有人需通过JIRA持续汇报进度，以便项目经理向统筹数千工程师工作的决策者发送进展更新</a><sup id="fnref:R"><a href="#fn:R" rel="noopener noreferrer">5</a></sup>）解决此问题，但效果均不及"主人翁文化"的一半（公平地说，有消息称Facebook运用激励机制效果显著，但<a href="https://twitter.com/danluu/status/1447268693075841024" rel="noopener noreferrer">我三次未通过其面试</a>，因此未能亲身观察）。</p>

<p>导致相对低严重缺陷率的另一要素是：Centaur全公司普遍重视品质，这种程度在大公司连团队层面都罕见。当一群重视品质且认为"任何问题都该解决"的人协作，品质自然产生。员工入职时，只要文化不被新人潮压垮，他们也会逐渐吸收相同习惯和价值观，尤其应届毕业生。尽管不普遍，但确实存在众多保持卓越文化的小型公司，无需重型流程或高额激励即可维系——这在大公司行不通，因为它们都经历过快速增长期，无法维持这种（以主流标准衡量）极端的文化价值观。</p>

<p>迄今为止，我们主要讨论公司如何向员工传递文化，但我认为同等重要的是员工离职后如何传承这种文化。我<a href="https://twitter.com/danluu/status/1444034823329177602" rel="noopener noreferrer">从硬件转向软件领域后相当成功</a>，我认为在可控因素中，最关键的是从首个全职工作中习得高效文化价值观，并持续以相同方式实践。我见过其他在职业上"成长"于卓越文化的人，在转向技能转移度更低的领域后（如从滑雪转向土木工程）仍保持高效。类似地，阅读高效领域从业者的著作（如<a href="https://amzn.to/3EZXykS" rel="noopener noreferrer">布莱恩·恩诺斯的《实用射击》</a>、<a href="https://www.sirlin.net/ptw" rel="noopener noreferrer">丹·瑟林的《制胜之道》</a>等），书中往往包含相同核心理念（严肃观察与技能提升、避免情绪自我破坏的重要性、直觉的重要性等）。</p>

<p>总之，我认为文化和技能的代际传承是择业时被低估的因素（我认为被高估的因素包括<a href="https://www.patreon.com/posts/25835707" rel="noopener noreferrer">声望</a>和<a href="https://twitter.com/danluu/status/1275191896097189888" rel="noopener noreferrer">总体声誉</a>）。人们应审慎选择所处文化环境，因为<a href="https://danluu.com/wat/" rel="noopener noreferrer">很少有人能完全避免吸收周边文化价值</a><sup id="fnref:L"><a href="#fn:L" rel="noopener noreferrer">6</a></sup>。</p>

<p>尽管本文聚焦技术领域，但此现象并非技术行业独有。典型例证是理想主义学生进入法学院时意图从事"拯救世界"类型工作，随后吸收周围学生<a href="https://www.patreon.com/posts/25835707" rel="noopener noreferrer">声望传递的文化价值观</a>，转而追求最光鲜职位——若非法官助理，通常是"顶级律所"工作，与"拯救世界"背道而驰。理论上每个人都认为"这不会发生在我身上"，但观察许多人加入<a href="https://danluu.com/wat/" rel="noopener noreferrer">最初极不认同其价值观与文化</a>的组织后，几乎无人能保持原状而不吸收周边价值观；<a href="https://danluu.com/look-stupid/" rel="noopener noreferrer">极少有人能坦然面对他人因自身价值观而投来的异样目光</a>。</p>

<h3 id="appendix-bay-area-culture">附录：湾区文化</h3>
<p>我欣赏湾区的一点是其改变世界氛围的感染力。在我生活过的地方，人们都会抱怨问题（抵押贷款行业糟糕，卖房流程繁琐等）。在湾区外，抱怨仅止于抱怨；但在湾区，与一年前抱怨某事的人交谈，很可能发现他们已创业尝试解决所抱怨的问题。我不认为湾区居民与他处之人有本质差异，更关键的是当你周围都是愿意辞职颠覆传统行业的人时，效仿似乎相当合理（这还产生网络效应，从"技术"层面（如融资）降低难度）。<a href="https://slatestarcodex.com/2017/05/11/silicon-valley-a-reality-check/" rel="noopener noreferrer">这类抱怨和修复尝试蕴含的真诚易遭嘲讽</a>，但<a href="https://danluu.com/look-stupid/" rel="noopener noreferrer">我由衷敬佩这份真诚</a>。</p>

<p>当然，<a href="https://twitter.com/pushcx/status/1442860058660913166" rel="noopener noreferrer">湾区文化并非全然积极</a>。除其他特点外，湾区有<a href="https://devonzuegel.com/post/why-is-flaking-so-widespread-in-san-francisco" rel="noopener noreferrer">著名的"放鸽子"文化</a>，程度令我初至时震惊。抵达不久后，我约老友晚餐并短信告知将迟到约15分钟。出现时他们大为惊讶，因他们认为"告知迟到"实际意味着"不会到场"（更令我惊讶的另一个极端规范是：对许多人而言，活动开始前未确认计划即视为取消，即计划默认取消）。</p>

<p>我听闻有人抱怨的另一相关规范是：管理层和领导者为避免冲突，在"讨好型"决策中对所有事都答"是"。这实际增加冲突，因为那些将"是"理解为"同意"而非"我说是为避免说不但实际并非如此"的人，事后发现"是"实为"否"时会大失所望。</p>

<h3 id="appendix-centaur-s-hiring-process">附录：Centaur的招聘流程</h3>
<p>当我谈及Centaur时，人们常有的评论是：该公司必然拥有极其严格的招聘流程才能招募精英工程师。但其招聘选择性远低于我工作过的任何"知名品牌"大公司（谷歌、微软和推特），且未必比我任职过的老派传统公司（IBM和美光）更严格。"独门秘诀"在于入职培训而非招聘。</p>

<p>应届生招聘方面（我们按比例招聘大量应届生），招聘难度高于我任职过的任何公司。资深岗位招聘并不困难（Centaur在奥斯汀本地声誉良好），但应届生中无人听闻我们且无人愿加入。我在招聘会招人时，需站在展位前拦截过路者才能获得交流机会。这意味着我们无法挑剔面试者。在杰夫·阿特伍德通过其极具影响力的文章<a href="https://blog.codinghorror.com/why-cant-programmers-program/" rel="noopener noreferrer">《程序员为何不会编程？》</a>普及"存在大量伪造程序员"观念（认为不通过基础编码题筛选就会招到不会编程的程序员）前后，我们开始大规模招聘应届生（下文加粗部分为其原文）：</p>

<blockquote>
<p><strong>任何所谓程序员在申请工作时无法编写最简单程序，这令我震惊</strong>。这是对以编程为业者的公然侮辱。
...
如此严苛的预筛选竟只为<strong>获得面试真正能编程的程序员的奢侈</strong>，可悲又可叹</p>
</blockquote>

<p>由于我们是偏重编码的硬件团队（验证工程师主要编写软件，设计工程师编写大量工具），我们尝试让应聘者根据斐波那契数列描述编写函数（允许朴素解法，不要求线性时间或更快算法）。但因无人能独立完成，需全程逐步引导，此问题对我们毫无区分度，故而废除。</p>

<p>尽管未真正考察编码能力，员工仍能编写复杂并发代码（内部处理器微码，常使用屏障作为并发控制机制），并以更快速度和更低缺陷率创建工具——速度与质量皆优于我任何其他工作经历。</p>

<p>放弃常规招聘方式对我们更有利，因为我们得以尝试雇佣其他公司不争抢的人才。若其他公司采用高效方法（如考察FizzBuzz编码和白板算法），这将毫无意义。虽然<a href="https://danluu.com/algorithms-interviews/" rel="noopener noreferrer">可能预期算法面试能筛选出能解决面试特定问题的人，但事实并非如此</a>。我们还<a href="https://twitter.com/danluu/status/1425514112642080773" rel="noopener noreferrer">大幅降低声望门槛</a>，从而招募其他公司甚至不会考虑的优秀工程师。</p>

<p>确实有少数人不适应，但从未出现"无法编写最简单程序的所谓程序员"。我确知两起雇佣无法编程的"伪造程序员"案例，均发生在拥有科技公司最严格编程面试的知名企业。一例中，此人能力不足的情况很快暴露，团队回查面试监控录像才发现代面试者并非入职者。另一例中，此人瞒过谷歌数年才被发现从未实际编写代码，任务完成均依赖他人。最终发现问题者安排结对编程环节，发现此人无法编写循环语句，甚至分不清<code>=</code>和<code>==</code>的差异——尽管他在谷歌"高级软件工程师"（L5/T5）职级任职多年。</p>

<p>我不否认编程题可能避免雇佣伪造程序员，但伪造程序员出现率极低，小公司十年不编程题也可避免；而大型诈骗目标公司即便编程题筛选仍难幸免。</p>

<h3 id="appendix-importing-culture">附录：文化移植</h3>
<p>尽管本文探讨公司文化对员工的影响，员工自然也会影响公司文化。招聘中（尤其高级领导者和高级独立贡献者）易被低估的是他们将带来的文化冲击。我多次近距离和远距离观察到：新聘高级人员成功移植与现有企业文化不兼容的自身文化，导致严重问题及高离职率——这通常发生在整合期后。</p>

<p>因资历渐深，我参与过多次高级职位招聘讨论，从未见他人提出候选人是否移植不兼容文化价值观的问题——除非明显问题（如候选人性情暴躁或发表种族/性别歧视言论）。</p>

<p>感谢彼得·巴特·哈金斯、劳伦斯·特拉特、朱利安·斯夸尔斯、安雅·博斯科维奇、陶·L、贾斯汀·布兰克、本·库恩、V·巴肯汉姆、马克·帕帕达基斯和杰米·布兰登的评论/修正/讨论。</p>
<div>
<hr>
<ol>
<li id="fn:C">哪些国家实际拥有低缺陷率制造业，通常与公众认知大相径庭。需查阅数据方能知晓，而这些数据常受保密协议限制，仅通过"酒吧闲谈"传播。
 <a href="#fnref:C" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:S">Centaur拥有我有时称之为"全球最愚蠢商业模式"的定位：自1995年起与英特尔竞争x86芯片，因此需要极高生产力才能生存。在艰难岁月中，AMD通过出售业务部门资助x86研发得以存活，其他竞争对手（Rise、Cyrix、TI、IBM、UMC、NEC和Transmeta）全军覆没。若将Centaur与存续最久的倒闭竞争对手Transmeta相比，Centaur出货速度更快——这是Centaur能存续至2021年（被英特尔伪收购）而Transmeta于2009年烧完约10亿美元融资（含诉讼赔偿）后倒闭的主要原因。Transmeta创立于1995年，2000年推出首款芯片（当时属新建CPU/微架构的常规周期）；Centaur于1997年出货首款芯片，并保持高速出货节奏至2010年左右（从高速放缓至停滞被收购的过程是另一话题）。
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:N">此人最初认为首任团队的流程和价值观荒谬，后因<a href="https://danluu.com/wat/" rel="noopener noreferrer">认知失调而成为企业文化的坚定拥护者——这在加入明显存在不良实践的公司时很常见</a>。
 <a href="#fnref:N" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:B">此例说明了激励机制与文化的非独立性。在某些此类工作未获奖励的环境中，我观察到：因文化因素，产生此类高影响力变动常需熬夜苦干，最终无任何奖励，导致倾向于主动担责并从事高影响力工作的员工流失率更高。在这种环境中，系统性地保留了不愿担责的人，使得新入职试图解决严重问题的新人更艰难且更易倦怠。
 <a href="#fnref:B" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:R">我补充此注释是因为向人描述时，许多人认为这定是讽刺。但并非讽刺。
 <a href="#fnref:R" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:L"><p>与许多其他特质一样，公司内部与公司之间都可能存在巨大差异。例如我曾在某公司遇到一个团队，其客户服务理念与公司其他部门截然不同。新加入该团队且未迅速离开者，通常会吸收其价值观。</p>
<p>公司多数部门对内部客户态度友善，但该团队秉持"客户永远是错的"态度。有趣的是：当我与该团队协作时，若由未吸收该团队文化的新晋工程师值班，我获得最佳支持；有时资深工程师断言某问题不可能或不可行，后续却由新晋工程师轻松解决。</p>
 <a href="#fnref:L" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 13329 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/culture/</link>
      <guid isPermaLink="false">https://danluu.com/culture/</guid>
      <pubDate>Mon, 8 Nov 2021 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>甘愿显得愚蠢</title>
      <description>&lt;p&gt;人们经常&lt;sup class=&quot;footnote-ref&quot; id=&quot;fnref:F&quot;&gt;&lt;a rel=&quot;footnote&quot; href=&quot;#fn:F&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;认为我非常愚蠢。我对此并不感到惊讶，因为我不介意别人认为我愚蠢，这意味着我不会调整自己的行为来避免显得愚蠢，结果就是人们认为我很愚蠢。虽然别人认为我愚蠢有一些坏处，比如面试失败（面试官显然认为我很蠢），但我认为总体而言，甘愿</description>
      <content:encoded><![CDATA[<p>人们经常<sup id="fnref:F"><a href="#fn:F" rel="noopener noreferrer">1</a></sup>认为我非常愚蠢。我对此并不感到惊讶，因为我不介意别人认为我愚蠢，这意味着我不会调整自己的行为来避免显得愚蠢，结果就是人们认为我很愚蠢。虽然别人认为我愚蠢有一些坏处，比如面试失败（面试官显然认为我很蠢），但我认为总体而言，甘愿显得愚蠢的好处远远大于坏处。</p>

<p>我不知为何这个例子一直留在我的脑海里，但对我来说，别人认为我愚蠢最令人难忘的例子来自大学时期。我有过很多次更多人认为我愚蠢、也有人认为我的愚蠢程度更深的情况，但这个例子对我真的很难忘。</p>

<p>大学时，有一群人，不管什么原因，在我看来是真正不理解课程内容的人。他们说话时表达的内容毫无意义，在课堂上挣扎，勉强及格。我不记得有过直接互动，但有一天，一个同样认识他们的朋友对我说："你知道[那群人]认为你真的很笨吗？"我觉得很有趣，就问为什么。原来原因是我问的问题听起来非常愚蠢。</p>

<p>特别是，经常有这样的情况：存在一个看似明显但实际上是错误的理由来解释某事为何成立，一个稍微不那么明显的理由解释某事看似不成立，然后是一个微妙而复杂的理由解释某事实际上为何成立<sup id="fnref:T"><a href="#fn:T" rel="noopener noreferrer">2</a></sup>。我经常会发现那个看似明显的理由是错的，然后提出一个问题试图理解那个更微妙的理由，这对那些认为明显理由正确、或者认为推翻了那个明显但错误的理由就意味着此事不成立的人来说，听起来很愚蠢。</p>

<p>在大多数特定情况下，问一个听起来愚蠢的问题所带来的好处很小，但随着时间的推移，复利效应带来的好处非常大，而且我观察到，那些愿意问愚蠢问题、思考"愚蠢想法"的人最终对事物的理解会深刻得多。相反，当我观察那些对某个主题有非常深刻理解的人时，他们中的许多人经常问一些天真得听起来的问题，并继续运用那些让他们获得深刻理解的技巧之一。</p>

<p>我想我最初通过高中时玩竞技电子游戏，确信了我认为是这一潜在现象表征的事情。那时在线玩电子游戏的人很少，你基本上能认出所有玩同一款游戏的人，并能看到每个人进步了多少。就像<a href="https://danluu.com/p95-skill/" rel="noopener noreferrer">我几年前尝试重玩电子游戏时看到的</a>一样，大多数人在游戏中"死亡"时会责怪外部因素（延迟、运气、故障、队友、不公平等）。最引人注目的是，那样做的人几乎永远无法变得厉害，也永远无法变得伟大。我玩那款游戏玩得相当不错<sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">3</a></sup>，我的"独特技巧"是每次出错时都思考哪里出了问题，然后尝试改进。但大多数人似乎对找个借口避免当下显得愚蠢（或者可能感觉愚蠢）更感兴趣，而不是真正去改进，这当然导致他们在游戏中显得愚蠢的时刻更多。</p>

<p>总的来说，我发现甘愿显得愚蠢非常有效。这里还有一些例子：</p>

<ul>
<li>走进苹果商店，要求（并购买）装在最小盒子里的电脑，我当时有充分的理由想要它</li>
<ul>
<li>帮助我的店员虽然非常有礼貌，但也明显认为我是个傻瓜，并不断解释诸如"盒子的大小和电脑的大小不一样"之类的事情。我当然知道这一点，但我不想说"我设计CPU。我理解电脑包装盒大小和电脑大小之间的区别，我也知道关心盒子大小非常不寻常，但我真的想要那个装在最小盒子里的"之类的话。仅仅说最后一部分，在没有建立任何权威性的情况下，并不能说服那个人</li>
<li>我最终请他们将就我一下，把不同型号笔记本电脑的盒子都拿出来让我看看，他们照做了，尽管显然认为我的决策过程毫无道理（<a href="https://twitter.com/altluu/status/1452704171447123969" rel="noopener noreferrer">我也尝试解释为什么我想要最小的盒子，但那也没用</a>）</li>
</ul>
<li>新冠疫情：我很早就认真对待，并在2020年1月26日购买了半面罩呼吸器，在此之前一周已经在使用手头已有的N95口罩（在我看来，基于现有的SARS接触者追踪、空气过滤器过滤病毒以及病毒载量的文献，认为新冠是空气传播且空气过滤会有帮助的证据非常充分）</li>
<ul>
<li>直到几个月后，人们才普遍不把我当作傻瓜看待，甚至在2020年8月，我偶尔还是会遇到一些人当面嘲笑我</li>
<li>另一方面，当时和我同住的人不想戴我给她弄的口罩，因为她觉得在无人乘坐BART（旧金山湾区捷运系统）或无人在创客空间时，为了往返创客空间的1小时BART行程而戴口罩太尴尬了。她成为旧金山湾区早期的新冠病例之一，这让她患上了长期新冠，卧床数月</li>
<ul>
<li>当她感染新冠后，我试图说服她，她应该告诉她常去的创客空间的人她感染了新冠，这样他们就知道自己暴露了，可以采取适当的预防措施以避免意外传播新冠，但她也觉得承认自己可能把新冠传播给别人太尴尬了（回想起来，我应该直接打电话给创客空间告诉他们）</li>
</ul>
<li>一个半相关的事情是，当加拿大开始接种疫苗时，我想打Moderna（莫德纳疫苗），尽管网上和我的社交圈里的普遍共识是Pfizer（辉瑞疫苗）更受欢迎</li>
<ul>
<li>其中一个原因是，尚不清楚政府是否会允许混合接种疫苗，而且交付计划表明，第二剂Pfizer将非常短缺，而Moderna则供应充足</li>
<li>另一个闪过我脑海的想法是，Moderna基本上比Pfizer"成分更多"，在某些情况下可能提供更好的免疫力，就像某些人群接种高剂量流感疫苗以获得更好免疫力一样</li>
</ul>
</ul>
<li>工作：我通常不担心提案或行动显得愚蠢</li>
<ul>
<li>我依然记得我第一次明确遇到这种情况。那是在我职业生涯早期，当时我从事芯片验证工作。在流片前不久，验证主管希望用我们的计算资源重新运行一组几乎不可能发现任何错误的测试（这些测试之前已经运行了数千次），而不是运行通常的测试组合（其中包括很多新生成的测试，这些测试发现错误的几率要高得多）。我争辩说我们应该运行那些能降低发布带有致命错误的产品的风险的测试（这会让我们损失数百万美元并延迟发布三个月），但小组负责人说，如果有错误能通过我们上次运行过的那些旧"金标准"测试捕捉到却漏掉了，我们会显得愚蠢和无能</li>
<ul>
<li>当时我很震惊有人会为了减少可能显得愚蠢的几率（而且，实际上只是在那些不理解运行最佳可用测试组合逻辑的人面前显得愚蠢；因为管理层中没有任何非技术人员，任何称职的人都应该理解这个推理）而故意做错事，但现在我在多个行业的各种公司工作过，我看到大多数人会选择做错事，以避免在不称职的人面前显得可能愚蠢。我理解其中的逻辑，但我认为这样行事是自我破坏，而且我职业生涯中因坚持我认为正确的事情而获得的收益如此巨大，即使未来十次我这么做都不走运且没有成功，也无法抹去过去多次做正确事情所获得的收益</li>
</ul>
</ul>
<li>空气净化：我研究了一点空气质量对健康的影响，并于2012年为我的公寓购买了空气净化器</li>
<ul>
<li>多年来朋友们一直因此调侃我，陌生人、约会对象和熟人有时会以不同程度的直白告诉我，我是在偏执和愚蠢</li>
<li>当我搬到野火风险较高的地区<a href="https://mobile.twitter.com/altluu/status/1409762306452459520" rel="noopener noreferrer">并研究了野火风险后</a>，我增加了更多的空气净化能力，这导致人们以更高的频率和更直白的语气告诉我，我有空气净化器很奇怪</li>
<ul>
<li>尽管经历了相当严重的野火季节两次，但我基本上完全没受野火影响</li>
<li>我认识的其他人则经历了一定程度的不适，有几个人在烟雾暴露后出现了持续性问题（其中一例是持续性哮喘，之前没有或至少之前没注意到）</li>
</ul>
</ul>
<li>学习对我来说困难的事：这是一个"感觉愚蠢"而非"显得愚蠢"的事情，但当我在某件事上挣扎时，我感觉自己真的很笨，就是说，我有一种感觉/情绪，我会用语言描述为"感觉很笨"</li>
<ul>
<li>当我比较年轻的时候，我想是在青少年之前，我注意到当我学习困难的东西时会发生这种情况，并试图将这种感觉视为"学习某事的感觉"而不是"感觉很笨"，这起到了一半的作用（我现在将这种感觉与两者都联系起来）</li>
</ul>
<li>提问：上面已经提到，但我经常在遇到不理解或不知道的事情时提问，从基础的东西，"某个词是什么意思？"到更微妙的东西。</li>
<ul>
<li>另一方面，我看到初级工程师最常见的失败模式之一，就是有人因为害怕显得愚蠢而不敢提问，结果学习非常缓慢；在某些情况下，这严重到导致他们被置于绩效改进计划（PIP）然后被解雇</li>
<ul>
<li>我确信还有其他原因会导致这种情况，比如不想打扰别人，但在那些我足够了解情况可以去问的案例中，总是因为尴尬和害怕显得愚蠢</li>
<li>我在指导实习生和初级人员时，会小心避免这种失败模式，并且通常很成功，但有时需要长达六周的时间才能说服他们提问是可以的，在那之前，我必须经常问他们进展如何，以确保他们没有卡住。如果那个人是我的实习生，这没问题，但我观察到许多指导实习生和新员工的导师并不这样做，这常常导致各方结果不佳</li>
<ul>
<li>几乎每个案例中，那人都至少在其他公司实习过，但他们没有学会提问是可以的。附注：如果你在一个不允许提问的地方工作，并且情况允许，你应该另找工作</li>
</ul>
</ul>
</ul>
<li>不为失败找借口：在电子游戏部分已经提到，但适用范围更广</li>
<li>学习时，刻意在成功与失败之间的区域摸索（这也适用于电子游戏、体育运动以及抽象的智力追求）</li>
<ul>
<li>一个例子是，在学习攀岩时，反复以各种方式尝试同一个简单的动作，以理解什么方式效果更好，什么方式效果更差。我曾遇到陌生人嘲笑我，甚至指着我对他们的朋友说风凉话</li>
<li>学习开车时，我想设置一些锥桶，然后开车勉强撞上它们，以了解汽车的边缘在哪里。我父亲认为这个想法非常愚蠢，我应该只是不撞到东西，比如路缘石或锥桶</li>
</ul>
<li>汽车保险：我上次购买汽车保险时，不得不确认三次我只想要为对他人造成的损害投保，不覆盖如果我方责任造成的对我自己车辆的损害。保险代理人每次都忍不住用看傻瓜的眼神看我，每次确认时都变得更加难以置信</li>
<li>本网站的样式和内容：我经常收到设计人员和字体排印师告诉我设计有多愚蠢，通常在我与他们互动时，很快变得居高临下</li>
<ul>
<li>但是，当我测试从广受好评的Octopress设计切换到当前设计时，这个设计在用户访问网站时获得了更好的互动效果，而且似乎也被更多地传播了</li>
<li>当我将自己的流量数据与大型企业博客进行比较时，我的博客完全主导了大多数市值低于1000亿美元的公司（例如，它的流量比雇主的博客高出一个数量级，而我的雇主是一家市值500亿美元的公司）</li>
<li>当我开始写博客时（今天依然如此），关于编程博客的<a href="https://twitter.com/danluu/status/1437539076324790274" rel="noopener noreferrer">写作建议是保持简短，也许500到1000字</a>。我的大多数博客文章是5000到10000字</li>
</ul>
<li>接受我现在的工作，几乎所有人都认为这是个愚蠢的主意</li>
<ul>
<li>密切相关：辞去在Centaur的工作去参加<a href="https://www.recurse.com/scout/click?t=b504af89e87b77920c9b60b2a1f6d5e8" rel="noopener noreferrer">RC</a>，然后最终转行进入软件领域（我认为现在这不会被认为是愚蠢，但在2013年，这被认为是非常愚蠢的事情）</li>
</ul>
<li>学习一项运动或电子游戏：我尝试事物以了解当你做它们时会发生什么，这常常导致当该动作看起来很蠢时，别人认为我是个彻底的傻瓜，但愿意显得愚蠢帮助我相对快速地进步</li>
<li>医疗保健：我发现很多医生对自己的意见非常自信，如果你不同意，他们会很快变得居高临下</li>
<ul>
<li>然而，在最极端的情况下，如果我听从我的医生，我会死；在次极端的情况下，我会失明</li>
<li>抽血时，我会向人们解释我的血管很难扎，并告诉他们过去什么方法有效</li>
<ul>
<li>大约一半的时候，护士或抽血师会认真对待我的评论，通常能顺利且无痛或几乎无痛地完成抽血</li>
<li>大约一半的时候，护士或抽血师会用看傻瓜的眼神看我，并对我进行愤怒的和/或居高临下的评论；到目前为止，所有这样做的人要么未能抽到血，要么给我留下了血肿</li>
<li>有人告诉我说我陈述偏好的方式可能具有冒犯性，我应该更有礼貌；然后我邀请他们一起观察，但没有人提出建议让我如何以不同的方式陈述以获得更高比例的正面回应；总的来说，当人们看到护士和抽血师的反应时，他们都感到震惊和不快</li>
<li>回想起来，我大概应该在有人出现"糟糕"反应时就起身离开，这可能会增加那个人认为我愚蠢的感觉</li>
<li>我遇到的一个问题（不是导致"扎针难"的主要问题）是，在抽血过程中，血液会减慢然后通常停止。有些护士喜欢晃动针头看看是否能重新开始流动，这有时有效（大约50/50），但通常会给我留下一大片淤青或血肿，或者两者都有。这种情况发生过几次后，我问是否在抽血前通过大量活动让血液流动（例如在抽血前多活动）会有帮助，我问过的每个护士或抽血师都说那很傻，不会有任何区别。我还是试了试，这解决了这个问题，虽然我仍然存在很难正确扎针的问题</li>
</ul>
</ul>
<li>面试：我在面试中通常不会对抗，但我会尽量说出我认为真实的事情，并尽量避免说我认为虚假的事情，这经常<a href="https://twitter.com/danluu/status/1447268693075841024" rel="noopener noreferrer">导致面试官认为我很蠢</a>（<a href="https://danluu.com/algorithms-interviews/" rel="noopener noreferrer">我面试的失败率通常相当高</a>，所以谁知道这是否相关，但当你说了"愚蠢"的话后，有人用看傻瓜的眼神看你，或者开始用居高临下的语气和肢体语言，这似乎是个坏兆头）。</li>
<li>通常努力改进事物并保持真诚</li>
<ul>
<li>甚至在"tryhard"（过分努力的人）成为贬义词之前，我社交圈里的很多人就认为做个"tryhard"是愚蠢的，一个人不应该努力，而应该装酷（这是在我成为工程师之前；作为一名工程师，我认为努力受到的尊重比我学校同学和当时我认识的网友都多）</li>
</ul>
<li>通常承认自己在某些方面能力不足或没天赋，例如，<a href="https://danluu.com/learning-to-program/" rel="noopener noreferrer">在这篇文章中提到我学习编程很吃力</a>；Jane Street的一位面试官深入探讨了我在那篇文章中写的内容，并因此对我大加抨击（这是我经历过最具敌意的面试，差距很大），当你真诚地表达自己时，有时会发生这种事情，但我仍然认为好处大于坏处</li>
<li>招聘：我有一个非传统的招聘说辞，坦率地先说缺点，经常导致人们说我是一个糟糕的招聘者（或者讽刺地说我是一个优秀的招聘者）；我还没有公开写下这个说辞（目前还没？），因为它负面到我担心把它放到网上会被解雇</li>
<ul>
<li>我从未在招聘全职员工上失败过（有一次在招聘实习生上失败过），并吸引了很多原本不会考虑为我们工作的人。我的招聘说辞听起来可笑地愚蠢，但它比大多数人使用的标准招聘说辞有效得多</li>
</ul>
<li>在互联网上发布内容：不言自明</li>
</ul>

<p>尽管上面的大多数例子是"现实生活"中的例子，但甘愿显得愚蠢在工作中也非常有效。除了它能让你学习更快、变得更高效的明显原因外，它还让你更容易找到高投资回报率的想法。如果你追求时髦或听起来合理的想法，想要做一些真正非凡的事情，你必须比处理同一问题的其他人有更好的想法/执行力。但如果你思考的想法大多数人都认为太愚蠢而不予考虑，你经常会遇到既具有高投资回报率又简单易行的想法，只要他们不一开始就否定这个想法，任何人都能做到。从技术上讲，你仍然可能需要比尝试同样事情的其他人有更好的执行力，但如果没有人尝试同样的事情，那就很容易做到了！</p>

<p>我实际上不必像大多数人那样聪明或努力工作就能取得好结果。如果我尝试通过做其他人都在做的事情来解决某个问题，并去其他人寻找问题的地方寻找问题，如果我想要做一些有价值的事情，我必须比很多人做得更好，甚至可能比所有人都更好，如果问题非常困难的话。如果问题被认为很时髦，很多非常聪明和勤奋的人会探索同一领域，要做得比他们更好非常困难。但如果我有一个愚蠢的想法，一个听起来太蠢以至于没人愿意尝试的想法，我就不一定需要特别聪明、有天赋或勤奋就能想出有价值的解决方案。通常，愚蠢的解决方案是任何白痴都能想到的，而问题之所以没有解决，是因为没有人愿意思考那个愚蠢的想法，直到像我这样的白痴看了这个问题。</p>

<p>总的来说，我认为甘愿显得愚蠢的好处远大于坏处。对于那些不涉及社会评判的事情，比如赢得游戏、理解事物、或者由于有良好的理解而能够构建事物，这全是好处。对于涉及社会评判的事情，比如面试和约会，可能存在坏处，但即使在那里，我认为很多看起来像坏处的事情实际上是好处。</p>

<p>例如，如果一个约会对象因为我不知道一个词的意思而认为The request was rejected because it was considered high risk</p><p><em>由 mimo-v2.5 模型翻译，花费 12483 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/look-stupid/</link>
      <guid isPermaLink="false">https://danluu.com/look-stupid/</guid>
      <pubDate>Thu, 21 Oct 2021 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>学什么好</title>
      <description>[AI 摘要] 本文探讨了学习策略，主张应专注于符合个人天赋的技能及有利环境，而非盲目遵循通用建议。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文探讨了学习策略，主张应专注于符合个人天赋的技能及有利环境，而非盲目遵循通用建议。</div>常见有人提倡学习自己擅长的技能或使用自己熟悉的方法。例如，Steve Yegge 发表过一系列博文，建议阅读编译器书籍、学习编译器原理。他的理由大体是：如果理解编译器，你就能在各种场合发现编译器问题，并识别出那些人们未借助编译器知识而解决的案例。与其拼凑半成品方案，不如运用一些计算机科学知识，以更省力的方式解决问题。这没错，但这不是必须专攻编译器的理由，因为计算机科学和数学的许多其他领域都可以这样类推——比如排队论、计算机体系结构、数学优化、运筹学等。

对此类反对意见的一种回应是<a href="https://twitter.com/danluu/status/899141882760110081" rel="noopener noreferrer">“应该学一切”</a>。尽管成为涉猎极广的通才可行，但如今要“对所有事物略知一二”并保持高效已变得困难得多，因为随着时间的推移，各领域在广度和深度上都在不断扩展。即使并非如此，我认为“应该”二字过于绝对；是否享受这种博学是个人品味问题。另一种可行方法更合我意：正如<a href="https://alumni.media.mit.edu/~cahn/life/gian-carlo-rota-10-lessons.html" rel="noopener noreferrer">Gian Carlo Rota 所言</a>，学习一些诀窍：

<blockquote>
<p>很久以前，一位年长的知名数论家对Paul Erdős的工作发表了些轻蔑评论。我和你一样钦佩数学贡献，所以听到那位数学家断然宣称Erdős的所有成果都可归结为几个反复使用的技巧时，我颇感恼火。但这位数论家没意识到，其他数学家——即使是最优秀的——也依赖几个反复使用的技巧。以希尔伯特为例。其论文集第二卷收录了他在不变式理论方面的论文。我曾仔细研读其中部分论文，令人惋惜的是，希尔伯特一些精妙成果已被彻底遗忘。但细读其在该领域深刻定理的证明时，我惊讶地发现希尔伯特的证明同样依赖相同的几个技巧。连希尔伯特都仅有几个技巧！</p>
</blockquote>

<p>观察各领域的成功者，会发现这是普遍做法。例如，<a href="https://judoinfo.com/weers1/" rel="noopener noreferrer">一项关于世界级柔道选手的分析发现，大多数人依赖少数几种投技</a>，并得出结论<sup id="fnref:J"><a href="#fn:J" rel="noopener noreferrer">1</a></sup>：</p>

<blockquote>
<p>柔道是专精的竞技。你必须使用最适合自己的技能。你必须坚持有效的方法，反复练习直至成为条件反射。</p>
</blockquote>

<p>若观看格斗题材的动画或电视剧，角色常通过增加招式数量来提升实力——因为这在视觉上容易表现。但现实中，精进已有技能往往比掌握数百种“招式”更有效。</p>

<p><a href="https://staffeng.com/stories/joy-ebertz" rel="noopener noreferrer">Joy Ebertz 也曾提及</a>：</p>

<blockquote>
<p>我曾得到一个建议：放大自身优势。人人都有优缺点，我们常把大量时间耗在“改进领域”上。人们容易认为最好的进步方式是消除所有弱点。但若某些方面确实薄弱，改变它们可能事倍功半。显然，你仍需确保没有真正糟糕的缺陷。但除此之外，应聚焦于放大优势。如何将你擅长的事物转化为超能力？</p>
</blockquote>

<p>个人在多个领域验证了这一点。尽管难以客观衡量程序员效能，但在我从事过的某些事（例如很久以前、电竞尚无“真正”奖金时的竞技游戏）中，让我从还算不错的玩家进阶为<a href="https://danluu.com/look-stupid/#fn:B" rel="noopener noreferrer">顶尖玩家</a>的关键，正是放弃练习不擅长的领域，全力提升那些我天赋异禀技能的优势。</p>

<p>这种方法在游戏和运动中有效，因为你可以通过策略将自己置于优势位置，避免暴露弱点。我认为在工作中这甚至更有效，因为在竞争环境中，对手会试图攻你弱点、迫使你远离优势领域。而工作中，假如我研究排队论而非编译器，同事不会阻止我参与排队论相关的项目，强迫我面对全是编译器知识的应用场景。</p>

<p>值得注意的是，技能不一定是通常认为的学科或离散技术。过去三年，我主要应用并提升的技能或许可称为“数据分析”——加引号是因为我找不到确切术语。我不认为这是多数人理解的“统计学”，因为我无需像逻辑回归那么复杂的方法，更不用说高深统计。或许有人认为这是数据科学家的工作，但对比我们雇佣的数据科学家实际工作内容和面试要求，我们似乎并不想招聘具备该技能的数据科学家，他们也不做我正在做的事（这个话题足够长，可能另写专文讨论）。</p>

<p>与 Matt Might 或 Steve Yegge 不同，我不会断言你应该采取特定方法，但可以说：专注少数领域、不刻意追求面面俱到，这种方法在多个截然不同的领域对我有效，对许多其他人似乎也管用。</p>

<p>若采用此法，仍需决定学什么技能。这是我最常被问的问题之一，我的答案可能非所期望、也不令人满意，因为它既<a href="https://twitter.com/danluu/status/1428445465662603272" rel="noopener noreferrer">显而易见又难以实践</a>。</p>

<p>对我来说，决定学什么的两个要素是：具备相对天赋（相对于我可能做的其他事，而非与他人相比），以及拥有适合学习的环境。说“应寻找这些”过于模糊近乎无用，但仍比常规建议（即“学我所学”）更实在——后者常导向“职业建议：若想精通大规模真实环境中的复杂有状态分布式系统设计，应学函数式编程，因为技能集几乎完全一致”之类论调，或某些语言社区更极端的主张，比如Chuck Moore声称Forth的生产力<a href="https://danluu.com/boring-languages/" rel="noopener noreferrer">至少是无聊语言的100倍</a>。</p>

<p>职业生涯早期我采纳了泛泛的网络建议（包括语言建议，当时这类建议尚不成熟，无法轻易观察到：遵循此类建议的人往往效率平平，而高效人士通常不采纳这类建议）。我学了<a href="https://twitter.com/sc13ts/status/1448003352655060997" rel="noopener noreferrer">Haskell、Lisp、Forth</a>等<a href="https://malisper.me/there-is-more-to-programming-than-programming-languages/" rel="noopener noreferrer">语言</a>。职业生涯某阶段，我在两人团队中实现了可能是当今性能最高的Forth处理器（2GHz IPC导向），并为其编程（该处理器适合栈式架构，Forth是合理选择）。<a href="https://yosefk.com/blog/my-history-with-forth-stack-machines.html" rel="noopener noreferrer">如同Yossi Kreinin</a>，我认为自己投入的精力超过大多数人掌握Forth的水平；但和他一样，我不仅未发现其生产力达百倍，甚至不清楚是否真有1倍优势。公平地说，某些工具的生产力超过1倍，但总体而言，我认为追随网络建议的投入产出比很低，而那些高回报的技能并非人们推荐的内容。</p>

<p>回顾来看，当有人说“Forth生产力很高”时，我怀疑其真意是“Forth让我很高效，但我未考虑这如何适配不同天赋或不同环境的人”。Forth（或Lisp、Haskell等任何工具技术）对特定个体确实有效，但人们常高估“它对我有效”与“它对他人也有效”的关联性，<a href="https://twitter.com/danluu/status/1355661542155378688" rel="noopener noreferrer">导致建议通常无用，因为它未能区分天赋或情境特定的建议与普适性建议</a>，这与真正讨论具体技术优劣的领域形成鲜明对比<sup id="fnref:F"><a href="#fn:F" rel="noopener noreferrer">2</a></sup>。</p>

<p>教练可通过一对一或小组形式提供个性化建议，但互联网上难以做到，因此我能做的只是无用的模糊建议：“选择适合你的技能”。例如，对我来说容易掌握的两项技能是“具备对抗性思维”和“数据分析”。或许稍有用的建议是：若难以识别这些技能，可询问非常了解你的人——例如我的经理和Ben Kuhn不约而同指出，“提出跨越多个抽象层次的解决方案”是我常运用的技能（在他们点明前，我未意识到这点）。</p>

<p>另一种方法是寻找那些你情不自禁去做、而他人似乎不做的事。对我而言，“数据分析”和“对抗性思维”皆如此。以对抗性思维为例：我曾参与某公司定制Bug跟踪系统的测试，提交了首批Bug并在字段中填入异常值测试系统极限。有些人无法理解为何有人这么做，感到困惑、反感或惊骇，但少数人（包括作者们，我知道他们不会介意）很赞赏看到系统被推至极限。探索系统崩溃边界于我而言不像工作；若不想做，我必须刻意停止——这让十年来提升测试与验证技术的历程感觉像是“难以不做”之事。深入数据分析是我已坚持十余年的另一领域，对我来说，若不提升这项技能几乎感觉不对。

</p><p>这些技能适合我，主要源于个人性格，而非人类共性。其他人会有不同的“感觉轻松/正确”的领域，这很好——倘若人人都热衷数据分析而无人建造事物，那将非常麻烦（尽管我认为数据分析平均而言被低估了）。</p>

<p>我尝试学习的另一要素是寻找适合学习与我技能匹配技能的环境。建议他人效仿听起来像无用的显而易见忠告，但根据我对人们选择团队和公司的观察，几乎无人这样做，因此讨论此事或许不全然无益。</p>

<p>一个典型反例：我偶然得知某位经理的团队流失了新转正的全职实习生。我询问此事，因为该经理通常善于留人、团队流失率低。原来实习生希望从事基础设施工作，但加入了该经理的产品团队，因为不知道可以要求匹配偏好的团队。经理知情后促成了转岗。此案例是双重打击：新员工既未尝试进入利于学习技能的环境，又加入了一家基础设施组织功能失调、设计与运维实践不佳的公司——使其在未尝试进入基础设施团队的前提下，更难学习相关知识。虽是异常糟糕的案例，但据我所见的典型情况，人们即便声称最渴望学习机会，其决策却很少带来良好的学习结果。</p>

<p>例如，Steve Yegge 曾指出：</p>

<blockquote>
<p>大学候选人最常问的问题是：“贵公司提供何种培训或指导？”
……一位华盛顿大学受访者向我详述福特汽车的指导计划，福特显然将其作为面试宣传的一部分。[我略去细节，因其无关紧要。-stevey 3/1/2006] 该学生能惊人地复述细节，这不意外，因为候选人最关心的正是这点。</p>
</blockquote>

<p>对我而言，幸运的是首份工作Centaur是培养测试与验证对抗性思维的理想环境。对比该验证团队的成就，其效率堪比其他公司用更大规模团队完成类似工作的同行项目，表明团队生产力极高——这使其成为绝佳的学习场所。</p>

<p>此外，我认为仅靠自学或遵循书籍/网络建议无法如此快速学习。<a href="https://danluu.com/hardware-unforgiving/" rel="noopener noreferrer">真正擅长某事的人，其知识体量难以压缩成书籍，更不用说博文</a>。体育领域中，优秀教练能逐步传达这类信息，但编程领域尚无类似机制，因此我认为提升学习效率的最佳途径是寻找充满专家的环境<sup id="fnref:M"><a href="#fn:M" rel="noopener noreferrer">3</a></sup>。</p>

<p>对于“数据分析”，尽管在人们未真正重视数据的环境中工作使我技能大幅精进，但过去几年的进步速度更高——因为我能与精于理解数据局限性、且具备深厚领域知识的人交流碰撞想法。可以说，我在当前公司每年的进步超过以往十年。</p>

<p>或许应注意：环境（你日常生活的空间）具有局部性。我现雇主可能是我工作过的三大科技巨头中最不数据驱动的，但我的周边却是提升数据分析能力的绝佳环境，因为我相对多地与擅长数据的人（如Rebecca Isaacs）共事，相对少地与漠视数据的人协作。</p>

<p>本文讨论了若干策略及其价值动因，但我必须承认，向专家学习的动机并非创造价值。更多是因为我发现学习充满乐趣，且在某些领域，无论环境如何，我都渴望运用技能；向专家学习是如此美妙的乐事，难以抗拒。数十年实践下来，这确实有用——但我许久才意识到这点（且从硬件转向软件前，我完全不知此技能可迁移至新领域）。</p>

<p>许多职业建议围绕职业发展、成功或成长，常倡导设定长期目标或战略，其论点常类似“随机游走仅能在某方向移动sqrt(n)，而定向行走可移动n”。我认为这没错，但对许多人而言，该建议隐晦地低估了寻找适合领域的难度<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">4</a></sup>，而我基本上<a href="https://twitter.com/jeanqasaur/status/1074528356324892672" rel="noopener noreferrer">通过试错</a>完成此事。</p>

<h3 id="appendix-parts-of-the-problem-this-post-doesn-t-discuss-in-detail">附录：本文未详述的问题部分</h3>

<p>一个未讨论的主要话题是如何平衡技能“层级”——从高层如“数据分析”，到中层如“贝叶斯多层模型”，再到低层如“打字速度”。这话题足够庞大，值得单独撰文，预计篇幅更长。目前可参考<a href="https://danluu.com/productivity-velocity/#appendix-one-way-to-think-about-what-to-improve" rel="noopener noreferrer">Gary Bernhardt关于相关主题的评论</a>，我认为同样适用于此话题。</p>

<p>另一个未讨论的主要话题是选择较可能适用的技能。仅建议考虑学习有天赋的技能，而不思考适用性，过于天真。</p>

<p>但虽然容易挑出那些极难产生影响、赚钱或达成目标的技能（如“篮球”或“拳击”），在看似合理的技能间选择（如计算机体系结构与编程语言）则更困难。</p>

<p>但我认为，半合理技能只要与个体匹配，很可能高回报，因此在半合理技能中试错可行——尽管<a href="https://danluu.com/productivity-velocity/" rel="noopener noreferrer">快速尝试的能力</a>或许有帮助。</p>

<h3 id="appendix-related-posts">附录：相关博文</h3>

<ul>
<li>Ben Kuhn论<a href="https://www.benkuhn.net/conviction/" rel="noopener noreferrer">真正学习某事是何体验</a></li>
<li>Holden Karnofsky论<a href="https://80000hours.org/podcast/episodes/holden-karnofsky-building-aptitudes-kicking-ass/" rel="noopener noreferrer">采用天赋优先而非职业路径优先的职业观</a>，这类似于思考跨领域技能（如“数据分析”或“对抗性思维”），而不仅关注具体技能（如“编译器”或“排队论”）</li>
<li>Peter Drucker论<a href="https://www.csub.edu/~ecarter2/CSUB.MKTG%20490%20F10/DRUCKER%20HBR%20Managing%20Oneself.pdf" rel="noopener noreferrer">如何理解自身优劣势，从事与性情相符的工作</a></li>
<li>Alexey Guzey论<a href="https://guzey.com/advice/" rel="noopener noreferrer">建议的有效性</a></li>
<li>Edward Kmett<a href="https://www.youtube.com/watch?v=Z8KcCU-p8QA" rel="noopener noreferrer">关于如何思考学习的另一视角</a></li>
<li>Patrick Collison<a href="https://patrickcollison.com/advice" rel="noopener noreferrer">论如何最大化有效学习并发现兴趣所在</a></li>
</ul>

<p><small>感谢Ben Kuhn、Alexey Guzey、Marek Majkowski、Nick Bergson-Shilcock、@bekindtopeople2、Aaron Levin、Milosz Danczak、Anja Boskovic、John Doty、Justin Blank、Mark Hansen、"wl"和Jamie Brandon的评论/修正/讨论。</small></p>
<div>

<hr>

<ol>
<li id="fn:J">这是一项旧分析。若在今日进行，你会看到不同的投技组合，但专精者仍获巨大成功（例如Riner的大外刈）
 <a href="#fnref:J" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:F">为无差别、无背景的建议辩护：学习特定主题（如函数式编程）对我确实有效。我能想象，若这种思维方式（源于硬件背景）对我而言本不自然，那么“应学函数式编程以改变问题思考方式”的建议可能有用。但换个角度，该建议同样可能是“学硬件工程”。
 <a href="#fnref:F" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:M"><p>我样本量不足，也未广泛调查，因此无法确信此为通用方法。但就寻找世界级专家群体而言，对我有效的是寻找卓越经理人。我工作过的世界级专家密度最高的两支团队，皆由真正杰出的管理者领导。我对优秀管理的标准高于多数人；通过与多人交流发现，几乎无人接触过我认为优秀的管理者（且通常交流双方认同此观点，并非他们在我不在意的维度有优秀管理者而我没有）。由此推测，我认为优秀的管理者至少处于99.9%分位。如何找到此类管理者是长篇话题，可能另文详述。</p>

<p>无论如何，尽管此样本较小，我认为其机制合理：我所知的卓越管理者拥有极高留任率，且有大量候选人渴望加入，因此他们较易雇佣并留住世界级专家，因为<a href="https://danluu.com/hiring-lemons/" rel="noopener noreferrer">行业整体状况太糟糕</a>。</p>

<p>更常见的策略（我认为通常无效，我尝试时效果也一般）是从事最有趣或最难的问题。虽然我在尝试<a href="https://www.benkuhn.net/hard/" rel="noopener noreferrer">从事有趣/困难问题</a>时确实遇到过一些优秀人才（包括我共事过的最顶尖工程师之一），但就与我想学习的人共事而言，此方法效果远逊于寻找优秀管理者。我认为此算法的普遍问题与因“电子游戏很酷”而从事游戏行业相同：太多人渴望参与激动人心的问题，导致功能失调的环境长期存在。</p>

<p>某案例中，我在一个九个月内流失率达100%的团队（若非一人转岗耗时过长，流失将达600%）。我同期加入的同批人平均年流失率约50%，我认为他们离职理由充分。此类流失不仅标志环境常令人不快，且往往导致我想合作/学习的人优先离开。例如，我在某团队（其主管不认同使用版本控制、自动化测试或流水线设计）时，曾与优秀的Ikhwan Lee共事。当然，Ikhwan很快离职，而那位主管仍在，六年后依然在职。</p>
 <a href="#fnref:M" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:S">我在熟人中多次观察到：人们会在毫无头绪时就选定方向。往往经过许久（某些案例超十年）才意识到实际深恶此方向——有时因不匹配性情，有时因自己不擅长。无论如何，与沿不快乐方向前进n步相比，随机探索并沿快乐方向前进sqrt(n)步似乎没那么糟。
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 11084 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/learn-what/</link>
      <guid isPermaLink="false">https://danluu.com/learn-what/</guid>
      <pubDate>Mon, 18 Oct 2021 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>关于提升生产力和速度的一些原因</title>
      <description>[AI 摘要] 本文探讨了提升生产力和速度的实际益处，并反驳了认为思考效率或努力工作在道德上或实践上是错误的常见观点。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文探讨了提升生产力和速度的实际益处，并反驳了认为思考效率或努力工作在道德上或实践上是错误的常见观点。</div><p>我与密友们常讨论的话题是我们生产力中的瓶颈以及如何更快地执行。这与我在更广泛的社交圈中所见大相径庭，那里的人们常说<a href="https://twitter.com/danluu/status/1440106603093495810" rel="noopener noreferrer">速度无关紧要</a>。在网上相关讨论中，我常看到人们更进一步，对此进行道德评判，声称试图提高速度、追求更高生产力或努力工作实际上是坏事（更多例子见附录）。</p>

<p>我看到人们认为生产力无关紧要（或实际上是坏事）的主要原因可分为三类：</p>

<ul>
<li>做正确的事比快速做事更重要</li>
<li>做某事（X）的速度无关紧要，因为你花在X上的时间并不多</li>
<li>考虑生产力是坏事，你应该“享受生活”</li>
</ul>

<p>我当然同意做正确的事很重要，但提高速度并不会妨碍你做正确的事。实际上，这两者相互促进，是彼此的力量倍增器。如果你善于选择正确的问题，强大的执行能力会变得更有影响力，反之亦然。</p>

<p>诚然，选择正确问题带来的收益可能大于提升战术执行能力带来的收益，因为前者的收益可能无限大。但提升战术执行能力也容易得多，并且这也有助于选择正确问题，因为更快的执行能力让你能更快地进行实验，从而帮助你找到正确的问题。</p>

<p>一个具体例子是我参与的一个量化车队机器健康状况的项目。该项目发现了许多严重问题（相当一部分主机正在积极破坏数据，或者存在会使尾部延迟增加超过两个数量级性能问题，或两者兼有）。问题严重到足以成立一个新团队来处理。</p>

<p>回顾起来，我最初量化问题的尝试注定失败，不可能真正奏效（或无法在合理时间内奏效）。我花了数周时间钻研那些行不通的想法，而能在“仅仅”几周后找到可行想法的关键部分，在于能够快速尝试并摒弃不奏效的想法。在之前某篇文章的部分内容中，我描述了这个过程中的一个小环节耗费了多长时间，结果有多人在网络评论中反对说这快得不可思议。</p>

<p>我觉得这有点好笑，因为我并非天生是高效的程序员。<a href="https://danluu.com/learning-to-program/" rel="noopener noreferrer">学习编程对我来说是真正的挣扎</a>，我曾经在很长一段时间里都很慢（并且在那些我尚未练习的方面，我现在仍然很慢）。我的“独门秘诀”是我曾刻意练习，提升我经常做但大多数人没有刻意去提升的事情。我认为这种情况有点类似于人们真正开始科学训练之前的体育运动。很长一段时间里，许多运动员并没有认真训练，而一旦人们开始尝试训练，按现代标准来看，这些训练往往是错误的。例如，如果你阅读70年代的棒球评论，你会看到人们说棒球运动员不应该进行力量训练，因为这会使他们“肌肉僵硬”（许多人认为举重会导致“过多”的肌肉块，使人变慢、爆发力下降、敏捷性降低）。但今天，球员从使用增强肌肉僵硬程度的性能增强药物中获得了巨大优势，这表明仅靠力量训练不可能让人变得太“肌肉僵硬”。类似前面讨论过的评论，会说运动员不应该担心力量/力量，而应该提升技术，但力量能提升技术的回报，反之亦然。</p>

<p>回到编程，如果你刻意练习和训练，而几乎没人这么做，那么即使你像我一样，在编程方面没有多少天赋，起步也极其艰难，你也能做得比大多数人快。当然，总会有一些更有天赋的人，在花更少时间提升后执行得更快。但幸运的是，对我来说，<a href="https://danluu.com/p95-skill/" rel="noopener noreferrer">认真尝试提升自己的人相对较少</a>，所以我做得还不错。</p>

<p>总之，尽管我的操作速度被一些网络评论者认为是不可能的，但我也花了数周死胡同才找到可行方案。如果我的速度是人们认为的“正常”速度，我怀疑找到可行方案所需的时间会足够长，以至于我可能在花费一两个季度后就放弃了这个问题。似乎合理但行不通的方案数量，可能并非与这个问题尽管是多年关键问题却仍是未解难题无关。当然，比我更擅长想出点子的人可能无需走死胡同就能解决问题，但正如我们之前讨论的，在“执行速度”上找到低垂果实相对容易，而在“产生更好想法”上则不然。然而，通过快速尝试和摒弃想法，在一定程度上可以模拟那些比我想法更好的人（我也在努力产生更好的想法，但追求那些更容易获得的高回报收益是合理的）。快速尝试想法的能力也提高了我能产生更好想法的速度，因为其中关键部分是通过获取反馈来建立直觉。</p>

<p>下一个主要反对意见是，在特定任务（X）上提高速度无关紧要，因为花在该任务上的时间有限。从根本上，我不同意这个反对意见，因为尽管这可能对任何特定类型的任务都成立，但解决方案是尝试提升每种任务，而不是彻底否定改进的想法。人们的一个次级反对意见是类似这样的：“但我每周花20小时在毫无成效的会议中，所以我其他时间做什么都无关紧要。”我认为这大错特错。因为如果你只剩下20小时可能高效的时间，那么无论你对这段时间有什么样的生产力乘数，它仍然适用于你的整体生产力。此外，通常有可能退出那些注定无用的会议，并提高那些并非注定无用的会议的生产力<sup id="fnref:M"><a href="#fn:M" rel="noopener noreferrer">1</a></sup>。</p>

<p>更广泛地说，当人们说优化X没有帮助，因为他们不花时间在X上，也不在X上遇到瓶颈时，这与我的经验不符，因为我发现我在那些常被忽视的X上花费了大量时间并遇到瓶颈。我认为部分原因是，在X上变得更快实际上可能增加在X上花费的时间，因为时间分配存在一种良性循环反馈。另一部分原因如Fabian Giesen这条评论所阐释：</p>

<blockquote>
<p>人们普遍接受（近乎陈词滥调）是，你不知道程序在哪里耗费时间，除非你实际进行分析；但由此衍生的结论，即你也不知道<em>你自己</em>的时间花在哪里，除非你进行测量，则远未被普遍接受。</p>
</blockquote>

<p>当我查看人们实际时间花费与他们自认为的时间花费对比时，结果非常不准确。我认为一个根本原因是，除非他们进行测量，否则人们对自己时间分配的估计往往偏差很大。这被Fabian Giesen的另一条精辟总结（虽然是关于解魔方，但也适用于其他认知任务）所概括：</p>

<blockquote>
<p>一位知名魔方玩家说：“你自己的停顿在解题过程中看起来从不糟糕，因为你的大脑在忙碌，你知道自己在想什么，但一旦你看了录像，你需要改进的地方往往就变得显而易见了。”这与通常针对程序的“不要假设，要分析”建议几乎一样，但适用于你全程专注忙碌的情况，而在编程圈的默认假设似乎是，只要你在实际工作，没有分心或偷懒，就不可能损失大量时间。</p>
</blockquote>

<p>与大多数在线讨论此话题的人不同，我实际查看了我的时间流向，很大一部分花在了那些被当作“不值得花时间改进”的典型例子上。</p>

<p>其中一个例子，也是我见过最常被引用的“不该优化”的例子，是打字速度（讨论时，人们通常说打字速度不重要，因为思考花的时间比打字多）。但是，当我查看我的时间流向时，发现很大一部分时间花在打字上。</p>

<p>一个具体例子是，我在当前工作中写过几份有影响力的文档，当人们问写某份文档花了多长时间时，他们通常惊讶地发现只花了一天。就像机器健康状况的例子一样，速度有助于确定哪些文档会产生影响。如果我看看我写的文档，我会说大约15%真正有高影响力（促成了新团队的成立、改变了现有团队的方向、显著影响了公司的利润等）。部分原因是我并不总是知道哪些想法会引起他人共鸣，但部分原因也是我经常提出一些看起来太傻而不会被认真对待的、成功几率渺茫的想法（例如，我提出的解决容量危机的一个方案是，为每个机架关闭10%的容量，从而增加有效配置容量——这听起来简直傻到家了）。如果我写文档的速度慢得多，提出这种真正成功几率渺茫的想法就不划算了。按目前情况，如果我认为一个想法有5%的成功率，那么平均而言，我需要写大约20天的文档才能让其中一个成功。</p>

<p>我大约一半的写作时间花在打字上。如果我按某些人所说的“中等打字速度”（40 WPM）打字，而不是按某次随机打字测试的速度（110 WPM），这将导致 0.5 + 0.5 * 110/40 = 1.875倍的减速，这意味着每篇有高影响力的“渺茫希望”文档需要近40天，这会让这个提议变得风险更大。如果我同时没有优化写作工作流中非打字的部分，我认为总体上可能会慢10倍<sup id="fnref:T"><a href="#fn:T" rel="noopener noreferrer">2</a></sup>，这意味着每篇高影响力“渺茫希望”文档需要大约200天，这足以让我认为我可能根本不会写这类文档了<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">3</a></sup>。</p>

<p>更广泛地说，Fabian Giesen指出，这种速度的非线性影响很常见：</p>

<blockquote>
<p>当你跨越某些阈值时（细节在某种程度上取决于具体问题），会出现“相变”，你的整个工作方式都会改变。
...
有很多事情理论上我可以在任何速度下完成，但实际上做不到，因为随着迭代时间增加，它首先会变得令人沮丧，让我无法长时间进行，最终耗时太长以至于完全超出了我的短期记忆范围，因此我需要记笔记或以其他方式组织信息，否则根本无法完成。</p>

<p>当然，如果我能在交互式界面中通过拖动滑块完成实验，并在不到一秒的时间内看到结果，那么在这个阶段就是“不过滤”的，你想尝试什么就直接去做。</p>

<p>一旦你的迭代时间缩短到几秒钟内（比如使用静态编译语言的编译-链接周期），你不再只是尝试，你还会花时间思考它是否能提供信息，因为这会耗费足够的时间，以至于你宁愿不浪费一次运行。</p>

<p>一旦进入几分钟或几小时的迭代时间，就需要大量规划以避免浪费运行，并且由于等待期间会做其他事情，会出现上下文切换，还需要记笔记/做记录；此外，在这个级别上，错误代价更高（因为浪费的运行浪费了更多时间），也更频繁（因为你的注意力如此分散）。</p>

<p>当你把迭代时间进一步拉长，可能需要为相当数量的资源占用一段显著的时间，这需要审批和预算，这本身又需要会议等等。</p>
</blockquote>

<p>一个在我的工作中，项目从一类转向另一类的具体例子是<a href="https://danluu.com/metrics-analytics/" rel="noopener noreferrer">这个关于指标分析的项目</a>。当时有多个关于如何解决这个问题的提案。大家普遍认为这个问题很重要，没有异议，但所有提案都是那种你会分配一个团队通过多个路线图周期去完成的。启动这样一个昂贵的项目需要大量的组织共识，以至于许多重要问题没有得到解决，包括这个问题。但事实证明，如果界定范围得当且执行合理，这个项目实际上是一个程序员一天就能创建出最小可行产品（MVP）的项目，启动它不需要任何组织共识。你不再需要多位总监和一位副总裁都同意这个问题是组织最重要的问题之一，只需要一个认为该问题值得解决的人即可。</p>

<p>回到人们常说“在X上速度不重要因为他们花在X上的时间不多”的X上，另一个我经常看到的例子是编码，我个人的经验也并非编码速度不重要。以上述机器健康状况项目为例，在我找出可行方案后，我花了一个月时间基本上只做这件事：编码、测试和调试。我记得那个月大约有6个小时的会议，除此之外加上吃饭等时间，我就是去上班，编码一整天，然后回家。我认为跨人比较编码速度要困难得多，因为很少看到人们做相同或非常相似的非平凡任务，所以我不会尝试与他人比较，但如果我看自己在刻意提升速度前的生产力和现在的对比，如果没有通过审视速度找到的这些提升，该项目可能根本不可行。</p>

<p>基于<a href="https://en.wikipedia.org/wiki/Amdahl%27s_law" rel="noopener noreferrer">阿姆达尔定律</a>的论点在固定基准测试（如SPECint的子任务）中寻找加速时可能是合理的，但当你的系统存在这样一个情况：在某个任务上变得更好会增加完成该任务的回报，并且可能增加在该任务上花费的时间时，说“你不应该花时间在某个任务上因为它不花很多时间”就没有意义了。我花时间在高回报的事情上，但这些事情通常之所以高回报，正是因为我花了时间提升速度，这降低了回报率（ROI）中的“I”（投入）部分。</p>

<p>我看到的最后一个反对提高速度的主要论点是，对考虑生产力和提升速度这个想法本身赋予了负面的道德权重。这类评论常常对各种休闲活动（如与朋友和家人共度时光）赋予正面的道德权重。我认为这个论点本末倒置。如果有人认为花时间与朋友和家人相处很重要，一个简单的办法就是在工作中更高效，从而减少工作时间。</p>

<p>就我个人而言，我刻意避免长时间工作，我怀疑我的工作时间并不比我所在公司的中位数多，而我认为我们公司的工作生活平衡整体相当不错。我的许多生产力提升都用于休闲而非工作。此外，刻意提升速度让我<a href="https://twitter.com/danluu/status/1444034823329177602" rel="noopener noreferrer">相对较快地获得晋升</a><sup id="fnref:P"><a href="#fn:P" rel="noopener noreferrer">4</a></sup>，这意味着我比未晋升时赚得更多，这让我有更多自由去花时间做我重视的事情。</p>

<p>对于那些不是在争论“不该考虑生产力因为最好专注于休闲”，而是争论“根本不该考虑生产力因为这是不自然的，人应该过自然生活”的人，这最终归结为个人偏好。但对我来说，我太重视工作之外的事物，以至于无法不在工作中刻意提升生产力。</p>

<p>正如<a href="https://danluu.com/why-benchmark/" rel="noopener noreferrer">这篇关于测量理由的文章</a>一样，虽然这篇文章是关于提高生产力的实用理由，但我个人努力提升生产力的主要原因并非实用。主要原因是，我享受在事情上变得更好的过程，无论那是某种极客桌游、一项我毫无天赋且对我永远不会有实际价值的运动，还是工作。对我来说，次要原因是，鉴于我的寿命有限，我希望将时间分配到我重视的事情上，而提高生产力让我能做更多这样的事，但这不是我在20岁之前就有的想法，那时我已经在提升大多数我花大量时间的事情的技能多年了。</p>

<p>另一个提升生产力的常见原因是，掌握和/或普遍擅长某事似乎对很多人来说都令人满足。这并非我个人的共鸣点，但当我问其他人为什么努力提升技能时，这似乎是一个常见的动机。</p>

<p>一个相关的想法，是Holden Karnofsky长期以来一直在讨论的，即如果你曾想以某种方式对世界产生影响，即使在那些不明显需要你更擅长工作的职位上，提升技能也是有用的，因为当你转向更符合你想实现的目标的事情时，已培养的技能会给你更大的杠杆来影响世界。</p>

<h3 id="appendix-one-way-to-think-about-what-to-improve">附录：一种思考改进方向的方法</h3>

<p>以下是来自Gary Bernhardt的一个我喜欢的框架（未单独用引用块，因为除本句外，整个部分都是他的观点）。</p>

<p>人们在谈论效率时，往往固守单一粒度的分析。例如，“思考是最重要的部分，所以别担心打字速度”。如果我们退后一步看，对此的回应是：“效率存在于从年度战略一直到毫秒级按键的连续体的每个节点上。”我认为可以合理假设，较大规模的收益影响最大。但当我们转向更细粒度时，ROI在何处下降并不明显。以下是从粗到细的一些例子：</p>

<ol>
<li>你开始时的宏观观点是：编程不仅仅是思考；它是思考加上像编辑代码这样的战术活动。更快地编辑意味着有更多时间用于思考。</li>
<li>但编辑代码的成本不仅仅是花在打字上的时间！编程高度依赖短期记忆。每次编辑的停顿都是一种干扰，你可能忘记正在处理的细节。较慢的编辑实际上削弱了你的短期记忆，从而降低了效率。</li>
<li>但编辑代码不仅仅是敲键盘！它是敲键盘加上这些按键所调用的编辑器命令。一个更高效的编辑器可以显著提高有效的代码编辑速度，即使你的WPM（每分钟单词数）与以前相同。</li>
<li>但每个编辑器命令并非孤立存在！通常有多种方式可以进行相同的编辑。一个Vim初学者在更高效的做法是“bdw”时可能会输入“hhhhxxxxxxxx”。一个高级Vim用户可能会使用“bdw”，却没有意识到尽管按键次数相同，它却比“diw”慢。（在QWERTY键盘布局下，前者全在左手，而后者是左右手交替。在140 WPM下，你大约每秒输入14个按键，因此每个手指只有70毫秒的时间定位并按键。左右手交替为下一个手指在前一个手指按键过程中留出了更多定位时间。）</li>
</ol>

<p>我们必须选择思考这个问题的深度。我认为思考1-3点并让它们指导工具选择和练习，有明确的ROI。我不认为（4）值得过多思考。我们似乎自然会在那里找到“足够好”的点。但这确实为其他点提供了一个很好的参照框架。</p>

<h3 id="appendix-more-examples">附录：更多例子</h3>

<ul>
<li><a href="https://twitter.com/b0rk/status/1367172498954059791" rel="noopener noreferrer">速度无关紧要</a>，来自Julia Evans，我相信她大约从2015年起就是阅读量最广的编程博主</li>
<li><a href="https://news.ycombinator.com/item?id=10529064" rel="noopener noreferrer">在Ben Kuhn一篇关于他通过更好地分配时间将生产力提高50%的帖子的评论中，人们几乎一致对该帖子持负面态度，说他工作过度</a>。尽管Ben在多个评论和帖子中澄清并非所有记录的时间都在工作，但评论者们忙于占据道德高地，并未真正回应帖子内容</li>
<li><a href="https://news.ycombinator.com/item?id=28879240" rel="noopener noreferrer">关于Jamie Brandon的“速度重要”的评论</a>

<ul>
<li><a href="https://news.ycombinator.com/item?id=28880190" rel="noopener noreferrer">快速工作毫无意义，因为你将被迫做更多工作</a></li>
<li><a href="https://news.ycombinator.com/item?id=28881360" rel="noopener noreferrer">如果你在做正确的事，速度就不重要；另外，即使速度这种东西真的存在，它也无法衡量，因此讨论它毫无意义</a></li>
<li><a href="https://news.ycombinator.com/item?id=28879823" rel="noopener noreferrer">考虑生产力是不健康的。人应该放松</a></li>
<li><a href="https://news.ycombinator.com/item?id=28881320" rel="noopener noreferrer">你只能在“好、快、便宜”中选择两项，因此努力提高速度是适得其反的</a></li>
<li><a href="https://news.ycombinator.com/item?id=28880653" rel="noopener noreferrer">大幅度的加速是不可能的</a></li>
<li><a href="https://news.ycombinator.com/item?id=28880173" rel="noopener noreferrer">“作者把编程和打字搞混了”</a></li>
<li>等等。</li>
<li>与Ben的帖子一样，这些评论几乎都在帖子本身中得到了回应。我将不再赘述这种情况，因为它普遍适用于此处提到的帖子。</li>
</ul></li>
<li><a href="https://news.ycombinator.com/item?id=22255996" rel="noopener noreferrer">Michael Malis关于“如何提升在职程序员生产力”的帖子下的第三条评论</a>：“去他的，整个工作环境似乎都在降低生产力……我为什么要费心……”

<ul>
<li>第四条评论：“如果我不想提升生产力呢？就慢慢来好了。”
<ul>
<li>在最初的愤慨之后，这条评论继续并证明了评论者完全误解了要点，因为评论的其余部分解释了评论者如何高效地工作，而评论者似乎只要不以提高生产力的方式表述就可以接受，因为人们应该对有人想要提高生产力并与可能对此感兴趣的人分享技巧这件事在道德上感到愤慨</li>
<li>在回复中，有人指出，更高效的人能有更多时间休闲；这条评论遭到了一致反对，因为“工作会膨胀到填满所有可用时间”，仿佛一个人如何花费时间是某种不可改变的自然法则，而非任何人可以控制的事情</li>
</ul></li>
<li>另一条评论：“好吧。我们在优化什么？生产力？还是以下任何目标：达成更多、爬上公司阶梯、赚更多钱等？”</li>
</ul></li>
<li><a href="https://news.ycombinator.com/item?id=13752887" rel="noopener noreferrer">antirez关于生产力的文章下的评论</a>

<ul>
<li><a href="https://news.ycombinator.com/item?id=13753443" rel="noopener noreferrer">这篇文章谈的是10倍程序员的宇宙，而非大多数人生活的正常宇宙</a></li>
<li><a href="https://news.ycombinator.com/item?id=13753611" rel="noopener noreferrer">努力提升生产力毫无意义，因为环境决定了生产力</a></li>
<li><a href="https://news.ycombinator.com/item?id=13753465" rel="noopener noreferrer">高效的程序员很自私，不指导他人等，对团队不利，因为他们生产力的提升总是来自忽略更重要的事情，因此任何在编程上高效的人实际上对团队来说是适得其反的</a>
<ul>
<li>如果你阅读该帖子的所有评论，你会发现这是一个共同主题</li>
</ul></li>
</ul></li>
<li><a href="https://news.ycombinator.com/item?id=20737304" rel="noopener noreferrer">Alexy Guezy关于生产力的想法的评论</a>

<ul>
<li><a href="https://news.ycombinator.com/item?id=20737854" rel="noopener noreferrer">“严肃的问题：有什么比阅读他人的生产力想法更没有生产力的吗？这是拖延症和找出对你来说可能更有效（即带来负罪感）的东西的结合。”</a></li>
<li><a href="https://news.ycombinator.com/item?id=20737854" rel="noopener noreferrer">一篇题为“反对生产力”的反生产力文章</a></li>
</ul></li>
<li><a href="https://news.ycombinator.com/item?id=9715810" rel="noopener noreferrer">打字速度无关紧要，因为你只花0.5%到1%的时间打字</a>

<ul>
<li>尽管谈论的是8小时工作日，但我认为那些一天能有4小时真正工作的人通常被认为是极其高效的。0.5%到1%的4小时是每天1.2分钟到2.4分钟，或者对于一个打字速度为100 WPM的人来说，是<a href="https://thorstenball.com/blog/2020/09/01/typing-can-be-the-bottleneck/" rel="noopener noreferrer">在Slack、JIRA、电子邮件、实际代码、提交消息、设计文档、设计文档评论、文档等中总共240个单词</a>；我不相信我认识任何打字这么少的专业程序员</li>
</ul></li>
<li><a href="https://news.ycombinator.com/item?id=38797640" rel="noopener noreferrer">“我觉得快速编程肌肉和技术债务之间存在相关性……但我们都有年轻的时候，我记得那时我认为唯一阻碍我的是4.77MHz”，这是对一篇比较不同机器构建时间的博客文章的评论（该文章中没有任何类似“唯一阻碍开发者的是构建时间”的观点）</a></li>
</ul>

<p>等等。</p>

<p>一些利用生产力为“支持”他们所重视事物的正面例子包括Andy Kelley（Zig）、Jamie Brandon（多个项目）、Andy Matuschak（记忆媒介、多个项目）、Saul Pwanson（VisiData）、Andy Chu（Oil Shell）。我这里引用的是编程领域的例子，但你也可以找到很多其他例子，例如Nick Adnitt（<a href="https://darksidecanoes.wordpress.com/" rel="noopener noreferrer">Darkside Canoes</a>），当然还有众多退休后追求完全不像工作那样兴趣的人。</p>

<h3 id="appendix-another-reason-to-avoid-being-productive">附录：避免高效的另一个原因</h3>

<p>在主要科技公司我的更广泛的社交圈中，一个越来越流行的观点是，人们应该避免工作并<a href="https://www.reddit.com/r/antiwork/comments/pvjc6f/they_dont_give_a_fuck_about_you/" rel="noopener noreferrer">尽可能浪费时间</a>，通常被称为“反工作”（antiwork），这似乎是“努力”（tryhard）成为一种侮辱的自然延伸。给出的理由通常是类似这样的：工作主要是让你雇主的高层管理人员和/或股东致富，而他们通常比你更富有。</p>

<p>我同情这种论点，并<a href="https://twitter.com/danluu/status/802971209176477696" rel="noopener noreferrer">同意高层管理人员和股东获取了工作的大部分价值</a>。但尽管我同情通过故意低效工作来“对抗老板”的想法，但我太重视将时间花在我想要做的事情上，以至于我宁愿快速完成工作，这样我就能做比我更喜欢的事情。此外，过去的高效工作为我提供了良好的工作选择，因此我拥有的工作比我那些拥抱“反工作”运动的科技圈熟人要喜欢得多。</p>

<p>你对环境的控制越少，就越有意义去拥抱“反工作”。主要科技公司的程序员相对来说对他们的环境有相当大的控制权，这就是为什么即使我同情这个事业，我也不是“反工作”的。</p>

<p>尽管是关于不同的话题，Prachee Avasthi的一条相关评论<a href="https://twitter.com/PracheeAC/status/1448789430488092672" rel="noopener noreferrer">是关于避免有争议的工作，以及在获得终身教职前避免推动必要变革会养成终身教职后难以打破的习惯</a>。如果一个人想永远“反工作”，那没问题；但如果一个人想在某个时候推动改变，在为一家大科技公司工作时养成“反工作”习惯会灌输适得其反的习惯。</p>

<p><small> 感谢Fabian Giesen、Gary Bernhardt、Ben Kuhn、David Turner、Marek Majkowski、Anja Boskovic、Aaron Levin、Lifan Zeng、Justin Blank、Heath Borders、Tao L.、Nehal Patel、@chozu@fedi.absturztau.be、Alex Allain和Jamie Brandon的评论/修正/讨论</small></p>
<div>

<hr>

<ol>
<li id="fn:M">当我查看会议的效率时，有些人非常擅长让会议保持在正轨且有用。例如，我参加过会议的一个人，她异常擅长确保会议富有成效，她是Bonnie Eisenman。在我现在这份工作的早期，我问她如何能如此有效地让会议保持高效，并从此一直使用那个建议（我远不如她做得好，但即便如此，提升这点对我来说也是一个重大收获）。
 <a href="#fnref:M" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:T"><p>10倍可能听起来像一个不可思议的写作加速，但在一个关于写作速度的私密Slack讨论中，一位知名通讯作者提到，他们一篇5000字通讯的净写作速度略低于每分钟2个单词（WPM）。我的净速度（包括编辑等时间）是每篇文档超过20 WPM。</p>

<p>以110 WPM的测量打字速度，这可能听起来我只花了一小部分时间打字，但结果发现大约是时间的一半。如果我看我的写作速度，它比我的打字测试速度慢得多，似乎大约只有一半。如果我看实际时间花在哪里，大约一半时间花在打字上，一半时间花在思考上，半串行地进行，这造成了我打字时的长时间停顿。</p>

<p>如果我看这里最大的潜在收益在哪里，那就是思考和打字并行，这是我尝试通过更多练习打字而非更少来实现的目标。但即使无法做到这一点，并且拥有高于平均水平的打字速度，我仍然花了一半时间打字！</p>

<p>我的净速度远低于我写作速度的原因是我进行了多次修改和重写。有些时间花在重写时的阅读上，但我阅读速度比写作快得多，所以那只是很小一部分时间。原则上，我可以采用重写更少的方法，但我尝试过一些可能预期能达成此目标的方法，目前尚未找到适合我的（也许？）。</p>

<p>虽然这里的例子是关于工作，但这同样适用于我的个人博客，我的写作速度也类似。如果我写得比现在慢十倍，我认为我就不会有这么多博客了。我猜我可能会写几篇帖子或甚至几篇草稿，然后就止步不前，不会发布，接着就放弃了。</p>

<p>我享受写作并从中获得了各种各样的价值，但我足够重视生活中的其他事情，以至于如果我的净写作速度只有2 WPM，我不认为写作还会在我的生活中有一席之地。</p>
 <a href="#fnref:T" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:S"><p>另一个策略是写更短的文档。有一种风格的文档这样效果很好，但我经常写的文档会利用我的写作速度来讨论一个如果不写长文档就难以令人信服地讨论的问题。</p>

<p>我的文档较长的一个原因是我经常处理跨多个技术栈层次的问题，这意味着我最终会呈现来自多个技术栈层次的数据，并为那些不熟悉该层次的人提供足够的上下文，说明某个层次的问题如何引发或解决其他层次的问题——这是必要的，因为很少有读者能对理解问题所需的每个层次都具备深入的了解。</p>

<p>在大多数情况下，之前尝试推动/资助解决这个问题的努力都没有取得进展，因为没有建立起一个层次的问题与另一个重要层次问题之间的联系。我可以避免处理跨越多个技术栈层次的问题，但由于技术和组织原因，这类问题中存在很多低垂果实，因此我认为仅仅因为花一天时间写文档解释问题就忽视它们是不合理的（尽管如果需要十天，至少在人们可能对解决方案持怀疑态度的情况下，这样做可能是合理的）。</p>
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:P">当然，晋升非常不公平，更高效并不能保证晋升。如果我只看哪些因素与职级相关，<a href="https://twitter.com/altluu/status/1448012821854257155" rel="noopener noreferrer">甚至对我来说，都不清楚生产力是否比身高与职级的相关性更强</a>，但在可控因素中，生产力是最容易改变的因素之一。
 <a href="#fnref:P" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 15754 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/productivity-velocity/</link>
      <guid isPermaLink="false">https://danluu.com/productivity-velocity/</guid>
      <pubDate>Fri, 15 Oct 2021 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>内部专长的价值</title>
      <description>[AI 摘要] 文章论述了大型科技公司（以Twitter为例）内部培养关键技术领域（如内核、JVM）专家团队的必要性和经济价值，这些专长虽非表面核心业务，但能解决关键问题、实现长期优化并创造显著收益。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章论述了大型科技公司（以Twitter为例）内部培养关键技术领域（如内核、JVM）专家团队的必要性和经济价值，这些专长虽非表面核心业务，但能解决关键问题、实现长期优化并创造显著收益。</div><p>这篇文章的另一个可能的标题是：“Twitter竟然有一个内核团队？”。迄今为止，我听到过太多次这种惊讶的感叹，次数多到已经记不清了（我猜大概超过十次，但不到一百次）。如果我们看看那些在规模上（无论是市值还是工程师数量）与Twitter相差不大的流行公司，它们大多没有类似的专业知识，这往往是路径依赖的结果——因为它们“成长”于云端，不像本地部署公司那样需要内核专业知识来维持运转。虽然从社交角度可以理解那些在更年轻、更潮流公司工作的人对Twitter拥有内核团队感到惊讶，但我认为在技术层面上这种惊讶是没有道理的。</p>

<p>无论是否拥有内核专业知识，一家与Twitter规模相当的公司都会定期遇到内核问题，从重大的生产事故到小的麻烦。如果没有内核团队或等效的专业知识，公司将在解决问题时步履维艰，遇到不必要的问题，并且需要不必要长的时间来缓解事故。以一个关键的生产事故为例，因为这件事已经公开撰文，我引用<a href="https://blog.twitter.com/engineering/en_us/topics/open-source/2020/hunting-a-linux-kernel-bug" rel="noopener noreferrer">这篇文章</a>，其中冷静地指出：</p>

<blockquote>
<p>去年早些时候，我们发现了一个防火墙配置错误，它意外地丢弃了大部分网络流量。我们预计重置防火墙配置可以修复这个问题，但重置防火墙配置却暴露了一个内核错误。</p>
</blockquote>

<p>这段话暗示但没有明确说明的是，这次防火墙配置错误是在我任职于Twitter期间发生的最严重的事故，我相信这实际上是自2013年左右以来Twitter经历的最严重的故障。作为一个公司，即使没有内核团队或另一个具备深厚Linux专业知识的团队，我们仍然能够缓解这个问题，但要理解为什么最初的修复不起作用需要更长时间，而这正是你处理严重故障时最不希望发生的。内核团队的成员已经熟悉各种诊断工具和调试技巧，能够快速理解为什么最初的修复没有奏效，而这在一些同行公司中并非共识（我曾调查过几家类似规模的同行公司，询问他们是否认为至少有一人具备快速调试该错误所需的知识，许多公司的回答是否定的）。</p>

<p>在内部各领域拥有专业知识的另一个原因是，它们很容易收回成本，这是<a href="https://danluu.com/sounds-easy/" rel="noopener noreferrer">大型公司应该比大多数人预期的规模更大这一普遍论点的特例，因为微小的百分比提升在绝对金额上价值巨大</a>。如果在像内核团队这样的专家团队的生命周期内，有一个人发现某项持续性措施能降低<a href="https://en.wikipedia.org/wiki/Total_cost_of_ownership" rel="noopener noreferrer">总拥有成本（TCO）</a>0.5%，那将足以永久支付该团队的成本，而Twitter的内核团队已经找到了许多这样的改变。除了有时会产生这种影响的<a href="https://patchwork.ozlabs.org/project/netdev/list/?submitter=211&amp;state=*&amp;archive=both&amp;param=4&amp;page=1" rel="noopener noreferrer">内核补丁</a>，人们还会发现具有同等影响的配置问题等。</p>

<p>到目前为止，我只谈到了内核团队，因为它是那个最常因其存在本身而引起人们惊讶的团队，但当人们发现Twitter拥有一批曾在Sun JVM团队工作、参与过HotSpot开发的专家时，比如Ramki Ramakrishna、Tony Printezis和John Coomes，我也会收到类似的反应。人们好奇为什么一家社交媒体公司需要如此深入的JVM专业知识。与内核团队一样，我们这个规模的使用JVM的公司会遇到奇怪的问题和JVM错误，拥有深厚专业知识的人来调试这类问题很有帮助。而且，与内核团队一样，对JVM的单独优化也能永久支付该团队的成本。一个具体的例子是<a href="https://github.com/oracle/graal/pull/636" rel="noopener noreferrer">Flavio Brasil的这个补丁，它虚拟化了比较并交换（CAS）调用</a>。</p>

<p>这里的背景是Twitter大量使用Scala。尽管有很多相反的说法，但Scala比Java占用更多内存，速度也明显更慢，如果在大规模使用Scala，这会产生显著的成本，因此有必要进行优化工作，以减少地道Scala与地道Java之间的性能差距。</p>

<p>在补丁之前，如果你分析我们的Scala代码，你会看到在Future/Promise上花费的时间多得不合理，甚至在一些你天真地期望编译器能优化掉工作的场景也是如此。一个原因是Future使用了对JVM优化不透明的<a href="https://en.wikipedia.org/wiki/Compare-and-swap" rel="noopener noreferrer">比较并交换（CAS）</a>操作。上面链接的补丁在Future不逃逸方法范围时避免了CAS操作。<a href="https://github.com/twitter/util/commit/3245a8e1a98bd5eb308f366678528879d7140f5e" rel="noopener noreferrer">这个配套补丁</a>则移除了一些不太适合编译器优化的地方的CAS操作。这两个补丁结合使用，将使用地道Scala的典型Twitter主要服务的成本降低了5%到15%，其收益足以永久支付JVM团队的成本许多次，而这甚至不是Flavio那年发现的最大收益。</p>

<p>我不会逐一剖析那些自身价值远超成本的团队，因为这样的团队太多了，即使我只限定在“人们惊讶于Twitter竟然拥有这些团队”的范围内。</p>

<p>一个相关话题是人们如何讨论“购买还是自建”的决策。我见过一些讨论，有人主张“购买”，因为这将免去在该领域所需的专业知识。这有时可能是对的，但我看到这种主张被提出的频率远高于其正确性。我认为一个往往不适用的例子是分布式跟踪。<a href="https://danluu.com/tracing-analytics/" rel="noopener noreferrer">我们之前研究过Twitter如何从跟踪中获得价值</a>，这源于Rebecca Isaacs提出的愿景。另一方面，当我与规模类似的同行公司的人交谈时，他们中的大多数人尚未（或至今未能）成功从分布式跟踪中获得显著价值。这非常普遍，以至于我每年不止一次看到Twitter上关于分布式跟踪多么无用的热门话题。尽管我们选择了更昂贵的“自建”选项，但仅凭记忆，我就能想到多个跟踪的用例，其回报是构建跟踪系统成本的10到100倍，而许多选择更便宜“购买”选项的公司的人则普遍抱怨跟踪不值。</p>

<p>巧合的是，我刚和Pam Wolf讨论过这个完全相同的话题，她是一位拥有跨大洲（土木工程）行业经验的土木工程教授，持有一个相关的观点。对于大型系统（项目），你需要为不在自己公司内部处理的每个领域配备一名内部专家（业主方工程师）。虽然从技术上讲，可以再聘请另一家公司来担任专家，但这比培养或招聘内部专家更昂贵，从长远来看风险也更大。这与我作为电气工程师的经历非常相似，那些将职能外包给其他公司而不保留内部专家的组织，付出了非常高的代价，而且不仅仅是金钱上的。他们常常交付有缺陷的设计，并伴随着高昂的成本和长时间的延迟。“购买”可以且常常减少所需专业知识的数量，但往往无法消除对专业知识的需求。</p>

<p>这与另一个常见的抽象论点有关，即公司应专注于“其比较优势领域”、“最重要的问题”或“核心业务需求”，并将其他一切都外包出去。我们已经看到几个例子说明这并不正确，因为在足够大的规模下，无论某事是否是核心业务，拥有内部专业知识都更有利可图（人们可能会辩称所有内部化的事情都变成了核心业务，但这将使“核心性”这一概念失去意义）。这种抽象建议过于简单的另一个原因是，企业可以在一定程度上任意选择其比较优势是什么。一个大的<sup id="fnref:L"><a href="#fn:L" rel="noopener noreferrer">1</a></sup>例子是苹果将CPU设计内部化。自从以2.78亿美元收购PA Semi（前身是SiByte团队，更早之前是DEC的一个团队）以来，苹果一直在生产<a href="https://twitter.com/danluu/status/1433297089866383364" rel="noopener noreferrer">在手机和笔记本电脑功耗范围内明显领先的最佳芯片</a>。但是，在收购之前，苹果并没有什么使其收购成为必然，也没有什么使CPU设计成为苹果固有的比较优势。但是，如果一家公司可以选择一个领域并使其成为比较优势，那么建议公司应专注于其比较优势就不是一个很有用的建议了。</p>

<p>2.78亿美元在绝对数额上是一大笔钱，但作为苹果资源的一部分，它微不足道。规模小得多的公司也可以通过投入一小部分资源来从事前沿工作，例如，Twitter以任何一家价值1亿美元公司都能负担的成本，<a href="https://twitter.com/danluu/status/1381687511362138113" rel="noopener noreferrer">创建了新颖的缓存算法和数据结构</a>，并正在进行其他前沿的缓存工作。拥有优秀的<a href="https://danluu.com/cache-incidents/" rel="noopener noreferrer">缓存基础设施</a>对Twitter业务的核心性，并不比为苹果创造优秀CPU更高，但它是Twitter可以用来赚取更多收入的一个杠杆。</p>

<p>对于小公司来说，为公司触及的所有领域都配备内部专家并不合理，但公司无需发展到非常大的规模，就可以开始在操作系统、语言运行时和其他人们通常认为相当专业的组件上拥有内部专业知识。回顾Twitter的历史，姚越指出，当她在Twitter早期（大约100名工程师）负责缓存工作时，她经常向内核团队寻求帮助来调试生产事故，在某些情况下，如果没有内核团队的帮助，调试可能需要10倍的时间。社交媒体公司通常在每用户和每美元基础上的规模相对较高，因此不是每家公司都会在拥有100名工程师时需要同样的专业知识，但在那些并非明显核心业务需求的领域，即使对于一个拥有100名工程师的初创公司，专业知识也会带来回报。</p>

<p>感谢Ben Kuhn、姚越、Pam Wolf、John Hergenroeder、Julien Kirch、Tom Brearley和Kevin Burke的评论/修正/讨论。</p>
<div>

<hr>

<ol>
<li id="fn:L"><p>其他一些大的例子是韩国财阀，如现代汽车。虽然审视现代集团各公司之间如何关联到现代汽车公司可能并非正确视角，但我将使用这个视角，因为本博客的大多数读者可能已经熟悉现代汽车，而不熟悉韩国财阀的运作方式。</p>

<p>粗略地说，有许多例外，但美国公司至少从80年代起，往往倾向于采纳专业化、专注于自身能力的建议。这与韩国财阀所走的方向相反。现代不仅制造汽车，他们还生产汽车使用的钢铁、用于自动化生产的机器人、工厂使用的水泥、建造工厂的建筑设备、运输汽车（他们也经营）的集装箱和船舶、汽车的变速箱等等。</p>

<p>如果我们看某个特定部件，比如他们的8速变速箱与广受好评且广泛使用的采埃孚8HP变速箱相比，评测者通常略微偏爱采埃孚变速箱。但即便如此，拥有足够好的内部变速箱，以及许多其他公司通常会购买的内部部件，似乎对现代来说并不构成劣势。</p>
 <a href="#fnref:L" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 5823 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/in-house/</link>
      <guid isPermaLink="false">https://danluu.com/in-house/</guid>
      <pubDate>Wed, 29 Sep 2021 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>测量、基准测试和数据分析被低估了</title>
      <description>[AI 摘要] 本文通过众多案例论述了测量、基准测试和数据分析如何推动产品改进和行业进步，但这一领域却被普遍低估。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文通过众多案例论述了测量、基准测试和数据分析如何推动产品改进和行业进步，但这一领域却被普遍低估。</div><p>我经常被问到这样一个问题：为什么要费心测量X，为什么不直接去构建点东西呢？更直白地说，在最近与一位通讯作者的对话中，他对一些我计划进行的未来测量项目（与其他项目类似，例如<a href="https://danluu.com/keyboard-v-mouse/" rel="noopener noreferrer">键盘与鼠标</a>、<a href="https://danluu.com/keyboard-latency/" rel="noopener noreferrer">键盘</a>、<a href="https://danluu.com/term-latency/" rel="noopener noreferrer">终端</a>和<a href="https://danluu.com/input-lag/" rel="noopener noreferrer">端到端</a>延迟测量），带着一丝自得的表情和轻蔑的语气评论道：“所以你只是想上Hacker News头条？”</p>

<p>前一种暗示测量不如构建有价值，后一种则暗示测量根本没有价值（或许除了成名），但我并不认为测量是次要的，更谈不上无价值。如果有的话，因为测量和<a href="https://twitter.com/danluu/status/1082321431109795840" rel="noopener noreferrer">写作一样</a>，通常不被重视，所以寻找高投资回报率（ROI）的测量项目比寻找高ROI的构建项目要容易得多。</p>

<p>让我们先来看几个高影响力的测量项目案例。我想到的首选例子是Kyle Kingsbury与<a href="https://jepsen.io" rel="noopener noreferrer">Jepsen</a>的工作。在Jepsen出现之前，只有少数几家大型公司（现在市值超过1万亿美元、被人们称为“超大规模”的公司）对其分布式系统进行了相当充分的测试。它们大多没有以能真正促使知识传播到整个行业的方式来讨论其测试方法。在那些公司之外，大多数分布式系统按照我的标准来看，测试得并不充分。</p>

<p>当时，在线讨论分布式正确性时常见的模式是：</p>

<p><strong>A人</strong>：数据库X损坏了我的数据。<br>
<strong>B人</strong>：我用着没问题。它从未损坏过我的数据。<br>
<strong>A人</strong>：你怎么知道？你检查过数据损坏吗？<br>
<strong>B人</strong>：你什么意思？如果发生数据损坏我会知道的（另一种答案：<a href="https://mobile.twitter.com/danluu/status/918845240240410624" rel="noopener noreferrer">是的，我们有时会有数据损坏，但这可能是硬件问题，因此不是我们的错</a>）</p>

<p>Kyle早期的工作发现，尽管当时的Jepsen远不如现在成熟，但它测试的几乎所有系统都存在严重缺陷：</p>

<ul>
<li><a href="https://aphyr.com/posts/283-call-me-maybe-redis" rel="noopener noreferrer">Redis集群 / Redis哨兵</a>：“我们展示Redis在分区期间丢失了56%的写入”</li>
<li><a href="https://aphyr.com/posts/284-call-me-maybe-mongodb" rel="noopener noreferrer">MongoDB</a>：“在本文中，我们将看到MongoDB丢失了惊人的大量数据”</li>
<li><a href="https://aphyr.com/posts/285-call-me-maybe-riak" rel="noopener noreferrer">Riak</a>：“我们将看到Riak中的最后写入获胜策略如何导致无限的数据丢失”</li>
<li><a href="https://aphyr.com/posts/292-call-me-maybe-nuodb" rel="noopener noreferrer">NuoDB</a>：“如果您正在考虑使用NuoDB，请注意，该项目的营销和文档可能超出了其当前能力”</li>
<li><a href="https://aphyr.com/posts/291-call-me-maybe-zookeeper" rel="noopener noreferrer">Zookeeper</a>：这是Jepsen早期测试分布式系统中唯一没有发现灾难性错误的一个</li>
<li><a href="https://aphyr.com/posts/315-call-me-maybe-rabbitmq" rel="noopener noreferrer">RabbitMQ集群</a>：“RabbitMQ丢失了约35%的已确认写入……这不是理论问题。我知道至少有两个RabbitMQ部署在生产环境中遇到了这个问题。”</li>
<li><a href="https://aphyr.com/posts/316-call-me-maybe-etcd-and-consul" rel="noopener noreferrer">etcd 和 Consul</a>：“etcd的寄存器不是线性化的……Consul中‘一致’的读取会返回任何自认为是领导者的节点的本地状态，允许陈旧读取。”</li>
<li><a href="https://aphyr.com/posts/317-call-me-maybe-elasticsearch" rel="noopener noreferrer">ElasticSearch</a>：“健康检查端点会说谎。它乐于在脑裂场景下报告一个绿色的集群……1961次写入中，有645次被确认然后丢失。”</li>
</ul>

<p>其中许多问题已经存在了相当长的时间</p>

<blockquote>
<p>真正令人惊讶的是，这个问题被忽视了这么久。最初的问题是在2012年7月报告的；距今已近整整两年。网站上没有相关讨论，文档中也没有提及，参加ElasticSearch培训的用户告诉我，这些问题在课程中也未被提及。</p>
</blockquote>

<p>Kyle随后引用了一些在生产环境中遇到问题的用户，并干巴巴地指出：</p>

<blockquote>
<p>有些人实际上提倡将ElasticSearch用作主要数据存储；我认为在当前这不太明智。</p>
</blockquote>

<p>尽管我们无法进行A/B测试来比较Kyle存在与不存在的宇宙，也说不出在没有Kyle的宇宙中分布式系统要多久才能认真对待正确性问题，但根据我多年来观察开发者如何对待正确性错误的经验，我敢打赌，在类似Kyle这样的人出现之前，分布式系统会普遍存在正确性问题。当我看到灾难性错误被报告时，典型的反应是项目维护者假设错误报告不正确（如果你查看Kyle工作头几年的回应，你会看到很多例子）。当报告者无法重现该错误时（在分布式系统中这很常见），该错误将被认为不存在。</p>

<p>当报告者确实能重现错误时，下一道防线是论证这种行为是正常的（你同样可以从Kyle工作的回应中看到很多这样的例子）。一旦错误被确认为真实存在，下一个辩护是争辩说该错误无需修复，因为它非常罕见（例如，“<a href="https://news.ycombinator.com/item?id=5913610" rel="noopener noreferrer">很容易站在象牙塔上宣讲理论，但现实世界的成本效益是什么？你是在建造NASA航天飞机运输车来买杂货吗？</a>”）。然后，在承认这个错误应该被修复之后，最后一道防线是争辩项目非常重视正确性，实在没有什么更多能做的了；开发和测试方法不需要改变，因为错误的出现只是偶然，没有方法论改变的情况下类似错误不会再次发生。</p>

<p>Kyle的工作突破了这些防线，如果没有类似的工作，我认为我们仍然会看到这些是对抗分布式系统错误的主要辩护（而不是那些实际上能够产生相当可靠系统的测试方法）。</p>

<p>这是一个特定的例子，但我发现一个普遍现象：在没有人发布产品测量/基准测试的领域，产品通常是次优的，而且一旦被测量，这些问题往往相对容易修复。以下是一些例子：</p>

<ul>
<li>键盘：在我发布<a href="https://danluu.com/keyboard-latency/" rel="noopener noreferrer">这篇关于键盘延迟的文章</a>后，至少有一家宣传高速游戏设备的主要制造商实际上开始优化输入设备延迟。当时，很少有人测量键盘延迟，我只能找到另一个做过测量的人（我想寻找其他测量结果是因为我的测量结果高得令人难以置信，而我在线找到的唯一一个测量结果与我的测量结果处于同一范围）。现在，所有主要的游戏键盘和鼠标制造商都提供了延迟相当低的设备，而在此之前，生产游戏设备的公司专注于那些几乎没有影响的流行词优化（比如更高速的USB轮询率）</li>
<li>计算机：在我发布其他一些关于<a href="https://danluu.com/input-lag/" rel="noopener noreferrer">计算机</a><a href="https://danluu.com/term-latency/" rel="noopener noreferrer">延迟</a>的文章后，一家之前并未认真进行UI延迟工作的主要软件公司的工程师告诉我，一些工程师已经开始测量和优化UI延迟；此外，alacritty的作者<a href="https://github.com/alacritty/alacritty/issues/673" rel="noopener noreferrer">提交了这个工单</a>讨论如何降低alacritty延迟</li>
<li>汽车前大灯：Jennifer Stockburger指出，当《消费者报告》开始测试前大灯时，汽车制造商的工程师们感谢CR为他们提供了让前大灯更有效的论据；此前，他们会输给那些想要外观更漂亮但效果较差的前大灯的设计师，因为通过设计更好的前大灯来使汽车更安全是一个很难推销的概念，因为没有商业案例，但让汽车在《消费者报告》评测中得分更高让他们有时能赢得辩论。没有第三方测量，以业务为导向的汽车高管没有理由听从工程师的意见，因为几乎没有新购车者会去做任何类似于好好测试他们的前大灯能照亮道路多远的测试，测试前大灯会让对向驾驶员眩目程度的人就更少了，因此<a href="https://mastodon.social/@danluu/111802159638869338" rel="noopener noreferrer">设计师不受约束地创造出他们认为最好看的产品，而不管效果如何</a></li>
<li>汽车<a href="https://en.wikipedia.org/wiki/Anti-lock_braking_system" rel="noopener noreferrer">防抱死制动系统</a>：在《消费者报告》和《Car and Driver》发现特斯拉Model 3的制动距离极长（从60英里/小时到0需要152英尺，从70英里/小时到0需要196英尺）后，特斯拉更新了用于调节制动的算法，这显著缩短了制动距离，使特斯拉从同级最差变为优于平均水平</li>
<li>汽车碰撞安全：除了沃尔沃，汽车制造商通常设计他们的汽车以在公布的碰撞测试中获得最高分数；<a href="https://danluu.com/car-safety/" rel="noopener noreferrer">当新测试公布时，他们会增加安全性以取得好成绩，但之前不会</a></li>
</ul>

<p>任何人都可以做上面的项目（虽然《消费者报告》购买他们测试的车辆，但一些新兴的汽车评论员通过Turo租车）！</p>

<p>这篇文章解释了为什么测量事物是有价值的，但老实说，我进行测量的动力是好奇心。我只是想知道一个问题的答案。这在我有博客之前就经常做，即使现在有了博客，我也常常不把结果写出来。但即使你对与世界互动时实际发生的事情没有好奇心，你只是“想找点有用的事情做”，几乎所有东西都缺乏测量意味着寻找高ROI的测量项目很容易，至少在对世界的影响方面如此——如果你想赚钱，构建东西可能更容易变现。</p>

<h3 id="appendix-so-you-just-want-to-get-to-the-top-of-hacker-news">附录：“所以你只是想上Hacker News头条？”</h3>

<p>当我看到那些我喜欢的、登上Hacker News头条的文章，比如<a href="https://www.chrisfenton.com/" rel="noopener noreferrer">Chris Fenton的项目</a>或<a href="https://www.windytan.com/" rel="noopener noreferrer">Oona Raisanen的项目</a>时，我认为很明显他们的动机不是Hacker News或其他名声，因为他们在博客在Hacker News或其他社交媒体上走红之前很久就在做这些有趣的项目。我不认识他们，但如果非要我猜他们为什么做这些项目，主要是因为他们觉得做这类项目很有趣。</p>

<p>我显然不能说没有人以登上Hacker News头条为主要目标来从事个人项目，但作为一种动机，它与我在Hacker News上阅读的个人项目内容的最明显解释（有人在享受乐趣、有人好奇等等）如此不一致，以至于我觉得有人认为这是一个合理的假定动机有点令人费解。</p>

<h3 id="appendix-the-motivation-for-my-measurement-posts">附录：我测量文章的动力</h3>

<p><a href="https://en.wikipedia.org/wiki/The_Death_of_the_Author" rel="noopener noreferrer">从某种意义上说，我为什么决定写这些文章并不重要</a>，但如果我读到别人关于这个话题的文章，我仍然会好奇是什么促使他们写作，所以以下是我写测量文章的动力（就本列表而言，包括我整理数据但不进行直接测量的文章）。</p>

<ul>
<li><a href="https://danluu.com/car-safety/" rel="noopener noreferrer">danluu.com/car-safety</a>：我当时在考虑买车，想知道鉴于美国测试中汽车大多获得高分，我是否应该预期制造商之间在安全性方面存在显著差异。

<ul>
<li>我没有把它写进文章是因为我觉得它太琐碎了（因为数量级很明显，即使不进行计算也能看出来），但我还计算了在车祸中死亡的概率，以及使用一辆较旧的二手车与一辆较新的二手车之间的预期寿命变化。</li>
</ul></li>
<li><a href="https://danluu.com/cli-complexity/" rel="noopener noreferrer">danluu.com/cli-complexity</a>：当我看到Gary Berhardt展示如何计算<code>ls</code>单字母命令行选项数量时，我产生了这个想法，这让我想知道这是不是近期的变化。</li>
<li><a href="https://danluu.com/overwatch-gender/" rel="noopener noreferrer">danluu.com/overwatch-gender</a>：我当时刚看到两个巨大的Reddit帖子在争论女性在线游戏中受到的待遇是否存在性别偏见，我觉得我获取相关数据的时间会比人们在那些帖子中写评论的时间短。</li>
<li><a href="https://danluu.com/input-lag/" rel="noopener noreferrer">danluu.com/input-lag</a>：我想知道我是否应该相信我的感觉，即我使用的现代计算机比我以前用过的旧设备延迟高得多。</li>
<li><a href="https://danluu.com/keyboard-latency/" rel="noopener noreferrer">danluu.com/keyboard-latency</a>：我想知道有多少延迟来自键盘（显示延迟已经由<a href="https://blurbusters.com" rel="noopener noreferrer">https://blurbusters.com</a>进行了很好的测试）。</li>
<li><a href="https://danluu.com/bad-decisions/" rel="noopener noreferrer">danluu.com/bad-decisions</a>：我看到理性社区有人为糟糕的棒球执教决策辩护，说它们不是什么大事，因为它们一年可能只让你输掉四场比赛，这不算什么，我想知道糟糕的执教决策到底有多大影响。</li>
<li><a href="https://danluu.com/android-updates/" rel="noopener noreferrer">danluu.com/android-updates</a>：我好奇由于大多数Android手机无法更新，有多少不安全的Android设备在外面。</li>
<li><a href="https://danluu.com/filesystem-errors/" rel="noopener noreferrer">danluu.com/filesystem-errors</a>：我好奇文件系统在解决2005年一篇论文发现的数据损坏错误方面改进了多少。</li>
<li><a href="https://danluu.com/term-latency/" rel="noopener noreferrer">danluu.com/term-latency</a>：我觉得终端基准测试都在测量一些基本上与用户体验无关的东西（吞吐量），我想知道如果有人测量可能更重要的东西会是什么样子；我也想知道我感觉iTerm2很慢是真实的还是我的想象。</li>
<li><a href="https://danluu.com/keyboard-v-mouse/" rel="noopener noreferrer">danluu.com/keyboard-v-mouse</a>：关于键盘与鼠标生产力最被广泛引用的来源明显是虚假的，并且被陈述得极其自信；我想看看非虚假的测试是否会得出相同或不同的结果。</li>
<li><a href="https://danluu.com/web-bloat/" rel="noopener noreferrer">danluu.com/web-bloat</a>：我在美国进行了一次公路旅行，在那里网络基本上无法使用，我想量化在没有非常快速的互联网访问时网络的不可用性。</li>
<li><a href="https://danluu.com/bimodal-compensation/" rel="noopener noreferrer">danluu.com/bimodal-compensation</a>：我好奇我们是否正在看到编程领域中层工作的空心化，就像我们在法律工作中看到的那样。</li>
<li><a href="https://danluu.com/yegge-predictions/" rel="noopener noreferrer">danluu.com/yegge-predictions</a>：我印象中Steve Yegge对科技未来的预测异常准确，我想看看我的印象是否正确。</li>
<li><a href="https://danluu.com/postmortem-lessons/" rel="noopener noreferrer">danluu.com/postmortem-lessons</a>：我想看看关于事后分析原因有什么数据，看看我是否能改变我的运作方式并变得更有效率。</li>
<li><a href="https://danluu.com/boring-languages/" rel="noopener noreferrer">danluu.com/boring-languages</a>：我好奇我使用的软件中有多少是用无聊的、古老的语言编写的。</li>
<li><a href="https://danluu.com/blog-ads/" rel="noopener noreferrer">danluu.com/blog-ads</a>：我想知道如果我想让博客变现，我能赚多少钱。</li>
<li><a href="https://danluu.com/everything-is-broken/" rel="noopener noreferrer">danluu.com/everything-is-broken</a>：我想看看我遇到这么多软件错误的印象是否正确。许多人告诉我，人们经常遇到很多软件错误的想法是选择性记忆造成的错觉，我想知道这对我来说是否属实。</li>
<li><a href="https://danluu.com/integer-overflow/" rel="noopener noreferrer">danluu.com/integer-overflow</a>：我曾与一位语言设计师讨论，他确信整数溢出检查太昂贵而不能做，理由显然站不住脚（因为如果你做100%是整数操作的基准测试，它确实昂贵），我想看看我快速心算的开销估算是否数量级正确。</li>
<li><a href="https://danluu.com/octopress-speedup/" rel="noopener noreferrer">danluu.com/octopress-speedup</a>：在观看了Dan Espeset的演讲后，我想知道是否有我可以对当时的Octopress网站进行的简单优化。</li>
<li><a href="https://danluu.com/broken-builds/" rel="noopener noreferrer">danluu.com/broken-builds</a>：我与某人进行了一系列讨论，他声称他们的项目有非常好的构建正常运行时间，尽管它经常中断；我想知道他的说法相对于其他类似项目是否正确。</li>
<li><a href="https://danluu.com/empirical-pl/" rel="noopener noreferrer">danluu.com/empirical-pl</a>：我想知道有哪些研究支持那些声称有坚实经验证据证明“花哨”类型系统优越性的人。</li>
<li><a href="https://danluu.com/2choices-eviction/" rel="noopener noreferrer">danluu.com/2choices-eviction</a>：我好奇如果将“两次随机选择”应用于缓存淘汰会发生什么。</li>
<li><a href="https://danluu.com/gender-gap/" rel="noopener noreferrer">danluu.com/gender-gap</a>：我想验证一篇声称科技领域薪资没有性别差距的文章中的说法。</li>
<li><a href="https://danluu.com/3c-conflict/" rel="noopener noreferrer">danluu.com/3c-conflict</a>：我想创建一个简单的例子来说明对齐对内存延迟的影响。</li>
</ul>

<p>顺便说一句，写下这个列表让我意识到我脑中关于我何时以及如何开始认真对待数据的叙事一定是错误的。我以为这是我当前工作带来的东西，但显然不可能，因为我当前工作之前的相当一部分文章都是关于数据分析和/或测量事物的（我甚至没有列出一些我阅读论文并查看其中数据的数据驱动文章）。看到上面的列表后，我意识到我做上述项目不仅远在我有这份工作之前，甚至远在我有这个博客之前。</p>

<h3 id="appendix-why-you-can-t-trust-some-reviews">附录：为什么你不能相信某些评测</h3>

<p>做好的测量既会增加也会减少其影响力，这一点的原因在于，大多数发布的测量效果并不好。这增加了理解如何做好测量以及进行良好测量的个人价值，但它削弱了对其他人的影响，因为人们通常不理解什么使测量无效，并且没有好的算法来决定信任哪些测量。</p>

<p>发布的测量/评测存在问题的原因多种多样。评测的一个主要问题是，在某些行业中，评测者严重依赖制造商提供评测样品。</p>

<p>汽车评测是其中最极端的例子之一。《消费者报告》是唯一独立采购评测车辆的主要评测机构，这常常导致他们与其他评测机构意见相左，因为他们会尝试购买大多数人购买的配置版本，而这通常与制造商提供给评测者的配置版本有很大不同，并且《消费者报告》通常能避免评测那些被特意挑选或调校过的不具代表性的车辆。有几次，《消费者报告》的评测员（他们也购买汽车）表示，他们认为有人意识到他们为《消费者报告》工作，然后说他们需要把车留宿一晚才能给他们刚刚购买的车；当这种情况发生时，评测员会放弃购买。</p>

<p>汽车之间存在相当大的个体差异，评测者得到的车往往是那些被挑选出来避免外观问题（油漆问题、面板间隙等）并且检查过更严重问题的车辆。此外，汽车的软件和固件可以被微调（例如，众所周知，宝马的评测车有引擎“调校”，如果你以类似方式改装你的车，会使保修失效）。</p>

<p>另外，因为《消费者报告》不是从制造商那里获取评测车，他们不必手下留情，可以写出非常负面的评测，这在汽车杂志上很少见，在汽车YouTube博主那里也不常见，你通常需要字里行间去解读才能得到诚实的评测，因为明确提到汽车负面内容的评测可能意味着失去访问权限（那个自称“savagegeese”的YouTube博主曾提到在给出诚实评测后难以获得一些公司的车辆访问权限）。</p>

<p>相机镜头是另一个有文献记录评测者会拿到异常好的样品的领域。镜头之间的个体差异巨大，因此供应商会挑选好的样品借给评测者。在许多情况下（例如任何FE卡口的ZA蔡司镜头或RX-1上的蔡司镜头），根据人们需要尝试和退回多少镜头才能得到一个好样品来看，似乎中等质量的镜头存在明显的制造缺陷，并且平均而言，可能只有十分之一的镜头没有明显缺陷（如果只有少数几个镜头有问题并被连续退回，也可能出现这种情况，但很少有摄影师真正检查他们的镜头是否存在制造差异问题）。由于获取大量镜头的成本很高，镜头个体差异的程度在<a href="https://www.lensrentals.com/" rel="noopener noreferrer">镜头租赁</a>开始测量之前一直未被量化；他们发现不同制造商可能有非常不同水平的个体差异，我希望这能对那些目前销售大量坏镜头却挑选好的样品给评测者的镜头制造商施加压力。</p>

<p>硬盘驱动器是另一个有文献记录评测样品不具代表性的领域。Extreme Tech曾多次报道，ADATA、Crucial和Western Digital向评测者提供的SSD评测样品与消费者购买的不同。我觉得这个案例有趣的一点是Extreme Tech说：</p>

<blockquote>
<p>同意评测制造商的产品是各方信任的延伸。提供样品的制造商相信评测将是高质量的、彻底的和客观的。评测者则相信制造商提供的样品准确地反映了最终产品的性能、功耗和整体设计。当读者来阅读评测时，他们相信相关的评测者确实测试了硬件，并且发布的任何基准测试都是公平运行的。</p>
</blockquote>

<p>这听起来像是评测者的工作是接受供应商交给他们的信任，然后运行好的基准测试，免除了评测者获取代表性设备并确保其代表性的责任。这让我想起SRE的座右铭，“希望不是一种策略”。信任供应商不是一种策略。我们知道供应商会为了在基准测试中表现更好而说谎和作弊。说这是供应商撒谎或作弊的过错可以推卸责任，但这不会导致评测变得准确或对消费者有用。</p>

<p>虽然我们只讨论了几个有已发表证据表明评测不可信、因为被公司损害的特定领域，但这并不特定于那些行业。作为消费者，我们应该预料到任何不是由可信的、独立的机构进行的、购买自己评测样品的评测都是被损害的，并且不代表中等消费者的体验。</p>

<p>评测的另一个问题是，大多数在搜索中排名靠前的在线评测实际上只是SEO联盟农场。</p>

<p>一个更普遍的问题是，评测也受到与未评测物品完全相同的问题影响：人们通常无法分辨哪些评测实际上是好的，哪些不是，因此评测网站是基于评测质量以外的因素被选择的。一个典型的例子是Wirecutter，它在科技人士中如此受欢迎，以至于注意到旧金山那么多科技公寓都摆放着Wirecutter推荐的相同物品已经是个老掉牙的笑话。对于没在旧金山生活过的人，你可以通过阅读<a href="https://www.reddit.com/r/fatFIRE/comments/iioq01/impossible_to_avoid_lifestyle_inflation_with/" rel="noopener noreferrer">这篇关于如何“不可能”不买Wirecutter推荐物品的帖子的评论</a>来一窥这种心态，评论中充满了人们向发帖人保证，由于其时间的高价值，不做其他任何事情将是不负责任的。</p>

<p>我觉得有趣的是，如果你认真对待基准测试（在任何领域），只需阅读中位数Wirecutter评测的方法论，甚至无需尝试评测中的物品，你就能看出方法论很差，并且他们通常会选择平庸、有时甚至是同级最差的物品。对此的彻底探讨真的值得专门写一篇文章，但我在这里举一个评测不良物品的例子：在<a href="https://benkuhn.net/vc" rel="noopener noreferrer">https://benkuhn.net/vc</a>中，Ben Kuhn研究了如何创造良好的视频通话体验，其中包括尝试各种麦克风和网络摄像头。自然，Ben尝试了Wirecutter推荐的麦克风和网络摄像头。网络摄像头相当差，不比使用一台古老的2014年iMac或他的2020年MacBook的摄像头好（而且在我看来，实际上差得多；稍后详述）。麦克风也大致相当于使用他笔记本电脑的内置麦克风。</p>

<p>我有很多使用Wirecutter推荐网络摄像头的经验，因为很多人都有它，并且它差得惊人且很独特。Ben指出，如果你看一张静态图片，当他使用的那个房子里，白平衡很糟糕，如果你和其他使用过这款摄像头的人交流，这是个常见问题。但我觉得更严重的问题是，在许多条件下（我认为大多数情况下，因为经常见到），网络摄像头会定期重新对焦，导致整个视频反复失焦然后再对焦（另一个问题是它经常对焦在错误的东西上，但这不那么常见，我并非在每个使用Wirecutter推荐网络摄像头与我通话的人那里都看到这个）。实际上，就在昨天，我和一个朋友通话，他用的不是我通常看到的配置，而是那个平庸但完全可接受的MacBook摄像头。他的视频每10-30秒就会失焦一次，所以我问他是不是用的Wirecutter推荐的网络摄像头，他当然是，因为除了这个，科技人士还会买哪个有同样问题的摄像头？</p>

<p>这种评测质量水平在Wirecutter评测中相当典型，并且它们似乎是科技人士中最受尊敬和使用最广泛的评测网站。</p>

<h3 id="appendix-capitalism">附录：资本主义</h3>

<p>我上高中时，有一群早期的“愤世嫉俗者”会做一些事情，比如读《钟形曲线》并把它的论点向任何愿意听的人宣扬。</p>

<p>他们最喜欢的话题之一是自由市场如何自然会导致生产好产品的公司脱颖而出，而生产差产品的公司会消失，从而使事物通常安全、物有所值等等。我仍然经常看到科技行业的人持这种观点，包括那些用Wirecutter推荐物品填满公寓的人。我觉得人们一边争辩市场通常会导致产品变好，一边自己却购买价格虚高的垃圾，这种并列充满了讽刺意味。公平地说，并不全是价格虚高的垃圾。有些是价格虚高的平庸之作，有些实际上还不错；只是它和你在亚马逊上不读第三方评测而随意购买的东西没有太大区别。</p>

<p>关于相关讨论，<a href="https://danluu.com/tech-discrimination/" rel="noopener noreferrer">请参见这篇关于人们一边声称市场消除了歧视，一边自己却在进行歧视的文章</a>。</p>

<h3 id="appendix-other-examples-of-the-impact-of-measurement-or-lack-thereof">附录：测量（或缺乏测量）影响的其他例子</h3>

<ul>
<li>电子稳定控制系统

<ul>
<li>丰田RAV4：<a href="https://www.youtube.com/watch?v=j3qrCNR4U9A" rel="noopener noreferrer">评测前</a> <a href="https://www.youtube.com/watch?v=VtQ24W_lamY" rel="noopener noreferrer">评测后</a></li>
<li>丰田Hilux <a href="https://www.youtube.com/watch?v=xoHbn8-ROiQ" rel="noopener noreferrer">评测前</a> <a href="https://www.youtube.com/watch?v=y2QSogJj3ec" rel="noopener noreferrer">评测后</a></li>
<li>日产Rogue：在《消费者报告》发现稳定控制系统问题后进行了重大改进。</li>
<li>Jeep大切诺基：<a href="https://www.youtube.com/watch?v=zaYFLb8WMGM" rel="noopener noreferrer">评测前</a> <a href="https://www.youtube.com/watch?v=_xFPdfcNmVc" rel="noopener noreferrer">评测后</a></li>
</ul></li>
<li>工作上的一些无聊事：一年前，我写了<a href="https://danluu.com/metrics-analytics/" rel="noopener noreferrer">这两篇</a><a href="https://danluu.com/tracing-analytics/" rel="noopener noreferrer">文章</a>，关于工作中的可观测性基础设施。当时，这项工作已经带来了8位数的成本节省，现在这个数字已经远超9位数。这可能值得单独写一篇文章，但大部分工作在有人真正能观察到发生什么之后就变得直截了当了。

<ul>
<li>相关地：在看到几个问题影响生产服务后，我写了一个小型（5千行代码）解析器，用于解析各种主机级日志中出现的每一行，作为检查那些未在我们的指标中捕获的问题是否被记录。这发现了那些未使用自动化解决方案来捕获和补救主机级问题的集群中的重大问题；对于某些集群，超过90%的主机正在主动损坏数据或存在严重的性能问题。这导致创建了一个新团队来处理这类问题。</li>
</ul></li>
<li>轮胎

<ul>
<li>几乎所有制造商（米其林除外）随着轮胎磨损，其湿滑路面、雪地和冰面性能都会严重下降

<ul>
<li>Jason Fenske说其中的技术原因之一是（还有其他原因）提高抓地力的<a href="https://en.wikipedia.org/wiki/Siping_(rubber)" rel="noopener noreferrer">刀槽花纹</a>通常不会被切到全深度，因为这样做会显著增加制造成本，因为切割刀槽的设备需要更强且磨损更快。</li>
<li>一个非技术原因是许多发布的轮胎测试都是在全新轮胎上进行的，因此轮胎制造商通过仅制作部分深度的刀槽花纹就能获得几乎相同的营销基准值。</li>
</ul></li>
<li>随着Tire Rack知名度的提高，一些轮胎制造商使其刀槽花纹更具多向性，以改善转弯时的操控性，而不是主要或仅垂直于行驶方向切割花纹（这主要只对加速和制动有帮助，《消费者报告》的雪地和冰面评分分别基于在雪地上直线加速和在冰面上直线制动，而Tire Rack的冬季测试评分则强调全方位的雪地操控性）</li>
<li>测量影响如何受限制的一个例子：米其林冬季轮胎项目经理Farrell Scott表示，在设计米其林X-ICE Xi3的后继产品时，主要设计标准之一是改变轮胎的外观，因为米其林发现客户认为X-ICE Xi3尽管与普利司通Blizzak WS80一起被认为是最佳的全能冬季轮胎（在某些方面略好，某些方面略差），但潜在客户通常会选择其他轮胎，因为它们看起来更像人们心目中“有侵略性”胎块的冬季轮胎（著名的诺记Hakkapeliitta轮胎系列在这方面做得更好得多）。他们也改了名字；新轮胎没有按数字递增，而是命名为米其林X-ICE SNOW，以强调该轮胎适用于雪地和冰面。</li>
<li>尽管有些消费者确实阅读评测，但许多（可能大多数）消费者并不阅读！</li>
</ul></li>
<li>用于实时视频的HDMI转USB转换器

<ul>
<li>如果你阅读<a href="https://amzn.to/3gBdiRE" rel="noopener noreferrer">Camlink 4k</a>的文档，他们会指出该设备在Windows上应使用批量传输，在Mac上应使用等时传输（如果使用他们的软件，它会自动进行此调整）

<ul>
<li>Fabian Giesen告诉我，这可能是出于和他同事测试特定USB3设备在Windows上只有5个测试的芯片组中有1个正确支持等时传输（其余会蓝屏或死机）相同的原因</li>
</ul></li>
<li>我尝试了各种便宜的HDMI转USB转换器作为Camlink 4k的替代品，至今尚未找到一款便宜且能在各种计算机上正常工作的。它们通常至少在我能访问的一台计算机上、使用至少一款我想用的软件时能工作，但在某些情况下根本无法工作或提供非常扭曲的视频。也许有人应该发布HDMI转USB转换器质量的基准测试！</li>
</ul></li>
<li>HDMI转VGA转换器

<ul>
<li>许多这类转换器会变得非常热，然后过热，在15分钟到2小时内停止工作。有些甚至摸起来都不热。祝你好运，弄清楚哪些能用！</li>
</ul></li>
<li>水过滤

<ul>
<li>Brita声称他们的“长效”滤芯能去除铅。然而，两位不同的亚马逊评测者表示，他们在过滤前后测量了受污染水中的铅含量，发现铅含量并未降低。</li>
<li>以前水流经“长效”滤芯的速度非常慢，这是购买该滤芯用户常见的投诉。现在一些（或许是所有）“长效”滤芯过滤水的速度快得多，但过滤效果未达到Brita声称的水平。</li>
</ul></li>
<li>体育裁判

<ul>
<li><a href="https://twitter.com/umpireauditor/status/1515648000076312579" rel="noopener noreferrer">棒球裁判以做出正确判罚能力差而闻名，我们拥有做出近乎完美判罚的技术已有数十年了</a>，但许多人争辩说，由人类做出不正确判罚是“比赛的一部分”，如果计算机参与判罚，比赛就不会那么真实了。</li>
<li>一些体育项目已部分屈服于在可能时做出正确裁决的压力，例如在橄榄球中，NFL教练在1999年被允许基于录像资料每场比赛挑战两次判罚（如果前两次挑战成功，2004年开始为三次），模仿了小众的USFL在1985年创建的系统。</li>
</ul></li>
<li>储物容器

<ul>
<li>乐柏美储物容器（Rougneck和Toughneck）过去以其质量和耐用性而闻名。当然，从短期来看，减少使用材料和容器强度更有价值，所以另一家公司收购了该品牌并继续使用它，生产外观相似但以堆叠容器时会压弯而闻名的容器，而这正是可嵌套/可堆叠容器的全部意义。我没有看到有人真正对储物容器的承重能力进行过认真基准测试，所以通常你无法知道这是否会发生在你身上。</li>
</ul></li>
<li>扬声器振动隔离方案

<ul>
<li><a href="https://ethanwiner.com/speaker_isolation.htm" rel="noopener noreferrer">Ethan Winer得出结论，这些是发烧友的安慰剂</a>

<br></li>
</ul></li>
</ul>

<p><small>感谢Fabian Giesen、Ben Kuhn、Yuri Vishnevsky、@chordowl、Seth Newman、Justin Blank、Per Vognsen、John Hergenroeder、Pam Wolf、Ivan Echevarria和Jamie Brandon的评论/修正/讨论。</small></p><p><em>由 mimo-v2.5 模型翻译，花费 17031 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/why-benchmark/</link>
      <guid isPermaLink="false">https://danluu.com/why-benchmark/</guid>
      <pubDate>Fri, 27 Aug 2021 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>反对本质复杂性与偶然复杂性</title>
      <description>[AI 摘要] 该文批判布鲁克斯《没有银弹》中关于编程本质复杂性占主导、技术改进空间有限的观点，认为偶然复杂性占比巨大且可通过工具与实践持续削减，布鲁克斯严重低估了技术进步潜力。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 该文批判布鲁克斯《没有银弹》中关于编程本质复杂性占主导、技术改进空间有限的观点，认为偶然复杂性占比巨大且可通过工具与实践持续削减，布鲁克斯严重低估了技术进步潜力。</div><p>在经典的1986年文章<a href="http://worrydream.com/refs/Brooks-NoSilverBullet.pdf" rel="noopener noreferrer">《没有银弹》</a>中，弗雷德·布鲁克斯认为，在某种意义上，几乎没有太多方法可以提高程序员生产力。他的推理是，编程任务包含一个核心的<strong>本质/概念</strong>复杂性<sup id="fnref:C"><a href="#fn:C" rel="noopener noreferrer">1</a></sup>，这种复杂性从根本上无法通过任何潜在的技术进步（如语言或工具）来攻克。然后他运用了<a href="https://en.wikipedia.org/wiki/Amdahl%27s_law" rel="noopener noreferrer">阿姆达尔定律</a>的论证，指出由于复杂性的1/X是本质的，因此通过技术改进永远不可能获得超过X倍的提升。</p>

<p>在文章接近尾声时，布鲁克斯声称编程中至少一半（大部分）的复杂性是本质的，将所有技术创新组合起来的潜在改进上限限制在最多2倍<sup id="fnref:T"><a href="#fn:T" rel="noopener noreferrer">2</a></sup>：</p>

<blockquote>
<p>所有针对软件过程中偶然问题的技术攻击，都从根本上受到生产力方程式的限制：</p>
<p>任务时间 = Σ（频率<sub>i</sub> * 时间<sub>i</sub>）</p>
<p>如果我相信，如我所信，任务的概念组成部分现在占据了大部分时间，那么无论对仅仅是概念表达的任务组成部分进行多少活动，都无法带来大的生产力提升。</p>
</blockquote>

<p>布鲁克斯陈述了程序员生产力可以提升的上限。但是，在实践中，要正确陈述这个上限，就必须能够构想出那些由于当前技术摩擦过大而无人会合理尝试解决的问题。</p>

<p>在无法预测未来的情况下，这是无法估计的。如果我们知道未来，可能会发现程序员可以高效使用的计算能力或存储资源存在某种实际限制，从而限定了程序员可用的资源，但要界定偶然复杂性的程度，仍然需要正确推断程序员将如何能够使用比现在多无数倍的资源，这如此困难，我们不妨称之为不可能。</p>

<p>此外，对于每一类可能存在的工具，人们必须有效地预见所有可能的创新。布鲁克斯的策略是查看现有的工具类别，并指出每一类要么无效，要么虽有效但已发展到头。这不仅是错误的，因为它低估了当时尚未存在、<a href="https://danluu.com/butler-lampson-1999/" rel="noopener noreferrer">尚未有效</a>或他不熟悉的工具类别（例如，他否定了形式化方法，却没想到提及fuzzing工具、不完全形式化验证代码的静态分析工具、valgrind之类的工具等），而且还因为布鲁克斯认为每一个有重大改进的工具类别都已发展到头，但事实证明它们都没有。例如，布鲁克斯认为编程语言已基本发展到头，而就在“脚本语言”兴起之前，以及在GC语言接管绝大多数编程之前<sup id="fnref:L"><a href="#fn:L" rel="noopener noreferrer">3</a></sup>。尽管你偶尔会听到类似的说法，但没有多少人会主动用C语言编写一个web应用程序，因为现代语言带来的收益不可能只是相对于现代语言的2倍。</p>

<p>布鲁克斯否定的另一个领域是人工智能，他说“用于语音识别的技术与用于图像识别的技术似乎没什么共同点，而且两者都与专家系统中使用的技术不同”。然而，这当然不再正确——神经网络在图像识别和语音识别方面都非常有效。它们作为编程工具是否有效尚待确定，但布鲁克斯反对人工智能论证的一个关键点已被证伪，认为一个显著改进的GPT-2可能会给程序员带来显著的生产力提升并非牵强。当然，布鲁克斯无法预见神经网络在语音和图像识别方面变得有效，这是合理的，但正是这一点使得布鲁克斯否定人工智能以及计算机科学其他所有领域的所有未来进步变得不合理。</p>

<p>布鲁克斯还低估了实践和支持实践的工具所带来的收益。仅举一例，看看老派编程大师的主张，我们有<a href="https://twitter.com/danluu/status/885214004649615360" rel="noopener noreferrer">肯·汤普森认为语言安全性毫无用处</a>，bug的发生是因为人们编写了脆弱的代码，如果他们不想有bug就不应该这样做；还有<a href="https://mastodon.social/@danluu/110213164472670607" rel="noopener noreferrer">杰米·扎温斯基认为，在紧迫的期限下，自动化测试是浪费时间，“第一次就做对有很多好处”而无需测试</a>。布鲁克斯承认测试的重要性，但他唯一提到的可能改进是能让初学者测试更容易的专家系统。如果你看看中等规模现代软件项目的复杂性，它们已经远远超出了80年代所见过的任何软件项目。如果你真正思考一下使用老派正确性实践来处理这些项目意味着什么，我认为从那些实践到现代实践的加速对于一个典型团队来说是无限的，因为大多数使用那些实践的团队如果面临许多大公司已独立解决的问题（例如，生产一个具有某种声明式SLO的分布式数据库），根本无法产出一个可工作的产品。有人可能会质疑无限加速的说法，但任何参与过认真对待正确性的复杂项目的人都会使用<a href="https://danluu.com/testing/" rel="noopener noreferrer">能带来巨大开发加速的工具和技术</a>，轻松超过相对于80年代实践的2倍提升，这种可能性似乎布鲁克斯从未想到，因为他认为由于测试涉及的本质复杂性，重大的测试改进是不可能的。</p>

<p>另一个基本的工具/实践例子是版本控制。一个支持多文件提交、分支、只要开发人员不修改同一行就能正常工作的自动合并等功能的版本控制系统，是一项相当现代的发明。<a href="https://danluu.com/microsoft-culture/" rel="noopener noreferrer">在90年代，微软处于软件开发的前沿，他们直到Win2k之后才设法获得一个能支持他们所需仓库规模（Win2k开发的3000万行代码）并支持分支的版本控制系统</a>。分支是通过简单地复制整个源代码树，然后手动尝试合并源代码树的副本来模拟的。更改源代码树需要特殊批准，由于手动合并的痛苦，整个Win2k团队（5000人，包括1400名开发人员和1700名测试人员）在顺利的情况下每天只能合并100个更改（在构建团队因花时间修复构建中断而停滞的糟糕日子里，则是0）。这是布鲁克斯写作十年后的事情，仅仅通过更好的版本控制工具、测试工具和实践、机器加速允许更快的测试等，就仍然存在着轻松一个数量级的加速机会。请注意，除了没有意识到版本控制和测试工具后来会带来巨大的生产力提升外，布鲁克斯还声称硬件加速不会显著提高开发人员的生产力，尽管硬件速度被认为是Win2k开发速度的一个主要限制因素。布鲁克斯无法想象有人会构建像Win2k这样复杂、真正能利用更快硬件的项目。当然，使用布鲁克斯时代的工具和实践，实际上不可能构建像Win2k这样复杂的项目，但工具和实践进步如此之快，以至于十年后就成为可能，即使由于“石器时代”的工具和实践，开发速度与我们今天习惯的相比慢如蜗牛。</p>

<p>从上面另一个子部分来看，布鲁克斯没有将CI/CD列为潜在的生产力改进，因为他甚至无法想象会有能够支持现代构建实践的工具。1995年写作时，布鲁克斯提到微软有人告诉他他们每晚构建。对此，布鲁克斯说启用每天（至少）一次构建可能工作量太大，并指出贝尔北方研究公司（Bell Northern Research）非常合理地每周构建一次。布鲁克斯写完后不久，谷歌成立了，谷歌的工程师甚至无法想象满足于微软那样的设置，更不用说每周构建了。他们不得不编写大量定制软件，将谷歌规模的单体仓库纳入今天被认为是现代实践的水平，但他们做到了。我工作过的一家成立于1995年的初创公司也建立了自己的CI基础设施，支持从HEAD进行持续合并和构建，因为任何着眼于可能实现而不是认为所有可能实现的事情都已完成的人都会这么做。对于大型项目，仅仅拥有CI/CD并保持干净的构建，相对于每周构建，应该轻松实现2倍的生产力提升，这比布鲁克斯声称的一半复杂性是本质所允许的更大。幸运的是，谷歌、我工作过的那家初创公司以及许多其他地方的工程师们并不相信不可能获得2倍的提升，并且实际上构建了能够带来巨大生产力提升的工具。</p>

<p>从某种意义上说，审视《没有银弹》非常类似于我们<a href="https://danluu.com/cli-complexity/#maven" rel="noopener noreferrer">查看Unix并发现Unix大师们说我们应该像70年代那样编写软件</a>，以及<a href="https://twitter.com/danluu/status/885214004649615360" rel="noopener noreferrer">他们发明的语言和任何语言一样安全</a>。早在计算机发明之前，长辈们就一直告诉下一代，他们已经做完了所有能做的事情，下一代将无法取得更多成就。在计算机时代，我们在编程之外也看到了无数类似的预测，比如克利夫·斯托尔现在臭名昭著的预测，即互联网不会改变任何东西：</p>

<blockquote>
<p>有远见者看到了一个远程办公工作者、交互式图书馆和多媒体课堂的未来。他们谈论电子城镇会议和虚拟社区。商业和商务将从办公室和购物中心转移到网络和调制解调器上。数字网络的自由将使政府更加民主。</p>
<p>胡说八道。我们的计算机专家是不是缺乏常识？事实是，没有在线数据库能取代你的日报……那电子出版呢？试试在光盘上读一本书。最好的情况，这也是一种不愉快的苦差事：笨重电脑的近视光芒取代了书本友好的页面。而且你不能把那台笔记本电脑带到海滩上。然而，麻省理工学院媒体实验室的主任尼古拉斯·内格罗蓬特预言，我们很快就会直接通过互联网购买书籍和报纸。呃，当然……然后是网络业务。我们被承诺了即时目录购物——只需点击即可获得超值优惠。我们将通过网络订购机票、进行餐厅预订并协商销售合同。商店将过时。那么为什么我本地的购物中心一个下午的营业额就超过了整个互联网一个月的处理量？</p>
</blockquote>

<p>如果你稍微搜索替换一下，斯托尔说的和布鲁克斯一样。当然，过去的技术改变了事物，但我无法想象新技术会如何改变事物，所以它们根本就不会改变。</p>

<p>即使对编程一无所知，我们也能看到这类论点在历史上从未站得住脚，并且相当有信心长辈们这一次并非正确。</p>

<p>布鲁克斯在成为实践者之后很长一段时间里一直在写作软件，但在1964年进入学术界后，并没有费心去跟进工业界发生的事情，这一点从我们审视的1986年文章中已经很明显，但如果你看他2010年的书《设计之设计》，这一点就更加明显，他在其中依赖于<a href="https://www.patreon.com/posts/46629220" rel="noopener noreferrer">他早期文章和书籍中使用的相同例子</a>，其大部分新材料来自他建造的一所房子。我们已经看到<a href="https://danluu.com/cocktail-ideas/" rel="noopener noreferrer">程序员试图将他们的知识推广到土木工程时，通常会说出任何土木工程二年级学生都能观察到是错误的愚蠢言论</a>，结果证明，试图从房屋建造技术中提炼出关于软件工程设计技术的深刻见解同样不可行，但既然布鲁克斯没有跟上行业步伐，这就是他所能提供的。虽然存在超越时代和行业的永恒见解，但布鲁克斯有非常具体的建议，例如像<a href="https://danluu.com/cocktail-ideas/" rel="noopener noreferrer">鸡尾酒会</a>式的外科手术团队那样管理软件团队，这些想法源于思考如何改进布鲁克斯在50年代IBM看到的开发实践。但事实证明，工业界已经远远超越了IBM在50年代的软件实践，那些相对于IBM在50年代实践有所改进的想法在70年后并非特别有用。</p>

<p>回到本文的主要话题，结合事后诸葛亮来看他关于偶然复杂性的具体论述，我们可以看到布鲁克斯1986年关于“我们基本上已经获得了高级语言所能提供的所有生产力提升”的说法，与1955年汇编语言程序员说汇编语言和任何语言一样好<sup id="fnref:A"><a href="#fn:A" rel="noopener noreferrer">4</a></sup>的说法没有太大区别，而他对其他类别的主张也类似。这些主张主要证明的是缺乏想象力。当布鲁克斯提到概念复杂性时，他指的是使用1986年布鲁克斯所熟悉的（在他认为是编程问题的）问题上的概念构建块的复杂性。没有任何理由认为布鲁克斯1986年对编程的理解是根本性的，就像我们不应该认为1955年汇编程序员的想法是根本性的一样。人们经常嘲笑那句传言的“640K应该对任何人都足够”的话，但布鲁克斯说在所有潜在的生产力改进类别中，我们已经完成了大部分可能的事情，这是类似的，而且并非传言！</p>

<p>如果我们展望未来，可能是偶然的复杂性比例实际上是无限的。有人可能会争辩说，如果我们审视现在，这些术语并非毫无意义。但是，尽管这会因领域而异，我个人从未参与过一个不是完全被偶然复杂性主导的非平凡问题，这使得本质复杂性的概念在我参与过的任何值得讨论的问题上都毫无意义。</p>

<h3 id="appendix-concrete-problems">附录：具体问题</h3>

<p>让我们看看我最近在工作中做的几件事如何印证这个本质复杂性的说法：</p>

<ul>
<li>从一堆主机scp以读取和下载日志，然后解析日志以了解问题的范围</li>
<li>查询我雇主所有软件每个实例两年的指标数据，针对某些类别的软件，然后生成各种图表，以了解我对我们的软件正在做什么以及如何使用计算机资源的一些疑问</li>
</ul>

<h4 id="logs">日志</h4>

<p>如果我们把这个任务分解一下，我们有：</p>

<ul>
<li>从几十万台机器scp日志到本地机器

<ul>
<li>用一个Python脚本来完成这个，以获得比pssh/parallel-scp更好的并行性和更健壮的错误处理
<ul>
<li>编写脚本大约1分钟</li>
</ul></li></ul></li>
<li>在日志下载时做其他工作
<ul>
<li>解析下载的日志（几TB）
<ul>
<li>用一个Rust脚本来完成这个，写几分钟（这里用Rust而不是Python是出于性能原因——仅仅是打开日志并逐行扫描的惯用Python实现，在我不想将任务分发到多台机器时，就已经比我想要的慢了）</li>
</ul></li></ul></li>
</ul>

<p>在1986年，也许我会用telnet或ftp代替scp。现代脚本语言还不存在（perl创建于1987年，而perl5，第一个有人认为是现代版本的，于1994年发布），因此编写一个具有并行性和“足够好”的错误处理的代码，会比今天花费多一个数量级以上的时间。事实上，我认为仅仅在管理连接池的同时实现半体面的错误处理，就很容易比我完成这个整个任务（不包括在后台下载日志的时间）所花费的时间长一个数量级。</p>

<p>接下来是解析日志。比较“1TB”这样的绝对数字是不公平的，所以我们只称之为“足够让我们关注性能”（我们将在指标示例中更详细地讨论规模）。今天，我们可以选择高性能语言，轻松编写快速、安全的代码，并利用库（例如，正则表达式库<sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">5</a></sup>）的力量，这些库使我们能够轻松编写一个快速而粗糙的脚本来解析和分类日志，将工作分配到我计算机的所有核心上（我认为Zig也能做到这一点，但我用了Rust，因为我团队有相当数量的Rust程序员）。</p>

<p>在1986年，不会有可比的语言，但更重要的是，我无法轻松找到、下载和编译相应的库，而不得不手动编写所有解析代码，将一个几分钟的任务变成一个运气好也要花一个小时才能完成的任务。此外，如果我不知道如何使用库或知道可以使用库，我可以轻松地在StackOverflow上找到如何解决问题的方法，这将大大减少偶然复杂性。不用说，1986年并没有真正的相当于谷歌搜索StackOverflow解决方案的东西。</p>

<p>而且，即使在今天，这个任务，一个非常标准的程序员开发/SRE任务，在比1986年的类似任务至少快一个数量级之后，仍然几乎完全是偶然复杂性。</p>

<p>如果数据导出到我们的指标系统中，或者如果我们的集中式日志工作方式稍有不同，整个任务将是微不足道的。如果两者都不成立，但日志格式更统一，那么在获取日志后我就不需要编写任何代码；<a href="https://github.com/BurntSushi/ripgrep" rel="noopener noreferrer">rg</a>或<a href="https://github.com/ggreer/the_silver_searcher" rel="noopener noreferrer">ag</a>就足够了。如果我看一下我在任务本质概念核心上花费了多少时间，它如此之小以至于难以估计。</p>

<h4 id="query-metrics">查询指标</h4>

<p>我们真的只需要一个反例，但我认为通过一个更复杂的任务来看看布鲁克斯的论点如何扩展，更具说明性。如果你想跳过这个冗长的例子，<a href="#summary" rel="noopener noreferrer">点击此处跳到下一节</a>。</p>

<p>我们可以将我的指标查询任务视为由以下子任务组成：</p>

<ul>
<li>编写一组<a href="https://en.wikipedia.org/wiki/Presto_(SQL_query_engine)" rel="noopener noreferrer">Presto SQL</a>查询，有效地扫描大约100TB的数据，从一个数据集中（如果我<a href="https://danluu.com/metrics-analytics/" rel="noopener noreferrer">不维护只包含相关数据子集的表</a>，该数据集大约为100PB的数据）

<ul>
<li>编写第一个查询大约30秒，查询完成需要几分钟，使用大约1个CPU年的CPU时间</li>
</ul></li>
<li>编写一些ggplot代码来绘制我好奇的各种属性

<ul>
<li>不确定这花了多长时间；比查询完成的时间短，所以没有增加这个任务的总时间</li>
</ul></li>
</ul>

<p>这些任务中的第一个在今天完成起来比1986年快了多个数量级，我甚至无法在1-2个数量级内大胆猜测今天快了多少，但让我们把第一个任务分解成组成部分，以了解任务变得更容易的方面。</p>

<p>将绝对数字如100PB转换到1986年是不公平的，但仅仅是拥有一个收集和持久化综合数据的管道，类似于我作为消费软件公司正在研究的数据（关于我们软件资源使用和效率的各种数据），在1986年会被认为是荒谬的。在这里，我们看到了偶然-本质复杂性概念作为生产力改进上限的一个致命缺陷：具有太多偶然复杂性的任务甚至不会被认为是可能的。布鲁克斯所看到的偶然复杂性限制实际上是他想象力的限制，而不是根本性的。</p>

<p>布鲁克斯明确否定了增加计算能力是提高生产力的一种方式（“嗯，一个人能有效使用多少MIPS？”，后面会更多讨论），但存储和CPU能力（更不用说网络速度和RAM）是如此巨大的偶然复杂性来源，以至于它们限制了布鲁克斯能够构想出的问题空间。</p>

<p>在这个例子中，假设我们在1986年以某种方式有足够的存储来保存我们想要查询的数据。接下来的部分将是调度大约1个CPU年的资源，并让查询在几分钟内完成。与存储问题一样，这在1986年也会是荒谬的<sup id="fnref:F"><a href="#fn:F" rel="noopener noreferrer">6</a></sup>，所以我们遇到了第二个如此巨大的非本质复杂性，以至于会让1986年的人根本想不到这个问题。</p>

<p>接下来是编写查询。如果我是在为Cray-2编写并想要高效，我可能会用Cray的Fortran 77方言编写查询。我能在每个查询不到300秒内完成吗？没门；我用Scala/Scalding甚至都无法接近，我认为即使用Python/PySpark也很悬。这是我们看到收益最小的一个方面，而我们仍然明显超过一个数量级。</p>

<p>在我们处理完数据后，必须生成图表。即使使用今天的技术，我认为不使用ggplot也会在生产力上损失至少2倍。我尝试过每一个据称等效的主要绘图库（无论哪种语言），我尝试过的每个库要么在绘制我认为在ggplot中是基础的图表时存在多个阻塞性错误，要么太底层，以至于我被迫手动完成在ggplot中微不足道的事情，损失了超过2倍的生产力。在2020年，仅仅一个库的存在就在这一个步骤上为我节省了2倍。如果我们回到1986年，在<a href="https://amzn.to/3r9Mvzw" rel="noopener noreferrer">图形语法</a>概念和任何合理的实现出现之前，即使假设有一些神奇的工作站硬件能够在合理的时间内完成我所做的绘图操作（我的机器渲染图表非常慢；Cray-2根本无法在合理的时间范围内完成渲染），我在绘图上损失的时间也不可能少于两个数量级。</p>

<p>从1986年到今天，这个问题偶然复杂性减少的数量级之多，我甚至无法估计，然而这个问题仍然包含如此大比例的偶然复杂性，以至于再次难以猜测复杂性的哪一部分是本质的。写下我能想到的所有偶然复杂性将需要至少2万字，但仅仅为了提供一点复杂性的风味，让我写下几件事。</p>

<ul>
<li>SQL；这是一件表面简单<a href="https://scattered-thoughts.net/writing/select-wat-from-sql/" rel="noopener noreferrer">但实际上极其复杂</a>的事情

<ul>
<li>还有，Presto SQL</li>
</ul></li>
<li>任意Presto限制，有些来自Presto本身，有些来自我们操作Presto的具体方式以及我们使用的版本

<ul>
<li>当我以特定方式同时使用<code>numeric_histogram</code>和<code>cross join unnest</code>时，会触发一个内部Presto数据结构断言失败。因为编写会暴露该bug的查询、等待它失败然后再重写它是浪费时间，所以我有一个心算的启发式方法来猜测，对于任何同时使用这两个结构的查询，我是否会遇到该bug，并应用它来避免编写两个查询。如果启发式方法适用，我会编写一个更冗长、执行更慢的查询，而不是那个更直接的查询</li>
<li>我们按日期对数据进行分区，但当我在查询中连接表时，Presto会丢弃这一点，导致在连接长时间跨度的数据时出现非常大且昂贵的连接，尽管原则上这可以是一系列廉价的连接；如果连接大到导致我的查询失败，我本质上会编写一个小查询编译器来执行逐日查询，然后根据需要对数据进行后处理，而不是编写朴素的查询

<ul>
<li>有很多情况下，查询中的某种优化可以使查询可行，而无需跨天拆分查询（例如，如果我想将主机级别的指标数据与包含主机所在集群的表连接，这是一个跨多年数据的非常慢的连接，但我也知道哪些类型的主机在哪些集群中，这在某些情况下使我能够过滤掉主机级别指标数据中的主机，如核心计数和总内存，这可以使这个连接的较大输入足够小，从而使查询能够成功，而无需手动分区查询）</li>
</ul></li>
<li>我们有一个“快”但内存限制“低”的Presto集群和一个“慢”但内存限制“高”的集群，所以我需要在心中估算查询所需的每个节点的内存，以便将其调度到正确的集群</li>
<li>等等</li>
</ul></li>
<li>出于性能原因，我应该在Presto中计算CDF或直方图，还是留到最后让ggplot计算</li>
<li>我需要对数据进行多少降采样（如果需要的话）才能让ggplot处理，以及这可能会如何影响分析</li>
<li>任意ggplot的东西

<ul>
<li>在散点图中大约需要多少个点，我才应该停止使用<code>size = [number]</code>，而应该切换到单像素绘图，因为将点绘制为圆太慢了</li>
<li>点的最小允许不透明度是多少</li>
<li>如果我超过散点图中可以看到渐变的最大密度限制，我需要将图像制作得多大以适当降低密度（我什么时候会这样做而不是使用热图值得单独写一篇文章）</li>
<li>等等</li>
</ul></li>
<li>所有这些都涉及我用来编写和检查查询的工具，但还有一个关于在编写查询以生成有效结果时必须考虑的所有数据问题的思维模型，包括时钟偏差、Linux计费错误、指标管道问题、由于底层数据源问题导致的数据问题等。</li>
<li>等等</li>
</ul>

<p>对于Presto和ggplot，我都在脑海中隐含地记住了数百件事，以使我的查询和图表能够工作，我选择使用这些，因为它们是我知道的可用工具中开销最低的。如果有人问我必须处理的复杂性中有多少是本质的，我会说它如此之低，以至于无法估计。对于某些查询，可以说它是零——我的工作之所以必要，仅仅是因为某些任意的怪癖，没有这个怪癖就不需要工作。但即使在某些查询似乎必要的情况下，我也认为令人难以置信的是，本质复杂性可能超过我必须处理的复杂性的1%。</p>

<p>重新审视布鲁克斯关于计算机性能的观点，即使我在2020年处理由于硬件性能限制而带来的复杂性，并且希望今天有更快的计算机，但布鲁克斯在1986年否定了更快的硬件对开发者生产力几乎没有提高作用：</p>

<blockquote>
<p>个人工作站功率和内存容量的确定而快速的增加，能为软件艺术带来什么收益？嗯，一个人能有效使用多少MIPS？程序和文档的编写和编辑完全得到了当今速度的支持。编译可以得到提升，但10倍的机器速度肯定会……</p>
</blockquote>

<p>但这至少在两个层面上是错误的。首先，如果我能够使用更快的计算机，我大量的偶然复杂性会消失（如果计算机足够强大，我就不需要像Presto这样复杂的工具；我可以在我的本地计算机上运行查询）。我们现在有了快得多的计算机，但拥有更快的计算机仍然会使许多复杂的工程任务变得微不足道。正如詹姆斯·黑格（James Hague）指出的，在80年代中期，<a href="https://prog21.dadgum.com/29.html" rel="noopener noreferrer">由于性能限制，编写拼写检查器是一个严肃的工程问题</a>。</p>

<p>第二，（仅举一例）ggplot之所以存在，是因为计算机速度如此之快。从事性能工作的人们一个常见的抱怨是，当你查看工具执行的基本操作与当今硬件的速度相比时，工具X存在2到10个数量级的效率低下<sup id="fnref:O"><a href="#fn:O" rel="noopener noreferrer">7</a></sup>。但有多少程序员能够实现现代多路服务器一半的潜在性能？我猜不到千分之一，我肯定说不到百分之一。而且性能知识并非与其他知识无关——在年龄和经验相同的情况下，它与“系统”以外领域的知识呈负相关，因为花时间学习实现计算机一半潜在性能所需的深奥偶然复杂性，就是花时间不学习“直接”应用的领域知识。当我们看到需要大量领域知识（例如ggplot）或规模大到需要大型团队实施（例如IntelliJ<sup id="fnref:V"><a href="#fn:V" rel="noopener noreferrer">8</a></sup>）的软件时，如果机器慢了几个数量级，编写可用软件需要榨取机器的大部分性能，那么这些软件的大部分都不会存在。幸运的是，硬件变得快得多，使绝大多数开发人员可以忽略与性能相关的偶然复杂性，而专注于在当今提高生产力所需的其他所有偶然复杂性。</p>

<p>更快的计算机既减少了工具用户遇到的偶然复杂性，也减少了工具创建者需要处理的偶然复杂性，从而允许更有生产力的工具得以存在。</p>

<h3 id="2022-update">2022年更新</h3>

<p>很多人都说这篇文章是错误的，因为布鲁克斯显然说的是X，而布鲁克斯并不是指我在本文中引用的内容。但人们对于布鲁克斯真正意思的说法五花八门，因此总的来说，这些反驳是自相矛盾的，因为他们认为布鲁克斯“显然”指的是某个特定的东西，但如果这么明显，人们就不会对布鲁克斯的意思有这么多不同的想法了。</p>

<p>当然，当遇到像布鲁克斯文章这样的罗夏墨迹测试文章时，这是不可避免的，文章陈述了各种不同且矛盾的内容。</p>

<p>感谢Peter Bhat Harkins、Ben Kuhn、Yuri Vishnevsky、Chris Granger、Wesley Aptekar-Cassels、Sophia Wisdom、Lifan Zeng、Scott Wolchok、Martin Horenovsky、@realcmb、Kevin Burke、Aaron Brown、@up_lurk和Saul Pwanson的评论/修正/讨论。</p>

<p>


</p>
<div>

<hr>

<ol>
<li id="fn:C"><blockquote>
<p>我在下一节中讨论的偶然性。首先让我们考虑本质</p>
<p>软件实体的本质是相互交错的概念的构建：数据集、数据项之间的关系、算法以及函数的调用。这个本质是抽象的，因为这个概念构建在许多不同的表示下是相同的。然而它非常精确且细节丰富。</p>
<p>我相信构建软件的困难部分在于规范、设计和测试这个概念构建，而不是表示它和测试表示忠实性的劳动。我们仍然会犯语法错误，当然；但与大多数系统中的概念错误相比，它们微不足道。</p>
</blockquote>
 <a href="#fnref:C" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:T"><p>奇怪的是，他也在同一篇文章中声称，没有任何单一改进能在十年内带来10倍的提升。虽然这在技术上并不与他的阿姆达尔定律论证加上“大多数”（即至少一半）复杂性是本质/概念的说法相矛盾，但不清楚为什么他还要加入这个说法。</p>
<p>当布鲁克斯在1995年《没有银弹：重燃》中重审他的文章时，他声称自己是正确的，是基于1986年提出的三个主张中最弱的一个，即在十年内，没有任何单一改进会导致一个数量级的提升。然而，他随后确实重申了1986年提出的最强形式的主张，并在1995年再次提出，这次是说没有任何一组技术改进能将生产力提高2倍以上，这是真的：</p>
<blockquote>
<p>这只是我的观点，我认为偶然或表示部分的工作现在已经下降到总量的一半或更少。由于这个比例是一个事实问题，原则上可以通过测量来确定其值。如果失败，我的估计可以通过更知情和更当前的估计来纠正。重要的是，公开或私下写过文章的人中，没有人断言偶然部分大到9/10。</p>
</blockquote>
<p>顺便说一句，我发现有趣的是他说没有人对这个9/10的说法提出异议。根据本文正文的内容，我会说我日常工作的复杂性远高于9/10，而如果我试图在1986年解决同样的问题，这个比例会高到人们甚至不会想到这个问题。作为从事硬件工作十年的附带结果，我也做过与一些人在1986年面临的工作没有太大不同的工作（为DOS编写的微码、汇编和C），我同样会认为那份工作轻松超过9/10。</p>
<p>我发现他后续内容中另一个有趣的部分是，他引用了哈雷尔1992年的《咬银弹》，该文章一方面认为十年期限对于一个数量级的改进是武断的。布鲁克斯对此的回应是</p>
<blockquote>
<p>十年限制还有其他原因：对候选银弹的主张都有某种即时性……我们肯定会在未来40年取得实质性进展；40年一个数量级并不神奇。</p>
</blockquote>
<p>但根据布鲁克斯自己在1995年重申论点时的话，如果9/10的复杂性是本质的，那么无论如何缩减它，都不可能获得超过一个数量级的生产力提升：</p>
<blockquote>
<p>“NSB”无可争辩地指出，如果工作中偶然部分少于总量的9/10，将其缩减到零（这需要魔法）也不会带来一个数量级的生产力提升。</p>
</blockquote>
<p>他的原始文章和1995年的后续文章都写得很有魅力，并且包含一种局部逻辑，其中文章的每一部分如果不太深入思考并且忘记文章说的其他一切，听起来都有些合理。与原文一样，学究可能会争辩说这在技术上并不自相矛盾——毕竟，布鲁克斯可能在说：</p>
<ul>
<li>最多9/10的复杂性是偶然的（如果我们忽略后来的1/2说法，这是阅读文章时必须做的一种记忆/怀疑暂停）</li>

</ul>
<p>虽然这在技术上是一致的（同样，如果我们忽略不一致的部分），并且是人们可以提出的一组主张，但这将意味着从1986年起40年后，即2026年，工具、语言或任何其他潜在改进来源在生产力提升方面没有任何空间是不合理的。但这是荒谬的。如果我们查看布鲁克斯文章的其他部分并结合它们的推理，我们会看到其他不一致和荒谬之处。</p>
 <a href="#fnref:T" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:L"><p>我们在这里看到的另一个问题是布鲁克斯坚持在类别之间划清界限。本质与偶然复杂性。“类型”的解决方案，如语言与“构建还是购买”等。</p>
<p>布鲁克斯承认“构建还是购买”是攻击本质复杂性的一种途径。也许他会同意购买一个正则表达式包会减少本质复杂性，因为这样我就可以避免在简单任务上将编写解析器相关的所有概念记在脑中。但如果我不购买正则表达式，而是使用一种将它们捆绑在标准库中或以其他方式随语言分发的语言呢？或者如果我不需要编写自己的并发原语，而是将它们捆绑到语言中呢？或者就此而言，<a href="https://golang.org/pkg/net/http/" rel="noopener noreferrer">一个完整的HTTP服务器</a>呢？库中“购买”的东西（如今许多是免费的）与语言捆绑的库之间没有清晰的界限，因此不可能在语言带来的收益与可以“购买”的收益之间划出明确的界限。但如果这里没有明确的界限，那么就不可能说其中一种可以减少本质复杂性而另一种不能，并在本质复杂性和偶然复杂性之间维持明确的界限（在回应布鲁克斯时，哈雷尔反对存在清晰的区分，而布鲁克斯的回应是说确实存在明确的区分，尽管他没有提供新的论据）。</p>
<p>布鲁克斯一再坚持这些错误的区分，意味着文章中的推理是不可组合的。正如我们在另一个脚注中已经看到的，如果你从文章的一部分取推理，并与文章另一部分的推理一起应用，很容易产生荒谬的结果，有时甚至是直接的矛盾。</p>
<p>我怀疑这就是为什么关于本质与偶然复杂性的讨论如此混乱的原因之一。这不仅仅是<a href="https://twitter.com/hillelogram/status/1211433465956196352" rel="noopener noreferrer">布鲁克斯含糊其辞、挥舞手臂</a>，他实际上并不自洽，因此不可能且不可能有连贯的启示。迈克尔·菲瑟斯（Michael Feathers）指出<a href="https://twitter.com/mfeathers/status/1259295515532865543" rel="noopener noreferrer">人们通常无法正确识别本质复杂性</a>；正如他所说，<a href="https://twitter.com/mfeathers/status/1256995176959971329" rel="noopener noreferrer">一个人的本质复杂性是另一个人的偶然复杂性</a>。这正是我们应该从文章中预料到的，因为思考其中不同部分的人最终会有不兼容的观点。</p>
<p>当批评布鲁克斯时，这也是一个问题。不可避免地，有人说布鲁克斯真正意思是指完全不同的东西。那会是真的。但布鲁克斯在说完全不同的意思的同时，也会意味着他所说的一些内容。为了支持我在正文中提出的观点，这是一个连贯的观点，人们在1986年可能就持有过。布鲁克斯的许多陈述甚至作为独立的陈述都没有意义，更不用说与文章的其他部分交叉引用了。例如，关于未来十年内没有任何单一开发会带来一个数量级提升的陈述。由于布鲁克斯没有定义并且没有人能明确说明什么是“单一改进”，所以这个陈述毫无意义。而且，如上所述，如果那真的是他想说的，布鲁克斯的文章读起来相当奇怪，基本上没有意义。对布鲁克斯大多数其他解读的另一个问题是，即使布鲁克斯下了功夫使它们定义明确，这些立场本身也是毫无意义的。如果是一个还是两个改进带来一个数量级的提升，这有什么关系？如果是两个，我们会同时使用它们。</p>
 <a href="#fnref:L" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:A"><p>顺便说一句，这不仅仅发生在1955年。我曾与一些在这个世纪的人一起工作过，他们告诉我汇编语言基本上和任何高级语言一样高效。这对几乎每一个阅读这个博客的人来说可能听起来很荒谬，但如果你和整天编写微码或汇编的人交谈，你会偶尔遇到这样的人。</p>
<p>认为你个人使用的工具是尽可能好的，是一个容易掉入的陷阱。</p>
 <a href="#fnref:A" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:B">另一个怪癖是，虽然布鲁克斯承认代码重用和库可以提高生产力，但他声称语言和工具差不多已经发展到头了，但这两个主张不能同时成立，因为库和语言/工具之间没有明确的界限。
 <a href="#fnref:B" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:F"><p>我们任意使用一个摩托罗拉68k处理器，带有FP协处理器，可以执行200 kFLOPS作为参考，来了解我们在消费级CPU中可能拥有的计算能力（FLOPS由于多种原因是不好的指标，但这只是为了了解获得1个CPU年计算资源需要什么，布鲁克斯本人使用MIPS这个术语，就好像它有意义一样）。相比之下，Cray-2可以达到1.9 GFLOPS，大约是其性能的10000倍（我认为如果我们进行可比的比较而不是使用不可比的GFLOPS数字，实际上会少一些，但让我们慷慨一点）。一年中有525600 / 5 = 105120个五分钟时间段，所以为了在五分钟内获得相当于1个CPU年的计算量，我们需要105120 / 10000 = 10台Cray-2每查询，不包括跨Cray-2聚合结果的开销。</p>
<p>认为1986年的一家消费软件公司会有足够的Cray-2，能够让任何随机程序员在想要进行一些数据分析时快速运行CPU年量的查询，这是不合理的。一个来源声称Cray-2在整个生产周期（1985年至1990年）内只制造了27台。即使我的雇主拥有全部，而且它们都在1986年之前生产出来，这也不足以提供我在2020年拥有的那种临时查询能力。</p>
<p>今天，初创公司的人甚至可以对十年前进行类似的论证。你过去必须运营一个对初创公司来说运营起来会非常麻烦的集群，除非初创公司非常专业化，但你现在可以使用Snowflake，基本上获得Presto，但只为使用的计算能力付费（加上健康的溢价），而不是为拥有集群以及确保集群可操作所需的所有员工付费。</p>
 <a href="#fnref:F" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:O">我实际上每次发布新文章时都会遇到其中一个。我在Google文档中写文章，然后将它们复制到运行在Alacritty内的tmux内的emacs中。我的文章很小，适合L2缓存，所以我可以有64B/3.5周期的写入带宽。然而，复制粘贴操作可能需要约1分钟，慢到我可以看到文本被粘贴进来。由于我的芯片正在全力工作以确保复制粘贴发生，它运行在完整的非睿频频率4.2GHz，提供76.8GB/s的写入带宽。对于一篇40kB的文章，1分钟 = 666B/s。76.8G / 666 ≈ 8个数量级的性能被闲置。
 <a href="#fnref:O" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:V">在这个具体情况下，我确信有人会争辩说Visual Studio在2000年就很不错，并且在慢得多的计算机上运行（调试器可以说比当前版本更好）。但在Linux上没有类似的工具，也没有任何类似于今天在VSCode类空间中的选择，即易于学习的编程编辑器，提供特定于编程的功能（而不是记事本的增强版），而无需成为全功能的IDE。
 <a href="#fnref:V" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 20641 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/essential-complexity/</link>
      <guid isPermaLink="false">https://danluu.com/essential-complexity/</guid>
      <pubDate>Tue, 29 Dec 2020 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>汽车在样本外碰撞测试中表现如何？</title>
      <description>[AI 摘要] 本文分析汽车制造商如何针对特定碰撞测试进行优化而非整体安全，基于IIHS新测试引入时的数据。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文分析汽车制造商如何针对特定碰撞测试进行优化而非整体安全，基于IIHS新测试引入时的数据。</div><p>每当有一个基准测试被认真对待时，有些人就会开始针对基准进行优化。计算机领域的一些著名例子包括CPU基准测试<a href="https://spec.org/benchmarks.html" rel="noopener noreferrer">specfp</a>和视频游戏基准测试。在specfp中，Sun通过编译器调整将179.art（specfp的一个子基准测试）的得分提高了12倍，这本质上是重写了基准测试内核，使得Sun <a href="https://en.wikipedia.org/wiki/UltraSPARC_III" rel="noopener noreferrer">UltraSPARC</a>的整体specfp得分提高了20%。有时，GPU厂商会在驱动程序中添加专门的基准测试检测代码，在基准测试期间降低图像质量以产生更高的基准测试分数。当然，针对基准进行优化并非计算机独有，我们看到人们在<a href="https://danluu.com/discontinuities/" rel="noopener noreferrer">其他领域</a>也这样做。这种行为并不令人惊讶，因为通过作弊来提高基准测试分数比通过实际改进产品来提高分数要便宜得多（因此投资回报率更高）。</p>

<p>因此，当人们过分重视高度具体和知名的基准测试时，我通常持怀疑态度。如果没有其他数据，你不知道当条件与基准测试条件不完全相同时会发生什么。对于GPU和CPU基准测试，大多数人都可以使用略微调整的条件来运行标准基准测试。如果结果在条件的小变化下发生显著变化，这就表明厂商即使没有作弊，至少也在掩饰真相。</p>

<p>物理设备的基准测试可能更难以复现。车辆碰撞测试就是一个典型例子——它们是高度具体和知名的基准测试，并且在测试运行中会消耗一辆汽车。</p>

<p>虽然有多个组织进行碰撞测试，但每个组织都遵循特定的规程。汽车制造商，如果有意愿，可以针对碰撞测试分数而不是实际安全性来优化他们的汽车。检查碰撞测试是否被过度具体的优化所操纵，对于非亿万富翁来说并不真正可行。我们能检查的最简单方法是查看当新测试添加时会发生什么，因为这让我们可以看到制造商并未为了获得好分数而进行优化的碰撞测试结果。</p>

<p>虽然有汽车碰撞测试结果显然比没有好，但这些结果本身并没有告诉我们当发生与基准测试不完全匹配的事故时会发生什么。不幸的是，如果我们发生车祸，我们无法要求碰撞车辆的驾驶员改变他们的位置、撞击角度和速度，以使碰撞符合<a href="https://en.wikipedia.org/wiki/Insurance_Institute_for_Highway_Safety" rel="noopener noreferrer">IIHS</a>、<a href="https://en.wikipedia.org/wiki/National_Highway_Traffic_Safety_Administration" rel="noopener noreferrer">NHTSA</a>或<a href="https://en.wikipedia.org/wiki/New_Car_Assessment_Program" rel="noopener noreferrer">*NCAP</a>的测试规程。</p>

<p>在本文中，我们将查看<a href="https://en.wikipedia.org/wiki/Insurance_Institute_for_Highway_Safety" rel="noopener noreferrer">IIHS</a>在添加（驾驶员侧）小重叠和乘客侧小重叠测试时的得分，这些测试分别于2012年和2018年添加。我们将从结果摘要开始，然后讨论这些结果的含义以及评估汽车安全时需要考虑的其他因素，随后是方法的详细信息。</p>

<h3 id="results">结果</h3>

<p>以下排名主要基于车辆在2012年添加驾驶员侧小重叠测试时的得分表现以及在修改后改善测试结果的模型得分。</p>

<ul>
<li><strong>第一梯队</strong>：无需修改即表现良好

<ul>
<li>沃尔沃</li>
</ul></li>
<li><strong>第二梯队</strong>：无需修改时表现一般；修改后表现良好

<ul>
<li>无</li>
</ul></li>
<li><strong>第三梯队</strong>：无需修改时表现差；修改后表现良好

<ul>
<li>梅赛德斯</li>
<li>宝马</li>
</ul></li>
<li><strong>第四梯队</strong>：无需修改时表现差；修改后表现一般

<ul>
<li>本田</li>
<li>丰田</li>
<li>斯巴鲁</li>
<li>雪佛兰</li>
<li>特斯拉</li>
<li>福特</li>
</ul></li>
<li><strong>第五梯队</strong>：修改后表现差或未进行修改

<ul>
<li>现代</li>
<li>道奇</li>
<li>日产</li>
<li>吉普</li>
<li>大众</li>
</ul></li>
</ul>

<p>这些描述是近似的。本田、福特和特斯拉最不符合这些描述，福特可以说介于第四和第五梯队之间，但也可以说优于第四梯队且不符合分类，而本田和特斯拉并不真正适合任何类别（他们的类别是最接近的拟合），但有些其他类别也不完美。详细信息如下。</p>

<h3 id="general-commentary">总体评论</h3>

<p>如果我们看看美国的整体死亡率，汽车事故在相当大的年龄范围内是主要死因。虽然数字会因我们查看的数据集而异，但当驾驶员侧小重叠测试被添加时，IIHS估计25%的车辆死亡来自小重叠碰撞。同样值得注意的是，自90年代以来，小重叠碰撞就被认为与相当一部分车辆死亡有关；这在2012年并不是一个新概念。</p>

<p>尽管小重叠碰撞很重要，但从IIHS在2012年和2018年添加驾驶员侧和乘客侧小重叠测试的结果来看，几乎所有汽车制造商都在优化基准而非整体安全性。除了沃尔沃，所有被调查的汽车制造商在驾驶员侧小重叠测试添加之前生产的汽车在驾驶员侧小重叠碰撞中表现都很差。</p>

<p>当2012年添加驾驶员侧小重叠测试时，大多数制造商修改了他们的车辆以提高驾驶员侧小重叠测试分数。然而，直到IIHS在2018年添加乘客侧小重叠测试之前，大多数制造商都忽略了乘客侧。当新测试添加时，他们也加强了乘客安全性。公平地说，有些汽车制造商在2012年驾驶员侧测试添加时就意识到了小重叠碰撞的问题，并且不需要进一步修改就能在乘客侧小重叠测试中获得好成绩，包括梅赛德斯、宝马和特斯拉（可能还有其他几家，但其他情况的数据较薄；沃尔沃不需要提示）。</p>

<h3 id="other-benchmark-limitations">其他基准测试限制</h3>

<p>我们可以在多个其他领域观察到大多数汽车制造商在以安全为代价优化基准。</p>

<h4 id="gender-weight-and-height">性别、体重和身高</h4>

<p>另一个问题是碰撞测试假人的过拟合。长期以来，成人NHTSA和IIHS测试使用1970年代的50%男性假人，身高5'9"，体重171磅。监管机构在1980年要求使用女性假人，但由于里根时代的预算削减，最初计划被搁置，NHTSA直到2003年才将女性假人放入汽车中。女性假人是男性假人的缩小版，缩小到1970年代的5%身高和体重（4'11"，108磅；另一种型号是4'11"，97磅）。在正面碰撞测试中，使用女性假人时，她总是乘客（一个5%女性在驾驶员座位上出现在NHTSA侧面碰撞测试和IIHS侧面碰撞测试中）。作为参考，2019年美国成年男性平均体重为198磅，美国成年女性平均体重为171磅。</p>

<p>使用1970年代美国成年男性碰撞测试假人会导致对1970年代50%男性的一定程度过拟合。例如，从90年代开始，制造商开始添加系统以防止颈部扭伤。沃尔沃和丰田使用一种系统，可以减少男性和女性的颈部扭伤，并且似乎对女性略有更多益处。大多数汽车制造商使用的系统减少了男性的颈部扭伤，但平均而言，对女性颈部扭伤影响很小。</p>

<p>看来我们也看到了类似的一般碰撞优化，而不仅仅是颈部扭伤。我们没有这方面的碰撞测试数据，并且查看真实世界安全数据超出了本文的范围，但我将指出，在NHTSA将5%女性假人放入一些碰撞测试之前，大多数非沃尔沃命名的汽车制造商在侧面碰撞中存在基于性别的显著死亡率差异（男性死亡率较低，女性死亡率较高）。</p>

<p>沃尔沃声称几十年来一直在使用计算机模型模拟如果女性（包括孕妇）卷入车祸会发生什么。</p>

<h4 id="other-crashes">其他碰撞</h4>

<p>据说沃尔沃有一个碰撞测试设施，他们进行许多测试机构不进行的其他碰撞测试。他们在小重叠测试添加时得分高的一个原因是在IIHS开始进行小重叠碰撞测试之前，他们就已经在进行小重叠碰撞测试了。</p>

<p>沃尔沃还表示，他们测试翻滚（IIHS测试车顶强度，NHTSA根据车辆属性计算车辆翻滚的难易程度，但两者都不测试真实翻滚事故中发生的情况）、后部碰撞（沃尔沃声称如果有儿童在3排SUV的第三排，这些测试尤其重要）和驶出道路（沃尔沃有一个他们使用的“标准”沟渠；他们声称这个测试很重要，因为驶出道路与很大一部分车辆死亡有关）。</p>

<p>如果其他汽车制造商进行类似测试，我找不到太多详细信息。根据碰撞测试分数，在2012年之前，他们似乎没有进行甚至没有考虑小重叠碰撞测试。根据2018年乘客侧小重叠测试添加时许多汽车制造商得分较低的情况，我认为如果其他汽车制造商有大量测试机构未进行的碰撞测试套件会令人惊讶，但理论上可能他们进行了，只是没有包括乘客侧小重叠测试。</p>

<h3 id="caveats">注意事项</h3>

<p>我们不应从这些测试结果过度概括。正如我们上面指出的，碰撞测试结果测试非常具体的条件。因此，当几个新碰撞测试添加时，我们能得出的结论也非常具体。此外，在解释这些结果时，我们还应记住许多其他事情。</p>

<h5 id="limited-sample-size">有限的样本量</h5>

<p>这些数据的一个局限性是我们没有大量相同型号副本的结果，因此我们无法观察型号内变化，这可能是由于测试条件的微小、实际上随机的差异以及相同型号不同副本之间的制造差异造成的。我们可以观察到这些确实重要，因为有些汽车在测试相同型号的两个副本时会看到不同的结果。例如，这是IIHS关于道奇Dart报告中的一段引用：</p>

<blockquote>
<p>道奇Dart在2013车型年推出。Dart进行了两次测试，因为第一次测试期间车载（汽车内部）摄像头的电源中断。在第二次Dart测试中，当铰链从门框撕裂时，驾驶员门打开了。在第一次测试中，铰链严重损坏，下方铰链撕裂，但门保持关闭。在每次测试中，Dart的安全带和正面及侧面帘式气囊似乎充分保护了假人的头部和上身，并且来自假人的测量显示头部和胸部受伤风险很小。</p>
</blockquote>

<p>看起来，如果车载摄像头的电源没有断开，将只有一次测试，就不会知道由于铰链撕裂导致门脱落的风险。一般来说，我们没有关于如果测试相同型号的另一个副本会发生什么的直接信息。</p>

<p>仅使用IIHS数据，我们在这里可能做的一件事是考虑同一制造商制造的（或基于相同平台构建的）不同型号的结果。虽然这不如同一型号的多次测试好，但同一制造商不同型号之间的测试结果是相关的，并且知道例如一个型号的第二次测试偶然显示显著更差的结果应该降低我们对同一制造商其他测试分数的信心。有些事情使这复杂化，例如，如果查看丰田，雅力士实际上是马自达2的重新贴牌，所以也许这不应被视为汇总测试结果的一部分，并且进行这种统计分析超出了本文的范围。</p>

<h4 id="actual-vehicle-tested-may-be-different">实际测试的车辆可能不同</h4>

<p>虽然我认为这不应影响本文的结果，但查看碰撞测试结果时要考虑的另一个问题是结果如何在型号之间共享。正如我们刚才看到的，相同型号的不同副本可能有不同的结果。有些相似的车辆通常被视为用于碰撞测试目的的相同车辆，并将共享相同的分数（只有一个型号会被测试）。</p>

<p>例如，起亚Stinger和Genesis G70就是如此。起亚Stinger比G70长6英寸，一辆全轮驱动的全加载Stinger比基础型号G70重约500磅。IIHS测试的是G70型号——如果你查找起亚Stinger，你会得到Stinger的分数，备注为基础型号G70被测试。考虑到名义上相同的汽车（如上面提到的道奇Dart）可能得到不同的分数，这是一个相当大的差异。</p>

<h4 id="quality-may-change-over-time">质量可能随时间变化</h4>

<p>我们也应谨慎不要过度概括时间。如果我们查看最近沃尔沃（基于沃尔沃P3和沃尔沃SPA平台的车辆）的碰撞测试分数，分数非常出色。然而，如果我们查看基于较旧福特C1平台<sup id="fnref:F"><a href="#fn:F" rel="noopener noreferrer">1</a></sup>的沃尔沃型号，其中一些的碰撞测试分数不那么好（特别是，虽然S40得分不差，但在某些类别中得分为可接受，而不是全部良好）。尽管沃尔沃最近的碰撞测试分数出色，但这并不意味着他们一直或将永远拥有出色的碰撞测试分数。</p>

<h4 id="models-may-vary-across-markets">型号可能因市场而异</h4>

<p>我们也不能概括不同市场销售的汽车，即使对于听起来可能相同的车辆也是如此。例如，<a href="https://www.youtube.com/watch?v=UL_2MdSTM7g" rel="noopener noreferrer">查看这段欧洲销售的日产NP300与非洲销售的日产NP300的碰撞测试视频</a>。由于欧洲汽车经过EuroNCAP测试（类似于美国汽车经过NHTSA和IIHS测试），在欧洲销售的车辆经过优化以在EuroNCAP测试中获得好成绩。对在非洲销售的汽车进行碰撞测试只是最近才开始，因此汽车制造商没有公共关系压力来优化他们的汽车以进行基准测试，他们会生产更便宜的型号或表面上相同型号的更便宜变体。这似乎与大多数汽车制造商在美国或欧洲所做的一样——只要他们能做到而不使基准测试分数变差，他们就在优化成本。只是由于没有非洲碰撞测试基准，这意味着他们可以全力以赴地在成本-安全性权衡上追求成本<sup id="fnref:A"><a href="#fn:A" rel="noopener noreferrer">2</a></sup>。</p>

<p><a href="https://deepblue.lib.umich.edu/bitstream/handle/2027.42/112977/103199.pdf" rel="noopener noreferrer">这份报告</a>比较了美国和欧洲汽车型号，并发现了由于法规差异导致的安全差异。他们发现欧洲型号在正面/侧面碰撞中受伤风险较低，并且驾驶员侧后视镜的设计方式降低了车道变换碰撞的风险，相对于美国设计，而美国车辆在翻滚中更安全，并且车灯使行人更可见。</p>

<h4 id="non-crash-tests">非碰撞测试</h4>

<p>随着时间的推移，碰撞安全的“低垂果实”越来越多，使得碰撞避免相对更加重要。碰撞缓解测试与碰撞测试相比相对原始，我们看到碰撞测试过去和现在都有重大漏洞。人们可能期望，根据我们在碰撞测试中看到的，沃尔沃为他们的碰撞避免技术（牵引力控制、稳定性控制、自动制动等）使用一套特别好的测试，但与熟悉车辆安全测试的人进行的“酒吧间”讨论似乎表明情况并非如此。最近有一些沃尔沃车辆被召回，因为安全系统错误地没有触发。我不打算在这里讲述这个故事，但我会说这相当可怕，表明存在严重的系统性问题。从其他后台讨论来看，作为汽车公司，宝马在安全软件方面相对认真，但这种测试缺乏严谨性，对于见过主流CPU发布过程的人来说将是可怕的。</p>

<p>碰撞避免变得更加重要也可能有利于那些拥有更用户友好的驾驶辅助系统的公司，例如，在多次测试中，《消费者报告》给通用的Super Cruise系统最高评分，而他们一再指出特斯拉的Autopilot系统促进了不安全行为。</p>

<h4 id="scores-of-vehicles-of-different-weights-aren-t-comparable">不同重量车辆的分数不可比</h4>

<p>一辆得分为良好的2700磅小型车可能比一辆得分为可接受的5000磅SUV表现更差。这是因为小重叠测试涉及将车辆撞向固定障碍物，而不是具有特定重量的参考车辆或类车辆障碍物。这在某种意义上等同于将车辆撞向相同重量的车辆，因此就像2700磅小型车被测试通过撞向另一辆2700磅小型车，而5000磅SUV被测试通过撞向另一辆5000磅SUV。</p>

<h4 id="how-to-increase-confidence">如何增加信心</h4>

<p>我们讨论了一些应该降低我们对碰撞测试分数信心的原因。如果我们想增加对结果的信心，我们可以查看其他测试机构的测试结果并汇总，还可以查看公开的碰撞死亡数据（稍后会更多讨论）。我没有查看其他机构分数的条款和条件，但一个复杂情况是IIHS不允许你使用其API或数据转储来显示任何类型聚合的结果（我为了这篇文章没有使用他们的API，因为那很耗时）。</p>

<h4 id="using-real-life-crash-data">使用真实世界碰撞数据</h4>

<p>公开的碰撞死亡数据很复杂，值得单独一篇文章讨论。在本文中，我将指出，如果你查看美国人最容易获得的相关数据，这些数据并未显示沃尔沃特别安全（或不安全）。例如，如果我们查看<a href="https://www.iihs.org/api/datastoredocument/status-report/pdf/52/3" rel="noopener noreferrer">这份涵盖2014型号的2017年报告</a>，两个沃尔沃型号进入报告，两者在其类别中得分大致中等。在上一份报告中，一个沃尔沃型号被包含，它是同类中最好的之一，在下一份报告中，一个沃尔沃型号被包含，它是同类中最差的之一。我们可以在其他型号中观察到这种方差。例如，在2014型号中，大众高尔夫在所有车辆中（不仅仅在其类别中）有最高死亡率之一。但在2017型号中，它在所有车辆中有最低死亡率之一。不清楚有多少变化来自随机方差，有多少是因为2014和2017大众高尔夫之间的差异。</p>

<p>总的来说，噪声似乎是结果中的一个重要因素。如果我们查看提供的信息，我们可以看到几件奇怪的事情。首先，有许多车辆的死亡率95%置信区间从0到N。我们应该有很强的先验信念，即没有2014型号车辆如此安全以至于在车祸中死亡的概率为零。如果我们采用贝叶斯方法（尽管我认为报告的作者不是），有人告诉我们一辆车的真实死亡率不确定性区间有≥5%包含零，我们会说要么我们应该使用更具信息性的先验，要么我们应该使用一个可以纳入更多数据的模型（在这种情况下，也许我们可以尝试理解同一类别中不同型号死亡率之间的方差，然后使用该类别的死亡率作为先验，或者如果我们认为相关，可以纳入同一品牌下其他型号的信息）。</p>

<p>有些人反对使用信息性先验作为偏见洗白的一种形式，但我们应该注意IIHS分析中使用的先验并非完全无信息。报告的所有区间都停止于零，因为他们利用车辆不能创造生命的事实将区间限制在零。但我们有几乎同样强大的信息，即没有2014型号车辆的预期死亡率为零，使用该信息与将区间限制在零并报告死亡率不确定性区间的负数没有根本不同。</p>

<p>此外，IIHS数据只包括驾驶员死亡。这是可以理解的，因为这是标准化乘客数量的最简单方式，但这意味着我们不可能看到汽车制造商在乘客侧小重叠安全性方面的改进（直到2018年乘客侧小重叠测试添加），以及沃尔沃认为重要的情况（3排SUV第三排儿童）缺乏后部碰撞测试的结果。这也意味着我们无法观察沃尔沃所做的一些事情的影响，例如，非常早期在自动制动系统中进行行人和骑车人检测，在驶出道路事故中添加溃缩区以减少背部伤害（他们观察到这些事故经常导致改变生活的脊柱损伤，由于车辆掉落的冲击）等。</p>

<p>我们还可以观察到，在IIHS分析中，许多可能想控制的因素没有被控制（例如，没有控制行驶里程，这将使卡车看起来相对更差，豪华车辆看起来更好；也没有控制农村与城市行驶里程，这也将产生相同方向的影响）。看到数字受混杂因素影响的一个方法是查看汽车的全轮驱动或四轮驱动与两轮驱动版本。它们通常有截然不同的死亡率，尽管安全差异并不大（而且差异通常有利于两轮驱动车辆）。一些合理的原因包括随机噪声、购买同一车辆不同版本的人的差异，以及车辆使用方式的差异。</p>

<p>如果我们想回答“哪些汽车品牌或型号更安全或不那么安全”的问题，我不认为任何公开可用的汇总令人满意，我认为我们需要查看源数据并进行自己的分析，以查看数据是否与我们在碰撞测试结果中看到的相符。</p>

<h3 id="conclusion">结论</h3>

<p>我们查看了12个不同汽车品牌以及当IIHS添加小重叠测试时它们的表现。我们看到只有沃尔沃在公司因IIHS公开羞辱小重叠安全性差之前就认真对待这种事故，尽管小重叠碰撞至少自90年代以来就被认为是死亡的重要来源。</p>

<p>虽然我没有预算进行其他测试，例如在完全占用的车辆中进行后部碰撞测试，但似乎合理甚至可能的是，大多数非沃尔沃的汽车制造商如果测试机构决定添加另一种类型的碰撞测试，将会得到一般或差的测试分数。</p>

<h3 id="bonus-real-engineering-vs-programming">额外：“真正的工程”与编程</h3>

<p>正如Hillel Wayne所指出的，尽管<a href="https://twitter.com/danluu/status/1162469763374673920" rel="noopener noreferrer">程序员通常对“真正的工程师”做什么有理想化的看法</a>，但当你<a href="https://youtu.be/3018ABlET1Y" rel="noopener noreferrer">比较“真正的工程师”所做的与程序员所做的，通常并没有那么不同</a>。特别是，程序员常见的抱怨是他们不需要为他们的错误或糟糕设计承担责任，即使在那些造成生命损失的情况下。</p>

<p>虽然汽车公司可以在某些情况下因不安全设计而承担责任，但仅仅优化一组小范围的基准测试（这肯定导致了额外的死亡，而不是优化安全性而不是基准分数），并不是工程师或公司通常被追究责任的事情。</p>

<h3 id="bonus-reputation">额外：声誉</h3>

<p>如果我看看我扩展社交圈的人对车辆安全的看法，特斯拉的声誉遥遥领先。如果你看广泛的消费者调查，情况就不同了，沃尔沃通常获胜，其他制造商争夺遥远的第二名。</p>

<p>我觉得特斯拉的事情很有趣，因为<a href="https://news.ycombinator.com/item?id=33512915" rel="noopener noreferrer">他们的回应基本上与你从一家认真对待安全的公司所期望的相反</a>。当发生严重问题（关于安全或其他方面）时，他们通常有非常快速的回应，基本上是“一切正常”。<a href="https://twitter.com/danluu/status/760652055446827009" rel="noopener noreferrer">我期望一个认真对待安全或改进的组织回应“我们正在调查”，然后发布详细的事后分析解释哪里出了问题，但这似乎不是特斯拉的风格</a>。</p>

<p>例如，在驾驶员侧小重叠测试中，特斯拉有一个相关得分的型号，即使在修改后改进分数，它也得分为可接受（低于良好，但高于差和边缘）。<a href="https://www.businessinsider.com/tesla-responds-to-model-s-crash-test-findings-iihs-2017-7" rel="noopener noreferrer">特斯拉对结果提出异议，称他们制造“历史上最安全的汽车”</a>，并暗示应忽略IIHS，因为他们有不良动机，转而支持一个客观且没有不良动机的机构的碰撞测试分数，即给予特斯拉好分数的机构：</p>

<blockquote>
<p>虽然IIHS和世界各地数十家其他私人行业组织有自己的方法和动机以适合其主观目的，但目前最客观和准确的独立车辆安全测试由美国政府完成，该政府发现Model S和Model X是其测试过的所有汽车中受伤概率最低的两辆车，使它们成为历史上最安全的汽车。</p>
</blockquote>

<p>正如我们所看到的，特斯拉并不罕见地优化一组特定的碰撞测试并在意外类型的碰撞中获得一般的分数，但他们的回应是不寻常的。然而，从愤世嫉俗的公共关系角度来看，这是有道理的。正如我们在过去几年中看到的，大声宣称某事，无论是否真实，即使有确凿证据表明它是不真实的，似乎不仅有效，这种夸大的言辞似乎吸引超级粉丝，他们会积极捍卫品牌。如果你在youtube上观看汽车评论员，他们有时会提到他们收到仇恨邮件，因为他们像评论任何其他汽车一样评论特斯拉，而他们没有看到任何其他品牌有类似情况。</p>

<p>苹果在90年代和00年代初也有效地使用了这个剧本，当时他们在性能上迅速落后，并没有通过提高性能来回应，而是进行了一系列广告宣传活动，声称他们拥有世界上最好的性能，并在桌面上发布“超级计算机”。</p>

<p>另一个声誉怪癖是我认识相当多的人认为他们能买到的最安全的汽车是“60年代和70年代制造的美国汽车，不是塑料制成的”。我们没有直接相关的小重叠碰撞测试分数对于旧车，但我们确实有的关于旧车的测试数据表明，它们在整体安全性方面与现代汽车相比表现极差。例如，<a href="https://www.youtube.com/watch?v=fPF4fBGNK0U" rel="noopener noreferrer">查看这辆1959年雪佛兰Bel Air与2009年雪佛兰Malibu的碰撞测试</a>。</p>

<h3 id="appendix-methodology-summary">附录：方法摘要</h3>

<p>顶部结果部分使用小重叠测试的分数，因为我认为它是测试测量的安全性最难偷工减料的，并且它已经存在足够长的时间，我们可以看到修改现有型号和后续型号变化的影响，这对乘客侧小重叠测试不成立（其中许多型号仍未测试）。</p>

<p>对于乘客侧小重叠测试，有人可能争辩说驾驶员侧更重要，因为你几乎总是有驾驶员在车祸中，可能有前排乘客也可能没有。此外，对于小重叠碰撞（模拟只有25%重叠的头对头碰撞），驾驶员侧碰撞比乘客侧碰撞更可能发生。</p>

<p>除了检查沃尔沃的分数，我没有查看车顶碰撞测试分数（于2009年添加）。我不打算详细描述车顶测试，但对于车顶测试，有人可能争辩说车顶测试分数应与车辆的翻滚概率评分一起使用，因为车顶测试只测试车顶强度，这只与车辆翻滚相关。我认为，鉴于数据所示，这种反对在许多情况下不成立（车顶测试分数最差的车辆通常是翻滚率相对较高的车辆），但在某些情况下成立，这将使分析复杂化。</p>

<p>在大多数情况下，我们每个型号只得到一个报告的测试结果。然而，一个型号可能有多个版本——包括在进行旨在改进测试分数的安全变化之前和之后。如果对型号进行了以改进安全性的更改，测试分数通常是在更改之后，我们通常看不到更改之前的分数。然而，有许多例外情况，这些在详细结果部分有说明。</p>

<p>在本文中，分数只在型号在新测试引入之前或附近引入时才计数，因为后来引入的型号可能有针对测试进行优化的设计更改。</p>

<h3 id="appendix-detailed-results">附录：详细结果</h3>

<p>在每个测试中，IIHS给出一个整体评级（从最差到最好）为差、边缘、可接受或良好。测试有子分数，但我们不打算在此分析中使用。在每个子部分中，我们将查看当小重叠测试添加时有多少型号获得了每个分数。</p>

<h4 id="volvo">沃尔沃</h4>

<p>所有被检查的沃尔沃型号在测试添加时（车顶、驾驶员侧小重叠和乘客侧小重叠）都得分为良好（最高可能分数）。一个型号，2008-2017 XC60，在2013年进行了更改以在侧帘气囊在小重叠碰撞中触发。其他型号未经修改即被测试。</p>

<h4 id="mercedes">梅赛德斯</h4>

<p>在三个具有驾驶员侧小重叠测试结果的预先存在的型号中，一个未经修改得分为边缘，两个在结构修改后得分为良好。我们只有未经修改测试分数的型号（梅赛德斯C级）在2014年后不久进行了全面重新设计，就在驾驶员侧小重叠测试引入后不久。</p>

<p>如上所述，我们通常只能看到未经修改的型号或经过修改的型号的公共结果，因此，对于得分为良好的型号，我们实际上不知道如果你在梅赛德斯更新设计之前购买车辆会如何得分，但我们拥有未经修改的一个型号的边缘分数是一个负面信号。</p>

<p>此外，当乘客侧小重叠测试添加时，梅赛德斯车辆通常也得分为良好。这表明梅赛德斯不仅在驾驶员侧增加了保护以提高测试分数。</p>

<h4 id="bmw">宝马</h4>

<p>在两个我们有相关测试分数的型号中，两个都在修改前得分为边缘。在其中一个案例中，还有在2017型号中进行结构更改后的分数（回想一下驾驶员侧小重叠测试于2012年引入），该型号之后得分为良好。另一个型号在2016年后进行了全面重新设计。</p>

<p>对于五个我们有相关乘客侧小重叠分数的型号，所有都得分为良好，表明为提高驾驶员侧小重叠测试分数而进行的更改不仅仅是在驾驶员侧进行。</p>

<h4 id="honda">本田</h4>

<p>在五个我们有相关驾驶员侧小重叠测试分数的本田型号中，两个得分为良好，一个得分为边缘，两个得分为差。得分为边缘的型号在2015年进行了结构更改和安全带更改，将其分数改为良好，其他型号未更新或没有更新的IIHS分数。</p>

<p>在六个我们有乘客驾驶员侧小重叠测试分数的本田型号中，两个未经修改得分为良好，两个未经修改得分为可接受，一个在修改保险杠后得分为良好。</p>

<p>所有这些型号在驾驶员侧小重叠测试中都得分为良好，表明当本田增加驾驶员侧安全性以在驾驶员侧测试中得分为良好时，他们没有将相同的更改应用于乘客侧。</p>

<h4 id="toyota">丰田</h4>

<p>在六个我们有未经修改型号相关驾驶员侧小重叠测试分数的丰田型号中，一个得分为可接受，四个得分为边缘，一个得分为差。</p>

<p>得分为可接受的型号进行了结构更改以将其分数提高到良好，但仅限于驾驶员侧。该型号后来在乘客侧小重叠测试中被测试并得分为可接受。在四个得分为边缘的型号中，一个在2017年进行了结构修改，将其分数提高到良好，另一个进行了气囊和安全带更改，将其分数提高到可接受。得分为差的型号在2014年进行了结构更改，将其分数提高到可接受，随后进行了进一步更改，将其分数提高到良好。</p>

<p>还有四个型号我们只有修改后的分数。其中，一个得分为良好，一个得分为可接受，一个得分为边缘，一个得分为差。</p>

<p>总的来说，更改似乎只进行在驾驶员侧，并且在乘客侧小重叠测试引入时，车辆乘客侧小重叠分数与修改前的驾驶员侧分数相同。</p>

<h4 id="ford">福特</h4>

<p>在两个我们有未经修改型号相关驾驶员侧小重叠测试分数的型号中，一个得分为边缘，一个得分为差。这两个型号都生产到2019年，两者都没有更新的测试结果。在三个我们有修改车辆相关结果的型号中，两个得分为可接受，一个得分为边缘。还有一个型号在小重叠测试引入的年份发布，另一个在之后一年发布；两者都得分为可接受。不清楚这些是否应被视为修改，因为设计可能在发布前进行了最后一刻的更改。</p>

<p>我们只有三个相关的乘客侧小重叠测试。一个是良好（对于2015年发布的型号），另外两个是差；这些是上面提到的在驾驶员侧小重叠测试中分别得分为边缘和差的两个型号。这些型号似乎继续生产到2019年，没有进行安全更改。这两个未经修改的型号都是卡车，这对于卡车来说并不罕见，这也是卡车死亡率普遍较高的一个原因——直到最近，许多卡车基于旧平台，很长时间没有更新。</p>

<h4 id="chevrolet">雪佛兰</h4>

<p>在三个我们有修改前相关驾驶员侧小重叠测试分数的雪佛兰型号中，一个得分为可接受，两个得分为边缘。其中一个边缘型号在2015年进行了结构更改以及导致侧帘气囊更早展开的更改，这将其分数提高到良好。</p>

<p>在四个我们只有修改后相关驾驶员侧小重叠测试分数的雪佛兰型号中（所有都进行了结构修改），两个得分为良好，两个得分为可接受。</p>

<p>我们只有一个相关的乘客侧小重叠测试分数，那个分数是边缘。那是在修改以将驾驶员侧小重叠测试分数从边缘提高到良好的型号上，表明更改是为了提高驾驶员侧测试分数而不是提高乘客安全性。</p>

<h4 id="subaru">斯巴鲁</h4>

<p>我们没有任何型号在修改前有相关乘客侧小重叠测试分数。</p>

<p>一个型号进行了更改以使其气囊在小重叠测试中展开；它得分为可接受。两个型号进行了某种结构更改，其中一个得分为良好，一个得分为可接受。</p>

<p>进行气囊更改的型号在2015年进行了结构更改，将其分数从可接受提高到良好。</p>

<p>对于一个我们有相关乘客侧小重叠测试分数的型号，分数是边缘。此外，对于一个进行结构更改的型号，据指出，在更改中，包括防火墙左侧部分的更改，表明更改是为了提高驾驶员侧测试分数，而不是提高乘客侧小重叠碰撞中的乘客安全性。</p>

<h4 id="tesla">特斯拉</h4>

<p>只有一个型号有驾驶员侧小重叠测试的相关结果。该型号在修改前和修改后都得分为可接受。</p>

<h4 id="hyundai">现代</h4>

<p>在五个我们有相关驾驶员侧小重叠测试分数的车辆中，一个得分为可接受，三个得分为边缘，一个得分为差。我们没有迹象表明型号被修改以提高其测试分数。</p>

<p>在两个我们有未经修改型号相关乘客侧小重叠测试分数的车辆中，一个得分为良好，一个得分为可接受。</p>

<p>我们还有一个型号的分数，该型号进行了结构修改以得分为可接受，后来进行了进一步修改，允许其得分为良好。该型号于2017年推出，在未修改的情况下在驾驶员侧小重叠测试中得分为良好，表明它被设计为在驾驶员侧测试中取得良好分数，而没有对乘客侧碰撞的类似考虑。</p>

<h4 id="dodge">道奇</h4>

<p>在五个我们有未经修改型号相关驾驶员侧小重叠测试分数的型号中，两个得分为可接受，一个得分为边缘，两个得分为差。还有两个型号在2015年为安全进行了结构更改后有测试分数；两个都得分为边缘。</p>

<p>我们没有任何型号的相关乘客侧小重叠测试分数，但即使有，修改后型号的糟糕分数意味着我们可能无法知道是否进行了类似的更改到乘客侧。</p>

<h4 id="nissan">日产</h4>

<p>在七个我们有未经修改型号相关驾驶员侧小重叠测试分数的型号中，两个得分为可接受，五个得分为差。</p>

<p>我们有一个型号只有修改后型号的测试分数；正面气囊和安全带在2013年进行了修改，侧帘气囊在2017年进行了修改。修改后的分数是边缘。</p>

<p>一个得分为差的型号在2015年进行了结构更改，将其分数提高到良好。</p>

<p>在四个我们有相关乘客侧小重叠测试分数的型号中，两个得分为良好，一个得分为可接受（该型号在驾驶员侧测试中得分为良好），一个得分为边缘（该型号在驾驶员侧测试中也得分为边缘）。</p>

<h4 id="jeep">吉普</h4>

<p>在两个我们有未经修改型号相关驾驶员侧小重叠测试分数的型号中，一个得分为边缘，一个得分为差。</p>

<p>有一个型号我们只有修改后的测试分数；该型号的气囊和安全带进行了更改，更改后得分为边缘。该型号后来也在乘客侧小重叠测试中被测试并得分为差。</p>

<p>另一个型号有相关的乘客侧小重叠测试分数；它得分为良好。</p>

<h4 id="volkswagen">大众</h4>

<p>两个我们有未经修改型号相关驾驶员侧小重叠测试分数的型号都得分为边缘。</p>

<p>在两个我们只有修改后分数的型号中，一个在2013年进行了修改，修改后得分为边缘。它然后在2015年再次修改，修改后得分为良好。该型号后来在乘客侧小重叠测试中被测试，其中得分为可接受，表明更改有差别地有利于驾驶员侧。另一个在2015年进行更改后得分为可接受，然后在2016年进行进一步更改后得分为良好。2016型号后来在乘客侧小重叠测试中被测试并得分为边缘，再次表明更改有差别地有利于驾驶员侧。</p>

<p>我们有另外两个型号的乘客侧小重叠测试，两者都得分为可接受。这些是在驾驶员侧小重叠测试引入很久之后于2015年推出的型号，并在驾驶员侧小重叠测试中得分为良好。</p>

<h3 id="2021-update">2021年更新</h3>

<p><a href="https://www.iihs.org/news/detail/small-suvs-struggle-in-new-tougher-side-test" rel="noopener noreferrer">IIHS发布了他们新的“升级”侧面碰撞测试的第一组结果</a>。他们一直在谈论做这个已经有一段时间了，并提到在真实世界数据（一些）严重碰撞中，他们观察到车厢侵入显著大于他们测试中所见的。他们提到一些车辆在新测试中表现相对较好，一些表现较差，但直到现在才发布官方分数。</p>

<p>新侧面碰撞测试的结果与本文中描述的结果不同。到目前为止，只有小型SUV的结果被发布，只有马自达CX-5得分为“良好”。在本文中测试中表现良好的三个制造商中，只有沃尔沃有公共结果，他们得分为“可接受”。我有一些问题：</p>

<ul>
<li>沃尔沃的其他型号（他们的大多数车辆基于与具有公共结果的车辆不同的平台）会得分更好吗？</li>
<li>沃尔沃会快速更新他们的车辆以在测试中获得最高分吗？与许多其他制造商不同，我们没有来自沃尔沃的最近数据，因为他们不需要更新他们的车辆以在最后两个新测试中获得最高分</li>
<li>宝马和梅赛德斯要么在新测试中得分良好，要么再次快速更新他们的车辆以得分良好吗？</li>
<li>其他马自达车辆也会在没有更新的情况下得分良好吗？</li>
</ul>

<h3 id="2024-update">2024年更新</h3>

<p>在<a href="https://mastodon.social/@danluu/113494789473565468" rel="noopener noreferrer">2024年对2018-2022年每英里死亡率的分析</a>中，最差的汽车制造商从最差开始是特斯拉、起亚、别克、道奇，然后是现代。别克在本文中未排名，起亚和现代被视为等同，因此在排名的四个品牌中，三个在这个评级中得分最差。并且，正如本文最初指出的，特斯拉不太适合分类，并且显示为安全最差的迹象，也可能平均，并且在某些维度上（ADAS / 自动驾驶），特斯拉似乎安全性非常差，因此有充分的理由认为特斯拉也应被置于最差类别。</p>

<p>还要注意，三个评级良好的制造商中没有一个在每英里最高死亡率型号列表中有一个型号。但很难说有多少是关于汽车的，有多少是关于其他属性（如汽车的使用方式），因为每英里死亡率与汽车价格呈相当强的负相关，并且所有三个制造商都是豪华品牌，其售价远高于平均。豪华车也往往比平均更大更重，而重量也与每英里死亡率负相关。</p>

<p>在排名的时间段内，<a href="https://mastodon.social/@danluu/113494789473565468" rel="noopener noreferrer">特斯拉似乎拥有最高的平均售价</a>（甚至高于三个顶级豪华品牌），并且每辆车重量也远高于中位数，使得特斯拉成为每英里死亡率的极端异常值。</p>

<h3 id="appendix-miscellania">附录：杂项</h3>

<p>一些名牌汽车品牌未被包括。有些因为它们在美国的销量相对较低和/或迅速下降（三菱、菲亚特、阿尔法·罗密欧等），有些因为测试的车辆重叠度很高（起亚、马自达、奥迪），有些因为没有具有驾驶员侧小重叠测试分数的相关型号（雷克萨斯）。当一家公司拥有多个品牌时，如FCA拥有吉普、道奇、克莱斯勒、公羊等，这些没有被汇总，因为大多数不是汽车迷的人不会认识到FCA，但可能认识到吉普、道奇和克莱斯勒。</p>

<p>如果API的服务条款允许你以任何方式使用IIHS数据，我会包括较小的品牌，但由于API附有关于如何显示或讨论数据的非常严格的条款，这些条款与探索性数据分析不兼容，并且在查看数据之前我不知道我想要如何显示或讨论数据，所以我手动提取了所有这些结果（并且没有点击任何最终用户许可协议等），这相当耗时，因此在更全面覆盖和我生活的其余部分之间存在权衡。</p>

<h3 id="appendix-what-car-should-i-buy">附录：我应该买什么车？</h3>

<p>这取决于你在寻找什么，没有办法提出全面的建议。有关特定车辆的实用信息，<a href="https://www.youtube.com/user/TTACVideo" rel="noopener noreferrer">Alex on Autos</a>是我知道的最佳来源。我通常不喜欢视频作为实用信息的来源，但汽车杂志往往比youtube汽车评论员信息量少得多。有更受欢迎的汽车评论员，但他们的受欢迎似乎来自于有魅力的联合主持人之间的机智聊天或其他不仅不直接提供信息，实际上还减少了信息提供的事情。如果你只想知道汽车如何工作，<a href="https://www.youtube.com/user/EngineeringExplained" rel="noopener noreferrer">Engineering Explained</a>也相当不错，但那里的信息通常不实用。</p>

<p>对于可靠性信息，《消费者报告》可能是你最好的选择（你也可以查看J.D. Power，但他们聚合信息的方式使其对消费者来说用处大大降低）。</p>

<p><small>感谢Leah Hanson、Travis Downs、Prabin Paudel、Jeshua Smith和Justin Blank的评论/纠正/讨论</small></p>

<p>
</p>
<div>

<hr>

<ol>
<li id="fn:F">这包括2004-2012沃尔沃S40/V50、2006-2013沃尔沃C70和2007-2013沃尔沃C30，这些是在福特拥有沃尔沃期间设计的。虽然C1平台是福特、沃尔沃和马自达工程师的合作项目，但工作是在福特副总裁的指导下在福特设施完成的。
 <a href="#fnref:F" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:A"><p>公平地说，正如我们在IIHS小重叠测试中看到的，并非每个制造商都做得很差。在2017年和2018年，8辆在非洲销售的汽车被碰撞测试。一辆在美国或欧洲得到我们考虑为一般或差的分数，五辆得到我们考虑为差的分数，“只有”三辆得到我们考虑为极差的分数。日产NP300、达特桑Go和奇瑞QQ3是三辆得分最差的车辆。达特桑是日产的一个子品牌，奇瑞是中国品牌，也称为奇瑞。</p>

<p>如果我们查看在印度销售的汽车，我们会看到同样的情况。最近，对印度市场的一些汽车进行了一些测试，来自达特桑、雷诺、雪佛兰、塔塔、本田、现代、铃木、马恒达和大众的多款车型得分极差，在美国或欧洲会被认为是不可能差的。</p>
 <a href="#fnref:A" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 23242 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/car-safety/</link>
      <guid isPermaLink="false">https://danluu.com/car-safety/</guid>
      <pubDate>Tue, 30 Jun 2020 07:06:34 +0000</pubDate>
    </item>
    <item>
      <title>寻找故事</title>
      <description>[AI 摘要] 本文批评《星际迷航：航海家号》未能充分利用其设定，探讨了电视剧角色发展与叙事的内在矛盾。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文批评《星际迷航：航海家号》未能充分利用其设定，探讨了电视剧角色发展与叙事的内在矛盾。</div><p><em>这是一篇90年代匿名发表的旧文存档，作者使用的化名似乎已从互联网上消失。</em></p>
<p>我注意到<cite>《星际迷航：航海家号》</cite>新增了一个角色，一个博格人。（从剧照看，24世纪他们仍在为女性培育胸部尺寸。）让我恼火的是制片人的评论（我概括一下），"七的加入将为我们提供无限的故事可能性。"</p>
<p>哦。真的吗。</p>
<p>听着，他们根本没有认识到自己已有的故事潜力。我看了几集<cite>《航海家号》</cite>，当我的"胡扯探测器"爆表时就弃剧了。（也许只看几集就评判他们不太公平。但让我忍受"全息肺"这种玩意儿也不公平啊。）</p>
<p>对于那些不看<cite>《星际迷航：航海家号》</cite>的人，其设定是这样的：星舰"航海家号"，一种太空轻型护卫舰，被传送到了距离其应到之处亿万光年之外的地方。以最高速度计算，他们需要超过七十年才能返回家园与亲人团聚。出于某些我们无需在此深究的原因，船员由忠诚的星际联邦成员和叛军混合组成。</p>
<p>在纸面上，这看起来不错。船员间有不稳定的联盟，有返航途中的探索，还有整套"太空孤岛"的戏码。而且<cite>《航海家号》</cite>远没有<cite>《进取号》</cite>那么大——人在上面待那么久对心理健康不利。</p>
<p>但是这个点子真能撑起一整部剧吗？连续看五年"船员斗嘴"或者"他们找到超光速星际旅行的新线索但又落空"会有趣吗？我认为不会。</p>
<p>（事实上，船员们<i>太快</i>就安顿下来了。）</p>
<p>电视剧集的需求颠覆了设定。电视剧的基本需求是让观众认识并关心我们的常驻角色——我们希望他们每周都出现在客厅。我们必须关心他们的变化、需求和愿望。当他们陷入险境时我们必须担心。但我们知道这是电视剧，所以很难真正担心。我们知道角色下周还会回来。</p>
<p>而<i>故事</i>的需求则要求某人自愿<i>改变</i>，认识到某种差异。改变的需要可以从外部强加，但实际的改变必须是自我驱动的。（这是电视剧的基本悖论：唯一被允许改变的是客串角色，但促成改变的工具必须是常驻角色，因此剥夺了两个角色做有趣事情的机会。）</p>
<p>具有严格剧集连续性的剧集（第二集必须承接第一集）允许改变——但它们在剧集停播后进入联合播放时更难销售。经济因素有利于角色一成不变。</p>
<p>一些剧集——比如<cite>《希街蓝调》</cite>——通过让角色真正可有可无来规避险境问题。有些角色出场几集后就死了，提醒我们常驻角色也可能如此。有时常驻角色确实会死。</p>
<p>（当角色在试播集就发生改变时，可能会有问题。一位被邀请为玛丽·泰勒·摩尔最后一部剧工作的编剧从设定中看出，它只够精彩六集，之后便无路可走。福克斯电视台由蒂娅·里奥尼主演的首部剧集<cite>《飞行盲区》</cite>，试播集非常有趣，但设定了一个无法维系的情境。）</p>
<p>有人告诉我，<cite>《航海家号》</cite>唯一有趣的角色一直是那个可以改变的医生。他是唯一被允许成长的角色。</p>
<p>因此，<cite>《航海家号》</cite>的第一个问题是角色不允许改变——或者改变是外部强加的。（顺便说一句，强加的改变是<i>开始</i>故事的好方法。角色随后会反抗，这很有趣。但它也是<i>结束</i>故事的糟糕方式。）</p>
<p>第二个问题是他们没有利用好手头的元素。回到第一季。有一集讲船上有个叛徒，和珍威舰长一样聪明。（精神病学测试怎么会漏掉这个，我不知道，但星际迷航宇宙的精神病学一直运气不佳。）在牵着珍威鼻子走了五十分钟后，她识破了他，并与他对质。他承认了——<i>然后传送离开了飞船</i>，方便地与当地人达成了交易。</p>
<p>对电视剧来说这很完美。我们有了一个据称聪明的反派在外面可能回来，珍威也遇到了对手——但我感觉被耍了。故事在哪？结局在哪？</p>
<p>我认为他们应该这样做。这不是传统电视剧的做法，但我认为它会是更好的故事。</p>
<p>首先，这集应该在珍威与坏人对质并逮捕他时结束。他被关进禁闭室——<i>并且一直关着</i>。观众会在这里获得一些胜利感。</p>
<p>但现在禁闭室里有个和珍威一样聪明的人。突然间，我们设定了<cite>《沉默的羔羊》</cite>。（我不介意"借鉴"，如果借鉴的是好来源。）每当问题<i>足够大</i>时，珍威就有这个选择：她可以去禁闭室，试图与坏人达成交易。"飞船完蛋，你也完蛋。"不仅如此，船上还有个与他有独特关系的人——一种不受正式军衔约束的关系。坏人到底想要什么？</p>
<p>每当珍威情绪低落时，他可以嘲讽她。"顺便说一句，我想到一个办法能让大家用十分之一的时间回家。你呢，舰长？"</p>
<p>你不会让他每集都出现。但任何时候你需要那额外的推动，他就在那里。记住，我们可以通过与原集相同的戏法让他随时逃脱。</p>
<p>此外，抓住他是一回事；关住他是另一回事。你可以用一次囚犯越狱尝试来生成另一整集故事。但那会是中间部分。让我们谈谈我希望看到的结局。</p>
<p>让我们虚构一场危机。博格能发电机爆炸；我们深处曲速空间；我们出色的技术员团队拼凑修复了传感器，发现了一颗可能适合修复的M级行星。只是它太远了。余量很小——但并非不可能。船上多了两个人。每个人需要一定量的食物、空气、水等。在压力下，尼利克斯承认他的族人可以进入假死状态，于是他这么做了。医生英勇尝试，但负责维护博格能发电机的工程师死了。（嗯。电力不足。医生只能在某些关键时刻被唤醒。）看起来不错——但他们一直在消耗空气直到死亡；必须再死一个船员，其余人才能活下去。</p>
<p>有人想起了禁闭室里的那个人。"他有罪的问题，"图沃克说，"已经解决。舰长的权威是绝对的。你完全有权举行简易军事法庭并判处他死刑。"</p>
<p>珍威拒绝了。"星际联邦不这样做。"</p>
<p>但如果她不这么做，所有人都会死。压力现在来到了珍威这边。作为珍威，她在寻找技术解决方案。"找到答案，该死！"最后期限临近。过了某个时点，囚犯必须死，连同另一个人一起。</p>
<p>一名船员（常驻角色）自愿赴死。在珍威接受前，另一名（常驻角色）船员也自愿了，珍威被迫做出决定。——图沃克指出，虽然如果该成员自愿赴死在道德上可以辩护，但飞船没有这两个船员中任何一个都无法继续运行。<i>没有</i>囚犯却可以继续。显然囚犯不如那些船员有价值，但她是舰长。<i>必须</i>做出这个决定。</p>
<p>我们无畏的技术团队认为可能有解决方案，但会耗尽几乎所有储备，他们需要另外六个小时来评估可行性。船上有人试图为她解决问题——杀掉囚犯——失败消耗了更多宝贵电力。现在最后期限提前了，超过了六小时的期限。技术团队的方案不再可行。</p>
<p>就囚犯而言，他现在开始讨价还价。他说他有办法帮忙。真的吗？他曾试图摧毁飞船。而且他不会在获得完全赦免前透露方法。</p>
<p>（这些都是基本的情节设计：不断堆积困难。在角色面前放个胡萝卜，不断把它拽走。）</p>
<p>棘手的部分是结局。要求结局必须合乎逻辑地从前文推导出来。如果你要使用技术解决手段，就必须在剧集前半段做好铺垫。否则就是技术废话。就是机械降神。（每当有人在最后广告休息后说："当然！如果我们反相抗波子流，就没事了！"我就想给编剧脑袋来一下。）</p>
<p>根据这里设定的情境，我们有三种可能的结局：</p>
<ul>
<li>某位船员试图通过自我牺牲来解决问题。（记住，麦考伊和史波克这么做过。）这是个薄弱的解决方案（除非珍威这么做），因为它转移了对珍威决定的焦点。</li>
<li>珍威与囚犯达成协议，他们一起想出解决方案（不涉及抗波子流）。这会产生有趣的后续影响：给予囚犯自由——同时船上所有人都恨透了他。至少可以作为另一集的素材。</li>
<li>珍威杀死囚犯但拒绝举行军事法庭。她可能侥幸成功——囚犯可能幸存；他们一直祈求但不能依赖的百万分之一机会实现了——但她决定杀死囚犯而非自己的船员。</li>
</ul>
<p>我倾向的结局是第三种，即使囚犯不必死。我们设定的决定是艰难的，且意义重大。这是一个指挥决策。她最终是否杀死囚犯并不重要；重要的是她<i>决定</i>这么做。</p>
<p>约翰·加利肖曾将所有故事归类为<i>成就</i>故事或<i>决策</i>故事。决策故事更难写，因为两种选择都必须有意义。</p><p><em>由 mimo-v2.5 模型翻译，花费 5358 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/voyager-story/</link>
      <guid isPermaLink="false">https://danluu.com/voyager-story/</guid>
      <pubDate>Tue, 2 Jun 2020 07:05:34 +0000</pubDate>
    </item>
    <item>
      <title>获取追踪更多价值的简单方法</title>
      <description>[AI 摘要] 本文描述了如何构建追踪基础设施和工具，以克服常见问题并从分布式追踪中提取实际价值，使其对公司值得。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文描述了如何构建追踪基础设施和工具，以克服常见问题并从分布式追踪中提取实际价值，使其对公司值得。</div><p>许多人似乎认为分布式追踪没有用，或者至少没有极大力气就不值得，对于比FB小的公司来说更是如此。例如，<a href="https://twitter.com/theatrus/status/1220205677672452097" rel="noopener noreferrer">这里有</a> <a href="https://twitter.com/mattklein123/status/1049813546077323264" rel="noopener noreferrer">一些</a>公开对话，听起来像我经历过的许多私下对话。当然，价值存在于某处，<a href="https://twitter.com/mattklein123/status/1220150248741326855" rel="noopener noreferrer">但解锁它的成本太高了</a>。</p>

<p>我认为这高估了从追踪中获取大量价值所需的工作量。在Twitter，Rebecca Isaacs能够描绘出从追踪中获取价值的愿景并加以执行（得到了许多其他人的帮助，包括Jonathan Simms、Yuri Vishnevsky、Ruben Oanta、Dave Rusek、Hamdi Allam等许多人<sup id="fnref:O"><a href="#fn:O" rel="noopener noreferrer">1</a></sup>），使得工作很容易收回成本。本文将描述我们构建的追踪“基础设施”，并描述一些我们发现其有价值的用例。在此之前，让我们先了解Rebecca愿景实现前的一些背景情况。</p>

<p>在高层次上，我们可以说我们有一个以追踪视图为导向的系统，并遇到了所有可能预期的那些问题。这些问题在<a href="https://medium.com/@copyconstruct/distributed-tracing-weve-been-doing-it-wrong-39fc92a857df" rel="noopener noreferrer">Cindy Sridharan的这篇文章</a>中有更详细的讨论。然而，我想更详细地讨论我们遇到的具体问题，因为我认为看看哪些具体事物导致了问题是有用的。</p>

<p>总的来说，这些问题足够严重，以至于追踪多年缺乏所有权，甚至可以说是无人负责。有些人利用业余时间维护或改进，但缺乏从追踪中获取明显价值导致了一个恶性循环：从追踪中获取价值的高壁垒使得组织难以资助，从而使得让追踪更可用变得困难。</p>

<p>一些导致追踪投资回报率低的问题包括：</p>

<ul>
<li>模式使得无法在“原位”运行简单查询</li>
<li>没有真正的聚合信息方式

<ul>
<li>无法找到有趣或有代表性的追踪</li>
</ul></li>
<li>无法知道实际采样率，采样高度非代表性</li>
<li>时间</li>
</ul>

<h3 id="schema">模式</h3>

<p>模式实际上是一组追踪，其中每个追踪是一组跨度，每个跨度是一组注释。每个非根跨度都有一个指向其父跨度的指针，因此可以确定追踪的图结构。</p>

<p>就本文而言，我们可以将每个追踪视为包括所有子RPC的外部请求，或请求的子集，而不是在请求顶部作为根。我们也追踪一些非请求的东西，如构建和git操作，但为简单起见，我们将在本文中忽略这些，尽管我们将讨论的技术也适用于它们。</p>

<p>每个跨度对应一个RPC，每个注释是开发者选择记录在跨度上的数据（例如，RPC负载大小、跨度时系统中各种队列的队列深度，或中断RPC的GC暂停的GC暂停时间）。</p>

<p>从具有集合（集合）模式中产生的一些问题包括：</p>

<ul>
<li>执行任何使用追踪固有图结构信息的查询需要读取追踪中的每个跨度并重建图</li>
<li>因为没有每个追踪信息的索引或摘要信息，任何对追踪的查询都需要读取追踪中的每个跨度</li>
<li>实际上，由于上述两项在查询时以临时方式执行成本过高，人们运行的唯一查询是“给我几个匹配简单过滤器的跨度”的某种变体</li>
</ul>

<h3 id="aggregation">聚合</h3>

<p>直到大约一年半前，查看追踪的唯一支持方式是去UI，从搜索框和下拉菜单的组合中按服务名过滤，然后查看最近追踪的列表，其中您可以点击任何追踪以获得“追踪视图”。每个搜索返回N个最近的结果，这些结果不一定代表所有最近的结果（原因在下面的采样部分提到），更不用说代表任何其他时间跨度的所有结果了。</p>

<p>根据上述模式部分讨论的问题，由于跨大量运行查询成本过高，无法提出诸如“我正在查看的任何追踪是否代表常见追踪，或者我正在查看的是奇怪的边缘情况？”或“显示特定尾部事件的追踪，例如，当服务A到服务B的请求超时或当服务A到某个后端数据库的写放大&gt; 3倍时”，或甚至“只显示我完整的追踪，即我们没有丢弃追踪中的跨度的追踪”之类的问题。</p>

<p>此外，如果您点击一个“太大”的追踪，查询会超时，您将无法查看追踪——这是缺乏任何速率限制逻辑加上模式的另一个常见副作用。</p>

<h3 id="sampling">采样</h3>

<p>在多个地方做出了采样或不采样的决定。没有列出所有这些地方的文档，甚至无法猜测采样率，而无需审计所有代码以找出采样决定的位置。</p>

<p>此外，在多个地方由于实现而做出了无意的采样决定。跨度从启用追踪的服务发送到本地代理，然后到“收集器”服务，然后从收集器服务发送到我们的后端数据库。跨度可能在这些点中的任何一个被丢弃：在本地代理中；在收集器中，其节点经常崩溃并丢失所有数据；以及在后端数据库中，后者因热键或一般高负载而拒绝写入。</p>

<p>这种以追踪ID为数据库键的设计，没有中间逻辑来调节写入，意味着一个1M跨度的追踪（我们有）会在几秒内导致1M次写入同一个键。另一个问题是有成千上万扇出的请求（在我工作过的每家科技公司都存在），这可能在几毫秒内导致数千次写入同一个键。</p>

<p>另一个采样怪癖是，为了避免错过不从前端开始的追踪，逻辑导致每个RPC独立采样决定。如果你做一下数学计算，如果你有一个像我们这样的面向服务的架构，采样率听起来像中等偏低，比如，你最终大部分跨度将从叶子RPC开始，导致单跨度追踪。对于非叶子RPC，大部分将从叶子的第二层开始，以此类推。我们大部分负载和存储成本来自这些实际上无用的追踪，从或接近叶子开始，如果你想对跨度做任何分析以了解整个系统的行为，你必须在计算所有其他独立采样决定的同时考虑这个采样偏差。</p>

<h3 id="time">时间</h3>

<p>没有真正针对时钟偏移进行调整（有一些，但它试图进行本地成对调整，这实际上没有改善情况，反而使合理考虑时钟偏移更加困难）。</p>

<p>如果您只是天真地计算跨度花费的时间，甚至使用来自单个主机的时间戳（这消除了许多可能的时钟偏移来源），您会得到很多负持续时间的跨度，这当然是不可能的，因为结果不能在创建结果请求之前返回。而且如果您比较不同主机上的时间，结果更糟。</p>

<h3 id="solutions">解决方案</h3>

<p>这些问题的解决方案分为我认为的两个桶。对于诸如收集器节点崩溃或后端数据库丢弃请求导致的跨度丢失等问题，有一些使用众所周知且广泛使用的技术的直接工程解决方案。对于这一对特定问题，短期权宜之计是进行一些GC调优，将收集器节点崩溃率降低约100倍。这只需要两分钟，然后我们用真正的队列替换了收集器节点，该队列可以吸收更大的流量突发并调节写入数据库。对于我们在每次RPC上掷采样骰子导致过采样叶子级跨度的问题，这是<a href="https://danluu.com/algorithms-interviews/" rel="noopener noreferrer">大多数人在面试中会做对的小问题之一，有时在作为较大系统的一部分时可能会丢失</a>，有多种解决方案，例如，由于每个跨度都有父指针，我们可以在相关位置知道RPC是否有父节点，并且我们可以做出采样决定，并仅当跨度没有父指针时创建traceid，这导致每个跨度被采样的均匀概率，每个采样追踪都是完整追踪。</p>

<p>另一个桶是构建数据集和工具（并添加注释），允许用户回答他们可能有的问题。这不是一个新想法，<a href="https://storage.googleapis.com/pub-tools-public-publication-data/pdf/36356.pdf" rel="noopener noreferrer">Dapper论文的第5部分</a>在2010年就讨论过。</p>

<p>当然，一个主要区别是Google可能在Dapper上投入了至少两个数量级的努力来构建工具，而我们在追踪基础设施上投入的工具建设少得多，因此我们的许多工具粗糙得多，例如，Dapper论文的图6显示了一个追踪视图，显示一组相关直方图，使理解追踪上下文变得容易。我们还没有做那个UI工作，所以类似的视图需要运行一个简单的SQL查询。虽然这并不难，但向用户提供数据比让用户查询数据是更好的用户体验。</p>

<p>在已完成的工作中，最简单的明显高ROI的事情是构建一组包含人们可能想查询的信息的表，结构使得常见的查询不需要做大量工作。</p>

<p>我们按天分区有以下表：</p>

<ul>
<li>trace_index

<ul>
<li>高级追踪级信息，例如，追踪是否有根；根是什么；如果相关，命中的请求端点是什么等。</li>
</ul></li>
<li>span_index

<ul>
<li>关于客户端和服务器的信息</li>
</ul></li>
<li>anno_index

<ul>
<li>人们经常想查询的“标准”注释，例如，请求和响应负载大小、客户端/服务器发送/接收时间戳等。</li>
</ul></li>
<li>span_metrics

<ul>
<li>计算的指标，例如，跨度持续时间</li>
</ul></li>
<li>flat_annotation

<ul>
<li>所有注释，以防您想查询不在anno_index中的内容</li>
</ul></li>
<li>trace_graph

<ul>
<li>对于每个追踪，包含追踪的图表示，用于需要图结构的查询</li>
</ul></li>
</ul>

<p>仅仅拥有这组表，可以通过SQL查询（或在某些情况下使用Scalding或Spark作业，例如进行图查询时Presto SQL不理想）查询，就足以让追踪收回成本，从难以证明合理到明显高价值。</p>

<p>我们可以用这组表回答的一些问题包括：</p>

<ul>
<li>对于这个有问题的服务，给我一组有代表性的追踪</li>
<li>对于负载增加的服务，显示是哪个上游服务导致了负载</li>
<li>给我所有对下游服务X有异常写放大的服务列表

<ul>
<li>来自特定服务或特定端点的流量是否导致异常写放大？例如，在某些情况下，我们看到B到C的总写放大没有异常，但当B由A调用时，B到C的放大率非常高。</li>
</ul></li>
<li>显示我们对各种请求在序列化/反序列化上与“实际工作”相比花费的时间</li>
<li>显示不同类型的请求在后端工作方面的成本</li>
<li>对于移动端检测到的高延迟请求，显示后端发生了什么</li>
<li>显示此请求端点的延迟关键路径集（使用我们目前的注释，这有一些问题，可能值得单独发文）</li>
<li>显示此服务依赖的服务的CDF

<ul>
<li>这是一个分布，因为特定服务是否调用另一个服务是数据相关的；一个服务平均每1000次调用才调用另一个服务并不罕见</li>
</ul></li>
</ul>

<p>我们正在构建和构建其他工具，但仅仅能够轻松地对追踪数据（包括最近和历史数据）运行查询和聚合，就足以收回我们想要进行的所有其他工作的成本。<a href="https://danluu.com/metrics-analytics/" rel="noopener noreferrer">这类似于我们在查看指标数据时看到的情况，获取我们已有的数据并以一种让人们立即运行任意查询的方式暴露出来立即产生了回报</a>。为追踪这样做不如为指标这样做直接，因为数据更丰富，但它不是一个根本不同的想法。</p>

<p>我认为拥有除了原始数据之外的东西查看对追踪也比对指标更重要，因为指标的原始“追踪视图”等价物，一个“仪表板视图”其中您只是查看图表，显然且直观有用。如果那是您为指标拥有的全部，人们不会说不值得资助您的指标基础设施，因为仪表板真的很有用！然而，很难看到如何从原始追踪视图中获取价值，这是许多关于追踪无价值评论的来源。指标数据和追踪数据之间的这种复杂性差异使得高层追踪视图的附加值大于指标。</p>

<p>将我们的数据以非NoSQL数据库中块状格式存储也使我们更容易在追踪数据上构建工具，让不想运行SQL查询的用户从我们的追踪数据中获取价值。一个例子是服务依赖探索器（SDE），主要由Yuri Vishnevsky、Rebecca Isaacs和Jonathan Simms构建，得到了Yihong Chen的帮助。如果我们尝试查看单个请求的RPC调用图，我们会得到相当大的内容。在某些情况下，调用树的深度可能达到数百层，并且在某些层看到20或更多的扇出也很常见，这使得天真可视化难以解释。</p>

<p>为了了解SDE的工作原理，让我们看一个较小的例子，其中相对容易理解正在发生什么。想象我们有8个服务，<code>A</code>到<code>H</code>，它们如下树所示相互调用，我们有服务<code>A</code>被调用10次，总共调用服务<code>B</code>10次，<code>B</code>分别调用<code>D</code>、<code>D</code>和<code>E</code> 50、20和10次，其中两个<code>D</code>通过不同的RPC端点（调用）区分，即使它们是同一个服务，等等，如下所示：</p>

<p><img src="https://danluu.com/images/tracing-analytics/rpc-tree.png" alt="RPC调用图；这将在相关部分隐式描述，尽管整个SDE部分在展示视觉工具，如果您只是阅读alt文本可能不会满意；如果您想要非视觉解释，上一节中描述的表更可能是您想要的；SDE是一种可视化" width="610" height="488"></p>

<p>如果我们从节点E的角度看SDE，我们会看到以下内容：
<img src="https://danluu.com/images/tracing-analytics/e.png" alt="以服务E为中心的SDE，显示呼叫者和被呼叫者，直接和间接" width="1266" height="576"></p>

<p>我们可以看到直接呼叫者和被呼叫者，E的100%调用来自C，并且E的100%调用也调用C，并且当调用C时我们有20倍负载放大（200/10 = 20），与我们在上面RPC树中看到的相同。如果我们查看间接被呼叫者，我们可以看到D有4倍负载放大（40 / 10 = 4）。</p>

<p>如果我们想查看E下游C直接调用的内容，我们可以选择它，我们将获得指向C的直接后代的箭头，在这种情况下，是E的所有间接被呼叫者。</p>

<p><img src="https://danluu.com/images/tracing-analytics/e.png" alt="以服务E为中心的SDE，被呼叫者C高亮显示" width="1263" height="239"></p>

<p>对于更复杂的例子，我们可以查看服务D，它在我们的原始树中以橙色显示。</p>

<p>在这种情况下，我们的摘要框显示：</p>

<ul>
<li>在2020年5月28日，有...

<ul>
<li>10个总<a href="https://courses.cs.washington.edu/courses/cse551/15sp/notes/talk-tsa.pdf" rel="noopener noreferrer">TFE</a>根追踪</li>
<li>110个总追踪到D的RPC</li>
<li>2.1千个总由D引起的追踪RPC</li>
<li>3个从TFE端点到D端点的唯一调用路径</li>
</ul></li>
</ul>

<p>我们在树中看到D三次的事实在摘要框中指示，它说我们有3个从我们的前端TFE到D的唯一调用路径。</p>

<p>我们可以展开对D的调用，在这种情况下，看到两个调用以及每个调用的流量比例。</p>

<p><img src="https://danluu.com/images/tracing-analytics/d.png" alt="以服务D为中心的SDE，通过对D的点击展开了D的不同调用" width="1262" height="364"></p>

<p>如果我们点击其中一个调用，我们可以看到特定调用<code>call4</code>的上游和下游依赖关系，如下所示，我们可以看到即使服务<code>D</code>对于<code>call3</code>访问服务<code>C</code>、<code>H</code>和<code>G</code>，它也从不访问这些服务。类似地，我们可以看到其上游依赖包括直接被C调用，间接被B和E调用，但不被A和C调用：</p>

<p><img src="https://danluu.com/images/tracing-analytics/d-4.png" alt="以服务D为中心的SDE，通过点击call4高亮显示了D的call4；仅显示与call4相关的上下游负载" width="1263" height="343"></p>

<p>我们从SDE可以轻松看到的一些事情是：</p>

<ul>
<li>服务或RPC调用引起的负载

<ul>
<li>我们有异常负载放大的地方，无论这是服务的一般情况还是仅发生在某些调用路径上</li>
</ul></li>
<li>什么导致服务或RPC调用的负载</li>
<li>我们在哪里以及为什么得到循环（对于<a href="https://www.thestrangeloop.com/2018/leverage-vs-autonomy-in-a-large-software-system.html" rel="noopener noreferrer">Strato</a>等非常常见）</li>
<li>什么导致奇怪的超深追踪</li>
</ul>

<p>这些都是用户可以通过查询我们存储的数据获得的事情，但拥有一个带有UI的工具，让您实时点击探索事情，降低了发现这些事情的障碍。</p>

<p>在上面显示的例子中，有少数服务，因此您可以从更常用的节点海洋视图中获得类似信息，其中每个节点是一个服务，可视化上有一些注释，但当我们查看真实追踪时，显示数千个服务和全局视图使得很难看清发生了什么。Rebecca的一些早期分析使用了这样的视图，但我们发现您需要很多隐含知识才能很好地使用这样的视图，一个丢弃更多信息并突出显示几件事的视图使得没有恰好具有正确隐含知识的用户更容易从查看追踪中获取价值。</p>

<p>虽然我们在这里演示了RPC计数/负载的视图，但我们也可以显示其他内容，如延迟、错误、负载大小等。</p>

<h3 id="conclusion">结论</h3>

<p>更一般地说，这只是对我们在拥有基本分布式追踪设置后从数据中构建的几件事情的简要描述。您可能不想完全按照我们所做的做，因为您可能有不同的问题，并且您极不可能遇到我们的追踪基础设施遇到的确切问题集。通过与其他公司人士的私下交流，我不认为我们遇到的问题水平是唯一的；如果有什么不同的话，我们的追踪基础设施比许多或大多数同行公司（不包括FB/Google/Amazon等巨头）处于更好的状态，因为它基本上可以工作，人们可以并且确实使用我们拥有的追踪视图来调试真实的生产问题。但正如他们所说，不快乐的系统以自己的方式不快乐。</p>

<p>像我们之前对<a href="https://danluu.com/metrics-analytics/" rel="noopener noreferrer">指标分析</a>的看法一样，这项工作是增量完成的。由于追踪数据比指标数据丰富得多，在编写生成本文提到的表的Scalding（MapReduce）作业之前，我们花了更多时间进行临时数据分析，但个别分析足够有价值，以至于在最初的几周清理一些最糟糕的数据质量问题并使用现有基础设施运行（极其痛苦的）临时分析之后，这个项目集几乎没有不收回成本的时候。</p>

<p>回顾关于是否值得从事追踪基础设施工作的讨论，人们经常指出多家公司的众多失败来证明购买（而不是构建）决策的合理性。我认为这并非完全不合理，类似项目的基础失败率不应被忽视。但另一方面，描述的大多数工作并不超级棘手，除了获得组织认同和清楚了解追踪可以带来的价值之外。</p>

<p>有一点超出了本文范围，可能值得单独发文，那就是追踪和指标虽然不完全正交，但互补，只拥有其中一个会让你对许多问题视而不见。你将为此付出高昂代价，以各种方式：不必要的事件、额外的调试事件时间、由于运行基础设施效率低下而导致的更高货币成本等。此外，虽然指标和追踪各自比单独拥有任何一个提供更好的可见性，但一些问题需要同时查看两者；我做过的最有趣的分析之一涉及连接（通常使用字面SQL连接）追踪数据和<a href="https://danluu.com/metrics-analytics/" rel="noopener noreferrer">指标数据</a>。</p>

<p>具体来说，一个容易用追踪看到但用日志看很烦人的东西（除非您添加日志试图特别查找这一点，您可以为任何单个案例这样做，但可能不想为追踪使其可见的数千件事这样做），是我们上面看过的：“显示从负载均衡器到<code>A</code>的特定调用路径导致对服务<code>B</code>高负载放大的情况，<code>B</code>可能在调用图中距离<code>A</code>多跳。在某些情况下，这会很明显，因为<code>A</code>通常导致<code>B</code>高负载放大，但如果只在某些情况下发生，用追踪仍然容易处理，但如果您只看指标，那会非常烦人。</p>

<p>一个您需要连接追踪和指标数据的例子是查看像坏主机对延迟的性能影响。通常，您无法注释经过主机的适当跨度，因为如果您知道主机在跨度时是坏的，主机就不会在生产中。但您有时可以用历史数据找到一组坏主机，然后查看经过主机的延迟关键路径以确定坏主机的端到端影响。</p>

<p>每个人都有自己的偏见，关于追踪，我的偏见通常来自尝试直接改进成本、可靠性和延迟的工作，因此示例集中在这些方面，但追踪也有许多其他用途。您可以查看《Distributed Tracing in Practice》或《Mastering Distributed Tracing》以获取其他视角。</p>

<h4 id="acknowledgements">致谢</h4>

<p><small>感谢Rebecca Isaacs、Leah Hanson、Yao Yue和Yuri Vishnevsky的评论/修正/讨论。</small></p>

<p>


</p>
<div>

<hr>

<ol>
<li id="fn:O"><p>这几乎肯定是一个不完整的列表，但其他参与的人包括Moses、Tiina、Rich、Rahul、Ben、Mike、Mary、Arash、Feng、Jenny、Andy、Yao、Yihong、Vinu和我自己。</p>

<p>注意，贡献者名单相对较长并不与这项工作的高ROI相矛盾。我估计在本文讨论的所有事情上投入的工作量不到2人年。例如，虽然我花了相当多时间做使用追踪基础设施的分析，但我认为我只在基础设施本身上花了大约一周时间。</p>

<p>如果上面还不明显，尽管我正在写这篇文章，但我是这项工作的相当次要的贡献者。我之所以写这篇文章，是因为当这项工作进行时，我坐在Rebecca旁边，并对她的过程和结果都印象深刻。</p>
 <a href="#fnref:O" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 12965 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/tracing-analytics/</link>
      <guid isPermaLink="false">https://danluu.com/tracing-analytics/</guid>
      <pubDate>Sun, 31 May 2020 07:06:34 +0000</pubDate>
    </item>
    <item>
      <title>从指标数据中获取更多价值的简单方法</title>
      <description>[AI 摘要] 该文章介绍了 Twitter 内部一个名为 LongTermMetrics 的系统，它通过长期存储和查询关键指标数据，以极低的初始成本发现了价值数百万美元的优化机会。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 该文章介绍了 Twitter 内部一个名为 LongTermMetrics 的系统，它通过长期存储和查询关键指标数据，以极低的初始成本发现了价值数百万美元的优化机会。</div>我们花了一天时间<sup id="fnref:D"><a href="#fn:D" rel="noopener noreferrer">1</a></sup>构建了一个系统，该系统立即发现了一个价值七位数的优化（最终得以实施）。在第一年，我们因此每年节省了八位数的成本。这个系统引入的关键功能是能够跨所有主机、所有服务以及任意时间段查询指标数据（自系统创建以来），因此我内部将其称为长期指标系统（LTM），因为我喜欢起无聊但描述性的名字。

这件事始于我寻找一个既能帮助我理解 Twitter 基础设施栈，又能带来易于量化价值的入门项目。Andy Wilcox 建议查看一些大型服务的 <a href="https://stackoverflow.com/a/37102630/334816" rel="noopener noreferrer">JVM 幸存者空间</a> 使用情况。如果你不熟悉幸存者空间是什么，你可以将其视为 JVM 中的一个可配置、固定大小的缓冲区（至少如果你使用 Twitter 默认的 GC 算法）。当时，如果你随机查看一个大型服务，通常会发现以下情况之一：<p></p>

<ol>
<li>缓冲区太小，导致性能不佳，在高负载下有时会灾难性地变差。
</li><li>缓冲区太大，导致内存浪费，也就是金钱浪费。
</li></ol>

<p>但与其随机查看服务，我们完全有理由查询所有服务，并得到一个可以改进配置的服务列表，按性能降级或成本节省排序。如果我们为 JVM 幸存者空间编写了这个查询，它同样适用于其他配置参数（例如，其他 JVM 参数、CPU 配额、内存配额等）。然而，由于数据一致性和性能问题的组合，为所有服务编写一个工作的查询比我预期的要困难一些。数据一致性问题包括：</p>

<ul>
<li>任何给定指标可能有约 100 个名称，例如，我发现了 94 个不同的 JVM 幸存者空间名称
<ul>
<li>我怀疑还有更多，这些只是我通过简单搜索找到的
</li></ul></li>
<li>相同的指标名称可能对不同服务有不同含义
<ul>
<li>可能是计数器或计量器
</li><li>可能有不同单位，例如，字节与 MB 或微秒与毫秒
</li></ul></li>
<li>指标有时被标记为错误的服务名称
</li><li>僵尸分片可以在集群管理器启动新分片实例后继续运行并报告指标，导致特定分片名称的指标重复且不一致
</li></ul>

<p>我们的指标数据库 <a href="https://blog.twitter.com/engineering/en_us/topics/infrastructure/2019/metricsdb.html" rel="noopener noreferrer">MetricsDB</a> 是专门用于处理监控、仪表板、警报等的，不支持通用查询。这完全合理，因为监控和仪表板在可观测性需求的马斯洛层次结构中低于通用指标分析。在其他公司人士的非正式讨论中，围绕 MetricsDB 的整个系统似乎解决了困扰其他类似规模公司的问题，但其专业化意味着我们无法对 MetricsDB 中的指标运行任意 SQL 查询。</p>

<p>另一种查询数据的方法是使用写入 <a href="https://en.wikipedia.org/wiki/Apache_Hadoop#HDFS" rel="noopener noreferrer">HDFS</a> 的 <a href="https://en.wikipedia.org/wiki/Apache_Parquet" rel="noopener noreferrer">Parquet</a> 格式的副本，它允许人们运行任意 SQL 查询（以及编写消费数据的 <a href="https://en.wikipedia.org/wiki/Cascading_(software)#cite_ref-27" rel="noopener noreferrer">Scalding</a>（MapReduce）作业）。</p>

<p>不幸的是，由于指标名称数量众多，HDFS 上的数据无法以列式格式存储（每个名称一列）——<a href="https://en.wikipedia.org/wiki/Presto_(SQL_query_engine)" rel="noopener noreferrer">Presto</a> 如果列数过多会出问题，而我们有足够多的不同指标，远远超过了那个限制。如果不使用列式格式（并且不应用任何其他技巧），任何非平凡查询都会读取大量数据。结果是，你无法跨所有服务或所有主机运行任何非平凡查询（甚至许多平凡查询）而不超时。Scalding 没有类似的超时限制，但其性能要差得多，一个简单的 Scalding 查询针对一天的数据通常需要三到二十小时，具体取决于集群负载，这使得 Scalding 不适合任何探索性数据分析。</p>

<p>鉴于已有的数据基础设施，解决这两个问题的一个简单方法是编写一个 Scalding 作业，存储我们关心的性能或容量相关查询中 0.1% 到 0.01% 的指标数据，并将其重写为列式格式。我猜测至少 90% 的指标是几乎在任何情况下都几乎没人会想看的东西，而在任何人在乎的指标中，绝大多数与性能无关。这样做还有一个可喜的副作用，因为只有极小一部分数据是相关的，所以长期存储它的成本很低。标准的指标数据转储在几周后会被删除，因为其规模大到长期存储成本过高；更长的指标记忆期对于容量规划或其他更倾向于拥有历史数据的分析将很有用。</p>

<p>我们保存的数据包括（但不限于）每个服务的每个分片的以下内容：</p>

<ul>
<li>各种缓冲区的使用率和大小
</li><li>CPU、内存和其他利用率
</li><li>线程数、上下文切换、核心迁移
</li><li>各种队列深度和网络统计
</li><li>JVM 版本、功能标志等
</li><li><a href="https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)" rel="noopener noreferrer">GC</a> 统计信息
</li><li><a href="https://github.com/twitter/finagle" rel="noopener noreferrer">Finagle</a> 指标
</li></ul>

<p>以及每台主机的：</p>

<ul>
<li>来自 <a href="https://en.wikipedia.org/wiki/Procfs" rel="noopener noreferrer">procfs</a> 的各种信息，如 <code>iowait</code> 时间、<code>空闲</code> 等
</li><li>机器所属的集群
</li><li>主机级信息，如网卡速度、主机核心数、内存
</li><li>主机级"健康"问题统计，如热节流、机器检查等
</li><li>操作系统版本、主机级软件版本、主机级功能标志等
</li><li><a href="https://github.com/twitter/rezolus" rel="noopener noreferrer">Rezolus</a> 指标
</li></ul>

<p>对于变化非常频繁的东西（如主机网卡速度），我们每天存储一次，但大多数内容都以与其他指标相同的频率和粒度存储。在某些情况下，这显然是浪费的（例如，对于 JVM 临时阈值，这通常在服务的所有分片中都相同且很少变化），但考虑到我们现有的指标基础设施，这是最简单的处理方式。</p>

<p>虽然这个项目的初衷是找出哪些服务的 JVM 幸存者空间配置不足或过度，但它始于 GC 和容器指标，因为这些是非常明显的观察点，此后我们一直在逐步添加其他指标。为了了解我们可以查询的内容类型以及如果你懂一些 SQL，查询有多简单，这里有一些示例：</p>

<h4 id="very-high-p90-jvm-survivor-space">极高的 p90 JVM 幸存者空间</h4>

<p>这是最初目标（找出配置不足/过度的服务）的一部分。任何具有极高 p90 JVM 幸存者空间使用率的服务，其幸存者空间很可能配置不足。类似地，任何在峰值负载下 p99 或 p999 JVM 幸存者空间使用率极低的服务，很可能配置过度（此处未显示查询，但我们可以将查询范围限定在高负载时段）。</p>

<p>一个 Presto 查询，用于获取跨所有服务的极高 p90 幸存者空间：</p>

<pre><code>with results as (
  select servicename,
    approx_distinct(source, 0.1) as approx_sources, -- 服务的分片数量
    -- 实际查询使用 [coalesce and nullif](https://prestodb.io/docs/current/functions/conditional.html) 处理边缘情况，为简洁起见省略
    approx_percentile(jvmSurvivorUsed / jvmSurvivorMax, 0.90) as p90_used,
    approx_percentile(jvmSurvivorUsed / jvmSurvivorMax, 0.50) as p50_used,
  from ltm_service 
  where ds &gt;= '2020-02-01' and ds &lt;= '2020-02-28'
  group by servicename)
select * from results
where approx_sources &gt; 100
order by p90_used desc
</code></pre>

<p>无需查看大量仪表板，我们可以直接获取列表，然后向相应的团队发送配置变更差异，或者编写一个脚本，获取查询输出并自动生成差异。上述查询为任何基本的利用率数字或比率提供了模式；你可以用类似的查询查看内存使用情况、新旧代 GC 频率等。在一个案例中，我们发现一个服务浪费了足够的 RAM，足以支付我十年的工资。</p>

<p>我正在逐步远离使用简单百分比的阈值来发现问题，但我展示这个查询是因为这是人们通常想做的、有用的事情，而且我可以编写它而无需花费大量篇幅解释为什么这是合理的事情；我更倾向于做的事情超出了本文的范围，可能值得单独写一篇文章。</p>

<h4 id="network-utilization">网络利用率</h4>

<p>上述查询是跨所有服务的，但我们也可以跨主机进行查询。此外，我们可以进行连接主机属性、功能标志等的查询。</p>

<p>使用一组查询，我们能够确定有相当多的服务尽管主机级网络利用率很低，却触及了网络限制。然后，计算平台团队逐步推出了网络上限的更改，我们使用类似下面的查询进行监控，以确定没有出现性能下降（理论上，如果增加网络上限导致主机或交换机触及网络限制，可能会发生这种情况）。</p>

<p>通过网络更改，我们能够观察到更小的队列深度、更小的队列大小（字节数）、更少的数据包丢弃等。</p>

<p>下面的查询仅显示队列深度以保持简洁；添加所有提到的数量只需多输入一些名称。</p>

<p>我们可以做的一般事情是，对于平台或服务级功能的任何特定推出，我们可以看到其对实际服务的影响。</p>

<pre><code>with rolled as (
 select
   -- 推出在时间段内对所有主机是固定的，可以从时间段中任意选择一个元素
   arbitrary(element_at(misc, 'egress_rate_limit_increase')) as rollout,
   hostId
 from ltm_deploys
 where ds = '2019-10-10'
 and zone = 'foo'
 group by ipAddress
), host_info as(
 select
   arbitrary(nicSpeed) as nicSpeed,
   hostId
 from ltm_host
 where ds = '2019-10-10'
 and zone = 'foo'
 group by ipAddress
), host_rolled as (
 select
   rollout,
   nicSpeed,
   rolled.hostId
 from rolled
 join host_info on rolled.ipAddress = host_info.ipAddress
), container_metrics as (
 select
   service,
   netTxQlen,
   hostId
 from ltm_container
 where ds &gt;= '2019-10-10' and ds &lt;= '2019-10-14'
 and zone = 'foo'
)
select
 service,
 nicSpeed,
 approx_percentile(netTxQlen, 1, 0.999, 0.0001) as p999_qlen,
 approx_percentile(netTxQlen, 1, 0.99, 0.001) as p99_qlen,
 approx_percentile(netTxQlen, 0.9) as p90_qlen,
 approx_percentile(netTxQlen, 0.68) as p68_qlen,
 rollout,
 count(*) as cnt
from container_metrics
join host_rolled on host_rolled.hostId = container_metrics.hostId
group by service, nicSpeed, rollout
</code></pre>

<h4 id="other-questions-that-became-easy-to-answer">变得容易回答的其他问题</h4>

<ul>
<li>X 的延迟、CPU 使用率、CPI 或其他性能影响是什么？
<ul>
<li>增加或减少每个容器监控的性能计数器数量
</li><li>调整内核参数
</li><li>操作系统或其他发布版本
</li><li>增加或减少主机级超额订阅
</li><li>一般的主机级负载
</li><li>重试预算耗尽
</li></ul></li>
<li>对于上面相关的项目，在一般情况下或特定条件下，X 的分布是什么？
</li><li>哪些主机在控制了负载等因素后，其上所有服务都表现出异常差的服务级性能？
<ul>
<li>这通常被证明是由于硬件配置错误或故障
</li></ul></li>
<li>除了对主机级负载的一般影响外，哪些服务与其他服务"相处不融洽"？
</li><li>故障转移或其他高负载事件的延迟影响是什么？
<ul>
<li>考虑到未来的高负载事件和当前增长，我们应该预期未来会有什么水平的负载？
</li><li>哪些服务在故障转移期间负载增加，哪些服务负载不变，哪些介于两者之间？
</li></ul></li>
<li>对于任何固定大小的缓冲区或分配，我们可以进行哪些配置更改以在不增加成本的情况下提高性能或在不降低性能的情况下降低成本？
</li><li>对于某些特定的主机级健康问题，如果我们看到 N 次，它再次出现的概率是多少？
</li><li>等等，有许多问题在你可以对历史指标数据编写任意查询时变得容易回答
</li></ul>

<h4 id="design-decisions">设计决策</h4>

<p>LTM 是一个尽可能无聊的系统。每个设计决策都源于采取阻力最小的路径。</p>

<ul>
<li>为什么使用 Scalding？
<ul>
<li>它是 Twitter 的标准工具，集成使得一切都变得简单。我尝试过 Spark，它有一些优势。然而，当时，使用 Scalding 我可以免费获得我需要的集成，而 Spark 则需要手动集成工作。
</li></ul></li>
<li>为什么使用 Presto 而不是像 Druid 这样允许实时切片和切块查询的工具？
<ul>
<li><a href="https://danluu.com/tracing-analytics/" rel="noopener noreferrer">Rebecca Isaacs 和 Jonathan Simms 当时正在做相关的链路追踪工作</a>，我们知道我们希望 LTM 与他们创建的任何系统进行连接。这在 Presto 中很简单，但当时使用像 Druid 这样的工具需要更多的规划和工作。
</li><li>George Sirois 将一部分数据导入了 Druid，以便我们可以试用它，它提供的功能非常好；可能值得在某个时候重新审视它。
</li></ul></li>
<li>为什么不用 Postgres 或类似的工具？
<ul>
<li>我们想要存储的数据量使得这在没有大量努力的情况下不可行；尽管数据存储成本相当低，但它仍然是一个"大数据"问题。
</li></ul></li>
<li>为什么用 Parquet 而不是更高效的格式？
<ul>
<li>在支持的标准格式中，它是最合适的（另一个主要支持的格式是原始 thrift），引入新格式将比这个项目大得多。
</li></ul></li>
<li>为什么系统不是实时的（至少延迟一小时）？
<ul>
<li>Twitter 的批处理作业管道很容易构建，只需要阅读一些关于其工作原理的教程，然后编写类似的东西，但具有不同的业务逻辑。
</li><li>几年前，在我加入 Twitter 之前，有人提出了一个关于构建实时分析管道用于指标数据的方案，但最终没有建成，因为（我估计）制作一个 MVP 需要一到四个季度的工作，并且不清楚哪个团队有合适的授权来做这件事，同时有四个季度的人手。但增加一个批处理作业只需要一天，你不需要为一天的工作进行路线图和规划会议，你可以直接去做，然后增量地进行后续工作。
</li><li>如果我们寻找的是配置错误或优化机会，它们很少在一小时内消失（如果它们消失了，那它们的总体影响一定很小），而且实际上它们通常会持续数月到数年，所以我们放弃实时性并不会损失太多（我们确实失去了将输出用于某些监控用例的能力）。
</li><li>实时版本将会是一个系统，其显著的运营成本使得一个人无法在不承担过度负担的情况下操作它。这个系统比我预想的有更多的运营/维护负担，平均每月大约需要我 1-2 天的时间，这在目前构成了系统总成本中相当大的一部分，但它从不需要报警，而且工作量很容易由一个人处理。
</li></ul></li>
</ul>

<h4 id="boring-technology">无聊的技术</h4>

<p>我认为撰写关于像这样仅仅是无聊工作的系统的文章被严重低估了。我阅读的许多帖子和演讲都使用了热门技术系统。我对新的热门技术没有任何意见，但许多有用的工作来自于将无聊的技术组合在一起并做显而易见的事情。由于关于无聊工作的帖子和演讲相对较少，我认为撰写像这样的东西比它理应具有的价值更大。</p>

<p>例如，几年前，在当地由 Matt Singer 为我们这类规模的公司组织的基础设施讨论会上（基本上是比 FB/Amazon/Google 小的公司），我询问是否有人做过类似我们刚完成的工作。当时没有一个人在做（或者至少没人承认），两家不同公司的工程师表示震惊，我们竟然可以存储如此多的数据，而不仅仅是每个时间段的平均值，甚至还有一些直方图信息。这项工作过于直截了当和显而易见，不可能是新颖的，我确信许多地方的人已经构建了类似的系统。这实际上只是将指标数据长期存储在 HDFS（或者，如果你喜欢更通用的术语，数据湖）上，采用允许交互式查询的格式。</p>

<p>如果你计算一下在我们这类规模的公司中，像这样的指标数据存储项目的存储成本，它基本上就是四舍五入的误差。我们单独发布的变更就很容易支付了数十年的存储成本。我认为当人们一直部署成本更高的分析和可观察性工具时，没有理由对存储几年甚至十年的指标感到震惊。但事实证明，这很令人惊讶，部分原因是人们不撰写如此无聊的工作。</p>

<p>另一个不相关的例子是，前段时间，我在一家类似规模的公司遇到一个人，他想从指标数据中获得类似的见解。他们没有从像这个项目这样一天就能完成的东西开始，而是从深度学习开始。虽然我认为将机器学习和/或统计应用于基础设施指标是有价值的，但他们将一个可能在几个人天内为公司带来显著价值的项目变成了一个需要人年的项目。如果你只打算<em>要么</em>应用由具有基础设施经验的人指导的简单启发式方法和简单统计模型，<em>要么</em>天真地应用深度学习，我认为前者具有更高的投资回报率。将复杂的统计/机器学习<em>和</em>实践者指导的启发式方法结合起来可以比单独使用任何一种获得更好的结果，但我认为从一个需要一天构建、再花一两天开始应用的简单项目开始，而不是从一个需要数月或数年构建和应用的项目开始，更有意义。但有很多偏见倾向于做更大的项目：它能成为一个更好的简历项目（深度学习！），在许多地方，它能成为一个更好的晋升案例，人们更有可能就使用深度学习的酷系统发表演讲或撰写博客文章。</p>

<p>上述讨论了为什么撰写工作对整个行业有价值。<a href="https://danluu.com/corp-eng-blogs/" rel="noopener noreferrer">我们在之前的文章中讨论了为什么撰写工作对进行撰写的公司有价值</a>，所以我这里不再赘述。</p>

<h4 id="appendix-stuff-i-screwed-up">附录：我搞砸的事情</h4>

<p><a href="https://twitter.com/danluu/status/1220228489522974721" rel="noopener noreferrer">我认为遗憾的是，你听不到系统的缺点，除非通过非正式渠道交流</a>，所以这里列出一些我事后看来相当明显的错误。当其他事情事后变得明显时，我会添加到这里。</p>

<ul>
<li>没有将几乎所有内容都使用 double 类型
<ul>
<li>在理想世界中，有些东西不是 double，但我们指标栈中的所有内容都经过了一个阶段，基本上每个指标都被转换为 double
</li><li>我将大多数"应该"是整数类型的东西存储为整数类型，但执行从 <code>long -&gt; double -&gt; long</code> 的转换永远不会比直接执行 <code>long -&gt; double</code> 转换更精确，而且它为其他问题打开了大门
</li><li>我将一些不应该作为整数类型的东西存储为整数类型，这导致小值不必要地失去精度
<ul>
<li>幸运的是，这尚未导致我做过的任何可操作分析出现严重错误，但有些分析可能会出现问题
</li></ul></li>
</ul></li>
<li>使用断言而不是将错误条目写入某种"错误条目"表
<ul>
<li>由于超出本文范围的原因，在 Scalding 作业中记录错误或警告没有真正合理的方法，因此我使用断言来捕获不应该发生的事情，这导致每次发生意外情况时整个作业都会终止；更好的解决方案是将错误输入条目写入一个表中，然后如果该表不为空，则将该表作为软警报发送电子邮件
<ul>
<li>一个例子，如果有一个记录错误条目的表，可以节省一些运营开销，例如我们遇到了异常的时钟偏移（3600年），导致时间戳溢出。如果我有一个记录错误条目的表，错误条目将从输出中省略，这是正确的行为，这将节省一次中断以及不得不推送修复并重新部署作业。
</li></ul></li>
</ul></li>
<li>代码中的 Longterm vs. LongTerm
<ul>
<li>我最初编写时不确定应该如何大写，而当我做出决定后，我没有搜索并消除所有写错的内容，所以现在这种无意义的不一致性在多个地方存在
</li></ul></li>
</ul>

<p>当你快速赶制东西而没有充分思考时，就会出现这类事情。最后一项很容易修复，问题不大，因为 Twitter 普遍使用 IDE 意味着基本上任何受影响的人都会得到 IDE 提供的正确大写。</p>

<p>第一项问题更大，因为它实际上可能导致分析不正确，而且修复它需要对我们现有的所有数据进行迁移。我猜到这一步，这大约需要半周到一周的工作，而我本来可以通过多花三十秒思考一下我在做什么来轻松避免。</p>

<p>第二项介于两者之间。在第一项和第二项之间，我认为我大概为此系统的直接工作量增加了大约一倍（所以，不包括在系统数据上花费的分析时间，只是构建系统所花的时间），这基本上没有带来任何好处。</p>

<p><small>感谢 Leah Hanson、Andy Wilcox、Lifan Zeng 和 Matej Stuchlik 的评论/修正/讨论</small></p>

<p>


</p>
<div>

<hr>

<ol>
<li id="fn:D"><p>实际工作量大约是一天，但它是在一周内完成的，因为我还需要学习 Scala、Scalding 和一般的 Twitter 基础设施栈、指标栈等。</p>

<p>一天也只是一个初始数据集工作的估计。从那时起，我可能又做了几周的工作，Wesley Aptekar-Cassels 和 Kunal Trivedi 可能也投入了另一两周的时间。运营成本可能平均每月大约需要我 1-2 天的时间，将总成本带到大约一两个月的量级。</p>

<p>我也没有计算使用数据集所花费的时间，或调试问题所花费的时间，这包括很多我只能粗略猜测的时间，例如，当计算平台团队根据大约一小时的数据分析更改了网络出口限制时，暴露了一个潜在的 mesos 错误，可能花费了 Ilya Pronin 一天的时间，David Mackey 花费了相当多的时间来追踪数据显示有些奇怪但不知道是什么的问题，等等。如果你想完全计算因讨论的数据集上的数据分析而产生的工作所花费的时间，我怀疑，在服务级团队、加上我们的 JVM、OS 和硬件等平台级团队之间，我们可能大约达到了一个人年的时间。</p>

<p>但是，因为创建一个可用且有用的系统所需的初始工作是一天加上编写入门材料的时间，并且该系统带来了七位数的回报，所以很容易为所有这些额外的时间花费辩护，如果需要一年的前期工作，情况可能就不是这样了。其余的大部分时间不是那种通常在路线图评审中"计入"的创建系统的时间（用户花费的时间、运营开销），但也许持续的运营成本应该在创建系统时被"计入"（我不认为应该将用户花费的时间"计入"系统，因为系统越有用，用户花费的使用时间就越多，这并不真正算是成本）。</p>

<p>还有人在这个基础上构建工具，Kunal Trivedi 花费了相当多的时间在其上构建一层，使呈现比 SQL 查询更用户友好，这可以说应该计入这个项目。
 <a href="#fnref:D" rel="noopener noreferrer"><sup>[返回]</sup></a></p></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 12283 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/metrics-analytics/</link>
      <guid isPermaLink="false">https://danluu.com/metrics-analytics/</guid>
      <pubDate>Sat, 30 May 2020 07:06:34 +0000</pubDate>
    </item>
    <item>
      <title>（部分）优秀的企业工程博客是如何写成的</title>
      <description>[AI 摘要] 文章探讨了优秀企业工程博客的共同特征：精简的审批流程、极少的非工程干预以及高层领导的明确支持，而乏味的博客则因冗长官僚流程而内容空洞。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章探讨了优秀企业工程博客的共同特征：精简的审批流程、极少的非工程干预以及高层领导的明确支持，而乏味的博客则因冗长官僚流程而内容空洞。</div><p>我一直在与运营企业工程博客的人交流，我认为奇怪的一点是，即使对于一家估值九位到十位数的公司，我的个人博客获得的流量也常常超过其整个工程博客的流量，而我的博客流量高出一个数量级的情况也并不少见。</p>

<p>我觉得这很奇怪，因为这类科技公司通常有成百上千名员工。他们几乎肯定比我更有能力写出引人入胜的博客，并且公司从引人入胜的博客中获得的价值也远大于我。</p>

<p>就前者而言，公司的员工会完成更有趣的工程工作、拥有更多有趣的故事，并且比任何拥有个人博客的个人拥有更深入的知识。就后者而言，我的博客有助于我求职，也有助于公司招聘。但我只需要一份工作，因此，更多的曝光最多能让我找到一份稍好一点的工作，而除了我工作过的那家公司外，所有其他我工作过的科技公司都迫切需要招聘，并且经常流失候选人给其他公司。此外，即使我们面试同一个职位，如果公司喜欢我们中的多个候选人，它通常只会创造更多职位，所以我在面试时并不真正与其他候选人竞争。这个博客在求职方面的关键作用在于，该流程是否能接受显著的非面试反馈，或者我是否会因为他们进行常规面试而失败。对于这一点，额外一篇文章的边际价值可能非常低。另一方面，公司在招聘方面的竞争相对直接，因此相对于另一家公司，更有吸引力对他们来说是有价值的；复制Cloudflare或Segment在其工程“品牌”方面所采用的策略将带来显著的招聘优势。这个策略并非秘密：这些公司向世界广播其成果，并且通常很乐意谈论他们的博客流程。</p>

<p>尽管拥有一个“优秀”的企业工程博客似乎有明显的好处，但大多数企业工程博客充斥着工程师不想阅读的内容。关于一切多么了不起的模糊、高层次的空话，内容营销，关于新热点的含糊其辞的文章（今天，可能是将深度学习应用于不适当的场景；十年前，可能是将“大数据”应用于不适当的场景）等。</p>

<p>为了理解拥有优秀企业工程博客的公司有何共同点，我采访了三家拥有引人入胜的企业工程博客的公司（Cloudflare、Heap和Segment）以及三家拥有乏味的企业工程博客的公司（我不会透露其名称）的员工。</p>

<p>从高层次来看，引人入胜的工程博客的流程具有以下共同特征：</p>

<ul>
<li>简单的审批流程，不需要太多审批环节</li>
<li>很少或不需要非工程审批</li>
<li>审批流程有隐式或显式的快速<a href="https://en.wikipedia.org/wiki/Service-level_objective" rel="noopener noreferrer">SLO</a>（服务水平目标）</li>
<li>审批/编辑流程主要是让帖子对工程师更具吸引力</li>
<li>高层领导（联合创始人、C级高管或VP级别）直接、高层次地支持保持博客流程轻量化</li>
</ul>

<p>吸引力较弱的工程博客的流程则具有以下共同特征：</p>

<ul>
<li>审批流程缓慢</li>
<li>需要多个审批环节</li>
<li>需要重要的非工程审批
<ul>
<li>非工程审批会提出让作者感到沮丧的修改意见</li>
<li>来回修改可能持续数月</li>
</ul></li>
<li>审批/编辑流程主要是为了降低帖子的风险、移除具体引用、让帖子变得更模糊，对工程师的吸引力更低</li>
<li>实际上没有高层支持博客
<ul>
<li>领导层可能抽象地认同博客是好的，但其优先级不够高，无法采取具体行动</li>
<li>改革流程以使博客更容易非常困难；之前的尝试已经失败</li>
<li>改变流程以减少开销需要所有“利益相关者”签字同意（在一个案例中是14人）
<ul>
<li>任何单一利益相关者都可以否决</li>
<li>没有任何单一利益相关者可以批准</li>
</ul></li>
<li>利益相关者对批准任何减少开销的提议持谨慎态度
<ul>
<li>批准意味着承担感知到的风险（万一发生不好的事情），而对他们来说没有感知到的好处</li>
</ul></li>
</ul></li>
</ul>

<p>一家拥有引人入胜博客的公司的一位员工指出，只有一个审批人和/或一个主要审批人的缺点是，如果此人很忙，帖子可能需要几周才能获得批准。这是公平的，这是集中式审批的一个缺点。然而，当我们与其他流程进行比较时，在一家公司，人们指出审批通常需要三到六个月，尾部案例可能需要一年。</p>

<p>对于习惯了快节奏公司的人来说，几周似乎很长，但对于在较慢节奏公司工作的人来说，如果审批流程只需要两倍的时间，他们会欣喜若狂。</p>

<p>以下是我采访的三家公司（以<code>sha512sum</code>顺序呈现，巧合的是按公司规模递增排序，从几百名员工到近千名员工）向我描述的流程：</p>

<h4 id="heap">Heap</h4>

<ul>
<li>有人有一个写文章的想法</li>
<li>作者（工程师）与一位“伙伴”配对，伙伴负责编辑并批准文章
<ul>
<li>伙伴是一位有良好写作记录的工程师</li>
<li>这可能需要几轮修改，可能会改变文章的主旨</li>
</ul></li>
<li>CTO阅读并批准
<ul>
<li>通常只有很小的反馈</li>
<li>可能会提出类似“设计师可以让这个图表更好看”的建议</li>
</ul></li>
<li>发布文章</li>
</ul>

<p>第一轮编辑阶段过去是在一个Slack频道里发布草稿，让“所有人”评论文章。这是一次不愉快的体验，因为“所有人”都会发表评论，需要大量修改。这个流程旨在避免收到“过多”的反馈。</p>

<h4 id="segment">Segment</h4>

<ul>
<li>有人有一个写文章的想法
<ul>
<li>通常来源于：内部文档、外部演讲、已发布的项目、Segment构建的开源工具</li>
</ul></li>
<li>作者（工程师）撰写草稿
<ul>
<li>可能会有一位资深工程师与他们一起撰写草稿</li>
</ul></li>
<li>直到最近，没有真正的人负责反馈流程
<ul>
<li>Calvin French-Owen（联合创始人）和Rick（工程经理）通常会提供大部分反馈</li>
<li>可能还会从经理和工程领导那里得到反馈</li>
<li>通常，第3稿被认为是完成稿</li>
<li>现在，有一位全职编辑负责编辑文章</li>
</ul></li>
<li>还会在工程团队内交流，从15-20人那里获得反馈</li>
<li>公关和法务会看一下，进行轻量级审批</li>
</ul>

<p>已经做出的一些改变包括：</p>

<ul>
<li>曾经在试图建立“工程品牌”时，将撰写深度技术文章作为最高优先级</li>
<li>举办过“博客静修”，花一周时间撰写一篇文章</li>
<li>在绩效评估和职业阶梯中明确将写作和演讲作为奖励标准</li>
</ul>

<p>尽管有法务和公关审批，Calvin指出“总的来说，我们尽量保持轻量化。我认为博客更大的问题是缺乏文章，或者是模糊、高层次、不感兴趣的内容，而不是泄露太多信息。”</p>

<h4 id="cloudflare">Cloudflare</h4>

<ul>
<li>有人有一个写文章的想法
<ul>
<li>内部博客是文化的一部分，有些文章来自内部博客</li>
</ul></li>
<li>John Graham-Cumming（CTO）阅读每一篇文章，其他人也会阅读并发表评论
<ul>
<li>John是文章的审批人</li>
</ul></li>
<li>Matthew Prince（CEO）也普遍支持博客</li>
<li>“非常快速”的法务审批流程，SLO为1小时
<ul>
<li>这个流程如此轻量级，以至于一个人并没有真正将其视为审批，另一个人甚至没有提到它（第三个人确实提到了这一步骤）</li>
<li>公关通常不参与</li>
</ul></li>
</ul>

<p>需要注意的是，这仅适用于技术博客文章。产品公告有更重的流程，因为它们与销售材料、新闻稿等相关联。</p>

<p>我发现有趣的一件事是，Marek因为Cloudflare的博客而面试（<a href="https://blog.cloudflare.com/a-tour-inside-cloudflares-latest-generation-servers/" rel="noopener noreferrer">这篇关于他们第四代服务器的2013年博客文章引起了他的注意</a>），现在他既是他们的关键工程师之一，也是Cloudflare博客上引人入胜文章的主要来源之一。目前，Cloudflare博客已经至少培养了几代员工，他们因为看到博客文章而来面试，现在又为博客撰写引人入胜的文章。</p>

<h4 id="negative-example-1">反面案例 #1</h4>

<ul>
<li>许多人建议我将这家公司作为正面案例，因为他们在早期有一个像上面那样的半轻量化流程</li>
<li>唯一使流程变得非轻量化的因素是，一位创始人坚持审批文章，并且经常会大量重写，但博客当时很成功，是招聘的重要驱动力</li>
<li>随着公司规模扩大，创始人审批花费的时间越来越长，导致博客流程出现长时间延迟</li>
<li>在某个时候，聘请了一位外部人士接管博客发布流程，因为领导层认为这很重要</li>
<li>之后，流程充满了典型的反模式，审批需要数月，有工程师感到沮丧的多轮修改，使他们的博客文章吸引力降低
<ul>
<li>多人告诉我，他们在写了一篇文章后发誓再也不为公司写博客文章了，因为这个过程太痛苦了</li>
<li>好消息是，由于博客曾经拥有合理流程的悠久历史，博客良好产出的记忆仍然给许多外部人士留下了对其公司及其工程的良好印象</li>
</ul></li>
</ul>

<h4 id="negative-example-2">反面案例 #2</h4>

<ul>
<li>我的一个朋友试图发布一篇博客文章，结果“公关部门”审批花了六个月</li>
<li>在上述事件发生约一年后，由于“反面案例 #1”的声誉，“反面案例 #2”聘请了在“反面案例 #1”负责该流程的人担任公关/传播部门的高级职位，并负责该公司的博客发布流程。在“反面案例 #1”，此人是在博客从工程师愿意写的东西变成博客流程如此繁琐以至于工程师在写了一篇文章后发誓再也不写的时候接手的</li>
<li>聘请“反面案例 #1”衰落期间负责人来改善“反面案例 #2”的流程，并未使流程精简，也没有在“反面案例 #2”产生更多或更好的产出</li>
</ul>

<h3 id="general-comments">总体评论</h3>

<p>我的观点是，企业工程博客的自然状态是，当人们能<a href="https://danluu.com/p95-skill/" rel="noopener noreferrer">得到一些反馈</a>时，它会相当有趣。目前<a href="https://twitter.com/rakyll/status/1043952902157459456" rel="noopener noreferrer">缺乏真实的、深入的技术写作</a>，这使得任何半吊子的、诚实的、关于技术工作的公开写作都显得有趣。</p>

<p>为了拥有一个无聊的博客，公司必须积极阻止工程师发布有趣的内容。不幸的是，大型公司的自然状态往往倾向于规避风险，阻止人们写作，以防它引起法律、公关或其他问题。个人贡献者（IC）可能认为阻止工程师撰写低风险技术文章是荒谬的，而与此同时，C级高管和副总裁经常发表演变成公关灾难的公开评论，但大型公司中的IC没有权威，或者觉得他们没有权威仅仅因为某事有意义就去做。而这十四个必须签字批准精简流程的利益相关者中，没有一个关心精简流程，因为这对公司有好处，但对他们自身没有真正的影响，而不像是要承担精简流程可能带来的风险责任（无论多小）。一位愿意冒险的高管或高级副总裁可以承担责任并处理后果，并且如果他们对工程招聘或士气感兴趣，他们可能会看到这样做的理由。</p>

<p>我经常从更官僚化的公司的人那里听到类似“我们这种规模的公司都是这样”的评论，但这并不正确。Cloudflare，一家拥有近1000名员工、估值60亿美元的公司，与许多其他博客流程更为繁琐的公司处于同一规模等级。企业工程博客的情况似乎类似于提供真实面试反馈的情况。<a href="http://blog.interviewing.io/no-engineer-has-ever-sued-a-because-of-constructive-post-interview-feedback-so-why-dont-employers-do-it/" rel="noopener noreferrer">interviewing.io声称这样做有显著的好处，而坏处很小</a>。一些公司确实提供真实的反馈，并且这样做的公司通常发现这给它们带来了招聘方面的轻松优势，几乎没有坏处，但绝大多数公司不这样做，而这些公司的人会声称提供反馈是不可能的，因为会被起诉或者公司会被“取消”，即使对于提供反馈的公司来说通常不会发生这种情况，甚至在一些整个行业都普遍提供面试反馈的领域。很容易含糊地说存在风险，而当来自多个组织的模糊风险警告出现时，很少有人有权驳斥它们。</p>

<p>尽管这是一个小样本，从小样本中进行过多概括是危险的，但你需要高层支持来突破官僚主义的观点，与我在其他大多数大型公司难以做一件容易且有明显但分散价值的事情的领域所看到的情况是一致的。虽然这篇文章碰巧是关于博客的，但我听过很多关于各种主题的类似故事。</p>

<h3 id="appendix-examples-of-compelling-blog-posts">附录：引人入胜的博客文章示例</h3>

<p>以下是一些来自上述博客的文章，附有简短评论，说明我认为这些文章引人入胜的原因。这次按sha512哈希值倒序排列。</p>

<h4 id="cloudflare-1">Cloudflare</h4>

<ul>
<li><a href="https://blog.cloudflare.com/how-verizon-and-a-bgp-optimizer-knocked-large-parts-of-the-internet-offline-today/" rel="noopener noreferrer">https://blog.cloudflare.com/how-verizon-and-a-bgp-optimizer-knocked-large-parts-of-the-internet-offline-today/</a>
<ul>
<li>讨论了一个影响许多人的真实技术问题，深度适中</li>
<li>时效性强，仅在故障发生八小时后发布，当时人们仍然非常有兴趣了解发生了什么；大多数公司无法如此快速地写出引人入胜的博客文章，或者只能在特殊情况下做到，Cloudflare能够半定期地发布及时文章</li>
</ul></li>
<li><a href="https://blog.cloudflare.com/the-relative-cost-of-bandwidth-around-the-world/" rel="noopener noreferrer">https://blog.cloudflare.com/the-relative-cost-of-bandwidth-around-the-world/</a>
<ul>
<li>对一些数据的探索</li>
</ul></li>
<li><a href="https://blog.cloudflare.com/the-story-of-one-latency-spike/" rel="noopener noreferrer">https://blog.cloudflare.com/the-story-of-one-latency-spike/</a>
<ul>
<li>一个调试故事</li>
</ul></li>
<li><a href="https://blog.cloudflare.com/when-bloom-filters-dont-bloom/" rel="noopener noreferrer">https://blog.cloudflare.com/when-bloom-filters-dont-bloom/</a>
<ul>
<li>一个调试故事，这次是在开发数据结构的背景下</li>
</ul></li>
</ul>

<h4 id="segment-1">Segment</h4>

<ul>
<li><a href="https://segment.com/blog/when-aws-autoscale-doesn-t/" rel="noopener noreferrer">https://segment.com/blog/when-aws-autoscale-doesn-t/</a>
<ul>
<li>对广泛使用的服务中的一个陷阱的具体解释</li>
</ul></li>
<li><a href="https://segment.com/blog/gotchas-from-two-years-of-node/" rel="noopener noreferrer">https://segment.com/blog/gotchas-from-two-years-of-node/</a>
<ul>
<li>对广泛使用工具中的一个陷阱的具体示例和解释</li>
</ul></li>
<li><a href="https://segment.com/blog/automating-our-infrastructure/" rel="noopener noreferrer">https://segment.com/blog/automating-our-infrastructure/</a>
<ul>
<li>关于公司如何运作的具体细节的文章；理论上任何公司都可以写，但很少有公司这样做</li>
</ul></li>
</ul>

<h4 id="heap-1">Heap</h4>

<ul>
<li><a href="https://heap.io/blog/engineering/basic-performance-analysis-saved-us-millions" rel="noopener noreferrer">https://heap.io/blog/engineering/basic-performance-analysis-saved-us-millions</a>
<ul>
<li>讨论了一个真实的问题和解决方案</li>
</ul></li>
<li><a href="https://heap.io/blog/engineering/clocksource-aws-ec2-vdso" rel="noopener noreferrer">https://heap.io/blog/engineering/clocksource-aws-ec2-vdso</a>
<ul>
<li>讨论了一个真实的问题和解决方案</li>
<li>在Hacker News评论中，工程师（malisper, kalmar）给出了有真实原因的技术回应，而不仅仅是大多数情况下看到的通常掩饰</li>
</ul></li>
<li><a href="https://heap.io/blog/analysis/migrating-to-typescript" rel="noopener noreferrer">https://heap.io/blog/analysis/migrating-to-typescript</a>
<ul>
<li>真实地谈论了首次尝试推动全公司技术变革失败的原因</li>
</ul></li>
</ul>

<p>需要注意的是，这些博客都有不同的风格。就我个人而言，我更喜欢Cloudflare博客的风格，它有更高比例的“深入探讨”技术文章，但不同的人会喜欢不同的风格。有很多风格都可以奏效。</p>

<p><small>感谢Marek Majkowski、Kamal Marhubi、Calvin French-Owen、John Graham-Cunning、Michael Malis、Matthew Prince、Yuri Vishnevsky、Julia Evans、Wesley Aptekar-Cassels、Nathan Reed、Jake Seliger、一位匿名评论者，以及来自我未透露名称的公司的信息源提供的评论/更正/讨论；致谢中明确提到的人员均未提供关于吸引力较弱博客的信息</small></p>

<p>


</p><p><em>由 mimo-v2.5 模型翻译，花费 8871 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/corp-eng-blogs/</link>
      <guid isPermaLink="false">https://danluu.com/corp-eng-blogs/</guid>
      <pubDate>Wed, 11 Mar 2020 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>命令行选项的增长：1979年至今</title>
      <description>[AI 摘要] 文章分析1979年至今Unix命令行选项数量激增的现象，探讨其对软件设计哲学和用户复杂性的影响。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章分析1979年至今Unix命令行选项数量激增的现象，探讨其对软件设计哲学和用户复杂性的影响。</div><p><a href="https://www.xkcd.com/1795/" rel="noopener noreferrer">我的爱好</a>：在一个显示器上打开<a href="https://danluu.com/mcilroy-unix/" rel="noopener noreferrer">麦克罗伊的 UNIX 哲学</a>，在另一个显示器上阅读手册页。</p>

<p>麦克罗伊的准则中第一条常被转述为“只做一件事，并做好它”，这其实是<a href="https://danluu.com/mcilroy-unix/" rel="noopener noreferrer">从</a>“让每个程序只做一件事并做好。要做新工作，应重新构建，而不是通过添加新‘功能’来使旧程序复杂化。”中提炼出来的。</p>

<p>麦克罗伊对此准则的例子是：</p>
<blockquote>
<p>让外界感到惊讶的是，UNIX 编译器不产生列表：打印可以由一个单独的程序完成得更好、更灵活。</p>
</blockquote>

<p>如果你在 Mac 上打开 <code>ls</code> 的手册页，你会看到它以</p>
<pre><code>ls [-ABCFGHLOPRSTUW@abcdefghiklmnopqrstuwx1] [file ...]
</code></pre>
<p>开头。也就是说，<code>ls</code> 的单字母参数包括了除 <code>{jvyz}</code> 以外的所有小写字母、14 个大写字母，以及 <code>@</code> 和 <code>1</code>。仅单字符选项就有 22 + 14 + 2 = 38 个。</p>

<p>在 Ubuntu 17 上，如果你阅读 coreutils 版本的 <code>ls</code> 手册页，虽然看不到选项的简明总结，但你会看到 <code>ls</code> 有 58 个选项（包括 <code>--help</code> 和 <code>--version</code>）。</p>

<p>为了了解 <code>ls</code> 是个特例，还是这类功能繁多的命令很常见，我们可以查看一些按使用频率排序的常用命令。</p>

<p>
</p><table>
<tbody><tr>
<th>命令</th><th>1979</th><th>1996</th><th>2015</th><th>2017</th></tr>
<tr>
<td>ls</td><td>11</td><td>42</td><td>58</td><td>58</td></tr>
<tr>
<td>rm</td><td>3</td><td>7</td><td>11</td><td>12</td></tr>
<tr>
<td>mkdir</td><td>0</td><td>4</td><td>6</td><td>7</td></tr>
<tr>
<td>mv</td><td>0</td><td>9</td><td>13</td><td>14</td></tr>
<tr>
<td>cp</td><td>0</td><td>18</td><td>30</td><td>32</td></tr>
<tr>
<td>cat</td><td>1</td><td>12</td><td>12</td><td>12</td></tr>
<tr>
<td>pwd</td><td>0</td><td>2</td><td>4</td><td>4</td></tr>
<tr>
<td>chmod</td><td>0</td><td>6</td><td>9</td><td>9</td></tr>
<tr>
<td>echo</td><td>1</td><td>4</td><td>5</td><td>5</td></tr>
<tr>
<td>man</td><td>5</td><td>16</td><td>39</td><td>40</td></tr>
<tr>
<td>which</td><td></td><td>0</td><td>1</td><td>1</td></tr>
<tr>
<td>sudo</td><td></td><td>0</td><td>23</td><td>25</td></tr>
<tr>
<td>tar</td><td>12</td><td>53</td><td>134</td><td>139</td></tr>
<tr>
<td>touch</td><td>1</td><td>9</td><td>11</td><td>11</td></tr>
<tr>
<td>clear</td><td></td><td>0</td><td>0</td><td>0</td></tr>
<tr>
<td>find</td><td>14</td><td>57</td><td>82</td><td>82</td></tr>
<tr>
<td>ln</td><td>0</td><td>11</td><td>15</td><td>16</td></tr>
<tr>
<td>ps</td><td>4</td><td>22</td><td>85</td><td>85</td></tr>
<tr>
<td>ping</td><td></td><td>12</td><td>12</td><td>29</td></tr>
<tr>
<td>kill</td><td>1</td><td>3</td><td>3</td><td>3</td></tr>
<tr>
<td>ifconfig</td><td></td><td>16</td><td>25</td><td>25</td></tr>
<tr>
<td>chown</td><td>0</td><td>6</td><td>15</td><td>15</td></tr>
<tr>
<td>grep</td><td>11</td><td>22</td><td>45</td><td>45</td></tr>
<tr>
<td>tail</td><td>1</td><td>7</td><td>12</td><td>13</td></tr>
<tr>
<td>df</td><td>0</td><td>10</td><td>17</td><td>18</td></tr>
<tr>
<td>top</td><td></td><td>6</td><td>12</td><td>14</td></tr>
</tbody></table><p></p>

<p>这个表格列出了各种命令在 V7 Unix (1979)、Slackware 3.1 (1996)、Ubuntu 12 (2015) 和 Ubuntu 17 (2017) 中的命令行选项数量。单元格颜色越深、越蓝，表示选项越多（使用对数尺度），灰色表示未找到该命令。</p>

<p>我们可以看到，命令行选项的数量随时间推移急剧增加；从左到右，条目通常变得更深（选项更多），没有选项减少（颜色变浅）的情况。</p>
<p><a href="https://archive.org/details/DougMcIlroy_AncestryOfLinux_DLSLUG" rel="noopener noreferrer">麦克罗伊长期以来一直批评命令的选项数量、大小和总体功能的增加</a><sup id="fnref:M"><a href="#fn:M" rel="noopener noreferrer">1</a></sup>：</p>
<blockquote>
<p>所有东西都很小，当我看到 Linux 的大小[听不清]时，我心都沉了。那些以前装在 8K（千字节）里的工具，现在要一兆。而手册页，以前真的只是一页，现在变成了一本有上千个选项的小册子……我们过去常在 UNIX 房间里坐着说‘我们能扔掉什么？为什么有这个选项？’通常，这往往是因为基本设计上存在一些缺陷——你没有真正抓住正确的设计点。与其添加一个选项，不如找出为什么、是什么迫使你添加这个选项。这种观点部分是由于硬件非常小……现在已经丢失了，而我们并没有因此变得更好。</p>
</blockquote>

<p>具有讽刺意味的是，命令行选项数量上升的原因之一是麦克罗伊的另一条准则：“编写程序来处理文本流，因为这是一个通用接口”（参见 <code>ls</code> 作为此点的一个例子）。</p>
<p>如果传递的是结构化数据或对象，格式化可以留给最后的格式化步骤处理。但是，使用纯文本时，格式化和内容是混在一起的；因为格式化只能通过解析内容来完成，所以命令通常为了方便而添加格式化选项。或者，用户可以利用他们对数据结构的知识，并将这些知识编码为传递给 <code>cut</code>、<code>awk</code>、<code>sed</code> 等命令的参数（同时也使用他们对这些程序如何处理格式化的知识；不同程序处理方式不同，用户需要了解，例如，<a href="https://unix.stackexchange.com/a/132322/261842" rel="noopener noreferrer">了解 <code>cut -f4</code> 和 <code>awk '{ print $4 }'</code> 的区别</a><sup id="fnref:T"><a href="#fn:T" rel="noopener noreferrer">2</a></sup>）。这比向序列中的最后一个命令传递一两个参数要麻烦得多，它把复杂性从工具推给了用户。</p>
<p>人们有时说他们不想支持结构化数据，因为他们必须支持多种格式才能制作一个通用工具，但他们已经需要支持多种格式来制作通用工具了。一些标准命令无法读取其他命令的输出，因为它们使用不同的格式，<code>wc -w</code> 不能正确处理 Unicode，等等。说“文本”是通用格式，就像说“二进制”是通用格式一样。</p>
<p>我听说有人说命令行工具没有真正的替代方案来应对这种复杂性，但这么说的人从未真正尝试过替代方案，比如 PowerShell。我对 PowerShell 有很多抱怨，但传递结构化数据并轻松地在结构化数据上操作，而无需在脑海中记住元数据信息以便在管道中的正确位置将正确的元数据传递给正确的命令行工具，这并不在我的抱怨之列<sup id="fnref:W"><a href="#fn:W" rel="noopener noreferrer">3</a></sup>。</p>
<p>当有人说我们可以通过让所有东西都处理文本来保持软件简单和兼容时，其障眼法就是假装文本数据没有需要解析的结构<sup id="fnref:C"><a href="#fn:C" rel="noopener noreferrer">4</a></sup>。在某些情况下，我们可以将所有内容视为单个空格分隔的行，或者可能是我们指定的一些行列分隔符的表格（<a href="https://unix.stackexchange.com/a/132322/261842" rel="noopener noreferrer">当然，不同工具的行为并不一致</a>）。当它有效时，这会增加一些麻烦，而当将数据序列化为扁平文本格式会增加相当大的复杂性时，数据的结构意味着简单的扁平化需要大量的解析工作才能以有意义的方式重新摄入数据。</p>
<p>命令现在有更多选项的另一个原因是，人们为本来可以由一系列命令拼凑完成的功能添加了便利标志。这可以追溯到 V7 Unix，其中 <code>ls</code> 有一个选项可以反转排序顺序（如果当时写了 <code>tac</code>，而不是添加一个特殊的反转选项，本可以通过将输出传递给类似 <code>tac</code> 的程序来完成）。</p>
<p>随着时间的推移，添加了更多的便利选项。例如，以一个最初没有选项的命令为例，<code>mv</code> 可以移动<em>并</em>创建备份（三个选项；两个是不同方式的备份指定，其中一个接受参数，另一个不接受显式参数而是从 <code>VERSION_CONTROL</code> 环境变量中读取隐式参数；一个选项允许覆盖默认备份后缀）。<code>mv</code> 现在还有从不覆盖和仅在文件更新时才覆盖的选项。</p>
<p><code>mkdir</code> 是另一个曾经没有选项的程序，排除 SELinux 或 SMACK 的安全事项以及帮助和版本选项后，添加的选项都是便利标志：设置新目录的权限和在父目录不存在时创建它们。</p>
<p>如果我们看看 <code>tail</code>，它最初只有一个选项（<code>-number</code>，告诉 <code>tail</code> 从哪里开始），它添加了格式化和便利选项。在格式化方面，它有 <code>-z</code>，使行分隔符变为 <code>null</code> 而不是换行符。一些便利选项的例子有 <code>-f</code> 在有新更改时打印，<code>-s</code> 设置检查 <code>-f</code> 更改之间的休眠间隔，<code>--retry</code> 在文件不可访问时重试。</p>
<p>麦克罗伊说“添加所有这些选项并没有让我们变得更好”，但我变得更好了。我们讨论过的一些选项我从未使用过，其他选项也只是偶尔使用，但这就是命令行选项的美妙之处——与图形用户界面不同，添加这些选项不会使界面变得杂乱。手册页可能会变得杂乱，但在谷歌和 StackOverflow 的时代，我怀疑许多人只是搜索他们试图做的事情的解决方案，而根本不阅读手册页。</p>
<p>这并不是说添加选项没有代价——更多的选项意味着更多的维护负担，但这是维护者为了用户利益而付出的成本，考虑到维护者与用户的比例，这并非明显不合理。这类似于 Gary Bernhardt 的评论：练习一个演讲五十次是合理的，因为如果有三百人的观众，观看演讲与练习的时间比仍然只有 1:6。总的来说，对于常用的命令行工具，这个比例甚至会更加极端。</p>
<p>有人可能会争辩说所有这些额外的选项给用户带来了负担。这并不完全错误，但这种复杂性负担一直存在，问题只是负担会落在哪里。如果你将一组命令行工具连同 shell 看作形成一种语言——一种任何人都可以编写新方法的语言，如果它流行起来，它实际上会被添加到标准库中，标准是由诸如“编写程序来处理文本流，因为这是一个通用接口”这样的准则定义的——这种语言整体上总会变成一个只写、混乱的烂摊子。至少，对于那些捆绑了比 UNIX 风格更多功能和选项的工具，用户可以用一个仅仅庞大但可能具有某些内部一致性的工具集（尽管彼此不一致）来替换一个巨大且极不一致的工具集。</p>
<p>麦克罗伊暗示问题在于人们思考得不够，老牌的 UNIX 大师们本可以坐在同一间屋子里，思考更久更努力，直到他们想出一套具有“非凡简洁性”的一致工具集。但这永远不会扩展，这种哲学使我们陷入的混乱成为必然。问题不在于思考得更久或更努力；而在于拥有一种哲学，除非你有一个相对较小的团队，具有共享的文化理解，能够坐在同一间屋子里，否则这种哲学无法扩展。</p>
<p>许多我们至今仍困在其中的 UNIX 主要长期反特性和反模式，都源于“我们都应该像在同一个房间里一样行动”的设计哲学，这与你想要创建良好、可用、通用的接口以适应原始设计者未曾想到的问题的方法相反。例如，一个常见的抱怨是现代 shell 和终端缺少许多任何设计现代界面的人都会想要的明显功能。当你与那些以现代原则编写新 shell 和新终端的人交谈时，比如 Jesse Luehrs，他们会指出一个主要问题是 UNIX 模型没有很好地分离接口和实现，如果你要编写一个与五十年前创建的终端行为相同的终端，这没问题，但如果你想构建一个现代终端，这会立即且明显地出现问题。如果每个人都在同一个房间里，系统不需要扩展贡献者数量或随时间扩展，这种设计哲学工作得很好，但这根本不是我们生活的世界。</p>
<p>如果任何人都可以编写一个工具，而主要的指令来自“UNIX 哲学”，那么人们对什么是“<a href="https://twitter.com/hillelogram/status/1174714902151421952" rel="noopener noreferrer">简洁</a>”或“只做一件事”<sup id="fnref:P"><a href="#fn:P" rel="noopener noreferrer">5</a></sup>、什么是正确的做事方式会有不同的看法，不一致性会滋生，导致处理一种极不一致的语言（如 PHP）时会出现的那种复杂性。人们嘲笑 PHP 和 JavaScript 有各种缺陷和奇怪的不一致性，但作为一个语言和标准库，任何常用的 shell 加上广泛使用的 *nix 工具集合在一起要糟糕得多，并且由于即使在单个 Linux 发行版内的不一致性而包含更多意外复杂性，而且没有其他可能的结果。如果你比较 Linux 发行版、BSD、Solaris、AIX 等，用户切换系统时必须记住的意外复杂性量级，远超 PHP 或 JavaScript 的混乱。相比之下，那些被嘲笑最多的编程语言是优秀设计的典范。</p>
<p><a id="maven" rel="noopener noreferrer"></a>需要明确的是，我并不是说我或其他任何人可以凭借 70 年代可用的知识，在当时做出实际有用且在今天看来优雅的系统方面做得更好。事后诸葛亮很容易发现问题。我不同意的是今天的 Unix 大师们发表的评论；像麦克罗伊这样的评论，暗示我们只是忘记了或者不理解简洁的价值，或者<a href="https://twitter.com/danluu/status/885214004649615360" rel="noopener noreferrer">肯·汤普森说 C 和任何语言一样安全，如果我们不想要 bug 就应该只写无 bug 的代码</a>。这类评论暗示我们从后视镜中没有太多可学的；在 70 年代，我们构建系统的方式和今天任何人一样有效；五十年的集体经验、数千万人年，没有教会我们任何东西；如果我们只是回到像最初的 Unix 大师们那样构建系统，一切都会好起来。恕我不能苟同。</p>

<h3 id="appendix-memory">附录：内存</h3>
<p>虽然解决麦克罗伊对二进制大小膨胀的抱怨有点超出范围，但我会指出，在 2017 年，我买了一台拥有 16GB RAM 的 Chromebook，价格是 300 美元。一个 1 兆字节的二进制文件在 1979 年可能是一个严重的问题，那时标准的 Apple II 有 4KB 内存。一台 Apple II 在 1979 年的美元价值是 1298 美元，相当于 2020 年的 4612 美元。你可以买到一台低端 Chromebook，其价格不到它的 1/15，但内存却多出四百万倍。抱怨内存使用增长了一千倍，而一台（便携式！）便宜了一个数量级以上的机器却拥有四百万倍的内存，这似乎有点荒谬。</p>
<p>我更喜欢更精简的软件，这就是为什么我把我的主页优化成两个数据包（如果我的 CDN 服务高压缩的 Brotli，它会是一个数据包），但这纯粹是审美偏好，是我为了好玩而做的事。命令行工具的瓶颈不是内存使用，花时间优化一个占用一兆的工具的内存占用，就像把主页优化成一个数据包一样。也许是一个有趣的爱好，但不应该是任何人强加的。</p>

<h3 id="methodology-for-table">表格方法论</h3>
<p>命令频率来源于 GitHub 上的公开命令历史文件，不一定代表你的个人使用情况。只保留了“简单”命令，因此排除了 curl、git、gcc（有 &gt; 1000 个选项）和 wget 等命令。什么是“简单”是主观的。<a href="https://en.wikipedia.org/wiki/Shell_builtin" rel="noopener noreferrer">Shell 内置命令</a>，如 <code>cd</code> 没有包括在内。</p>
<p>重复选项不计为单独选项。例如，<code>git blame -C</code>、<code>git blame -C -C</code> 和 <code>git blame -C -C -C</code> 行为不同，但这些都会被计为一个参数，即使 <code>-C -C</code> 实际上与 <code>-C</code> 不同。</p>
<p>表格将子选项计为单个选项。例如，<code>ls</code> 有以下内容：</p>
<blockquote>
<p>--format=WORD
across -x, commas -m, horizontal -x, long -l, single-column -1, verbose -l, vertical -C</p>
</blockquote>
<p>尽管有七个格式选项，但这只算一个选项。</p>
<p>明确说明不起作用的选项仍然计为选项，例如，<code>ls -g</code>，其说明为“已忽略；为兼容 Unix 而保留。”，被计为一个选项。</p>
<p>同一选项的多个版本也被视为一个选项。例如，对于 <code>ls</code>，<code>-A</code> 和 <code>--almost-all</code> 被计为一个选项。</p>
<p>在手册页说某个选项应该存在但实际不存在的情况下，该选项不计入表格。例如，V7 <code>mv</code> 的手册页说：</p>
<blockquote>
<p>缺陷</p>
<p>如果 file1 和 file2 位于不同的文件系统上，mv 必须复制文件并删除原始文件。在这种情况下，所有者名称将变为复制进程的名称，并且与任何其他文件的链接关系都将丢失。</p>
<p>Mv 应该像 rm 一样接受 <strong>-f</strong> 标志，如果目标存在且不可写，则禁止询问。</p>
</blockquote>
<p><code>-f</code> 在表格中不计为标志，因为该选项实际上不存在。</p>
<p>表格中最新的年份是 2017 年，因为我在 2017 年写了这篇文章的初稿，直到 2020 年才整理出来。</p>

<h3 id="related">相关内容</h3>
<p><a href="https://blog.plover.com/Unix/tools.html" rel="noopener noreferrer">mjd 谈 Unix 哲学，并对 /usr/bin/time 与内置 time 的混乱进行评述</a>。</p>
<p><a href="https://groups.google.com/forum/m/#!topic/rec.humor.funny/Q-HG4LpW564" rel="noopener noreferrer">mjd 在 1991 年对命令行选项激增的调侃</a>。</p>
<p>在 Hacker News 上：</p>
<blockquote>
<blockquote>
<p>p1mrx:</p>
<p><a href="https://unix.stackexchange.com/q/112125/261842" rel="noopener noreferrer">奇怪的是 ls 已经增长到 58 个选项，但仍然不能输出以 \0 终止的文件名</a></p>
<p>作为一个练习，尝试按大小或日期对目录进行排序，并将结果传递给 xargs，同时支持任何有效的文件名。我最终还是放弃了，让我的脚本忽略任何包含 \n 的文件名。</p>
</blockquote>
<p>whelming_wave:</p>
<p>这里有：按修改时间对当前目录中的所有文件进行排序，并支持文件名中的空格。`printf (od -&gt; sed)' 构造将分隔的字符转回换行符分隔，但你可以用任何接受分隔输入的东西替换它。当然，`sort --zero-terminated' 是 GNU 扩展，有点作弊，但它甚至在 macOS 上可用，所以可能没问题。</p>
</blockquote>
<pre><code>      printf '%b' $(
        find . -maxdepth 1 -exec sh -c '
          printf '\''%s %s\0'\'' "$(stat -f '\''%m'\'' "$1")" "$1"
        ' sh {} \; | \
        sort --zero-terminated | \
        od -v -b | \
        sed 's/^[^ ]*//
      s/ *$//
      s/  */ \\/g
      s/\\000/\\012/g')
</code></pre>
<blockquote>
<p>如果你在 zsh 下运行它，你需要用 `command' 前缀来使用系统可执行文件：zsh 的内置 printf 不支持打印通常可打印字符的八进制转义代码，并且你可能需要将输出分配给一个变量并显式地进行分词。</p>
<p>据我所知，这都是 POSIX 标准的，除了 sort。</p>
</blockquote>
<p><a href="https://en.wikipedia.org/wiki/The_Unix-Haters_Handbook" rel="noopener noreferrer">The Unix haters handbook</a>。</p>
<p><a href="http://www.oilshell.org/blog/2018/01/28.html" rel="noopener noreferrer">为什么要创建一个新的 shell</a>？</p>
<p><small>
感谢 Leah Hanson、Jesse Luehrs、Hillel Wayne、Wesley Aptekar-Cassels、Mark Jason Dominus、Travis Downs 和 Yuri Vishnevsky 的评论/修正/讨论。
</small></p>
<p>


</p>
<div>
<hr>
<ol>
<li id="fn:M">这段引文与我之前看到的版本略有不同，因为我观看了<a href="https://archive.org/details/DougMcIlroy_AncestryOfLinux_DLSLUG" rel="noopener noreferrer">源视频</a>。据我所知，网上（被 Bing、DuckDuckGo 或 Google 索引）的这段引文的每个副本都是一个人对这段引文的转录副本。存在一些歧义，因为音频质量低劣，我听到的与转录那段引文的人听到的有点不同。
 <a href="#fnref:M" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:T">另一个用户因不同命令处理格式化方式不同而承担复杂性的例子是<a href="https://blog.plover.com/Unix/tools.html" rel="noopener noreferrer">时间格式化</a> —— shell 内置的 <code>time</code> 当然与 <code>/usr/bin/time</code> 不一致，用户需要知道这一点并知道如何处理。
 <a href="#fnref:T" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:W"><p>举个例子，你可以对任何对象使用 <code>ConvertTo-Json</code> 或 <code>ConvertTo-CSV</code>，你可以<a href="https://docs.microsoft.com/en-us/powershell/scripting/samples/using-format-commands-to-change-output-view" rel="noopener noreferrer">使用 cmdlet 来改变对象属性的显示方式</a>，并且你可以编写格式化配置文件来定义你偏好的格式。</p>
<p>另一种看待方式是通过<a href="https://en.wikipedia.org/wiki/Conway's_law" rel="noopener noreferrer">康威定律</a>的视角。如果我们有一组由不同人员构建的命令行工具，他们通常在组织上没有联系，除非有人能定义一个标准并让人们采纳它，否则这些工具将会极不一致。这在 Windows 上实际上相对有效，而不仅仅是在 PowerShell 中。</p>
<p>对微软的一个常见抱怨是他们造成了巨大的 API 变动，通常出于非技术性的组织原因（例如，Sinofsky 的权力博弈，就像已删除的推文的回复中描述的那样：<a href="https://twitter.com/stevesi/status/733654590034300929" rel="noopener noreferrer">https://twitter.com/stevesi/status/733654590034300929</a>）。这是事实。即便如此，从一个简单用户的角度来看，现成的 Windows 软件通常比 *nix 更擅长传递非文本数据。这源于 Windows 对非文本数据的拥抱，至少可以追溯到 1999 年的 <a href="https://en.wikipedia.org/wiki/Component_Object_Model" rel="noopener noreferrer">COM</a>（以及可以说是 1990 年和 1987 年分别发布的 OLE 和 DDE）。</p>
<p>例如，如果你从支持二进制格式 <code>A</code> 和 <code>B</code> 的 Foo 复制内容，粘贴到支持格式 <code>B</code> 和 <code>C</code> 的 Bar 中，然后你再从 Bar 复制到支持 <code>C</code> 和 <code>D</code> 的 Baz 中，这将有效，即使 Foo 和 Baz 没有共同支持的格式。</p>
<p>当你剪切/复制时，应用程序基本上会“告诉”剪贴板它能提供哪些格式的数据。当你粘贴到应用程序时，目标应用程序可以请求任何可用格式的数据。如果数据已经在剪贴板中，“Windows”会提供它。如果不在，Windows 会从源应用程序获取数据，然后交给目标应用程序，一份副本会在 Windows 中保存一段时间。如果你从 Excel “剪切”，它会“告诉”你它有数十种格式的数据可用。这种系统对于兼容性相当好，尽管它绝对不是简单或最小化的。</p>
<p>除了很好地支持多种格式并持续足够长时间以至于很多软件都能良好兼容外，Windows 通常还有更好的开箱即用的剪贴板支持。</p>
<p>假设你复制然后粘贴少量文本。大多数情况下，这在 Windows 和 Linux 上都会按预期工作。但现在假设你复制了一些文本，关闭了复制它的程序，然后粘贴。很多人的心智模型是，当你复制时，数据存储在剪贴板中，而不是被复制的程序中。在 Windows 上，软件通常编写为符合这一期望（尽管从技术上讲，剪贴板 API 的用户不必这样做）。这在使用 X 的 Linux 上不太常见，对于大多数软件，正确的心智模型是复制存储的是一个指向数据的指针，数据仍然由复制它的程序拥有，这意味着如果程序关闭，粘贴将无法工作。当我（非正式地）调查程序员时，如果他们没有实际为应用程序做过复制+粘贴相关的工作，他们通常对此感到惊讶。当我调查非程序员时，他们往往觉得这种行为既令人困惑又令人惊讶。</p>
<p>操作系统有效拥有剪贴板内容的缺点是复制大量数据代价高昂。假设你复制了大量文本，许多千兆字节，或者一些复杂的对象，然后从未粘贴它。你并不真的想将这些数据从你的程序复制到操作系统，以便它可用。Windows 也能合理处理这一点：应用程序可以<a href="https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeclip/fa309d1b-8034-44bf-b927-adfc753e69c1" rel="noopener noreferrer">仅在请求时提供数据</a>，当这被认为是有利的。在上述情况下，当有人关闭程序时，程序可以决定是否应该将该数据推送到剪贴板中或丢弃它。在这种情况下，许多软件（例如 Excel）会提示“保留”剪贴板中的数据或丢弃它，这是相当合理的。</p>
<p>在 Linux 上支持部分功能并非不可能。例如，<a href="https://freedesktop.org/wiki/ClipboardManager/" rel="noopener noreferrer">ClipboardManager 规范</a> 描述了一种持久化机制，GNOME 应用程序通常支持它（尽管<a href="https://bugzilla.gnome.org/show_bug.cgi?id=510204#c8" rel="noopener noreferrer">存在一些 bug</a>），但 *nix 上的情况与 Windows 应用程序倾向于支持良好剪贴板行为的情况确实不同。
 <a href="#fnref:W" rel="noopener noreferrer"><sup>[返回]</sup></a></p></li>
<li id="fn:C"><p>另一个例子是在现代编译器之上可用的工具。如果我们回头看看麦克罗伊的经典例子，真正的 UNIX 编译器如此专业化以至于列表是单独的工具，我们可以看到这已经发生了变化，即使你仍然可以使用单独的工具来处理列表。一些常用的 Linux 编译器有成千上万个选项，并做很多事情。例如，<code>clang</code> 现在做的许多事情之一就是静态分析。截至撰写本文时，<a href="https://clang.llvm.org/docs/analyzer/checkers.html#default-checkers" rel="noopener noreferrer">有 79 个常规静态分析检查和 44 个实验性检查</a>。如果这些是单独的命令（可能每个命令都是独立的，或者是一个 <code>static_analysis</code> 命令），它们仍然会依赖相同的底层编译器基础设施，并施加相同的维护负担——让这些静态分析工具处理纯文本并重新实现整个编译器工具链以达到能进行静态分析的程度，这并不真正合理。它们可以是单独的命令而不是捆绑到 <code>clang</code> 中，但它们仍然会依赖于用于编译器的相同机制，并且要么给编译器施加维护和复杂性负担（编译器必须支持为其构建的工具的非破坏性接口），要么它们会经常中断。</p>
<p>“让一切都变成文本，这样就简单了”作为一个简短的流行语很不错，但在现实中，如果你想做真正有用的工作，数据的文本表示通常不是你想要的。</p>
<p>就 clang 而言，无论你是将其做成一个单一的命令还是数千个更小的命令，clang 所做的事情都比 1979 年存在的任何编译器，甚至 1979 年所有编译器加起来都要多。很容易说 1979 年时事情更简单，而我们现代程序员迷失了方向。更难的是实际提出一个真正更简单且能真正被采纳的设计。这样的设计要维持所有现有功能和可配置性，并像 1979 年的东西一样简单，这是不可能的。
 <a href="#fnref:C" rel="noopener noreferrer"><sup>[返回]</sup></a></p></li>
<li id="fn:P">自诞生以来，curl 从支持 3 个协议发展到支持 40 个。这是否意味着它做了 40 件事，将其分成 40 个单独的命令会更“UNIX 风格”？取决于你问谁。如果每个协议都是自己的命令，由不同的人创建和维护，我们就会陷入与其他命令相同的状况。命令行选项不一致，尽管都是文本流，输出格式却不一致，等等。这会更接近麦克罗伊所倡导的简洁性吗？取决于你问谁。
 <a href="#fnref:P" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 19189 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/cli-complexity/</link>
      <guid isPermaLink="false">https://danluu.com/cli-complexity/</guid>
      <pubDate>Tue, 3 Mar 2020 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>可疑的不连续性</title>
      <description>[AI 摘要] 文章通过多个领域案例，阐述了硬性阈值（如税收门槛、资格标准）如何引发扭曲行为或数据不连续性。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章通过多个领域案例，阐述了硬性阈值（如税收门槛、资格标准）如何引发扭曲行为或数据不连续性。</div><p>如果您在去年年底浏览过任何个人理财论坛，很可能遇到过有人拼命想在年底前亏钱的问题。人们可以通过多种方式做到这一点；一个常见的建议是购买<a href="https://en.wikipedia.org/wiki/Put_option" rel="noopener noreferrer">预计会到期作废的看跌期权</a>，让买方（很可能）承受损失。</p>

<p>人们寻求亏钱方法的一个原因是，在美国，<a href="https://en.wikipedia.org/wiki/Patient_Protection_and_Affordable_Care_Act#Subsidy_Cliff_at_400%_FPL" rel="noopener noreferrer">健康保险补贴的收入门槛是硬性的</a>，个人为48,560美元（家庭人数越多，门槛越高；四口之家为100,400美元）。有许多因素会影响具体细节（年龄、居住地、家庭规模、计划类型），但在所有情况下，从门槛一侧跨到另一侧的个人其健康保险成本每年增加约7,200美元并不罕见。这意味着，如果一个购买ACA保险的个人预计收入5.5万美元，那么将其收入降低6,440美元，使其低于48,560美元的补贴上限，比赚5.5万美元更划算。</p>

<p>虽然这是一个特别极端的例子，但<a href="http://www.cbo.gov/sites/default/files/cbofiles/attachments/11-15-2012-MarginalTaxRates.pdf" rel="noopener noreferrer">美国税收政策充满了抑制收入增长，甚至在某些情况下实际上激励减少收入的不连续性</a>。其他一些不连续性包括<a href="https://en.wikipedia.org/wiki/Temporary_Assistance_for_Needy_Families" rel="noopener noreferrer">TANF</a>收入限制、<a href="https://en.wikipedia.org/wiki/Medicaid" rel="noopener noreferrer">Medicaid</a>收入限制、<a href="https://en.wikipedia.org/wiki/Children%27s_Health_Insurance_Program" rel="noopener noreferrer">CHIP</a>免费覆盖的收入限制以及CHIP减价覆盖的收入限制。这些限制因地点和情况而异；TANF和Medicaid的收入限制通常属于“低收入”范围，而CHIP限制则通常属于“中产阶级”范围。这些补贴不连续性与ACA补贴不连续性的影响相同——在某些收入水平上，人们被激励去亏钱。</p>

<blockquote>
<p>任何人都可以安排自己的事务，使其税负尽可能低；他没有义务选择那种最能充实国库的模式。甚至没有增加税款的爱国义务。法院一再声明，如此安排事务以保持税负最低并无任何不妥。每个人都是这样做的，无论贫富，所有人都做得对，因为没有人有义务支付超过法律要求的税款。</p>
</blockquote>

<p>如果您同意著名的<a href="https://en.wikipedia.org/wiki/Learned_Hand" rel="noopener noreferrer">勒尼德·汉德</a>的引言，那么为了降低有效税率、增加可支配收入而亏钱，在个人层面上是完全合理的行为。然而，一个鼓励人们亏钱——也许是通过购买看跌期权将钱（平均而言）输给了更富有的期权交易员——的税制似乎是次优的。</p>

<p>解决上述问题的一个简单方法是采用缓慢的逐步淘汰，而不是硬性门槛。缓慢的逐步淘汰实际上已经在一些补贴中实施，虽然这也会带来问题，但通常比在税收/补贴政策中引入硬性不连续性问题要小。</p>

<p>在这篇文章中，我们将探讨各种各样的不连续性。</p>

<h3 id="hardware-or-software-queues">硬件或软件队列</h3>

<p>一个简单的队列表现出不连续的行为。如果队列已满，新条目会被丢弃。如果队列未满，新条目不会被丢弃。根据您的目标，这通常会产生不理想的影响。例如，在网络中，一个简单的队列可能被认为对“突发”工作负载（总体带宽利用率低）“不公平”，因为低带宽利用率的工作负载“不应该”比不那么突发但使用更多带宽的工作负载遭受更多丢弃（这根据您的目标不同，也可以认为并非不公平）。</p>

<p>解决这个问题的一类方案是<a href="https://en.wikipedia.org/wiki/Random_early_detection" rel="noopener noreferrer">随机早期丢弃</a>及其变体，它根据队列满度（可能还有其他因素）为传入项目赋予一个可能被丢弃的概率，从而平滑不连续性，并缓解由丢包概率不连续引起的问题。</p>

<p><a href="//danluu.com/randomize-hn/" rel="noopener noreferrer">这篇关于链接聚合网站投票的文章</a>本质上是相同的想法，尽管在某种意义上极性是相反的。某个内容是否出现在首页，对其获得的流量影响存在非常尖锐的不连续性。您可以将其视为：如果一个链接只获得N-1票而不是N票，它就会被从队列中丢弃。</p>

<h3 id="college-admissions-and-pell-grant-recipients-https-www-insidehighered-com-admissions-article-2019-01-28-study-pressure-enroll-more-pell-eligible-students-has-skewed-colleges"><a href="https://www.insidehighered.com/admissions/article/2019/01/28/study-pressure-enroll-more-pell-eligible-students-has-skewed-colleges" rel="noopener noreferrer">大学招生与佩尔助学金获得者</a></h3>

<p><a href="https://en.wikipedia.org/wiki/Pell_Grant" rel="noopener noreferrer">佩尔助学金</a>开始被用作衡量学校帮助/招收低收入学生认真程度的代理指标。一阶影响是，超过佩尔助学金门槛的学生被录取的可能性显著降低，而低于门槛的学生被录取的可能性显著提高。这样描述的话，听起来事情正按预期运作。</p>

<p>然而，当我们观察每个群体内部的情况时，我们看到了与目标（即帮助低收入家庭学生）相反的结果。在不符合佩尔助学金资格的人中，收入最低者受到的影响最严重，录取概率降低得最厉害。在符合资格的人中，收入最高者最有可能受益，同样，如果您的目标是帮助低收入家庭学生，这恰恰相反。</p>

<p>我们可以在下面的图表中看到这些，它们是2008年（第一张图）和2016年（第二张图）两所大学学生父母收入的直方图，红线表示佩尔助学金门槛。</p>

<p><img src="https://danluu.com/images/discontinuities/pell-2008.jpg" alt="2008年两所大学学生父母收入分布直方图；高收入群体相对于一般人口严重过度代表，但分布是平滑的" height="816" width="1056"></p>

<p><img src="https://danluu.com/images/discontinuities/pell-2016.jpg" alt="2016年两所大学学生父母收入分布直方图；高收入群体仍然严重过度代表，但在佩尔助学金门槛处出现了尖锐的不连续性；图表大致呈现两个向上倾斜的分段线性函数，在佩尔助学金门槛处骤降至接近0" height="816" width="1056"></p>

<p>大学优化佩尔助学金获得者的二阶效应是，精明的父母可以做一些人在年末减少应税收入时所做的同样的事情。有人可能会将钱存入传统IRA而不是罗斯IRA，如果达到了他们的IRA缴款限额，他们可以尝试在期权上亏钱，从而有效地将钱转移给可能比他们更富有的期权交易员，以使他们的收入低于佩尔助学金门槛，增加其子女被选择性学校录取的概率。</p>

<h3 id="election-statistics-https-arxiv-org-pdf-1410-6059-pdf"><a href="https://arxiv.org/pdf/1410.6059.pdf" rel="noopener noreferrer">选举统计数据</a></h3>

<p>以下俄罗斯选举投票站的直方图显示，自2004年左右开始，在诸如95%等整齐的整数附近，投票率和结果出现了奇怪的峰值。这似乎表明存在通过伪造结果进行的选举舞弊，并且至少一些伪造结果的人并不费心去伪造一个平滑分布的结果。</p>

<p><img src="https://danluu.com/images/discontinuities/russian-elections.png" height="1418" width="1822"></p>

<p>关于查找欺诈性数字，另见<a href="https://en.wikipedia.org/wiki/Benford%27s_law" rel="noopener noreferrer">本福特定律</a>。</p>

<h3 id="used-car-sale-prices-https-www-ftc-gov-sites-default-files-documents-public-events-3rd-annual-microeconomics-conference-lacetera-slide-pdf"><a href="https://www.ftc.gov/sites/default/files/documents/public_events/3rd-annual-microeconomics-conference/lacetera_slide.pdf" rel="noopener noreferrer">二手车销售价格</a></h3>

<p><a href="https://twitter.com/ainsworld/status/1436418752409706519" rel="noopener noreferrer">Mark Ainsworth指出</a>，在美国汽车拍卖销售价格中，以10,000美元为边界存在不连续性，拍卖的汽车数量也是如此。下面的价格图已针对车型年份等因素进行了调整，但我们可以在原始未调整数据中看到同样的不连续性。</p>

<p><img src="https://danluu.com/images/discontinuities/car-prices.png" alt="汽车拍卖销售价格图表，显示上述描述的不连续性" height="1670" width="942"></p>

<p><img src="https://danluu.com/images/discontinuities/car-volumes.png" alt="汽车拍卖数量图表，显示经销商销售给拍卖的数量存在上述不连续性，但车队销售给拍卖的没有" height="1710" width="992"></p>

<h3 id="p-values-https-en-wikipedia-org-wiki-p-value"><a href="https://en.wikipedia.org/wiki/P-value" rel="noopener noreferrer">p值</a></h3>

<p>心理学论文的作者有动机去产生<a href="https://en.wikipedia.org/wiki/P-value" rel="noopener noreferrer">p值</a>低于某个阈值（通常是0.05，但有时是0.1或0.01）的论文。<a href="https://www.ncbi.nlm.nih.gov/pubmed/22853650" rel="noopener noreferrer">Masicampo等人绘制了三本心理学期刊上发表论文的p值</a>，发现p值刚好低于0.05的论文数量异常之多。</p>

<p><img src="https://danluu.com/images/discontinuities/p-value.png" alt="发表论文p值的直方图；在p=0.05处出现峰值" height="1000" width="1874"></p>

<p>p = 0.05处的峰值与几个不太好的假设一致，例如：</p>

<ul>
<li>作者篡改结果以获得p = 0.05</li>
<li>期刊接受p = 0.05的论文的可能性远高于p = 0.055</li>
<li>作者在p = 0.055时提交结果的可能性远低于p = 0.05</li>
</ul>

<p><a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4359000/" rel="noopener noreferrer">Head等人（2015）</a>调查了多个领域的证据。</p>

<p>Andrew Gelman和其他人多年来一直致力于消除统计显著性和p值阈值的概念，<a href="https://stat.columbia.edu/~gelman/research/unpublished/abandon.pdf" rel="noopener noreferrer">参见这篇论文了解简要原因</a>。这不仅可以减少作者在p值上作弊的动机，还有其他原因不希望用一条明确的规则来决定某事是否“显著”。</p>

<h3 id="drug-charges-http-econweb-umd-edu-tuttle-files-tuttle-mandatory-minimums-pdf"><a href="http://econweb.umd.edu/~tuttle/files/tuttle_mandatory-minimums.pdf" rel="noopener noreferrer">毒品指控</a></h3>

<p>这组四个图表中的上面两个图表显示了2010年《公平量刑法案》通过前后，人们被指控持有可卡因数量的直方图。该法案将触发10年强制性最低监禁刑期的可卡因持有量从50克提高到280克。2010年之前分布相对平滑，之后则出现了尖锐的不连续性。</p>

<p>左下方的图表显示了在280克处起诉率急剧上升，随后在2013年证据标准改变后可能出现下降<sup id="fnref:R"><a href="#fn:R" rel="noopener noreferrer">1</a></sup>。</p>

<p><img src="https://danluu.com/images/discontinuities/cocaine-280.png" height="1118" width="1408"></p>

<h3 id="high-school-exit-exam-scores-danluu-com-matura-2013-pdf"><a href="//danluu.com/matura-2013.pdf" rel="noopener noreferrer">高中毕业考试分数</a></h3>

<p>这是波兰语考试毕业分数的直方图。我们可以看到，分数刚好达到或略高于30分的学生数量异常之多，而分数在23-29分之间的学生数量异常之少。这是2013年的数据；我查看的其他年份（2010-2012）显示出类似的不连续性。</p>

<p>在我查看的年份（2010-2013）中，数学毕业考试分数没有表现出任何异常的不连续性。</p>

<p><img src="https://danluu.com/images/discontinuities/matura-polish-2013.png" height="772" width="1416"></p>

<p><a href="https://www.reddit.com/r/dataisbeautiful/comments/1bqf9r/unusual_distributions_of_scores_on_final/c994zxt/" rel="noopener noreferrer">一位匿名的Reddit评论者解释了这一点</a>：</p>

<blockquote>
<p>当老师批改matura（高中毕业考试）时，他/她不知道是谁的试卷。唯一知道的是：学生的编号（代码）和考试来源的地区（通常来自波兰完全不同的地区）。该系统旨在防止任何形式的操纵，例如，老师的主管有时会来检查试卷是否批改正确。我不想过多谈论系统的缺陷（和优点），这在世界上每个进行最终考试的教育系统中都是众所周知的，但您必须记住，老师在批改时严格遵循一个关键点。</p>

<p>所以，如果考试分数低于30%，考试就不及格。然而，在协议中做出最终声明之前，一个由3人（我不记得确切人数）组成的委员会会再次检查试卷。这就是人文科学和数学差异显现的时刻：老师们通常会努力寻找丢失的一分（或几分），以使考试不会不及格，因为这对这个人、他的学校以及批改团队来说都是一场悲剧。在批改写作或开放性问题时（这在波兰语考试中是常见情况）找到“丢失”的分数并不太难，但在数学中几乎不可能。这就是为什么分数分布如此不同的原因。</p>
</blockquote>

<p>与p值一样，设定了一个明确的门槛会导致奇怪的行为。在这种情况下，任何科目分数低于30分（每个科目都需要30分或以上）而导致考试不及格，会对人们产生任意的负面影响，所以老师们通常会尝试在有简单方法的情况下防止人们不及格，但更深层次的问题根源在于认为有必要对一个连续分数进行离散化以产生一个证书。</p>

<h3 id="birth-month-and-sports">出生月份与运动</h3>

<p>这些是<a href="https://en.wikipedia.org/wiki/UEFA_Youth_League" rel="noopener noreferrer">欧足联青年联赛</a>中足球（soccer）球员的散点图。这两个图的x轴是球员年龄对年取模，即其出生月份标准化为0到1。</p>

<p>左侧的图表是一个直方图，显示一个人的出生日期在一年中的位置与其进入俱乐部青年联赛（U19级别）的机会之间存在非常强的关系。右侧的图表声称显示出生时间与在球场上提供的实际价值仅弱相关。作者使用出场时间作为价值的代理，可能是因为这容易测量。这不是一个很好的衡量标准，但他们的发现（在U19联赛中，同一年内更年轻的球员价值更高）与其他关于体育和歧视的研究一致，这些研究发现（例如）<a href="https://danluu.com/tech-discrimination/" rel="noopener noreferrer">在棒球种族隔离废除后的几十年里，黑人棒球运动员明显优于白人棒球运动员，法裔加拿大后卫也优于平均水平（法裔加拿大人通常被认为害怕打架、不够努力、过于专注于进攻）</a>。</p>

<p>不连续性并未在上面的图表中直接显示，因为图表只显示了一年的出生日期。如果我们要绘制多年队列的出生日期图，我们预计会看到球员进入欧足联青年联赛概率的锯齿形模式，出生在门槛前一天与后一天的人之间相差10倍。</p>

<p><img src="https://danluu.com/images/discontinuities/u19-age.png" height="1030" width="1758"></p>

<p>这种现象，即出生日期或月份是参与高水平青年体育以及职业体育的良好预测指标，已在各种运动中得到研究。</p>

<p>人们普遍认为这是由青年体育中的一个不连续性引起的：</p>

<ol>
<li>孩子们按年龄分组，并在同一年龄组内进行比赛</li>
<li>在同一年龄组内，年龄较大的孩子更强壮、速度更快等，表现也更好</li>
<li>这导致同一年龄组内年龄较大的孩子超越年龄较小的孩子，随后由于各种原因，年龄较大的孩子参与度更高</li>
</ol>

<p>这可以说是青年体育运作方式中的一个“缺陷”。但<a href="//danluu.com/bad-decisions/" rel="noopener noreferrer">正如我们在棒球中看到的</a><a href="//danluu.com/tech-discrimination/" rel="noopener noreferrer">以及对多项运动的调查</a>，明显糟糕的决策在人们公开讨论其弊端的情况下仍可能持续数十年，这些决策使个别球队损失数千万甚至数亿美元。在这种情况下，青年运动队不是职业队的预备队，因此它们没有经济激励去选拔相对于年龄有技能的球员（而不是仅仅因为年龄稍大而更高更快），所以这种系统性的非最优比职业球队做出完全在其控制范围内的局部非最优决策更难修复。</p>

<h3 id="procurement-auctions-http-www-keikawai-com-full-0804-pdf"><a href="http://www.keikawai.com/Full_0804.pdf" rel="noopener noreferrer">采购拍卖</a></h3>

<p>Kawai等人研究了日本政府的采购，以寻找类似于<a href="https://www.nber.org/papers/w4013" rel="noopener noreferrer">Porter等人（1993）</a>所描述的投标可疑模式，后者研究了美国纽约长岛采购拍卖中的串通行为。给出的一个例子是：</p>

<blockquote>
<p>1983年2月，纽约州交通部举行了一次采购拍卖，用于重新铺设0.8英里长的道路。拍卖中的最低出价为400万美元，交通部决定不授予合同，因为该出价相对于其自身成本估算被认为过高。该项目于1983年5月重新进行拍卖，所有初始拍卖的投标者都参与了。重新拍卖的最低出价比初始拍卖高出20%，由之前的最低出价者提交。同样，合同未被授予。交通部于1984年2月举行了第三次拍卖，投标者与初始拍卖相同。第三次拍卖的最低出价比第二次高出10%，再次由同一投标者提交。交通部显然认为这很可疑：“值得注意的是，同一家公司在每次拍卖中都提交了最低出价。由于不寻常的投标模式，合同直到1987年都未被授予。”</p>
</blockquote>

<p>可以说这是预料之中的，因为不同公司有不同的成本结构，因此在针对某个特定项目的拍卖中，最低出价者预期在后续针对同一项目的拍卖中仍是最低出价者。为了区分串通和企业之间真实的结构性成本差异，Kawai等人（2015）研究了第一名和第二名企业之间出价差异非常小的拍卖，使得胜者实际上具有随机性。</p>

<p>在所研究的拍卖结构中，投标者提交秘密出价。如果秘密出价高于秘密最低价，则最低出价者赢得拍卖并获得合同。如果不是，则最低出价会向所有投标者公开，并进行另一轮投标。Kawai等人发现，在大约97%的拍卖中，第一轮提交最低出价的投标者在第二轮也提交了最低出价（第二低出价者保持第二低的概率为26%）。</p>

<p>下面是一个直方图，显示了最低出价者和第二低出价者之间（左列）以及第二低出价者和第三低出价者之间（右列）第一轮和第二轮出价的差异。每行都有不同的过滤标准，以确定拍卖需要多接近才能被包括在内。在顶行，包括了所有进入第三轮的拍卖；在第二行和第三行，第一名和第二名投标者之间的标准化差值分别小于0.05和0.01；在最后一行，第一名和第三名投标者之间的标准化差值小于0.03。所有数字都是标准化的，因为拍卖的绝对规模可能有所不同。</p>

<p><img src="https://danluu.com/images/discontinuities/jp-procurement-bidding.png" height="1446" width="1682"></p>

<p>我们可以看到，当比较第二低和第三低出价者时，第一轮和第二轮之间的差异分布大致对称。但当比较最低和第二低出价者时，在零处出现了尖锐的不连续性，表明第二低出价者几乎从未将出价降低得比最低出价者降低的幅度更大。如果您阅读该论文，会发现相同的结构持续到进入第三轮的拍卖中。</p>

<p>我并非要特别批评日本的采购拍卖。关于采购拍卖的文献非常广泛，在许多案例中发现了串通行为，通常比上述案例更明目张胆（例如，有几家公司，他们在拍卖中轮流获胜；或者有少数公司，除了赢家之外，所有公司都提交相同的失败出价）。</p>

<h3 id="restaurant-inspection-https-iquantny-tumblr-com-post-76928412519-think-nyc-restaurant-grading-is-flawed-heres-scores-http-datafra-me-blog-calling-out-nyc-restaurant-violations"><a href="https://iquantny.tumblr.com/post/76928412519/think-nyc-restaurant-grading-is-flawed-heres" rel="noopener noreferrer">餐厅检查</a><a href="http://datafra.me/blog/Calling-out-NYC-restaurant-violations" rel="noopener noreferrer">分数</a></h3>

<p>下面的直方图显示了13分和14分之间存在尖锐的不连续性，这是A级和B级之间的区别。似乎一些地区在27分和28分之间也存在不连续性，这是B级和C级之间的区别，并且<a href="https://iquantny.tumblr.com/post/76928412519/think-nyc-restaurant-grading-is-flawed-heres" rel="noopener noreferrer">2014年的这个较早的分析</a>发现了B级和C级之间类似的不连续性。</p>

<p><img src="https://danluu.com/images/discontinuities/nyc-restaurant-inspections.png" height="900" width="1260"></p>

<p>检查员在计算哪些违规行为时有自由裁量权，似乎有些情况下餐厅被“推”到了更高的等级。</p>

<h3 id="marathon-finishing-times-https-faculty-chicagobooth-edu-devin-pope-research-pdf-website-marathons-pdf"><a href="https://faculty.chicagobooth.edu/devin.pope/research/pdf/Website_Marathons.pdf" rel="noopener noreferrer">马拉松完赛时间</a></h3>

<p>一个包含9,789,093次完赛记录的马拉松完赛时间直方图（完赛时间在x轴，计数在y轴）显示在每个整点（如3:00、3:30）以及像:10、:15和:20这样的“整数”时间存在明显的不连续性。</p>

<p><img src="https://danluu.com/images/discontinuities/marathon-times.png" height="1094" width="1530"></p>

<p>对每场比赛内时间的分析（<a href="faculty.chicagobooth.edu/devin.pope/research/pdf/Website_Marathons.pdf" rel="noopener noreferrer">见第4.4节，图7-9</a>）表明，这至少部分是因为人们如果接近一个“整数”时间，会在比赛结束时加速（或减速幅度小于通常情况）<sup id="fnref:M"><a href="#fn:M" rel="noopener noreferrer">2</a></sup>。</p>

<h3 id="notes">注释</h3>

<p>这篇文章其实没有目标或观点，只是收集了一些我觉得有趣的不连续性案例。</p>

<p>也许值得一提的是，在我的职业生涯中，从怀疑不连续性并弄清楚其来源，以及应用标准技术来平滑不连续性中，我都获益良多。</p>

<p>要发现不连续性，像“绘制散点图”、“<a href="//danluu.com/perf-tracing/#histogram" rel="noopener noreferrer">绘制直方图</a>”、“绘制<a href="https://en.wikipedia.org/wiki/Cumulative_distribution_function" rel="noopener noreferrer">累积分布函数</a>”这样的基本工具通常很方便。其他增加时间维度的可视化方式，如<a href="https://github.com/Netflix/flamescope" rel="noopener noreferrer">flamescope</a>，也很有用。</p>

<p>我们<a href="#hardware-or-software-queues" rel="noopener noreferrer">在上文中指出，队列在某些情况下会产生应该被平滑的不连续性</a>。我们还注意到，对于其他类型的阈值，我们看到了类似的行为，并且随机化可以成为平滑阈值不连续性的有用工具。随机化也可用于在机器学习和其他应用中减少精度以减少量化误差。</p>

<p><small>感谢Leah Hanson、Omar Rizwan、Dmitry Belenko、Kamal Marhubi、Danny Vilea、Nick Roberts、Lifan Zeng、Mark Ainsworth、Wesley Aptekar-Cassels、Thomas Hauk、@BaudDev和Michael Sullivan的评论/纠正/讨论。</small></p><small>

</small><p><small>另外，请随时<a href="https://twitter.com/danluu/status/1230595642390564866" rel="noopener noreferrer">向我发送其他有趣的不连续性</a>！
</small></p>

<p>

</p>
<div>

<hr>

<ol>
<li id="fn:R"><p>我看到的大多数关于这篇论文的在线评论都是不正确的。我曾看到这篇论文被用作警察渎职的证据，因为缉获的可卡因数量跳到了280克。这恰恰与论文中描述的情况相反，作者指出，根据缉毒记录，缉获量似乎不是造成这种变化的原因。在指出缉毒不是原因之后，作者指出检察官可以指控人们持有与缉获量不同的数量，然后指出：</p>

<blockquote>
<p>我在美国检察官执行办公室（EOUSA）的案件管理数据中发现，2010年后出现了在280克处聚集的现象。我还发现大约30%的检察官应对2010年后280克案件的增加负责，并且检察官级别的聚集在地区内部和地区之间存在差异。在2010年后，聚集案件在280克处的检察官在28克以上（2010年后5年门槛）的案件比例也很高，并且在2010年前（2010年前10年门槛）50克以上的案件比例也很高。此外，聚集在强制性最低门槛之上的情况在检察官更换地区时仍然持续。此外，当一个“聚集”检察官调入一个新地区时，该地区的所有其他检察官也会增加他们在强制性最低门槛处的聚集。这些结果表明，在量刑时观察到的聚集是由于检察官的自由裁量权。</p>
</blockquote>

<p>这在摘要中被提及，并在引言中（引文来自引言）进行了阐述，所以我认为大多数评论这篇论文的人可能根本没有读过它。我做过一些关于博客文章上论文评论的调查，我通常发现在可能识别这一点的情况下（例如，当帖子标题错误时），绝大多数评论者不可能阅读过他们评论的论文或帖子，但这是另一篇文章的主题。</p>

<p>有一些证据表明缉毒可能存在猫腻（例如，参见图A8.(c)），但如果论文中的分析是正确的，那种影响比检察官自由裁量权的影响要小得多。</p>
 <a href="#fnref:R" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:M">我在线上看到的关于这张图和/或这篇论文最常见的评论之一是，这是由马拉松提供的领跑员造成的。论文第4.4节给出了多个解释说明为何不可能是这种情况，再次表明人们倾向于在未阅读论文的情况下发表评论。</li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 14274 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/discontinuities/</link>
      <guid isPermaLink="false">https://danluu.com/discontinuities/</guid>
      <pubDate>Tue, 18 Feb 2020 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>95%-ile 并不算多优秀</title>
      <description>[AI 摘要] 文章通过游戏和现实案例论证，达到95百分位并不难，大多数人通过基础反馈和练习即可大幅进步。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章通过游戏和现实案例论证，达到95百分位并不难，大多数人通过基础反馈和练习即可大幅进步。</div><p>达到95百分位（95%-ile）并不那么令人印象深刻，因为这并非难事。我认为这是我最容易被嘲讽的观点之一。当直白地说出时，它听起来难免有精英主义之嫌。但我想表达的恰恰相反：大多数人其实可以在大多数事情上做到（相对）不错。</p>

<p>需要说明的是，我所说的95百分位，是指在“参与者”中的95%，而非所有人（对于许多活动，只要你参与，你就已经超过了99%的人）。我也不是指在“经常练习的人”中的95%。那个“<a href="https://en.wikipedia.org/wiki/One_weird_trick_advertisements" rel="noopener noreferrer">神奇诀窍</a>”在于，对于许多活动，在“经常练习者”中处于10%的水平，就足以在“所有参与者”中达到约90%或99%的水平。</p>

<p>本文将引用具体事例，因为我看到的关于此话题的讨论都过于抽象，容易沦为罗夏墨迹测试。例如，斯科特·亚当斯有一篇被广泛引用的文章，声称通才比专才更好，因为要变得“非凡”，你必须在一件事上成为“最好”，或者在两件事上达到75%水平。如果严格如此，做通才确实更好，但这当然是夸张。即使不是“最好”，从专业技能中也能获得巨大价值；既然其字面上的具体主张显然荒谬，而文章其余部分又含糊其辞，讨论最终不可避免地会沦为人们陈述其先入为主的信念，并基本无视文章内容。</p>

<p>就个人而言，在我参与的每一项可以获得粗略百分位排名的活动中，处于95百分位的人们都会持续犯一些看起来应该很容易观察和纠正的错误。“现实世界”的活动通常无法简化为百分位评级，但要达到类似的精通程度似乎同样容易。</p>

<p>我们将首先详细探讨《守望先锋》（一款电子游戏），因为这是我熟悉的活动，很容易获取排名信息并观察情况；然后我们将看一些“现实世界”的例子，尽管无法获取排名信息<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">1</a></sup>，但也能观察到相同现象。</p>

<h3 id="overwatch">《守望先锋》</h3>

<p>在《守望先锋》的90百分位和95百分位段，绝大多数玩家几乎会持续犯下导致游戏失败的基本错误。这些是简单的错误，比如在比赛倒计时结束时站在目标旁边而不是站上目标，从而将可能的胜利变成必然的失败。如果你想了解足够详细的特定错误示例，以判断某个错误是否“基本”，请参阅附带的脚注<sup id="fnref:O"><a href="#fn:O" rel="noopener noreferrer">2</a></sup>。</p>

<p>我们可能预期这种情况发生的原因有：</p>
<ol>
<li>人们不想赢或不在乎赢</li>
<li>人们理解自己的错误，但没有投入足够时间去纠正</li>
<li>人们没有天赋</li>
<li>人们不知道如何发现并纠正自己的错误</li>
</ol>

<p>在《守望先锋》中，你可能会在较低段位看到很多（1）类情况，即那些似乎不在乎赢的玩家，但到了30百分位时，常见到人们通过各种方式表达他们想赢的意愿，例如对那些被认为不关心胜利或技术不佳的玩家大喊大叫，抱怨他们认为导致团队失败的人等等<sup id="fnref:A"><a href="#fn:A" rel="noopener noreferrer">3</a></sup>。除了偶尔的故意捣蛋者外，认为人们通常都在努力赢，尤其是在他们因输掉比赛而非常生气时，是合理的。</p>

<p>（2）没有投入足够时间来纠正错误，这一点最终会适用于所有进步中的玩家，但如果你看看50百分位的中位游戏时间，那些稳定在此段位的玩家已经投入了数百小时（更高段位的中位游戏时间更长）。考虑到我们讨论的错误如此简单，“没有投入足够时间”不可能是大多数玩家的情况。</p>

<p>在《守望先锋》论坛上，低段位玩家的一个常见抱怨是他们只是没有天赋，永远无法进步。大多数人可能无论怎样练习，都没有天赋进入职业联赛，但当你只需纠正“没意识到应该站在目标上”这样的错误就能达到95百分位时，你其实并不需要太多天赋就能达到这个水平。</p>

<p>虽然（4）人们不知道如何发现并纠正自己的错误并非唯一可能的解释<sup id="fnref:D"><a href="#fn:D" rel="noopener noreferrer">4</a></sup>，但我相信这是对大多数玩家最可能的解释。大多数表达沮丧、卡在某个段位（最高可能到95或99百分位）的玩家，似乎没有意识到，通过观察自己的游戏录像或请他人复盘，他们可以显著提高。</p>

<p>有趣的一点是，《守望先锋》使发现基本错误变得容易（相比大多数其他活动）。在你被击杀后，游戏会从击杀你的玩家视角向你展示死亡过程，让你明白发生了什么导致你的死亡。《守望先锋》还记录整场比赛并允许你观看回放，让你弄清楚发生了什么以及为何赢或输。在许多其他游戏中，你需要自己设置录像软件才能查看回放。</p>

<p>如果你阅读《守望先锋》论坛，你会看到源源不断的帖子，内容基本上是“我太郁闷了！我玩了1200小时，仍然排在10百分位，[一些因人而异的《守望先锋》具体情况]”。另一位用户不可避免地会回复类似这样的话：“从你的文字描述我们看不出问题所在，请发一段你的游戏录像。”在原始发帖者上传游戏录像的情况下，人们会给出有帮助的反馈，如果玩家认真对待这些反馈，会立即变得更好。如果你关注这些求助的玩家，你常常会看到他们很快在更高的段位（例如从10百分位升到40百分位）再次寻求反馈。看到建议奏效是件好事，但不幸的是，这么多玩家没有意识到，观看自己的录像或上传录像寻求反馈本可以为他们节省1198小时的沮丧。</p>

<p>对于《守望先锋》玩家（甚至95百分位及以上），常见的情况是：</p>
<ul>
<li>想要提高</li>
<li>没有获得反馈</li>
<li>如果获得反馈本可轻松快速进步，他们却进步缓慢</li>
</ul>

<p>《守望先锋》提供了工具，让获取反馈相对容易，但那些强烈表达进步愿望的人并没有利用这些工具。</p>

<h3 id="real-life">现实生活</h3>

<p>我的经验是，其他游戏也类似，我认为“现实世界”的活动也差不多，尽管存在一些复杂性。</p>

<p>一个复杂之处是现实活动往往没有单一、一维、客观的目标去优化。另一个复杂之处是，即使相对于一个具体、定义明确的目标，是什么让一个人擅长某项现实活动，其理解也往往不如游戏和体育运动清晰。</p>

<p>带有评级系统的游戏很容易优化：你的元目标可以是获得高分，这通常可以通过提高胜率来实现，方法就是纠正上述错误，比如没意识到应该站上目标。对于任何特定的错误，你甚至可以合理猜测它对你胜率的影响，从而推断出对分数的影响。</p>

<p>在现实生活中，如果你想成为（例如）“一个好的演讲者”，这可能意味着你想做有信息量的演讲帮助人们学习，或者做有趣的演讲让人们享受，或者在知名会议上做主旨演讲，或者被邀请做每次出场费5万美元的演讲。这些都是不同的目标，有着不同的实现策略，而对于某个特定错误（例如在20分钟的演讲中花8分钟介绍自己），它对你的目标意味着什么并不明确。</p>

<p>游戏，至少是主流游戏，易于优化的另一个原因是它们往往拥有大量痴迷于研究有效性的爱好者。这意味着如果你想提高，除非你试图跻身世界顶尖，否则你只需找出哪些资源对其他人有用，选择一个，阅读/观看，然后练习。例如，如果你想在桥牌或黑桃王这类吃墩游戏中达到99百分位（在所有玩家中，而非“拥有大师分的ACBL玩家”或“定期参加北美桥牌锦标赛的选手”等子群体中），你可以这样做：</p>
<ul>
<li>学习游戏基础</li>
<li>阅读<a href="https://amzn.to/2sUtmsk" rel="noopener noreferrer">一本关于牌技的入门书</a></li>
<li>练习应用书中的内容</li>
</ul>

<p>如果你想成为一个好的演讲者，并且心中对“好的演讲者”有具体定义，仍然没有一条明确的路径。伟大的演讲者会给出完全矛盾的建议（例如，避免关注演讲技巧 vs 练习演讲技巧）。相对较少的人痴迷于提高并研究什么有效，这导致缺乏用于提高的严谨课程体系。但这也意味着在百分位上进步很容易，因为<a href="https://twitter.com/danluu/status/1442945072144678914" rel="noopener noreferrer">相对较少的人真的在努力提高</a>。</p>

<p>尽管有上述种种注意事项，我的信念是，相比游戏或体育运动，在现实活动中变得相对更好更容易，因为大多数现实活动中投入的刻意练习实在太少了。举个例子，如果你是一个本地的乒乓球高手，能打败当地酒吧里的任何路人，当你挑战某人并问“行啊，你的评级多少？”时，你知道你可能要被一个很可能用鞋刷都能打败你的人（顺便说一句，这是我朋友的亲身经历）痛击了。你可能处于99百分位，但一个没有天赋但投入时间练习基础的人，其发球将是你无法良好回击的，而且他也能轻松扣杀本地酒吧高手能稳定打出的任何回球。在大多数现实活动中，几乎没有人投入相当于一个每周去本地乒乓球俱乐部练习两小时的人那样的刻意练习量，更不用说像顶级职业选手那样每天认真训练四小时的人了。</p>

<p>举几个具体例子，我在2013年至2017年期间帮助<a href="http://blog.leahhanson.us/speaking.html" rel="noopener noreferrer">Leah</a>准备演讲。她最初几次的练习演讲，大概和你随便走进一个大型技术会议的普通演讲水平差不多。在她演讲的头几年，每次公开演讲她都会进行大约30次左右的练习预演，其中我大约看了一半并给出了反馈。她的第一次公开演讲（在我看来）就远超大型知名技术会议的平均水平，并且她的演讲水平在2017年停止演讲前持续提高。</p>

<p>正如我们上面讨论的，这比游戏评级更主观，无法真正确定百分位，但如果你看看大多数人如何准备演讲，Leah表现优于平均就不太令人惊讶了。在她最初几次演讲的会议上，会议前一天晚上，我们遇到另一位演讲者，他提到自己还没完成演讲，只有十五分钟的素材（而演讲时长是四十分钟）。他还在想办法如何填补剩余时间。这种准备方式并不罕见，而绝大多数这样准备的演讲并不出色。</p>

<p>大多数人认为为一个演讲进行30次练习预演是荒谬的，是完全强迫症式的练习量，但我认为加里·伯恩哈特说得对：如果你要在一个300人的观众面前做一个30分钟的演讲，那就是150人·时在看你的演讲，那么花15小时练习显然并非不合理（而且30次练习预演可能不到15小时，因为你可以缩短一些预演次数，和/或反复练习有问题的段落）。需要注意的是，这种被认为是强迫症水平的练习量，与一个中等水平的乒乓球俱乐部选手的练习时间相比仍然相形见绌。</p>

<p>如果你学过教育学，你可能会说我帮助Leah的方式简直糟糕透顶。众所周知，让外行自己琢磨如何提高是最糟糕的学习方式之一，好的教学指导更有效，而由一个熟练的教练或老师进行一对一指导则更加有效<sup id="fnref:C"><a href="#fn:C" rel="noopener noreferrer">5</a></sup>。这百分之百正确，我的帮助确实糟糕透顶。然而，大多数人演讲预演不会超过几次，很多人甚至一次都不练习（我没有可靠数据证明这一点，这是我在参加的会议上非正式询问演讲者得出的）。这使得Leah的30次练习预演与大多数演讲者相比成为巨量的练习，最终导致了相对较好的结果，尽管我们使用的是最糟糕的提高技巧之一。</p>

<p>我的写作是另一个例子。我不会将自己与任何人比较，但我的写作在写这个博客的头几年有了巨大进步，仅仅是因为我花了一点精力去获取和接受反馈。</p>

<p>Leah几乎阅读了每篇文章的一两个草稿并给我反馈。在最初的几篇文章中，由于我们俩都不懂写作，我们很难识别问题所在。如果我写得有些别扭或叙事结构混乱，我们能指出来并说“那看起来不对”，但无法描述哪里不对或提出修改建议。这就像在拼写检查出现之前的时代，当你拼错一个单词时，你知道有问题，但你能想到的每个替换拼法都是错的。</p>

<p>我的解决办法是聘请一位我尊重其文笔的专业编辑，指示是：“我不关心拼写和语法修改，我的写作有根本性问题而我不理解，请告诉我它们是什么”<sup id="fnref:F"><a href="#fn:F" rel="noopener noreferrer">6</a></sup>。我认为这比帮助Leah演讲更有效，因为我们让一个基本上是专业教练的人参与进来。我的编辑帮助我们的一个例子是，给了我们一套可以用来讨论结构性问题的词汇，就像<a href="https://amzn.to/2GPPmYE" rel="noopener noreferrer">设计模式</a>给了人们谈论面向对象设计的词汇一样。</p>

<h3 id="back-to-this-blog-s-regularly-scheduled-topic-programming">回到本博客的常规话题：编程</h3>

<p>编程与上述现实世界的例子类似，不可能分配评级或计算百分位之类的，但通过获取你所做事情的反馈，仍然可以在不费太多力气的情况下相对于过去的自己取得显著进步。</p>

<p>例如，<a href="https://twitter.com/danluu/status/926492239081197569" rel="noopener noreferrer">这是迈克尔·马利斯做过的一件事</a>：</p>
<blockquote>
<p>我发现一个极其有用的练习是观察自己编程。在一周的时间里，我有一个程序在后台运行，录制我的屏幕。在周末，我会观看上周的一些片段。通常我会看那些感觉完成某项任务花了太长时间的时刻。观看时，我会特别注意时间花在了哪里，并思考我本可以如何做得更好。当我第一次这样做时，我对时间都花在了哪里感到非常惊讶。</p>
<p>例如，以前在编写代码时，我会预先为一个新功能编写所有代码，然后一起测试所有代码。这样测试代码时，我必须隔离找出哪个函数有错误，然后调试那个特定的函数。在观看了自己写代码的录像后，我意识到我花了大约四分之一的总时间来实现这个功能，却花在追踪哪个函数有错误上！这对我来说完全不明显，如果不是录下自己，我不会发现这一点。现在我意识到我花了这么多时间来隔离哪个函数有错误，我现在边写边测试每个函数以确保它们正常工作。这使我写代码快了很多，因为它大大减少了调试代码所需的时间。</p>
</blockquote>

<p>过去，我曾花时间弄清楚编程时时间都花在了哪里，基本上看到了和在《守望先锋》中一样的情况，只不过不是持续犯导致游戏失败的错误，而是持续做一些无意义地浪费时间的事情。仅仅改掉一些坏习惯，我的生产力可能就至少提高了一倍，这很容易衡量，因为解决这些问题基本上就是挽回浪费的时间。例如，我注意到，如果我在需要等待两分钟时读点网上的东西，我会分心N分钟，所以我确保保持一个有用工作的队列来填补空闲时间（如果我在处理延迟敏感、不想切换任务的工作，我会什么也不做直到等待结束）。</p>

<p>这里需要注意的一点是，实际追踪你正在做的事情非常重要，而不是仅仅猜测。当我记录下人们实际在做的事情并与他们自认为在做的事情对比时，两者常常大相径庭。通常认为，在没有度量或追踪的情况下操作复杂的软件系统是荒谬的，但在没有度量或追踪的情况下操作自己（尽管你比你操作的软件复杂得多、也难理解得多）却被认为是正常的。</p>

<p><a href="//danluu.com/hn-comments/#what-makes-engineers-productive-https-news-ycombinator-com-item-id-5496914" rel="noopener noreferrer">乔纳森·唐指出选择正确的问题比执行速度更重要</a>。我不同意这一点，<a href="https://danluu.com/productivity-velocity/" rel="noopener noreferrer">但将执行速度提高一倍仍然是不错的收获，它独立于选择正确的问题去解决</a>，而且我认为讨论如何选择正确的问题无法在抽象层面有效描述，必要的上下文举例所需的篇幅将比本文中已经过长的《守望先锋》示例更长，也许我会另写一篇只讨论这个话题的文章。</p>

<p>总之，写这篇文章对我来说有点奇怪，因为我认为在文化上，尤其是在我最近居住的地方（纽约和旧金山），我们过于关注生产力了。但在个人层面，从事工作或杂务的高生产力不必转化为更多的工作或杂务，也可以转化为更多的假期时间或更多时间去做你珍视的事情。</p>

<p>而对于像《守望先锋》这样的游戏，我认为提高并不是道德上的要求；在50百分位或10百分位或任何段位玩得开心并没有错。但在我玩过的所有带有评级和/或联赛/锦标赛系统的游戏中，很多人在输掉比赛时真的会非常沮丧和不快，即使他们并没有付出太多努力去提高。如果情况如此，为什么不多花一点点努力去提高，而少花一点时间沮丧呢？</p>

<h3 id="some-meta-techniques-for-improving">一些提高的元技巧</h3>
<ul>
<li>获取反馈并练习
<ul>
<li>理想情况下来自专家教练，但如果没有，也可以来自外行甚至你自己（只要你有某种方式记录/追踪你正在做的事情）</li>
</ul></li>
<li>指导性练习或有答案的练习
<ul>
<li>在关于“老”游戏（如国际象棋或桥牌）的书籍中很容易找到这类内容。</li>
<li>在特定领域，你常常可以找到包含此类练习的系列书籍，例如在数学中，施普林格本科生数学系列（SUMS）的书籍往往有带答案的习题。</li>
</ul></li>
</ul>

<p>当然，这些并不是新颖的想法，例如，科托夫在70年代的系列著作《像特级大师一样思考》、《像特级大师一样下棋》、《像特级大师一样训练》就涵盖了这些相同的观念，因为这些是最明显的提高方式。</p>

<h3 id="appendix-other-most-ridiculable-ideas">附录：其他最容易被嘲讽的观点</h3>

<p>以下是我发布过的在发布时最广泛受到嘲讽的观点：</p>
<ul>
<li><a href="//danluu.com/startup-tradeoffs/" rel="noopener noreferrer">在潮流科技公司工作的程序员年薪达到35万美元或更高并不少见</a>（2015年，当时陈述的数字是25万美元/年）</li>
<li><a href="//danluu.com/monorepo/" rel="noopener noreferrer">使用代码库（Monorepos）可以是合理的</a>（2015年）</li>
<li><a href="//danluu.com/cpu-bugs/" rel="noopener noreferrer">我们应该预期会看到更多CPU漏洞</a>（2016年）</li>
<li><a href="//danluu.com/tech-discrimination/" rel="noopener noreferrer">市场与歧视并非不相容</a>（2014年）</li>
<li><a href="//danluu.com/input-lag/" rel="noopener noreferrer">计算机在某些方面正变得更慢</a>（2017年）</li>
<li><a href="//danluu.com/empirical-pl/" rel="noopener noreferrer">关于类型系统好处的实证证据几乎不存在</a>（2014年）</li>
<li>撰写避免使用领域特定术语的技术文章是合理的</li>
</ul>

<p>我关于薪酬的那些文章有一个可疑的殊荣：它们既被最频繁地指责为显而易见到毫无意义，又被指责为荒谬错误。我怀疑它们也是对人们产生最大整体影响的文章——我已经收到两位数人数的人告诉我，其中一篇薪酬文章改变了他们的生活，他们现在比以前多赚了数十万美元，因为他们知道有可能获得高得多的薪酬，而且我怀疑即使有从中受益做出重大改变的人，我也只听到了不到10%。</p>

<p>当我2015年写第一篇关于薪酬的文章时，我更多地因为写了一些显而易见的错误内容而被嘲讽，但过去几年情况发生了反转。当推特或网络论坛的某个角落（完全在HN/Reddit圈子之外）看到我的文章时，我偶尔仍会因为错误而被嘲讽，但“显而易见错误”与“显而易见正确”的比例可能已从20:1变为1:5。</p>

<p>关于代码库的观点自2015年以来也经历了类似的变化。在大公司的一些人之外，代码库曾被紧跟潮流的人普遍认为是愚蠢的，但这真的改变了。虽然没有薪酬观点变化那么大，但已经足以让我现在遇到一个坚决反对代码库的人时有点惊讶了。</p>

<p>尽管关于CPU漏洞的观点转变花了更长时间，但这可能是现在从上面列表中被嘲讽最少的一篇文章。</p>

<p>市场并未消除所有歧视，这一点是观点转变最少的。强硬的“所有市场都是有效率的”人士并不真正被学术研究说服，例如<a href="https://amzn.to/2Or7Z9k" rel="noopener noreferrer">贝克尔的《歧视经济学》</a>或<a href="//danluu.com/tech-discrimination/" rel="noopener noreferrer">我文章中阐述的证据</a>。</p>

<p>关于计算机具有更高延迟和缺乏类型系统好处实证证据的文章，是我见过最常被引用来为某个容易被嘲讽的观点辩护的文章。我在开始为这两篇文章做研究时都不知道结果，而它们碰巧都得出了与最常见大声主张相反的证据（有很好的证据表明先进的类型系统在实践中确实提高了安全性，当然计算机在各方面都更快了，认为它们变慢的人只是沉溺于怀旧）。我不知道这是否改变了很多人的观点。然而，尽管这两篇文章都直接陈述了我在网上看到常被嘲讽的立场，我都没有收到太多直接的嘲讽。我怀疑部分原因是两篇文章都是基于实证的，因此没什么可争辩的（尽管关于歧视的文章也是实证的，但它仍然得到了不少嘲讽）。</p>

<p>列表中的最后一个观点更偏向元认知：没有人直接告诉我应该使用更晦涩的术语。相反，我收到的评论是，因为我未使用专业术语，我一定对X知之甚少。使用专业术语是建立可信度或权威性的常见方式，但这不是我真正相信的东西。诉诸权威论据什么也说明不了；添加不必要的术语只会让那些非本领域、因兴趣而非为了深入该领域而阅读的读者更加困难。</p>

<p>这是我和许多人一个相当根本的分歧。举个例子，我最近与一位权威人士发生了争论，他坚称我无法合理地与他们意见不同（我建议我们各自保留意见），因为他们是该主题的权威而我不是。碰巧我曾在一个与我们讨论的系统非常类似的系统的正式验证项目上工作过，但我没有提及，因为我不认为我作为该主题权威的身份重要。如果某人的论点如此薄弱，以至于必须退回到不可错的权威那里，这通常表明他们没有为自己立场提供有理有据的辩护。当他们指向自己作为不可错的权威时，就更是如此了。</p>

<p>我脑中还有大约20篇关于听起来愚蠢的观点的文章待写，但我主要试图避免写有争议的内容，所以我不确定我会写多少。如果我每月写一篇文章（比我近期的频率高很多），并将自己限制在10%的文章用于讨论容易被嘲讽的观点，那需要16年才能写完我目前所有的这类观点。</p>

<h3 id="appendix-commentary-on-improvement">附录：关于提高的评论</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=h1a-lhjmfPw" rel="noopener noreferrer">天际线（Skyline）</a>：99%-ile 并不算多优秀</li>
<li><a href="https://twitter.com/JamesClear/status/1292574538912456707" rel="noopener noreferrer">詹姆斯·克利尔（James Clear）</a>：90%-ile 并不算多优秀</li>
<li><a href="https://web.archive.org/web/20120428193015/http://jinfiesto.posterous.com/how-to-seem-good-at-everything-stop-doing-stu" rel="noopener noreferrer">乔什·因菲斯托（Josh Infiesto）</a>：80%-ile 并不算多优秀</li>
<li><a href="https://www.newyorker.com/magazine/2011/10/03/personal-best" rel="noopener noreferrer">阿图尔·葛文德（Atul Gawande）</a>：教练指导/反馈强大且被低估</li>
</ul>

<p><small>感谢莱亚·汉森、希尔·韦恩、罗伯特·许斯勒、迈克尔·马利斯、凯文·伯克、杰瑞米·乔斯特、皮埃尔-伊夫·巴库、维特·海勒、杰夫·福勒、马尔特·斯卡鲁佩、大卫·特纳、阿基瓦·莱弗特、曾立凡、约翰·赫根罗德、韦斯利·阿普泰卡-卡塞尔、克里斯·兰普尔、朱莉娅·埃文斯、安贾·博斯科维奇、瓦伊巴夫·萨加尔、肖恩·塔尔茨、埃米尔·西特、本·库恩、瓦伦丁·哈特曼、肖恩·巴雷特、凯文·香农、恩佐·费雷、安德鲁·麦科勒姆、尤里·维什涅夫斯基以及一位匿名评论者的评论/修正/讨论。</small></p>

<p>





</p>

<div>
<hr>
<ol>
<li id="fn:S"><p>选择《守望先锋》是任意的，它是我熟悉的活动之一，满足：</p>
<ul>
<li>我对该活动了解足够多，可以评论</li>
<li>我观察了足够多学习该活动的人，可以判断纠正某些错误或某类错误是否“容易”</li>
<li>有足够数量的带评级玩家来支持论点</li>
<li>许多读者也熟悉该活动</li>
</ul>
<p>我99%的游戏背景来自90年代的电子游戏，但我不会用它们作为例子，因为相对较少的读者熟悉那些游戏。我也可以用《波多黎各》、《领土》、《神秘大地》、<a href="https://en.wikipedia.org/wiki/Advanced_Squad_Leader" rel="noopener noreferrer">ASL</a>等“现代”桌游，但进行评级游戏的人数很少，这使得论点说服力减弱（也许参加评级游戏的人比不参加的人差很多——不太可能，但在没有比较评级与非评级游戏玩法的情况下难以证明，这会离本文主题太远）。</p>
<p>有许多活动比《守望先锋》更适合使用，但我不够熟悉，无法将其作为例子。例如，凯文·伯克在阅读本文草稿时指出，他在指导青年篮球时观察到同样的情况，多位读者提到他们在国际象棋中观察到同样的情况，但我对青年篮球或国际象棋不够熟悉，无法自信地多谈，即使它们可能是更好的例子，因为可能更多读者熟悉篮球或国际象棋，而非《守望先锋》。</p>
<a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:O"><p>当我刚开始玩《守望先锋》（也是我做那个实验的时候），我的评级略高于50百分位（对《守望先锋》玩家来说，那是铂金段位——本文将使用百分位而非段位，以避免让非《守望先锋》玩家去了解段位含义）。通常认为并且很可能正确的是，玩主要排位模式的《守望先锋》玩家，平均而言比只玩非排位模式的玩家强，因此我实际的百分位可能略高于50百分位，并且本文列出的所有“真实”百分位都高于名义百分位。</p>
<p>在略高于50百分位时，你会经常看到：</p>
<ul>
<li>辅助（治疗者）会在一名队友满血时治疗他（毫无作用），而旁边另一名队友正在死亡，然后自己也死了</li>
<li>玩家不会注意到一个从他们队伍正后方走过来、一个一个杀死队员的人，直到整个队伍被消灭</li>
<li>玩家会射击一个敌人，直到只需再开一枪就能杀死他，然后切换目标，让只剩1滴血的敌人回满血，之后再向那个敌人开火</li>
<li>死亡后，玩家不会等待队友复活，而是直接冲向敌方队伍1v6战斗。这种情况会在整场比赛中重复（游戏设计为6v6，但在低于95百分位的段位，一旦一方有一人死亡，就很少能看到6v6的交战了）</li>
<li>玩家显然不知道角色技能的作用，包括他们自己玩的角色</li>
<li>玩家会进行风险很高但收益很低的操作（对《守望先锋》玩家来说，一个经典例子是莱因哈特在2CP地图防守开局时尝试“梗”冲锋，这在50百分位很常见，但在95百分位很少见，因为认为这个操作是个好主意的玩家通常决策能力较差）。</li>
<li>人们瞄准能力很差，会连续四五枪打不中，而他们只需要打中一下就能杀死敌人</li>
<li>如果一个单独的绕后敌人威胁到一个无法逃脱的治疗者以及一个有逃脱技能的非治疗者，非治疗者很可能会使用技能逃跑，留下治疗者死亡，即使他们本可以轻松杀死绕后者并救下治疗者，只需在被治疗时攻击即可。</li>
</ul>
<p>只要游戏水平的某一方面仅仅是糟糕而非糟糕透顶，就足以达到50百分位。对我来说，那是我的团队合作，对其他人来说，是游戏水平的其他部分。我会说我的团队合作是糟糕而非不错甚至一般，原因是我基本上不知道怎么玩这个游戏，不知道任何角色的优缺点、能力，因此我根本不可能有效地与队友协调。我也不知道游戏模式实际上是如何运作的（例如，在什么情况下游戏会以平局结束而非进入下一轮），所以我基本上是漫无目的地闲逛，倾向于待在能找到的最大一群队友附近。这就算高于平均水平了。</p>
<p>你可以说某人挺擅长这游戏，因为他高于平均水平。但从非相对意义上说，略高于平均水平是相当差的——很难说一个没注意到整个队伍从后方被消灭、同时两名队友在语音中大喊“[敌人]在我们后面！”的玩家不糟糕。</p>
<p>多玩一点后，当我使用我知道怎么用的角色时，我最终达到了看起来约90百分位的“真实”段位。由于排位波动以及匹配系统，我参加过的游戏最高达到98百分位。我的瞄准和躲避能力仍然糟糕透顶。相对于我的段位，我的瞄准实际上比我玩50百分位游戏时更差，因为我的对手强得多，而我只强了一点点。在90百分位，两个我在开阔地带很可能输给大多数人的2v1。如果对手在开阔地带而我在掩体后只有10%的身体暴露出来，我也通常会输掉战斗，因此我的瞄准能力可以说比我所在段位的平均水平差了10倍以上。</p>
<p>我“达到”90百分位的“诀窍”是学习游戏如何运作，并以最大化获胜概率的方式去玩（尽我所能），而不是把游戏玩成<a href="https://en.wikipedia.org/wiki/Deathmatch" rel="noopener noreferrer">自由击杀（FFA）</a>游戏，目标是尽快击杀。描述这在90百分位意味着什么需要更多背景，所以我只提供几个例子，但这些代表了绝大多数90百分位玩家持续在犯的错误（除了少数像我这样瞄准能力严重缺陷的玩家，他们通过在其他方面表现优于该段位平均水平来弥补瞄准的不足）。</p>
<p>游戏内，目标是获胜。有不同的游戏模式，但对于主线排位游戏，它们都涉及某种你需要待在上面或附近的目标。经常出现的情况是：回合倒计时归零，如果你的队伍没人碰目标，你们必然输掉；但如果你的队伍有人能碰目标而不被瞬间杀死（这会导致游戏进入加时，直到双方都停止碰目标），你们可能赢。一个有时会发生的具体例子是：敌方队伍有四人在目标点上，而你的队伍有两人在目标点附近，一个坦克和一个治疗者。你队伍的其他四人死了，正在从重生点赶来。他们足够近，如果你能碰目标而不立刻死，他们就会赶到并很可能拿下目标赢得比赛；但如果你碰目标后立刻死了，他们就来不及了，那时你们就输了。</p>
<p>如果你玩的是90到95百分位的辅助/治疗者，这局游戏几乎总是这样结束：坦克会向目标移动，被射击，决定不想承受伤害，然后从目标点后退。作为辅助，你血量少，如果碰目标会被敌方集火秒杀。由于你的队伍如果你们不前压目标就必输，你被迫前压去争取任何赢的机会。在你被杀后，坦克要么冲上目标点然后死亡，要么向目标点走去但时间耗尽前没走到。无论哪种情况，你们都可能输。</p>
<p>如果坦克做了他们该做的事，在你死前冲上目标点，你可以治疗坦克足够长时间，让你的队友赶到，你们很可能赢。敌方队伍如果足够协调，可以绕过或穿过坦克来杀你，但他们不会那样做——任何知道那样做会导致他们赢并且瞄准足够好能成功执行的玩家，不可能不升到更高段位）。而你队伍中那个懂得在这种情况下应该由坦克为辅助吸收伤害而非相反的坦克，不会在95百分位待太久，因为他们会赢太多比赛而升入更高段位。</p>
<p>另一个绝大多数90到95百分位玩家都会搞错的基本情况是：你在进攻方，等待队友复活以便组队进攻。即使在90百分位，可能也有1/4到1/3的玩家不会这样做，而是直接冲向敌方队伍，但足够多的玩家会意识到1v6不是个好主意，以至于你经常看到5v6或6v6的战斗，而不是你在50百分位看到的持续1v6和2v6。总之，在等待队友复活以形成5v6时，很可能有一个意识到不该直接冲进敌方队伍1v6的玩家，会决定尝试用远程攻击1v6。人们会这样做，而不是安全地躲在墙后，即使敌方有多个狙击手能瞬杀远程攻击。人们甚至会在面对多个狙击手时这样做，即使他们玩的不是狙击手角色，需要打中敌人2-3次才能击杀，这极有可能导致他们无法击杀敌人，同时自己冒着被杀死的巨大风险。对《守望先锋》玩家来说，当他们有满终极技能而敌方没有时也会这样做，将本应是你方优势的情况（你方有终极技能准备就绪，敌方已用过终极技能）<em>最多</em>变成双方都有终极技能的中性局面，最坏情况下瞬间输掉战斗。</p>
<p>如果你阅读《守望先锋》论坛，无论是reddit论坛还是暴雪官方论坛，一个常见的抱怨是“为什么我的队友这么差？我处于[90到95百分位段位]，但我的队友一直在做明显愚蠢、导致游戏失败的事情，比如[上面举的例子]”。答案当然是，抱怨的人自己也一直在做明显愚蠢、导致游戏失败的事情，因为任何不持续犯重大错误的玩家都会赢太多而无法停留在95百分位。这也适用于我。</p>
<p>人们会争论这个段位的玩家<em>应该</em>玩得好，因为他们比其他95%的玩家强，这使他们相对优秀。但从非相对意义上说，很难说一个没意识到应该站上目标以可能赢得游戏、而不是不碰目标必然输掉游戏的人是好的。关于《守望先锋》最基本的一点是它是一个目标制游戏，但90到95百分位的大多数玩家并非如此玩。</p>
<p>对于任何没达到远超99百分位的玩家来说，回顾录像会不断揭示导致游戏失败的错误。对于我自己（通常排在90百分位左右），观看一场录像通常会揭示一场势均力敌的比赛中数十个导致游戏失败的错误（这可能是30%的失利，另外70%是惨败，没有一个简单的错误能决定游戏）。</p>
<p>通常纠正这些并不太难，因为错误像上面的例子一样：简单到一旦你意识到自己在犯，修正就很简单，因为错误本身就很直接。</p>
<a href="#fnref:O" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:A"><p>可能有一些人只是想对队友发火。由于匹配到相同玩家的频率很低，在主要排位模式中很难看到这一点，但我想你有时在《守望先锋》偶尔举办的迷你排位模式中可以看到这种情况。</p>
<p>迷你排位模式的玩家基数比主要排位模式小得多，这有两个显著的副作用：技能水平差异更大的玩家会被匹配进同一局游戏，如果你玩多局游戏，会反复遇到相同的玩家。</p>
<p>由于你反复匹配到相同的玩家，你会看到玩家犯相同的错误，并以相同的方式导致自己输掉，然后在每一局后以相同的方式发脾气并责怪队友。</p>
<p>在普通排位模式中，你也会看到发脾气和责怪队友的情况，但当你看到时，你通常无法判断发脾气的人是只是心情不好还是其他一次性事件，因为除非你的段位非常高或非常低（那里评级接近的玩家池较小），否则你遇到相同玩家的频率并不高。但当你在几周内看到一组玩家打了15-20局游戏，并且看到他们以相同原因输掉比赛双位数次数，随后出现完全相同的发脾气，你可能会开始怀疑其中部分人真的想生气，他们玩游戏的主要目的就是获得一个生气的来源。你可能也会从某些人使用社交媒体的方式想到这一点，但那是另一篇文章的话题了。</p>
<a href="#fnref:A" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:D"><p>例如，也会有一些玩家有某种残疾阻碍他们进步，但在我们讨论的99百分位或以下水平，这将相对罕见（肯定远低于50%，我认为猜测远低于10%的选择玩游戏的人是合理的）。据我所知，至少有一个玩家处于前500名但他是聋人（这极其不利，因为声音提示提供了大量无法通过其他方式获得的细微位置信息），至少一个法定盲人玩家达到99百分位，还有多个有身体障碍无法精细控制鼠标的玩家（即基本上无法瞄准）达到了99百分位。</p>
<p>还有其他原因可能导致人们无法进步。例如，凯文·伯克指出，当他指导青年篮球时，一些孩子不想做他们认为让自己显得愚蠢的练习（例如，在所有人都因使用非惯用手而运球糟糕时，避免学习用非惯用手运球）。当我在攀岩馆花了很多时间与一位世界级教练相处（他经常送一些孩子去全国赛，甚至世界赛）时，我在他的课程中观察到了同样的情况——孩子们，即使是全国或国际水平有竞争力的孩子，有时会避免做某些事情，因为他们害怕在同龄人面前显得愚蠢。教练在这种情况下解决方法是故意让孩子显得极其愚蠢，并告诉他们现在看起来笨总比在全国赛上笨好。</p>
<a href="#fnref:D" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:C">注意，这里指的熟练教练是擅长教学的教练，不一定是擅长该项活动的人。擅长该活动但未明确学习过如何教学或未花大量时间研究教学的人通常不是好教练。
<a href="#fnref:C" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:F">如果你阅读我任何文章的致谢部分，你会看到我大多数文章从不止两个人那里获得反馈（我真的很感激这些反馈），但我认为，从量上来说，我收到的反馈有超过90%来自莱亚和一位专业编辑。
<a href="#fnref:F" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 20316 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/p95-skill/</link>
      <guid isPermaLink="false">https://danluu.com/p95-skill/</guid>
      <pubDate>Fri, 7 Feb 2020 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>算法面试：理论与实践</title>
      <description>[AI 摘要] 本文通过作者亲身经历和实例，论证了算法面试在解决实际生产环境中的低效问题上效果有限，指出激励机制错位是导致代码效率低下的更深层原因。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文通过作者亲身经历和实例，论证了算法面试在解决实际生产环境中的低效问题上效果有限，指出激励机制错位是导致代码效率低下的更深层原因。</div><p>当我询问那些在时髦的大型科技公司工作的人，为什么算法测试是强制性的时，最常见的回答类似于“我们的规模太大，不能允许有人不小心写一个 <code>O(n^2)</code> 算法并导致网站瘫痪”<sup id="fnref:A"><a href="#fn:A" rel="noopener noreferrer">1</a></sup>。我发现有趣的一点是，尽管我为公司提供的相当一部分价值来自于工作中解决手机屏幕级别的算法问题，但我却通不过算法面试！当我说这话时，人们通常认为我是说我面试失败一半左右。实际上，超过一半。</p>

<p>当我写了一篇关于我面试经历的博客草稿时，草稿读者批评它太无聊和重复，因为我失败了太多次面试。他们说我应该把我的失败总结成一个表格，因为没人愿意读一篇一万多字的、只是一系列失败的博客文章（这是个好建议；我正在做一个带表格的版本）。我可能参加了大约40次“真正”的软件面试，通过了大约一两次（严格来说可能是零次）<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">2</a></sup>。</p>

<p>让我们看几个例子，以便更清楚地理解我上面所说的“手机屏幕级别的算法问题”是什么意思。</p>

<p>在我工作过的一家大公司，一个团队为了自身目的编写了一个实现可调整大小数组的核心库。在每次溢出数组后备存储的调整大小时，该实现会添加固定数量的元素，然后将旧数组复制到新分配的、略大的数组中。这是<a href="https://en.wikipedia.org/wiki/Dynamic_array" rel="noopener noreferrer">实现可调整大小数组</a>时典型的错误示例，因为它导致线性时间调整大小，而不是<a href="https://en.wikipedia.org/wiki/Amortized_analysis" rel="noopener noreferrer">摊还常数时间</a>调整大小。这是一个如此经典的例子，它经常被用作展示摊还分析时的标准案例。</p>

<p>对于不习惯大型科技公司手机屏幕的人来说，我收到的典型手机屏幕问题通常是以下之一：</p>

<ul>
<li>一个“简单”的编码/算法问题，前面可能有一个“非常简单”的热身问题。</li>
<li>一系列“非常简单”的编码/算法问题，</li>
<li>一堆冷知识（对于通才角色很少见，但对于底层或与性能相关的角色并不罕见）</li>
</ul>

<p>这个数组实现问题被认为非常简单，属于“非常简单”的范畴，通常是“真正”手机屏幕问题的热身，或者与一堆类似简单的问题捆绑在一起。然而，这个可调整大小的数组在该公司所有JVM代码中导致了大约1%的GC压力（它是所有代码中第二大的分配来源），以及相当一部分的CPU使用率。幸运的是，这个可调整大小的数组实现并非用作通用的可调整大小数组，它只是被一个半特殊用途的包装器实例化，这使得它“仅仅”承担了该公司1%的GC压力。如果作为面试问题来问，绝大多数团队成员在面试中都能正确实现它。我修复这个问题为我雇主带来的年收入，比我有生以来赚的钱都多。</p>

<p>这是第二大分配来源，最大的来源是在同一个核心库中将一对<code>long</code>值转换为字节数组。这似乎是因为有人编写或复制粘贴了一个接受字节数组作为输入的哈希函数，然后通过接受两个字节数组并按顺序对它们进行操作来修改它以接受两个输入，这使得哈希函数的接口变成了<code>(byte[], byte[])</code>。为了在两个long值上调用这个函数，他们使用了一个广泛使用的实用库中方便的<code>long</code>转<code>byte[]</code>转换函数。该函数除了分配一个<code>byte[]</code>并将一个<code>long</code>塞入其中之外，还会反转long的字节顺序（该函数似乎旨在将<code>long</code>值转换为网络字节顺序）。</p>

<p>不幸的是，切换到更合适的哈希函数将是一个重大的改动，所以我对此的修复是将哈希函数接口改为接受一对long值而不是一对字节数组，并让哈希函数完成字节顺序反转，而不是将其作为单独的步骤（因为哈希函数已经在打乱字节顺序，这不会产生额外的工作）。移除这些不必要的分配为我雇主带来的年收入，比我有生以来赚的钱都多。</p>

<p>找到一个常数因子的提速从技术上讲不是一个算法问题，但它也是你在算法面试中会看到的东西。作为算法问题的后续，我经常被问到“你能做得更快吗？”答案通常涉及进行简单的优化，从而带来常数因子的改进。</p>

<p>一个我曾在面试中被问过两次的具体例子是：你正在将ID存储为int，但你已经在题目背景中知道ID是密集排列的，所以你可以将它们存储为位域。位域面试问题与现实世界中多余的数组之间的区别在于，现实世界中现有的解决方案与预期答案相差甚远，你可能不会被要求找到一个常数因子的提速。更有可能的是，你在那一点上就已经面试失败了。</p>

<p>从另一家公司举个例子，<a href="https://danluu.com/bitfunnel-sigir.pdf" rel="noopener noreferrer">BitFunnel</a>的配置是另一个面试级别算法问题的例子<sup id="fnref:P"><a href="#fn:P" rel="noopener noreferrer">3</a></sup>。BitFunnel是必应使用的搜索索引。</p>

<p>描述解决方案所需的全部背景对于这篇博客文章来说有点多，但基本上，有一组需要配置的布隆过滤器。一种方法是编写一个黑盒优化函数，使用梯度下降来尝试找到最优解。我听说这总是导致一些奇怪的属性，并且输出配置总是导致非理想情况，这通过使后备布隆过滤器不那么密集来解决，即投入更多的资源（因此金钱）来解决问题。</p>

<p>为了创建一个更优化的解决方案，你可以观察到BitFunnel中的基本操作相当于将概率相乘，因此，对于任何特定的配置，你可以将一些概率相乘来确定配置的性能如何。由于配置空间并不大，你可以将其放在几个for循环中，遍历可能的配置空间，然后选出最佳的配置组合。这并不完全正确，因为将概率相乘假设了一种独立性，这在现实中并不成立，但<a href="https://en.wikipedia.org/wiki/Naive_Bayes_spam_filtering" rel="noopener noreferrer">朴素贝叶斯垃圾邮件过滤</a>在引入时效果相当好，尽管它错误地假设了任何两个单词出现在一封邮件中的概率是独立的，这似乎是有效的，原因类似。如果你想要完整的解决方案，你可以推导出非独立性的细节，尽管这可能超出了面试的范围。</p>

<p>这只是我能想到的三个例子，我经常遇到这种情况，可以随口举出几十个例子，如果我坐下来尝试列出我参与过的每一个例子，可能超过一百个，如果我列出我知道的、由其他人（或无人）处理过的例子，肯定超过一百个。本文中的例子以及我未包括的例子都具有以下特性：</p>

<ul>
<li>该例子可以表述为一个面试问题。</li>
<li>如果表述为面试问题，你会期望大多数（可能是所有）相关团队的人在面试的时间框架内得到正确答案。</li>
<li>修复该例子所带来的年度成本节约，比我到目前为止的终身收入还多。</li>
<li>该例子存在了足够长的时间，可以合理地假设它不会被其他人发现。</li>
</ul>

<p>在本文开头，我们注意到大型科技公司的人们通常声称他们必须进行算法面试，因为在规模上效率低下代价高昂。我的经验是，这些例子在我工作过的每一家进行算法面试的公司都比比皆是。试图通过在面试中问算法问题来让人们在工作中解决算法问题的方法行不通。</p>

<p>一个原因是，即使大公司试图确保他们雇佣的人能够解决算法谜题，他们也激励了大多数开发者避免运用这种推理来赚钱。</p>

<p>在上面三个例子的解决方案中，两个已经投入生产，一个没有。如果我带着一个差异（diff）去找一个随机团队，而没有持续跟进（这不同于我有理由相信会接受的团队，或者寻求帮助的团队，或者我一直纠缠一个团队直到修复被采纳），这大约是我的正常成功率。</p>

<p>如果你非常愤世嫉俗，你可能会说成功率这么高令人惊讶。如果我去找一个随机团队，效率很可能既不在团队的目标中，也不在其组织的目标中。公司很可能已经投入了相当大的努力来激励团队实现他们的目标——否则设定目标有什么意义呢？接受我的差异（diff）将要求他们测试、集成、部署这个改动，并带来风险（因为所有部署都有非零风险）。基本上，我是在要求团队做一些工作并承担一些风险来做对他们来说毫无价值的事情。尽管有激励措施，人们通常还是会接受差异（diff），但他们不太可能花很多自己的业余时间去寻找效率改进（他们的正常工作时间会花在与团队目标一致的事情上）<sup id="fnref:E"><a href="#fn:E" rel="noopener noreferrer">4</a></sup>。</p>

<p>假设一下，如果一家公司不试图确保其开发者能通过算法测试，而是激励开发者使用相对高效的算法。我认为上面三个例子中的任何一个都不可能未被发现地存在数年，也不可能一直未被修复。在一个人们分析其代码的公司，某个假想的开发者可能会查看公司计算密集型库中分析最热的项目。这两个例子的“诀窍”并非任何算法技巧，而只是查看所有内容，这是激励措施可以解决的。第三个例子不那么必然，因为没有标准工具会告诉你去看这个问题。也很容易试图将结果包装成某种魔法——那个例子构成了一篇论文的核心部分，该论文在其领域（信息检索）的顶级会议上获得了“最佳论文奖”，但现实是，“诀窍”只是应用了高中数学，这意味着真正的诀窍是有足够的时间去寻找高中数学可能适用的地方。</p>

<p>我实际上曾在一家采用“面试中不问算法问题，但激励那些对公司全局有益的行为”的策略的公司工作过。在我在那里工作期间，我只找到了一个几乎符合上述例子标准的修复（如果公司规模更大，它就会满足所有标准，但由于公司的规模，效率提高的价值远低于大公司——比我当时的收入多得多，但年回报仍低于我到目前为止的终身总收入）。</p>

<p>我认为我只找到一个近乎例子的主要原因是，足够多的人将使公司变得更好视为自己的工作，因此简单的高价值修复往往不存在，因为系统通常被设计成本身就没有容易发现的改进。在少数并非如此的情况下，有足够的人在努力为公司做正确的事情（而不是被迫遵守与公司全局利益大相径庭的局部激励措施），以至于在我遇到问题之前，可能已经有其他人修复了它。</p>

<p>那家公司的算法/编码部分面试（初始筛选加现场面试）比大公司的手机筛选更容易，我们基本上不做系统设计面试。</p>

<p>有一段时间，我们尝试了一个算法现场面试题，难度偏大，但在大公司手机筛选的正常范围内（但仍比你预期在现场面试中看到的要简单）。我们停止问这个问题，因为我们面试的每一个应届毕业生都未能回答这个问题（我们不给有经验的候选人那种问题）。我们就是没有足够的声望来吸引那些能轻松回答这些问题的候选人，所以不可能使用<a href="//danluu.com/programmer-moneyball/" rel="noopener noreferrer">和其他所有人一样的时髦招聘过滤器</a>来招聘。在关于面试的当代讨论中，我们所做的通常被称为“降低标准”，但我不明白为什么我们应该关心一个人能跳过多高的横杆，尤其是在他们被雇佣来做的工作中，很少（在某些情况下根本没有）涉及跳横杆。而且，在那些你确实想让他们跳横杆的情况下，横杆可能只有2英寸高，可以轻松跨过去。</p>

<p>如果以实际生产力来衡量，那是我工作过的最具生产力的公司。我相信原因是文化性的，并且过于复杂，无法在本文中完全探讨，但我认为我们没有用算法测试过滤掉完全合格的候选人，并且假设在一个人们普遍做正确事情的文化环境中，人们可以在工作中学会那些东西，这有所帮助。</p>

<p>如果其他公司希望人们在工作中解决面试级别的算法问题，他们或许可以尝试激励人们解决算法问题（当相关时）。这可以在筛选能白板算法问题的人之外甚至取代其进行。</p>

<h3 id="appendix-how-did-we-get-here">附录：我们是如何走到这一步的？</h3>

<p>很久以前，面试通常涉及“冷知识”问题。这些问题的现代版本可能如下所示：</p>

<ul>
<li>什么是MSI？MESI？MOESI？MESIF？MESIF相对于MOESI有什么优势？</li>
<li>抛出析构函数时会发生什么？如果是C++11呢？如果一个被顶层析构函数调用的子对象的析构函数抛出异常，哪些其他子对象的析构函数会执行？如果在栈展开期间抛出异常呢？在什么情况下这不会导致<code>std::terminate</code>被调用？</li>
</ul>

<p>我从学校时就听说过这种做法，甚至在一些“老派”的公司见过。那时微软是最大的玩家，想要效仿成功公司的人很可能会模仿微软。当时阅读量最大的编程博主（Joel Spolsky）告诉人们他们需要采用软件实践X，因为微软在这样做，如果不采用同样的实践，他们就无法竞争。例如，在那个时代最具影响力的编程博客文章之一中，Joel Spolsky倡导他所谓的Joel测试，部分原因是你必须做这些事情才能跟上像微软这样的公司：</p>

<blockquote>
<p>12分是完美的，11分可以容忍，但10分或更低就意味着你有严重的问题。事实是，大多数软件组织的运行分数只有2或3，他们需要<em>严肃的</em>帮助，因为像微软这样的公司全天候以12分运行。</p>
</blockquote>

<p>当时，流行的传说说微软会问人们如下问题（我确实在2001年左右微软的面试中被问过其中一个脑筋急转弯，以及严格意义上的零个算法或编码问题）：</p>

<ul>
<li>如果你只有半英寸高，你会如何从搅拌机里逃出来？</li>
<li>为什么井盖是圆的？</li>
<li>一个没有窗户的房间里有3盏灯，每盏灯都由房间外的一个开关控制。你在房间外。你只能进入房间一次。你如何确定哪个开关控制哪个灯泡？</li>
</ul>

<p>由于我是在这个变化发生的时代面试的，我被问到了很多冷知识问题，也有很多脑筋急转弯（包括所有上述脑筋急转弯）。当时流行的其他一些技术上不属于脑筋急转弯的问题是<a href="https://en.wikipedia.org/wiki/Fermi_problem" rel="noopener noreferrer">费米问题</a>。当时的另一个趋势是行为面试，我面试过的许多公司进行了100%的行为面试，没有任何技术面试。</p>

<p>总之，那时人们需要为模仿微软风格的面试找一个合理的解释。当我问人们为什么认为脑筋急转弯或费米问题是好的时，人们告诉我的方便解释通常是，它们能看出候选人是否真的会思考，不像那些愚蠢的冷知识问题，只能告诉你是否记住了一些冷知识。我们真正需要雇佣的是那些真正会思考的候选人！</p>

<p>回顾过去，人们现在意识到这并不有效，盲目模仿微软的每一个决定不会让你像微软一样成功，因为微软的成功归结为几个关键因素加上网络效应，所以模仿他们的面试方式不可能让你成为微软。相反，它会让你成为一家面试方式像微软、但无法利用微软所能利用的网络效应的公司。</p>

<p>对于求职者来说，脑筋急转弯的过程与现在算法问题的过程基本相同，只不过你在面试前复习的是<a href="https://www.amazon.com/gp/product/0316778494/ref=as_li_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0316778494&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=bcc3345d4905164e75b79f2cd2a0f5fb" rel="noopener noreferrer">《How Would You Move Mount Fuji》</a>而不是<a href="https://www.amazon.com/gp/product/0984782850/ref=as_li_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0984782850&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=381539624231e0e40b66f2695c925536" rel="noopener noreferrer">《Cracking the Coding Interview》</a>，以掌握一堆你在工作中永远不会用到的脑筋急转弯知识，而不是你永远不会用到的算法知识。</p>

<p>那时，面试官会从“How Would You Move Mount Fuji?”这样的面试准备书籍中专门学习问题，然后向从“How Would You Move Mount Fuji?”这样的书籍中学到答案的候选人提问。当我与比我年轻十岁的人交谈时，他们认为这很荒谬——这些问题显然与工作无关，能很好地回答它们更强烈地与做过一些面试准备相关，而不是与胜任工作相关。Hillel Wayne讨论了人们今天如何设计面试问题（我也在几家不同的公司亲眼目睹过），除了测试被认为是专业知识的小组之外，现在的情况似乎没有太大不同。</p>

<p>到这时，我们已经经历了几十年的编程面试潮流，每一个回头看都很荒谬。要么是我们终于找到了有效面试的真正秘诀，并且已经通过推理克服了过去导致每个人都使用明显虚假的潮流面试技术的障碍，要么是我们正处于另一个潮流的中间，从现在一两年后回顾，这个潮流会显得同样荒谬。</p>

<p>在不了解面试有效性的情况下，在一个元层面上，由于人们获取面试技术的方式是相同的（从最负盛名的公司窃取高级技术），我认为如果不是一个潮流，那将是相当令人惊讶的。如果人们在做或参考实证研究，或者独立发现了什么有效，我会更不惊讶地发现当前技术不是一个潮流。</p>

<p>受<a href="https://twitter.com/wesleyac/status/925728187375595521" rel="noopener noreferrer">Wesley Aptekar-Cassels评论的启发</a>，我上次找工作时，问了一些人他们如何检查面试流程的有效性以及他们如何试图减少流程中的偏见。我得到的答案（相似的分组在一起，按频率降序排列）是：</p>

<ul>
<li>哈？我们不做这个，或者为什么要做这个？</li>
<li>我们真的不知道我们的流程是否有效。</li>
<li>我/我们就是知道它有效。</li>
<li>我/我们没有偏见。</li>
<li>我/我们会注意到偏见是否存在，但它不存在。</li>
<li>有人调查过/做过研究，但告诉我这件事的人永远无法告诉我任何关于它是如何被调查的或研究方法论的具体信息。</li>
</ul>

<h3 id="appendix-training">附录：培训</h3>

<p>与大多数现实世界的问题一样，当试图弄清楚为什么每年七位数、八位数甚至九位数的、面试级别的算法错误会闲置在那里等待修复时，并没有一个可以指出的“根本原因”。相反，存在一种<a href="https://en.wikipedia.org/wiki/Hedgehog_defense" rel="noopener noreferrer">刺猬防御</a>式的激励错位。另一部分是<a href="//danluu.com/programmer-moneyball/#training-mentorship" rel="noopener noreferrer">培训严重不受重视</a>。</p>

<p>我们已经讨论过，在我工作过的所有公司（除了一家），都存在激励系统，导致开发者觉得他们不应该花时间寻找效率提升，即使简单的计算显示存在数千万或数亿美元的浪费可以轻松修复。然后，由于这不被激励，开发者往往没有做这类事情的经验，使其变得不熟悉，这使得它感觉比实际更难。所以，即使一天的工作可能带来每年100万美元的节省或利润（根据我的经验，在大公司这很常见），人们也没有意识到这只是一天的工作，并且只会在速度上做出小小的妥协。解决后一个问题的一种方法是通过培训，但这比效率提升更难获得认可，而效率提升甚至不在你的目标中！</p>

<p>举个例子，我曾经写了一篇中等长度的教程（4500字，按字数比这篇文章短，但加上图片可能更长），关于如何发现各种低效之处（如何使用分配或CPU时间分析器，如何对我们使用的GC进行特定服务的GC调优，如何使用我构建的一些工具自动发现JVM或容器配置中的低效之处等，基本上是简单且通常影响大的东西，很容易编写操作手册；如果你在Twitter，可以在<a href="http://go/easy-perf" rel="noopener noreferrer">http://go/easy-perf</a>阅读）。我听到过几个人告诉我，他们以前会就某个问题来寻求我的帮助，但现在他们能够自己调试和解决问题，并且，间接地，我听说其他几个我不认识的人能够去提高他们服务的效率。如果我听到的案例不到10%，我会感到惊讶，所以我猜测这已经帮助了几十位工程师，甚至可能更多。</p>

<p>如果我花一周时间做“真正”的工作而不是写教程，我会有一些具体的、可量化的价值，我可以轻松地将其放入晋升包或绩效评估中。相反，我只有这个模糊的东西，往好了说，算是一点“额外加分”。我并不是在抱怨这件事本身——这正是我预期的结果。但是，平均而言，公司得到他们激励的东西。如果他们期望培训来自开发者（而不是雇人来制作培训材料，后者往往比工程获得的资金少得多），但又不重视它，就像他们重视开发工作一样，那么就会出现培训短缺。</p>

<p>我相信在公共教育材料中也能看到培训激励不足，因为教育和培训的货币化相对困难。如果你想通过解释事物来赚钱，有几种技术似乎非常有效。如果某些东西直接明显有价值，销售一门定价“非常高”（几千美元的短期课程）的视频课程似乎很有效。做企业培训也很有效，公司让你飞过去给30人的教室讲课，每人收费3000美元。</p>

<p>如果你想接触（并可能帮助）很多人，在互联网上发布文本并免费提供效果很好，但这样货币化效果很差。对于技术主题，我不确定非广告拦截受众群体是否真的足够大，可以通过广告来货币化（而不是通过付费墙）。</p>

<p>举个例子，Julia Evans可以靠她的<a href="https://jvns.ca/blog/2019/10/01/zine-revenue-2019/" rel="noopener noreferrer">小册子收入</a>养活自己，她说过去两年每年大约带来10万美元。做企业培训做得很好的人，一两天的培训课程就能赚到那个数，而且，根据我听说的企业演讲费率，一些高薪的技术演讲者两次活动就能赚到那个数。这些费率明显高于平均水平，尤其是演讲活动，但既然我们是在和Julia Evans比较，我认为使用高于平均水平的费率是公平的。</p>

<h3 id="appendix-misaligned-incentive-hedgehog-defense-part-3">附录：激励错位的刺猬防御，第三部分</h3>

<p>在上面的三个例子中，我在一个团队上发现了一个，在那个团队上，做任何对公司真正有价值的事情对我来说显然是零价值；另外两个是在一个团队上发现的，在那里做对我不论是什么，对公司有益的事情对我来说都有价值。根据我的经验，在大公司的团队中，这非常罕见，但即使在那个团队上，激励一致性仍然相当差。有一次，在获得晋升和加薪之后，我计算了我的改动为公司赚的钱与我的加薪之间的比率，发现我的加薪是我为公司赚的钱的0.03%，仅计算容易量化且完全无可争议的对利润的影响。我的大部分工作与工具相关，其价值难以量化，我怀疑实际上比可量化影响的价值更大，所以我可能只收到了我所产生边际价值的不到0.01%。而这实际上高估了我对这份工作的激励程度——在边际上，我强烈怀疑我做的任何事情对我来说都是零价值。在最初的1000万美元/年或2000万美元/年之后，在绩效评估、晋升、加薪等方面基本上没有区别。因为做工作没有好处，而且有一些坏处（可能卷入政治斗争，可能使网站瘫痪等），对我来说做超过“足够”工作的边际回报可能是负面的。</p>

<p>有些公司会定期给人们非常大的带外奖金，但那份工作不是为一家经常这样做的公司做的，所以一旦一个人做了“足够”的工作以获得绩效评估的最佳评级，公司就无法采取任何措施来表明它重视额外的工作。从<a href="https://en.wikipedia.org/wiki/Mechanism_design" rel="noopener noreferrer">机制设计</a>的角度来看，公司基本上是在要求员工在完成一年“足够”的工作后停止工作。</p>

<p>所以即使在这个团队上，与大多数团队相比，它与公司成功的一致性相对较好，但公司的薪酬系统也为团队的一致性设置了很低的上限。</p>

<p>这也在另一种方式上发生了。像许多公司一样，经理们获得了一个主要基于团队规模的团队加薪预算，然后以零和方式分配给团队成员。对每个团队成员来说（至少就薪酬而言），不幸的是，团队几乎只有高效的工程师，这意味着在零和加薪游戏中没有人会做得特别好。团队的人员流失率很低，因为人们喜欢与优秀的同事一起工作，但公司正在利用它拥有的最大杠杆之一——薪酬，试图让人们离开团队，加入效率较低的团队。</p>

<p>因为这是一个非常常见的设置，我听说过多家公司有经理试图留住无害但低效的人，以绕过这个问题。如果你抽象地问某人，公司是否希望雇佣和留住低效的人，我怀疑他们会说不。但就公司可以说想要什么而言，它想要的是它所激励的。</p>

<h3 id="related">相关文章</h3>

<ul>
<li><a href="//danluu.com/programmer-moneyball/" rel="noopener noreferrer">盲目模仿时髦招聘实践的弊端</a></li>
<li><a href="//danluu.com/wat/" rel="noopener noreferrer">偏差的正常化</a></li>
<li><a href="https://thezvi.wordpress.com/2019/05/30/quotes-from-moral-mazes/" rel="noopener noreferrer">Zvi Mowshowitz关于《道德迷宫》</a>，一本关于公司系统性问题如何导致各层级激励错位的书</li>
<li><a href="https://www.jefftk.com/p/programmers-should-plan-for-lower-pay#lw-ftocr9cDfYtyQj9x2" rel="noopener noreferrer">“randomsong”关于如何教会几乎任何人编程</a>，主题相关，观点是编程并不像许多程序员希望的那么难</li>
<li><a href="https://noidea.dog/glue" rel="noopener noreferrer">Tanya Reilly关于“粘性工作”激励不足</a>，培训激励不足可以说是这种情况的一个特例</li>
<li><a href="https://sockpuppet.org/blog/2015/03/06/the-hiring-post/" rel="noopener noreferrer">Thomas Ptacek关于使用与工作表现相关性不错的招聘过滤器</a></li>
<li><a href="https://mtlynch.io/why-i-quit-google/" rel="noopener noreferrer">Michael Lynch关于他在大公司激励机制的个人经历</a></li>
<li><a href="https://news.ycombinator.com/item?id=21961560" rel="noopener noreferrer">一位匿名HN评论者关于在Google几乎不工作（他们说大约10%的产能）六年却获得晋升的经历</a></li>
</ul>

<p><small>感谢Leah Hanson、Heath Borders、Lifan Zeng、Justin Findlay、Kevin Burke、@chordowl、Peter Alexander、Niels Olson、Kris Shamloo、Chip Thien、Yuri Vishnevsky和Solomon Boulos的评论/纠正/讨论</small></p>

<p>

</p>
<div>

<hr>

<ol>
<li id="fn:A">首先，大多数模仿谷歌面试的公司并没有那么大的规模。但即使对于那些确实有规模的公司，大多数人也没有设计大规模算法的工作（也许他们在2003年左右的谷歌做过，但根据我在三家不同大科技公司的观察，大多数人的工作对算法的要求相当轻）。
 <a href="#fnref:A" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:S"><p>“真正”加引号是因为我通过了一些与面试流程无关的原因通过了多次面试。也许我有非常强的内部推荐可以覆盖我的面试表现，也许有人读了我的博客并根据我的写作假定我能做合理的工作，也许有人从我的前同事那里得到了内部推荐，或者也许有人读了我一些开源代码并据此来评判我，而不是白板编码题（据我所知，最后一种情况只发生过一两次）。在那些我显然没有通过技术面试的情况下，我通常会问为什么我得到了工作邀请，所以我从人们那里收集了这些原因。</p>
<p>严格说是零的原因是，唯一一次我无可置疑地参加了“真正”面试且是冷面的软件面试是在谷歌，但那之所以发生，是因为被分配面试我的面试官面试我时搞错了职级——我面试的是硬件职位，但面试我的是软件人员，所以我得到的基本上是标准的软件面试，除了一个面试官问了我一些关于状态机和缓存一致性（或类似的东西）的问题。在他们意识到面试我搞错了职级之后，我有一个来自硬件工程师的后续电话面试，以确保我并不是完全伪造自己在2005年至2013年在一家硬件初创公司工作过。可能我失败了软件部分的面试，并且基本上是凭借后续电话屏幕的实力被录用的。</p>
<p>注意这仅指软件——我实际上相当擅长硬件面试。目前，我对硬件相当生疏，可能需要相当多的时间才能胜任真正的硬件工作，但面试对我来说小菜一碟。一个非常了解我的人认为这是因为我说话“像硬件工程师”，既说出能让硬件人员觉得我靠谱的话，也说出对大多数程序员来说听起来极其愚蠢的话，这更多是行话而非实际知识或技能。</p>
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:P"><p>这个比你预期的在手机屏幕上看到的要难一点，但在现场面试中也不算过分（尽管我有个朋友曾在谷歌的手机面试中得到<a href="https://code.google.com/codejam/contest/2437491/dashboard#s=p2" rel="noopener noreferrer">一道谷歌Code Jam世界决赛题目</a>，所以你可能会得到这么难或更难的题目，这取决于你遇到的面试官）。</p>
<p>顺便说一句，如果你好奇我朋友在遇到那个问题时做了什么，结果他实际上知道答案，因为他以前在谷歌Code Jam中见过并尝试过这个问题。他当时没有得到正确答案，但后来为了好玩弄明白了。然而，我朋友认为把它作为手机屏幕问题不合理，要求面试官换一个问题。面试官拒绝了，于是我朋友面试失败了。当时，我怀疑世界上能在手机屏幕上回答那个问题正确的人不超过几百个，而且他们中几乎所有人都可能意识到这是一个荒谬的手机屏幕问题。面试失败后，我朋友最终找了将近六个月的工作，才通过一家初创公司的面试，他在那里最终构建了许多核心系统（无论是在业务影响还是工程难度方面）。在那次超过10亿美元的IPO后，我朋友仍然在那里——公司明白替换这个人有多难，并且对他非常好。其他面试过他的人甚至都不想雇佣他，而且他实际上很难找到工作。</p>
 <a href="#fnref:P" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:E">除了那些会导致服务崩溃的明显架构问题之外，我看到团队修复效率问题的最常见方式是要求更多的容量。一些公司试图以某种方式平衡这一点（例如，我听说在Facebook，许多从事效率改进工作的团队汇报给容量组织，这使他们能够阻止容量请求，如果他们观察到一个团队有他们拒绝修复的极端低效问题），但我没有在有有效的系统解决方案的环境中工作过。谷歌有一个旨在解决这个问题的系统，其中涉及使人员配置与计算资源可互换，但我听说它已被恢复为更传统的系统，原因可能是<a href="https://en.wikipedia.org/wiki/Incentive_compatibility" rel="noopener noreferrer">激励相容性</a>。
 <a href="#fnref:E" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 16126 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/algorithms-interviews/</link>
      <guid isPermaLink="false">https://danluu.com/algorithms-interviews/</guid>
      <pubDate>Sun, 5 Jan 2020 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>文件暗藏风险</title>
      <description>[AI 摘要] 该文章探讨了文件操作在API、文件系统和磁盘层面存在的复杂性和风险，挑战了开发者对文件简单性的普遍误解。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 该文章探讨了文件操作在API、文件系统和磁盘层面存在的复杂性和风险，挑战了开发者对文件简单性的普遍误解。</div><p><small><em>这是2019年Deconstruct大会上演讲的伪文字稿。<a href="//danluu.com/web-bloat/" rel="noopener noreferrer">为了照顾网速较慢的用户</a>以及屏幕阅读器使用者，幻灯片已替换为行内文本（演讲约有120张幻灯片；按平均每张20 kB计算，总计2.4 MB。若认为这微不足道，请考虑<a href="https://blogs.microsoft.com/on-the-issues/2019/04/08/its-time-for-a-new-approach-for-mapping-broadband-data-to-better-serve-americans/" rel="noopener noreferrer">仅半数美国人使用宽带</a>，且发展中国家情况更为严峻）。</em></small></p>

<p>让我们来谈谈文件！大多数开发者似乎认为文件很简单。例如，我们来看看Dropbox宣布在Linux上仅支持ext4文件系统（Linux上最常用的文件系统）时，Reddit的r/programming论坛上热门评论。对于不熟悉r/programming的人来说，我推测它是全球最广受关注的英文编程论坛。</p>

<p>置顶评论写道：</p>

<blockquote>
<p>我有点困惑，为什么这些应用必须直接支持这些文件系统？内核本身难道不是抽象了文件实际存储的底层细节吗？</p>

<p>不同文件系统之间我唯一能想到的区别是文件大小限制和权限，但现代文件系统不都大同小异吗？</p>
</blockquote>

<p>第二条热门评论（及其两级回复）如下：</p>

<blockquote>
<p>#2: 为什么应用程序要在意文件系统是什么？</p>

<p>#2: 对于“普通应用”来说，这不该由操作系统来抽象吗？</p>

<p>回复：这是一个有漏洞的抽象。我敢打赌每个不同的文件系统都有其自身的缺陷和文件系统特定的修复代码存在于Dropbox代码库中。更多文件系统意味着更多测试以确保一切正常……</p>

<p>二级回复：你在说什么？这是Dropbox，它到底需要从文件系统获得什么？有成千上万的文件同步工具、数据传输工具、分布式存储软件，它们都能很好地与inotify配合工作。Dropbox到底有什么不行的？</p>

<p>另一条二级回复：当然，但由此产生的任何缺陷都应在各自的抽象层修复，而不是自己重新实现整个协议栈。除非你从抽象层得不到所需的数据，否则不应重新实现……DropBox实现文件系统特定的变通方案和怪癖是过度设计。这就像vim为了应对错误按键而提供特定键盘的变通方案。所有抽象都有漏洞——但如果无人使用这些抽象，任何事都无从谈起（我们将会有数十亿个“操作系统”）。</p>
</blockquote>

<p>在这次演讲中，我们将探讨文件系统之间有何差异，以及在写入文件时可能遇到的其他问题。我们将从“文件栈”顶端开始，即文件API，我们将看到它几乎不可能被正确使用，并且支持多个文件系统而不损坏数据比支持单个文件系统要困难得多；然后深入到文件系统层，我们将看到存在导致数据丢失和损坏的严重缺陷；最后我们将审视磁盘，看到磁盘可能以厂商数据手册中声称速率的五百万倍轻易损坏数据。</p>

<h3 id="file-api">文件API</h3>

<h4 id="writing-one-file">写入单个文件</h4>

<p>假设我们想安全地写入一个文件，避免数据损坏。在本次演讲中，这意味着我们希望写入操作是“原子的”——我们的写入要么完全完成，要么我们能撤销写入并恢复到初始状态。让我们看一个Pillai等人在OSDI’14论文中的例子。</p>

<p>我们有一个文件包含文本<code>a foo</code>，想用<code>bar</code>覆盖<code>foo</code>，最终得到<code>a bar</code>。我们将做若干简化。例如，你可能应该将我们写入的每个字符想象为磁盘上的一个扇区（或者，如果你愿意，可以想象我们使用一种假想的先进NVM驱动器）。如果你不清楚这是什么意思，别担心，我指出这一点只是为了说明本次演讲将包含许多简化，我不会逐一说明，因为我们只有二十五分钟，而未简化的版本大约需要三个小时。</p>

<p>要写入，我们可能会使用<code>pwrite</code>系统调用。这是操作系统提供的让我们与文件系统交互的函数。我们的调用如下：</p>

<pre><code>pwrite(
  [文件],
  “bar”, // 要写入的数据
  3,     // 写入3个字节
  2)     // 偏移量为2
</code></pre>

<p><code>pwrite</code>接收我们要写入的文件、要写入的数据<code>bar</code>、要写入的字节数<code>3</code>以及开始写入的偏移量<code>2</code>。如果你习惯使用高级语言如Python，你可能习惯于不同的接口，但底层在写入文件时，最终会产生像这样的系统调用，这才是实际将数据写入文件的操作。</p>

<p>如果我们像这样直接调用<code>pwrite</code>，我们可能成功并在输出中得到<code>a bar</code>，或者可能什么都没发生得到<code>a foo</code>，或者可能得到某种中间状态，如<code>a boo</code>、<code>a bor</code>等。</p>

<p>这里发生的情况是，在写入过程中我们可能崩溃或断电。由于<code>pwrite</code>不保证是原子的，如果我们崩溃，可能导致写入的部分完成，从而造成数据损坏。避免这个问题的一种方法是存储一个“撤销日志”，让我们能恢复损坏的数据。在修改文件之前，我们将要修改的数据做一个副本（存入撤销日志），然后正常修改文件，如果一切顺利，我们就删除撤销日志。</p>

<p>如果在写入撤销日志时崩溃，那没关系——我们会看到撤销日志不完整，知道无需恢复，因为我们尚未开始修改文件。如果在修改文件时崩溃，也没问题。当尝试从崩溃中恢复时，我们会看到撤销日志是完整的，可以用它来恢复数据损坏：</p>

<pre><code>creat(/d/log) // 创建撤销日志
write(/d/log, “2,3,foo”, 7) // 要撤销，在偏移量2处，写入3个字节“foo”
pwrite(/d/orig, “bar”, 3, 2) // 像之前一样修改原文件
unlink(/d/log) // 删除日志文件
</code></pre>

<p>如果我们使用<code>ext3</code>或<code>ext4</code>（广泛使用的Linux文件系统），并采用<code>data=journal</code>模式（我们稍后会讨论这些模式的含义），我们可能得到以下一些结果：</p>

<pre><code>d/log: “2,3,f”
d/orig: “a foo”

d/log: “”
d/orig: “a foo”
</code></pre>

<p>我们可能在日志文件写入进行中崩溃，导致日志文件不完整。在上面第一种情况中，我们知道日志文件不完整，因为文件说应该在偏移量<code>2</code>开始写入<code>3</code>个字节，但只指定了一个字节<code>f</code>，所以日志文件必然不完整。在第二种情况中，我们可以判断日志文件不完整，因为撤销日志格式应以偏移量和长度开头，但我们两者都没有。无论哪种方式，既然我们知道日志文件不完整，就知道无需恢复。</p>

<p>另一种可能的结果类似：</p>

<pre><code>d/log: “2,3,foo”
d/orig: “a boo”

d/log: “2,3,foo”
d/orig: “a bar”
</code></pre>

<p>第一种情况，日志文件完整，我们在写入文件时崩溃。这没问题，因为日志文件告诉我们如何恢复到已知良好状态。第二种情况，写入完成，但日志文件尚未删除，我们将从日志文件恢复。</p>

<p>如果我们使用<code>ext3</code>或<code>ext4</code>并采用<code>data=ordered</code>模式，我们可能看到：</p>

<pre><code>d/log: “2,3,fo”
d/orig: “a boo”

d/log: “”
d/orig: “a bor”
</code></pre>

<p>使用<code>data=ordered</code>模式时，无法保证对日志文件的<code>write</code>和修改原文件的<code>pwrite</code>会按程序顺序执行。相反，我们可能得到：</p>

<pre><code>creat(/d/log) // 创建撤销日志
pwrite(/d/orig, “bar”, 3, 2) // 在写入撤销日志之前就修改文件！
write(/d/log, “2,3,foo”, 7) // 写入撤销日志
unlink(/d/log) // 删除日志文件
</code></pre>

<p>为了防止这种重排序，我们可以使用另一个系统调用<code>fsync</code>。<code>fsync</code>是一个屏障（防止重排序）并刷新缓存（我们将稍后讨论）。</p>

<pre><code>creat(/d/log)
write(/d/log, “2,3,foo”, 7)
fsync(/d/log) // 添加fsync以防止重排序
pwrite(/d/orig, “bar”, 3, 2)
fsync(/d/orig) // 添加fsync以防止重排序
unlink(/d/log)
</code></pre>

<p>这在<code>ext3</code>或<code>ext4</code>的<code>data=ordered</code>模式下有效，但如果我们使用<code>data=writeback</code>模式，我们可能看到：</p>

<pre><code>d/log: “2,3,WAT”
d/orig: “a boo”
</code></pre>

<p>不幸的是，在<code>data=writeback</code>模式下，对日志文件的<code>write</code>操作不保证是原子的，跟踪文件长度的文件系统元数据可能在完成日志文件写入之前就更新了，这将使日志文件看起来包含日志文件创建位置磁盘上的任何内容。由于日志文件存在，当我们崩溃后尝试恢复时，可能会将随机数据“恢复”到原文件中。为了防止这种情况，我们可以为日志文件添加校验和（确保文件实际有效的方法）。</p>

<pre><code>creat(/d/log)
write(/d/log,“…[✓∑],foo”,7) // 为日志文件添加校验和以检测不完整的日志文件
fsync(/d/log)
pwrite(/d/orig, “bar”, 3, 2)
fsync(/d/orig)
unlink(/d/log)
</code></pre>

<p>这应该能在<code>data=writeback</code>模式下工作，但我们可能仍然看到以下情况：</p>

<pre><code>d/orig: “a boo”
</code></pre>

<p>没有日志文件！尽管我们创建了文件、写入了它，然后进行了fsync。不幸的是，无法保证目录在崩溃后实际存储文件的位置。为了确保在崩溃后恢复时能轻松找到文件，我们需要对新建日志的父目录进行fsync。</p>

<pre><code>creat(/d/log)
write(/d/log,“…[✓∑],foo”,7)
fsync(/d/log)
fsync(/d) /// 对父目录进行fsync
pwrite(/d/orig, “bar”, 3, 2)
fsync(/d/orig)
unlink(/d/log)
</code></pre>

<p>还有几件事我们需要做。我们还应在完成后进行fsync（未显示），并且需要检查错误。这些系统调用可能返回错误，这些错误需要被适当处理。至少有一个文件系统问题使这非常困难，但由于这本身不是API使用问题，我们将在<strong>文件系统</strong>部分再讨论。</p>

<p>我们现在看到了安全写入文件必须做什么。它可能比我们希望的更复杂，但似乎可行——如果有人要求你以独立方式写入文件，比如面试问题，并且你知道适当的规则，你可能可以正确完成。但如果这是日常工作的一部分，我们需要在大型代码库中每次写入文件时都安全地完成，会发生什么？</p>

<h4 id="api-in-practice">实践中的API</h4>

<p>Pillai等人在OSDI’14论文中研究了许多写入文件的软件，包括我们期望能安全写入文件的软件，如数据库和版本控制系统：Leveldb、LMDB、GDBM、HSQLDB、Sqlite、PostgreSQL、Git、Mercurial、HDFS、Zookeeper。然后他们编写了一个静态分析工具，可以发现文件API的错误使用，例如错误地假设非原子操作是原子的，错误地假设可能被重排序的操作会按程序顺序执行等。</p>

<p>当他们这样做时，他们发现测试的每个软件除SQLite在特定模式下外，都至少有一个缺陷。这不是对这些软件开发者或软件的贬低——在Leveldb、LBDM等项目工作的程序员比大多数程序员更了解文件系统，软件也有比大多数软件更严格的测试。但他们仍然无法每次都安全地使用文件！对此的一个自然后续问题是：为什么文件API如此难以使用，连专家都会犯错？</p>

<h5 id="concurrent-programming-is-hard">并发编程很难</h5>

<p>有多个原因。如果你问人们“编程中有哪些难题？”，你会得到诸如分布式系统、并发编程、安全、CSS对齐、日期等答案。</p>

<p>如果我们看看人们在并发编程中犯错导致缺陷的原因，我们发现缺陷来自诸如“错误地假设操作是原子的”和“错误地假设操作会按程序顺序执行”等。这些使并发编程困难的因素也使安全写入文件变得困难——我们在第一个例子中看到了这两种错误的例子。更广泛地说，许多使并发编程困难的因素也是使安全写入文件困难的因素，所以我们当然应该预期写入文件是困难的！</p>

<p>安全写入文件与并发编程共享的另一个特性是，很容易编写出具有罕见、非确定性故障的代码。关于文件，人们有时会说这使事情更容易（“我从未注意到数据损坏”、“你的数据大部分时候仍然完好”），但如果你因为要编写不应损坏数据的软件而希望安全地写入文件，这会使事情更困难，因为它更难判断你的代码是否真正正确。</p>

<h5 id="api-inconsistent">API不一致</h5>

<p>正如我们在第一个例子中看到的，即使使用单个文件系统，不同模式也可能有显著不同的行为。文件API的大部分都类似，行为因文件系统或同一文件系统不同模式而异。例如，如果我们看看主流文件系统，追加是原子的，除非使用<code>ext3</code>或<code>ext4</code>的<code>data=writeback</code>模式，或<code>ext2</code>在任何模式下；目录操作不能相对于任何其他操作重排序，除了在<code>btrfs</code>上。理论上，我们应该都仔细阅读POSIX规范并确保所有代码符合POSIX，但如果人们检查文件系统行为，他们倾向于针对文件系统实际行为而非抽象规范编写代码。</p>

<p>如果我们看看某个文件系统特定模式（<code>ext4</code>的<code>data=journal</code>），它似乎相对可能安全处理，但当为多种文件系统编写代码，特别是处理与<code>ext3</code>和<code>ext4</code>非常不同的文件系统（如<code>btrfs</code>）时，人们编写正确代码就变得非常困难。</p>

<h4 id="docs-unclear">文档不清晰</h4>

<p>在第一个例子中，我们看到使用不同的<code>data=</code>模式可能得到不同行为。如果我们查看<code>ext3</code>或<code>ext4</code>中这些模式含义的手册页，我们会得到：</p>

<blockquote>
<p>journal：所有数据在写入主文件系统之前都提交到日志中。</p>

<p>ordered：这是默认模式。所有数据在元数据提交到日志之前都直接强制写入主文件系统。</p>

<p>writeback：不保留数据顺序——数据可能在元数据提交到日志之后写入主文件系统。<strong>据传</strong>这是吞吐量最高的选项。它保证内部文件系统完整性，但可能允许旧数据在崩溃和日志恢复后出现在文件中。</p>
</blockquote>

<p>如果你想了解如何安全使用文件系统，并且你还不知道什么是日志文件系统，这肯定无法帮助你。如果你知道什么是日志文件系统，这会给你一些提示，但仍然不够。理论上可以通过阅读源代码了解所有细节，但对于大多数还不了解文件系统工作原理的人来说，这很不切实际。</p>

<p>对于英文文档，有lwn.net和Linux内核邮件列表（LKML）。LWN很棒，但他们无法跟上所有内容，所以如果你想要全面的信息，LKML是去处。以下是LKML上关于文件系统的一个讨论示例：</p>

<p><strong>开发者1</strong>：就个人而言，我关心元数据一致性，ext3文档表明日志保护其完整性。除非在损坏的存储设备上，你仍然需要在那里运行fsck。<br>
<strong>开发者2</strong>：正如ext3作者多年来多次表示的，无论如何你仍然需要定期运行fsck。<br>
<strong>开发者1</strong>：这在哪里有记录？<br>
<strong>开发者2</strong>：linux-kernel邮件列表归档。<br>
<strong>文件系统开发者</strong>：可能来自大约6-8年前，我发出的电子邮件中。<br></p>

<p>虽然文件系统开发者往往乐于助人，他们写信息丰富的回复，但大多数人可能不会关注过去6-8年的LKML。</p>

<h4 id="performance-correctness-conflict">性能/正确性冲突</h4>

<p>另一个问题是文件API在性能和正确性之间存在固有冲突。我们之前提到<code>fsync</code>是一个屏障（我们可以用来强制顺序）并刷新缓存。如果你曾经参与过高性能缓存（如微处理器缓存）的设计，你可能觉得将这两件事捆绑到一个原语中很不寻常。一个原因是刷新缓存有显著的性能成本，有很多情况我们想强制顺序但不想支付此性能成本。将这两件事捆绑到一个原语中迫使我们在只关心顺序时支付缓存刷新成本。</p>

<p>Chidambaram等人在SOSP’13中通过修改<code>ext4</code>添加了一个不刷新缓存的屏障机制来研究这个性能成本。他们发现，如果适当修改软件并在不需要完整<code>fsync</code>的地方使用他们的屏障操作，他们能达到大致相当于<code>ext4</code>完全禁用缓存刷新（这不安全且可能导致数据损坏）的性能，而没有牺牲安全性。然而，对大多数编写用户级软件的人来说，制作自己的文件系统并使其被采纳是不切实际的。有些数据库会完全或几乎完全绕过文件系统，但这对大多数软件来说也不切实际。</p>

<p>这就是文件API。现在我们已经看到它异常难以使用，让我们看看文件系统。</p>

<h3 id="filesystem">文件系统</h3>

<p>如果我们想确保文件系统正常工作，最基本的测试之一是在文件系统层以下注入错误，看文件系统是否正确处理它们。例如，在写入时，我们可能让磁盘未能写入数据并返回适当的错误。如果文件系统丢弃此错误或未正确处理，就意味着我们有数据丢失或数据损坏。这类似于Kyle Kingsbury昨天在他的分布式系统测试演讲中讨论的分布式系统故障类型（尽管这类错误测试起来更直接）。</p>

<p>Prabhakaran等人在SOSP’05中这样做了，他们发现对于测试的大多数文件系统，几乎所有写入错误都被丢弃了。主要的例外是ReiserFS，它在测试的所有类型错误上都做得相当好，但ReiserFS今天已不再使用，原因超出了本次演讲范围。</p>

<p><a href="//danluu.com/filesystem-errors/" rel="noopener noreferrer">我们（Wesley Aptekar-Cassels和我）在2017年再次研究了这个问题</a>，发现情况有显著改善。大多数文件系统（除JFS外）都能通过这些基本的错误处理测试。</p>

<p>寻找错误的另一种方法是查看文件系统代码，看它是否正确处理内部错误。Gunawai等人在FAST’08中这样做了，他们发现内部错误在相当比例的情况下被丢弃。他们使用的技术难以判断可能返回多种错误的函数是否正确处理了每个错误，所以他们也查看了只能返回单一错误的函数的调用。在那些情况下，根据函数不同，错误大约在2/3到3/4的时间内被丢弃。</p>

<p>Wesley和我在2017年再次研究了这个问题，发现显著改善——Gunawai等人研究的相同函数的错误“仅”在1/3到2/3的时间内被忽略，取决于函数。</p>

<p>Gunawai等人还查看了这些被丢弃错误附近的注释，发现诸如“此时忽略错误。除了尝试继续前进，我们别无他法。”（XFS）和“错误，跳过块并祈祷最好情况。”（ext3）之类的注释。</p>

<p>现在我们已经看到，虽然文件系统过去甚至丢弃最基本错误，但现在它们正确处理错误，但有些代码路径中错误可能被丢弃。对于一个具体情况，让我们回到第一个例子。如果我们遇到<a href="https://lwn.net/Articles/752063/" rel="noopener noreferrer"><code>fsync</code>错误</a>，除非我们有相当新的Linux内核（约2018年第二季度），很有可能错误会被丢弃，甚至可能被报告给错误的进程！</p>

<p>在新的Linux内核上，错误很可能会被报告（甚至给正确的进程）。Wilcox在PGCon’18中指出，<code>fsync</code>错误基本上是不可恢复的。细节取决于文件系统——在<code>XFS</code>和<code>btrfs</code>上，已修改但在文件系统中的数据将被丢弃，无法恢复。在<code>ext4</code>上，数据不会被丢弃，但被标记为未修改，因此文件系统不会尝试稍后将其写回磁盘；如果有内存压力，数据可能随时被丢弃。如果你有冒险精神，可以在数据被丢弃前尝试恢复数据，使用各种技巧（例如，强制文件系统再次将其标记为已修改，或将其写入另一个设备，这将强制文件系统写出数据，即使它被标记为未修改），但无法保证能在数据丢弃前恢复数据。在Linux <code>ZFS</code>上，似乎有一条代码路径设计来做正确的事，但CPU使用率飙升，系统可能挂起或变得无法使用。</p>

<p>一般来说，在Linux上没有好的方法恢复。Postgres、MySQL和MongoDB（广泛使用的数据库）会自身崩溃，用户需要从最后一个检查点恢复。大多数软件可能只是静默丢失或损坏数据。而<code>fsync</code>是相对较好的情况——例如，<code>syncfs</code>在Linux上根本不返回错误，导致静默数据丢失和数据损坏。</p>

<p>顺便说一句，当Craig Ringer首次提议Postgres应在<code>fsync</code>错误时崩溃，<a href="//danluu.com/fsyncgate/" rel="noopener noreferrer">Postgres开发邮件列表上的第一个回复</a>是：</p>

<blockquote>
<p>你肯定在开玩笑……如果[fsync的当前行为]确实如此，我们需要抵制这种内核脑损伤。</p>
</blockquote>

<p>但在讨论细节后，大家都同意崩溃是唯一的好选择。许多不幸的事情之一是大多数磁盘错误是暂时的。由于文件系统丢弃了在没有数据损坏情况下继续进行所需的关键信息，本可重试的暂时错误迫使软件采取激烈措施。</p>

<p>虽然我们讨论了Linux，但这并非Linux独有。在许多不同操作系统上，fsync错误处理（以及一般错误处理）都是有问题的。当Postgres“发现”Linux上fsync的行为时，FreeBSD可能有正确的实现，但OpenBSD和NetBSD的行为与Linux相同（丢弃真实错误状态，重试导致成功响应，数据丢失）。OpenBSD可能已修复此问题，其他一些BSD可能也已修复，但Linux基本上仍然有相同的行为，你无法保证在任何类UNIX操作系统上这会有效。</p>

<p>现在我们已经看到，多年来文件系统在某些最直接和简单的情况下未能正确处理错误，并且有些情况至今仍未正确处理，让我们看看磁盘。</p>

<h3 id="disk">磁盘</h3>

<h4 id="flushing">刷新</h4>

<p>我们已经看到，当我们必须调用<code>fsync</code>时很容易没有意识到，即使我们正确调用了<code>fsync</code>，缺陷也可能阻止<code>fsync</code>实际工作。Rajimwale等人在DSN’11中调查磁盘在你要求刷新时是否实际刷新，假设磁盘以上一切正常（他们的论文其实主要关于其他内容，只是在开头简要讨论）。微软某人匿名告诉他们“[某些磁盘]不允许文件系统强制将写入正确刷新到磁盘”，希捷（磁盘制造商）的某人告诉他们“[某些磁盘（但非我们生产）]不允许文件系统强制将写入正确刷新到磁盘”。Bairavasundaram等人在FAST’07中研究磁盘可靠性时也发现了同样的问题。</p>

<h4 id="error-rates">错误率</h4>

<p>我们已经看到文件系统有时不正确处理磁盘错误。如果想知道这个问题多严重，我们应该查看磁盘产生错误的速率。磁盘数据手册通常声称消费级HDD（常被称为旋转金属或旋转锈盘）不可纠正位错误率为1e-14，企业级HDD为1e-15，消费级SSD为1e-15，企业级SSD为1e-16。这意味着平均每读取1e14位，我们会预期在HDD上看到一个不可恢复的数据错误。</p>

<p>为了解这在实践中的意义，1TB现在是相当标准的磁盘大小。如果我们读取整个驱动器一次，那是1e12字节，或接近1e13位（严格说是8e12位），这意味着如果购买一个1TB HDD并读取整个磁盘约十次，我们预期会看到一个不可恢复错误。如今，我们可以购买10TB HDD，这种情况下我们预期在读取整个消费级HDD时每次都会看到错误（严格说是8/10个错误）。</p>

<p>实践中，观察到的错误率显著更高。Narayanan等人在SYSTOR’16（微软）中观察到SSD错误率从1e-11到6e-14，取决于驱动器型号。Meza等人在SIGMETRICS’15（脸书）中观察到更糟的SSD错误率，从2e-9到6e-11，取决于驱动器型号。取决于驱动器类型，2e-9是2千兆位，或250 MB，根据驱动器类别，比数据手册中声明的差五十万到五百万倍。</p>

<p>位错误率可以说是磁盘驱动器的一个糟糕指标，但这是磁盘厂商声称的指标，所以如果我们想做同等比较，必须与此比较。参见Bairavasundaram等人在SIGMETRICS’07、Schroeder等人在FAST’16等关于其他类型错误率的论文。</p>

<p>需要注意的是，经常有人声称SSD没有数据损坏问题，因为它们使用纠错码（ECC），可以修复数据损坏问题。“闪存消除了不可恢复数据错误的幽灵”，等等。这种观点忽略的是现代高密度闪存设备非常不可靠，需要ECC才能使用。Grupp等人在FAST’12中研究了SSD底层闪存的错误率，发现错误率从1e-1到1e-8。1e-1是每十位一个错误，1e-8是每1亿位一个错误。</p>

<h4 id="power-loss">断电</h4>

<p>你会听到的另一个声称是SSD对断电和某些类型崩溃是安全的，因为它们现在有“断电保护”——SSD中有某种机制能在断电期间保持足够长的电力，使内部SSD缓存可以安全写入。</p>

<p><a href="http://lkcl.net/reports/ssd_analysis.html" rel="noopener noreferrer">Luke Leighton测试了这一点</a>，他购买了6个声称有断电保护的SSD，发现测试的六款型号中有四款失败（除了英特尔驱动器外的所有驱动器）。如果我们查看测试细节，当驱动器失败时，似乎是因为它们的使用方式是断电保护实现者未预料到的（“太快”写入，尽管远低于驱动器能够写入的速率，或并行“太多”文件）。当驱动器宣传有断电保护时，这似乎意味着某人花费了一些精力实现某种东西，在某些情况下能在断电时防止数据丢失或数据损坏。但正如我们在昨天Kyle的分布式系统演讲中看到的，如果你想确保该机制确实有效，不能依赖厂商进行严格甚至任何半认真的测试，你必须自己测试。</p>

<h4 id="retention">保持力</h4>

<p>如果我们查看SSD数据手册，较新的驱动器（有90%写入周期剩余）通常规格在写入后保持数据约十年。如果我们查看磨损严重的驱动器，接近使用寿命终点的，规格保持数据时间为一年到三个月，取决于驱动器类别。我认为人们经常惊讶地发现，在数据写入三个月后丢失数据是在规格内的。</p>

<p>这些数字都来自数据手册和规格，正如我们所看到的，数据手册可能有点乐观。在许多早期SSD上，用完大部分或全部写入周期会导致驱动器变砖，所以你甚至得不到规格中提到的三个月数据保持力。</p>

<h3 id="corollaries">推论</h3>

<p>现在我们已经看到文件栈每一层都有重大问题，让我们看看由此得出的一些推论。</p>

<h4 id="what-to-do">该怎么办？</h4>

<p>对此我们该怎么办是一个大话题，在剩余的时间里，我们可以做的一件事是使用数据库而不是写入文件。如果你想轻量简单，可以在大多数使用文件的地方使用，SQLite相当不错。我不是说永远不该使用文件。这里有一个权衡。但如果你的应用程序希望降低数据损坏率，考虑使用数据库存储数据而不是使用文件。</p>

<h4 id="fs-support">文件系统支持</h4>

<p>在本次演讲开始时，我们看了这个Dropbox例子，大多数人认为没有理由移除对大多数Linux文件系统的支持，因为文件系统都一样。我相信他们的手被他们想要存储/使用数据的方式束缚了，他们只能用<code>ext</code>实现他们的方式（这可以说是一个错误特性），但即使情况并非如此，也许你能看出为什么试图可靠且具有合理性能地将数据同步到磁盘的软件可能不想支持一个操作系统上的每个文件系统（对他们的产品来说相对小众）。也许为了公关原因值得支持每个文件系统，然后为每个文件系统采取必要措施避免数据损坏（你可以尝试直接按你的POSIX规范理解编码，但正如我们所看到的，这在Linux上救不了你），但公关问题是由于误解造成的。</p>

<p>我们看的Reddit上的另一条评论，也是一种常见情绪，是说程序不应该绕过库或操作系统的缺陷。但无论错误是“谁的”责任，用户数据都会损坏，正如我们所看到的，缺陷可以在文件系统层存在多年。在<code>Linux</code>的情况下，除<code>ZFS</code>外的大多数文件系统似乎已决定在fsync错误时丢弃数据且不报告数据无法写入（与<code>FreeBSD</code>或<code>OpenBSD</code>相比，大多数文件系统在错误未解决时至少会在后续<code>fsync</code>中报告错误）。这可以说是一个缺陷，也可以说是正确的行为，但无论如何，如果你的软件没有考虑到这一点，你将丢失或损坏数据。如果你坚持认为不是你的错文件系统在损坏数据，你的用户将为此付出代价。</p>

<h3 id="faq">常见问题</h3>

<p>在准备这次演讲时，我阅读了许多关于如何安全写入文件的在线讨论。在专业社区（如LKML、Postgres邮件列表等）之外，许多人会出来说类似“为什么大家都把这弄得这么复杂？你可以用这个简单的技巧轻松且完全安全地做到”。让我们看看两千条关于如何安全写入磁盘的互联网评论中最常见的“简单技巧”。</p>

<h4 id="rename">重命名</h4>

<p>最常被提及的技巧是重命名而不是覆盖。如果你记得我们写入单个文件的例子，我们在修改文件之前复制了要覆盖的数据。这里的技巧是做相反的事情：</p>

<ol>
<li>复制整个文件</li>
<li>修改副本</li>
<li>将副本重命名覆盖原文件</li>
</ol>

<p>这个技巧无效。人们似乎认为这是安全的，因为POSIX规范说<code>rename</code>是原子的，但这只意味着<code>rename</code>相对于正常操作是原子的，并不意味着在崩溃时是原子的。这不只是理论问题；如果我们看看主流Linux文件系统，大多数至少有一种模式在崩溃时重命名不是原子的。重命名也不保证按程序顺序执行，正如人们有时预期的那样。</p>

<p>最主流的重命名在崩溃时是原子的例外可能是<code>btrfs</code>，但即使在那里，也有点微妙——正如Bornholt等人在ASPLOS’16中指出的，<code>rename</code>仅在替换现有文件时在崩溃时是原子的，在创建新文件时则不是。而且Mohan等人在OSDI’18中发现了<code>btrfs</code>上众多重命名原子性缺陷，有些相当古老，有些在论文发表同年引入，所以即使你在写<code>btrfs</code>特定代码，也不应依赖此特性而不经过广泛测试。</p>

<p>即使这有效，该技术的性能也很差。</p>

<h4 id="append">追加</h4>

<p>第二个最常被提及的技巧是只追加（而不是有时覆盖）。这也无效。正如Pillai等人在OSDI’14和Bornholt等人在ASPLOS’16中指出的，追加不保证顺序或原子性，相信追加是安全的是某些缺陷的原因。</p>

<h4 id="one-weird-tricks">简单技巧</h4>

<p>我们已经看到最常被引用的简单技巧无效。我觉得有趣的是，在这些讨论中，人们会介入一个已经解释过的讨论（通常非常详细）为什么写入文件比某人天真想象的要困难，忽略所有警告和解释，仍然坚持他们的解释，认为这实际上真的很容易。即使被警告文件比人们想的要难，人们仍然认为它们很容易！</p>

<h3 id="conclusion">结论</h3>

<p>总之，计算机不工作（但如果你在Gary-conf，你可能已经知道这一点）。这次演讲恰好关于文件，但有许多领域我们可以深入研究，会看到类似的情况。</p>

<p>在结束前，我想指出，我认为根本问题不是技术性的。如果你看看大型科技公司（如FB、亚马逊、MS、谷歌等）的做法，它们通常相当安全地处理磁盘写入。他们会确保拥有真正有效的断电保护磁盘，他们会有操作系统补丁或其他工具确保错误正确报告，会有大型分布式存储组确保数据安全复制等。我们知道如何使这些东西相当可靠。这很难，需要大量时间和努力，即很多钱，但可以做到。</p>

<p>如果你问从事这类工作的人为什么他们花费惊人的金钱来确保（或者真的，增加）正确性，你经常会得到类似“我们有数以百万计的机器，如果你计算数据损坏率，如果我们不做所有这些，我们每天每分钟都会有数据损坏。这将完全无法忍受”的答案。一家大型科技公司可能有大约一千万台机器？有趣的是，如果你计算消费者机器数量以及多少消费者软件运行在不可靠的磁盘上，数字是相似的。消费者机器多得多；它们通常在更轻的负载下运行，但数量足够多，如果你拥有一款广泛使用的桌面/笔记本电脑/工作站软件，数据损坏的数字相当相似。没有“极端”保护，我们应该预期会看到数据损坏一直发生。</p>

<p>但如果我们看看消费者软件如何运作，它在处理数据方面通常很不安全。我认为这里的关键区别是，当一家大型科技公司丢失数据时，无论是关于谁可能点击哪些广告的数据还是用户电子邮件，公司直接或间接地付出代价，且代价足够大，显然值得投入大量精力避免数据丢失。但当消费者自己的机器上数据损坏时，他们大多不够专业，不知道是谁的责任，所以公司可以避免承担主要指责。如果我们有一个全局优化函数，数字是相同的——当然我们应该投入更多精力保护消费者机器上的数据。但如果我们是一家为自己利益局部优化的公司，数字计算就不同了，也许不值得花很多精力避免数据损坏。</p>

<p>昨天，Ramsey Nasser发表演讲，他提出了一个非常令人信服的理由说明某个问题是严重问题，随后有评论说他提出的解决方案很难被采纳。我同意这两部分——他讨论了一个重要问题，但解决这个问题如何让某人赚很多钱尚不清楚，所以问题可能得不到解决。</p>

<p>通过GDPR，我们看到监管可以迫使科技公司以他们不倾向于的方式保护人们的隐私，但监管是一把非常大的锤子，意外后果常常会抵消或超过监管的好处。当我们回顾旨在迫使公司做正确事情的监管历史时，我们常常看到需要很多年，有时几十年，才能完全理解监管的全部影响。设计好的监管很难，比我们今天讨论的任何技术问题都难得多。</p>

<h3 id="acknowledgements">致谢</h3>

<p>感谢Leah Hanson、Gary Bernhardt、Kamal Marhubi、Rebecca Isaacs、Jesse Luehrs、Tom Crayford、Wesley Aptekar-Cassels、Rose Ames、chozu@fedi.absturztau.be和Benjamin Gilbert对这次演讲的帮助！</p>

<p>抱歉我们讲得这么快。如果你错过了任何内容，可以在danluu.com/deconstruct-files的伪文字稿中查看。</p>

<p><small><em>这份“文字稿”相当粗糙，因为我是今天早上在演讲前匆忙写成的。我会在几周内尝试清理，包括添加遗漏的内容、插入链接、修正错别字、添加遗漏的参考文献等。</em></small></p><small>

</small><p><small><em>感谢Anatole Shaw、Jernej Simoncic、@junh1024、Yuri Vishnevsky和Josh Duff对这份文字稿的评论/修正/讨论。</em></small></p>

<p>
</p><p><em>由 mimo-v2.5 模型翻译，花费 19251 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/deconstruct-files/</link>
      <guid isPermaLink="false">https://danluu.com/deconstruct-files/</guid>
      <pubDate>Fri, 12 Jul 2019 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>《守望先锋》性别随机试验</title>
      <description>&lt;p&gt;《守望先锋》（及其他网络游戏）中一个反复出现的讨论是，女性玩家是否受到与男性玩家不同的对待。如果你快速搜索一下，会发现数百篇相关讨论，其中一些评论数甚至超过千条。这些讨论往往走向相同的模式，每次涉及相同的辩论，双方提出的观点也大同小异。例如，仅由一篇帖子引发的&lt;a href=&quot;https://www.reddit.com/r/Overwatch/comments/8hvmih/the_gir</description>
      <content:encoded><![CDATA[<p>《守望先锋》（及其他网络游戏）中一个反复出现的讨论是，女性玩家是否受到与男性玩家不同的对待。如果你快速搜索一下，会发现数百篇相关讨论，其中一些评论数甚至超过千条。这些讨论往往走向相同的模式，每次涉及相同的辩论，双方提出的观点也大同小异。例如，仅由一篇帖子引发的<a href="https://www.reddit.com/r/Overwatch/comments/8hvmih/the_girl_problem_an_open_letter_to_the_overwatch/" rel="noopener noreferrer">这</a><a href="https://www.reddit.com/r/Overwatch/comments/8i4wvs/a_response_to_the_girl_problem_post_moral/" rel="noopener noreferrer">三</a><a href="https://www.reddit.com/r/Overwatch/comments/8i7cbx/when_we_call_talking_about_sexism_in_overwatch/" rel="noopener noreferrer">个</a>Reddit讨论串，总评论数就达10.4万。一方认为，“女性确实会被喷，但我是男的也被喷，每个人都会被喷，没有区别”，“我从未见过这种事，不可能是真的”等。另一方则说：“我和男友一起玩时，总被指责是他带飞，但反过来从未发生”，“人们经常告诉我应该玩辅助（一个女性治疗角色）”等等。与其花时间进行一次大型讨论，不如直接做实验，因此就有了这项研究。</p>

<p>这是在两大主要游戏模式——快速游戏（QP）和竞技模式（comp）中进行339场比赛的结果。其中约一半比赛使用男性化用户名（使用一个常见的男性术语），另一半使用女性化用户名（使用一个女性名字）。我记录了每场比赛中的所有评论，并按类型分类。评论类别包括“性/性别相关评论”、“被指导如何游戏”、“侮辱性评论”和“赞美评论”。</p>

<p>在每场纳入实验的比赛中，我都是在英雄选择界面加载前决定是否将其纳入。在纳入的比赛中，我使用相同的英雄选择算法，不会因刷屏或行为恶劣而屏蔽任何人，不在语音聊天中说话（尽管我启用了语音），从不发送好友请求，且不组队以确保与5名随机队友匹配。正常游玩时，我可能会选择一个不熟悉的英雄，并会屏蔽那些发表不当言论的人。许多比赛未被纳入实验，因为我不愿听人骂队友十五分钟，而我的实验流程要求预先承诺不屏蔽这类人。</p>

<h3 id="sexual-or-sexually-charged-comments">性或性暗示评论</h3>

<p>我原以为使用女性化用户名时会看到更多性评论，但事实并非如此。两种情况下都有关于性、生殖器等的提及，比率并无显著差异，且在男性化条件下实际更高。</p>

<p>在男性化条件下，没有一场游戏有针对我个人的性评论；在女性化条件下，两场游戏（共184场）有针对我个人的评论。大多数评论是针对其他玩家或在队伍/游戏聊天中的泛泛而谈。</p>

<p>两种条件下都可能出现的典型非针对性评论示例包括：“我女友不停发色情短信，怎么让她停下？”，“深入腹地”，“真意外。（抚弄XX）[赛后精彩时刻]”，以及“支持当地胸部”。</p>

<p>两场有针对我个人的性评论的游戏中的具体评论如下：</p>
<ul>
<li>“女士能给我点OO吗”、“是的女士，求你了”[来自两个不同的人]，“:boicootie:”</li>
<li>“我XX硬了”[根据上下文推断是针对我]</li>
</ul>

<p>在未纳入实验的比赛中（通常我没注意当时用的哪个用户名），我也收到过“发裸照”这类评论。据我观察，这类针对性评论的比率似乎有所不同，但实验中观察到的比率非常低，以至于除非使用<a href="https://en.wikipedia.org/wiki/Strong_prior" rel="noopener noreferrer">强先验</a>，否则任何真实比率估计的<a href="https://statmodeling.stat.columbia.edu/2010/12/21/lets_say_uncert/" rel="noopener noreferrer">不确定性区间</a>在两种条件下都会相似。</p>

<p>在339场游戏中未能观察到这种差异令我惊讶，但这与<a href="https://aquila.usm.edu/cgi/viewcontent.cgi?article=1429&amp;context=honors_theses" rel="noopener noreferrer">McDaniel关于女性玩家的调查论文</a>并不矛盾。339场游戏对重度玩家来说可能显得不多，但我所知的另一项关于此主题的随机实验（除了本实验）是<a href="https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0131613" rel="noopener noreferrer">Kasumovic等人的研究</a>，其中指出“[我们]在163[场游戏]后停止，因为这已耗费大量时间”。</p>

<p>所有分析均使用某类评论出现的游戏场次数量而非语气，以避免为评论标注语气可能引入偏差。情感分析模型，即便是最先进的，也常<a href="https://twitter.com/danluu/status/896176897675153409" rel="noopener noreferrer">返回无意义的结果</a>，因此目前基本上只能手工完成。如果有更大数据量，进行某种情感分析（辅以大量抽查和模型再训练）可能可行，但在此案例中评论总数太少，实质上仍需逐条手工编码。</p>

<p>手工编码评论也可以以一定程度的盲法进行，但这可能需要更多人参与（因为我在游玩时看到和听到评论），并依赖无薪或低薪劳动。</p>

<h3 id="being-told-how-to-play">被指导如何游戏</h3>

<p>最显著、易于量化的差异是，在“人们告诉我该如何玩”的游戏中进行的场次比率。由于仅看原始比率难以判断我们对这种差异应有多大信心，我们将使用简单的统计模型来获得估计值周围的<a href="https://statmodeling.stat.columbia.edu/2010/12/21/lets_say_uncert/" rel="noopener noreferrer">不确定性区间</a>。由于我不确定对此应有的信念是什么，这里使用<a href="https://en.wikipedia.org/wiki/Prior_probability#Uninformative_priors" rel="noopener noreferrer">无信息先验</a>，因此估计值接近实际比率。以下是简单模型给出的、在至少一人告诉我玩错了、应改变玩法或应更换英雄的游戏中所占百分比的不确定性区间：</p>

<p>
</p><div><p></p>
<table>
<thead>
<tr>
<th>条件</th>
<th>估计值</th>
<th>25%分位数</th>
<th>75%分位数</th>
</tr>
</thead>
<tbody>
<tr>
<td>女性化 竞技</td>
<td>19</td>
<td>13</td>
<td>25</td>
</tr>
<tr>
<td>男性化 竞技</td>
<td>6</td>
<td>2</td>
<td>10</td>
</tr>
<tr>
<td>女性化 快速</td>
<td>4</td>
<td>3</td>
<td>6</td>
</tr>
<tr>
<td>男性化 快速</td>
<td>1</td>
<td>0</td>
<td>2</td>
</tr>
</tbody>
</table>
<p>此表中的实验条件是男性化与女性化用户名（M/F）和竞技模式与快速游戏（comp/QP）。数字为百分比。<code>Est</code>是估计值，<code>P25</code>是25%分位数估计值，<code>P75</code>是75%分位数估计值。竞技模式和使用女性化用户名都与被指导如何游戏相关。参见<a href="http://andrewgelman.com/2016/11/05/why-i-prefer-50-to-95-intervals/" rel="noopener noreferrer">Andrew Gelman的这篇博文</a>，了解为何可能要看50%区间而非95%区间。</p>

<p>对于不熟悉《守望先锋》的玩家，在竞技模式中，你会明确看到自己的类ELO评级，并获得反映你评级的徽章。在快速游戏中，你的评级会被追踪，但不会直接显示给用户，你也不会获得徽章。</p>

<p>通常认为，玩家在竞技游戏中更紧张，更可能发火（例如，告诉你该如何玩）。数据与这一普遍看法一致。</p>

<p>如前所述，我不希望为消息语气编码以避免偏差，因此此表仅显示人们告诉我玩错了或要求我换英雄的游戏频率。此表低估了体验的质的差异。例如，在男性化条件下唯一一次有人要求我换英雄时，请求是一句礼貌的：“嘿，我们死太快了，我们能从标准的一主奶一副奶配置换成双主奶，或者换我们的坦克到（一个能挡更多伤害的坦克）吗？”而在使用女性化用户名时，典型情况是1-4人整场游戏都在骂我是人渣，并自我安慰地认为团队输掉的唯一原因就是我不换英雄。</p>

<p>我们使用的简单模型表明，竞技模式与快速游戏之间、使用男性化与女性化用户名之间，很可能都存在差异。然而The request was rejected because it was considered high risk</p></div><p><em>由 mimo-v2.5 模型翻译，花费 11485 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/overwatch-gender/</link>
      <guid isPermaLink="false">https://danluu.com/overwatch-gender/</guid>
      <pubDate>Tue, 19 Feb 2019 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Fsyncgate：fsync错误不可恢复</title>
      <description>&lt;p&gt;&lt;small&gt;&lt;em&gt;这是原始&quot;Fsyncgate&quot;邮件讨论的存档。之所以在此发布，是因为我需要一个适合&lt;a href=&quot;//danluu.com/deconstruct-files/&quot;&gt;关于文件安全的演讲&lt;/a&gt;幻灯片的链接，该链接采用&lt;a href=&quot;//danluu.com/web-bloat/&quot;&gt;移动友好且简洁的格式&lt;/a&gt;。&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code</description>
      <content:encoded><![CDATA[<p><small><em>这是原始"Fsyncgate"邮件讨论的存档。之所以在此发布，是因为我需要一个适合<a href="//danluu.com/deconstruct-files/" rel="noopener noreferrer">关于文件安全的演讲</a>幻灯片的链接，该链接采用<a href="//danluu.com/web-bloat/" rel="noopener noreferrer">移动友好且简洁的格式</a>。</em></small></p>

<pre><code>发件人：Craig Ringer &lt;craig(at)2ndquadrant(dot)com&gt;
主题：回复：PostgreSQL 对 fsync() 错误的处理不安全，至少在 XFS 上存在数据丢失风险
日期：2018-03-28 02:23:46
</code></pre>

<p>大家好</p>

<p>不久前，我遇到一个问题：用户在遇到存储错误后发生数据损坏。PostgreSQL 在此过程中起到了一定作用，因为它在检查点期间允许将本应是致命错误的情况视为可恢复错误。</p>

<p>简而言之：Pg 应在 fsync() 返回 EIO 时立即进入 PANIC 状态。至少在 Linux 上，重试 fsync() 是不安全的。当 fsync() 返回成功时，其含义是"自上次 fsync 以来的所有写操作都已落盘"，但我们（PostgreSQL）却将其理解为"自上次成功 fsync 以来的所有写操作都已落盘"。</p>

<p>Pg 写入了一些数据块，这些块进入操作系统的脏缓冲区等待回写。回写因底层存储错误而失败。块 I/O 层和 XFS 将回写页面标记为失败（AS_EIO），但无法将错误信息传递给应用程序。当 Pg 在下一个检查点期间对文件描述符调用 fsync() 时，fsync() 由于标记的页面而返回 EIO，以告知 Pg 之前的一次异步写操作失败了。Pg 将该检查点视为失败，并未在控制文件中推进重做起始位置。</p>

<p>到目前为止一切正常。</p>

<p>但随后我们重试了该检查点，也就重试了 fsync()。由于之前的 fsync() <em>清除了 AS_EIO 错误页面标志</em>，重试成功了。</p>

<p>写操作实际上从未落盘，但我们却完成了检查点，并愉快地继续运行。糟糕，数据丢失了。</p>

<p>据我所知，fsync 这种"清除错误并继续"的行为并未被记录在文档中。同样，除非你拥有一个非常新的 Linux man-pages（其中包含了我编写用于添加该行为的补丁），否则 fsync() 返回 EIO 的行为也未被文档化。但根据我所看到的 POSIX 标准，我们根本没有得到关于 fsync() 失败后会发生什么的任何保证，因此我们假设重试 fsync() 是安全的，这很可能是错误的。</p>

<p>如果服务器使用的是带有 errors=remount-ro 选项的 ext3 或 ext4，该问题就不会发生，因为第一个 I/O 错误就会重新挂载文件系统并阻止 Pg 继续运行。但 XFS 没有这个选项。可能还存在其他情况，涉及 LVM 和/或多路径，但我尚未全面挖掘细节。</p>

<p>结果证明，可以通过伪造一个早于第一个错误成功检查点的备份标签来恢复系统，强制重做重复操作并写入丢失的数据块。但这……一团糟。</p>

<p>我之前在这里讨论过底层的 fsync 问题：</p>

<p><a href="https://stackoverflow.com/q/42434872/398670" rel="noopener noreferrer">https://stackoverflow.com/q/42434872/398670</a></p>

<p>但一直没有机会跟进 Pg 的具体情况。</p>

<p>我断断续续地研究这个问题，至今未找到好的解决方案。我认为我们应该直接 PANIC，让重做通过重复自上次检查点以来失败的写操作来解决问题。</p>

<p>异步缓冲写和 fsync 提供的 API 没有办法让我们知道哪个页面失败了，因此我们无法选择性地重做那次写操作。我认为我们确实知道与 fsync 失败的文件描述符关联的 relfilenode，但除此之外知之甚少。所以替代方案似乎是某种可能复杂的在线重做方案，即仅对具有 fsync() 错误的关联执行 WAL 重放，同时其他查询照常服务。这很可能极易出错且难以测试，并且试图解决的问题在其他文件系统上会导致整个数据库停止运行。</p>

<p>我调查了是否可以通过使用 AIO API 来解决，但情况更糟——据我所知，在所有 Linux 内核版本上，你甚至无法可靠地保证 fsync。</p>

<p>我们已经在 WAL 段上遇到 fsync() 失败时 PANIC。我们只需要对数据分支做同样的处理，至少是针对 EIO。这并不像看起来那么糟糕，因为据我所知，fsync 只在我们应该停止整个世界的情况下才返回 EIO，并且许多文件系统会替我们做到这一点。</p>

<p>有相当多的 pg_fsync() 调用点。虽然我们可以逐个案例处理每个调用，但我倾向于直接让 pg_fsync() 本身拦截 EIO 并 PANIC。大家怎么看？</p>

<hr>

<pre><code>发件人：Tom Lane &lt;tgl(at)sss(dot)pgh(dot)pa(dot)us&gt;
日期：2018-03-28 03:53:08
</code></pre>

<p>Craig Ringer  写道：</p>

<blockquote>
<p>简而言之：Pg 应在 fsync() 返回 EIO 时 PANIC。</p>
</blockquote>

<p>你肯定是在开玩笑。</p>

<blockquote>
<p>至少在 Linux 上，重试 fsync() 是不安全的。当 fsync() 返回成功时，其含义是"自上次 fsync 以来的所有写操作都已落盘"，但我们假设其含义是"自上次成功 fsync 以来的所有写操作都已落盘"。</p>
</blockquote>

<p>如果情况确实如此，我们需要对这种内核的脑残行为进行抵制，因为如你所描述的，fsync 将完全无用。</p>

<p>此外，POSIX 规范非常明确：成功的 fsync 意味着之前对该文件的所有写操作都已完成，句号，无论它们何时发出。</p>

<hr>

<pre><code>发件人：Michael Paquier &lt;michael(at)paquier(dot)xyz&gt;
日期：2018-03-29 02:30:59
</code></pre>

<p>2018年3月27日（星期二）下午 11:53:08 EDT，Tom Lane 写道：</p>

<blockquote>
<p>Craig Ringer  写道：</p>

<blockquote>
<p>简而言之：Pg 应在 fsync() 返回 EIO 时 PANIC。</p>
</blockquote>

<p>你肯定是在开玩笑。</p>
</blockquote>

<p>后端代码中任何 pg_fsync 的调用者都非常仔细地检查返回的状态，有时会在 mdsync 中进行重试，因此这里提出的方案将是一个退步。</p>

<hr>

<pre><code>发件人：Thomas Munro &lt;thomas(dot)munro(at)enterprisedb(dot)com&gt;
日期：2018-03-29 02:48:27
</code></pre>

<p>2018年3月29日（星期四）下午 3:30，Michael Paquier  写道：</p>

<blockquote>
<p>2018年3月27日（星期二）下午 11:53:08 EDT，Tom Lane 写道：</p>

<blockquote>
<p>Craig Ringer  写道：</p>

<blockquote>
<p>简而言之：Pg 应在 fsync() 返回 EIO 时 PANIC。</p>
</blockquote>

<p>你肯定是在开玩笑。</p>
</blockquote>

<p>后端代码中任何 pg_fsync 的调用者都非常仔细地检查返回的状态，有时会在 mdsync 中进行重试，因此这里提出的方案将是一个退步。</p>
</blockquote>

<p>Craig，你描述的现象是否与本文中讨论的第二个问题“报告回写错误”相同？</p>

<p><a href="https://lwn.net/Articles/724307/" rel="noopener noreferrer">https://lwn.net/Articles/724307/</a></p>

<p>“当前内核可能在 fsync() 调用上报告回写错误，但有多种方式可能导致此操作失败。”</p>

<p>这……我无语了。</p>

<hr>

<pre><code>发件人：Justin Pryzby &lt;pryzby(at)telsasoft(dot)com&gt;
日期：2018-03-29 05:00:31
</code></pre>

<p>2018年3月29日（星期四）上午 11:30:59 +0900，Michael Paquier 写道：</p>

<blockquote>
<p>2018年3月27日（星期二）下午 11:53:08 EDT，Tom Lane 写道：</p>

<blockquote>
<p>Craig Ringer  写道：</p>

<blockquote>
<p>简而言之：Pg 应在 fsync() 返回 EIO 时 PANIC。</p>
</blockquote>

<p>你肯定是在开玩笑。</p>
</blockquote>

<p>后端代码中任何 pg_fsync 的调用者都非常仔细地检查返回的状态，有时会在 mdsync 中进行重试，因此这里提出的方案将是一个退步。</p>
</blockquote>

<p>重试正是问题的根源；第一次 fsync() 可能返回 EIO，并且<em>同时清除了错误</em>，导致第二次 fsync（对相同的数据）返回成功。</p>

<p>（注意，我明白在 EIO 时 PANIC 而在 ENOSPC 时重试可能有用。）</p>

<p>2018年3月29日（星期四）下午 03:48:27 +1300，Thomas Munro 写道：</p>

<blockquote>
<p>Craig，你描述的现象是否与本文中讨论的第二个问题“报告回写错误”相同？
<a href="https://lwn.net/Articles/724307/" rel="noopener noreferrer">https://lwn.net/Articles/724307/</a></p>
</blockquote>

<p>更糟的是，该文章承认了这种行为但并未建议更改：</p>

<p>“将该值存储在文件结构中有一个重要好处：它使得向每个调用 FSYNC() 的进程报告一次且仅一次的回写错误成为可能……在当前内核中，只有错误发生后第一个调用者有机会看到该错误信息。”</p>

<p>我相信我用 dmsetup "error" 目标复现了该问题行为，请参见附件。</p>

<p>strace 看起来像这样：</p>

<p>内核版本为 Linux 4.10.0-28-generic #32~16.04.2-Ubuntu SMP Thu Jul 20 10:19:48 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux</p>

<pre><code>1open("/dev/mapper/eio", O_RDWR|O_CREAT, 0600) = 3
2write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192
3write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192
4write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192
5write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192
6write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192
7write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 8192
8write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = 2560
9write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192) = -1 ENOSPC (No space left on device)
10dup(2)                                  = 4
11fcntl(4, F_GETFL)                       = 0x8402 (flags O_RDWR|O_APPEND|O_LARGEFILE)
12brk(NULL)                               = 0x1299000
13brk(0x12ba000)                          = 0x12ba000
14fstat(4, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
15write(4, "write(1): No space left on devic"..., 34write(1): No space left on device
16) = 34
17close(4)                                = 0
18fsync(3)                                = -1 EIO (Input/output error)
19dup(2)                                  = 4
20fcntl(4, F_GETFL)                       = 0x8402 (flags O_RDWR|O_APPEND|O_LARGEFILE)
21fstat(4, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
22write(4, "fsync(1): Input/output error\n", 29fsync(1): Input/output error
23) = 29
24close(4)                                = 0
25close(3)                                = 0
26open("/dev/mapper/eio", O_RDWR|O_CREAT, 0600) = 3
27fsync(3)                                = 0
28write(3, "\0", 1)                       = 1
29fsync(3)                                = 0
30exit_group(0)                           = ?
</code></pre>

<p>2: 最初未看到 EIO 是因为回写页缓存；</p>

<p>9: ENOSPC 由于设备较小；</p>

<p>18: 原始 IO 错误通过 fsync 报告，很好；</p>

<p>25: 原始文件描述符被关闭；</p>

<p>26: ……文件重新打开；</p>

<p>27: 对包含仍脏数据+EIO 的文件进行 fsync 返回成功，错误；</p>

<p>10, 19: 我不确定为什么会有 dup(2)，我猜 glibc 认为 perror 应该写到单独的 FD（？）</p>

<p>另外请注意，close() 也返回成功……这可能会让你认为第二次 fsync() 无辜，但我认为这本身可能就有问题，不是吗？无论如何，第二个字节肯定从未写入 DM 错误设备，并且在 fsync() 之后丢失了失败状态。</p>

<p>如果我在一次 write() 循环后中断，以避免 ENOSPC，我会得到完全相同的行为。</p>

<hr>

<pre><code>发件人：Thomas Munro &lt;thomas(dot)munro(at)enterprisedb(dot)com&gt;
日期：2018-03-29 05:06:22
</code></pre>

<p>2018年3月29日（星期四）下午 6:00，Justin Pryzby  写道：</p>

<blockquote>
<p>重试正是问题的根源；第一次 fsync() 可能返回 EIO，并且<em>同时清除了错误</em>，导致第二次 fsync（对相同的数据）返回成功。</p>
</blockquote>

<p>我无法理解的是那个错误标志（无论它是一个位还是如补丁中描述的计数器）为什么如此重要。如果回写失败了，<em>该页面仍然是脏的</em>。因此所有对 fsync() 的未来调用都需要尝试再次刷新它，并且（大概）会再次失败（除非这次碰巧成功）。</p>

<hr>

<pre><code>发件人：Craig Ringer &lt;craig(at)2ndquadrant(dot)com&gt;
日期：2018-03-29 05:25:51
</code></pre>

<p>2018年3月29日 13:06，Thomas Munro 
写道：</p>

<blockquote>
<p>2018年3月29日（星期四）下午 6:00，Justin Pryzby 
写道：</p>

<blockquote>
<p>重试正是问题的根源；第一次 fsync() 可能返回 EIO，并且<em>同时清除了错误</em>，导致第二次 fsync（对相同的数据）返回成功。</p>
</blockquote>

<p>我无法理解的是那个错误标志（无论它是一个位还是如补丁中描述的计数器）为什么如此重要。如果回写失败了，<em>该页面仍然是脏的</em>。因此所有对 fsync() 的未来调用都需要尝试再次刷新它，并且（大概）会再次失败（除非这次碰巧成功）。
<a href="http://www.enterprisedb.com" rel="noopener noreferrer">http://www.enterprisedb.com</a></p>
</blockquote>

<p>你可能会这么认为。但实际情况似乎并非如此。你可以自己通过将错误设备映射器目标映射到卷的一部分来看到这一点。</p>

<p>我在这里写了一个测试用例。</p>

<p><a href="https://github.com/ringerc/scrapcode/blob/master/testcases/fsync-error-clear.c" rel="noopener noreferrer">https://github.com/ringerc/scrapcode/blob/master/testcases/fsync-error-clear.c</a></p>

<p>我不假装内核的行为是合理的。而且我可能在分析中犯了错误。但既然我在实际环境中观察到这一点，并在测试用例中看到，我强烈怀疑我所描述的正是正在发生的事情，不管多么脑残。</p>

<p>大概是内核在将页面分派给 I/O 子系统时将其标记为干净，并且在 I/O 错误时不会重新标记为脏？我还没有深入挖掘内核这方面的内容。详见我在 StackOverflow 帖子中关于内核代码分析的发现。</p>

<hr>

<pre><code>发件人：Craig Ringer &lt;craig(at)2ndquadrant(dot)com&gt;
日期：2018-03-29 05:32:43
</code></pre>

<p>2018年3月29日 10:48，Thomas Munro 
写道：</p>

<blockquote>
<p>2018年3月29日（星期四）下午 3:30，Michael Paquier 
写道：</p>

<blockquote>
<p>2018年3月27日（星期二）下午 11:53:08 EDT，Tom Lane 写道：</p>

<blockquote>
<p>Craig Ringer  写道：</p>

<blockquote>
<p>简而言之：Pg 应在 fsync() 返回 EIO 时 PANIC。</p>
</blockquote>

<p>你肯定是在开玩笑。</p>
</blockquote>

<p>后端代码中任何 pg_fsync 的调用者都非常仔细地检查返回的状态，有时会在 mdsync 中进行重试，因此这里提出的方案将是一个退步。</p>
</blockquote>

<p>Craig，你描述的现象是否与本文中讨论的第二个问题“报告回写错误”相同？</p>

<p><a href="https://lwn.net/Articles/724307/" rel="noopener noreferrer">https://lwn.net/Articles/724307/</a></p>
</blockquote>

<p>看起来像是它的变种。</p>

<p>我们这种情况下的问题是，内核只向我们报告了一次错误。然后它就忘记了。所以是的，这似乎是以下陈述的一个变种：</p>

<blockquote>
<p>“当前内核可能在 fsync() 调用上报告回写错误，但有多种方式可能导致此操作失败。”</p>

<p>这……我无语了。</p>
</blockquote>

<p>是的。</p>

<p>这有点疯狂。</p>

<p>当我看到这种行为并且它似乎未被记录在文档中时，我感到惊讶。</p>

<hr>

<pre><code>发件人：Craig Ringer &lt;craig(at)2ndquadrant(dot)com&gt;
日期：2018-03-29 05:35:47
</code></pre>

<p>2018年3月29日 10:30，Michael Paquier  写道：</p>

<blockquote>
<p>2018年3月27日（星期二）下午 11:53:08 EDT，Tom Lane 写道：</p>

<blockquote>
<p>Craig Ringer  写道：</p>

<blockquote>
<p>简而言之：Pg 应在 fsync() 返回 EIO 时 PANIC。</p>
</blockquote>

<p>你肯定是在开玩笑。</p>
</blockquote>

<p>后端代码中任何 pg_fsync 的调用者都非常仔细地检查返回的状态，有时会在 mdsync 中进行重试，因此这里提出的方案将是一个退步。</p>
</blockquote>

<p>我在最初的帖子中已经讨论过这一点。</p>

<p>是的，我们检查返回值。但我们用它做什么？对于堆文件的 fsync，我们返回 ERROR，中止检查点。我们稍后会重试检查点，这将重试 fsync()。<strong>而这次它现在似乎成功了</strong>，因为内核在第一次告诉我们之后忘记了它丢失了我们的写操作。所以我们确实检查了错误代码，返回成功，我们完成检查点并继续前进。</p>

<p>但我们只重试了 fsync，而不是 fsync 之前的写操作。</p>

<p>所以我们丢失了数据。或者更确切地说，我们没有检测到内核已经这样做了，所以我们的检查点是错误的并且无法完成。</p>

<p>问题在于我们在<em>没有</em>重复导致检查点的写操作的情况下不断重试检查点，并且重试 fsync。</p>

<p>我不假装内核的行为是合理的，但我们最好还是处理它。</p>

<hr>

<pre><code>发件人：Craig Ringer &lt;craig(at)2ndquadrant(dot)com&gt;
日期：2018-03-29 05:58:45
</code></pre>

<p>2018年3月28日 23:53，Tom Lane  写道：</p>

<blockquote>
<p>Craig Ringer  写道：</p>

<blockquote>
<p>简而言之：Pg 应在 fsync() 返回 EIO 时 PANIC。</p>
</blockquote>

<p>你肯定是在开玩笑。</p>
</blockquote>

<p>不。我非常认真。更糟的是，我们可能必须对 ENOSPC 也做同样的事情，以避免类似的丢失页面写问题。</p>

<p>在 ext3/ext4 上，如果使用 errors=remount-ro，这并不是必需的，但这仅仅是因为 FS 会阻止我们。</p>

<p>我不假装这是合理的。内核的行为在我看来是疯狂的。如果它要丢失一次写操作，它至少应该将 FD 标记为损坏，这样就不会有任何进一步的 fsync() 或任何其他操作可以在 FD 上成功，并且一个关心持久性的应用程序必须重复自上次成功 fsync() 以来的全部工作。仅仅报告一次然后忘记是疯狂的。</p>

<p>但即使我们让内核人员意识到这一点，其他平台如何表现？这些内核还要使用多久？我们最好处理它，不管多么疯狂。</p>

<p>请参阅我的 StackOverflow 帖子以了解内核级别的解释。另请注意那里的测试用例链接。<a href="https://stackoverflow.com/a/42436054/398670" rel="noopener noreferrer">https://stackoverflow.com/a/42436054/398670</a></p>

<blockquote>
<blockquote>
<p>至少在 Linux 上，重试 fsync() 是不安全的。当 fsync() 返回成功时，其含义是"自上次 fsync 以来的所有写操作都已落盘"，但我们假设其含义是"自上次成功 fsync 以来的所有写操作都已落盘"。</p>
</blockquote>

<p>如果情况确实如此，我们需要对这种内核的脑残行为进行抵制，因为如你所描述的，fsync 将完全无用。</p>
</blockquote>

<p>它并非无用，它只是告诉我们一些与我们想象不同的东西。它似乎给我们的承诺是，如果它报告了一次错误，那么之后的一切都是<em>无用</em>的，所以我们应该扔掉玩具，关闭并重新打开所有内容，并从最后一个已知良好的状态重做。</p>

<p>尽管正如 Tomas 在下面发布的，它在其他一些领域也提供了相当弱的保证。请参阅他链接的那篇 lwn.net 文章。</p>

<blockquote>
<p>此外，POSIX 规范非常明确：成功的 fsync 意味着之前对该文件的所有写操作都已完成，句号，无论它们何时发出。</p>
</blockquote>

<p>我找不到任何东西这样对我说。请引用相关规范。</p>

<p>我参考的是 <a href="http://pubs.opengroup.org/onlinepubs/009695399/functions/fsync.html" rel="noopener noreferrer">http://pubs.opengroup.org/onlinepubs/009695399/functions/fsync.html</a>，其中指出</p>

<p>"fsync() 函数应请求将 fildes 命名的打开文件描述符的所有数据传输到与 fildes 描述的文件关联的存储设备。传输的性质是实现定义的。fsync() 函数应不返回，直到系统完成该操作或检测到错误。"</p>

<p>我的理解是，POSIX 并未指定检测到错误<em>之后</em>会发生什么。它没有说该错误必须是持久的，并且后续调用也必须报告该错误。它还说：</p>

<p>"如果 fsync() 函数失败，未完成的 I/O 操作不保证已完成。"</p>

<p>但这也没有让事情变得清晰多少，因为它可以被解读为一旦某些 I/O 操作报告了错误，即使在后续 fsync 返回成功后，这些操作也永远无法完成。</p>

<p>我不是在试图为内核似乎正在做的事情辩护。而是说，我们可能在其他平台上看到类似的行为，不管多么疯狂。我还没有研究 Linux 之外的情况。</p>

<hr>

<pre><code>发件人：Thomas Munro &lt;thomas(dot)munro(at)enterprisedb(dot)com&gt;
日期：2018-03-29 12:07:56
</code></pre>

<p>2018年3月29日（星期四）下午 6:58，Craig Ringer  写道：</p>

<blockquote>
<p>2018年3月28日 23:53，Tom Lane  写道：</p>

<blockquote>
<p>Craig Ringer  写道：</p>

<blockquote>
<p>简而言之：Pg 应在 fsync() 返回 EIO 时 PANIC。</p>
</blockquote>

<p>你肯定是在开玩笑。</p>
</blockquote>

<p>不。我非常认真。更糟的是，我们可能必须对 ENOSPC 也做同样的事情，以避免类似的丢失页面写问题。</p>
</blockquote>

<p>我发现了你与内核黑客 Jeff Layton 的讨论
<a href="https://lwn.net/Articles/718734/" rel="noopener noreferrer">https://lwn.net/Articles/718734/</a>，他在其中说："stackoverflow 文章似乎希望一种方案，在回写失败后页面保持脏状态以便我们可以再次尝试 fsync 它们。请注意，在 AFAIK 中，Linux 在发生硬回写失败后从未这样做过，因此程序绝对不应假设这种行为。"</p>

<p>上面那篇文章以几种不同的方式表达了相同的意思，即回写失败会给你留下既未成功写入磁盘也未标记为脏的页面。</p>

<p>如果我阅读各篇文章是正确的，在他的 errseq_t 修补程序落地之前，情况甚至更糟。该修补程序修复了由于共享 PG_error 用于某些文件系统的回写和读取错误而导致的完全未报告回写失败的情况，但它没有解决干净页面的问题。</p>

<p>是的，我明白你为什么要 PANIC。</p>

<blockquote>
<blockquote>
<p>此外，POSIX 规范非常明确：成功的 fsync 意味着之前对该文件的所有写操作都已完成，句号，无论它们何时发出。</p>
</blockquote>

<p>我找不到任何东西这样对我说。请引用相关规范。</p>

<p>我参考的是
<a href="http://pubs.opengroup.org/onlinepubs/009695399/functions/fsync.html" rel="noopener noreferrer">http://pubs.opengroup.org/onlinepubs/009695399/functions/fsync.html</a>，其中指出</p>

<p>"fsync() 函数应请求将 fildes 命名的打开文件描述符的所有数据传输到与 fildes 描述的文件关联的存储设备。传输的性质是实现定义的。fsync() 函数应不返回，直到系统完成该操作或检测到错误。"</p>

<p>我的理解是，POSIX 并未指定检测到错误<em>之后</em>会发生什么。它没有说该错误必须是持久的，并且后续调用也必须报告该错误。它还说：</p>
</blockquote>

<p>顺便说一下，我的理解和 Tom 的一样。它说的是"打开文件描述符的所有数据"，没有任何限定或在错误后的特殊处理。不是"部分"。</p>

<blockquote>
<p>我不是在试图为内核似乎正在做的事情辩护。而是说，我们可能在其他平台上看到类似的行为，不管多么疯狂。我还没有研究 Linux 之外的情况。</p>
</blockquote>

<p>我没有理由认为任何其他操作系统会以那种方式表现，除非有强有力的证据……这篇文章被公开承认为"一团糟"和"一个意外"，在文件系统峰会上。我其实没有资格评论，但从对 FreeBSD 的 vfs_bio.c 的粗略查看来看，我认为它做了你所希望的……请参阅注释"写入失败，重新脏"附近的代码。</p>

<hr>

<pre><code>发件人：Craig Ringer &lt;craig(at)2ndquadrant(dot)com&gt;
日期：2018-03-29 13:15:10
</code></pre>

<p>2018年3月29日 20:07，Thomas Munro 
写道：</p>

<blockquote>
<p>2018年3月29日（星期四）下午 6:58，Craig Ringer 
写道：</p>

<blockquote>
<p>2018年3月28日 23:53，Tom Lane  写道：</p>

<blockquote>
<p>Craig Ringer  写道：</p>

<blockquote>
<p>简而言之：Pg 应在 fsync() 返回 EIO 时 PANIC。</p>
</blockquote>

<p>你肯定是在开玩笑。</p>
</blockquote>

<p>不。我非常认真。更糟的是，我们可能必须对 ENOSPC
也做同样的事情，以避免类似的丢失页面写问题。</p>
</blockquote>

<p>我发现了你与内核黑客 Jeff Layton 的讨论
<a href="https://lwn.net/Articles/718734/" rel="noopener noreferrer">https://lwn.net/Articles/718734/</a>，他在其中说："stackoverflow
文章似乎希望一种方案，在回写失败后页面保持脏状态以便我们可以再次尝试 fsync 它们。请注意，在 AFAIK 中，Linux 在发生硬回写失败后从未这样做过，因此程序绝对不应假设这种行为。"</p>

<p>上面那篇文章以几种不同的方式表达了相同的意思，即回写失败会给你留下既未成功写入磁盘也未标记为脏的页面。</p>

<p>如果我阅读各篇文章是正确的，在他的 errseq_t 修补程序落地之前，情况甚至更糟。该修补程序修复了由于共享 PG_error 用于某些文件系统的回写和读取错误而导致的完全未报告回写失败的情况，但它没有解决干净页面的问题。</p>

<p>是的，我明白你为什么要 PANIC。</p>
</blockquote>

<p>在多个方面都是如此 ;)</p>

<blockquote>
<p>我不是在试图为内核似乎正在做的事情辩护。而是说，我们可能在其他平台上看到类似的行为，不管多么疯狂。我还没有研究 Linux 之外的情况。</p>
</blockquote>

<p>我没有理由认为任何其他操作系统会以那种方式表现，除非有强有力的证据……这篇文章被公开承认为"一团糟"和"一个意外"，在文件系统峰会上。我其实没有资格评论，但从对 FreeBSD 的 vfs_bio.c 的粗略查看来看，我认为它做了你所希望的……请参阅注释"写入失败，重新脏"附近的代码。</p>


<p>好的，这令人放心，但对我们绝大多数用户部署的平台没有帮助 :(</p>

<p>"如果在 Linux 上，PANIC"</p>

<p>嗯。</p>

<hr>

<pre><code>发件人：Catalin Iacob &lt;iacobcatalin(at)gmail(dot)com&gt;
日期：2018-03-29 16:20:00
</code></pre>

<p>2018年3月29日（星期四）下午 2:07，Thomas Munro  写道：</p>

<blockquote>
<p>我发现了你与内核黑客 Jeff Layton 的讨论
<a href="https://lwn.net/Articles/718734/" rel="noopener noreferrer">https://lwn.net/Articles/718734/</a>，他在其中说："stackoverflow
文章似乎希望一种方案，在回写失败后页面保持脏状态以便我们可以再次尝试 fsync 它们。请注意，在 AFAIK 中，Linux 在发生硬回写失败后从未这样做过，因此程序绝对不应假设这种行为。"</p>
</blockquote>

<p>在同一篇评论的稍下方，针对这个关于 Pg 的问题："那么，此时有哪些选择？假设是我们可以重复 fsync（正如你指出的那样，这不是事实），或者关闭数据库并从 WAL 执行恢复"，同一个 Jeff Layton 似乎同意 PANIC 是合适的响应：
"当你从 fsync 得到一个错误时，同步重放 WAL 似乎是最简单的方法。这些在大多数情况下很少发生，因此当这种情况发生时，退回到缓慢、同步的错误恢复模式可能是你想要做的事情。"。
紧接着，他确认 errseq_t 补丁只是为了更好地保证检测到，而不是更多：
"我正在做的主要事情是更好地保证当这种情况发生时你确实得到一个错误，而不是静默地损坏你的数据。可能发生这种情况的情况需要一些角落案例，但我认为我们需要确保它不会发生。"</p>

<p>Jeff 在合并 errseq_t 的拉取请求中的评论也值得一读：
<a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=088737f44bbf6378745f5b57b035e57ee3dc4750" rel="noopener noreferrer">https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=088737f44bbf6378745f5b57b035e57ee3dc4750</a></p>

<blockquote>
<p>上面那篇文章以几种不同的方式表达了相同的意思，即回写失败会给你留下既未成功写入磁盘也未标记为脏的页面。</p>

<p>如果我阅读各篇文章是正确的，在他的 errseq_t 修补程序落地之前，情况甚至更糟。该修补程序修复了由于共享 PG_error 用于某些文件系统的回写和读取错误而导致的完全未报告回写失败的情况，但它没有解决干净页面的问题。</p>
</blockquote>

<p>确实，这完全是我独立阅读你的句子之前形成的观点。顺便说一句，errseq_t 补丁在 v4.13 中落地，所以非常新。</p>

<blockquote>
<p>是的，我明白你为什么要 PANIC。</p>
</blockquote>

<p>确实。即使这样做也对 v4.13 之前的所有内核版本留下了疑问，目前这些版本几乎都是所有正在运行的版本，甚至无法可靠地检测到这一点。这很混乱。</p>

<hr>

<pre><code>发件人：Thomas Munro &lt;thomas(dot)munro(at)enterprisedb(dot)com&gt;
日期：2018-03-29 21:18:14
</code></pre>

<p>2018年3月30日（星期五）凌晨 5:20，Catalin Iacob  写道：</p>

<blockquote>
<p>Jeff 在合并 errseq_t 的拉取请求中的评论也值得一读：
<a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=088737f44bbf6378745f5b57b035e57ee3dc4750" rel="noopener noreferrer">https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=088737f44bbf6378745f5b57b035e57ee3dc4750</a></p>
</blockquote>

<p>哇。看起来可能存在一个单独的问题，即每个文件系统何时采用了这个新基础设施？</p>

<blockquote>
<blockquote>
<p>是的，我明白你为什么要 PANIC。</p>
</blockquote>

<p>确实。即使这样做也对 v4.13 之前的所有内核版本留下了疑问，目前这些版本几乎都是所有正在运行的版本，甚至无法可靠地检测到这一点。这很混乱。</p>
</blockquote>

<p>预 errseq_t 问题超出了我们的控制范围。我们无法在用户空间做任何事情（除非可能放弃 OS 缓冲 IO，这是一个大项目）。我们只需要意识到这个问题存在于某些内核版本中，并感谢 Layton 修复了它。</p>

<p>丢弃脏标志问题是我们可以并且我认为应该做些什么的，无论我们对该设计选择怎么想。正如 Andrew Gierth 在关于此问题的邮件列表外聊天中向我指出的，当你达到这个状态时，PostgreSQL 的缓冲区和内核的缓冲区都是干净的，可能随时被重用于另一个块，因此你的数据可能已经从已知的宇宙中消失——我们甚至无法普遍重写我们的缓冲区。恢复是唯一的选择。</p>

<p>感谢 Craig 追踪到这一点，并支持他的提案，仅限 Linux。</p>

<hr>

<pre><code>发件人：Anthony Iliopoulos &lt;ailiop(at)altatus(dot)com&gt;
日期：2018-03-31 13:24:28
</code></pre>

<p>2018年3月30日（星期五）上午 10:18:14 +1300，Thomas Munro 写道：</p>

<blockquote>
<blockquote>
<blockquote>
<p>是的，我明白你为什么要 PANIC。</p>
</blockquote>

<p>确实。即使这样做也对 v4.13 之前的所有内核版本留下了疑问，目前这些版本几乎都是所有正在运行的版本，甚至无法可靠地检测到这一点。这很混乱。</p>
</blockquote>
</blockquote>

<p>可能仍然有办法从用户空间可靠地在旧内核版本上检测到这一点，但无论怎样都会很混乱。对于 EIO 错误，内核不会恢复脏页面标志，但它会翻转失败页面上的错误标志。可以 mmap() 相关文件，获取 PFN（通过 /proc/pid/pagemap）并枚举那些通过 /proc/kpageflags 切换错误标志的页面。这至少可以作为检测机制，但也可以进一步使用此信息逻辑上将失败的 IO 页面映射回原始文件偏移量，并可能仅重试覆盖失败页面的文件范围的 IO。只是一个想法，未经测试。</p>

<hr>

<pre><code>发件人：Craig Ringer &lt;craig(at)2ndquadrant(dot)com&gt;
日期：2018-03-31 16:13:09
</code></pre>

<p>2018年3月31日 21:24，Anthony Iliopoulos  写道：</p>

<blockquote>
<p>2018年3月30日（星期五）上午 10:18:14 +1300，Thomas Munro 写道：</p>

<blockquote>
<blockquote>
<blockquote>
<p>是的，我明白你为什么要 PANIC。</p>
</blockquote>

<p>确实。即使这样做也对 v4.13 之前的所有内核版本留下了疑问，目前这些版本几乎都是所有正在运行的版本，甚至无法可靠地检测到这一点。这很混乱。</p>
</blockquote>
</blockquote>

<p>可能仍然有办法从用户空间可靠地在旧内核版本上检测到这一点，但无论怎样都会很混乱。对于 EIO 错误，内核不会恢复脏页面标志，但它会翻转失败页面上的错误标志。可以 mmap() 相关文件，获取 PFN（通过 /proc/pid/pagemap）并枚举那些通过 /proc/kpageflags 切换错误标志的页面。这至少可以作为检测机制，但也可以进一步使用此信息逻辑上将失败的 IO 页面映射回原始文件偏移量，并可能仅重试覆盖失败页面的文件范围的 IO。只是一个想法，未经测试。</p>
</blockquote>

<p>这听起来像是大量的复杂性，而且内核到内核的行为不确定性，收益微乎其微。</p>

<p>我曾探索基于我们假设我们知道与 fsync() 失败的文件描述符关联的 relfilenode 的想法，对单个 relfilenode 进行选择性恢复。我们可以仅重放该关联上的 WAL。但它失败了同样的测试：它太复杂，不适合一个首先不应该发生的特殊情况，所以它可能有错误，或者随着时间的推移而产生错误。</p>

<p>记住，如果你在 ext4 上使用 errors=remount-ro，你会比 PANIC 更彻底地关闭。所以我们应该在这里使用大锤。</p>

<p>我本周会发送一个补丁。</p>

<hr>

<pre><code>发件人：Tom Lane &lt;tgl(at)sss(dot)pgh(dot)pa(dot)us&gt;
日期：2018-03-31 16:38:12
</code></pre>

<p>Craig Ringer  写道：</p>

<blockquote>
<p>所以我们应该在这里使用大锤。</p>
</blockquote>

<p>并大声公开地抱怨这种内核行为是多么的残缺。如果我们制造足够的噪音，也许它会被修复。</p>

<hr>

<pre><code>发件人：Michael Paquier &lt;michael(at)paquier(dot)xyz&gt;
日期：2018-04-01 00:20:38
</code></pre>

<p>2018年3月31日（星期六）下午 12:38:12 EDT，Tom Lane 写道：</p>

<blockquote>
<p>Craig Ringer  写道：</p>

<blockquote>
<p>所以我们应该在这里使用大锤。</p>
</blockquote>

<p>并大声公开地抱怨这种内核行为是多么的残缺。如果我们制造足够的噪音，也许它会被修复。</p>
</blockquote>

<p>那不会修复已经发布的任何东西，因此根据收集到的信息，无论如何都必须做些什么。这个主题的讨论实际上正在广泛传播。</p>

<p>在底层处理事情看起来对后端来说是一个更好的计划。像 pg_basebackup 和 pg_dump 这样的工具也会对创建的数据发出 fsync，我们应该对它们做同样的事情，在 file_utils.c 中进行一些 exit() 调用。目前，失败被记录到 stderr，但并未被视为致命错误。</p>

<hr>

<pre><code>发件人：Anthony Iliopoulos &lt;ailiop(at)altatus(dot)com&gt;
日期：2018-04-01 00:58:22
</code></pre>

<p>2018年4月1日（星期日）凌晨 00:13:09 +0800，Craig Ringer 写道：</p>

<blockquote>
<p>2018年3月31日 21:24，Anthony Iliopoulos  写道：</p>

<blockquote>
<p>2018年3月30日（星期五）上午 10:18:14 +1300，Thomas Munro 写道：</p>

<blockquote>
<blockquote>
<blockquote>
<p>是的，我明白你为什么要 PANIC。</p>
</blockquote>

<p>确实。即使这样做也对 v4.13 之前的所有内核版本留下了疑问，目前这些版本几乎都是所有正在运行的版本，甚至无法可靠地检测到这一点。这很混乱。</p>
</blockquote>
</blockquote>

<p>可能仍然有办法从用户空间可靠地在旧内核版本上检测到这一点，但无论怎样都会很混乱。对于 EIO 错误，内核不会恢复脏页面标志，但它会翻转失败页面上的错误标志。可以 mmap() 相关文件，获取 PFN（通过 /proc/pid/pagemap）并枚举那些通过 /proc/kpageflags 切换错误标志的页面。这至少可以作为检测机制，但也可以进一步使用此信息逻辑上将失败的 IO 页面映射回原始文件偏移量，并可能仅重试覆盖失败页面的文件范围的 IO。只是一个想法，未经测试。</p>
</blockquote>

<p>这听起来像是大量的复杂性，而且内核到内核的行为不确定性，收益微乎其微。</p>
</blockquote>

<p>那些接口自内核 2.6 时代就存在，并且相当稳定，但我只是在回应你最初帖子中关于有一种方法可以找出哪些页面失败的评论。我假设确实没有任何益处，特别是因为这些错误通常不是瞬态的（通常来自硬介质故障），尽管文件系统理论上可以通过分配不同的逻辑块来掩盖错误，但我不知道当前有任何实现这样做。</p>

<blockquote>
<p>我曾探索基于我们假设我们知道与 fsync() 失败的文件描述符关联的 relfilenode 的想法，对单个 relfilenode 进行选择性恢复。我们可以仅重放该关联上的 WAL。但它失败了同样的测试：它太复杂，不适合一个首先不应该发生的特殊情况，所以它可能有错误，或者随着时间的推移而产生错误。</p>
</blockquote>

<p>完全同意，这些情况应该足够罕见，以至于一个复杂且可能无法维护的解决方案并不真正值得。</p>

<blockquote>
<p>记住，如果你在 ext4 上使用 errors=remount-ro，你会比 PANIC 更彻底地关闭。所以我们应该在这里使用大锤。</p>
</blockquote>

<p>我不完全确定你的意思，Pg 真的将 write() 错误视为致命错误吗？此外，ext4 使用此选项检测的错误类型是在超级块级别并管理元数据，而不是实际的数据写入（回想一下那些是缓冲的，在 write() 时实际上不需要实际的设备 IO）。</p>

<hr>

<pre><code>发件人：Anthony Iliopoulos &lt;ailiop(at)altatus(dot)com&gt;
日期：2018-04-01 01:14:46
</code></pre>

<p>2018年3月31日（星期六）下午 12:38:12 EDT，Tom Lane 写道：</p>

<blockquote>
<p>Craig Ringer  写道：</p>

<blockquote>
<p>所以我们应该在这里使用大锤。</p>
</blockquote>

<p>并大声公开地抱怨这种内核行为是多么的残缺。如果我们制造足够的噪音，也许它会被修复。</p>
</blockquote>

<p>它不太可能被修复（除了已经在报告级别通过 manpage 补丁和 errseq_t 修复所做的工作之外）。问题是内核需要以某种方式处理硬 IO 错误，由于这些错误通常会持续存在，重新脏化页面并不能真正解决问题（除非某些文件系统将请求重新映射到不同的块，假设设备仍然存活）。保留无法写出的脏页面本质上是内存泄漏，因为即使在应用程序退出后，这些页面也会保留下来。</p>

<hr>

<pre><code>发件人：Thomas Munro &lt;thomas(dot)munro(at)enterprisedb(dot)com&gt;
日期：2018-04-01 18:24:51
</code></pre>

<p>2018年3月30日（星期五）上午 10:18，Thomas Munro
 写道：</p>

<blockquote>
<p>... 仅限 Linux。</p>
</blockquote>

<p>看来我过于乐观了。我只看了 FreeBSD，它保留页面并将其脏化以便我们可以重试，但其他 BSD 显然没有（FreeBSD 在 1999 年改变了这一点）。根据我下面的来源，我们有：</p>

<pre><code>Linux, OpenBSD, NetBSD: 重试 fsync() 后 EIO 说谎
FreeBSD, Illumos: 重试 fsync() 后 EIO 说实话
</code></pre>

<p>也许我对那些内核例程的一次性评估是错误的，有人会纠正我，但我开始认为你最好在所有系统上假设最坏的情况。也许是一个 GUC 默认 panic，这样那些罕见的 OS 用户可以关闭它？即使那样，我也不确定故障模式会有多好，或者是否值得有两种行为。想法？</p>

<p><a href="http://mail-index.netbsd.org/netbsd-users/2018/03/30/msg020576.html" rel="noopener noreferrer">http://mail-index.netbsd.org/netbsd-users/2018/03/30/msg020576.html</a>
<a href="https://github.com/NetBSD/src/blob/trunk/sys/kern/vfs_bio.c#L1059" rel="noopener noreferrer">https://github.com/NetBSD/src/blob/trunk/sys/kern/vfs_bio.c#L1059</a>
<a href="https://github.com/openbsd/src/blob/master/sys/kern/vfs_bio.c#L867" rel="noopener noreferrer">https://github.com/openbsd/src/blob/master/sys/kern/vfs_bio.c#L867</a>
<a href="https://github.com/freebsd/freebsd/blob/master/sys/kern/vfs_bio.c#L2631" rel="noopener noreferrer">https://github.com/freebsd/freebsd/blob/master/sys/kern/vfs_bio.c#L2631</a>
<a href="https://github.com/freebsd/freebsd/commit/e4e8fec98ae986357cdc208b04557dba55a59266" rel="noopener noreferrer">https://github.com/freebsd/freebsd/commit/e4e8fec98ae986357cdc208b04557dba55a59266</a>
<a href="https://github.com/illumos/illumos-gate/blob/master/usr/src/uts/common/os/bio.c#L441" rel="noopener noreferrer">https://github.com/illumos/illumos-gate/blob/master/usr/src/uts/common/os/bio.c#L441</a></p>

<hr>

<pre><code>发件人：Craig Ringer &lt;craig(at)2ndquadrant(dot)com&gt;
日期：2018-04-02 15:03:42
</code></pre>

<p>2018年4月2日 02:24，Thomas Munro 
写道：</p>

<blockquote>
<p>也许我对那些内核例程的一次性评估是错误的，有人会纠正我，但我开始认为你最好在所有系统上假设最坏的情况。也许是一个 GUC 默认 panic，这样那些罕见的 OS 用户可以关闭它？即使那样，我也不确定故障模式会有多好，或者是否值得有两种行为。想法？</p>
</blockquote>

<p>我看到无条件地对 EIO PANIC 几乎没有好处。它不应该发生，如果发生了，我们希望非常保守并采用数据保护方法。</p>

<p>我更担心在 ENOSPC 上这样做。根据我在测试用例 + 内核代码阅读中发现的情况，这似乎是必要的。我真的不想对可能瞬时的 ENOSPC 不必要地 PANIC 整个服务器。</p>

<p>顺便说一句，2ndQ 的支持团队目前正在处理两个独立的问题，其中 ENOSPC 导致数据库损坏，尽管这两个问题都不涉及丢失页面写入的日志。我计划明天花些时间编写一个针对 Pg 的 ENOSPC 处理的测试工具，并验证我在原始 StackOverflow 帖子中链接的测试用例中的 ENOSPC 处理。</p>

<p>如果这只与 EIO 有关，那么我认为除了无条件 PANIC 之外没有意义做其他任何事情。</p>

<p>如果是 ENOSPC 的问题，我们应该在可能的情况下努力以更好的方式失败。</p>

<hr>

<pre><code>发件人：Andres Freund &lt;andres(at)anarazel(dot)de&gt;
日期：2018-04-02 18:13:46
</code></pre>

<p>你好，</p>

<p>2018-04-01 03:14:46 +0200，Anthony Iliopoulos 写道：</p>

<blockquote>
<p>2018年3月31日（星期六）下午 12:38:12 EDT，Tom Lane 写道：</p>

<blockquote>
<p>Craig Ringer  写道：</p>

<blockquote>
<p>所以我们应该在这里使用大锤。</p>
</blockquote>

<p>并大声公开地抱怨这种内核行为是多么的残缺。如果我们制造足够的噪音，也许它会被修复。</p>
</blockquote>

<p>它不太可能被修复（除了已经在报告级别通过 manpage 补丁和 errseq_t 修复所做的工作之外）。问题是内核需要以某种方式处理硬 IO 错误，由于这些错误通常会持续存在，重新脏化页面并不能真正解决问题（除非某些文件系统将请求重新映射到不同的块，假设设备仍然存活）。</p>
</blockquote>

<p>丢弃脏页面<em>并且</em>持续保存错误似乎更加合理。然后提供一个 fcntl（或其他）扩展，可以在少数想要优雅处理该情况的情况下清除错误状态。</p>

<blockquote>
<p>保留无法写出的脏页面本质上是内存泄漏，因为即使在应用程序退出后，这些页面也会保留下来。</p>
</blockquote>

<p>为什么在持续错误的情况下需要保留脏页面？我认为在这种情况下缺乏自动恢复并不是任何人抱怨的内容。而是错误消失了，没有合理的方法来区分这种错误和某些潜在的瞬时错误。</p>

<hr>

<pre><code>发件人：Anthony Iliopoulos &lt;ailiop(at)altatus(dot)com&gt;
日期：2018-04-02 18:53:20
</code></pre>

<p>2018年4月2日（星期一）上午 11:13:46 -0700，Andres Freund 写道：</p>

<blockquote>
<p>你好，</p>

<p>2018-04-01 03:14:46 +0200，Anthony Iliopoulos 写道：</p>

<blockquote>
<p>2018年3月31日（星期六）下午 12:38:12 EDT，Tom Lane 写道：</p>

<blockquote>
<p>Craig Ringer  写道：</p>

<blockquote>
<p>所以我们应该在这里使用大锤。</p>
</blockquote>

<p>并大声公开地抱怨这种内核行为是多么的残缺。如果我们制造足够的噪音，也许它会被修复。</p>
</blockquote>

<p>它不太可能被修复（除了已经在报告级别通过 manpage 补丁和 errseq_t 修复所做的工作之外）。问题是内核需要以某种方式处理硬 IO 错误，由于这些错误通常会持续存在，重新脏化页面并不能真正解决问题（除非某些文件系统将请求重新映射到不同的块，假设设备仍然存活）。</p>
</blockquote>

<p>丢弃脏页面<em>并且</em>持续保存错误似乎更加合理。然后提供一个 fcntl（或其他）扩展，可以在少数想要优雅处理该情况的情况下清除错误状态。</p>
</blockquote>

<p>鉴于无法写出的脏页面实际上被丢弃，fsync() 的语义（在 4.13 修复之后）本质上是正确的：第一个调用表明确实发生了回写错误，而后续调用没有理由表明错误（假设没有发生其他错误）。</p>

<p>因此，错误报告与预期的语义一致（可惜未被正确记录）。对 fsync() 的重复调用并不意味着内核将重试先前失败的页面回写，因此应用程序需要意识到这一点。在 fsync() 级别持续保存错误本质上意味着将应用程序策略移动到内核中。</p>

<hr>

<pre><code>发件人：Andres Freund &lt;andres(at)anarazel(dot)de&gt;
日期：2018-04-02 19:32:45
</code></pre>

<p>2018-04-02 20:53:20 +0200，Anthony Iliopoulos 写道：</p>

<blockquote>
<p>2018年4月2日（星期一）上午 11:13:46 -0700，Andres Freund 写道：</p>

<blockquote>
<p>丢弃脏页面<em>并且</em>持续保存错误似乎更加合理。然后提供一个 fcntl（或其他）扩展，可以在少数想要优雅处理该情况的情况下清除错误状态。</p>
</blockquote>

<p>鉴于无法写出的脏页面实际上被丢弃，fsync() 的语义（在 4.13 修复之后）本质上是正确的：第一个调用表明确实发生了回写错误，而后续调用没有理由表明错误（假设没有发生其他错误）。</p>
</blockquote>

<p>呸^2。</p>

<p>"没有理由" - 除了你完全无法知道数据处于什么状态。并且你的应用程序需要显式处理此类失败。而且一个 FD 可能在应用程序的许多不同部分中使用，在一个部分中的 fsync 可能是可接受的失败，在另一个部分中则不是。要求显式操作来确认"我们因未知原因丢弃了你的数据"似乎是完全合理的。</p>

<blockquote>
<p>因此，错误报告与预期的语义一致（可惜未被正确记录）。对 fsync() 的重复调用并不意味着内核将重试先前失败的页面回写，因此应用程序需要意识到这一点。</p>
</blockquote>

<p>这不是我所建议的。</p>

<blockquote>
<p>在 fsync() 级别持续保存错误本质上意味着将应用程序策略移动到内核中。</p>
</blockquote>

<p>呸。</p>

<hr>

<pre><code>发件人：Anthony Iliopoulos &lt;ailiop(at)altatus(dot)com&gt;
日期：2018-04-02 20:38:06
</code></pre>

<p>2018年4月2日（星期一）下午 12:32:45 -0700，Andres Freund 写道：</p>

<blockquote>
<p>2018-04-02 20:53:20 +0200，Anthony Iliopoulos 写道：</p>

<blockquote>
<p>2018年4月2日（星期一）上午 11:13:46 -0700，Andres Freund 写道：</p>

<blockquote>
<p>丢弃脏页面<em>并且</em>持续保存错误似乎更加合理。然后提供一个 fcntl（或其他）扩展，可以在少数想要优雅处理该情况的情况下清除错误状态。</p>
</blockquote>

<p>鉴于无法写出的脏页面实际上被丢弃，fsync() 的语义（在 4.13 修复之后）本质上是正确的：第一个调用表明确实发生了回写错误，而后续调用没有理由表明错误（假设没有发生其他错误）。</p>
</blockquote>

<p>呸^2。</p>

<p>"没有理由" - 除了你完全无法知道数据处于什么状态。并且你的应用程序需要显式处理此类失败。而且一个 FD 可能在应用程序的许多不同部分中使用，在一个部分中的 fsync 可能是可接受的失败，在另一个部分中则不是。要求显式操作来确认"我们因未知原因丢弃了你的数据"似乎是完全合理的。</p>
</blockquote>

<p>只要 fsync() 在第一次调用时指示错误，应用程序就完全知道从此时到上次调用 fsync() 之间数据已丢失。进一步持续保存此错误不会改变这一点或增加任何新信息 - 相反，它会增加混乱，因为对其他页面的后续 write() 和 fsync() 可以成功，但会被报告为失败。</p>

<p>应用程序将需要处理第一个错误，而不管 fsync() 的后续返回码如何。概念上，每个 fsync() 调用都划分了一个纪元，报告该纪元的潜在错误，因此调用者需要为该特定纪元负责。</p>

<p>不受 fsync() 潜在结果影响的调用者并且不对错误做出反应的调用者，首先就没有调用它的理由（并且因此从可能确实关心的后续调用者那里掩盖了失败）。</p>

<hr>

<pre><code>发件人：Stephen Frost &lt;sfrost(at)snowman(dot)net&gt;
日期：2018-04-02 20:58:08
</code></pre>

<p>问候，</p>

<p>Anthony Iliopoulos (ailiop(at)altatus(dot)com) 写道：</p>

<blockquote>
<p>2018年4月2日（星期一）下午 12:32:45 -0700，Andres Freund 写道：</p>

<blockquote>
<p>2018-04-02 20:53:20 +0200，Anthony Iliopoulos 写道：</p>

<blockquote>
<p>2018年4月2日（星期一）上午 11:13:46 -0700，Andres Freund 写道：</p>

<blockquote>
<p>丢弃脏页面<em>并且</em>持续保存错误似乎更加合理。然后提供一个 fcntl（或其他）扩展，可以在少数想要优雅处理该情况的情况下清除错误状态。</p>
</blockquote>

<p>鉴于无法写出的脏页面实际上被丢弃，fsync() 的语义（在 4.13 修复之后）本质上是正确的：第一个调用表明确实发生了回写错误，而后续调用没有理由表明错误（假设没有发生其他错误）。</p>
</blockquote>

<p>呸^2。</p>

<p>"没有理由" - 除了你完全无法知道数据处于什么状态。并且你的应用程序需要显式处理此类失败。而且一个 FD 可能在应用程序的许多不同部分中使用，在一个部分中的 fsync 可能是可接受的失败，在另一个部分中则不是。要求显式操作来确认"我们因未知原因丢弃了你的数据"似乎是完全合理的。</p>
</blockquote>

<p>只要 fsync() 在第一次调用时指示错误，应用程序就完全知道从此时到上次调用 fsync() 之间数据已丢失。进一步持续保存此错误不会改变这一点或增加任何新信息 - 相反，它会增加混乱，因为对其他页面的后续 write() 和 fsync() 可以成功，但会被报告为失败。</p>
</blockquote>

<p>fsync() 不反映给定页面的状态，然而它反映的是调用它的文件描述符的状态，并因此反映文件的状态。认为 fsync() 实际上仅负责自上次 fsync() 调用以来对文件所做的更改的观点是完全愚蠢的。如果我们能够向 fsync() 传递一个页面列表或数据范围以验证它们已落盘，那么情况可能就不同了，但我们不能，我们所能做的就是请求"请将与此文件描述符关联的所有脏页面刷新到磁盘，该描述符代表我们打开的此文件，并在成功时告诉我们。"</p>

<p>给我们一个方式来询问"这些特定页面是否已写入持久存储？"，我们肯定会乐意使用它，并且会反复尝试刷新由于某些瞬时错误而未同步到磁盘的页面，并跟踪这些情况并确保我们不会错误地假设它们已传输到持久存储。</p>

<blockquote>
<p>应用程序将需要处理第一个错误，而不管 fsync() 的后续返回码如何。概念上，每个 fsync() 调用都划分了一个纪元，报告该纪元的潜在错误，因此调用者需要为该特定纪元负责。</p>
</blockquote>

<p>我们确实处理那个错误 - 意识到它失败了，然后稍后<em>重试</em> fsync()，这时我们得到"一切正常！你打开的这个文件描述符的所有内容都已同步！"并愉快地期望这是事实，而实际上，这是一个不幸的谎言，并且仍然存在与该文件描述符关联的页面，这些页面实际上很脏且未同步到磁盘。</p>

<p>考虑两个独立的程序，第一个程序写入一个文件，然后调用第二个程序，其工作是出去并对这些文件进行 fsync()，可能与第一个程序异步。第二个程序是否应该去写入第一个程序写入的每个页面，以确保设置了所有脏位，以便如果所有脏页面都已写入，fsync() 实际上会返回？</p>

<blockquote>
<p>不受 fsync() 潜在结果影响的调用者并且不对错误做出反应的调用者，首先就没有调用它的理由（并且因此从可能确实关心的后续调用者那里掩盖了失败）。</p>
</blockquote>

<p>对 fsync() 调用的错误做出反应可能根据其文档化方式和在其他 OS 上的实际实现意味着"运行另一个 fsync() 以查看错误是否已自行解决"。要求那意味着"你必须去脏化你先前脏化的所有页面，以实际让后续的 fsync() 做任何事情"确实不合理 - 一个给定的程序可能不知道先前写入了什么，或者也不需要知道，期望 fsync() 调用将刷新任何脏页面，如其文档</p><p><em>由 mimo-v2.5 模型翻译，花费 172366 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/fsyncgate/</link>
      <guid isPermaLink="false">https://danluu.com/fsyncgate/</guid>
      <pubDate>Wed, 28 Mar 2018 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>计算机延迟：1977-2017</title>
      <description>&lt;p&gt;我一直有种挥之不去的感觉：现在用的电脑似乎比小时候用的要慢。通常我不太相信这种感觉，因为实证研究已表明人类感知并不可靠。于是我随身携带一台高速相机，测量了过去几个月遇到的设备的响应延迟。结果如下：&lt;/p&gt;

&lt;p&gt;&lt;style&gt;table {border-collapse:collapse;margin:0px auto;}table,th,td {border: 1px solid bla</description>
      <content:encoded><![CDATA[<p>我一直有种挥之不去的感觉：现在用的电脑似乎比小时候用的要慢。通常我不太相信这种感觉，因为实证研究已表明人类感知并不可靠。于是我随身携带一台高速相机，测量了过去几个月遇到的设备的响应延迟。结果如下：</p>

<p>
</p><table>
<tbody><tr>
<th>计算机</th><th>延迟<br>(毫秒)</th><th>年份</th><th>时钟频率</th><th>晶体管数量</th></tr>
<tr>
<td><a href="https://en.wikipedia.org/wiki/Apple_IIe" rel="noopener noreferrer">苹果 2e</a></td><td>30</td><td>1983</td><td>1 MHz</td><td>3.5k</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B004GYBZBE/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B004GYBZBE&amp;linkId=123a7b9e283914f633fbdc9b001f255b" rel="noopener noreferrer">德州仪器 ti 99/4a</a></td><td>40</td><td>1981</td><td>3 MHz</td><td>8k</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B01FJLAITC/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01FJLAITC&amp;linkId=8ffc49fdd6c38da2429e5f3ab5527314" rel="noopener noreferrer">定制 Haswell-E <i>165Hz</i></a></td><td>50</td><td>2014</td><td>3.5 GHz</td><td>2G</td></tr>
<tr>
<td><a href="https://en.wikipedia.org/wiki/Commodore_PET" rel="noopener noreferrer">Commodore PET 4016</a></td><td>60</td><td>1977</td><td>1 MHz</td><td>3.5k</td></tr>
<tr>
<td><a href="https://en.wikipedia.org/wiki/SGI_Indy" rel="noopener noreferrer">SGI Indy</a></td><td>60</td><td>1993</td><td>0.1 GHz</td><td>1.2M</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B01FJLAITC/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01FJLAITC&amp;linkId=8ffc49fdd6c38da2429e5f3ab5527314" rel="noopener noreferrer">定制 Haswell-E <i>120Hz</i></a></td><td>60</td><td>2014</td><td>3.5 GHz</td><td>2G</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B01LWNVFIM/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01LWNVFIM&amp;linkId=d4f3e06001021e2bfc95eb2c8c3a9805" rel="noopener noreferrer">ThinkPad 13 <b>ChromeOS</b></a></td><td>70</td><td>2017</td><td>2.3 GHz</td><td>1G</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B007O35FI8/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B007O35FI8&amp;linkId=dc8b3670da16e98e718b157661037e4b" rel="noopener noreferrer">iMac G4 <b>OS 9</b></a></td><td>70</td><td>2002</td><td>0.8 GHz</td><td>11M</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B01FJLAITC/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01FJLAITC&amp;linkId=8ffc49fdd6c38da2429e5f3ab5527314" rel="noopener noreferrer">定制 Haswell-E <i>60Hz</i></a></td><td>80</td><td>2014</td><td>3.5 GHz</td><td>2G</td></tr>
<tr>
<td><a href="https://en.wikipedia.org/wiki/Macintosh_Color_Classic" rel="noopener noreferrer">Macintosh Color Classic</a></td><td>90</td><td>1993</td><td>16 MHz</td><td>273k</td></tr>
<tr>
<td><a href="http://www.powerspec.com/systems/system_specs.phtml?selection=G405" rel="noopener noreferrer">PowerSpec G405 <b>Linux</b> <i>60Hz</i></a></td><td>90</td><td>2017</td><td>4.2 GHz</td><td>2G</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B0096VDM8G/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B0096VDM8G&amp;linkId=d134e4e812fd9e68ddb6191e03f78a72" rel="noopener noreferrer">MacBook Pro 2014</a></td><td>100</td><td>2014</td><td>2.6 GHz</td><td>700M</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B01LWNVFIM/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01LWNVFIM&amp;linkId=d4f3e06001021e2bfc95eb2c8c3a9805" rel="noopener noreferrer">ThinkPad 13 <b>Linux chroot</b></a></td><td>100</td><td>2017</td><td>2.3 GHz</td><td>1G</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B072BDGLBC/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B072BDGLBC&amp;linkId=b316192e2edacb439be22ef01a6bb779" rel="noopener noreferrer">联想 X1 Carbon 4G <b>Linux</b></a></td><td>110</td><td>2016</td><td>2.6 GHz</td><td>1G</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B007O35FI8/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B007O35FI8&amp;linkId=dc8b3670da16e98e718b157661037e4b" rel="noopener noreferrer">iMac G4 <b>OS X</b></a></td><td>120</td><td>2002</td><td>0.8 GHz</td><td>11M</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B01FJLAITC/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01FJLAITC&amp;linkId=8ffc49fdd6c38da2429e5f3ab5527314" rel="noopener noreferrer">定制 Haswell-E <i>24Hz</i></a></td><td>140</td><td>2014</td><td>3.5 GHz</td><td>2G</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B072BDGLBC/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B072BDGLBC&amp;linkId=b316192e2edacb439be22ef01a6bb779" rel="noopener noreferrer">联想 X1 Carbon 4G <b>Windows</b></a></td><td>150</td><td>2016</td><td>2.6 GHz</td><td>1G</td></tr>
<tr>
<td><a href="https://en.wikipedia.org/wiki/NeXTcube" rel="noopener noreferrer">NeXTcube</a></td><td>150</td><td>1988</td><td>25 MHz</td><td>1.2M</td></tr>
<tr>
<td><a href="http://www.powerspec.com/systems/system_specs.phtml?selection=G405" rel="noopener noreferrer">PowerSpec G405 <b>Linux</b></a></td><td>170</td><td>2017</td><td>4.2 GHz</td><td>2G</td></tr>
<tr>
<td>环球数据包往返</td><td>190</td><td></td><td></td><td></td></tr>
<tr>
<td><a href="http://www.powerspec.com/systems/system_specs.phtml?selection=G405" rel="noopener noreferrer">PowerSpec G405 <b>Windows</b></a></td><td>200</td><td>2017</td><td>4.2 GHz</td><td>2G</td></tr>
<tr>
<td><a href="https://en.wikipedia.org/wiki/Symbolics" rel="noopener noreferrer">Symbolics 3620</a></td><td>300</td><td>1986</td><td>5 MHz</td><td>390k</td></tr>
</tbody></table><p></p>

<p>这些是测试按键到终端显示字符之间的延迟（更多细节见附录）。结果按从快到慢排序。在延迟列中，背景颜色从绿色过渡到黄色再到红色最后到黑色，设备越慢，背景越深。没有设备是绿色的。当同一台机器测试多个操作系统时，操作系统名称以<b>粗体</b>显示。当同一台机器测试多个刷新率时，刷新率以<i>斜体</i>显示。</p>

<p>在年份列中，设备越老旧，背景颜色越深且紫色越浓。如果旧设备更慢，我们会在图表中看到年份列随着向下阅读而变深。</p>

<p>接下来的两列显示处理器的时钟频率和晶体管数量。数字越小颜色越深、蓝色越浓。如前所述，如果较慢的时钟频率和较小的芯片与更长的延迟相关，这些列会在表格向下阅读时变深，但实际情况似乎恰恰相反。</p>

<p>作为参考，数据包通过光纤从纽约到东京再到伦敦返回纽约的环球往返延迟也被插入表中。</p>

<p>从整体结果看，最快的机器都是古老的。新机器的表现参差不齐。具有异常高刷新率显示器的花式游戏设备几乎能与70年代末80年代初的机器相媲美，但“普通”的现代电脑无法与三四十年前的机器竞争。</p>

<p>我们也可以看看移动设备。这种情况下，我们关注浏览器中的滚动延迟：</p>

<table>
<tbody><tr>
<th>设备</th><th>延迟<br>(毫秒)</th><th>年份</th></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B072V4HK9F/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B072V4HK9F&amp;linkId=5679f47f9a699d00e721eff9592bed11" rel="noopener noreferrer">iPad Pro 10.5" + Apple Pencil</a></td><td>30</td><td>2017</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B072V4HK9F/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B072V4HK9F&amp;linkId=5679f47f9a699d00e721eff9592bed11" rel="noopener noreferrer">iPad Pro 10.5"</a></td><td>70</td><td>2017</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B006FMDVDK/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B006FMDVDK&amp;linkId=9ad1ee1c78ed24a00d7aeaa7acba466b" rel="noopener noreferrer">iPhone 4S</a></td><td>70</td><td>2011</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B00YD547Q6/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B00YD547Q6&amp;linkId=8760bcadabac2a1a34059f5695523d18" rel="noopener noreferrer">iPhone 6S</a></td><td>70</td><td>2015</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B008VUNRZQ/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B008VUNRZQ&amp;linkId=f9f973947891d7eb01a251146c8a17ed" rel="noopener noreferrer">iPhone 3GS</a></td><td>70</td><td>2009</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B075QN8NDJ/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B075QN8NDJ&amp;linkId=17f3a63a6d672e453c1bb28c26f1e680" rel="noopener noreferrer">iPhone X</a></td><td>80</td><td>2017</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B075QJSQLT/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B075QJSQLT&amp;linkId=0e9df41aaed6dc80988793dcbc08a175" rel="noopener noreferrer">iPhone 8</a></td><td>80</td><td>2017</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B0743HK992/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B0743HK992&amp;linkId=ad1f03111643f0e52f245f120f8efb2b" rel="noopener noreferrer">iPhone 7</a></td><td>80</td><td>2016</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B00YD545CC/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B00YD545CC&amp;linkId=cc899d2e45b87f032042af8eb666cef1" rel="noopener noreferrer">iPhone 6</a></td><td>80</td><td>2014</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B077629NJ2/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B077629NJ2&amp;linkId=cadd23b2ef84a2b9c6abe268754e6cb4" rel="noopener noreferrer">Game Boy Color</a></td><td>80</td><td>1998</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B00WZR5URO/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B00WZR5URO&amp;linkId=f781b3a6200f81f6a0137a69ab1b4c0f" rel="noopener noreferrer">iPhone 5</a></td><td>90</td><td>2012</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B00CES5B96/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B00CES5B96&amp;linkId=fab4357d277c9716f691d15431aace90" rel="noopener noreferrer">BlackBerry Q10</a></td><td>100</td><td>2013</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B01IVV7W8M/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01IVV7W8M&amp;linkId=be83bfddcd1ff8029f548810b405a281" rel="noopener noreferrer">华为荣耀 8</a></td><td>110</td><td>2016</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B0766TPHSH/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B0766TPHSH&amp;linkId=774b61b9fe3692df21821844dfedc18b" rel="noopener noreferrer">谷歌 Pixel 2 XL</a></td><td>110</td><td>2017</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B01F48QLFA/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01F48QLFA&amp;linkId=00e8a7505d84e546c50e3d8761944d05" rel="noopener noreferrer">Galaxy S7</a></td><td>120</td><td>2016</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B00TJ4FFHQ/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B00TJ4FFHQ&amp;linkId=f74214ac520214bcb199ce2710ed470f" rel="noopener noreferrer">Galaxy Note 3</a></td><td>120</td><td>2016</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B00EP2CLII/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B00EP2CLII&amp;linkId=f3ad24ebb82fb4b8282ae94deede1319" rel="noopener noreferrer">Moto X</a></td><td>120</td><td>2013</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B076KTGJPG/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B076KTGJPG&amp;linkId=9164d7895c51ee31fd8db00c3d1597e7" rel="noopener noreferrer">Nexus 5X</a></td><td>120</td><td>2015</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B01MXR13TZ/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01MXR13TZ&amp;linkId=924c9f9d8f2413c64f0f295d505f09da" rel="noopener noreferrer">一加 3T</a></td><td>130</td><td>2016</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B075QBLBN8/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B075QBLBN8&amp;linkId=439fabf5b71beb2b4820618432366e72" rel="noopener noreferrer">BlackBerry KEYone</a></td><td>130</td><td>2017</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B016QP7SKC/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B016QP7SKC&amp;linkId=207d8847bf6e6740694ea85cc97a8363" rel="noopener noreferrer">Moto E (2G)</a></td><td>140</td><td>2015</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B071YC3G5V/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B071YC3G5V&amp;linkId=d5c16509db8a503bfd0e9fa4480bd92f" rel="noopener noreferrer">Moto G4 Play</a></td><td>140</td><td>2017</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B01DZJFYLC/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01DZJFYLC&amp;linkId=d173832b8e78e184d0ec5f801c582403" rel="noopener noreferrer">Moto G4 Plus</a></td><td>140</td><td>2016</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B0731KJVGG/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B0731KJVGG&amp;linkId=a52d46d36b6136641937b82c5f99fa1d" rel="noopener noreferrer">谷歌 Pixel</a></td><td>140</td><td>2016</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B00N3FSWG8/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B00N3FSWG8&amp;linkId=a4df2be4f14002eed6fdbd267b8dca91" rel="noopener noreferrer">三星 Galaxy Avant</a></td><td>150</td><td>2014</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B01LZ8516T/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01LZ8516T&amp;linkId=16070e26fe5b1eee184b3fcfa6a7f373" rel="noopener noreferrer">华硕 Zenfone 3 Max</a></td><td>150</td><td>2016</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B01LACBMV2/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01LACBMV2&amp;linkId=6a38fa7701b76f17fc1ba183e2bac551" rel="noopener noreferrer">索尼 Xperia Z5 Compact</a></td><td>150</td><td>2015</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B00F618DXE/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B00F618DXE&amp;linkId=481f35c95f57b730ab417f680ff1cdf3" rel="noopener noreferrer">HTC One M4</a></td><td>160</td><td>2013</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B00UZ7QJ6W/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B00UZ7QJ6W&amp;linkId=a37479092aca4085fc7bee582232fbfe" rel="noopener noreferrer">Galaxy S4 Mini</a></td><td>170</td><td>2013</td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B072K1BY62/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B072K1BY62&amp;linkId=7a9dc6d3a3400a24cba9141b09d85f32" rel="noopener noreferrer">LG K4</a></td><td>180</td><td>2016</td></tr>
<tr>
<td>数据包往返</td><td>190</td><td></td></tr>
<tr>
<td><a href="https://www.amazon.com/gp/product/B0073YBGCC/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B0073YBGCC&amp;linkId=75785e57c1c5a2318dcd95a6178af60c" rel="noopener noreferrer">HTC Rezound</a></td><td>240</td><td>2011</td></tr>
<tr>
<td><a href="https://en.wikipedia.org/wiki/Pilot_1000" rel="noopener noreferrer">Palm Pilot 1000</a></td><td>490</td><td>1996</td></tr>
<tr>
<td><a href="https://en.wikipedia.org/wiki/Amazon_Kindle" rel="noopener noreferrer">Kindle Oasis 2</a></td><td>570</td><td>2017</td></tr>
<tr>
<td><a href="https://en.wikipedia.org/wiki/Amazon_Kindle" rel="noopener noreferrer">Kindle Paperwhite 3</a></td><td>630</td><td>2015</td></tr>
<tr>
<td><a href="https://en.wikipedia.org/wiki/Amazon_Kindle" rel="noopener noreferrer">Kindle 4</a></td><td>860</td><td>2011</td></tr>
</tbody></table>

<p>如上所述，结果按延迟排序，并按设备从快到慢用绿色到黄色再到红色最后到黑色进行颜色编码。同样，设备越老旧，年份颜色越紫（且越深）。</p>

<p>如果我们排除与其他设备属于不同类别的 <code>Game Boy Color</code>，所有最快的设备都是苹果手机或平板电脑。下一个最快的设备是 <code>BlackBerry Q10</code>。虽然我们没有足够的数据真正说明为什么 <code>BlackBerry Q10</code> 作为非苹果设备却异常快，但一个合理的猜测是它得益于拥有物理按键，这比触摸屏更容易实现低延迟。另外两个拥有物理按键的设备是 <code>Game Boy Color</code> 和 <code>Kindle 4</code>。</p>

<p>在这些 <code>iPhone</code> 和非 Kindle 按键设备之后，我们看到了各种不同年份的安卓设备。在底部，我们看到古老的 <code>Palm Pilot 1000</code>，然后是各种 Kindle。Palm 因触摸屏和显示屏而受限，那个时代的触摸屏技术要慢得多；Kindle 使用 <a href="https://en.wikipedia.org/wiki/E_Ink" rel="noopener noreferrer">E-Ink</a> 显示屏，这种显示屏比现代手机使用的显示屏慢得多，所以看到这些设备垫底并不奇怪。</p>

<h3 id="why-is-the-apple-2e-so-fast">为什么 <code>Apple 2e</code> 如此之快？</h3>

<p>与现代电脑（非最新的 <code>iPad Pro</code>）相比，<code>Apple 2</code> 在输入和输出两端都有显著优势，并且除了最精心编写的代码外，它在输入和输出之间也具有优势，因为 <code>Apple 2</code> 不必处理上下文切换、不同进程间交接涉及的缓冲等。</p>

<p>在输入端，如果我们看看现代键盘，通常以 <code>100 Hz</code> 到 <code>200 Hz</code> 的频率扫描输入（例如，<a href="https://github.com/benblazak/ergodox-firmware" rel="noopener noreferrer">Ergodox 声称以 <code>167 Hz</code> 扫描</a>）。相比之下，<code>Apple 2e</code> 实际以 <code>556 Hz</code> 扫描。详情见附录。</p>

<p>如果我们看看流水线的另一端——显示屏，我们也能发现延迟膨胀。我有一台显示器，包装上标称 <code>1 ms</code> 切换时间，但如果我们看看从屏幕上首次看到字符痕迹到字符完全稳定显示需要多长时间，很容易达到 <code>10 ms</code>。你甚至可以在一些以低延迟著称的高刷新率显示器上看到这种效果。</p>

<p>在 <code>144 Hz</code> 下，每帧耗时 <code>7 ms</code>。屏幕的变化会有 <code>0 ms</code> 到 <code>7 ms</code> 的额外延迟，因为它要等待下一个帧边界才能被渲染（平均而言，我们期望最大延迟的一半，即 <code>3.5 ms</code>）。除此之外，即使我家的显示器标称 <code>1 ms</code> 切换时间，但实际上从显示器开始改变颜色到完全改变颜色，似乎需要 <code>10 ms</code>。当我们把等待下一帧的延迟加上实际颜色变化的延迟加起来，我们得到预期延迟为 <code>7/2 + 10 = 13.5ms</code>。</p>

<p>对于 <code>Apple 2e</code> 中的老式 CRT，我们预期是 <code>60 Hz</code> 刷新率的一半（<code>16.7 ms / 2</code>）加上可忽略的延迟，即 <code>8.3 ms</code>。这在今天很难被超越：最先进的“游戏显示器”可以将总显示延迟降到相同范围，但在市场份额方面，拥有这种显示器的人非常少，甚至标称为快速的显示器也不一定真的快。</p>

<h3 id="ios-rendering-pipeline">iOS 渲染流水线</h3>

<p>如果我们看看输入和输出之间发生了什么，现代系统与 <code>Apple 2e</code> 之间的差异太多，不写一整本书无法描述。为了了解现代机器的情况，这里是前 iOS/UIKit 工程师 <a href="https://andymatuschak.org/" rel="noopener noreferrer">Andy Matuschak</a> 对 iOS 上发生事情的高层级概述，他说呈现时应附带免责声明：“这是我对过时信息的过时记忆”：</p>

<ul>
<li>硬件有自己的扫描率（例如，<code>120 Hz</code> 用于最近的触摸屏面板），这可能会引入高达 <code>8 ms</code> 的延迟。</li>
<li>事件通过固件传递到内核；这相对快速，但系统调度考虑可能在这里引入几毫秒。</li>
<li>内核通过 mach 端口将这些事件传递给特权订阅者（此处为 <code>backboardd</code>）；可能存在更多的调度损失。</li>
<li><code>backboardd</code> 必须确定哪个进程应该接收事件；这需要与窗口服务器（共享该信息）进行锁操作（一次返回内核，更多的调度延迟）。</li>
<li><code>backboardd</code> 将该事件发送给相关进程；在事件被处理之前可能有更多的调度延迟。</li>
<li>这些事件仅在主线程上出队；主线程上可能正在发生其他事情（例如，由于计时器或网络活动），因此可能会导致更多延迟，具体取决于该工作。</li>
<li>UIKit 引入了 <code>1-2 ms</code> 的事件处理开销，受 CPU 限制。</li>
<li>应用程序决定如何处理事件；应用程序编写不佳，因此通常需要许多毫秒。后果被批处理在一个数据驱动的更新中，通过 IPC 发送到渲染服务器。
<ul>
<li>如果应用程序因为事件需要一个新的共享内存视频缓冲区（每当有非平凡的事情发生时就会发生这种情况），这将需要与渲染服务器进行往返 IPC；更多的调度延迟。</li>
<li>（平凡的更改是渲染服务器本身可以合并的更改，例如仿射变换更改或图层颜色更改；非平凡的更改包括与文本、大多数光栅和矢量操作相关的任何内容。）</li>
<li>这类更新通常最终是三重缓冲的：GPU 可能正在使用一个缓冲区进行渲染；渲染服务器可能为下一个帧排队了另一个缓冲区；而你想绘制另一个。这里有更多的（跨进程）锁操作；更多的进入内核之旅。</li>
</ul></li>
<li>渲染服务器将这些更新应用到其渲染树（几毫秒）。</li>
<li>每 <code>N Hz</code>，渲染树被刷新到 GPU，GPU 被要求填充一个视频缓冲区。
<ul>
<li>实际上，屏幕缓冲区通常也是三重缓冲的，原因与我上面描述的相同：GPU 正在绘制一个；另一个可能正在被读取以准备下一帧。</li>
</ul></li>
<li>每 <code>N Hz</code>，该视频缓冲区与另一个视频缓冲区交换，显示器直接从该内存驱动。
<ul>
<li>（这个 <code>N Hz</code> 不一定与前一步骤的 <code>N Hz</code> 理想对齐。）</li>
</ul></li>
</ul>

<p>Andy 说“这里实际发生的<em>工作量</em>通常相当小。几毫秒的 CPU 时间。主要开销来自：”</p>

<ul>
<li>周期性扫描率（输入设备、渲染服务器、显示器）未完美对齐。</li>
<li>跨进程边界的多次交接，每一次都有可能调度其他任务而不是输入事件的后果。</li>
<li>大量的锁操作，尤其是跨进程边界的，需要进入内核。</li>
</ul>

<p>相比之下，在 Apple 2e 上，基本上没有交接、锁或进程边界。一些非常简单的代码运行并将结果写入显示内存，这会在下一次扫描时导致显示器更新。</p>

<h3 id="refresh-rate-vs-latency">刷新率 vs. 延迟</h3>

<p>关于计算机结果的一件奇怪的事情是刷新率的影响。我们从 <code>24 Hz</code> 提升到 <code>165 Hz</code> 获得了 <code>90 ms</code> 的改进。在 <code>24 Hz</code> 下，每帧耗时 <code>41.67 ms</code>；在 <code>165 Hz</code> 下，每帧耗时 <code>6.061 ms</code>。正如我们上面看到的，如果没有缓冲，我们预期由帧刷新增加的平均延迟在前一种情况下是 <code>20.8ms</code>，在后一种情况下是 <code>3.03 ms</code>（因为我们预期在一个帧的均匀随机点到达，需要等待 <code>0ms</code> 到整个帧时间），差异约为 <code>18ms</code>。但实际差异是 <code>90 ms</code>，这意味着我们有相当于 <code>(90 - 18) / (41.67 - 6.061) = 2</code> 个缓冲帧的延迟。</p>

<p>如果我们将同一台机器上的其他刷新率结果绘制出来（未显示），我们会看到它们大致符合一条“最佳拟合”曲线，假设对于运行 powershell 的那台机器，无论刷新率如何，我们都会得到 2.5 个帧时间的延迟。这让我们可以估计如果我们给这台低延迟游戏设备配备一个 <code>无限 Hz</code> 显示器，延迟会是多少——我们期望延迟为 <code>140 - 2.5 * 41.67 = 36 ms</code>，几乎与七八十年代快速但标准的机器一样快。</p>

<h3 id="complexity">复杂性</h3>

<p>今天人们购买的几乎每台电脑和移动设备都比七八十年代的常见型号慢。低延迟游戏台式机和 <code>iPad Pro</code> 可以达到三四十年前快速机器的范围，但大多数现成设备甚至都接近不了。</p>

<p>如果我们必须选择一个延迟膨胀的根本原因，我们可能会说是因为“复杂性”。当然，我们都知道复杂性是坏事。如果你在过去十年参加过非学术、非企业的技术会议，很可能至少有一个演讲是关于复杂性如何是万恶之源，我们应该致力于减少复杂性。</p>

<p>不幸的是，消除复杂性比发表一个我们应该消除复杂性的演讲要困难得多。很多复杂性为我们直接或间接地带来了好处。当我们比较现代花式键盘与 <code>Apple 2</code> 键盘的输入时，我们看到使用相对强大且昂贵的通用处理器处理键盘输入可能比专用的键盘逻辑更慢，后者既更简单又更便宜。然而，使用处理器让人们能够轻松定制键盘，并且将“编程”键盘的问题从硬件转移到软件，这降低了制造键盘的成本。更昂贵的芯片增加了制造成本，但考虑到这些小批量手工键盘设计成本的巨大占比，用制造成本换取编程便利性似乎是净赢。</p>

<p>我们在流水线的每个部分都看到这种权衡。其中最大的例子之一是你可能在现代桌面电脑上运行的操作系统与 <code>Apple 2</code> 上运行的循环。现代操作系统允许程序员编写通用代码，可以处理同一台机器上同时运行的其他程序，并且相当合理的通用性能，但我们为此付出了巨大的复杂性成本，而使这一切变得轻松的交接带来了显著的延迟损失。</p>

<p>很多复杂性可能被称为<a href="http://wiki.c2.com/?AccidentalComplexity" rel="noopener noreferrer">偶然复杂性</a>，但大多数偶然复杂性之所以存在，是因为它非常方便。从硬件架构到系统调用接口再到我们使用的 I/O 框架，我们在各个层面都承担了复杂性，其中很多如果今天我们能坐下来重写所有系统及其接口是可以消除的，但重新发明宇宙以减少复杂性太不方便了，而且我们从规模经济中获益，所以我们忍受现状。</p>

<p>由于这些原因以及更多，实际上，解决由“过多”复杂性引起的性能不良的方案往往是增加更多复杂性。特别是，我们看到让我们回到三四十年前最快机器速度的进展，并非来自听从减少复杂性的呼吁，而是通过堆砌更多复杂性实现的。</p>

<p><code>iPad Pro</code> 是现代工程的壮举；投入其中用于提高输入和输出两端刷新率以及确保软件流水线没有不必要缓冲的工程就是复杂的！设计和制造高刷新率显示器以降低系统延迟，其复杂性也是非平凡的，并且对于标准 <code>60 Hz</code> 显示器并非必要。</p>

<p>这实际上是降低延迟时的一个常见主题。减少延迟的一个常见技巧是添加缓存，但向系统添加缓存会使其更复杂。对于生成新数据且无法容忍缓存的系统，解决方案通常更复杂。例如可能是<a href="https://en.wikipedia.org/wiki/RDMA_over_Converged_Ethernet" rel="noopener noreferrer">大规模 RoCE 部署</a>。它们可以将远程数据访问延迟从毫秒范围降低到微秒范围，<a href="//danluu.com/infinite-disk/" rel="noopener noreferrer">从而启用新类别的应用程序</a>。然而，这付出了巨大的复杂性代价。早期的大规模 RoCE 部署轻松耗费数十人年的努力才正确完成，并且带来了巨大的运营负担。</p>

<h3 id="conclusion">结论</h3>

<p>有点荒谬的是，一台现代游戏机以 <code>4,000倍</code> 于 <code>Apple 2</code> 的速度运行，其 CPU 拥有 <code>500,000倍</code> 的晶体管（其 GPU 拥有 <code>2,000,000倍</code> 的晶体管），在非常精心编写的应用程序中，也许能管理与 <code>Apple 2</code> 相同的延迟，前提是我们有一个刷新率接近 <code>3倍</code> 的显示器。更荒谬的是，直到2017年10月之前拥有最快单线程性能的 <code>Powerspec G405</code> 的默认配置，其从键盘到屏幕（大约 <code>3英尺</code>，可能实际布线 <code>10英尺</code>）的延迟，比环球发送数据包（从纽约到东京再到伦敦返回纽约，<code>16187英里</code>，更多是因为运行最短光纤长度的成本）还要高。</p>

<p>从积极的一面看，我们可以说正在走出延迟的黑暗时代，现在可以组装一台电脑或购买一台平板电脑，其延迟范围与七八十年代现成设备相当。这让我想起了屏幕分辨率和密度的黑暗时代，当时九十年代的 CRT 提供了比非笔记本电脑 LCD 更好的分辨率和更高的像素密度，直到最近。4K 显示器现在已成为标准，价格合理的 8K 显示器也即将出现，超越了我们在消费级 CRT 上看到的任何东西。我不知道我们是否会在延迟方面看到同样的改善，但人总是怀有希望。有独立的开发者在为某些非常精心编写的应用程序的用户改善体验，但尚不清楚有什么力量能导致大多数用户看到的默认体验显著改善。</p>

<h3 id="other-posts-on-latency-measurement">关于延迟测量的其他文章</h3>

<ul>
<li><a href="//danluu.com/term-latency/" rel="noopener noreferrer">终端延迟</a></li>
<li><a href="//danluu.com/keyboard-latency/" rel="noopener noreferrer">键盘延迟</a></li>
<li><a href="//danluu.com/keyboard-v-mouse/" rel="noopener noreferrer">鼠标 vs. 键盘延迟</a>（人为因素，非设备延迟）</li>
<li><a href="https://pavelfatin.com/typing-with-pleasure/" rel="noopener noreferrer">编辑器延迟</a>（Pavel Fatin 著）</li>
<li><a href="http://www.lofibucket.com/articles/dwm_latency.html" rel="noopener noreferrer">Windows 10 合成延迟</a>（Pekka Vaananen 著）</li>
<li><a href="http://blogs.valvesoftware.com/abrash/latency-the-sine-qua-non-of-ar-and-vr/" rel="noopener noreferrer">AR/VR 延迟</a>（Michael Abrash 著）</li>
<li><a href="//danluu.com/latency-mitigation/" rel="noopener noreferrer">延迟缓解策略</a>（John Carmack 著）</li>
</ul>

<h3 id="appendix-why-measure-latency">附录：为什么要测量延迟？</h3>

<p>延迟很重要！对于非常简单的任务，<a href="https://pdfs.semanticscholar.org/386a/15fd85c162b8e4ebb6023acdce9df2bd43ee.pdf" rel="noopener noreferrer">人们能感知低至 <code>2 ms</code> 甚至更短的延迟</a>。此外，增加的延迟不仅用户会注意到，<a href="http://www.tactuallabs.com/papers/howMuchFasterIsFastEnoughCHI15.pdf" rel="noopener noreferrer">还会导致用户执行简单任务时的准确性下降</a>。如果你想直观地了解延迟是什么样子，而你手头又没有一台快速的旧电脑，<a href="https://www.youtube.com/watch?v=vOvQCPLkPt4" rel="noopener noreferrer">请查看这个关于触摸屏延迟的 MSR 演示</a>。</p>

<p>最常被引用的关于响应时间的文档是 Nielsen 集团关于响应时间的文章，该文章声称低于 <code>100ms</code> 的延迟感觉等同并被感知为瞬时。一个简单的方法来看到这是错误的是，进入你的终端尝试 <code>sleep 0; echo "pong"</code> 与 <code>sleep 0.1; echo "test"</code>（或者，试试玩一个没有延迟补偿的老游戏，比如 Quake 1，用 <code>100 ms</code> 的 ping，甚至 <code>30 ms</code> 的 ping，或者在终端中以 <code>30 ms</code> 的 ping 打字）。有关此以及其他延迟误区的更多信息，请<a href="//danluu.com/keyboard-latency/#appendix-counter-arguments-to-common-arguments-that-latency-doesn-t-matter" rel="noopener noreferrer">参见这份关于延迟常见误区的文档</a>。</p>

<p><a href="https://stackoverflow.com/a/39187441/334816" rel="noopener noreferrer">吞吐量</a>也很重要，但这已被广泛理解和测量。如果你去几乎任何主流评论或基准测试网站，你都能找到各种各样的吞吐量测量，因此编写额外的吞吐量测量价值不大。</p>

<h3 id="appendix-apple-2-keyboard">附录：Apple 2 键盘</h3>

<p><code>Apple 2e</code> 没有使用编程的微控制器读取键盘，而是使用了一个更简单的专用芯片 AY 3600 来读取键盘输入。如果我们查看<a href="//danluu.com/AY3600.pdf" rel="noopener noreferrer">AY 3600 数据手册</a>，我们可以看到扫描时间是 <code>(90 * 1/f)</code>，去抖时间列为 <code>strobe_delay</code>。这些量由一些电容器和电阻器决定，对于 Apple 2e，这些似乎是 <code>47pf</code>、<code>100k 欧姆</code> 和 <code>0.022uf</code>。将这些数字代入<a href="//danluu.com/AY3600.pdf" rel="noopener noreferrer">AY3600 数据手册</a>，我们可以看到 <code>f = 50 kHz</code>，给出 <code>1.8 ms</code> 的扫描延迟和 <code>6.8 ms</code> 的去抖延迟（假设数值准确——<a href="http://www.kemet.com/Lists/TechnicalArticles/Attachments/191/Why%2047%20uF%20capacitor%20drops%20to%2037%20uF-%2030%20uF-%20or%20lower.pdf" rel="noopener noreferrer">电容器可能随时间退化</a>，所以我们应该预期旧 Apple 2e 上的实际延迟更短），总共不到 <code>8.6 ms</code> 用于内部键盘逻辑。</p>

<p>与扫描率为 <code>167 Hz</code>、<a href="https://www.arduino.cc/en/Tutorial/Debounce" rel="noopener noreferrer">扫描两次以进行去抖</a>的键盘相比，等效数字是 <code>3 * 6 ms = 18 ms</code>。在 <code>100Hz</code> 扫描率下，这变为 <code>3 * 10 ms = 30 ms</code>。<code>18 ms</code> 到 <code>30 ms</code> 的键盘扫描加上去抖延迟，与<a href="//danluu.com/keyboard-latency/" rel="noopener noreferrer">我们进行的一些初步键盘延迟测量结果一致</a>。</p>

<p>供参考，Ergodox 使用 <code>16 MHz</code> 微控制器，约有 80k 晶体管；而 <code>Apple 2e</code> CPU 是 <code>1 MHz</code> 芯片，有 3.5k 晶体管。</p>

<h3 id="appendix-why-should-android-phones-have-higher-latency-than-old-apple-phones">附录：为什么安卓手机的延迟应该比旧苹果手机高？</h3>

<p>正如我们所看到的，原始处理能力对流水线中许多导致延迟的原因帮助不大，例如不同进程之间的交接，因此一台处理器比古老 iPhone 强大 10 倍的安卓手机，并不能保证其响应更快，即使它能更快地渲染 JavaScript 密集型页面。</p>

<p>如果你与从事非苹果移动 CPU 工作的人交谈，你会发现他们运行像 Dhrystone（一个甚至在其创建的1984年就已无关紧要的合成基准测试）和 SPEC2006（一个工作站基准测试的更新版本，在90年代甚至可能在2000年代初期，如果你关心工作站工作负载，那与移动工作负载完全不同）这样的基准测试。这个问题出现在组件制造商有一个<a href="//danluu.com/percentile-latency/" rel="noopener noreferrer">中间目标</a>，该目标仅与实际用户体验有弱相关性。我听说 Pixel 手机的团队中有人关心端到端延迟，但当你必须使用为 Dhrystone 和 SPEC2006 等优化的组件时，很难获得良好的延迟。</p>

<p>如果你与苹果的人交谈，你会发现他们相当谨慎，但他们很长时间以来一直以端到端用户体验为目标，并且他们能够进行“全栈”优化，这是安卓厂商很难做到的。并非绝对不可能，但要对芯片进行更改并贯穿操作系统，除非谷歌亲自优化，否则你不太可能看到，而谷歌直到最近才真正重视端到端体验。</p>

<p>在未被测量的方面表现不佳是一个常见主题，我们在查看<a href="//danluu.com/term-latency/" rel="noopener noreferrer">终端延迟</a>时也见过。在检查终端延迟之前，公开的基准测试都是面向吞吐量的，优先考虑性能的终端致力于增加吞吐量，即使增加终端吞吐量并非真正有用。在那些终端延迟基准测试之后，一些终端作者开始研究他们的延迟，找到了可以削减缓冲和消除延迟的地方。你测量什么，就得到什么。</p>

<h3 id="appendix-experimental-setup">附录：实验设置</h3>

<p>大多数测量使用 <a href="https://www.amazon.com/gp/product/B071W3DDM7/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B071W3DDM7&amp;linkId=fde44f1346bdf4bc60896580c2f3dffa" rel="noopener noreferrer">iPhone SE</a> 中的 240fps 相机（<code>4.167 ms</code> 分辨率）进行。响应时间低于 <code>40 ms</code> 的设备使用 <a href="https://www.amazon.com/gp/product/B01M62I8Q7/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01M62I8Q7&amp;linkId=d5f90ebe0b220a8c6e550761f663fb19" rel="noopener noreferrer">Sony RX100 V</a> 的 1000fps 相机（<code>1 ms</code> 分辨率，PAL 模式）重新测量。表格中的结果是多次运行的结果，并四舍五入到最近的 <code>10 ms</code>，以避免造成虚假精确的印象。对于台式机结果，测量是从按键开始移动到屏幕更新完成。请注意，这与网上大多数键盘到屏幕更新测量不同，后者通常使用的设置实际上消除了大部分或全部键盘延迟，作为端到端测量，除非你与计算机有心灵感应，否则这并不现实（这并不是说这些测量没用——如果作为程序员，你想要一个可重复的基准测试，减少无法控制的测量噪声是很好的，但这与最终用户无关）。人们通常主张从以下之一测量：{按键触底、开关的触觉反馈}。除了测量方便之外，似乎没有理由做这些，但人们经常声称那是用户期望键盘“真正”工作的时候。但这些与开关实际触发的时间无关。按键触底和激活之间的距离，以及感觉反馈和激活之间的距离都是任意的，可以调整。有关键盘误区的更多信息，请参阅<a href="//danluu.com/keyboard-latency/" rel="noopener noreferrer">这篇关于键盘延迟测量的文章</a>。</p>

<p>另一个显著的区别是，测量是在尽可能接近默认操作系统设置的情况下进行的，因为大约 0% 的用户会摆弄显示设置以减少缓冲、禁用合成器等。等待屏幕更新完成也与大多数端到端测量不同——大多数测量认为任何屏幕上检测到运动时更新就“完成”。等待屏幕完全改变类似于 WebPageTest 的“视觉完成”时间。</p>

<p>计算机结果使用系统的“默认”终端（例如 Windows 上的 Powershell，Lubuntu 上的 lxterminal），<a href="//danluu.com/term-latency/" rel="noopener noreferrer">这很容易导致快速终端和慢速终端之间 <code>20 ms</code> 到 <code>30 ms</code> 的差异</a>。在终端中测量时间与测量完整的端到端时间之间，本文中的测量应该比其他类似文章中的测量慢（那些文章倾向于测量游戏中首次变化的时间）。</p>

<p><code>Powerspec G405</code> 基准结果使用集成显卡（机器没有附带显卡），<code>60 Hz</code> 结果使用了一块便宜的显卡。基准结果是 <code>30 Hz</code>，因为集成显卡仅支持 <code>HDMI</code> 输出，而连接的显示器仅在 <code>HDMI</code> 上以 <code>30 Hz</code> 运行。</p>

<p>移动设备的结果是通过使用默认浏览器，访问 <a href="https://danluu.com" rel="noopener noreferrer">https://danluu.com</a>，并测量从手指移动到屏幕首次更新以指示滚动开始发生的延迟。在不适用的情况下（Kindle、Game Boy Color 等），采取对该平台有意义的操作（在 Kindle 上</p><p><em>由 mimo-v2.5 模型翻译，花费 34115 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/input-lag/</link>
      <guid isPermaLink="false">https://danluu.com/input-lag/</guid>
      <pubDate>Sun, 24 Dec 2017 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>如何评判决策质量？——在易于评估的领域审视决策效果</title>
      <description>[AI 摘要] 本文通过棒球和棋盘游戏案例，论证即使在易于评估的领域，糟糕决策也可能长期存在。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文通过棒球和棋盘游戏案例，论证即使在易于评估的领域，糟糕决策也可能长期存在。</div>在科技乌托邦圈子里，我常听到这样的论调：某些看似低效的做法不可能真的低效，因为市场是高效的，低效之处很快会被消除。一个颇具争议的例子是<a href="//danluu.com/tech-discrimination/" rel="noopener noreferrer">企业不可能存在歧视，因为市场竞争过于激烈，无法容忍歧视行为</a>。争议较小的例子是，当你看到大公司做出看似极其低效的决策时，也许这并非低效，只是你缺乏理解该决策为何有效的必要信息。这类言论常伴随着“激励很重要”或CEO“切身利益攸关”（而评论者则不然）的说法。

不幸的是，这类争论难以平息，因为即使事后回顾，通常也无法获得足够信息来确定某项决策的确切“价值”。即使在决策导致明确成功或失败的情况下，导致结果的因素如此之多，以至于很难精确弄清事情为何发生。

在本系列文章中，我们将审视两类例子，从中可以观察人们的决策质量如何，以及当他们面对易于获取的数据证明其决策糟糕时作何反应。这两类例子都来自决策者或讨论者似乎非常重视决策，且数据清楚地表明决策非常糟糕的领域。

第一类例子来自体育运动，第二类来自棋盘游戏。体育运动的好处在于，它们通常有详细的逐场比赛数据和明确的胜负标准，这让我们能大致判断某项决策的预期价值。本文将审视某项运动中糟糕决策的代价，并简要讨论为何体育运动中的决策质量可能与其他领域相当或更好。体育运动是沃土，因为在相当近期之前，决策并非数据驱动且普遍糟糕，因此我们拥有超过一个世纪的美国主要运动数据，而且在其中相当长的一段时间里，球迷会撰写分析文章，指出决策有多糟糕以及因此让球队损失了多少，但球队对此置之不理（这种情况现已改变，基本上每支球队都有一支统计学博士或等效人员组成的团队在分析数据）。

<h4 id="baseball">棒球</h4>

<p>在<a href="https://danluu.com/talent/" rel="noopener noreferrer">另一篇文章中，我们审视了体育运动中“招聘”决策如何纯属无稽之谈</a>。在本文中，仅仅因为“理性圈”某位顶级思想领袖给出了一个常见借口，即棒球比赛中的场上决策成本并不高（“糟糕的场上决策会输掉比赛吗？当然会。但不会<em>那么多</em>。也许一年162场里输掉4场。”；整篇文章暗示这没什么大不了的，浪费4场比赛没关系），我们将审视糟糕决策的代价有多大，以及球队为购买等量胜利在其他方面花费了多少。不过，你可以对橄榄球、冰球、篮球等进行类似的分析，据我所知，在所有这些情况下，你会得到大致相似的结果。</p>

<p>我们将把棒球建模为一个状态机，因为这既便于理解特定决策的预期价值，也让我们无需赘述大部分棒球规则就能讨论决策的价值。</p>

<p>我们可以将每场棒球比赛视为一个独立事件。每场比赛中，两支球队对阵，得分更高（跑分）的队伍获胜。比赛分为9个“局”，每局每队有一次进攻机会。每局中，每队进攻直到累计3个“出局”。任何特定进攻都可能产生出局，也可能不产生。</p>

<p>我们状态机的一个状态块是出局数和局数。我们将追踪的另外两个状态块是“跑垒员”情况和“击球员”是哪个。每支球队为其现役球员定义一个击球顺序，每位球员击球一次后，顺序循环重复，直到该队累计3个出局，该局结束。击球员的状态会跨局保存。例如，你可能会看到1至5号击球员在第一局击球，第二局6至9号然后又是1号击球，第三局2号击球……以此类推。</p>

<p>当一名球员击球时，该球员可能推进到垒包，垒上的球员也可能根据发生的情况推进。当一名球员推进4个垒包（即经过一垒、二垒、三垒，到达本该称为四垒但实际不这么叫的地方）时，就得一分，该球员离开垒包。如上所述，各种事件可能导致球员出局，此时他们也不再在垒上。</p>

<p>我们状态机的一个示例状态是：</p>

<pre><code>{一垒, 三垒; 2出局}
</code></pre>

<p>这表示有一名球员在一垒，一名在三垒，有两个出局数。注意这与比分、实际参赛球员和局数无关。</p>

<p>另一个状态是：</p>

<pre><code>{--; 0出局}
</code></pre>

<p>使用这样的模型，如果我们想确定上述状态的预期价值，只需查看一个赛季中所有局数的总跑分除以局数，即可找到从上述状态预期能得到的跑分（忽略第九局，因为棒球规则的一个特点会扭曲第九局的统计数据）。如果这样做，我们会发现，从上述状态，一支球队预期能得到 .555 分。</p>

<p>然后我们可以计算所有其他状态的预期跑分：</p>

<p>
</p><table>
<tbody><tr>
<th></th><th>0</th><th>1</th><th>2</th></tr>
<tr><th>垒包</th><th colspan="3">出局数</th></tr>
<tr>
<th>--</th><td>.555</td><td>.297</td><td>.117</td></tr>
<tr>
<th>一垒</th><td>.953</td><td>.573</td><td>.251</td></tr>
<tr>
<th>二垒</th><td>1.189</td><td>.725</td><td>.344</td></tr>
<tr>
<th>三垒</th><td>1.482</td><td>.983</td><td>.387</td></tr>
<tr>
<th>一二垒</th><td>1.573</td><td>.971</td><td>.466</td></tr>
<tr>
<th>一三垒</th><td>1.904</td><td>1.243</td><td>.538</td></tr>
<tr>
<th>二三垒</th><td>2.052</td><td>1.467</td><td>.634</td></tr>
<tr>
<th>满垒</th><td>2.417</td><td>1.650</td><td>.815</td></tr>
<tr>
</tr>
</tbody></table><p></p>

<p>在此表中，每个条目表示从某个特定状态在该局剩余部分预期能得到的跑分。每列显示出局数，每行显示垒上状态。配色方案是：起始状态（<code>.555</code> 分）背景为白色。预期跑分更高的状态更蓝，预期跑分更低的状态更红。</p>

<p>此表和本文中的其他统计数据来自<a href="https://www.amazon.com/gp/product/1494260174/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=1494260174&amp;linkId=1d8e432457b958c87f46820d5aa51539" rel="noopener noreferrer">坦戈等人的《The Book》</a>，该书主要讨论1999年至2002年间的棒球。若对使用更详细模型后的变化感兴趣，请参阅附录。</p>

<p>我们在此为一局追踪的状态是垒上情况和出局数。每局开始时无人在垒且无人出局。</p>

<p>如上所述，我们看到该局开始时预期得分为<code>.555</code>。如果一次进攻让球员上一垒且未造成出局，我们现在的预期得分为<code>.953</code>，即让球员上一垒且无出局的价值为<code>.953 - .555 = .398</code>分。</p>

<p>这立即让我们能评估某些决策的价值，例如在无出局且一垒有人时试图“盗”二垒。如果我们观察击球员状态未改变的情况，成功的盗垒使我们进入<code>{二垒, 0出局}</code>状态，即带来<code>1.189 - .953 = .236</code>分。失败的盗垒使我们进入<code>{--, 1出局}</code>状态，即带来<code>.953 - .297 = -.656</code>分。要达到盈亏平衡，我们需要成功的次数是失败次数的<code>.656 / .236 = 2.78</code>倍，即需要<code>.735</code>的成功率才能保本。如果我们想计算一次盗垒的平均价值，我们可以对所有状态进行加权求和，但现在，我们只需说明这是可能做到的，并且你需要盗垒的成功率达到约<code>.735</code>才合理。</p>

<p>然后我们可以查看球队的盗垒成功率，发现在任何给定赛季，也许只有5-10支球队做得比盈亏平衡更好，其余20-25支球队处于保本线或以下（主要是以下）。如果我们看那个时代一支表现不佳但非历史最差的盗垒球队，他们的成功率可能是0.6。在那个时代，一支球队尝试100到200次盗垒并不罕见。为了估算，假设所有尝试都发生在{一垒, 0出局}状态，那么每次尝试的平均跑分价值为0.4 * (-.656) + 0.6 * .236 = -0.12分。另一个一阶近似是10分的差值相当于1胜，那么100次尝试损失1.2胜，200次尝试损失2.4胜。</p>

<p>如果我们使用实际状态而非一阶近似来计算，会发现平均一次盗垒价值-.467分，平均一次成功盗垒价值.175分。在这种情况下，一次成功率为0.6的盗垒尝试价值为0.4 * (-.467) + 0.6 * .175 = -0.082分。根据这个新的近似，我们估计对于一支盗垒糟糕且频繁的球队，相比“禁止盗垒”规则，其“常规”盗垒每赛季造成的胜场损失约为0.82至1.64胜。注意这低估了盗垒的代价，因为准备盗垒会增加被“牵制出局”的可能性，而我们未考虑这一点。从我们状态机的角度看，被牵制出局几乎等同于盗垒失败，但计算牵制出局概率差异所需的分析超出了本文的范围。</p>

<p>我们也可以对教练可能引起（或阻止）的其他进攻进行类似分析。对于“故意四坏球保送”，我们看到一次故意四坏球保送似乎为对手带来.102分。2002年，一支“大量”投出故意四坏球的球队可能投出50次，给对手带来50 * .102分，损失约5分或0.5胜。</p>

<p>如果我们乐观地假设“牺牲触击”从不失败，每次牺牲触击的代价是.027分。如果我们看投手不击球的联盟，一支大量使用牺牲触击的球队可能执行了49次牺牲触击（我们这样是为了避免“投手触击”，这会给近似计算增加复杂性），总共损失49 * .027 = 1.32分或0.132胜。</p>

<p>另一个由教练做出的决定是设定击球顺序。球员按1-9的顺序（模9）轮流击球。也就是说，当第10位“球员”上场时，实际上是回到1号球员击球。比赛在某个时间点结束，因此并非队中每个人都获得相同数量的“击球机会”。</p>

<p>有一个老生常谈的理由来证明将速度最快的球员放在第一棒，将“打击率”高的球员放在第二棒，相当不错的球员放第三棒，最好的击球员放第四棒等。这个故事或类似版本已经沿用了一百多年。</p>

<p>我不会详细讲解计算更优击球顺序的数学过程，因为我认为没有简短易描述的近似方法。事实证明，如果我们计算“最优”顺序与前文所述故事所证明的“典型”顺序之间的差异，使用最优顺序似乎每赛季价值1到2胜。</p>

<p>这些近似都遗漏了重要信息。在四种情况中有三种，我们假设始终是平均球员，而没有考虑具体是谁在击球。上述信息其实在一定程度上考虑了这一点，但并未完全考虑。这与更好的近似方法有何不同，这是一个长篇故事，对于一篇用棒球来讨论非棒球领域决策的文章来说可能过于详细，所以我们只能说，我们有一个相当不错但并非惊人的近似，它表明一位遵循传统智慧做出糟糕决策、其决策处于棒球赛季中正常糟糕范围内的教练，仅在这三项决策上就可能让球队损失约1 + 1.2 + .5 + .132 = 2.83胜，相较于一个说“永远不做那些平均而言具有负价值的行为”的决策规则。如果我们与更好的决策规则（比如“在这些行为具有正价值时做，负价值时不做”）或一位通常能做出好决策的经理相比，保守估计，这可能价值3胜。</p>

<p>我们审视了四项决策（牺牲触击、盗垒、故意四坏球保送和击球顺序）。但还有许多其他决策！让我们任意假设，如果我们审视所有决策而不仅仅是这四项，拥有适用于所有决策的更好启发式方法可能每赛季价值4或5胜。</p>

<p>每赛季4或5胜到底意味着什么？一种看法是，棒球队打162场比赛，所以一支“平均”球队赢81场。如果我们看涵盖的赛季，进入季后赛的球队胜场数为{103, 94, 103, 99, 101, 97, 98, 95, 95, 91, 116, 102, 88, 93, 93, 92, 95, 97, 95, 94, 87, 91, 91, 95, 103, 100, 97, 97, 98, 95, 97, 94}。由于系统结构，我们无法为某个赛季定出一个数字N，说N胜是进入季后赛的必要条件，胜场少于N的球队无法进入，但我们可以说95胜给了一支球队不错的季后赛机会。95 - 81 = 14。5胜超过了平均球队与进入季后赛球队之间差值的三分之一。这无论在声望还是直接经济价值上都是大事。</p>

<p>如果我们从边际角度而非平均角度看，每个联盟中进入季后赛与未进入的球队之间最小胜场差为{1, 7, 8, 1, 6, 2, 6, 3}。对于处于边缘的球队，5胜的差值并不总是决定成功赛季（进入季后赛）与失败赛季（未进入）的因素，但大多数赛季都有球队与季后赛门槛的胜场差在5胜以内。如果我们实际运营一支棒球队，我们会希望使用更精细的模型，但作为初步近似，我们可以说场上决策是球队表现的重要因素，并且通过某种计算，我们可以确定非最优决策的预期代价。</p>

<p>看待5胜价值的另一种方式是，获得一位比平均水平高出5胜（WAA）的非投手球员需要花费多少（我们关注非投手是因为他们往往每场比赛都上场，而投手只在部分比赛中上场，使得投手与非投手之间的比较更复杂）。在8个非投手位置（我们关注非投手位置是为了简化比较）上，有30支球队，所以我们有240个球队-位置对。2002年，在这240个球队-位置对中，<a href="https://www.baseball-reference.com/leagues/team_compare.cgi?request=1&amp;year=2002&amp;lg=MLB&amp;stat=WAA" rel="noopener noreferrer">有两个≥5 WAA</a>，德克萨斯队-游击手（阿历克斯·罗德里格斯，年薪2200万美元）和旧金山队-左外野手（巴里·邦兹，年薪1500万美元）。如果我们看我们关注的日期范围内的其他赛季，要么有2个，要么有3个球队-位置对能在单赛季获得≥5 WAA。这些并非跨赛季稳定，因为球员表现波动很大，所以不像找个厉害的人付他1500万美元那么简单。例如，2002年，有7位非投手年薪1400万美元或以上，但只有两位价值达到5 WAA或以上。作为参考，2002年球队平均总薪资（每队26人）为6700万美元，最低3400万，最高1.26亿。当时100万美元的经理薪水会被认为很高，因此一位5 WAA的经理是超值交易。</p>

<p>5 WAA假设典型决策以糟糕但非最差的方式与事件对应。更典型的情况可能是经理给球队造成3胜的损失。那么在2002年，240个球队-位置对中有25个，单个球员能弥补因传统智慧管理造成的损失。能提供如此价值且未因球员交易机制而被特定球队锁定在人为低价合同的球员，仍然比经理昂贵得多。</p>

<p>如果我们审视球队如何采用数据分析来改进场上决策和球队组建决策，这是一个缓慢的、跨越数十年的过程。<a href="https://www.amazon.com/gp/product/0393324818/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0393324818&amp;linkId=391fa6566a95327d7f06f533fb3e2daf" rel="noopener noreferrer">《点球成金》</a>描述了从使用直觉和观察来选择球员到将统计数据纳入流程的部分转变。至少从<a href="https://en.wikipedia.org/wiki/Sabermetrics" rel="noopener noreferrer">1971年</a>开始，统计极客就在谈论如何做到这一点，但直到90年代才真正有球队认真对待，这些想法直到2000年代中期才真正成为主流，那时一本畅销书已经出版。</p>

<p>如果我们审视球队在本文所述的场上决策方面有多大改进，这个过程甚至更慢。时至今日，数据驱动的决策仍未成为主流。情况正在改善，如果我们看过去二十年这些非最优决策的总体成本，随着直觉驱动的决策逐渐与统计极客几十年来一直主张的做法更趋一致，总体成本一直在下降。例如，如果我们看从1999年至今所有球队的牺牲触击总数，我们看到：</p>

<div>
<table>
<tbody><tr>
<th>1999</th><th>2000</th><th>2001</th><th>2002</th><th>2003</th><th>2004</th><th>2005</th><th>2006</th><th>2007</th><th>2008</th><th>2009</th><th>2010</th><th>2011</th><th>2012</th><th>2013</th><th>2014</th><th>2015</th><th>2016</th><th>2017</th></tr>
<tr>
<td>1604</td><td>1628</td><td>1607</td><td>1633</td><td>1626</td><td>1731</td><td>1620</td><td>1651</td><td>1540</td><td>1526</td><td>1635</td><td>1544</td><td>1667</td><td>1479</td><td>1383</td><td>1343</td><td>1200</td><td>1025</td><td>925</td></tr>
</tbody></table>
</div>

<p>尽管有几十年的统计数据表明牺牲触击被过度使用，但直到2012年左右，我们才看到所有球队的使用量真正下降。原因各队各例不同，但不断重复的基本故事是，对于数据驱动的球队组建和数据驱动的场上决策，拥有决策权的人往往坚持传统智慧，而非使用“激进的”数据驱动理念。发生这种情况有多种原因。一个高层原因是，我们谈论的这种变化是文化变革，而文化变革是缓慢的。即使在这种变化发生且更数据驱动的球队相对于其预算表现更出色时，反数据的人仍然嘲笑任何使用数据的人。如果你是早期的数据信徒之一，你必须<a href="https://danluu.com/look-stupid/" rel="noopener noreferrer">愿意承受许多业界大牌、球迷、朋友等称你愚蠢</a>。在面对这种反对时，科学共识需要一代人才能转变，所以如果棒球不是这样，人们也不会感到惊讶。</p>

<p>一个具体的低层原因是，看似明显非最优的决策能持续这么久，是因为球队结果中存在大量噪音。你有时会看到一位经理做出一些激进决策（不一定基于数据），随后结果不佳，导致管理层解雇该经理。波动性如此之大，你无法根据小样本判断球员或经理，但这并不能阻止人们这样做。波动性和对激进想法的怀疑相结合，极大地抑制了违背传统智慧的行为。</p>

<p>这种噪音的众多后果之一是，“世界大赛”（棒球冠军赛）的赢家很大程度上由随机性决定。一支球队能否进入季后赛是由162场比赛决定的，这不足以消除所有随机性，但足以使结果不主要由随机性决定。季后赛则不然，其持续时间太短，无法主要由球队质量的差异决定结果。一旦球队赢得世界大赛，人们就会编出各种自圆其说的故事来证明该队为何应该获胜，但如果我们纵观所有比赛，就会发现这些故事仅仅是故事。这或许与听人们讲述他们创业为何成功没什么不同。</p>

<p>有些指标能更好地预测未来的胜负（即比胜负本身波动更小），但直到最近，说服人们相信这些指标有意义也是一个激进的想法。</p>

<h4 id="board-games">棋盘游戏</h4>

<p>以上是棒球的例子。现在来看棋盘游戏的例子。在这个例子中，我们将审视那些对“现代”棋盘游戏策略发表评论的人，我指的是他们对卡坦岛、波多黎各、方舟动物园等游戏策略的评论。</p>

<p>人们经常激烈争论什么有效，什么无效。如今，这类讨论大多发生在boardgamegeek（BGG）上，这是一个迄今为止最大的棋盘游戏讨论论坛。这些讨论的一个特点是，人们在BGG和boardgamearena（BGA）——一个在线棋盘游戏平台，玩家的等级（Elo）被追踪，你可以看到他们的Elo等级分——上使用相同的用户名。</p>

<p>所以，在这些讨论中，你会看到有人说策略X占主导地位。然后另一个人会出来说，不，策略Y能打败策略X，当别人用策略X时，我总用策略Y赢，等等。如果你理解这个游戏，你会看到主张X的人是对的，主张Y的人是错的，然后你会查一下这些人的Elo，发现X玩家是高排名玩家，Y玩家是低排名玩家。</p>

<p>奇怪的是，为什么低排名玩家如此自信地坚持他们的立场是正确的？他们不仅每场比赛都收到表明他们错了的信息（因为他们经常输），还有一个等级分聚合了他们所有的游戏表现，大致告诉他们水平如何。尽管这个等级分告诉他们在游戏中不知道自己在做什么，他们却完全确信自己是优秀的玩家，下得很好，而且他们不仅有好的策略，这些策略好到足以去建议等级分高得多的玩家如何玩。</p>

<p>当人们纠正这些玩家时，他们常常感到被冒犯，因为他们确信自己很强，他们会说“我是优秀的[游戏名]玩家。我赢了很多场比赛”，接着是他们的建议未被认真对待的愤慨，和/或关于认为策略X有效的人都是在群体思维中，即使这些人是在同一个有竞争力的在线玩家池中玩，如果策略X玩家真在进行错误的群体思维，策略Y玩家会打败他们并获得更高的等级分。而且，正如我们在<a href="https://danluu.com/p95-skill/" rel="noopener noreferrer">审视电子游戏技能时指出的</a>，玩家经常对输掉游戏和没有变得更好表达极大的挫败和愤怒，所以很明显他们想做得更好并赢球。但即使屏幕始终显示一个相当准确总结你技能的等级分，似乎也不足以让人们意识到，他们平均而言做出糟糕的决策，通过听取高排名玩家的建议而不是坚持他们那失败的策略，他们可以很容易地做出更好的决策。</p>

<p>在审视电子游戏《守望先锋》时，我们注意到玩家经常高估自己的技能，并将失败归咎于队友。但在这种棋盘游戏中，玩家通常不是组队的，所以没有其他人可以责怪。不仅没有可以责怪的队友，在大多数游戏中，最严肃的等级格式是1对1，而不是某种多人乱斗，所以你甚至不能责怪一个不在你队里的随机人。总的来说，某人在1对1游戏中的等级分，是衡量其领域特定决策技能最准确的指标。</p>

<p>然而，人们对自己的技能极其自信，尽管他们的等级分很低。如果你看当今的棋盘游戏策略评论，几乎所有的评论都是错误的，当你查看玩家的等级分时，几乎所有评论都来自那些在他们玩的每款游戏中等级都很低的玩家，他们似乎不懂如何玩好任何游戏。当然，如果有人乐在其中，玩得糟糕本身没有任何问题。这里的不协调在于，人们玩得糟糕，有一个明确定义的等级分表明他们玩得糟糕，却坚信自己玩得很好，并在人们指出他们提倡的策略无效时感到被冒犯。</p>

<h4 id="life-outside-of-games">游戏之外的生活</h4>

<p>在现实世界中，很少能得到像体育和棋盘游戏中那样清晰的证据来证明我们决策的质量。在做出工程决策时，你几乎从未拥有像棒球那样干净的数据，也从未拥有一个能基本准确总结你过去决策质量的Elo等级分。这使得在体育和棋盘游戏中根据反馈调整并做出好决策容易得多，然而，我们可以观察到，体育和棋盘游戏中的大多数决策都是糟糕的。在体育运动中，这基本上一直是事实，尽管涉及巨额资金；在棋盘游戏中也是如此，尽管人们对它们非常投入，似乎非常重视。</p>

<p>如果我们思考我们所审视的棒球决策的一般版本，发生的情况是决策具有概率性收益。实际结果（胜负）方差非常高，因此可能做出好决策却很久看不到直接效果。即使有指标能让我们更好地了解决策的“真实”价值，如果你在一个管理层不相信这些指标的环境中运作，如果你想做一些激进的事情，而其价值只能通过某些听起来深奥的指标来证明，那么你要保住工作（或一开始就找到工作）就会很困难，除非他们给你一两年机会。在指标被接受方面确实发生过一些重大转变，但这需要几十年时间。</p>

<p>如果我们审视商业或工程决策，情况就复杂得多。如果我们将产品或基础设施的成功视为“胜利”，那么团队能否获得“胜利”似乎存在更多噪音。而且，与棒球不同，那种能让某人分析“胜利”和“失败”以确定根本原因的逐场比赛甚至比赛数据并未被记录，因此无法确定决策的真实价值。即使数据可用，决定某件事是否是“胜利”的因素也多得多，即使我们有数据，也不清楚我们能否确定决策的预期价值。</p>

<p>我们已经看到，在一个人们能坐下来确定决策预期价值的领域，这类分析可能需要几十年才能影响一些重要决策。如果我们审视那些更难确定决策真实价值的领域，我们应该期望多久才能让“好”的决策浮现出来？似乎需要很长时间，也许是永远，除非棒球和其他运动的结构使得替换糟糕决策者、引入更好决策者特别困难。</p>

<p>有人可能会说棒球不同，因为球队数量固定，新球队进入市场非常罕见，但如果你看看公共云、操作系统、搜索引擎、汽车制造商等情况，看起来差别并不大。实际上，接管一支棒球队并更换管理层似乎便宜得多（有时棒球队售价约为10亿美元），而且在美国，我们刚才讨论的市场中，棒球队的数量也比有竞争力的产品数量多。也有人可能会说，如果你看棒球队的结构，很明显职位通常不是基于决策能力来分配的，<a href="https://thezvi.wordpress.com/2017/10/29/leaders-of-men/" rel="noopener noreferrer">其他因素往往占主导</a>，但这在棒球领域似乎并不比在工程领域更明显。</p>

<p>这并不是说我们期望到处都有明显糟糕的决策。如果你在<a href="https://www.amazon.com/gp/product/0393324818/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0393324818&amp;linkId=3ec395b394d2f8caaa30df2b2fc15d5d" rel="noopener noreferrer">《点球成金》</a>出版前后（以及之后相当长一段时间）混迹于棒球统计极客论坛，你可能会有这种印象，但如果你看同时期的一级方程式赛车（F1），你会看到车队雇佣拥有经济学和博弈论专长的博士来确保他们做出合理的决策。这并不意味着F1车队总是做出完美决策，但至少他们避免了做出几十年来有兴趣的业余爱好者都能识别为低效的决策。有些领域竞争激烈，你必须进行严格分析才能生存，而有些领域竞争则较为平缓。在有生之年，<a href="https://repositories.lib.utexas.edu/bitstream/handle/2152/17760/bournen.pdf?%852" rel="noopener noreferrer">曾几何时，训练体育项目被认为是不绅士的</a>，任何使用类似现代训练技术训练的人都将拥有巨大优势。在过去的十年左右，我们看到了类似的转变，但这次是棒球中的统计技术，而不是各种运动的训练。</p>

<p>如果我们想审视决策质量，简单地说我们期望一家公司做出好决策是因为他们暴露在市场中，做出好决策有经济价值，公司内部的人如果做出好决策很可能会获得丰厚回报，这是过于简单化的。你甚至无法通过询问人们是否正在做严格、数据驱动的决策来判断这是否正在发生。如果你问棒球界的人他们是否在决策中使用数据，他们在70年代和80年代都会说是的。棒球长期以来一直被称为人们跟踪各种数字然后使用这些数字的运动。只是人们没有回测他们的预测，更不用说用保留数据回测他们的预测了。</p>

<p><a href="https://www.uky.edu/~eushe2/Pajares/Kuhn.html" rel="noopener noreferrer">范式转变</a>——有效地使用数据驱动决策——在过去几十年里以不同的速度冲击着不同领域，包括运动领域内外。为什么这种变化在F1发生得比在棒球早，是由于F1车队与棒球队在激励结构上的差异以及<a href="https://danluu.com/wat/" rel="noopener noreferrer">机构文化</a>的差异。我们可能会在未来的一篇文章中探讨这个问题，但这被证明是一个相当复杂的问题，需要更多的背景知识。</p>

<p>纵观全局，我们可以将这个杯子视为半空（哇，人们在做出他们认为非常重要的简单决策时表现糟糕，那么他们在做非简单决策时肯定糟糕透顶），或者将杯子视为半满（哇，你可以在很多地方找到改进的好机会，甚至在那些人们声称由于<a href="https://danluu.com/cocktail-ideas/" rel="noopener noreferrer">经济学101推理</a>如“他们一定在做出正确决策因为他们被高度激励”而可能很难的领域，这可能会让人误以为没有容易的机会）。</p>

<h3 id="appendix-non-idealities-in-our-baseball-analysis">附录：我们棒球分析中的非理想因素</h3>

<p>为了使这是一篇简短的博客文章而不是一本书，我们讨论的近似模型做了许多简化。一个主要的简化是所有跑分都等值的观点。这足够接近事实，是一个不错的近似。但有些情况下近似效果不太好，例如当比赛进行到第九局且比分持平时。在这种情况下，增加得1分概率但降低得多分概率的决策实际上是正确选择。</p>

<p>这常被用作相对较晚局数牺牲触击的理由。但如果我们看成功牺牲触击的概率，会发现它在较晚局数会下降。我们没有讨论防守如何布置，但防守方可以采用降低成功牺牲触击概率但提高非触击成功概率的方式布置，反之亦然。在最后一局之前，这实际上使得较晚局数的牺牲触击更糟，而不是更好！如果我们考虑所有这些因素，在平局的最后一局，牺牲触击是否是好主意取决于我们尚未讨论的另一个因素——当前击球员。</p>

<p>在我们简化的模型中，我们计算了所有击球员的平均预期跑分。但在任何特定时间，是某个特定球员在击球。成功的牺牲触击推进跑垒员并增加一个出局。替代方案是让击球员“自由挥棒”，这将导致某种随机结果。击球员越好，出现比牺牲触击结果更好的结果的概率就越高。要确定最优决策，我们不仅需要知道当前击球员有多好，还需要知道后续击球员有多好。支持牺牲触击的一个常见理由是投手是糟糕的击球手，而且因为他们是糟糕的击球手，他们练习牺牲触击很多，所以他们做得不错，但事实证明投手也是低于平均水平的牺牲触击者，如果我们详细看数据，认为我们应该期待投手牺牲触击因为他们是糟糕击球手的论点站不住脚。</p>

<p>另一个牺牲触击（或一般触击）的原因是，有时这样做的倾向会引发防守变化，使非触击进攻效果更好。</p>

<p>完整的计算还应考虑当前击球员的球数和坏球数（这是我们完全没有讨论过的状态）、击球手和垒上球员的速度、比赛所在的具体球场、对方投手以及他们防守的质量。所有这些都可以做到，即使在笔记本电脑上——就计算机而言，这都是“小数据”，但即使对一个特定决策进行分析，其细节也会比本文所有内容（包括这份免责声明）长得多。考虑到所有这些非理想因素并没有推翻一般结果，这或许有点令人惊讶，但事实证明它没有（它发现牺牲触击在许多情况下具有正预期价值，但牺牲触击仍被过度使用了几十年）。</p>

<p>故意四坏球保送的情况类似，我们分析中的非理想因素似乎支持投出故意四坏球。特别是，支持故意四坏球保送的两个主要传统理由是：</p>

<ol>
<li>通过保送当前击球员，我们可以制造“强迫进垒”或“双杀”的机会（增加一局或两局出局的概率）。如果比赛在最后一局平分，让另一名球员上垒几乎没有坏处，却有增加不让任何跑者得分并维持平局的概率的好处。</li>
<li>通过保送当前击球员，我们可以让下一个、更差的击球员上场。</li>
</ol>

<p>人们应用第(1)条理由的一个例子是在<code>{一垒, 三垒; 2出局}</code>状态。防守方如果三垒的球员推进一个垒包就会输球。推理是，通过保送一名球员将状态变为<code>{一二三垒; 2出局}</code>，并不会增加三垒球员得分结束比赛的概率（如果当前击球员“将球打进场内”），而让另一名球员上垒增加了防守方能够取得出局的概率。</p>

<p>这个推理的漏洞在于击球员不一定会将球打进场内。在状态变为<code>{一二三垒; 2出局}</code>后，投手可能会投出非故意四坏球，导致每名跑者推进一个垒包并输掉比赛。事实证明，处于这个状态并不会非常影响非故意四坏球的概率。投手非常努力地避免四坏球，但与此同时，击球员也非常努力地想引诱四坏球！</p>

<p>关于第(2)条，这种理由通常被应用的两种情况是当前击球员优秀或卓越，或者当前击球员紧挨着投手之前击球。让我们分别看看这两种情况。</p>

<p>巴里·邦兹2001、2002和2004赛季是历史上统计意义上最好的赛季之一，也是现代棒球中能找到的最极端案例。如果我们进行同样的分析并考虑邦兹之后击球球员的质量，我们发现对方球队有时确实应该故意四坏球保送邦兹，但在大多数情况下没有必要故意四坏球保送，而且邦兹经常在没有必要的情况下被故意四坏球保送。对于一个没有打出现代棒球历史上统计意义上最佳赛季的击球员来说，故意四坏球保送甚至更不合理。</p>

<p>对于投手击球的情况，进行类似的分析也表明，在某些情况下故意四坏球保送是合适的（非晚局，{一二垒; 2出局}，当投手不是显著高于平均水平的投手击球手时）。即使故意四坏球保送并非总是错误的决策，但它仍然被严重过度使用了。</p>

<p>有人可能会争辩说，我们的简单分析存在所有这些可能推翻分析的非理想因素，是棒球决策并非那么糟糕的迹象，但我认为这站不住脚。一个能在一两小时内完成的一阶近似发现，平均而言决策似乎相当糟糕。如果一支球队对数据分析感兴趣，这应该引导他们进行更详细的分析，考虑基于传统智慧对显而易见的一小时分析的批评。似乎这并未被做到，至少几十年没有。</p>

<p>问题在于，在人们开始运行数据之前，我们只能依靠故事。有人会说“在2出局时，你应该保送投手之前的击球员，让投手上场[在某些情况下]让投手上场并取得确定的出局”。另一个人可能会回应“我们在晚局显然不应该这样做，因为投手会被代打换下，而在早局我们不应该这样做，因为即使有效并取得轻松出局，也给对方球队在下一局开局用他们的1号击球手而不是一个轻松出局创造了条件”。哪个故事是正确的故事，最终是一个实证问题。我发现最不幸的是，在人们开始运行数据、争论变成故事与数据之争之后，人们仍然坚持基于故事的论点长达数十年。我们在商业和工程领域也看到同样的情况，但在那里可以说更情有可原，因为那些领域的决策往往更难量化。即使你能把某件事简化为一个简单的工程方程，总有人可以争辩说工程决策并非真正重要，而其他难以量化的商业考量才是最重要的。</p>

<h3 id="appendix-possession">附录：控球权</h3>

<p>我发现一件有趣的事是，在橄榄球、棒球和篮球中的统计分析表明，几十年来球队一直严重低估了控球权的价值。棒球本身没有控球的概念，但如果你把进攻视为“拥有控球权”，将累计3个出局视为“失去控球权”，那就非常相似。</p>

<p>在橄榄球中，我们看到<a href="https://eml.berkeley.edu/~dromer/papers/JPE_April06.pdf" rel="noopener noreferrer">保持控球权</a>如此重要，以至于在第四档弃踢通常是一个错误，但这并没有阻止球队基本上一直默认弃踢。在篮球中，那些出手很多但命中率很低的球员被高估了（或者可以说现在仍然如此）。</p>

<p>我认为这不是根本性的——控球权如此有价值源于每项游戏的规则。这是武断的。不过，我仍然觉得这很有趣。</p>

<h3 id="appendix-other-analysis-of-management-decisions">附录：对管理决策的其他分析</h3>

<p><a href="https://people.stanford.edu/nbloom/sites/default/files/dmm.pdf" rel="noopener noreferrer">布鲁姆等人的《管理重要吗？来自印度的证据》</a>审视了管理干预的影响以及对生产力的效果。</p>

<p><a href="https://people.stanford.edu/nbloom/research" rel="noopener noreferrer">布鲁姆的其他工作</a>。</p>

<p><a href="http://web.stanford.edu/~gentzkow/research/uniform-pricing.pdf" rel="noopener noreferrer">德拉维尼亚等人的《美国零售连锁店的统一定价》</a>声称发现零售连锁店留下了大量未实现的利润（利润的百分之七），并探讨了可能的原因及其影响。</p>

<p>这类工作与体育工作相比的好处在于，它试图量化人为游戏之外事物的影响。缺点是研究对象相当混乱，很难弄清楚研究到底意味着什么。例如，如果你看关于创新的研究，经济学家通常用专利作为创新的代理指标，然后基于某个变量与专利数量得出结论。但如果你熟悉工程专利，你就知道专利数量是创新非常差的代理指标。在硬件领域，IBM以产出大量无用专利而闻名（既指对创新无用，也指在专利诉讼中作为反击无用），而有些公司提交的专利数量少得多却获得了更大的效益。</p>

<p>据我所知，我们在这里的选择是要么了解一个在某种意义上完全无关的背景中的大量决策，要么拥有模糊的信息，并且可能对一个看似与现实世界相关的背景知之甚少。我很乐意听到更多属于任一阵营的研究（或者更好的是，没有这两种问题的研究）。</p>

<p><i>感谢莉亚·汉森、大卫·特纳、米沃什·丹、安德鲁·尼科尔斯、贾斯汀·布兰克、@hoverbates、凯特·墨菲、本·科恩、帕特里克·科利森以及一位匿名评论者的评论/更正/讨论。</i></p><p><em>由 mimo-v2.5 模型翻译，花费 22218 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/bad-decisions/</link>
      <guid isPermaLink="false">https://danluu.com/bad-decisions/</guid>
      <pubDate>Tue, 21 Nov 2017 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Android设备过时程度如何？</title>
      <description>[AI 摘要] 文章通过数据分析指出Android设备日益过时，这给开发者带来挑战并导致大量设备面临安全风险。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章通过数据分析指出Android设备日益过时，这给开发者带来挑战并导致大量设备面临安全风险。</div><p>众所周知，与iOS设备相比，Android设备往往更容易过时，但这究竟意味着什么？让我们查看Android市场份额数据，了解市面上设备的实际“年龄”。下方图表的X轴为日期，Y轴为Android市场份额，所有设备份额总和为100%（由于Google提供的公开数据精度较低，存在部分误差）。</p>

<p>颜色代表设备“年龄”：</p>

<ul>
<li>蓝色：当前版本（API主版本）</li>
<li>黄色：6个月</li>
<li>橙色：1年</li>
<li>深红色：2年</li>
<li>亮红色/白色：3年</li>
<li>浅灰色：4年</li>
<li>灰色：5年</li>
<li>黑色：6年及以上</li>
</ul>

<p>观察图表可见多条反S型曲线，从左至右每两条曲线之间设备逐渐老化。每条曲线对应一个新Android版本的发布及运行该版本的设备群体。随着时间推移，运行该版本的设备老化。当设备升级时，它会从一条曲线转移到另一条曲线，颜色变为较新的状态。</p>

<p><img src="https://danluu.com/images/android-updates/android-age.svg" width="1152" height="1152" alt="过时的Android设备市场份额正在增加"></p>

<p>该图表在三个方面低估了过时设备的数量：</p>

<p>第一，此处使用API版本数据，无法获取小版本更新的市场份额，因此假设同一API版本的设备在新API发布前均为最新状态。但许多（或许是大多数）设备不会在API版本内接收更新。</p>

<p>第二，此图表显示市场份额，但Android设备总量随时间急剧增长。例如，若观察第80百分位最过时设备（即从底部向上20%处划线），今日第80百分位设备比2014年同期过时数月。Android的巨大增长意味着如今过时设备数量远超2014年。</p>

<p>第三，数据来自抓取Google Play商店的市场份额信息，反映的是最近7天内访问Play商店的设备。通常，访问Play商店的设备比未访问的设备更可能保持更新，因此数据存在未知偏差，使图表显示的设备比实际更“新”。无论是传统移动设备还是取代传统嵌入式设备、POS终端等的移动设备，这似乎都合理。</p>

<p>从安全角度看，部分设备会在不升级主版本的情况下接收安全更新，这会使日期看起来比实际更过时。然而，研究人员使用更细粒度数据研究设备更新情况时发现，<a href="https://www.cl.cam.ac.uk/~drt24/dissertation.pdf" rel="noopener noreferrer">此效应并不显著</a>。</p>

<p>图表显示，随着时间推移，世界积累了更大比例的老旧设备。这符合直觉，无需数据也能推断——2010年初Android手机使用年限难以超过一年，而现在已有可能存在使用近十年的Android设备。</p>

<p>若不观察数据，一个不易察觉的现象是新版本采用速度似乎正在放缓——观察图表右上角最近几条曲线（对应最新Android版本）可见，其斜率较之前版本更平缓。仅凭现有数据无法判断斜率变缓的原因，可能包括：</p>

<ul>
<li>Android增长放缓</li>
<li>Android设备更替（升级速度）减慢</li>
<li>获得更新的设备减少</li>
</ul>

<p>缺乏更多数据的情况下，无法确定各因素的贡献程度。另外，<a href="https://twitter.com/danluu/" rel="noopener noreferrer">如您知道追溯至2010年的Android活跃设备合理数据来源</a>，请告知！我希望能制作配套图表展示过时设备总数。</p>

<p>但即便基于现有数据，我们仍可推测当前使用中的过时设备数量。2017年5月，Google宣布Android活跃设备超过20亿。查看最新统计数据（图表最右侧），可见近半数设备已过时两年。此时我们应预期有超过十亿设备过时两年！鉴于Android的更新模式，其中约0%设备可能升级至现代Android版本。</p>

<h4 id="percentiles">百分位数</h4>

<p>由于图表信息密集，观察局部可能更清晰。若观察单条水平线，它对应特定百分位的设备“年龄”：</p>

<p><img src="https://danluu.com/images/android-updates/android-percentile-age.svg" width="1152" height="1152" alt="随着时间推移，第N百分位过时设备正变得更加过时"></p>

<p>本图中X轴为日期，Y轴为“年龄”（月）。每条线对应不同百分位（百分位越高越老），即顶部图表在该百分位的水平切片。</p>

<p>每条曲线大致呈现两个阶段（另含其他细节）。第一阶段设备“年龄”随时间快速增长；第二阶段设备平均仅略微老化。第二阶段中，新版本发布有时会将较新设备推入该百分位，使设备“年龄”减小，但频率不足以抵消普遍老化趋势。整体而言，若当前趋势持续，未来过时Android设备比例将更高，这与首个更复杂的图表显示一致。</p>

<h4 id="dates">日期视角</h4>

<p>另一种观察方式是查看垂直切片而非水平切片。每条垂直线对应特定日期所有设备的“年龄”：</p>

<p><img src="https://danluu.com/images/android-updates/android-age-over-time.svg" width="1152" height="1152"></p>

<p>本图X轴为“年龄”百分位，Y轴为原始“年龄”（月）。每条线代表一个日期，日期越早颜色越浅/黄，日期越新颜色越深/绿。</p>

<p>与同数据的其他视图一样，可见Android设备似乎随时间变得更加过时。若绘制所有可用日期的数据，图表将过于密集，但我们可通过动画查看：</p>

<p><img src="https://danluu.com/images/android-updates/android-age.gif" width="480" height="480"></p>

<h4 id="ios">iOS对比</h4>

<p>作为参考，iOS 11发布于两个月前，尽管11月数据发布于iPhone X上市之前，其已占据近50% iOS市场份额（而最新Android版本8月发布时份额不足1%）。极有可能在明年初iOS 11市场份额将超过50%，甚至可能达到75%，即iOS对应图表中第50百分位（红色）线在“年龄=0”处，第75百分位（橙色）线偶尔降至0也并非不可能。与Android类似，部分旧设备固执地拒绝更新；发布于两年多前的iOS 9.3目前份额略高于5%。这意味着在iOS图表中，第99百分位（绿色）线可能处于略高于两年的位置（仅为Android图表对应值的一半）。</p>

<h4 id="windows-xp">Windows XP对比</h4>

<p>人们有时将Android与Windows XP相比，因两者在市面上都有大量设备且多数不再获得安全更新。但这对Windows XP极不公平，它发布于2001年10月，直至2014年4月仍在提供安全更新，持续十二年半。此外，微软在官方支持期后仍发布了至少一次安全更新（2017年5月针对WannaCry勒索软件的更新）。尽管微软决定在仍有大量XP设备在用时终止支持，但支持旧操作系统超过十二年并在十五年后发布紧急安全补丁，使微软在设备支持方面与Google和Apple处于完全不同的层次。</p>

<p>Android与Windows的另一区别在于Android的规模在桌面领域前所未有。2017年PC销量约2亿台。仅三星自2008年起年销量即达此数。当然，2008年这些并非Android设备，但Android在非iOS移动领域的主导地位意味着总体上它们主要是Android设备。如今，<a href="https://twitter.com/danluu/status/864194210752344064" rel="noopener noreferrer">我们仍可见近50年历史的PDP-11设备在用</a>。PDP设备数量稀少，遇到一台是有趣而古雅的惊喜（共售出60万台）。台式设备比PDP更早淘汰，移动设备淘汰更快，但现代计算设备的普及导致数量差异巨大，意味着50年后我们将看到更多XP时代PC在用，而移动设备在50年后可能更多。许多古老的PDP、VAX、DOS等设备因非联网配置而基本安全，但许多将服役数十年的老旧XP和Android设备情况似乎并非如此。</p>

<h3 id="conclusion">结论</h3>

<p>我们已看到Android设备似乎随时间变得更加过时。这使开发者难以针对“新”Android API特性开发（“新”指过去几年引入的特性）。这也意味着<em>大量</em>Android设备在安全方面滞后，绝对数量和相对iOS均如此。</p>

<p>直到最近，Android仍与硬件紧密绑定，导致更新旧设备极为困难，需要针对手机特定（或至少SoC特定）工作进行定制Android构建。<a href="https://android-developers.googleblog.com/2017/05/here-comes-treble-modular-base-for.html" rel="noopener noreferrer">Google声称此问题已在最新Android版本（8.0，Oreo）中解决</a>。记得Google <a href="https://www.google.com/search?client=ubuntu&amp;hs=xVc&amp;channel=fs&amp;ei=laMJWombGJSvjwSmu5TQCA&amp;q=android+update+alliance&amp;oq=android+update+alliance&amp;gs_l=psy-ab.3..35i39k1l2.41466.41684.0.42014.2.2.0.0.0.0.111.193.1j1.2.0....0...1.1.64.psy-ab..0.2.192...0i22i30k.1.0.SNY-JcWhD9M" rel="noopener noreferrer">2011年“Android更新联盟”公告</a>的人可能对近期公告持怀疑态度。2011年，Google与美国运营商宣布将保持设备更新18个月，<a href="https://www.pcmag.com/article2/0,2817,2397729,00.asp" rel="noopener noreferrer">但多数未兑现</a>。然而，即使当前公告非空谈且最新Android版本解决了更新问题，我们也看到Android版本采用需数年，且最近几版Android采用速度明显慢于以往。此外，尽管这应使更新更便捷，Android在更新方面可能仍将落后iOS一段时间。Google承诺其最新手机（Pixel 2，2017年10月）将获得三年更新。这似乎是进步，但从上图可见，延长一年支持远不足以保持大多数Android设备更新。但若您使用iPhone，最新iOS版本（2017年9月发布）支持回溯至iPhone 5S（2013年9月发布）。</p>

<p>查看最新Android版本（8.0，2017年8月发布），若您使用两年机龄设备仍能获得更新则相当幸运。获得支持的最老“Google”手机为Nexus 6P（2015年9月发布），支持期近两年。</p>

<p>回溯至与iPhone 5S同期发布的设备，情况更糟。当时我在Google工作获得一部Moto X，该机是当时最接近官方Google手机的设备（当时Google拥有Moto）。Moto X发布于2013年8月（比iPhone 5S早一个月），其支持的最新Android版本为5.1，发布于2015年2月，仅一年半后。对于同期的Android手机，Moto X支持期异常长。随着时间推移情况看起来更糟是个好兆头，但按当前改善速度，还需要数年才能出现支持良好的Android设备，而该Android版本广泛采用又需数年。可能<a href="https://lwn.net/Articles/718267/" rel="noopener noreferrer">Fuchsia</a>能解决此问题，但Fuchsia距广泛采用仍需多年。</p>

<p><a href="https://danluu.com/input-lag/" rel="noopener noreferrer">在后续文章中，我们将探讨Android响应延迟</a>，其远高于iPhone和iPad延迟。</p>

<p><i>
感谢Leah Hanson、Kate Murphy、Daniel Thomas、Marek Majkowski、@zofrex、@Aissn、Chris Palmer、JonLuca De Caro及一位匿名人士的评论/修正/相关讨论。</i></p><i>

</i><p><i>同时感谢Victorien Villard提供这些图表所依据的数据！
</i></p><p><em>由 mimo-v2.5 模型翻译，花费 6857 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/android-updates/</link>
      <guid isPermaLink="false">https://danluu.com/android-updates/</guid>
      <pubDate>Sun, 12 Nov 2017 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>UI向后兼容性</title>
      <description>[AI 摘要] 文章讨论了UI向后兼容性的重要性，以及破坏它如何损害用户体验，并提出了改进方法。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章讨论了UI向后兼容性的重要性，以及破坏它如何损害用户体验，并提出了改进方法。</div><p>大约每个月一次，我常用的应用程序会改变其用户界面，破坏肌肉记忆，基本上诱使用户做他们不想做的事情。</p>

<h3 id="zulip">Zulip</h3>

<p>在近期的记忆中，Zulip（一个Slack竞争对手）更改了其换行行为，使得<code>ctrl + enter</code>发送消息而不是插入换行。在这一更改后，我发送了一些半成品消息，看起来其他一些人也这样做了。</p>

<p>大约在他们做出这一更改时，他们还做了另一个更改，导致一系列点击原本会向某人发送私信，现在却会向在线人员中字母顺序排列的第一位发送私信。大多数人没有注意到这是一个变化，但当我提到过去几周我发生了几次这种情况时，多人立即表示他们完全相同的事情也发生了。有些人还提到导航快捷键的行为被更改，可能导致人们广播消息而不是发送私信。在两种情况下，有些人都责怪自己，不知道为什么他们开始犯错误，导致他们将消息发送到错误的地方。</p>

<h3 id="doors">门</h3>

<p>不久前，我在Black Seed Bagel，那里有一扇门，<a href="https://www.google.com/search?tbm=isch&amp;source=hp&amp;biw=1739&amp;bih=1689&amp;ei=ADIFWq7SLcTPmwH8g6y4Dg&amp;q=black+seed+bagel&amp;oq=black+se&amp;gs_l=img.3.0.35i39k1l2j0l8.761.2250.0.3272.9.9.0.0.0.0.165.806.4j4.8.0....0...1.1.64.img..1.8.805.0...0.cicb-SDN7SU#imgrc=zwf_w60g8r1uWM:" rel="noopener noreferrer">从两面看都75%像一扇“推”门</a>，但实际上从外面是推门，从里面是拉门。另一个让它看起来更像从里面“推”门的线索是，大多数企业都有向外开的门（在美国，当房间容量超过50时，出口门必须这样，许多小空间的企业也自愿遵循相同的惯例）。在大约一小时的谈话过程中，我看到很多人进进出出，我猜有十个人在第一次尝试出门时都失败了。当人们成对或成组旅行时，前面的人通常会说“我很傻。我们一分钟后刚用过这扇门”。但人们实际上并不傻。如果有什么是傻的，那就是设计这样的门，让用户必须记住哪些门像“正常”门一样，哪些门的提示被反转了。</p>

<p>如果你对物理世界感兴趣，<a href="https://www.amazon.com/gp/product/0465050654/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0465050654&amp;linkId=671c3ae6f7143212c71afe93c7c66951" rel="noopener noreferrer">《设计心理学》</a>提供了许多现实世界的例子，其中用户被微妙地推动去做错误的事情。它还讨论了通用原则，让你能看到一般想法，并在设计软件时应用和避免相同的问题。</p>

<h3 id="facebook">Facebook</h3>

<p>上周，Facebook更改了界面，以至于我正常点击序列来隐藏故事现在会保存故事而不是隐藏它！保存与隐藏几乎是相反的！从用户角度来看，以及作为提要排名的信号，都是相反的。这种更改的真正“伟大”之处在于，如果你通过点击次数来衡量新功能的“参与度”，它在A/B测试中表现非常好，因为许多用户会意外保存他们本想隐藏的故事。今年早些时候，Twitter做了类似的事情，交换了“时刻”和“通知”的位置。</p>

<p>即使进行更改的人没有为了提高参与度数字而创建这个棘手的界面，这种更改仍然有问题，因为它毒化了分析数据。虽然从技术上讲可以构建一个模型来区分意外点击和有意点击，但这很少见（我不知道有任何A/B测试这样做），即使在用户显然会意外触发操作的情况下，我仍然看到开发人员和产品经理根据原始统计数字（如<a href="https://en.wikipedia.org/wiki/Daily_active_users" rel="noopener noreferrer">DAU</a>/MAU）来证明功能的合理性。</p>

<h3 id="api-backwards-compatibility">API向后兼容性</h3>

<p>当涉及软件API时，有一种观点认为，对于某些广泛使用的软件类别，你永远不应该破坏向后兼容性。一个著名的例子是<a href="http://lkml.iu.edu/hypermail/linux/kernel/1710.3/02487.html" rel="noopener noreferrer">Linus Torvalds</a>：</p>

<blockquote>
<p>人们应该基本上总能感觉他们可以更新内核而根本不用担心它。</p>

<p>我拒绝引入“只有当你同时更新另一个程序时才能更新内核”的限制。如果内核曾经为你工作，规则就是它继续为你工作。
……
我见过，并且可以指出，很多项目说“我们需要为了进展而破坏那个用例”或者“你依赖了未记录的行为，那是你的问题”或者“有更好的方法做你想做的事，你必须更改到那个新的更好的方法”，而我根本认为这在非常早期的alpha版本之外是不可接受的，那些实验用户知道他们签了什么。内核在过去的二十年里没有处于那种情况。
……
我们在内核<em>内部</em>经常进行API破坏。我们会通过说“你现在需要做XYZ”来修复内部问题，但那是关于内部内核API的，然后那些做这个的人显然也必须修复该API的所有内核内部用户。没有人能说“我现在破坏了你使用的API，现在<em>你</em>需要修复它”。破坏了某事的人也必须修复它。
……
而我们根本不破坏用户空间。</p>
</blockquote>

<p><a href="https://blogs.msdn.microsoft.com/oldnewthing/20031223-00/?p=41373" rel="noopener noreferrer">Raymond Chen引用Colen的话</a>：</p>

<blockquote>
<p>从客户的立场来看这个场景。你购买了程序X、Y和Z。然后你升级到Windows XP。你的计算机现在随机崩溃，程序Z根本不工作。你会告诉你的朋友，“不要升级到Windows XP。它随机崩溃，而且与程序Z不兼容。”你会调试你的系统来确定程序X导致崩溃，并且程序Z不工作是因为它使用了未记录的窗口消息吗？当然不会。你会退还Windows XP盒子退款。（你几个月前购买了程序X、Y和Z。30天退货政策不再适用于它们。你唯一能退还的是Windows XP。）</p>
</blockquote>

<p>虽然这种观点是少数派，但它是有影响力的少数派。很少听到这种针对UI向后兼容性的论点。你可能会争辩说这没关系——如今人们被迫升级，所以东西坏了也没关系。但即使用户无法逃避，这仍然是糟糕的用户体验。</p>

<p>对这种观点的反对意见是，维护兼容性会产生技术债务。确实如此！例如，Linux充满了由于“不破坏用户空间”规定而略有或适度错误的API。一个例子是<a href="http://man7.org/linux/man-pages/man2/recvmmsg.2.html" rel="noopener noreferrer"><code>int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, unsigned int flags, struct timespec *timeout);</code></a>。你可能期望如果没收到数据包，超时会触发，但手册页写道：</p>

<blockquote>
<p><em>timeout</em>参数指向一个<em>struct timespec</em>（参见clock_gettime(2)），为接收操作定义超时（秒加纳秒）（<em>但参见BUGS！</em>）。</p>
</blockquote>

<p>BUGS部分写道：</p>

<blockquote>
<p><em>timeout</em>参数不按预期工作。超时仅在每个数据报接收后检查，因此如果在超时过期前收到多达<em>vlen-1</em>个数据报，但之后没有收到更多数据报，调用将永远阻塞。</p>
</blockquote>

<p>这可以说甚至不是<code>recvmmsg</code>最糟糕的错误特性，<a href="http://www.openwall.com/lists/musl/2014/06/07/5" rel="noopener noreferrer">它返回一个<code>ssize_t</code>到大小为<code>int</code>的字段中</a>。</p>

<p>如果你有“我们根本不破坏用户空间”这样的政策，这种技术债务将永远存在。但在我看来，世界上最广泛使用的桌面、笔记本电脑和服务器操作系统费尽心思维护向后兼容性并非巧合。</p>

<p>UI向后兼容性的论点可以说比API向后兼容性的论点更强，因为破坏性的API更改可以通过机械方式修复，并且<a href="//danluu.com/monorepo/" rel="noopener noreferrer">在适当的环境中</a>，所有调用者都可以在API更改的同时修复。没有等效的方法可以深入人们的脑海并改变用户习惯，因此破坏性的UI更改不可避免地会给某些用户带来痛苦。</p>

<p>UI向后兼容性的论点可以说比API向后兼容性的论点更弱，因为API向后兼容性的成本较低——如果某些API有问题，你可以创建一个新的API，然后将旧API记录为不应使用的东西（如果你查看Linux系统调用，你会看到很多这样的例子）。这在GUI中并不真正有效，因为UI元素为少量的屏幕空间而竞争。我认为一个被低估的论点是，改变UI并不像大多数公司认为的那样伟大——非常过时的UI，没有更新以跟上趋势，也可以成功（例如，plentyoffish和craigslist）。公司甚至可以在没有任何重大UI更新的情况下取得巨大成功，更不用说UI重新设计了——LinkedIn火箭般增长的很大一部分发生在UI基本上冻结的时期。我被告知冻结UI不是有意的设计决定；相反，它是严重技术债务的副作用，并且UI在重写让人们可以自信地更改UI时被解冻。LinkedIn在解冻前端后成功地<a href="https://darkpatterns.org/" rel="noopener noreferrer">添加了很多暗模式</a>，但以前的UI在增长方面似乎运行良好。</p>

<p>尽管有一些UI不总是更新以跟踪最新趋势并取得了成功，但在大多数公司，基本上不可能提出不应该随意更改UI而不添加功能的论点，更不用说提出UI不应该用新功能替换旧功能的论点了。</p>

<h3 id="ui-deprecation">UI弃用</h3>

<p>一个可能更容易提出的论点是，快捷方式和类似快捷方式的UI元素可以在移除前被弃用，类似于API演变在进行破坏性更改前会添加弃用警告。与其定期更改UI以至于用户的肌肉记忆被用来对付他们并导致用户做与想要相反的事情，UI可以更改为先前训练的操作集导致什么也不发生。例如，Facebook可以将“隐藏帖子”向下移动并在旧位置插入一个空操作项，然后在人们习惯于不在旧“隐藏帖子”位置点击“隐藏帖子”后，他们可以将“保存帖子”放在旧“隐藏帖子”位置。</p>

<p>Zulip可以做类似的事情，导致一系列曾经让你向想要的人发送私信的操作现在不发送消息，而不是向<code>在线</code>列表中字母顺序排列的第一位发送私信。</p>

<p>这些解决方案并不理想，因为用户仍然需要在新东西上重新训练肌肉记忆，但这仍然比当前情况好得多，在当前情况下，许多UI定期引入看似任意的变化，引发混乱和困惑。</p>

<p>在某些情况下（例如，空操作菜单项），这对新用户来说呈现了一个相当奇怪的界面。用户不期望看到一个什么也不做的菜单项，箭头说点击菜单的其他地方。这可以通过仅向经常使用旧快捷方式或类似快捷方式路径的用户推出弃用“警告”来修复。如果有多个更改被弃用，这会导致组合爆炸，但如果你定期弃用多个独立项目，那相当极端，用户无论如何都会感到困惑。考虑到<a href="https://twitter.com/danluu/status/891508449414197248" rel="noopener noreferrer">避免用户敌对更改所付出的努力</a>以及“快速行动，打破常规”思维模式的主导地位，仅仅为了避免给用户带来糟糕体验而添加这种复杂性的论点在大多数公司可能站不住脚，但这至少在原则上似乎可行。</p>

<p>打破现有用户工作流程对于像Facebook这样的应用程序可能无关紧要，因为它在其领域中的主导地位使其相对粘性，但大多数应用程序更像Zulip而不是Facebook。当Zulip和Slack都还年轻时，Zulip消息无法编辑或删除。这是有意的——消息是不可变的，我认识的每个建议允许编辑的人都被否决了，因为可变消息不符合不可变模型。当时，如果有一个UI更改或错误导致用户意外发送公开消息而不是私信，那基本上是永久性的。我看到人们意外发送公开消息的频率足以让我养成将私信对话转移到另一个媒介的习惯。这并没有太困扰我，因为我习惯了<a href="https://danluu.com/everything-is-broken/" rel="noopener noreferrer">古怪的软件</a>，但我知道有些人当时尝试了Zulip，至今仍拒绝使用Zulip，因为那时遇到的UI问题。那是一个极端的例子，但用户倾向于避免给他们带来反复痛苦的应用程序这一普遍想法并不牵强。</p>

<p>在用户保留研究中，似乎额外的500毫秒页面加载延迟会负面影响保留率。如果是这样，切换UI以至于用户必须花费5秒钟撤销一个操作或以无法撤销的方式广播私信公开，应该对保留率有显著影响，尽管我不知道有任何公开研究对此进行调查。</p>

<h3 id="conclusion">结论</h3>

<p>如果我从事UI工作，我可能有一些建议或行动呼吁。但作为局外人，我谨慎提出实际建议——程序员似乎特别容易进入一个他们不熟悉的领域，告诉专家应该如何解决他们的问题。虽然这偶尔有效，但最可能的结果是，局外人要么重新发明了几十年来已知的东西，要么完全忽略了问题最重要的部分。</p>

<p>如果快捷方式不经常坏到我花和实际使用应用程序一样多的时间有意识地阻止自己使用它们，那当然会很好。但可能有一些原因使得测试/强制执行很困难。需要进行健壮UI测试的平台数量巨大，即使没有添加这种额外类型的测试，测试也很困难。而且，即使在讨论功能正确性问题时，“快速行动，打破常规”也比“尽量少打破东西”更时髦。由于UI“正确性”通常优先级甚至低于功能正确性，不清楚有人如何能成功提出投入更多努力的论点。</p>

<p>另一方面，尽管有这些免责声明，Google有时确实做了本文中描述的确切事情。Chrome最近移除了<code>backspace</code>后退功能；如果你按<code>backspace</code>，你会得到一条笔记告诉你使用<code>alt+left</code>代替，当地图前一阵子移动一些项目时，他们放置了空操作占位符指向新位置。这并不意味着Google总是做得很好——在2016年愚人节，Gmail将<code>发送并存档</code>替换为<code>发送并附加一个在某些上下文中冒犯的gif</code>——但这些例子表明，在重大更改中维护向后兼容性不仅仅是一个假设想法，它可以并且已经被做了。</p>

<p><i>
感谢Leah Hanson、Allie Jones、Randall Koutnik、Kevin Lynagh、David Turner、Christian Ternus、Ted Unangst、Michael Bryc、Tony Finch、Stephen Tigner、Steven McCarthy、Julia Evans、@BaudDev和一位反对公开致谢的匿名人士对评论/更正/讨论的贡献。</i></p><i>

<p>如果你好奇为什么“匿名”反对致谢，那是因为他们首先在Paul Graham的写作中看到这些，他的致谢是硅谷的谁是谁。匿名的看法是，这些列表作为一种信号传递。我不会说这是错的，但我在写作中得到了很多帮助，不仅来自阅读草稿的人，也来自偶尔有帮助的公共互联网评论，我认为重要的是要明确这不是一个人的努力来对抗<a href="https://www.bunniestudios.com/blog/?p=5046" rel="noopener noreferrer">Bunnie Huang所称的“偶像效应”</a>。</p>

</i><p><i>在未来的文章中，我们将探讨行长度如何影响可读性的实证研究。我阅读了我能找到的每项研究，但我可能漏掉了一些。如果你知道一项好研究你认为我应该包括，<a href="https://twitter.com/danluu/" rel="noopener noreferrer">请让我知道</a>。
</i></p><p><em>由 mimo-v2.5 模型翻译，花费 9185 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/ui-compatibility/</link>
      <guid isPermaLink="false">https://danluu.com/ui-compatibility/</guid>
      <pubDate>Thu, 9 Nov 2017 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>文件系统错误处理</title>
      <description>[AI 摘要] 本文通过错误注入测试比较了新旧文件系统的错误处理能力，发现现代文件系统大多能正确传播写错误，但仅btrfs能检测并纠正静默失败。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文通过错误注入测试比较了新旧文件系统的错误处理能力，发现现代文件系统大多能正确传播写错误，但仅btrfs能检测并纠正静默失败。</div><p>我们将复现大约十年前<a href="//danluu.com/file-consistency/" rel="noopener noreferrer">关于文件系统健壮性的论文中的一些结果</a>：<a href="http://research.cs.wisc.edu/wind/Publications/iron-sosp05.pdf" rel="noopener noreferrer">Prabhakaran等人SOSP 05论文</a>（在文件系统下层注入错误）和<a href="https://www.usenix.org/legacy/event/fast08/tech/full_papers/gunawi/gunawi_html/index.html" rel="noopener noreferrer">Gunawi等人FAST 08论文</a>（研究文件系统检查可能返回错误的函数返回码的频率）。</p>
<p><a href="http://research.cs.wisc.edu/wind/Publications/iron-sosp05.pdf" rel="noopener noreferrer">Prabhakaran等人</a>在块设备层（紧邻文件系统下层）注入错误，发现<code>ext3</code>、<code>reiserfs</code>、<code>ntfs</code>和<code>jfs</code>大多能合理处理读错误，但<code>ext3</code>、<code>ntfs</code>和<code>jfs</code>大多忽略了写错误。虽然这篇论文很有趣，但现在在系统上安装Linux的人更可能使用<code>ext4</code>而非Prabhakaran等人测试的那些已过时的文件系统。我们将尝试在更现代的文件系统如<code>ext4</code>和<code>btrfs</code>、一些传统文件系统如<code>exfat</code>、<code>ext3</code>和<code>jfs</code>以及<code>overlayfs</code>上复现该论文的一些基本结果。</p>
<p><a href="https://www.usenix.org/legacy/event/fast08/tech/full_papers/gunawi/gunawi_html/index.html" rel="noopener noreferrer">Gunawi等人</a>发现大多数错误未被检查。在查看现代文件系统的错误注入后，我们将看看文件系统的错误处理代码改善了多少（或多差）。</p>
<h3 id="error-injection">错误注入</h3>
<p>文件读取的简图可能是：<code>pread系统调用 -&gt; 操作系统通用文件系统代码 -&gt; 文件系统特定代码 -&gt; 块设备代码 -&gt; 设备驱动 -&gt; 设备控制器 -&gt; 磁盘</code>。磁盘收到请求后，将数据传回：<code>磁盘 -&gt; 设备控制器 -&gt; 设备驱动 -&gt; 块设备代码 -&gt; 文件系统特定代码 -&gt; 操作系统通用文件系统代码 -&gt; pread</code>。我们将查看在块设备层（文件系统正下方）注入错误的情况。</p>
<p>让我们看看2017年注入错误与Prabhakaran等人2005年的发现有何不同。</p>
<p>
</p><table>
<tbody><tr><th rowspan="3"></th><th colspan="3">2005</th><th colspan="6">2017</th></tr>
<tr>
<th>read</th><th>write</th><th>silent</th><th>read</th><th>write</th><th>silent</th><th>read</th><th>write</th><th>silent</th></tr>
<tr><th colspan="6">file</th><th colspan="3">mmap</th></tr>
<tr>
<th>btrfs</th><td colspan="3"></td><td>prop</td><td>prop</td><td>prop</td><td>prop</td><td>prop</td><td>prop</td></tr>
<tr>
<th>exfat</th><td colspan="3"></td><td>prop</td><td>prop</td><td>ignore</td><td>prop</td><td>prop</td><td>ignore</td></tr>
<tr>
<th>ext3</th><td>prop</td><td>ignore</td><td>ignore</td><td>prop</td><td>prop</td><td>ignore</td><td>prop</td><td>prop</td><td>ignore</td></tr>
<tr>
<th>ext4</th><td colspan="3"></td><td>prop</td><td>prop</td><td>ignore</td><td>prop</td><td>prop</td><td>ignore</td></tr>
<tr>
<th>fat</th><td colspan="3"></td><td>prop</td><td>prop</td><td>ignore</td><td>prop</td><td>prop</td><td>ignore</td></tr>
<tr>
<th>jfs</th><td>prop</td><td>ignore</td><td>ignore</td><td>prop</td><td>ignore</td><td>ignore</td><td>prop</td><td>prop</td><td>ignore</td></tr>
<tr>
<th>reiserfs</th><td>prop</td><td>prop</td><td>ignore</td><td colspan="6"></td></tr>
<tr>
<th>xfs</th><td colspan="3"></td><td>prop</td><td>prop</td><td>ignore</td><td>prop</td><td>prop</td><td>ignore</td></tr>
</tbody></table><p></p>
<p>每行显示一个文件系统的结果。<code>read</code>和<code>write</code>分别表示读取和写入数据，其中块设备返回错误表示操作失败。<code>silent</code>表示读取失败（数据不正确），但块设备未指示错误。这可能发生在磁盘损坏、瞬时读取失败或瞬时写入失败静默导致写入坏数据的情况下。<code>file</code>表示操作在通过<code>open</code>打开的文件上进行，<code>mmap</code>表示测试在通过<code>mmap</code>映射的文件上进行。<code>ignore</code>（红色）表示错误被忽略，<code>prop</code>（黄色）表示错误被传播且<code>pread</code>或<code>pwrite</code>系统调用返回错误码，<code>fix</code>（绿色）表示错误被纠正。未纠正任何错误。灰色条目表示未测试的配置。</p>
<p>从表中可以看出，2005年<code>ext3</code>和<code>jfs</code>在块设备指示写入失败时仍忽略写错误，而情况已改善，任何你可能使用的文件系统都能正确告知写入失败。<code>jfs</code>没有改善，但<code>jfs</code>现在在遗留安装外很少使用。</p>
<p>除<code>btrfs</code>外，其他测试的文件系统均未正确处理静默失败。其他测试的文件系统既不复制也不校验数据，因此无法检测静默失败。<code>zfs</code>可能也能正确处理静默失败，但未测试。尽管<code>apfs</code>出现晚于<code>btrfs</code>和<code>zfs</code>，但它明确决定不校验数据并在块设备静默错误时静默失败。我们将稍后讨论这一点。</p>
<p>在错误被传播的所有测试情况下，文件读写分别从<code>pread</code>或<code>pwrite</code>返回<code>EIO</code>；<code>mmap</code>读写导致进程接收<code>SIGBUS</code>信号。</p>
<p>上述2017年测试使用8k文件，其中包含文件数据的第一个块在块设备层返回错误或被损坏（取决于测试）。下表测试相同内容，但使用445字节文件而非8k文件。选择445是任意的。</p>
<table>
<tbody><tr><th rowspan="3"></th><th colspan="3">2005</th><th colspan="6">2017</th></tr>
<tr>
<th>read</th><th>write</th><th>silent</th><th>read</th><th>write</th><th>silent</th><th>read</th><th>write</th><th>silent</th></tr>
<tr><th colspan="6">file</th><th colspan="3">mmap</th></tr>
<tr>
<th>btrfs</th><td colspan="3"></td><td>fix</td><td>fix</td><td>fix</td><td>fix</td><td>fix</td><td>fix</td></tr>
<tr>
<th>exfat</th><td colspan="3"></td><td>prop</td><td>prop</td><td>ignore</td><td>prop</td><td>prop</td><td>ignore</td></tr>
<tr>
<th>ext3</th><td>prop</td><td>ignore</td><td>ignore</td><td>prop</td><td>prop</td><td>ignore</td><td>prop</td><td>prop</td><td>ignore</td></tr>
<tr>
<th>ext4</th><td colspan="3"></td><td>prop</td><td>prop</td><td>ignore</td><td>prop</td><td>prop</td><td>ignore</td></tr>
<tr>
<th>fat</th><td colspan="3"></td><td>prop</td><td>prop</td><td>ignore</td><td>prop</td><td>prop</td><td>ignore</td></tr>
<tr>
<th>jfs</th><td>prop</td><td>ignore</td><td>ignore</td><td>prop</td><td>ignore</td><td>ignore</td><td>prop</td><td>prop</td><td>ignore</td></tr>
<tr>
<th>reiserfs</th><td>prop</td><td>prop</td><td>ignore</td><td colspan="6"></td></tr>
<tr>
<th>xfs</th><td colspan="3"></td><td>prop</td><td>prop</td><td>ignore</td><td>prop</td><td>prop</td><td>ignore</td></tr>
</tbody></table>
<p>在小文件测试表中，所有结果相同，除了<code>btrfs</code>在所有测试情况下返回正确数据。这里的情况是，该文件系统是在旋转磁盘上创建的，默认情况下，<code>btrfs</code>在旋转磁盘上复制文件系统元数据（可以在SSD上配置这样做，但不是默认值）。由于文件很小，<code>btrfs</code>将文件打包进元数据，文件随元数据一起复制，允许文件系统在某个块返回坏数据或报告失败时纠正错误。</p>
<h4 id="overlay">Overlay</h4>
<p><a href="https://en.wikipedia.org/wiki/OverlayFS" rel="noopener noreferrer">Overlayfs</a>允许将一个文件系统“叠加”在另一个上。<a href="https://github.com/torvalds/linux/commit/e9be9d5e76e34872f0c37d72e25bc27fe9e2c54c" rel="noopener noreferrer">如初始提交所述</a>，一个用例可能是将一个（上层）可读写目录树放在一个（下层）只读目录树上，所有修改都进入上层可写层。</p>
<p>虽然未在表格中列出，我们还测试了除<code>fat</code>外的每个文件系统作为overlay fs的下层文件系统（ext4在所有测试中作为上层文件系统）。每个测试的文件系统在<code>overlay</code>中作为底层使用时显示相同结果。<code>fat</code>未测试，因为挂载<code>fat</code>会导致<code>文件系统不支持</code>错误。</p>
<h4 id="error-correction">错误纠正</h4>
<p><code>btrfs</code>默认不在SSD上复制元数据，因为开发者认为冗余无法防止SSD错误（这也是<code>apfs</code>没有冗余的原因）。SSD执行一种写合并，这可能导致连续发生的写入落入同一个块。如果该块完全失效，冗余副本将全部丢失，因此冗余在旋转驱动器上提供的保护更少。</p>
<p>我不确定这意味着冗余没有帮助——单个闪存单元随操作退化并随老化失去电荷。SSD内置<a href="https://en.wikipedia.org/wiki/Wear_leveling" rel="noopener noreferrer">磨损均衡</a>和<a href="https://en.wikipedia.org/wiki/Low-density_parity-check_code" rel="noopener noreferrer">纠错</a>，旨在降低块返回坏数据的概率，但随着时间的推移，一些块会发展出如此多的错误以至于纠错无法修复，块将返回坏数据。在这种情况下，读取应返回一些坏位和大部分好位。据我所知，关于SSD错误率的公开数据似乎与这一观点一致。</p>
<h4 id="error-detection">错误检测</h4>
<p>相关地，似乎<a href="http://dtrace.org/blogs/ahl/2016/06/19/apfs-part5/" rel="noopener noreferrer"><code>apfs</code>不校验数据，因为“[apfs]工程师认为Apple设备基本上不会返回虚假数据”</a>。关于SSD可靠性的公开研究未发现没有模型有时不返回坏数据。普遍认为SSD比旋转磁盘更少返回坏数据，但当谷歌在所有驱动器上研究时，他们发现：</p>
<blockquote>
<p>硬盘的年度更换率之前报告为2-9%[19,20]，这与我们看到的闪存驱动器在4年内更换4-10%相比很高。然而，闪存驱动器在错误率方面不太吸引人。超过20%的闪存驱动器在四年内出现不可纠正错误，30-80%出现坏块，2-7%出现坏芯片。相比之下，之前关于HDD的工作[1]报告，在大型人群中只有3.5%的磁盘在32个月内出现坏扇区——考虑到硬盘上的扇区数量比固态驱动器上的块或芯片数量高出几个数量级，且扇区比块小，因此故障不那么严重，这个数字很低。</p>
</blockquote>
<p>虽然从某种意义上说SSD比旋转磁盘更可靠，但从另一种意义上说它们似乎更不可靠。Apple在其驱动器上使用某种自定义固件，将更多位用于纠错，这并非不可能，但即使如此，你可能会将非Apple驱动器插入Apple计算机，并希望获得某种数据损坏保护。</p>
<h3 id="internal-error-handling">内部错误处理</h3>
<p>既然我们已经复现了Prabhakaran等人的一些测试，我们将转向<a href="https://www.usenix.org/legacy/event/fast08/tech/full_papers/gunawi/gunawi_html/index.html" rel="noopener noreferrer">Gunawi等人</a>。由于论文相当复杂，我们只看其中一小部分，即他们检查三个函数调用<code>filemap_fdatawait</code>、<code>filemap_fdatawrite</code>和<code>sync_blockdev</code>以查看这些函数有多少次未检查错误。</p>
<p>他们查看这些函数的理由如下：</p>
<blockquote>
<p>如第3.1节所述，一个函数可以同时返回多个错误码，仅检查其中一个就足够了。然而，如果我们知道某个函数只返回单个错误码，但调用者没有正确保存返回值，那么我们会知道这样的调用确实是一个缺陷。为了找到文件系统代码中的真实缺陷，我们检查了三个重要的函数：sync_blockdev、filemap_fdatawrite和filemap_fdatawait，我们已知它们只返回单个错误码。不检查这些函数返回错误码的文件系统显然会让上层故障未被注意到。</p>
</blockquote>
<p>忽略这些函数的错误似乎有相当严重的后果。<code>filemap_fdatawait</code>的文档说：</p>
<blockquote>
<p>filemap_fdatawait — 等待所有正在写回的页面完成
...
遍历给定地址空间的正在写回的页面列表并等待所有页面。检查地址空间的错误状态并返回它。
由于地址空间的错误状态被此函数清除，调用者负责检查返回值并处理和/或报告错误。</p>
</blockquote>
<p><code>sync_blockdev</code>的代码旁注释说：</p>
<blockquote>
<p>通过其映射写出并等待与块设备关联的所有脏数据。不获取超级块锁。</p>
</blockquote>
<p>在这两种情况下，忽略错误码可能意味着数据未能写入磁盘而未通知写入者数据实际上未写入？</p>
<p>让我们看看这些函数调用有多少次没有完全忽略错误码：</p>
<table>
<thead>
<tr>
<th>fn</th>
<th>2008</th>
<th>'08 %</th>
<th>2017</th>
<th>'17 %</th>
</tr>
</thead>
<tbody>
<tr>
<td>filemap_fdatawait</td>
<td>7 / 29</td>
<td>24</td>
<td>12 / 17</td>
<td>71</td>
</tr>
<tr>
<td>filemap_fdatawrite</td>
<td>17 / 47</td>
<td>36</td>
<td>13 / 22</td>
<td>59</td>
</tr>
<tr>
<td>sync_blockdev</td>
<td>6 / 21</td>
<td>29</td>
<td>7 / 23</td>
<td>30</td>
</tr>
</tbody>
</table>
<p>此表适用于<code>fs</code>下的所有代码。每行显示一个函数调用的数据。对于每年，最左边的单元格显示对返回值做了某些处理的调用数量（除以总调用数）。右边的单元格显示对返回值做了某些处理的调用百分比。“做了某些处理”在这里使用非常宽松——根据返回值分支然后在任一分支中未能处理错误、返回返回值而调用者未能处理返回值、以及保存返回值然后忽略它，都出于此表目的视为做了某些处理。</p>
<p>例如，Gunawi等人指出<code>cifs/transport.c</code>有</p>
<pre><code>int SendReceive () { 
    int rc;
    rc = cifs_sign_smb(); // 
    ... 
    rc = smb_send();
}
</code></pre>
<p>尽管<code>cifs_sign_smb</code>返回了错误码，但在被<code>smb_send</code>覆盖之前从未检查过，这在我们的目的上算作使用了，即使错误未被处理。</p>
<p>总的来说，该表似乎显示现在处理的错误比Gunawi等人2008年分析时处理的错误多得多，但仅从原始数字很难说这意味着什么，因为某些错误可能不处理是可以接受的，不同代码行以不同概率执行。</p>
<h3 id="conclusion">结论</h3>
<p>文件系统错误处理似乎有所改善。当块设备报告错误时，在<code>pwrite</code>上报告错误可能是健壮文件系统应做的最基本错误传播；2005年很少文件系统正确报告该错误。今天，在没有复杂因素且最简单的错误条件（不涉及整个驱动器死亡）下，大多数文件系统将正确报告错误。</p>
<p>大多数文件系统没有数据校验，将错误检测和纠正留给用户空间软件。当我和大公司的服务器端开发者交谈时，他们的答案通常是“谁在乎？我们所有的文件访问都通过校验数据的库，跨机器和数据中心的冗余处理了故障，因此我们只需要错误检测而不需要纠正”。虽然对某些大公司的开发者来说这是真的，但有很多软件并非健壮编写，只是假设文件系统和磁盘没有错误。</p>
<p><i>这是与Wesley Aptekar-Cassels的联合项目；项目的大部分工作在<a href="https://www.recurse.com/scout/click?t=b504af89e87b77920c9b60b2a1f6d5e8" rel="noopener noreferrer">RC</a>结对编程时完成。我们也得到了Kate Murphy的<em>很多</em>帮助。Wesley (w.aptekar@gmail.com) 和 Kate (hello@kate.io) 都在找工作。他们很棒，如果你在招聘，我强烈建议与他们交谈！</i></p>
<h3 id="appendix-error-handling-in-c">附录：C语言中的错误处理</h3>
<p>已经付出了相当多的努力来正确处理错误。但C语言使出错变得非常容易，即使你付出了相当多的努力，甚至应用了额外的工具。代码中的一个例子是<code>submit_one_bio</code>函数。如果你看定义，可以看到它标注了<code>__must_check</code>，这将在结果被忽略时导致编译器警告。但如果你查看<code>submit_one_bio</code>的调用，会发现它的调用者未标注，可以忽略错误。如果你深入挖掘，会发现一个错误传播路径如下：</p>
<pre><code>submit_one_bio
submit_extent_page
__extent_writepage
extent_write_full_page
write_cache_pages
generic_writepages
do_writepages
__filemap_fdatawrite_range
__filemap_fdatawrite
filemap_fdatawrite
</code></pre>
<p>距离<code>submit_one_bio</code>九层之远，我们看到了老朋友`filemap_fdatawrite，我们知道它经常未被检查错误。</p>
<p>关于如何防止这类事情意外发生，存在一个非常古老的辩论。一派思想，我称之为Uncle Bob (UB) 学派，认为<a href="https://twitter.com/danluu/status/916315156199636992" rel="noopener noreferrer">我们无法用工具或流程修复这些问题，只需要成为更好的程序员以避免错误</a>。你经常会听到UB学派的人说，“你无法用更好的工具（或流程）消除所有错误”。在他著名且广受好评的演讲<a href="https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/SimpleMadeEasy.md" rel="noopener noreferrer">Simple Made Easy</a>中，Rich Hickey说</p>
<blockquote>
<p>野外发现的每个错误的真相是什么？</p>
<p>[观众回答：有人写了它？] [观众回答：它被写出来了。]</p>
<p>它被写出来了。是的。关于它更有趣的事实是什么？它通过了类型检查器。</p>
<p>[观众笑声]</p>
<p>它还做了什么？</p>
<p>[观众回答：（听不清）]</p>
<p>它通过了所有测试。好吧。那么现在怎么办？对吧？我想我们处于一个我想称之为护栏编程的世界。真可悲。我们就像：我能改变因为我有测试。谁那样做？谁开车到处撞护栏然后说，“哇！幸好我有这些护栏，否则我永远赶不上演出。”</p>
<p>[观众笑声]</p>
</blockquote>
<p>如果你看这个演讲，Rich使用“简单性”的方式就像Uncle Bob使用“纪律”。这些陈述的使用方式大致等同于Ken Thompson说“<a href="https://twitter.com/danluu/status/885214004649615360" rel="noopener noreferrer">错误就是错误。你写的代码有错误，因为你就是这样</a>”。UB学派将工具和流程抛在一边，说完全依赖工具或流程是不安全的。</p>
<p>Rich的修辞技巧很巧妙——我听过那句话被引用数十次来反对测试或工具或类型。但是，像护栏一样，大多数工具和流程不是为了消除所有错误，而是为了降低错误的严重性或概率。如果我们看这个特定的函数调用，可以看到静态分析工具未能发现这个错误。这是否意味着我们应该放弃静态分析工具？静态分析工具可以查找所有<code>submit_one_bio</code>调用，并向你显示错误传播N层后被丢弃的情况。Gunawi等人正是这样做的，并发现<em>很多</em>错误。一个人基本上无法在没有工具的情况下做同样的事情。他们可以尝试，但人们如果手动挖掘这类东西能达到95%的准确率就很幸运了。代码的巨大体量保证了手工操作的人会犯错。</p>
<p>比静态分析工具更好的是一种语言，使忘记检查错误更难。这里的一个问题是，有时丢弃错误是有效的。在许多地方，没有接口允许错误从文件系统传播出去，因此丢弃错误是正确的，除非更改接口。在当前情况下，作为读代码的外部人员，如果你看一堆丢弃错误的调用，很难说所有这些中哪些是错误，哪些是正确的。如果默认情况是我们有一种护栏说“此错误必须被检查”，人们仍然可能错误地忽略错误，但你至少得到一个注释说明省略是有意的。例如，如果你被迫编写代码明确表明你忽略错误，并且在旨在健壮的代码中（如文件系统代码），故意丢弃错误的代码很可能附带解释为什么丢弃错误的注释。</p>
<h3 id="appendix-why-wasn-t-this-done-earlier">附录：为什么这没有早点做？</h3>
<p>毕竟，如果我们知道现代文件系统能否正确执行基本任务就好了。文件系统开发者可能知道这些，但因为我不<a href="http://lkml.iu.edu/hypermail/linux/kernel/0908.3/01481.html" rel="noopener noreferrer">关注LKML</a>，在我们进行实验之前我不知道自2005年以来事情是否有所改善。</p>
<p>我们这里看的论文来自Andrea和Remzi Arpaci-Dusseau的研究实验室。Remzi有一个演讲，提到研究生不想复制和更新旧工作。考虑到他们面临的激励，这完全合理。我这里不是要针对学术界——这项工作来自学术界，不是工业界。如果没有学术激励系统，这种工作可能根本不会发生。</p>
<p>总的来说，资助正确性工作似乎相当困难。有相当多的论文关于寻找错误的新方法，但关于将现有技术应用于现有代码的工作相对较少。在学术界，这似乎很难获得好的出版物，在开源世界，这似乎比编写新代码对人们吸引力更小。这也完全合理——人们应该做他们想做的事，即使他们喜欢研究正确性，那通常也不是一个好的职业决定。我前几天晚上去<a href="https://www.recurse.com/scout/click?t=b504af89e87b77920c9b60b2a1f6d5e8" rel="noopener noreferrer">RC</a>招聘会，我的徽章写着我对测试感兴趣。第一个和我说话的人开口就是“你做QA吗？”。当我在硬件行业工作时，那不会是个危险信号，但在软件行业，“QA”是低技能、枯燥且薪酬微薄工作的代号。工业界大多认为测试和QA是事后考虑。因此，公司依赖的开源项目通常<a href="https://en.wikipedia.org/wiki/OpenSSL" rel="noopener noreferrer">资金严重不足</a>。谷歌资助了一些很棒的工作（如afl-fuzz），但这是例外而非规则，即使在谷歌内部也是如此，大多数公司不资助任何开源工作。这篇文章的工作由几个有意暂时失业的人完成，这不是一个可扩展的模型。</p>
<p>偶尔，你会看到有人花费大量精力提高正确性，但这通常是以大量免费劳动力完成的。Kyle Kingsbury可能是典型例子——据我所知，他在<a href="http://jepsen.io/" rel="noopener noreferrer">Jepsen分布式系统测试工具</a>上工作了多年晚上和周末，然后将其转变为咨询业务。他这样做很棒——他证明了几乎每个开源分布式系统都有严重的数据丢失或损坏错误。我认为这很棒，但像这样英勇努力的故事总是让我担心，因为英勇不可扩展。如果Kyle没有出现，他和他的工具发现的大多数错误今天是否仍困扰着开源分布式系统？这是个可怕的想法。</p>
<p>如果我知道如何资助更多正确性工作，我会尝试说服你我们应该切换到这种新模式，但我不知道有哪种资助模式有效。我设置了<a href="https://www.patreon.com/danluu" rel="noopener noreferrer">Patreon（捐赠账户）</a>，但足以真正资助大量工作将是相当非凡的。如果你看看程序员从捐赠中赚了多少钱，如果我赚的比在工业界工作少两个数量级，那已经让我成为Patreon上前1%的程序员。如果我赚的比在工业界少一个数量级，那将是非凡的。我脑海里想到的，唯一从Patreon赚得更多的程序员要么制作吸引力更广的东西（如游戏），要么是Evan You，他制作了现存使用最广泛的前端库之一。如果我实际赚的和在工业界一样多，我怀疑那会让我成为Patreon上收入最高的程序员，即使按行业标准，我的薪酬并不特别。</p>
<p>如果我必须猜测，我认为这种工作难以资助的部分原因是消费者没有激励公司资助这类工作。如果你看“大”科技公司，其中两家在正确性上比竞争对手认真得多。这导致关于丢失电子邮件、文档以及整个账户的恐怖故事少得多。如果你看对消费者的影响，可能类似于1%的人看到丢失/损坏电子邮件与0.001%的人之间的差异。我认为如果将成本乘以所有消费者，这相当重要，但绝大多数消费者不会基于这种差异做决定。如果你看正确性问题更明显的领域，如数据库或备份，你会发现即使最差的解决方案也有辩护者会跳进任何讨论说“对我来说有效”。一个90%时间有效的备份解决方案相当糟糕，但如果你有一个90%时间有效的方案，它仍然会有坚定的辩护者跳进讨论说“我从备份恢复过三次，从未失败！你肯定在编造！”。我不责怪公司理性地响应消费者，但我认为结果对消费者来说是不幸的。</p>
<p>顺便说一句，做免费开放工作的一大奇迹是，你做的免费工作越多，抱怨你做得不够的人就越多。正如David MacIver所说，<a href="https://www.youtube.com/watch?v=2XCDm3MoXUY" rel="noopener noreferrer">做开源工作就像做正常的带薪工作，只不过你得到的是抱怨而不是现金</a>。基本上可以保证这篇文章有史以来最普遍的评论是我们没有测试某人钟爱的文件系统，因为我们是<code>btrfs</code>吹捧者或只是懒惰，尽管我们包含了允许任何人添加测试的仓库链接。几乎每次我做任何免费实验工作，那些显然没有阅读实验设置或源代码的人就会抱怨实验不可能正确，因为[某件不真实的事情，任何人查看设置都能看到]，而我没有在他们想看到的确切东西上运行实验是绝对不可原谅的。由于我很久以前在竞技视频游戏中玩过，我习惯了更激烈的网络喷子，但总的来说，这种激励系统似乎是倒置的。</p>
<h3 id="appendix-experimental-setup">附录：实验设置</h3>
<p>对于错误注入设置，实验设置的高层视图是使用<a href="https://stackoverflow.com/q/1870696/334816" rel="noopener noreferrer"><code>dmsetup</code></a>模拟磁盘上的坏块。</p>
<p>运行的命令列表如下所示：</p>
<pre><code>cp images/btrfs.img.gz /tmp/tmpeas9efr6.gz
gunzip -f /tmp/tmpeas9efr6.gz
losetup -f
losetup /dev/loop19 /tmp/tmpeas9efr6
blockdev --getsize /dev/loop19
#        0 74078 linear /dev/loop19 0
#        74078 1 error
#        74079 160296 linear /dev/loop19 74079
dmsetup create fserror_test_1508727591.4736078
mount /dev/mapper/fserror_test_1508727591.4736078 /mnt/fserror_test_1508727591.4736078/
mount -t overlay -o lowerdir=/mnt/fserror_test_1508727591.4736078/,upperdir=/tmp/tmp4qpgdn7f,workdir=/tmp/tmp0jn83rlr overlay /tmp/tmpeuot7zgu/
./mmap_read /tmp/tmpeuot7zgu/test.txt
umount /tmp/tmpeuot7zgu/
rm -rf /tmp/tmp4qpgdn7f
rm -rf /tmp/tmp0jn83rlr
umount /mnt/fserror_test_1508727591.4736078/
dmsetup remove fserror_test_1508727591.4736078
losetup -d /dev/loop19
rm /tmp/tmpeas9efr6
</code></pre>
<p><a href="https://github.com/danluu/fs-errors/" rel="noopener noreferrer">查看此github仓库以获取执行测试的确切命令集</a>。</p>
<p>注意所有这些测试都是在linux上完成的，所以<code>fat</code>指的是linux的<code>fat</code>实现，而不是windows的<code>fat</code>实现。<code>zfs</code>和<code>reiserfs</code>未测试，因为它们无法以我们测试其他文件系统的确切相同方式轻松测试（我们中一人花了一两个小时试图让<code>zfs</code>工作，但其配置接口与所有测试的文件系统不一致；<code>reiserfs</code>似乎有统一接口，但测试它需要对看似已死的文件系统做额外工作）。<code>ext3</code>支持现在由<code>ext4</code>代码提供，所以现在的<code>ext3</code>含义与2005年不同。</p>
<p>所有测试在ubuntu 17.04, 4.10.0-37和arch, 4.12.8-2上运行。我们在两台机器上得到相同结果。所有文件系统均使用默认配置。对于<code>btrfs</code>，这意味着复制元数据但不复制数据，据我们所知，设置对其他文件系统没有影响。</p>
<p>本文第二部分没有太多实验设置可说。设置是grep linux源代码中的相关函数。</p>
<p><em>感谢Leah Hanson、David Wragg、Ben Kuhn、Wesley Aptekar-Cassels、Joel Borggrén-Franck、Yuri Vishnevsky和Dan Puttick对本文的评论/纠正。</em></p><p><em>由 mimo-v2.5 模型翻译，花费 17962 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/filesystem-errors/</link>
      <guid isPermaLink="false">https://danluu.com/filesystem-errors/</guid>
      <pubDate>Mon, 23 Oct 2017 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>键盘延迟</title>
      <description>[AI 摘要] 文章通过实际测量指出，许多游戏键盘虽标榜低延迟，但实测延迟并不一定优于普通键盘，且现代键盘普遍增加了用户感知的延迟。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章通过实际测量指出，许多游戏键盘虽标榜低延迟，但实测延迟并不一定优于普通键盘，且现代键盘普遍增加了用户感知的延迟。</div><p>如果你观察“游戏”键盘，很多都以速度快为卖点，售价100美元或更高。你可能会看到如下广告文案：</p>

<ul>
<li>定制设计的键帽，缩短了按键触发并注册操作所需的时间</li>
<li>8倍更快 - 1000Hz轮询率：响应时间0.1毫秒</li>
<li>配备45克轻压力轴体和比标准Cherry MX红轴快40%的触发速度，对你的对手施加终极性能优势</li>
<li>全球最快的超级轮询率1000Hz</li>
<li>全球最快的游戏键盘，1000Hz轮询率，0.001秒响应时间</li>
</ul>

<p>尽管有这些说法，我只能找到<a href="http://forums.blurbusters.com/viewtopic.php?f=10&amp;t=1836" rel="noopener noreferrer">一个人公开对键盘延迟进行了基准测试</a>，而他只测试了两款键盘。总的来说，我的看法是，如果有人在没有基准测试的情况下提出性能声称，这些声称很可能不真实，就像未经测试（或未经其他方式验证）的代码应该被视为有缺陷一样。</p>

<p>游戏键盘的情况让我想起和汽车销售员交谈：</p>

<blockquote>
<p>销售员：这辆车超级安全！它有12个安全气囊！
我：那很好，但碰撞测试表现如何？
销售员：12个安全气囊！</p>
</blockquote>

<p>当然，游戏键盘有1000Hz轮询率，但那又怎样？</p>

<p>两个明显的问题是：</p>

<ol>
<li>键盘延迟重要吗？</li>
<li>游戏键盘真的比其他键盘更快吗？</li>
</ol>

<h3 id="does-keyboard-latency-matter">键盘延迟重要吗？</h3>

<p>一年前，如果有人问我是否会构建自定义装置来测量键盘延迟，我会说这很傻，然而我现在正用<a href="https://www.amazon.com/gp/product/B074TVSLN1/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B074TVSLN1&amp;linkId=9377ad585ac27e528ee655cc01b48cef" rel="noopener noreferrer">逻辑分析仪</a>测量键盘延迟。</p>

<p>这一切都始于<a href="//danluu.com/input-lag/" rel="noopener noreferrer">我有一种感觉，一些旧电脑比现代机器反应快得多</a>。例如，运行macOS 9的iMac G4或Apple 2都比我的<a href="https://www.amazon.com/gp/product/B01MXSI216/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01MXSI216&amp;linkId=ce7b5129c29c9c012b53e5402ae33a18" rel="noopener noreferrer">4.2 GHz Kaby Lake</a>系统感觉更快。我从不相信这种感觉，因为有几十年的研究表明，用户的感受常常与事实完全相反，所以我买了一台高速摄像机，开始测量实际的按键到屏幕更新延迟以及鼠标移动到屏幕更新延迟。事实证明，那些感觉快的机器确实更快，比我的现代计算机快得多——70年代和80年代的计算机通常在30ms到50ms范围内，而现代计算机在终端中按键时通常处于100ms到200ms范围内。在经过良好优化的游戏中，配合高端游戏设置，可以降到50ms范围内，有一款非凡的消费设备可以轻松低于50ms，但默认体验要慢得多。现代计算机拥有更好的<a href="https://stackoverflow.com/a/39187441/334816" rel="noopener noreferrer">吞吐量，但其延迟</a>却不太理想。</p>

<p>总之，当我进行这些测量时，我的4.2 GHz Kaby Lake拥有你能买到的任何机器中最快的单线程性能，但其延迟却比一台来自70年代的快速机器（大约是Apple 2的6倍）更差，这似乎有点奇怪。为了弄清楚延迟从何而来，我开始测量键盘延迟，因为那是管道的第一部分。我的计划是查看端到端管道，并从起点开始，排除键盘延迟作为真正的延迟源。但结果键盘延迟相当显著！我惊讶地发现，我测试的键盘中位数延迟比Apple 2的整个端到端管道还要高。如果这让你立刻感到荒谬，请考虑Apple 2有3500个晶体管，运行在1MHz，而Atmel的一名员工估计，如今许多高端键盘中使用的内核有<a href="https://www.embeddedrelated.com/showthread/comp.arch.embedded/14362-1.php" rel="noopener noreferrer">80k个晶体管</a>，运行在16MHz。那是20倍的晶体管，运行在16倍的时钟速度——键盘通常比70年代和80年代的整台计算机更强大！然而，如今中等键盘增加的延迟与70年代快速机器的整个端到端管道相当。</p>

<p>让我们看看一些键盘上测量的按键到USB延迟：</p>

<p>
</p><div><p></p>

<table>
<thead>
<tr>
<th>键盘</th>
<th>延迟<br>(ms)</th>
<th>连接</th>
<th>游戏</th>
</tr>
</thead>

<tbody>
<tr>
<td><a href="https://www.amazon.com/gp/product/B016QO64FI/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B016QO64FI&amp;linkId=37ca409628c850d85d4b8649699cae9b" rel="noopener noreferrer">apple magic</a> (usb)</td>
<td>15</td>
<td>USB FS</td>
<td></td>
</tr>

<tr>
<td><a href="https://www.amazon.com/gp/product/B0000U1DJ2/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B0000U1DJ2&amp;linkId=20d58842cee7c22d03e405c59cb1d2b9" rel="noopener noreferrer">hhkb lite 2</a></td>
<td>20</td>
<td>USB FS</td>
<td></td>
</tr>

<tr>
<td><a href="https://www.amazon.com/gp/product/B004XMAL00/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B004XMAL00&amp;linkId=82939c8d37d45d1a0a8a77d22eb3ac77" rel="noopener noreferrer">MS natural 4000</a></td>
<td>20</td>
<td>USB</td>
<td></td>
</tr>

<tr>
<td><a href="https://www.amazon.com/gp/product/B008PFABI8/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B008PFABI8&amp;linkId=3d1945a4fa3cfb25d6f7de0278fbbdfc" rel="noopener noreferrer">das</a> 3</td>
<td>25</td>
<td>USB</td>
<td></td>
</tr>

<tr>
<td><a href="https://www.amazon.com/gp/product/B0086G2YE0/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B0086G2YE0&amp;linkId=0772fce67ce00f03760bbd53b179e355" rel="noopener noreferrer">logitech k120</a></td>
<td>30</td>
<td>USB</td>
<td></td>
</tr>

<tr>
<td><a href="https://www.amazon.com/gp/product/B01M29PYF4/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01M29PYF4&amp;linkId=d885df4dca893dd7deaf36c9d0dd3213" rel="noopener noreferrer">unicomp model M</a></td>
<td>30</td>
<td>USB FS</td>
<td></td>
</tr>

<tr>
<td><a href="https://www.amazon.com/gp/product/B00OFM51L2/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B00OFM51L2&amp;linkId=96bf16aaba80391dd38bfbb763d02bcc" rel="noopener noreferrer">pok3r vortex</a></td>
<td>30</td>
<td>USB FS</td>
<td></td>
</tr>

<tr>
<td><a href="https://www.amazon.com/gp/product/B004WOF7S0/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B004WOF7S0&amp;linkId=3dc1bc2fd4fa06be77f90a73f4992911" rel="noopener noreferrer">filco majestouch</a></td>
<td>30</td>
<td>USB</td>
<td></td>
</tr>

<tr>
<td>dell OEM</td>
<td>30</td>
<td>USB</td>
<td></td>
</tr>

<tr>
<td>powerspec OEM</td>
<td>30</td>
<td>USB</td>
<td></td>
</tr>

<tr>
<td><a href="https://www.amazon.com/gp/product/B00CMALD3E/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B00CMALD3E&amp;linkId=8873c99f91b35e17f61d8510fbb50a39" rel="noopener noreferrer">kinesis freestyle 2</a></td>
<td>30</td>
<td>USB FS</td>
<td></td>
</tr>

<tr>
<td><a href="https://www.amazon.com/gp/product/B01D2TSGL0/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01D2TSGL0&amp;linkId=966405c7866aff4b1e24e1dbfacd5035" rel="noopener noreferrer">chinfai silicone</a></td>
<td>35</td>
<td>USB FS</td>
<td></td>
</tr>

<tr>
<td><a href="https://www.amazon.com/gp/product/B01LVTI3TO/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01LVTI3TO&amp;linkId=b1f13ca07d80d302103bb0c313a994d9" rel="noopener noreferrer">razer ornata chroma</a></td>
<td>35</td>
<td>USB FS</td>
<td>是</td>
</tr>

<tr>
<td><a href="https://olkb.com/planck/" rel="noopener noreferrer">olkb planck rev 4</a></td>
<td>40</td>
<td>USB FS</td>
<td></td>
</tr>

<tr>
<td><a href="https://www.amazon.com/gp/product/B01CJ4LJ68/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01CJ4LJ68&amp;linkId=7ce482c03631b402385c5b0bb5fca221" rel="noopener noreferrer">ergodox</a></td>
<td>40</td>
<td>USB FS</td>
<td></td>
</tr>

<tr>
<td><a href="https://www.amazon.com/gp/product/B014W20C90/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B014W20C90&amp;linkId=a72b89b476d857d4df4389bb7995bc8a" rel="noopener noreferrer">MS comfort 5000</a></td>
<td>40</td>
<td>无线</td>
<td></td>
</tr>

<tr>
<td><a href="https://www.amazon.com/gp/product/B01DBJTZU2/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01DBJTZU2&amp;linkId=fb0a73e976666bebd9c1e6a5657e83b9" rel="noopener noreferrer">easterntimes i500</a></td>
<td>50</td>
<td>USB FS</td>
<td>是</td>
</tr>

<tr>
<td><a href="https://www.amazon.com/gp/product/B01K32MU2A/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01K32MU2A&amp;linkId=99b7e5c9826b61a0f30f43210e21d38" rel="noopener noreferrer">kinesis advantage</a></td>
<td>50</td>
<td>USB FS</td>
<td></td>
</tr>

<tr>
<td><a href="https://www.amazon.com/gp/product/B003YGVDDU/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B003YGVDDU&amp;linkId=c8c51873d132176213b864de76f31dbe" rel="noopener noreferrer">genius luxemate i200</a></td>
<td>55</td>
<td>USB</td>
<td></td>
</tr>

<tr>
<td><a href="https://www.amazon.com/gp/product/B00DGJALYW/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B00DGJALYW&amp;linkId=96a1e65f5e4dee9f5d20306048c1dd0c" rel="noopener noreferrer">topre type heaven</a></td>
<td>55</td>
<td>USB FS</td>
<td></td>
</tr>

<tr>
<td><a href="https://www.amazon.com/gp/product/B00L1Y11D4/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B00L1Y11D4&amp;linkId=8a84bfe004ab161c183e26c35f5966c3" rel="noopener noreferrer">logitech k360</a></td>
<td>60</td>
<td>“unifying”</td>
<td></td>
</tr>
</tbody>
</table>

<p>延迟测量值是从按键开始移动到与该按键相关的<a href="https://docs.mbed.com/docs/ble-hid/en/latest/api/md_doc_HID.html" rel="noopener noreferrer">USB数据包</a>出现在USB总线上的时间。数字四舍五入到最近的<code>5 ms</code>，以避免给出虚假的精确感。<code>easterntimes i500</code>也以<code>tomoko MMC023</code>的名义销售。</p>

<p>连接列指示使用的连接类型。<code>USB FS</code>代表<code>USB全速</code>协议，它允许高达1000Hz的轮询率，这是高端键盘常用宣传的功能。<code>USB</code>是<code>USB低速</code>协议，是大多数键盘使用的协议。“游戏”列指示该键盘是否被标记为游戏键盘。<code>无线</code>表示某种键盘专用的加密狗，<code>unifying</code>是罗技的无线设备标准。</p>

<p>我们可以看到，即使在有限的键盘测试集下，键盘之间的延迟差异也可能高达45毫秒。此外，连接了较慢键盘的现代计算机不可能像70年代或80年代的快速机器那样灵敏，因为仅键盘本身就比某些旧计算机的整个响应管道还慢。</p>

<p>这确立了现代键盘是我们过去四十年所见的延迟膨胀的一个原因。问题的另一半是，现代键盘增加的延迟是否真的对用户有影响？从表格中可以看出，在测试的键盘中，平均延迟差异可达40毫秒。40毫秒的延迟能被察觉吗？让我们先看看一些键盘的延迟测量值，然后看看关于用户能察觉多少延迟的经验研究。</p>

<p>有相当多的经验证据表明，对于非常简单的任务，<a href="https://pdfs.semanticscholar.org/386a/15fd85c162b8e4ebb6023acdce9df2bd43ee.pdf" rel="noopener noreferrer">人们可以察觉到低至2毫秒或更短的延迟</a>。此外，增加延迟不仅对用户来说是可察觉的，<a href="http://www.tactuallabs.com/papers/howMuchFasterIsFastEnoughCHI15.pdf" rel="noopener noreferrer">它还会导致用户执行简单任务时准确性下降</a>。如果你想直观地了解延迟是什么样子，而且手边没有一台超快的旧计算机，可以<a href="https://www.youtube.com/watch?v=vOvQCPLkPt4" rel="noopener noreferrer">看看这个关于触摸屏延迟的MSR演示</a>。</p>

<h3 id="are-gaming-keyboards-faster-than-other-keyboards">游戏键盘比其他键盘更快吗？</h3>

<p>我真的很想在做出有力声明之前测试更多键盘，但根据这里的初步测试，游戏键盘通常似乎并不比非游戏键盘更快。</p>

<p>游戏键盘经常声称拥有减少延迟的功能，例如通过USB FS连接并使用1000Hz轮询。USB低速规范规定数据包之间的最小时间为<code>10ms</code>，或100 Hz。然而，通常会看到USB设备将此时间向下舍入到最接近的2的幂，并以<code>8ms</code>或125Hz运行。在<code>8ms</code>轮询下，由于需要等待下一个轮询间隔而增加的平均延迟为<code>4ms</code>。在<code>1ms</code>轮询下，USB轮询带来的平均延迟为<code>0.5ms</code>，两者相差<code>3.5ms</code>。虽然对于像Apple magic键盘这样快速的键盘来说，这可能是延迟的一个重要贡献因素，但显然其他因素主导了大多数键盘的延迟，并且这里测试的游戏键盘如此之慢，以至于减少<code>3.5ms</code>也无济于事。</p>

<p>关于游戏键盘的另一点需要注意的是，它们通常宣传“N键无冲”（同时按下n个键的能力——对于许多键组合，典型键盘通常只允许同时按下两个键，不包括修饰键）。尽管这里通常没有测试，我试过一款“Razer DeathStalker Expert Gaming Keyboard”，它宣传“最多支持10个按键同时按下的防鬼键能力”。这款雷蛇游戏键盘并未以有用的方式实现此功能，许多三键组合无效。我想他们的广告声称在技术上可能是真实的，因为3在某些情况下可以“最多10个”，但就像游戏键盘声称通过1000 Hz轮询具有更低延迟一样，这种说法往好了说也是极具误导性的。</p>

<h3 id="conclusion">结论</h3>

<p>大多数键盘增加了足够的延迟，导致用户体验明显变差，而宣传速度的键盘并不一定更快。我们测量的两款游戏键盘并不比非游戏键盘快，而测量的最快的键盘是一款来自Apple的极简键盘，其市场定位更侧重于设计而非速度。</p>

<p>先前，我们已经看到<a href="//danluu.com/term-latency/" rel="noopener noreferrer">终端可以增加显著的延迟，在轻度悲观条件下，如果你选择“正确的”终端，延迟可达100毫秒</a>。在未来的文章中，我们将查看整个端到端管道，看看其他地方延迟是如何悄悄增加的，我们还将看看一些现代设备如何保持低延迟。</p>

<h3 id="appendix-where-is-the-latency-coming-from">附录：延迟从何而来？</h3>

<p>一个主要的延迟来源是按键行程时间。测量的最快键盘也具有最短的按键行程距离，这并非巧合。我用来测量端到端延迟的视频设置是240帧/秒的摄像机，这意味着帧间隔为4毫秒。当拍摄“正常”按键和打字时，一个键完全按下需要4-8帧。大多数开关会在键完全按下之前就开始触发，但按键行程时间仍然很显著，很容易增加<code>10ms</code>的延迟（或更多，取决于开关机制）。与之对比的是测量的Apple“magic”键盘，其按键行程如此之短，以至于用240帧/秒的摄像机无法捕捉，表明按键行程时间&lt;4毫秒。</p>

<p>请注意，与我在网上找到的另一个测量不同，此测量是从按键开始而非开关激活时开始的。因为作为一个人，你不是激活开关，你是按下键。从开关激活时间开始的测量会错过这个延迟的巨大组成部分。例如，如果你在玩游戏，看到某事发生时从前进切换到后退，你必须支付按键移动的代价，这对不同键盘是不同的。对此的一个常见回应是，“真正的”玩家会预加载按键，这样他们就不必支付按键行程的代价，但如果你拿着高速摄像机四处观察人们实际如何使用键盘，即使观察游戏玩家，显著预加载按键的按键比例基本上为零。如果你观察高水平的职业玩家，可能会看到不同的情况，但即便如此，例如，使用标准wasd或esdf布局的人们通常不会在从后退切换到前进时预加载按键。此外，认为按键有一堆无用的行程没关系，因为你可以在真正按下键之前先按下一点键的想法简直荒谬。这就像说现代计算机的延迟没关系，因为有些人构建的游戏盒子，在运行经过特殊优化的软件时，可以获得50毫秒的响应时间。普通的、非硬核游戏用户根本不会这样做。既然这是市场的绝大部分，即使所有“认真的”玩家都这样做，那也只是一个舍入误差。</p>

<p>另一个主要的延迟来源是扫描<a href="http://blog.komar.be/how-to-make-a-keyboard-the-matrix/" rel="noopener noreferrer">键盘矩阵</a>和去抖动。这两种延迟都不是固有的——键盘使用矩阵而不是每个键一根线，因为这样可以节省几美元，大多数键盘扫描矩阵的速率如此之慢，以至于产生了人类可察觉的延迟，因为这样可以节省几美元，但制造商如果愿意在制造键盘上多花一点钱，就可以使该延迟远低于人类感知的阈值。关于去抖动延迟，请参见下文。</p>

<p>虽然我们在这篇文章中没有讨论吞吐量，但当我测量我的打字速度时，我发现我用<a href="https://www.amazon.com/gp/product/B01NABDNPH/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01NABDNPH&amp;linkId=ccde207372bd2239204e1ebc3a36a9cd" rel="noopener noreferrer">低行程Apple键盘</a>打字比用任何其他键盘都快。没有办法为此做盲测实验，但Gary Bernhardt和其他人也观察到了同样的现象。有些人声称按键行程对打字速度不重要，因为他们使用最小必要行程，因此这不可能重要，但就像上面关于按键的说法一样，如果你拿着高速摄像机观察人们实际打字时的情况，很难找到真正这样做的人。</p>

<h3 id="2022-update">2022年更新</h3>

<p>当我进行这些实验时，似乎没有人测试过多款键盘的延迟。我觉得我得到的结果非常反直觉，所以我试图找到任何其他人的键盘延迟测量结果，我能找到的只是一篇论坛帖子，有人试图测量他们的键盘（仅一款），结果在同一范围内，但使用的设置不够快，无法真正正确测量延迟。我还用高速摄像机拍摄了我的测试以及非测试按键，以查看按键需要多长时间，结果与我现在得到的结果并非明显不一致。</p>

<p>在我写完这篇文章后的一两年，我目睹了一些游戏鼠标和键盘制造商讨论如何制造更低延迟的设备，他们开始发布确实具有更低延迟的设备，而不是他们之前拥有的那些，基本上是游戏皮肤，并且经常发光。</p>

<p>如果你想要一款非Apple键盘（我和很多人交谈过，他们报告说长时间使用Apple键盘后手指会疼）的低延迟键盘，<a href="https://amzn.to/3AYD7FG" rel="noopener noreferrer">SteelSeries Apex Pro</a>延迟相当低；对于鼠标，<a href="https://amzn.to/3gPLoVl" rel="noopener noreferrer">Corsair Sabre</a>也相当快。</p>

<p>自那时以来的另一个变化是，更多人理解去抖动不必增加明显的延迟。当我写原始文章时，有多个键盘制造商向我解释说这篇文章是错误的，在去抖动时不可能不增加延迟。我觉得这非常奇怪，因为我认为一个电气工程专业的新生，或者甚至一个玩电子的高中生，都应该明白为什么情况并非如此，但无论出于什么原因，多个以制造键盘为生的人不理解这一点。现在，如何在不增加延迟的情况下进行去抖动已成为常识，当我看到有人声称去抖动必然会增加大量延迟的讨论时，他们通常会被纠正。这种知识已传播到大多数键盘制造商，并降低了某些新键盘的延迟，尽管我知道至少有一家键盘制造商仍然不相信你可以进行低延迟的去抖动，他们新键盘的结果仍然是增加了相当多的延迟。</p>

<h3 id="appendix-counter-arguments-to-common-arguments-that-latency-doesn-t-matter">附录：对认为延迟不重要的常见论点的反驳</h3>

<p>在撰写本文之前，我阅读了我能找到的关于延迟的内容，很难找到非专家文章或评论区没有至少一个以下列出的论点：</p>

<h4 id="computers-and-devices-are-fast">计算机和设备很快</h4>

<p>对延迟问题最常见的回应是，输入延迟基本上为零，或者非常接近零，以至于可以忽略不计。例如，<a href="https://hardware.slashdot.org/story/13/07/13/1331235/ask-slashdot-low-latency-ps2usb-gaming-keyboards" rel="noopener noreferrer">这篇询问键盘延迟的Slashdot文章</a>上的两个顶级评论是键盘速度太快，以至于键盘速度无关紧要。甚至有人说：</p>

<blockquote>
<p>没有一款现代键盘的延迟是50毫秒。你（人类）有那种延迟。

至于响应时间，你只需要增加USB栈上的轮询时间就可以了。</p>
</blockquote>

<p>正如我们所看到的，一些设备确实有50毫秒范围内的延迟。这段话以及该帖子中的其他评论说明了另一个常见的谬误——输入设备受限于USB轮询的速度。虽然这在技术上是可能的，但大多数设备远远不够快，以至于受到USB轮询延迟的限制。</p>

<p>不幸的是，大多数关于输入延迟的在线解释<a href="https://byuu.org/articles/latency/" rel="noopener noreferrer">都假设USB总线是限制因素</a>。</p>

<h4 id="humans-can-t-notice-100ms-or-200ms-latency">人类无法察觉100毫秒或200毫秒的延迟</h4>

<p><a href="https://stackoverflow.com/a/39985088/334816" rel="noopener noreferrer">这里有一位“研究视觉感知和认知的认知神经科学家”</a>，他提到人类反应时间大约是200毫秒，然后又加了一堆科学术语来说没人能真正察觉到100毫秒以下的延迟。这有点不寻常，因为评论者声称某种特殊权威，并使用很多术语，但人们常声称你无法察觉到50毫秒或100毫秒的延迟，因为人类反应时间是200毫秒。这实际上没有道理，因为它们是独立的量。这种论证就像说你不会注意到航班延迟一小时，因为航班时长是六小时。</p>

<p>这种推理的另一个问题是，从按键到屏幕更新的完整管道相当长，如果你说在这里添加10毫秒，在那里添加10毫秒总是没关系的，你最终会在整个管道中得到更大的膨胀，这就是我们今天所处的情况，你可以购买一台拥有你能买到的最快单线程性能的CPU的系统，却得到70年代机器6倍的延迟。</p>

<h4 id="it-doesn-t-matter-because-the-game-loop-runs-at-60-hz">没关系，因为游戏循环以60Hz运行</h4>

<p>这从根本上与上述谬误相同。如果你有一个延迟等于时钟周期的一半，就有50%的几率该延迟会将事件推迟到下一个处理步骤。这比100%的几率好，但我不明白为什么人们认为延迟需要与时钟周期一样长才会有影响。作为参考，这里测量的最慢和最快键盘之间<code>45ms</code>的差异对应60fps下的2.7帧。</p>

<h4 id="keyboards-can-t-possibly-response-faster-more-quickly-than-5ms-10ms-20ms-due-to-debouncing-http-www-eng-utah-edu-cs5780-debouncing-pdf">由于<a href="http://www.eng.utah.edu/~cs5780/debouncing.pdf" rel="noopener noreferrer">去抖动</a>，键盘不可能比5毫秒/10毫秒/20毫秒更快响应</h4>

<p>即使不通过复杂手段优化开关机制，如果你愿意在系统中引入滞后，键盘也没有理由不能在看到边沿时立即假定按键（或释放）正在发生。这在其他类型的系统中是常见的做法，据我所知，没有理由键盘不能做同样的事情（也许有些确实如此）。去抖动时间可能会限制按键的重复速率，但没有固有的理由它必须影响延迟。如果我们考虑重复速率，想象一下由于引入滞后，按键状态变化速率限制为5毫秒。这给了我们一个完整的按键周期（按下和释放）每10毫秒，或每个键每秒100次按键，这远远超出了任何人的能力。你可能会争辩说这引入了一种不精确性，这在某些应用（音乐、节奏游戏）中可能很重要，但这受限于开关机制。使用带有滞后的去抖动机制不会让我们比之前更糟。</p>

<p>去抖动延迟的另一个问题是，大多数键盘制造商似乎混淆了扫描速率和去抖动延迟。常见的是扫描速率在100 Hz到200 Hz范围内的键盘。这可以用“因为去抖动延迟是5ms，所以更快扫描没有意义”之类的说法来辩解，这结合了上面提到的两个谬误。如果你拿出Apple 2e的原理图，你会发现扫描速率大约是50 kHz。其去抖动时间大约是6毫秒，对应167 Hz的频率。为什么扫描这么快？快速扫描允许键盘控制器几乎立即（最多20微秒后）开始去抖动计时，而现代键盘以167 Hz扫描，可能在6毫秒后，或300倍的时间后才开始去抖动计时。</p>

<p>抱歉这里没有解释术语，但我认为任何提出这个异议的人都应该理解这个解释:-)。</p>

<h3 id="appendix-experimental-setup">附录：实验设置</h3>

<p>USB测量设置是一个被切开以暴露电线的<a href="https://www.amazon.com/gp/product/B000E5CYW8/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B000E5CYW8&amp;linkId=4e14a96314e989f8166fe183a0a9b0ee" rel="noopener noreferrer">USB电缆</a>，连接到逻辑分析仪。逻辑分析仪的确切型号并不重要，但如果你对细节感兴趣，这组实验使用了一个<a href="https://www.amazon.com/gp/product/B074TWM5WX/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B074TWM5WX&amp;linkId=562b98ee01041c876c27311abdff418c" rel="noopener noreferrer">salae pro</a>。虽然USB电缆的确切型号并不重要，但如果你想重复实验，我建议使用<a href="https://www.amazon.com/gp/product/B000E5CYW8/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B000E5CYW8&amp;linkId=4e14a96314e989f8166fe183a0a9b0ee" rel="noopener noreferrer">相对较短的USB电缆</a>。切开电缆会损害信号完整性，我发现，使用很长的电缆时，一些弱驱动数据线的键盘驱动能力不足，无法用我使用的廉价逻辑分析仪获得良好的信号。</p>

<p>输入开始是通过同时按下两个键来测量的——键盘上的一个键和一个也连接到逻辑分析仪的按钮。这会引入一些抖动，因为两个按钮不会完全同时被按下。为了校准设置，我们使用了两个连接到逻辑分析仪的相同按钮。中位数抖动&lt;1毫秒，90百分位抖动大约5毫秒。这足以使快速键盘的尾部延迟测量无法通过此设置实现，但像这里进行的平均延迟测量似乎应该没问题。输入抖动可能通过构建一个装置来减少到可忽略不计的水平，该装置可以同时触发逻辑分析仪并按下被测键盘上的一个键。有了这样的设置，平均延迟测量也会更好（因为更容易运行大量的测量）。</p>

<p>如果你想知道确切的设置，使用了一个<a href="https://www.digikey.com/product-detail/en/e-switch/LL1105AF065Q/LL1105AF065Q-ND/3777946" rel="noopener noreferrer">E-switch LL1105AF065Q</a>开关。电源和地线由<a href="https://www.amazon.com/gp/product/B008GRTSV6/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B008GRTSV6&amp;linkId=51afaa1d76d1e6ef7c1e6bc0eeebdf9d" rel="noopener noreferrer">一块arduino板</a>提供。没有特别理由使用这个设置。事实上，用一个完整的arduino来供电有点荒谬，但这都是用手头的备用零件完成的，这些东西碰巧是<a href="https://www.recurse.com/scout/click?t=b504af89e87b77920c9b60b2a1f6d5e8" rel="noopener noreferrer">RC</a>实验室里的东西，除了开关。没有两种相同的开关，所以我们买了几个开关，以便可以用两个相同的开关进行校准测量。这里开关的确切类型并不重要；任何低电阻开关都可以。</p>

<p>测试通过按下<code>z</code>键，然后在USB总线上寻找字节29，然后标记包含相应信息的第一个数据包的结束来进行。但，如上所述，任何键都可以。</p>

<p>我实际上并不信任这个设置，我想在测试更多键盘之前构建一个完全自动化的设置。虽然测量结果与我能在网上找到的另一个键盘测量结果一致，但这个设置存在固有的不精确性，可能在1毫秒到10毫秒的范围内。虽然对多次测量取平均可以减少这种不精确性，但由于测量是由人完成的，不能保证甚至不太可能误差是独立的并会平均抵消。</p>

<p>这个项目是在Wesley Aptekar-Cassels、Leah Hanson和Kate Murphy的帮助下完成的。</p>

<p>感谢<a href="https://www.recurse.com/scout/click?t=b504af89e87b77920c9b60b2a1f6d5e8" rel="noopener noreferrer">RC</a>、Ahmad Jarara、Raph Levien、Peter Bhat Harkins、Brennan Chesley、Dan Bentley、Kate Murphy、Christian Ternus、Sophie Haskins和Dan Puttick，让我们使用他们的键盘进行测试。</p>

<p>感谢Leah Hanson、Mark Feeney、Greg Kennedy和Zach Allaun对本文的评论/修正/讨论。
</p></div><p><em>由 mimo-v2.5 模型翻译，花费 19301 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/keyboard-latency/</link>
      <guid isPermaLink="false">https://danluu.com/keyboard-latency/</guid>
      <pubDate>Mon, 16 Oct 2017 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>分支预测</title>
      <description>[AI 摘要] 本文介绍CPU分支预测的基本原理、必要性以及从静态到动态的各种经典预测算法，旨在为读者理解现代分支预测研究奠定基础。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文介绍CPU分支预测的基本原理、必要性以及从静态到动态的各种经典预测算法，旨在为读者理解现代分支预测研究奠定基础。</div><p><em>这是2017年8月22日在Two Sigma举行的关于分支预测的演讲伪记录，为<a href="https://www.recurse.com/scout/click?t=b504af89e87b77920c9b60b2a1f6d5e8" rel="noopener noreferrer">RC</a>组织的“localhost”系列演讲拉开序幕。</em></p>

<p>你们中有多少人在代码中使用分支？如果使用if语句或模式匹配，请举手示意。</p>

<p><code>大部分听众举手</code></p>

<p>接下来的部分我不请你们举手了，但我猜想，如果我问有多少人觉得自己能很好地理解CPU执行分支时做了什么、这对性能有什么影响，以及有多少人觉得自己能理解现代关于分支预测的论文，举手的人会变少。</p>

<p>本次演讲的目的是解释CPU“分支预测”的方式和原因，并介绍经典分支预测算法的基本原理，使你能够阅读关于分支预测的现代论文并大致理解其内容。</p>

<p>在讨论分支预测之前，我们先探讨CPU为什么要做分支预测。为此，我们需要了解一点CPU的工作原理。</p>

<p>就本次演讲而言，你可以将计算机想象为CPU加一些内存。指令存储在内存中，CPU从内存中执行一系列指令，这些指令类似于“将两个数相加”、“将一块数据从内存移动到处理器”等。通常，执行完一条指令后，CPU将执行下一个顺序地址处的指令。然而，存在一种称为“分支”的指令，可以改变下一条指令的来源地址。</p>

<p>以下是CPU执行一些指令的抽象示意图。x轴代表时间，y轴区分不同的指令。</p>

<p><img src="https://danluu.com/images/branch-prediction/1-pipeline.png" alt="指令顺序执行" height="130" width="1262"></p>

<p>这里，我们依次执行指令<code>A</code>、<code>B</code>、<code>C</code>、<code>D</code>。</p>

<p>一种可能的CPU设计方式是让CPU完成一条指令的所有工作，然后继续下一条指令，完成下一条的所有工作，以此类推。这没什么问题；许多老式CPU就是这样做的，一些现代低成本CPU仍然如此。但如果你想制造更快的CPU，你可能会制造像流水线一样工作的CPU。也就是说，你将CPU分成两部分，这样一半CPU可以做指令的“前半部分”工作，而另一半CPU处理指令的“后半部分”工作，就像流水线一样。这通常被称为流水线CPU。</p>

<p><img src="https://danluu.com/images/branch-prediction/2-pipeline.png" alt="指令重叠执行" height="275" width="1263"></p>

<p>如果你这样做，执行过程可能看起来像上图。在指令A的前半部分完成后，CPU可以在执行指令B的前半部分的同时处理A的后半部分。当A的后半部分完成时，CPU可以开始处理B的后半部分和C的前半部分。在此图中，你可以看到流水线CPU在单位时间内可以执行的指令数是非流水线CPU的两倍。</p>

<p>CPU没有理由只能分成两部分。我们可以将CPU分成三部分，获得3倍加速，或分成四部分获得4倍加速。这并非严格成立，通常对于三级流水线或四级流水线，我们获得的加速比小于3倍或4倍，因为将CPU分成更多部分并使用更深的流水线会产生开销。</p>

<p>一个开销来源是如何处理分支。CPU为一条指令需要做的第一件事就是获取指令；为此，它必须知道指令的位置。例如，考虑以下代码：</p>

<pre><code>if (x == 0) {
  // 做些事情
} else {
  // 做其他事情
}
  // 后续操作
</code></pre>

<p>这可能转化为类似下面的汇编代码：</p>

<pre><code>branch_if_not_equal x, 0, else_label
// 做些事情
goto end_label
else_label:
// 做其他事情
end_label:
// 后续操作
</code></pre>

<p>在这个例子中，我们将<code>x</code>与0进行比较。<code>if_not_equal</code>，则跳转到<code>else_label</code>并执行else块中的代码。如果比较失败（即<code>x</code>为0），则继续执行，执行<code>if</code>块中的代码，然后跳转到<code>end_label</code>以避免执行else块中的代码。</p>

<p>对流水线处理来说特别有问题的指令序列是：</p>

<pre><code>branch_if_not_equal x, 0, else_label
???
</code></pre>

<p>CPU不知道这会是</p>

<pre><code>branch_if_not_equal x, 0, else_label
// 做些事情
</code></pre>

<p>还是</p>

<pre><code>branch_if_not_equal x, 0, else_label
// 做其他事情
</code></pre>

<p>直到分支完成（或几乎完成）执行。由于CPU为一条指令需要做的第一件事就是从内存中获取指令，而我们不知道<code>???</code>会是什么指令，因此在前一条指令几乎完成之前，我们甚至无法开始处理<code>???</code>。</p>

<p>早些时候，当我们说三级流水线能获得3倍加速或20级流水线能获得20倍加速时，那假设是每个周期都能启动一条新指令，但在这里，这两条指令几乎是串行的。</p>

<p><img src="https://danluu.com/images/branch-prediction/3-branch-stall.png" alt="因分支停顿导致的非重叠执行" height="72" width="640"></p>

<p>解决这个问题的一种方法是使用分支预测。当遇到分支时，CPU会猜测分支是“跳转”还是“不跳转”。</p>

<p><img src="https://danluu.com/images/branch-prediction/4-branch-speculation.png" alt="推测分支结果" height="69" width="326"></p>

<p>在这种情况下，CPU预测分支不会被采用，并在执行分支后半部分的同时开始执行<code>stuff</code>的前半部分。如果预测正确，CPU将在执行<code>stuff</code>后半部分时，像我们在第一个流水线图中看到的那样，可以开始另一条指令。</p>

<p><img src="https://danluu.com/images/branch-prediction/5-branch-correct-prediction.png" alt="正确预测后的重叠执行" height="72" width="482"></p>

<p>如果预测错误，当分支完成执行时，CPU将丢弃<code>stuff.1</code>的结果，并开始执行正确的指令而不是错误的指令。因为如果没有分支预测，我们会停顿处理器且不执行任何指令，所以我们的处境并不比没有做预测时更糟（至少在我们考虑的详细程度上）。</p>

<p><img src="https://danluu.com/images/branch-prediction/6-branch-misprediction.png" alt="中止的预测" height="101" width="640"></p>

<p>这样做对性能有什么影响？为了估算，我们需要一个性能模型和一个工作负载。就本次演讲而言，我们的CPU卡通模型是一个流水线CPU，其中非分支指令平均每个时钟执行一条，未预测或预测错误的分支需要20个周期，正确预测的分支需要一个周期。</p>

<p>如果我们看看最常用的“工作站”整数工作负载基准测试SPECint，其组成大约是20%分支和80%其他操作。在没有分支预测的情况下，我们预计“平均”指令耗时<code>分支百分比 * 1 + 非分支百分比 * 20 = 0.2 * 20 + 0.8 * 1 = 4 + 0.8 = 4.8周期</code>。在完美、100%准确的分支预测下，我们预计平均指令耗时为0.8 * 1 + 0.2 * 1 = 1周期，加速比达4.8倍！另一种看法是，如果我们的流水线具有20个周期的分支预测错误代价，那么仅分支一项就带来了接近5倍于理想流水线加速比的额外开销。</p>

<p>让我们看看能做些什么来改善。我们将从最朴素的方法开始，逐步介绍更好的方案。</p>

<h3 id="predict-taken">总是预测跳转</h3>

<p>与其随机预测，我们可以查看所有程序执行中的所有分支。如果我们这样做，会发现“跳转”和“不跳转”分支并不完全平衡——跳转的分支比不跳转的分支多得多。一个原因是循环分支通常是跳转的。</p>

<p>如果我们预测每个分支都是跳转的，可能会达到70%的准确率，这意味着我们将为30%的分支支付预测错误代价，使得平均指令代价为<code>(0.8 + 0.7 * 0.2) * 1 + 0.3 * 0.2 * 20 = 0.94 + 1.2 = 2.14周期</code>。如果我们将“总是预测跳转”与“无预测”和“完美预测”进行比较，尽管它是一个非常简单的算法，却获得了完美预测大部分的好处。</p>

<p><img src="https://danluu.com/images/branch-prediction/7-always-taken-cpi.png" alt="每指令周期数2.14" height="156" width="1147"></p>

<h3 id="backwards-taken-forwards-not-taken-btfnt">向后跳转则跳转，向前跳转则不跳转（BTFNT）</h3>

<p>预测分支为跳转对循环效果很好，但对其他所有分支效果不佳。如果我们根据分支是向前跳转（跳过代码）还是向后跳转（回到之前的代码）来查看分支是否被跳转，我们会看到向后跳转的分支比向前跳转的分支更常被跳转，因此我们可以尝试一种预测器，预测向后跳转的分支会跳转，向前跳转的分支不会跳转（BTFNT）。如果我们在硬件中实现这种方案，编译器作者会与我们合作，安排代码使得编译器认为会跳转的分支是向后跳转的，而认为不会跳转的分支是向前跳转的。</p>

<p>如果我们这样做，可能达到约80%的预测准确率，使得我们的代价函数为<code>(0.8 + 0.8 * 0.2) * 1 + 0.2 * 0.2 * 20 = 0.96 + 0.8 = 1.76</code>每指令周期。</p>

<p><img src="https://danluu.com/images/branch-prediction/8-btfnt-cpi.png" alt="每指令周期数1.76" height="157" width="1147"></p>

<h4 id="used-by">使用者</h4>
<ul>
<li>PPC 601（1993年）：也使用编译器生成的分支提示</li>
<li>PPC 603</li>
</ul>

<h3 id="one-bit">一位预测器</h3>

<p>到目前为止，我们研究的是不存储任何状态的方案，即预测忽略程序执行历史的方案。在文献中，这些被称为<em>静态</em>分支预测方案。这些方案的优点是简单，但缺点是对于那些行为随时间变化的分支预测效果不佳。如果你想看一个行为随时间变化的分支示例，可以想象如下代码：</p>

<pre><code>if (flag) {
  // 事情
  }
</code></pre>

<p>在程序执行过程中，我们可能有一个阶段flag被设置且分支被跳转，另一个阶段flag未被设置且分支未被跳转。静态方案无法对此类分支做出良好的预测，因此让我们考虑<em>动态</em>分支预测方案，其预测可以根据程序历史而变化。</p>

<p>我们可能做的最简单的事情之一是根据分支的上次结果进行预测，即如果分支上次跳转，我们预测跳转；如果分支上次未跳转，我们预测未跳转。</p>

<p>因为为每个可能的分支存储一个位数太多了，无法可行存储，所以我们维护一个包含我们见过的一定数量的分支及其上次结果的表。在本次演讲中，让我们将<code>未跳转</code>存储为<code>0</code>，将<code>跳转</code>存储为<code>1</code>。</p>

<p><img src="https://danluu.com/images/branch-prediction/9-1bit.png" alt="使用一位条目的预测表，通过分支地址的低位索引" height="420" width="1358"></p>

<p>在这种情况下，仅仅为了让内容适合图示，我们有一个64项的表，这意味着我们可以用6位索引，因此我们用分支地址的低6位索引表。执行一个分支后，我们更新预测表中的条目（如下所示），下次再次执行该分支时，我们索引到相同的条目并使用更新后的值进行预测。</p>

<p><img src="https://danluu.com/images/branch-prediction/10-1bit-update.png" alt="索引条目在更新时更改" height="420" width="1358"></p>

<p>我们可能会观察到别名，即两个不同位置的分支映射到表中的同一位置。这并不理想，但在表的速度和成本与大小之间存在权衡，这实际上限制了表的大小。</p>

<p>如果我们使用一位方案，可能会达到85%的准确率，代价为<code>(0.8 + 0.85 * 0.2) * 1 + 0.15 * 0.2 * 20 = 0.97 + 0.6 = 1.57</code>每指令周期。</p>

<p><img src="https://danluu.com/images/branch-prediction/11-1bit-cpi.png" alt="每指令周期数1.57" height="201" width="1147"></p>

<h4 id="used-by-1">使用者</h4>
<ul>
<li>DEC EV4（1992年）</li>
<li>MIPS R8000（1994年）</li>
</ul>

<h3 id="two-bit-https-courses-cs-washington-edu-courses-cse590g-04sp-smith-1981-a-study-of-branch-prediction-strategies-pdf"><a href="https://courses.cs.washington.edu/courses/cse590g/04sp/Smith-1981-A-Study-of-Branch-Prediction-Strategies.pdf" rel="noopener noreferrer">两位预测器</a></h3>

<p>一位方案对于<code>TTTTTTTT…</code>或<code>NNNNNNN…</code>这样的模式效果良好，但对于大多数跳转但有一个不跳转的分支流<code>...TTTNTTT...</code>，会导致预测错误。这可以通过为每个地址添加第二位并实现一个饱和计数器来修复。让我们任意地说，当分支不跳转时计数减少，当跳转时计数增加。如果我们查看二进制值，最终会得到：</p>

<pre><code>00: 预测不跳转
01: 预测不跳转
10: 预测跳转
11: 预测跳转
</code></pre>

<p>饱和计数器的“饱和”部分意味着，如果我们从<code>00</code>向下计数，它不会下溢，而是停留在<code>00</code>；从<code>11</code>向上计数时停留在<code>11</code>的情况类似。此方案与一位方案相同，只是预测表中的每个条目是两位而不是一位。</p>

<p><img src="https://danluu.com/images/branch-prediction/12-2bit.png" alt="与一位相同，只是表有两位" height="481" width="1371"></p>

<p>与一位方案相比，两位方案在相同大小/成本下可以有一半的条目（如果我们只考虑存储成本而忽略饱和计数器的逻辑成本），但即便如此，对于大多数合理的表大小，两位方案提供了更好的准确率。</p>

<p>尽管简单，但这效果很好，我们可能期望两位预测器达到约90%的准确率，这给我们带来了1.38每指令周期的代价。</p>

<p><img src="https://danluu.com/images/branch-prediction/13-2bit-cpi.png" alt="每指令周期数1.38" height="234" width="1147"></p>

<p>一个自然的做法是将该方案推广到n位饱和计数器，但事实证明，增加更多位对准确率的影响相对较小。我们还没有真正讨论分支预测器的成本，但从每个分支2位增加到3位会使表大小增加1.5倍，而收益甚微，这使得在大多数情况下不值得付出成本。两位方案预测不佳的最简单和最常见的情况是<code>NTNTNTNTNT...</code>或<code>NNTNNTNNT…</code>这样的模式，但增加到n位也无法很好地预测这些模式！</p>

<h4 id="used-by-2">使用者</h4>
<ul>
<li>LLNL S-1（1977年）</li>
<li>CDC Cyber?（80年代初）</li>
<li>Burroughs B4900（1982年）：状态存储在指令流中；硬件会覆盖指令以更新分支状态</li>
<li>Intel Pentium（1993年）</li>
<li>PPC 604（1994年）</li>
<li>DEC EV45（1993年）</li>
<li>DEC EV5（1995年）</li>
<li>PA 8000（1996年）：实际上是一个3位移位寄存器，带多数表决</li>
</ul>

<h3 id="two-level-adaptive-global-http-www-seas-upenn-edu-cis501-papers-two-level-branch-pred-pdf-1991"><a href="http://www.seas.upenn.edu/~cis501/papers/two-level-branch-pred.pdf" rel="noopener noreferrer">两级自适应，全局</a>（1991年）</h3>

<p>如果我们考虑如下代码：</p>

<pre><code>for (int i = 0; i &lt; 3; ++i) {
  // 这里的代码。
  }
</code></pre>

<p>这段代码会产生类似<code>TTTNTTTNTTTN...</code>的分支模式。</p>

<p>如果我们知道分支的最近三次执行，我们应该能够预测分支的下一次执行：</p>

<pre><code>TTT:N
TTN:T
TNT:T
NTT:T
</code></pre>

<p>我们之前考虑的方案使用分支地址索引到一个表，该表告诉我们根据最近历史，分支更可能跳转还是不跳转。这告诉我们分支倾向于哪个方向，但它无法告诉我们是否处于重复模式的中间。为了解决这个问题，我们将存储最近分支的历史以及一个预测表。</p>

<p><img src="https://danluu.com/images/branch-prediction/14-global.png" alt="使用全局分支历史和分支地址索引预测表" height="481" width="1371"></p>

<p>在这个例子中，我们将4位分支历史与2位分支地址连接起来索引预测表。与之前一样，预测来自一个两位饱和计数器。我们不想仅使用分支历史来索引预测表，因为如果这样做，任何具有相同历史的两个分支都会别名到同一表项。在真正的预测器中，我们可能会有一个更大的表并使用更多的分支地址位，但为了让表适合幻灯片，我们的索引只有6位长。</p>

<p>下面，我们将看到执行分支时更新了什么。</p>

<p><img src="https://danluu.com/images/branch-prediction/15-global-update.png" alt="更新更改索引，因为索引使用了分支历史中的位" height="481" width="1371"></p>

<p>粗体部分是被更新的部分。在此图中，我们从右向左移入新的分支历史位，更新分支历史。由于分支历史被更新，预测表索引的低位被更新，因此下次再次执行同一分支时，我们将使用表中的不同条目进行预测，这与之前索引由分支地址固定的方案不同。旧条目的值被更新，因此下次再次执行具有相同分支历史的同一分支时，我们将拥有更新的预测。</p>

<p>由于此方案中的历史是全局的，它将正确预测内层循环中的<code>NTNTNTNT…</code>等模式，但可能并非总能对更高级别的分支做出正确预测，因为历史是全局的，会被其他分支的信息污染。然而，这里的权衡是，保持全局历史比保持一个本地历史表更便宜。此外，使用全局历史让我们能够正确预测相关分支。例如，我们可能有：</p>

<pre><code>if x &gt; 0:
  x -= 1
if y &gt; 0:
  y -= 1
if x * y &gt; 0:
  foo()
</code></pre>

<p>如果第一个分支或下一个分支未跳转，那么第三个分支肯定不会跳转。</p>

<p>使用此方案，我们可能达到93%的准确率，给我们带来1.27每指令周期的代价。</p>

<p><img src="https://danluu.com/images/branch-prediction/16-global-cpi.png" alt="每指令周期数1.27" height="290" width="1147"></p>

<h4 id="used-by-3">使用者</h4>
<ul>
<li>Pentium MMX（1996年）：4位全局分支历史</li>
</ul>

<h3 id="two-level-adaptive-local-http-www-cse-iitd-ac-in-srsarangi-col-718-2017-papers-branchpred-alternative-impl-pdf-1992"><a href="http://www.cse.iitd.ac.in/~srsarangi/col_718_2017/papers/branchpred/alternative-impl.pdf" rel="noopener noreferrer">两级自适应，局部</a>（1992年）</h3>

<p>如上所述，全局历史方案的一个问题是，可以被干净预测的本地分支的历史会被其他分支污染。</p>

<p>获得良好本地预测的一种方法是为不同的分支保持单独的分支历史。</p>

<p><img src="https://danluu.com/images/branch-prediction/17-local.png" alt="保持每个分支的历史表，而不是全局历史" height="505" width="1369"></p>

<p>我们不是保持单个全局历史，而是保持一个局部历史表，通过分支地址索引。此方案与我们刚看过的全局方案相同，只是我们保持了多个分支历史。一种思考方式是，全局历史是局部历史的特例，其中我们跟踪的历史数量为<code>1</code>。</p>

<p>使用此方案，我们可能达到约94%的准确率，这给我们带来了1.23每指令周期的代价。</p>

<p><img src="https://danluu.com/images/branch-prediction/18-local-cpi.png" alt="每指令周期数1.23" height="362" width="1147"></p>

<h4 id="used-by-4">使用者</h4>
<ul>
<li>Pentium Pro（1996年）：<a href="http://www.ece.uah.edu/~milenka/docs/milenkovic_WDDD02.pdf" rel="noopener noreferrer">4位局部分支历史，用PC的低位索引</a>。注意这存在一些争议，Agner Fog声称PPro及后续处理器使用4位全局历史</li>
<li>Pentium II（1997年）：同PPro</li>
<li>Pentium III（1999年）：同PPro</li>
</ul>

<h3 id="gshare">gshare</h3>

<p>全局两级方案必须做出的一个权衡是，对于固定大小的预测表，必须将位分配给分支历史或分支地址。我们希望将更多位分配给分支历史，因为这允许跨越更“远距离”的关联以及跟踪更复杂的模式；我们希望将更多位分配给分支地址，以避免不相关分支之间的干扰。</p>

<p>我们可以通过同时哈希分支历史和分支地址来尝试兼顾两者，而不是连接它们。一种最简单合理的做法，也是第一个提出的机制是<a href="https://en.wikipedia.org/wiki/XOR_gate" rel="noopener noreferrer"><code>xor</code></a>它们。这种将位<code>xor</code>在一起的两级自适应方案称为<code>gshare</code>。</p>

<p><img src="https://danluu.com/images/branch-prediction/19-gshare.png" alt="哈希分支地址和分支历史，而不是追加" height="488" width="1371"></p>

<p>使用此方案，我们可能看到约94%的准确率。这是我们从刚看过的局部方案获得的准确率，但gshare避免了保持大型局部历史表的需要；在需要跟踪更少状态的情况下获得相同的准确率是一个重大改进。</p>

<h4 id="used-by-5">使用者</h4>
<ul>
<li>MIPS R12000（1998年）：<a href="https://courses.cs.washington.edu/courses/csep548/06au/lectures/branchPred.pdf" rel="noopener noreferrer">2K条目，11位PC，8位历史</a></li>
<li>UltraSPARC-3（2001年）：<a href="https://courses.cs.washington.edu/courses/csep548/06au/lectures/branchPred.pdf" rel="noopener noreferrer">16K条目，14位PC，12位历史</a></li>
</ul>

<h3 id="agree-http-meseec-ce-rit-edu-eecc722-fall2006-papers-branch-prediction-5-agree-isca24-pdf-1997"><a href="http://meseec.ce.rit.edu/eecc722-fall2006/papers/branch-prediction/5/agree_isca24.pdf" rel="noopener noreferrer">同意预测器</a>（1997年）</h3>

<p>分支预测错误的原因之一是不同分支别名到同一位置时的干扰。有许多方法可以减少别名到同一预测表项的分支之间的干扰。事实上，本次演讲只涉及90年代发明的方案的原因是，提出了各种各样的减少干扰的方案，内容太多无法在半小时内覆盖。</p>

<p>我们将看一个方案，它可能会让你了解减少干扰方案的样子，即“同意”预测器。当两个分支-历史对冲突时，预测要么匹配，要么不匹配。如果匹配，我们称之为中性干扰；如果不匹配，我们称之为负面干扰。其思想是大多数分支往往有强烈的倾向（也就是说，如果我们在预测表中使用两位条目，我们期望没有干扰时大多数条目在大多数时间是<code>00</code>或<code>11</code>，而不是<code>01</code>或<code>10</code>）。对于程序中的每个分支，我们将存储一个位，我们称之为“倾向”。预测表不是存储绝对的分支预测，而是存储预测是否与倾向匹配或不匹配。</p>

<p><img src="https://danluu.com/images/branch-prediction/20-agree.png" alt="预测分支是否与其倾向一致，而不是预测其是否跳转" height="782" width="1371"></p>

<p>如果我们看看这是如何工作的，预测器与gshare预测器相同，只是我们做了上述更改——预测是同意/不同意而不是跳转/不跳转，并且有一个通过分支地址索引的倾向位，为我们提供了同意或不同意的对象。在原始论文中，他们建议使用你看到的第一个值作为倾向，其他人建议使用配置文件引导优化（基本上运行程序并将数据反馈给编译器）来确定倾向。</p>

<p>注意，当我们执行一个分支，然后稍后回到同一分支时，我们将使用相同的倾向位（因为倾向是通过分支地址索引的），但我们将使用不同的预测表项（因为那是通过分支地址和分支历史索引的）。</p>

<p><img src="https://danluu.com/images/branch-prediction/21-agree-update.png" alt="更新使用相同的倾向，但不同的元预测表项" height="782" width="1371"></p>

<p>如果这看起来奇怪，让我们看一个具体例子。假设我们有两个分支，分支A有90%的概率跳转，分支B有10%的概率跳转。如果这两个分支别名，我们假设每个分支跳转的概率是独立的，那么它们不一致并产生负面干扰的概率是<code>P(A跳转) * P(B不跳转) + P(A不跳转) * P(B跳转) = (0.9 * 0.9) + (0.1 * 0.1) = 0.82</code>。</p>

<p>如果使用同意方案，我们可以重新计算，但两个分支不一致并产生负面干扰的概率是<code>P(A同意) * P(B不同意) + P(A不同意) * P(B同意) = P(A跳转) * P(B跳转) + P(A不跳转) * P(B跳转) = (0.9 * 0.1) + (0.1 * 0.9) = 0.18</code>。另一种看法是，要发生破坏性干扰，其中一个分支必须与其倾向不一致。根据定义，如果我们正确确定了倾向，这不太可能发生。</p>

<p>使用此方案，我们可能达到约95%的准确率，给我们带来1.19每指令周期的代价。</p>

<p><img src="https://danluu.com/images/branch-prediction/22-agree-cpi.png" alt="每指令周期数1.19" height="432" width="1147"></p>

<h4 id="used-by-6">使用者</h4>
<ul>
<li>PA-RISC 8700（2001年）</li>
</ul>

<h3 id="hybrid-http-www-hpl-hp-com-techreports-compaq-dec-wrl-tn-36-pdf-1993"><a href="http://www.hpl.hp.com/techreports/Compaq-DEC/WRL-TN-36.pdf" rel="noopener noreferrer">混合预测器</a>（1993年）</h3>

<p>正如我们所看到的，局部预测器可以很好地预测某些类型的分支（例如内层循环），全局预测器可以很好地预测某些类型的分支（例如某些相关分支）。一种尝试兼顾两者优势的方法是同时拥有两种预测器，然后有一个元预测器来预测应该使用局部还是全局预测器。一个简单的方法是让元预测器使用与上面两位预测器相同的方案，只不过它预测的不是<code>跳转</code>或<code>不跳转</code>，而是<code>局部预测器</code>或<code>全局预测器</code>。</p>

<p><img src="https://danluu.com/images/branch-prediction/23-hybrid.png" alt="预测两个预测器中哪个正确，而不是预测分支是否跳转" height="752" width="1358"></p>

<p>正如存在许多可能的减少干扰的方案（上面的<code>同意</code>预测器是其中之一），也存在许多可能的混合方案。我们可以使用任何两种预测器，而不仅仅是局部和全局预测器，我们甚至可以使用两种以上的预测器。</p>

<p>如果我们使用局部和全局预测器，我们可能达到约96%的准确率，给我们带来1.15每指令周期的代价。</p>

<p><img src="https://danluu.com/images/branch-prediction/24-hybrid-cpi.png" alt="每指令周期数1.15" height="496" width="1147"></p>

<h4 id="used-by-7">使用者</h4>
<ul>
<li>DEC EV6（1998年）：局部（1k条目，10位历史，3位计数器）和全局（4k条目，12位历史，2位计数器）预测器的组合</li>
<li>IBM POWER4（2001年）：局部（16k条目）和gshare（16k条目，11位历史，与分支地址异或，16k选择器表）</li>
<li>IBM POWER5（2004年）：双模态（未涵盖）和两级自适应的组合</li>
<li>IBM POWER7（2010年）</li>
</ul>

<h3 id="not-covered">未涵盖的内容</h3>

<p>本次演讲中我们有很多内容没有涵盖！正如你可能预料的，我们未涵盖的材料集合比我们涵盖的要大得多。我将简要描述一些我们没有涵盖的内容，并提供参考文献，以便你如果想了解更多信息可以查阅。</p>

<p>一个主要我们没谈到的是<a href="https://en.wikipedia.org/wiki/Branch_target_predictor" rel="noopener noreferrer">如何预测分支目标</a>。注意，即使对于一些无条件分支（即不需要方向预测因为总是跳转的分支），也需要这样做，因为<a href="http://www.engr.uconn.edu/~zshi/course/cse5302/ref/bray91btb_tr.pdf" rel="noopener noreferrer">(某些)无条件分支有未知的目标</a>。</p>

<p>分支目标预测成本很高，以至于一些早期CPU的分支预测策略是“总是预测不跳转”，因为当预测分支不会被跳转时，不需要分支目标！总是预测不跳转的准确率不高，但仍然比根本不预测要好。</p>

<p>在我们没有讨论的减少干扰的预测器中，包括<a href="https://people.eecs.berkeley.edu/~kubitron/courses/cs152-S04/handouts/papers/p4-lee.pdf" rel="noopener noreferrer">双模态</a>、<a href="http://meseec.ce.rit.edu/eecc722-fall2002/papers/branch-prediction/7/michaud97trading.pdf" rel="noopener noreferrer">gskew</a>和<a href="http://web.eecs.umich.edu/~tnm/papers/yags.pdf" rel="noopener noreferrer">YAGS</a>。简单来说，双模态有点像同意预测器，它尝试根据方向分离分支，但双模态使用的机制是，我们保持多个预测表，并使用基于分支地址的第三个预测器来预测对于特定的分支和分支历史组合使用哪个预测表。双模态似乎比同意预测器更成功，因为它被更广泛地使用。在gskew中，我们至少保持三个预测表，并使用不同的哈希来索引每个表。其思想是，即使两个分支别名，这两个分支也只会在一个表中别名，因此我们可以使用投票，另外两个表的结果将覆盖可能来自别名表的不良结果。我不知道如何简要描述YAGS :-)。</p>

<p>因为我们没有讨论速度（即延迟），所以我们没有讨论的一个预测策略是拥有一个小/快的预测器，当较慢但更准确的预测器计算出其结果时，可以覆盖它。</p>

<p>一些现代CPU有完全不同的分支预测器；AMD Zen（2017年）和AMD Bulldozer（2011年）芯片似乎使用<a href="https://www.cs.utexas.edu/~lin/papers/hpca01.pdf" rel="noopener noreferrer">基于感知器的分支预测器</a>。感知器是单层神经网络。</p>

<p><a href="https://hal.inria.fr/hal-01100647/document" rel="noopener noreferrer">有人认为</a>Intel Haswell（2013年）使用<a href="http://www.irisa.fr/caps/people/seznec/JILP-COTTAGE.pdf" rel="noopener noreferrer">TAGE预测器</a>的变体。TAGE代表TAgged GEometric history length predictor。如果我们看看我们涵盖过的预测器，并查看程序的实际执行以查看哪些分支我们预测不正确，一个主要类别是需要大量历史的分支——相当多的分支需要几十或几百位历史，有些甚至需要超过一千位分支历史。如果我们有一个单一的预测器，甚至是一个结合几种不同预测器的混合预测器，保持一千位历史是适得其反的，因为这会降低对那些需要相对少量历史的分支（特别是相对于成本而言，这是大多数分支）的预测准确率。TAGE预测器的一个思想是，通过保持几何级数的历史长度，每个分支都可以使用适当的历史。这解释了GE部分。TA部分是分支被标记，这是预测器用来跟踪哪些分支应使用哪组历史的机制，我们没有讨论过。</p>

<p>现代CPU通常有专门的预测器，例如循环预测器可以准确预测循环分支，而通用分支预测器无法合理存储足够的历史来对循环的每次迭代做出完美预测。</p>

<p>我们完全没有讨论使用更多空间和获得更好预测之间的权衡。改变表的大小不仅会改变预测器的性能，还会改变哪些预测器相对更好。</p>

<p>我们也没有讨论不同的工作负载如何影响不同的分支预测器。预测器性能不仅基于表大小而变化，还基于运行的特定程序而变化。</p>

<p>我们还将分支预测错误代价视为固定的东西，<a href="http://users.elis.ugent.be/~leeckhou/papers/ispass06-eyerman.pdf" rel="noopener noreferrer">但事实并非如此</a>，同样，非分支指令的代价在不同工作负载之间也有很大差异。</p>

<p>我尽量避免引入非自解释的术语，因此如果你阅读文献，术语会有所不同。</p>

<h3 id="conclusion">结论</h3>

<p>我们研究了各种经典的分支预测器，并非常简要地讨论了几种较新的预测器。我们讨论的一些经典预测器今天仍在CPU中使用，如果这是一个一小时而不是半小时的演讲，我们可以讨论最先进的预测器。我认为很多人觉得CPU神秘且难以理解，但我认为CPU实际上比软件更容易理解。我可能有偏见，因为我以前研究CPU，但我认为这不是我偏见的结果，而是根本性的。</p>

<p>如果你考虑软件的复杂性，主要的限制因素是你的想象力。如果你能想象出足够详细的东西并能写下来，你就能制造出来。当然，有些情况下那不是限制因素，有更实际的东西（例如大规模应用程序的性能），但我认为我们大多数人把大部分时间花在编写软件上，其中限制因素是创造和管理复杂性的能力。</p>

<p>硬件与此大不相同，因为存在反对复杂性的力量。你实现的每一块硬件都要花钱，所以你想实现尽可能少的硬件。此外，性能对大多数硬件很重要（无论是绝对性能还是每美元、每瓦特或其他成本的性能），而增加复杂性会使硬件变慢，从而限制性能。今天，你可以花300美元购买一个现成的CPU，可以超频到5 GHz。在5 GHz下，一个工作单元是五分之一纳秒。作为参考，光在一纳秒内大约传播一英尺。另一个限制因素是当CPU不能一直完美工作时，人们会非常不高兴。虽然<a href="https://danluu.com/cpu-bugs/" rel="noopener noreferrer">CPU确实有bug</a>，但bug率比几乎所有软件都低得多，也就是说，验证/测试的标准高得多。增加复杂性使测试和验证变得更困难。因为CPU的正确性标准高于<a href="https://danluu.com/everything-is-broken/" rel="noopener noreferrer">大多数软件</a>，增加复杂性给CPU带来了更高的测试/验证负担，这使得增加类似数量的复杂性在硬件上比在软件上成本高得多，即使不考虑我们讨论的其他因素。</p>

<p>这些反对芯片复杂性的因素的一个副作用是，对于任何特定的“高级”通用CPU功能，它通常概念上足够简单，可以在半小时或一小时的演讲中描述。CPU比许多程序员想象的更简单！顺便说一下，我说“高级”是为了排除像晶体管和电路设计这样的东西，它们可能需要相当多的底层（物理或固态）背景才能理解。</p>

<h3 id="cpu-internals-series">CPU内部系列</h3>
<ul>
<li><a href="//danluu.com/new-cpu-features/" rel="noopener noreferrer">自80年代以来的新CPU功能</a></li>
<li><a href="//danluu.com/integer-overflow/" rel="noopener noreferrer">实际代码中分支和整数溢出检查的代价</a></li>
<li><a href="https://danluu.com/cpu-bugs/" rel="noopener noreferrer">CPU bug</a></li>
<li><a href="//danluu.com/branch-prediction/" rel="noopener noreferrer">分支预测简史</a></li>
<li><a href="//danluu.com/hardware-unforgiving/" rel="noopener noreferrer">为什么CPU开发很难</a></li>
<li><a href="//danluu.com/why-hardware-development-is-hard/" rel="noopener noreferrer">Verilog很糟糕，第1部分</a></li>
<li><a href="//danluu.com/pl-troll/" rel="noopener noreferrer">Verilog很糟糕，第2部分</a></li>
</ul>

<p><em>感谢Leah Hanson、Hari Angepat和Nick Bergson-Shilcock审阅了演讲的练习版本，感谢Fred Clausen Jr.在本文中发现了一个错别字。对于本文有些草率的状态表示歉意——我写得很快，以便参加演讲的人可以很快参考“记录”并查找参考文献，但这意味着错误可能比通常多，组织结构也不如普通博客文章好。特别是，在演讲中通过一系列动画解释的内容，在文中没有以相同程度的细节解释。我浏览时注意到，对每种预测器不擅长处理哪些分支的解释较少，因此对每个预测器的动机说明也较少。我可能会尝试回头添加更多动机，但不太可能完全重组文章并在有几幅静态图形与文本相邻时生成一组新的更能传达概念的图形。感谢Julien Vivenot、Ralph Corderoy、Vaibhav Sagar、Mindy Preston、Stefan Kanthak和Uri Shaked在这篇仓促写成的文章中发现错别字。</em></p><p><em>由 mimo-v2.5 模型翻译，花费 20443 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/branch-prediction/</link>
      <guid isPermaLink="false">https://danluu.com/branch-prediction/</guid>
      <pubDate>Wed, 23 Aug 2017 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Sattolo算法</title>
      <description>[AI 摘要] 本文解释了Sattolo算法如何通过交换不同循环的元素，生成一个恰好包含一个循环的排列。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文解释了Sattolo算法如何通过交换不同循环的元素，生成一个恰好包含一个循环的排列。</div><p>我最近遇到一个问题，解决方案的一部分是进行一系列指针访问，以伪随机顺序遍历一块内存。Sattolo算法为这个问题提供了解决方案，因为它生成了一个列表的排列，且该排列恰好只有一个循环，这保证了我们即使以随机顺序遍历，也能到达列表的每个元素。</p>

<p>然而，我在网上能找到的解释该算法为何有效的说明，要么使用了某种数学工具（如斯特林数、假设读者熟悉循环表示法等），要么使用了我难以跟随的逻辑。我发现对于那些可以但不必使用大量数学工具来解释的概念，这种情况很常见。我并不认为使用现有数学方法本身有什么问题——如果你熟悉这些概念，它是一个不错的思维捷径。如果你正在上组合数学课，涵盖斯特林数然后快速得出一系列结果（如果你熟悉斯特林数，这些结果的证明是微不足道的）是有意义的，但对于那些只对单个结果感兴趣的人来说，不幸的是很难找到一个相对简单、<a href="https://twitter.com/danluu/status/1147984717238562816" rel="noopener noreferrer">不需要任何背景知识</a>的解释。当我在寻找简单解释时，还发现很多人在不恰当的地方使用了Sattolo算法，也有许多人不知道他们寻找的正是Sattolo算法，所以这里尝试提供一个不假设具有本科组合数学背景的、解释该算法为何有效的说明。</p>

<p>在我们看Sattolo算法之前，先来看一下Fisher-Yates算法。这是一个<a href="https://en.wikipedia.org/wiki/In-place_algorithm" rel="noopener noreferrer">原地</a>算法，用于生成数组/向量的随机排列，其中每种可能的排列都以均匀概率出现。</p>

<p>我们将查看Fisher-Yates的代码，然后看如何证明该算法产生了预期结果。</p>

<pre><code>def shuffle(a):
    n = len(a)
    for i in range(n - 1):  # i 从 0 到 n-2，包含。
        j = random.randrange(i, n)  # j 从 i 到 n-1，包含。
        a[i], a[j] = a[j], a[i]  # 交换 a[i] 和 a[j]。
</code></pre>

<p><code>shuffle</code> 接受一个数组并产生该数组的一个排列，即洗牌。我们可以将这个循环视为依次放置数组 <code>a</code> 的每个元素，从 <code>a[0]</code> 到 <code>a[n-2]</code>。在某次迭代 <code>i</code> 中，我们从 <code>n-i</code> 个元素中选择一个进行交换，并将元素 <i> 与某个随机元素交换。数组的最后一个元素 <code>a[n-1]</code> 被跳过，因为它总是会与自身交换。一种能看出这能以均匀概率产生每种可能排列的方法是写下每个元素最终出现在任何特定位置的概率<sup id="fnref:P"><a href="#fn:P" rel="noopener noreferrer">1</a></sup>。另一种方法是观察该算法的两个事实：</i></p><i>

<ol>
<li>Fisher-Yates产生的每个输出都是以均匀概率产生的</li>
<li>Fisher-Yates产生的输出数量与排列数量相同（且每个输出都是一个排列）</li>
</ol>

<p>(1) 对于我们在算法中进行的每次随机选择，如果我们做出不同的选择，就会得到不同的输出。例如，如果我们看结果中的 <code>a[0]</code>，将原始 <code>a[k]</code>（对于某个 <code>k</code>）中的元素放置到结果 <code>a[0]</code> 中的唯一方法是在迭代 <code>0</code> 中交换 <code>a[0]</code> 和 <code>a[k]</code>。如果我们选择不同的元素进行交换，最终会得到不同的 <code>a[0]</code>。一旦我们放置了 <code>a[0]</code> 并查看结果 <code>a[1]</code>，对于 <code>a[1]</code> 以及每个 <code>a[i]</code> 同样如此。此外，每次选择都以相同的幅度缩减范围——存在某种对称性，尽管我们先放置 <code>a[0]</code>，但本可以先放置任何其他元素；每次选择都有相同的效果。这有点类似于你可以通过每次均匀随机选择一个数字来均匀随机选择一个整数的原因。</p>

<p>(2) Fisher-Yates产生多少种不同的输出？在第一次迭代中，我们固定了 <code>a[0]</code> 的 <code>n</code> 种可能选择之一，然后在给定该选择的情况下，我们固定了 <code>a[1]</code> 的 <code>n-1</code> 种选择之一，接着是 <code>a[2]</code> 的 <code>n-2</code> 种，依此类推，因此有 <code>n * (n-1) * (n-2) * ... 2 * 1 = n!</code> 种可能的不同输出。</p>

<p>这与 <code>n</code> 个元素的可能排列数完全相同，推理过程也基本相同。如果我们想计算 <code>n</code> 个元素的可能排列数，我们首先为第一个位置选择 <code>n</code> 种可能元素之一，为第二个位置选择 <code>n-1</code> 种，依此类推，得到 <code>n!</code> 种可能的排列。</p>

<p>由于Fisher-Yates只产生唯一的排列，并且输出数量与排列数量完全相同，因此Fisher-Yates产生每种可能的排列。由于Fisher-Yates以均匀概率产生每个输出，因此它以均匀概率产生所有可能的排列。</p>

<p>现在，让我们看看Sattolo算法，它几乎与Fisher-Yates相同，也产生输入的洗牌版本，但产生的东西却大不相同：</p>

<pre><code>def sattolo(a):
    n = len(a)
    for i in range(n - 1):
        j = random.randrange(i+1, n)  # i+1 而不是 i
        a[i], a[j] = a[j], a[i]
</code></pre>

<p>我们不是像在Fisher-Yates中那样随机选择一个元素进行交换，而是随机选择一个不是被放置元素的元素，即我们不允许一个元素与自身交换。这的一个副作用是没有任何元素最终停留在其原始位置。</p>

<p>在我们讨论为什么这能产生预期结果之前，让我们先确保在术语上达成共识。看待数组的一种方式是将其视为图的描述，其中索引指示节点，值指示边指向的位置。例如，如果我们有列表 <code>0 2 3 1</code>，可以将其视为从其索引指向其值的有向图，这是一个具有以下边的图：</p>

<pre><code>0 -&gt; 0
1 -&gt; 2
2 -&gt; 3
3 -&gt; 1
</code></pre>

<p>节点0指向自身（因为索引0处的值为0），节点1指向节点2（因为索引1处的值为2），依此类推。如果我们遍历这个图，会发现有两个循环。<code>0 -&gt; 0 -&gt; 0 ...</code> 和 <code>1 -&gt; 2 -&gt; 3 -&gt; 1...</code>。</p>

<p>假设我们交换位置0的元素与某个其他元素。可以是任何元素，但假设我们与位置2的元素交换。那么我们将得到列表 <code>3 2 0 1</code>，可以将其视为以下图：</p>

<pre><code>0 -&gt; 3
1 -&gt; 2
2 -&gt; 0
3 -&gt; 1
</code></pre>

<p>如果我们遍历这个图，会看到循环 <code>0 -&gt; 3 -&gt; 1 -&gt; 2 -&gt; 0...</code>。这是一个恰好有一个循环的排列的示例。</p>

<p>如果我们交换两个属于不同循环的元素，会将两个循环合并成一个循环。一种理解方式是，当我们交换列表中的两个元素时，我们实际上是抬起指向每个元素的箭头头部并交换它们的指向（而不是保持不动的箭头尾部）。追踪这个结果就像追踪一个8字形。举个例子，假设我们将 <code>0</code> 与另一个循环的任意元素交换，比如元素2，我们最终会得到 <code>3 2 0 1</code>，其唯一的循环是 <code>0 -&gt; 3 -&gt; 1 -&gt; 2 -&gt; 0...</code>。注意这个操作是可逆的——如果我们再次进行相同的交换，会再次得到两个循环。一般来说，如果我们交换来自同一循环的两个元素，会将该循环拆分成两个独立的循环。</p>

<p>如果我们向Sattolo算法输入由 <code>0 1 2 ... n-1</code> 组成的列表，我们将得到一个恰好有一个循环的排列。此外，我们生成任何具有恰好一个循环的排列的概率是相同的。让我们看看Sattolo算法为什么能生成恰好一个循环。之后，我们将弄清楚为什么它能以均匀概率生成所有可能的循环排列。</p>

<p>对于Sattolo算法，假设我们从列表 <code>0 1 2 3 ... n-1</code> 开始，即一个包含 <code>n</code> 个长度为 <code>1</code> 的循环的列表。在每次迭代中，我们进行一次交换。如果我们在两个独立的循环中交换元素，将合并这两个循环，将循环数量减少1。然后我们将进行 <code>n-1</code> 次迭代，将循环数量从 <code>n</code> 减少到 <code>n - (n-1) = 1</code>。</p>

<p>现在让我们看看为什么可以安全地假设我们总是交换来自不同循环的元素。在算法的每次迭代中，我们将某个索引 &gt; <code>i</code> 的元素与索引 <code>i</code> 处的元素交换，然后递增 <code>i</code>。由于 <code>i</code> 被递增，放入索引 <code>i</code> 的元素再也不会被交换，即每次交换将被交换的两个元素中的一个放入其最终位置，即对于每次交换，我们取两个可能可交换的元素并使其中一个变得不可交换。</p>

<p>一开始，我们有 <code>n</code> 个长度为 <code>1</code> 的循环，每个循环有 <code>1</code> 个可交换元素。当我们用某个随机元素交换初始元素时，我们将取一个可交换元素并使其不可交换，创建一个长度为 <code>2</code> 的循环（其中有 <code>1</code> 个可交换元素），并留下 <code>n-2</code> 个其他循环（每个有 <code>1</code> 个可交换元素）。</p>

<p>维护的关键不变式是每个循环恰好有 <code>1</code> 个可交换元素。当我们有 <code>n</code> 个长度为 <code>1</code> 的循环时，该不变式在开始时成立。只要这个事实成立，每次我们合并任意长度的两个循环时，我们将取一个循环中的可交换元素与另一个循环中的可交换元素交换，使两个元素中的一个变得不可交换，并创建一个仍然只有一个可交换元素的更长循环，从而维持不变式。</p>

<p>由于我们无法交换来自同一循环的两个元素，因此每次交换都会合并两个循环，每次迭代将循环数量减少1，直到我们运行了 <code>n-1</code> 次迭代，恰好剩下一个循环。</p>

<p>为了看出我们以相同概率生成每个循环，注意每种输出只有一种产生方式，即改变任何特定的随机选择都会导致不同的输出。在第一次迭代中，我们随机选择 <code>n-1</code> 种放置方式之一，然后 <code>n-2</code> 种，然后 <code>n-3</code> 种，依此类推，因此对于任何特定的循环，我们以概率 <code>(n-1) * (n-2) * (n-3) ... * 2 * 1 = (n-1)!</code> 生成它。如果我们能证明有 <code>(n-1)!</code> 个具有恰好一个循环的排列，那么我们就知道我们以均匀概率生成每个具有恰好一个循环的排列。</p>

<p>假设我们有一个长度为 <code>n</code> 的任意列表，恰好有一个循环，并且我们添加一个元素，有 <code>n</code> 种方式将其扩展为长度为 <code>n+1</code> 的循环，因为我们可以将新元素添加到 <code>n</code> 个位置中的任何一个并保持循环，这意味着长度为 <code>n+1</code> 的循环数，<code>cycles(n+1)</code>，是 <code>n * cycles(n)</code>。</p>

<p>例如，假设我们有一个产生路径 <code>0 -&gt; 1 -&gt; 2 -&gt; 0 ...</code> 的循环，并且我们想要添加一个新元素 <code>3</code>。我们可以用 <code>-&gt; 3 -&gt;</code> 替换任何一个 <code>-&gt;</code>，从而得到一个长度为4而不是长度为3的循环。</p>

<p>在基础情况下，有一个长度为2的循环，即排列 <code>1 0</code>（长度为2的另一个排列 <code>0 1</code> 有两个长度为1的循环，而不是一个长度为2的循环），因此我们知道 <code>cycles(2) = 1</code>。如果我们应用<a href="https://en.wikipedia.org/wiki/Recurrence_relation" rel="noopener noreferrer">上述递推关系</a>，得到 <code>cycles(n) = (n-1)!</code>，这恰好是Sattolo算法生成的不同排列的数量，这意味着我们生成了所有具有一个循环的可能排列。由于我们知道我们以均匀概率生成每个循环，我们现在知道我们以均匀概率生成所有可能的单循环排列。</p>

<p>另一种看待有 <code>(n-1)!</code> 个具有恰好一个循环的排列的方式是，我们将每个循环旋转，使 <code>0</code> 位于开头，并将其写为 <code>0 -&gt; i -&gt; j -&gt; k -&gt; ...</code>。这些排列的数量与 <code>0 -&gt;</code> 右侧元素的排列数量相同，即 <code>(n-1)!</code>。</p>

<h3 id="conclusion">结论</h3>

<p>我们已经研究了两个完全相同的算法，除了两个字符的更改。这些算法产生非常不同的结果——一个算法产生随机排列，另一个产生恰好有一个循环的随机排列。我认为这些算法很巧妙，因为它们非常简单，只是一个双重循环加上一个交换。</p>

<p>在实践中，你可能不“需要”知道这些算法是如何工作的，因为大多数现代语言的标准库都会提供某种生成随机洗牌的方法。如果你有一个洗牌函数，如果你不介意一个非原地且需要额外遍历的算法，你可以生成一个恰好有一个循环的排列。我将这作为读者的练习，但如果你想得到提示，一种方法类似于“替代”的看待有 <code>(n-1)!</code> 个具有恰好一个循环的排列的方式。</p>

<p>虽然我说你可能不需要知道这些，但如果你要实现自定义洗牌算法，你确实需要知道！这听起来可能显而易见，但长期以来一直有人实现不正确的洗牌算法。这在游戏和<a href="http://www.datamation.com/entdev/article.php/616221/How-We-Learned-to-Cheat-at-Online-Poker-A-Study-in-Software-Security.htm" rel="noopener noreferrer">90年代甚至21世纪初的在线赌博网站中</a>很常见，你仍然会看到偶尔实现错误的洗牌，例如当<a href="http://www.robweir.com/blog/2010/02/microsoft-random-browser-ballot.html" rel="noopener noreferrer">微软实现了一个错误的洗牌并未能正确随机化浏览器选择投票</a>时。当时，<code>javascript random array sort</code> 的Google搜索结果是<a href="https://web.archive.org/web/20100102004604/http://www.javascriptkit.com/javatutors/arraysort.shtml" rel="noopener noreferrer">微软最终使用的那个不正确的算法</a>。该网站已经修正，但你仍然可以在网上找到不正确的教程。</p>

<h4 id="appendix-generating-a-random-derangement">附录：生成随机错排</h4>

<p>一个没有任何元素停留在其原始位置的排列称为错排。当我在搜索Sattolo算法的用途时，发现很多人使用Sattolo算法来生成随机错排。虽然Sattolo算法生成错排，但它只生成恰好有一个循环的错排，而存在多于一个循环的错排（例如 <code>3 2 1 0</code>），所以它不可能以均匀概率生成随机错排。</p>

<p>生成随机错排的一种方法是使用Fisher-Yates生成随机洗牌，然后重试直到得到一个错排：</p>

<pre><code>def derangement(n):
    assert n != 1, "无法生成长度为1的错排"
    a = list(range(n))
    while not is_derangement(a):
        shuffle(a)
    return a
</code></pre>

<p>这个算法很简单，并且极有可能最终返回一个错排（当 n != 1 时），但我们应该预期运行多久才能返回结果并不立即明显。也许我们第一次尝试就得到一个错排并运行 <code>shuffle</code> 一次，或者可能需要100次尝试，我们必须做100次洗牌才能得到一个错排。</p>

<p>为了弄清楚这一点，我们想要知道随机排列（洗牌）是错排的概率。为此，我们需要知道给定一个长度为 <code>n</code> 的列表，有多少个排列以及有多少个错排。</p>

<p>既然我们已经深入附录，我将假设你知道<a href="https://en.wikipedia.org/wiki/Permutation" rel="noopener noreferrer"><code>n</code> 个元素的排列数是 <code>n!</code></a>，什么是<a href="https://en.wikipedia.org/wiki/Binomial_coefficient" rel="noopener noreferrer">二项式系数</a>，并且熟悉<a href="https://en.wikipedia.org/wiki/Taylor_series" rel="noopener noreferrer">泰勒级数</a>。</p>

<p>为了计算错排的数量，我们可以从排列数 <code>n!</code> 开始，减去有元素保留在起始位置的排列数，即 <code>(n choose 1) * (n - 1)!</code>。这不完全正确，因为它双重减去了有两个元素保留在起始位置的排列，因此我们必须加回 <code>(n choose 2) * (n - 2)!</code>。这也不完全正确，因为我们过度校正了有三个元素保留的排列，因此我们必须将它们加回来，<a href="https://en.wikipedia.org/wiki/Inclusion%E2%80%93exclusion_principle" rel="noopener noreferrer">依此类推</a>，结果是 <code>∑ (−1)ᵏ (n choose k)(n−k)!</code>。如果我们展开这个表达式并除以 <code>n!</code> 并化简，得到 <code>∑ (−1)ᵏ (1 / k!)</code>。如果我们看元素数量趋于无穷大时的极限，这看起来就像 <a href="https://en.wikipedia.org/wiki/Taylor_series" rel="noopener noreferrer">泰勒级数</a> 中的 <code>e^x</code>，其中 <code>x = -1</code>，即 <code>1/e</code>，即在极限情况下，我们预期排列中错排的比例是 <code>1/e</code>，即我们预期生成一个错排需要进行的交换次数是生成随机排列的 <code>e</code> 倍。像许多交错级数一样，这个级数收敛很快。当 <code>k = 10</code> 时，它在7位有效数字内接近 <code>e</code>！</p>

<p>关于我们算法的一个小问题是，如果我们将第一个元素放在第一个位置，我们已经知道没有错排，但我们继续放置元素直到创建整个排列。如果我们拒绝非法放置，我们可以做得比 <code>e</code> 倍的开销更好。也有可能提出<a href="http://mathforum.org/library/drmath/view/61957.html" rel="noopener noreferrer">一个非拒绝的算法</a>，但我真的很喜欢朴素的拒绝算法，因为当<a href="https://www.cs.princeton.edu/courses/archive/fall13/cos521/lecnotes/lec2final.pdf" rel="noopener noreferrer">由“不断重试”组成的简单随机算法</a>运行良好时，我觉得这很令人愉快。</p>

<h4 id="appendix-wikipedia-s-explanation-of-sattolo-s-algorithm">附录：维基百科对Sattolo算法的解释</h4>

<p>我写这个解释是因为我发现<a href="https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle" rel="noopener noreferrer">维基百科中的解释</a>相对难以理解，但如果你觉得上面的解释难以理解，也许你会喜欢维基百科的版本：</p>

<blockquote>
<p>Sattolo算法总是产生长度为n的循环这一事实可以通过归纳法证明。假设通过归纳，在循环的初始迭代之后，剩余迭代根据长度为n-1的循环排列前n-1个元素（这些剩余迭代只是应用于前n-1个元素的Sattolo算法）。这意味着追踪初始元素到其新位置p，然后将原始位于位置p的元素追踪到其新位置，依此类推，只有在访问所有其他位置后才会返回到初始位置。假设初始迭代将最后一个元素与位置k（非最终位置）的元素交换，并且随后对前n-1个元素的排列将其移动到位置l；我们将所有n个元素的排列π与剩余的前n-1个元素的排列σ进行比较。如上所述，追踪连续的位置，在到达位置k之前，σ和π之间没有区别。但在π下，原始位于位置k的元素被移动到最后一个位置而不是位置l，并且原始位于最后一个位置的元素被移动到位置l。从那里开始，π的位置序列再次跟随σ的序列，并且所有位置都将在返回到初始位置之前被访问，如要求所示。</p>
<p>至于排列的等概率性，只需观察修改后的算法涉及 (n-1)! 个不同的随机数序列，每个序列显然产生不同的排列，并且在随机数源无偏的假设下，每个序列都以等概率出现。由此产生的 (n-1)! 个不同的排列恰好穷尽了长度为n的循环集：每个这样的循环都有一个唯一的循环表示法，其中值n位于最终位置，这允许 (n-1)! 种排列剩余的值来填充循环表示法的其他位置</p>
</blockquote>

<p><em>感谢 Mathieu Guay-Paquet, Leah Hanson, Rudi Chen, Kamal Marhubi, Michael Robert Arntzenius, Heath Borders, Shreevatsa R, @chozu@fedi.absturztau.be, 和 David Turner 的评论/纠正/讨论。</em></p>

<p>
</p>
<div>

<hr>

<ol>
<li id="fn:P"><p><code>a[0]</code> 在循环的第一次迭代中被放置。假设 <code>randrange</code> 在相应范围内以均匀概率生成整数，原始 <code>a[0]</code> 与任何元素（包括自身）交换的概率是 <code>1/n</code>，因此结果 <code>a[0]</code> 是原始 <code>a</code> 中任何元素的概率是 1/n，这正是我们想要的。</p>

<p><code>a[1]</code> 在循环的第二次迭代中被放置。此时，<code>a[0]</code> 是未修改数组中的某个元素。让我们称未修改的数组为 <code>original</code>。对于某个 <code>k</code>，<code>a[0]</code> 是 <code>original[k]</code>。对于任何特定的 <code>k</code> 值，它包含 <code>original[k]</code> 的概率是 <code>1/n</code>。然后我们将 <code>a[1]</code> 与范围 <code>[1, n-1]</code> 内的某个元素交换。</p>

<p>如果我们想弄清楚 <code>a[1]</code> 是 <code>original</code> 中某个特定元素的概率，我们可以这样想：<code>a[0]</code> 是 <code>original[k_0]</code>，对于某个 <code>k_0</code>。然后 <code>a[1]</code> 变成 <code>original[k_1]</code>，对于某个 <code>k_1</code>，其中 <code>k_1 != k_0</code>。由于 <code>k_0</code> 是均匀随机选择的，如果我们对所有 <code>k_0</code> 积分，<code>k_1</code> 也是均匀随机的。</p>

<p>另一种看法是，我们放置 <code>a[0]</code> 并选择 <code>k_0</code>，然后再放置 <code>a[1]</code> 并选择 <code>k_1</code>，这是任意的。我们完全可以先放置 <code>a[1]</code> 并选择 <code>k_1</code>，因此，在所有可能的选择中，选择 <code>k_0</code> 不可能偏向选择 <code>k_1</code>。
 <a href="#fnref:P" rel="noopener noreferrer"><sup>[返回]</sup></a></p></li>
</ol>
</div></i><p><em>由 mimo-v2.5 模型翻译，花费 12488 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/sattolo/</link>
      <guid isPermaLink="false">https://danluu.com/sattolo/</guid>
      <pubDate>Wed, 9 Aug 2017 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>终端延迟</title>
      <description>[AI 摘要] 该文章通过测量和分析指出，许多终端的输入延迟过高，足以影响用户体验，而常见的性能基准测试（如stdout吞吐量）对此关注不足。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 该文章通过测量和分析指出，许多终端的输入延迟过高，足以影响用户体验，而常见的性能基准测试（如stdout吞吐量）对此关注不足。</div><p><a href="https://www.youtube.com/watch?v=vOvQCPLkPt4" rel="noopener noreferrer">微软研究院在2012年有一个精彩的演示，展示了延迟对平板电脑使用体验的影响</a>。如果你不想看那个三分钟的视频，他们基本上制作了一个可以模拟任意延迟的设备，延迟可低至几毫秒以下。在100毫秒（1/10秒）时——这是消费级平板电脑的典型值——体验非常糟糕。在10毫秒（1/100秒）时，延迟可以被注意到，但体验尚可。而在&lt;1毫秒时，体验非常好，如同纸笔般流畅。如果你想亲自看看这个效果的迷你版本，可以试试用随机一款安卓平板搭配手写笔，与当前一代的iPad Pro搭配Apple Pencil进行对比。Apple设备的端到端延迟远高于10毫秒，但差异仍然相当明显——这足以让我实际上会用新的iPad Pro来记笔记或画图表，而我觉得安卓平板完全无法胜任纸笔替代品。</p>

<p>如果你尝试不同延迟的VR头显，也能看到类似的现象。<a href="http://oculusrift-blog.com/john-carmacks-message-of-latency/682/" rel="noopener noreferrer">20毫秒感觉良好，50毫秒感觉有延迟，而150毫秒则令人难以忍受</a>。</p>

<p>奇怪的是，我很少听到有人抱怨键盘和鼠标输入慢。一个原因可能是键盘和鼠标输入很快，输入的反馈几乎是即时的。但我认为这并非事实。人们经常告诉我这是真的，但我认为恰恰相反。认为计算机对输入响应很快，快到人类无法察觉延迟的观点，是我从专业程序员那里听到的最常见的性能相关谬误。</p>

<p>当人们测量普通计算机上游戏的端到端延迟时，通常发现延迟在<a href="http://renderingpipeline.com/2013/09/measuring-input-latency/" rel="noopener noreferrer">100毫秒</a><a href="https://www.youtube.com/watch?v=GxaEJY-zd_4&amp;index=5&amp;list=PLfOoCUS0PSkXVGjhB63KMDTOT5sJ0vWy8&amp;t=187s" rel="noopener noreferrer">的范围内</a>。</p>

<p>如果我们看看<a href="http://renderingpipeline.com/2013/09/measuring-input-latency/" rel="noopener noreferrer">Robert Menzel对游戏端到端管线的分析</a>，就不难理解为什么我们预期会有100+毫秒的延迟：</p>

<ul>
<li>~2毫秒（鼠标）</li>
<li>8毫秒（等待游戏处理输入的平均时间）</li>
<li>16.6毫秒（游戏模拟）</li>
<li>16.6毫秒（渲染代码）</li>
<li>16.6毫秒（GPU正在渲染上一帧，当前帧被缓存）</li>
<li>16.6毫秒（GPU渲染）</li>
<li>8毫秒（错过垂直同步的平均时间）</li>
<li>16.6毫秒（显示器内部的帧缓存）</li>
<li>16.6毫秒（重绘帧）</li>
<li>5毫秒（像素切换）</li>
</ul>

<p>请注意，这假设使用的是游戏鼠标和相当不错的LCD显示器；鼠标和像素切换的延迟通常会慢得多。</p>

<p>通过调整可以达到40毫秒的范围，但绝大多数用户不会进行这种调整。即使他们调整了，这仍然远未达到平板电脑和VR开始真正“感觉正确”的10毫秒至20毫秒范围。</p>

<p>按键到显示的测量主要是在游戏中进行的，因为游戏玩家比大多数人更关心延迟，但我认为大多数应用程序在延迟方面与游戏相差不大。虽然游戏通常比“典型”应用程序每帧做更多的工作，但它们也比“典型”应用程序优化得好得多。Menzel为游戏分配了33毫秒，一半用于游戏逻辑，一半用于渲染。非游戏应用程序需要多少时间？Pavel Fatin为文本编辑器测量了这个，发现延迟范围从<a href="https://pavelfatin.com/typing-with-pleasure/" rel="noopener noreferrer">几毫秒到几百毫秒不等</a>，他<a href="https://github.com/pavelfatin/typometer" rel="noopener noreferrer">使用他编写的一个应用程序进行了测量</a>，该应用程序使用<a href="https://docs.oracle.com/javase/7/docs/api/java/awt/Robot.html" rel="noopener noreferrer">java.awt.Robot</a>生成按键并进行屏幕截图，我们可以用它来测量其他应用程序的延迟。</p>

<p>就个人而言，我想看看不同终端和shell的延迟，原因有几个。首先，我大部分时间都在终端中度过，通常也在终端中进行编辑，所以我看到的延迟至少是终端的延迟。其次，我看到被引用最多的终端基准测试（至少多出两个数量级）是终端显示输出的速度，通常是通过在大型文件上运行<code>cat</code>来测量的。这几乎是我能想到的最没用的基准测试。我不记得上次有什么任务是受限于我向终端的<code>stdout</code>执行<code>cat</code>文件的速度（除非我是在emacs中使用eshell），也想不出任何任务对这个子测量有用。我关心的最接近的东西是当我意外地向<code>stdout</code>输出太多内容时，我能多快按下<code>^C</code>中止命令。但正如我们在查看实际测量时将看到的，终端吸收大量<code>stdout</code>输入的能力与其对<code>^C</code>的响应速度只有微弱的关联。我能滚动整个页面的速度听起来相关，但在实际测量中，这两者并不高度相关（例如，emacs-eshell滚动很快，但接收<code>stdout</code>极慢）。另一件我关心的事情是延迟，但知道某个终端具有高<code>stdout</code>吞吐量，对我了解其延迟几乎没有帮助。</p>

<p>让我们看看一些不同的终端，看看是否有些终端增加了足够的延迟，以至于我们预期差异会很明显。如果我们在我的笔记本电脑上测量从按键到内部屏幕捕获的延迟，我们会看到不同终端的以下延迟</p>

<p><img src="https://danluu.com/images/term-latency/idle-terminal-latency.svg" alt="终端尾部延迟图">
<img src="https://danluu.com/images/term-latency/loaded-terminal-latency.svg" alt="终端尾部延迟图"></p>

<p>这些图表显示了每个终端延迟的分布。Y轴是延迟（毫秒）。X轴是百分位数（例如，50代表50%百分位按键，即中位数按键）。除非另有说明，测量是在macOS上进行的。左侧的图表是机器空闲时的情况，右侧的图表是负载下的情况。如果我们只看中位数延迟，有些设置看起来还不算太糟——terminal.app和emacs-eshell在未加载时大约是5毫秒，小到很多人不会注意到。但大多数终端（st、alacritty、hyper和iterm2）都在<a href="https://pdfs.semanticscholar.org/386a/15fd85c162b8e4ebb6023acdce9df2bd43ee.pdf" rel="noopener noreferrer">你可能预期人们会注意到</a><a href="http://www.tactuallabs.com/papers/howMuchFasterIsFastEnoughCHI15.pdf" rel="noopener noreferrer">额外延迟的范围内</a>，即使机器空闲。如果我们看机器空闲时的尾部延迟，比如99.9%百分位延迟，每个终端都进入了根据用户交互研究应该被察觉的范围。作为参考，某些终端内部生成按键到GPU内存的往返时间比<a href="http://ipnetwork.bgtmo.ip.att.net/pws/network_delay.html" rel="noopener noreferrer">从波士顿发送数据包到西雅图<em>再返回</em>所需的时间</a>（约70毫秒）还要慢。</p>

<p>所有测量都是在输入仅发生在单个终端上、电池充满并使用交流电源供电的情况下完成的。负载测量是在编译Rust时进行的（同样，使用充满电的电池和交流电源供电，并且为了使测量可重现，每次测量在下载所有依赖项后15秒开始进行Rust的干净构建，每次运行之间留有足够时间以避免跨运行的热节流干扰）。</p>

<p>如果我们看中位数加载延迟，除了emacs-term，大多数终端在负载下的表现并不比空闲时差多少。但当我们看尾部测量，比如90%百分位或99.9%百分位测量时，每个终端都变得慢得多。在macOS和Linux之间切换会有一些差异，但不同终端的差异也不同。</p>

<p>这些测量远非最坏情况（如果我们使用低电量的电池供电，并在编译开始10分钟后进行以加剧热节流，很容易看到几百毫秒的延迟）但即便如此，每个终端的尾部延迟都是可以观察到的。另外，请记住，这只是总端到端延迟的一小部分。</p>

<p>为什么人们不抱怨键盘到显示的延迟，就像他们抱怨手写笔到显示的延迟或VR延迟那样？我的理论是，对于VR和平板电脑，人们都有使用低得多延迟应用程序的大量经验。对于平板电脑，这个“应用程序”是纸和笔；对于VR，这个“应用程序”是不戴VR头显时转头。但每个应用程序的输入到显示延迟都太差了，以至于大多数人只是习惯了糟糕的延迟。</p>

<p>另一个理论可能是，键盘和鼠标输入与平板电脑输入有根本性的不同，使得延迟不那么明显。即使没有任何数据，我也觉得这不可信，因为当我通过增加几十毫秒额外延迟的方式访问远程终端时，我发现打字有明显的滞后感。而事实证明，当额外延迟进行A/B测试时，<a href="http://forums.blurbusters.com/viewtopic.php?f=10&amp;t=1134" rel="noopener noreferrer">人们能够并且确实注意到了我们正在讨论的这个范围内的延迟</a>。</p>

<p>只是为了能将最常用的基准测试（stdout的吞吐量）与延迟进行比较，让我们测量不同终端接收stdout输入的速度：
</p><div><p></p>

<table>
<thead>
<tr>
<th>终端</th>
<th>stdout<br>(MB/s)</th>
<th>空闲50%<br>(毫秒)</th>
<th>负载50%<br>(毫秒)</th>
<th>空闲99.9%<br>(毫秒)</th>
<th>负载99.9%<br>(毫秒)</th>
<th>内存<br>(MB)</th>
<th>^C</th>
</tr>
</thead>

<tbody>
<tr>
<td>alacritty</td>
<td>39</td>
<td>31</td>
<td>28</td>
<td>36</td>
<td>56</td>
<td>18</td>
<td>正常</td>
</tr>

<tr>
<td>terminal.app</td>
<td>20</td>
<td>6</td>
<td>13</td>
<td>25</td>
<td>30</td>
<td>45</td>
<td>正常</td>
</tr>

<tr>
<td>st</td>
<td>14</td>
<td>25</td>
<td>27</td>
<td>63</td>
<td>111</td>
<td>2</td>
<td>正常</td>
</tr>

<tr>
<td>alacritty tmux</td>
<td>14</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>

<tr>
<td>terminal.app tmux</td>
<td>13</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>

<tr>
<td>iterm2</td>
<td>11</td>
<td>44</td>
<td>45</td>
<td>60</td>
<td>81</td>
<td>24</td>
<td>正常</td>
</tr>

<tr>
<td>hyper</td>
<td>11</td>
<td>32</td>
<td>31</td>
<td>49</td>
<td>53</td>
<td>178</td>
<td>失败</td>
</tr>

<tr>
<td>emacs-eshell</td>
<td>0.05</td>
<td>5</td>
<td>13</td>
<td>17</td>
<td>32</td>
<td>30</td>
<td>失败</td>
</tr>

<tr>
<td>emacs-term</td>
<td>0.03</td>
<td>13</td>
<td>30</td>
<td>28</td>
<td>49</td>
<td>30</td>
<td>正常</td>
</tr>
</tbody>
</table>

<p></p></div>
终端接收<code>stdout</code>的速度与其延迟之间的关系并不明显。就此而言，终端接收<code>stdout</code>的速度与其看起来是否流畅之间的关系也不明显。在这次测试中，terminal.app看起来非常慢。滚动的文本跳跃很多，好像屏幕很少更新。此外，hyper和emacs-term在这个测试中都遇到了问题。Emacs-term真的无法跟上输出速度，测试完成后需要几秒钟显示才能完成更新（显示已输出多少行的状态栏似乎是最新的，所以它在测试结束前就完成了递增）。Hyper更加落后，在闪烁几次后几乎不再更新屏幕。<code>Hyper Helper</code>进程的CPU使用率锁定在100%大约两分钟，整个期间终端完全无响应。<p></p>

<p>alacritty使用tmux进行了测试，因为alacritty不支持向上滚动，文档中指出如果你想向上滚动应该使用tmux。只是为了有另一个参考，terminal.app也使用tmux进行了测试。对于大多数终端，tmux似乎不会降低<code>stdout</code>速度，但alacritty和terminal.app足够快，以至于它们实际上受限于tmux的速度。</p>

<p>Emacs-eshell从技术上讲不是一个终端，但我也测试了eshell，因为它可以在某些用例中作为终端的替代品。实际上，Emacs（包括eshell和term）的速度足够慢，以至于我关心它接收<code>stdout</code>的速度。过去使用eshell或term时，我发现如果运行一个将大量日志输出到<code>stdout</code>或<code>stderr</code>的命令，我有时需要等待几千行文本滚动过去。由于这种情况很少发生，对我来说除非它慢到我需要等待半秒或一秒，否则真的不是什么大问题，而没有其他终端慢到需要在意这个。</p>

<p>相反，我足够频繁地输入单个字符，以至于我会注意到尾部延迟。假设我以120字/分钟的速度打字，这相当于每分钟600个字符，或者每秒10个字符的输入。那么我预期每100秒就会看到一次99.9%尾部延迟（千分之一）！</p>

<p>总之，我更关心的<code>cat</code>“基准测试”是当我意外运行一个命令向屏幕输出数百万行而非数千行内容时，我能否<code>^C</code>中止该进程。对于这个基准测试，除了hyper和emacs-eshell外，所有终端都没问题，这两个终端都挂起了至少十分钟（我在十分钟后终止了每个进程，而不是等待终端追上）。</p>

<p>启动时的内存使用也包含在表中作为参考，因为这是另一个我看到人们用来测试终端的测量。虽然我觉得终端启动时使用40MB内存有点荒谬，但即使是我在用的这台三年前的淘汰笔记本电脑也有16GB RAM，所以将40MB压缩到2MB对用户体验没有任何显著影响。天啊，甚至我们最近买的300美元Chromebook都有16GB RAM。</p>

<h3 id="conclusion">结论</h3>

<p>大多数终端的延迟足够大，如果终端作者更专注于延迟而非其他功能或其他方面的性能，用户体验本可以得到改善。然而，当我搜索终端基准测试时，我发现终端作者（如果他们测试任何东西的话）<a href="https://github.com/jwilm/alacritty/issues/289" rel="noopener noreferrer">测试的是</a><a href="https://github.com/jwilm/alacritty/issues/205" rel="noopener noreferrer">接收stdout的速度</a>或启动时的内存使用。这很不幸，因为大多数“低性能”终端接收<code>stdout</code>的速度已经比人类能跟上的速度快好几个数量级，因此进一步优化<code>stdout</code>吞吐量对大多数用户的实际用户体验影响相对较小。对于减少空闲终端在我这台现在相当低端的笔记本电脑上使用0.01%内存的内存使用来说，情况也是如此。</p>

<p>如果你从事终端开发，也许可以考虑相对更多地优化延迟和交互性（例如对<code>^C</code>的响应性），相对更少地优化吞吐量和空闲内存使用。</p>

<p><em>更新：作为对本文的回应，<a href="https://github.com/jwilm/alacritty/issues/673" rel="noopener noreferrer">alacritty的作者解释了alacritty的延迟来源，并描述了alacritty如何降低其延迟</a></em></p>

<h3 id="appendix-negative-results">附录：阴性结果</h3>

<p>Tmux和延迟：我尝试了tmux和各种终端，发现差异在测量噪声范围内。</p>

<p>Shell和延迟：我尝试了多个shell，发现即使在最快的终端中，shell之间的差异也在测量噪声范围内。Powershell在我的测试设置中使用起来有些问题，因为它不能正确处理颜色（输入的第一个字符显示为终端指定的颜色，但其他字符无论设置如何都是黄色的，<a href="https://github.com/lzybkr/PSReadLine/issues/472" rel="noopener noreferrer">这似乎是一个待解决的问题</a>），这让我使用的图像识别设置感到困惑。<a href="https://www.youtube.com/watch?v=cz5Hczlzvio" rel="noopener noreferrer">Powershell也不能始终如一地将光标放在它应该在的位置</a>——它在一行内随机跳跃，这也让我使用的图像识别设置感到困惑。然而，尽管有其他问题，powershell的性能与其他shell相当。</p>

<p>Shell和stdout吞吐量：如上所述，不同shell之间的速度差异在测量噪声范围内。</p>

<p>单行与多行文本和吞吐量：尽管一些文本编辑器在处理极长的行时会变慢，但当我将一个大文件放入终端时，无论该文件全是单行还是每80个字符换行一次，吞吐量都相似。</p>

<p>队头阻塞/协调遗漏：我以每秒10.3个字符的速率运行这些测试。但事实证明这并不重要，人类能够达到的输入速率和延迟与每10.3秒输入一次非常相似。可以压垮终端，hyper是第一个在高输入率下开始崩溃的，但使尾部延迟变差所需的速度超出了我所知的任何人类的打字速度。</p>

<h3 id="appendix-experimental-setup">附录：实验设置</h3>

<p>所有测试都在一台双核2.6GHz 13英寸2014年中MacBook Pro上完成。该机器有16GB RAM和2560x1600屏幕。操作系统版本是OS X 10.12.5。一些测试在Linux（Lubuntu 16.04）上进行，以比较macOS和Linux。每次延迟测量都进行了1万次按键。</p>

<p>延迟测量使用<code>.</code>键完成，吞吐量测量使用默认的<code>base32</code>输出完成，这都是纯ASCII文本。George King指出，不同类型的文本可能会改变输出速度：</p>

<blockquote>
<p>我注意到Terminal.app在输出非拉丁Unicode范围时会显著变慢。我知道有三件事可能导致这种情况：必须加载不同的字体页面，必须解析BMP之外的代码点，以及宽字符。</p>

<p>第一点可能归结为字体字形的延迟加载、字体回退计算以及字形页面的缓存（或其他类似机制）的非常复杂的混合。</p>

<p>第二点有点推测性，但我敢打赌Terminal.app使用了基于UTF16的Cocoa NSString，由于代理对的原因，当代码点高于BMP时几乎肯定会进入慢速路径。</p>
</blockquote>

<p>终端在运行测试前都已全屏显示。这会影响测试结果，并且调整终端窗口大小会并确实显著改变性能（例如，在保持其他条件不变的情况下调整窗口大小可以使hyper比iterm2更慢）。macOS上的st是在XQuartz下作为X客户端运行的。为了看看XQuartz本身是否缓慢，我尝试了<a href="https://github.com/doy/runes/" rel="noopener noreferrer">runes</a>，另一个使用XQuartz的“原生”Linux终端；runes的尾部延迟比st和iterm2好得多。</p>

<p>“空闲”延迟测试是在新重启的机器上完成的。所有终端都在运行，但输入一次只发送到一个终端。</p>

<p>“负载”延迟测试是在Rust编译开始15秒后进行的，编译在后台运行。</p>

<p>终端带宽测试通过创建一个大型伪随机文本文件完成：</p>

<pre><code>timeout 64 sh -c 'cat /dev/urandom | base32 &gt; junk.txt'
</code></pre>

<p>然后运行：</p>

<pre><code>timeout 8 sh -c 'cat junk.txt | tee junk.term_name'
</code></pre>

<p>Terminator和urxvt没有测试，因为它们在Mac上并非完全容易安装，我不想费劲去让它们工作。Terminator很容易从源码构建，但它启动时挂起，没有达到shell提示符。Urxvt通过brew安装，但它的一个依赖项（也通过brew安装）版本错误，导致无法启动。</p>

<p><small>
感谢Kamal Marhubi、Leah Hanson、Wesley Aptekar-Cassels、David Albert、Vaibhav Sagar、Indradhanush Gupta、Rudi Chen、Laura Lindzey、Ahmad Jarara、George King、Tim Dierks、Nikith Naide、Veit Heller和Nick Bergson-Shilcock的评论/纠正/讨论。
</small></p><p><em>由 mimo-v2.5 模型翻译，花费 10821 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/term-latency/</link>
      <guid isPermaLink="false">https://danluu.com/term-latency/</guid>
      <pubDate>Tue, 18 Jul 2017 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>关于鼠标与键盘效率的广泛引用研究完全不可信</title>
      <description>[AI 摘要] 本文反驳了关于鼠标效率高于键盘的广泛引用研究，指出这些研究方法存在问题，且实际任务中各有利弊。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文反驳了关于鼠标效率高于键盘的广泛引用研究，指出这些研究方法存在问题，且实际任务中各有利弊。</div><p>键盘和鼠标哪个更快？许多程序员相信键盘在所有（编程相关的）任务上都更快。然而，一些被广泛引用的AskTog网页声称，苹果的研究表明，鼠标在所有方面都比键盘更快，而那些认为键盘更快的人只是在自欺欺人。这听起来可能有些极端，但举个例子，其中一页说作者“从未见过[键盘]的表现超过鼠标”。</p>

<p>但鼠标不可能在所有任务上都更快——几乎没有人能用鼠标在屏幕上点击软键盘比用物理键盘打字更快。相反，有一些任务更适合使用鼠标而非键盘（例如，第一人称射击游戏中的瞄准）。对于一个没有预设立场的人来说，问题不应该是在所有任务上哪个更快，而是哪些任务用键盘更快，哪些用鼠标更快，以及哪些任务在两者结合使用时更快？</p>

<p>你可能会问这是否重要。这要看情况！我认识的一位最好的程序员是“二指禅”打字员，所以显然成为优秀程序员并不一定需要特别快的输入速度。但我正在做一个简单的数据处理任务，其速度受限于我能输入大量枯燥代码的速度。如果我能更快，这个任务就能更快完成，我也会去做那些现在没时间做的其他任务。我可以打字速度超过100字/分钟，这还不错，但我可以说话速度超过400字/分钟，而我的思考速度远超说话速度。即使在说话时，我经常也感到受到限制；打字就更差了，在这里花半秒、那里花一秒进行导航操作显然也无助于效率。当我刚进入科技行业时，我从事一个普通的测试/验证/质量保证角色，主要工作是分类测试失败。即使在我开始自动化任务之前，我每天能分类的bug数量几乎是同一职位其他人的两倍，因为我认真对待基本导航任务的效率。如今，我的工作不再90%是重复性的，但我估计我在电脑前大约三分之一的时间都花在那些受我的输入和导航速度限制的机械性任务上。如果我能更快完成这些枯燥的任务，花更少时间在上面，而把更多时间花在有趣的事情上，那就太好了。</p>

<p>无论如何，首先，让我们看看被引用的研究，看看鼠标在哪些地方确实更快。网上的大多数引用，追溯源头后，都指向AskTog，这是一个由<a href="https://en.wikipedia.org/wiki/Bruce_Tognazzini" rel="noopener noreferrer">Bruce Tognazzini</a>（他自称为“人机交互设计领域的公认领导者”）创建的网站。</p>

<p><a href="http://www.asktog.com/TOI/toi06KeyboardVMouse1.html" rel="noopener noreferrer">被引用最多的AskTog页面声称他们投入了5000万美元的研发资金，并进行了各种研究</a>；该页面声称，除了其他发现外，这5000万美元的研发显示“测试对象一致报告打字比使用鼠标快”，而“秒表一致证明使用鼠标比打字快”。该论点声称这既证明了鼠标比键盘更快，也解释了为什么程序员认为键盘比鼠标更快（尽管实际上更慢）。然而，这个结果无法复现，因为“Tog”不仅没有引用实验细节，他甚至没有描述实验，只是做出了一个笼统的断言。</p>

<p><a href="http://www.asktog.com/TOI/toi22KeyboardVMouse2.html" rel="noopener noreferrer">第二个被广泛引用的AskTog页面</a>是对前一页回应的回应，它只是重复说第一页已经证明了键盘快捷键更慢。尽管有很多讽刺意味的话，比如“也许我们这些年一直被误导了。也许那些一次又一次显示Mac用户生产力更高、学得更快、购买更多软件包等等的独立研究，不知何故都是有缺陷的。也许……”，但和之前一样，没有引用实际结果。然而，其中有一个伪科学的解释，说明为什么鼠标比键盘更快：</p>

<blockquote>
<p>命令键并不更快。正如我从八月专栏中了解到的那样，决定使用一个命令键所花费的时间与使用鼠标一样长。区别在于，命令键的决定是一个高级认知功能，不会形成长期记忆。因此，主观上，键盘似乎更快，而实际上使用它们通常需要同样长的时间。</p>

<p>由于获取鼠标是一个低级认知功能，用户在获取鼠标期间无需中断对主要任务的认知过程。因此，使用鼠标的用户实现了更高的生产力。</p>
</blockquote>

<p>这引发了一个问题：为什么在键盘上打字就与使用命令键不同呢？确实有些人不精通触摸打字，在打字时需要思考要按哪个键。这些人打字非常慢，甚至可能比那些能快速使用鼠标在软键盘上打字的人更慢。但也有精通键盘的人，他们可以在有意识地思考要按哪些键的情况下打字。这里隐含的断言是，不可能像精通键盘打字那样精通命令键。这可能是真的，但我认为这个说法非常不可信，无论是从原则上，还是从我观察到的那些确实似乎精通命令键的人来看，而且这个说法没有提供任何证据支持。</p>

<p><a href="http://www.asktog.com/SunWorldColumns/S02KeyboardVMouse3.html" rel="noopener noreferrer">第三个被广泛引用的AskTog页面引用了一个单一的实验</a>，作者输入了一段文字，然后需要将所有的“e”替换为“|”，分别使用方向键或鼠标。作者发现，使用方向键的平均时间是99.43秒，使用鼠标的平均时间是50.22秒。没有提供关于段落长度或“e”数量的信息。第三页是对一位用户引用具体编辑示例（他们发现使用键盘比使用鼠标更快）的回应。</p>

<p>我的基准测试经验是，绝大多数微基准测试都有错误或误导性的结果，因为它们很难正确设置，而且即使设置正确，要理解微基准测试结果与现实世界结果之间的关系也需要对该领域有深入的理解。因此，除非作者证明对其特定领域的基准测试有深入的理解，否则我对来自微基准测试的广泛主张持深度怀疑态度，即使那样，我也会问为什么他们认为其结果具有普遍性。认为微基准测试很难正确解读的观点在<a href="https://twitter.com/shipilev/status/709982588673388544" rel="noopener noreferrer">理解基准测试的人群中被广泛认同</a>。</p>

<p>描述的<code>e -&gt; |</code>替换任务不仅是一个微基准测试，而且是一个奇怪的人工微基准测试。</p>

<p>根据结果中给出的时间，该任务要么是为非常初级的用户设计的，要么是禁止使用任何查找和替换功能。这篇特定的AskTog专栏文章是对一位提到编辑任务的程序员的回应，因此这个微基准测试没有意义，除非那个程序员被困在一个不允许使用编辑器基本功能的实验中。此外，替换任务本身不切实际——人们多久会将<code>e</code>替换为<code>|</code>一次？</p>

<p>我计时了这个任务（移除了奇怪的禁止查找替换限制），得到了以下结果：</p>

<ul>
<li>键盘快捷键：1.26秒</li>
<li>M-x，“replace-string”（而不是使用映射的键盘快捷键）：2.8秒</li>
<li>用鼠标导航到查找替换功能：5.39秒</li>
</ul>

<p>第一个结果是使用键盘快捷键得到的。第二个结果是我在别人的Emacs设置中可能会做的（它映射了不同的快捷键）；Emacs允许通过按“M-x”并输入完整的命令名称来运行命令。这比直接使用键盘快捷键慢得多，但仍然比使用鼠标快（至少对我来说，在这里）。这是否意味着键盘很棒而鼠标很糟糕？不，这个结果几乎完全没意义，因为我几乎不花时间做单字符查找替换，所以单字符查找替换的速度无关紧要。</p>

<p>此外，因为我习惯使用键盘，这里的鼠标速度可能异常慢。这一点在这里尤其明显，因为我通常的编辑器设置（<code>emacs -nw</code>）不允许使用鼠标，所以我最终为鼠标测试使用了一个不熟悉的编辑器<code>TextEdit</code>。我每个任务只做了一次，以避免“练习”特定任务（这可能会不切实际地让键盘快捷键版本几乎瞬间完成，因为快速按下一连串练习过的键很容易）。然而，这意味着我使用的是一个不熟悉的鼠标，在不熟悉的菜单中操作。此外，像许多很久以前玩过电子游戏的人一样，我习惯于关闭“<a href="https://www.google.com/search?q=mouse+acceleration" rel="noopener noreferrer">鼠标加速度</a>”，但Mac默认开启此功能，我没有进行必要的繁琐步骤来禁用鼠标加速度。另外，我使用的录制程序（QuickTime）使整个机器变慢，这可能对鼠标速度的影响大于对键盘速度的影响，而且我碰巧使用的程序的菜单设置迫使我在两个级别的菜单中导航。</p>

<p>话虽如此，尽管不习惯使用鼠标，如果我想找一个我使用鼠标比使用键盘更快的微基准测试，这很容易：让我试试选择一个屏幕上但不在光标附近的文本块：</p>

<ul>
<li>键盘：1.8秒</li>
<li>鼠标：0.7秒</li>
</ul>

<p>我倾向于通过在Emacs中搜索块开头的某个东西、设置标记，然后搜索标记结尾的某个东西来选择文本块。我通常输入三个字符以确保获得唯一的文本片段（如果我认为三个字符不够，我会输入更多）。这使得选择任务比替换任务稍慢，因为替换任务使用单个字符，而此任务使用多个字符。</p>

<p>鼠标更适合选择文本块，以至于即使在一个不熟悉的鼠标设置中（我最终不得不进行修正，而不是能够一次完成选择），鼠标的速度仍然快了一倍多。但是，如果我想选择屏幕外的内容，而且选择范围很大，无法在一屏内显示，那么键盘时间不会改变，而鼠标时间会变得慢得多，使得键盘更快。</p>

<p>除了进行测量，我还（非正式地）询问了人们他们认为键盘或鼠标对于特定任务哪个更快。查找替换和选择文本这两个任务对大多数人来说结果是显而易见的。但并非所有任务都显而易见；滚动就是一个人们没有强烈倾向的任务。让我们看看滚动，这是一个键盘和鼠标都很适合的任务。为了具体，让我们看看向下滚动4页：</p>

<ul>
<li>键盘：0.49秒</li>
<li>鼠标：0.57秒</li>
</ul>

<p>虽然有一些差异，而且我怀疑如果我重复足够多次实验，可能会得到统计上显著的结果，但差异很小，不足以具有实际意义。</p>

<p>与Tog的结果（每个人都认为键盘更快，而实际上鼠标更快）相反，我发现人们很擅长估计哪个设备对哪个任务更快，也擅长估计何时两种设备会给出相似的结果。一个可能的原因是我调查的是程序员，特别是<a href="https://www.recurse.com/scout/click?t=b504af89e87b77920c9b60b2a1f6d5e8" rel="noopener noreferrer">RC</a>的程序员，他们可能与Tog在他的研究中可能研究的群体不同。他在一个研究如何设计通用计算机UI的小组中工作，在80年代，专注于研究那些许多人在成长过程中使用计算机、然后选择了一份整天使用计算机职业的人，这实际上是不合理的。对等的群体将不得不在60年代甚至更早开始使用计算机，但即使他们做到了，输入设备也相当不同（滚球鼠标直到1972年才发明，而且发明后肯定没有立即广泛使用）。研究那些相对不精通计算机输入设备的人群没有错，但将这些结果推广到那些相对精通的人群就有问题了。</p>

<p>与键盘或鼠标的倡导者的主张不同，当我亲自做实验时，结果是混合的。有些任务使用键盘会快得多，而有些使用鼠标会快得多。此外，大多数结果很容易预测（当结果相似时，预测是很难预测）。如果我们看看网上最广泛引用的、权威的结果，会发现它们做出了非常强烈的主张，认为鼠标比键盘快得多，但除了一个站不住脚的实验外，没有任何证据支持这些主张。可能那些值得称道的5000万美元研发投入到了有效的实验中，但如果这些实验存在，它们并未被引用。</p>

<p>我花了一些时间回顾这个主题的文献，但没有找到任何确凿的证据。与其像<a href="https://danluu.com/empirical-pl/" rel="noopener noreferrer">我在另一个有争议的话题上做的那样</a>逐点总结每项研究，我将提到使这些研究对我来说无关紧要的高级问题。我能找到的所有研究都至少存在以下列出的一个问题；如果你有一个研究链接，不是因为以下原因之一而无关紧要，我很乐意了解！</p>

<ol>
<li>研究年代久远：不清楚80年代中期关于与计算机交互的研究如何转化为今天人们与计算机的交互方式。即使忽略编辑程序的差异，界面也存在巨大差异。鼠标更精确，一个不错的现代光学鼠标可以移动得像人能移动的那么快而不会跟踪不稳定，这在我试过的任何80年代鼠标上都不成立，只有90年代的高质量鼠标（在球最近清洁过且鼠标放在优质鼠标垫上时）才成立。键盘改进没有那么大，但即便如此，在现代低键程键盘上打字也比在80年代的任何键盘上快得多。</li>
<li>狭窄的微基准测试：并非所有这些都像那个没有查找替换功能的<code>e -&gt; |</code>任务一样无关紧要，但即使对于那些并非明显无关紧要的任务，也不清楚结果对我的实际工作有什么影响。</li>
<li>并非键盘与鼠标的比较：已发表的研究中只有一小部分是关于键盘与鼠标的比较。当一项研究是关于设备交互时，通常是关于某种新型设备或新的交互模型。</li>
<li>描述模糊：许多研究会说类似“发现了7.8%的改进，结果在p &lt; 0.005下显著”这样的话，却没有提供足够的信息来判断结果是否真的具有实际意义，而不仅仅是统计显著性（回想一下，实际上无意义的滚动结果差异是0.08秒，这也可以报告为16.3%的改进）。</li>
<li>不熟练的用户：在一篇典型的论文中，他们注意到用户可能需要长达两秒钟的时间才能将鼠标从屏幕一侧移动到另一侧的滚动条。虽然为了找出对使用最困难的用户来说最容易的界面类型而研究不熟练的用户有其价值，但一项研究用户需要2秒才能将鼠标移到滚动条上的研究似乎与我的用户体验无关。当我为自己计时时，从屏幕另一侧移动到滚动条并滚动一小段距离需要0.21秒，尽管使用了一个不熟悉的鼠标（与我习惯的不同灵敏度）并运行着一个使鼠标操作比平时更困难的录制程序。</li>
<li>看似不合理的结果：一些研究声称显示从一种设备切换到另一种设备时总体生产力大幅提高（例如，从一种鼠标切换到另一种鼠标带来20%的总体生产力提升）。</li>
</ol>

<h4 id="conclusion">结论</h4>

<p>Tog的组织花费5000万美元进行的神秘研究完全有可能证明，除了原始文本输入外，鼠标在所有任务上都更快，但似乎没有足够的信息来了解实际研究是什么。关于用户输入有很多公开研究，但我找不到任何相关的研究能告诉我是否应该在边际上更多或更少地使用鼠标。</p>

<p>当我亲自查看各种任务时，结果是混合的，并且与我调查的大多数程序员预测的方式一致。这个结果如此乏味，如果不是有大量的人相信键盘总是比鼠标快或反之亦然，它几乎不值得一提。</p>

<p>请让我知道是否有相关主题的研究我应该阅读！我不熟悉相关领域，所以可能我用了错误的关键词搜索或读错了论文。</p>

<h4 id="appendix-note-to-self">附录：给自己</h4>

<p>我意识到相对于搜索（博客文章中没有明确提到，但占文本选择任务的一半）来说，滚动是如此之快。我倾向于使用搜索来滚动到屏幕外的内容，但看来当我没有光标位置要求时，应该考虑使用滚动。</p>

<p><small>感谢Leah Hanson、Quentin Pradet、Alex Wilson和Gaxun对本文的评论/修正，感谢Annie Cherkaev、Chris Ball、Stefan Lesser和David Isaac Lee的相关讨论。</small></p><p><em>由 mimo-v2.5 模型翻译，花费 8244 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/keyboard-v-mouse/</link>
      <guid isPermaLink="false">https://danluu.com/keyboard-v-mouse/</guid>
      <pubDate>Tue, 13 Jun 2017 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>创业公司期权 vs. 现金</title>
      <description>[AI 摘要] 本文质疑为何创业公司不支付现金而是提供期权，并分析了期权价值常被高估的原因以及少数非愤世嫉俗的理由。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文质疑为何创业公司不支付现金而是提供期权，并分析了期权价值常被高估的原因以及少数非愤世嫉俗的理由。</div><p>我经常与声称其薪酬方案预期价值高于 Facebook、Google、Twitter 或 Snapchat 等公司同等方案的创业公司交流。对于这种说法，我不理解的一点是：如果这是真的，为什么创业公司不去找投资者，按他们声称的期权价值出售期权，然后用现金支付我？期权的非显性价值及其波动性是招聘的一大障碍。</p>

<p>此外，根据我的风险函数和风险投资家（VC）的风险函数，这对每个人似乎都是一笔更好的交易。和大多数人一样，额外收入给我的效用是递减的，但风险投资家在收入方面的效用可以说是近乎线性的。而且，即使风险投资家与我有相同的风险函数，由于风险投资家持有的是多元化的投资组合，同样的期权对他们来说价值比对我更高，因为他们能比我更有效地分散下行风险。如果这些创业公司对其期权价值的声称属实，那么这里应该存在一个能让所有各方都变得更好的交易。</p>

<p>在十年前的一系列经典文章中，Paul Graham 似乎旨在说服人们去创办或加入创业公司，他写道：“如果你想致富，你会怎么做？我认为你最好的选择是创办或加入一家创业公司。几个世纪以来，这一直是可靠的致富途径”，以及“风险和回报总是成正比的。”这种风险回报论断被用来支持这样一个观点：通过加入创业公司并接受高风险的股权包，人们在预期中能比接受支付现金或现金加公开股票的工作赚更多的钱。然而，其前提——风险和回报<em>总是</em>成正比——在一般情况下并不成立。基础金融课程指出，只有那些风险无法被分散的资产才会承担风险溢价（平均而言）。既然风险投资家能够并且确实分散了风险，就没有理由相信一个通过在创业公司工作“投资”期权的员工会因为所承担的风险而获得划算交易。顺便说一下，当你审视历史回报时，<a href="https://web-beta.archive.org/web/20121118012653/http://www.kauffman.org/uploadedfiles/vc-enemy-is-us-report.pdf" rel="noopener noreferrer">风险投资基金的业绩似乎并没有超过其他投资类别</a>，尽管它们购买的是一种比普通员工获得的期权下行风险更小的股权。</p>

<p>那么，为什么创业公司不能或不愿意获得更多投资并用现金支付员工呢？让我们先看看一些愤世嫉俗的原因，然后再看看一些没那么愤世嫉俗的原因。</p>

<h3 id="cynical-reasons">愤世嫉俗的原因</h3>

<p>一种可能的答案，也许是最简单的答案是，期权并不值创业公司声称的价值，而且创业公司更喜欢期权，因为其缺乏价值的事实不如现金那么明显。一个简单的论据是，如果你看看投资者为早期或中期创业公司的一小部分支付的价格，以及公司如果将员工期权池给投资者所能筹集的额外现金，通常不足以支付员工具有竞争力的薪酬方案。鉴于风险投资家平均而言并没有超额回报，这似乎意味着员工期权的价值并不像创业公司经常声称的那么高。如果你能说服人们接受一系列价值未知的彩票票根，而不是现金，薪酬成本会便宜得多。</p>

<p>员工期权被错误表述的一些常见方式有：</p>

<h4 id="strike-price-as-value">将行权价作为价值</h4>

<p>一家公司给你 100 万份期权，行权价为 10 美元，可能会声称这些期权“价值”1000 万美元。然而，如果股价在期权有效期内一直保持在 10 美元，这些期权最终将价值 0，因为行权价为 10 美元的期权是一种以 10 美元购买股票的权利，这与授予价值为每股 10 美元的实际股票不同。</p>

<h4 id="public-valuation-as-value">将公开估值作为价值</h4>

<p>假设一家公司通过出售 30% 的股份筹集了 3 亿美元，使公司的隐含估值达到 10 亿美元。我看到最常见的错误表述是，公司会声称因为他们给了例如 0.1% 公司的期权，你的期权价值就是 10 亿 * 0.001 = 100 万美元。一个相关且常见的错误表述是，公司去年融资后价值有所增加，例如，公司价值翻了一番，所以你的期权价值 200 万美元。即使你假设行权价为 0，并采用公司最后一次融资时的估值，你的期权的隐含价值也不是 100 万美元，因为投资者购买的是与你作为员工获得的股份类别不同的股票。</p>

<p>风险投资家获得的优先股和员工获得的普通股之间存在许多差异；让我们看几个具体的场景。</p>

<p>假设那些为公司 30% 股份支付 3 亿美元的投资者拥有直线（1倍）清算优先权，公司以 5 亿美元出售。1 倍清算优先权意味着投资者在低贱的普通股股东获得任何东西之前，将首先拿回其投资额的 1 倍，因此投资者将获得 3 亿美元作为其 30% 股份的回报。其余 70% 的股权将分配剩下的 2 亿美元：你持有的行权价为 0 美元的 0.1% 普通股期权价值 28.5 万美元（而不是你可能预期的 5 亿 * 0.001 = 50 万美元）。</p>

<p>风险投资家获得的优先股通常<em>至少</em>有 1 倍清算优先权。假设在上述场景中，投资者拥有 2 倍清算优先权。他们将在普通股股东分配公司剩余部分之前拿回投资额的 2 倍。由于 2 * 3 亿 &gt; 5 亿，投资者将拿走所有东西，其余的股权持有者将获得 0。</p>

<p>你的普通股和优先股的另一个区别是，优先股有时附带反稀释条款，而你作为普通工程雇员没有机会获得这种条款。让我们看一个<a href="https://news.ycombinator.com/item?id=11200296" rel="noopener noreferrer">真实公司稀释的实例</a>。Mayhar 在公司估值为 500 万美元时获得了 0.4% 的股份。到公司价值 10 亿美元时，Mayhar 在公司的股份被稀释了 8 倍，这使得他在公司的份额价值低于 50 万美元（减去他行使期权的成本），而不是 400 万美元（减去行使期权的成本）。</p>

<p>这个故事还有一些额外的复杂因素，说明了期权通常比看起来价值更低的其他原因。Mayhar 在加入时负担不起行权费用（支付行权价乘以他所持期权对应的股份数），这对于那些并非来自富裕家庭、刚大学毕业就加入创业公司的人来说很常见。四年后他离开时，他负担得起行权成本，但由于美国税法的一个怪异之处，他要么负担不起税单，要么不想为这个仍然是彩票票根的东西支付这笔费用——当你行使期权时，你实际上是对当前估值与行权价之间的差额征税。即使公司在几年后成功上市，价值增长 10 倍，你仍然在行使期权的那一年负有税单责任（如果公司长期保持私有状态或失败，你什么也得不到，只有未来的税收抵扣）。由于，像大多数期权一样，Mayhar 的期权有 90 天的行权窗口，他没有从期权中得到任何东西。</p>

<p>虽然这比平均稀释程度要严重，但也存在更糟糕的情况，例如，投资者和高级管理层基本上保留了他们的股权，而其他人则被<a href="http://avc.com/2010/11/employee-equity-how-much/#comment-100654148" rel="noopener noreferrer">稀释到股权变得毫无价值</a>的情况。</p>

<p>这些只是优先股和普通股之间的差异导致期权价值与根据公开估值天真计算的价值可能大相径庭的众多方式中的一部分。我经常看到公司和员工都使用公开的优先股估值作为基准来精确地评估普通股期权的价值，但这是不可能的，即使在原则上，如果没有公司的股权结构表（显示不同投资者持有公司多少股份）以及每次投资的具体细节。即使你能得到这些（通常你得不到），确定插入模型以得出预期价值的适当数字也并非易事，因为它需要回答诸如“在收购中，高层管理人员会与投资者勾结，把一切都留给自己，而让员工一无所获的概率是多少？”这样的问题。</p>

<h4 id="black-scholes-valuation-as-value">将布莱克-斯科尔斯估值作为价值</h4>

<p>由于上述问题，人们有时会尝试使用模型来估计期权的价值。布莱克-斯科尔斯模型因其知名度和易于使用的封闭解而被普遍使用，它是最常用的模型。不幸的是，布莱克-斯科尔斯模型的大多数主要假设对于创业公司期权来说是错误的，这使得布莱克-斯科尔斯模型的输出与你的期权实际价值之间的关系不明确。</p>

<h4 id="options-are-often-free-to-the-company">期权对公司来说通常是免费的</h4>

<p>当员工自愿或非自愿离开时，很大一部分期权会返还给员工期权池。我找不到全面的数据，但据轶事传闻，听说超过 50% 的期权最终被从员工那里收回并返还给通用池。Dan McKinley 指出<a href="https://medium.com/eshares-blog/broken-cap-tables-bbf84574a76a" rel="noopener noreferrer">一项（未经证实的）分析显示，只有 5% 的员工授予的期权被行权</a>。即使按照保守估计，授予期权打五折听起来也相当不错。打 20 折听起来就很惊人了，这解释了为什么公司如此喜欢期权。</p>

<h4 id="present-value-of-a-future-sum-of-money">未来金钱总和的现值</h4>

<p>当有人说创业公司的薪酬方案和 Facebook 的一样值钱时，他们通常是指在 N 年内支付的总价值相似。但固定名义金额的金钱越早到手就越值钱，因为你可以（至少）将其投资于低风险资产，如国债，并获得一些回报。</p>

<p>这是你在经济学入门课上会听到的抽象论点，但在实践中，如果你生活在一个生活成本相对较高的地方，比如旧金山或纽约，尽早拿到钱而不是晚拿还有更大的价值，因为它让你可以住在一个相对不错的地方（无论你如何定义不错），而不需要和比美国其他地方认为合理的数量更多的室友挤在一起。上几代的许多创业公司似乎推迟了它们的首次公开募股（IPO）；对于那些有合同禁止他们在二级市场出售期权的公司的员工来说，这很可能意味着他们潜在的大部分财富在他们职业生涯的头十年都被锁定了。即使创业公司的薪酬方案在考虑通货膨胀和利息后价值更高，对于大多数还不算富裕的人来说，这是否是一个好的选择也不清楚。</p>

<h3 id="non-cynical reasons">非愤世嫉俗的原因</h3>

<p>我们已经看了公司可能想提供期权而非现金的一些愤世嫉俗的原因，即他们可以声称他们的期权比实际价值更高。现在，让我们看看公司可能想发放股票期权的一些非愤世嫉俗的原因。</p>

<p>从员工的角度来看，一个非愤世嫉俗的原因可能是股票期权回溯日期（backdating），至少在这个漏洞被大部分填补之前是这样。直到 2000 年代早期，许多公司都回溯了期权授予的日期。让我们看一个<a href="http://www.law.harvard.edu/faculty/jfried/option_backdating_and_its_implications.pdf" rel="noopener noreferrer">由 Jessie M. Fried 解释的例子</a></p>

<blockquote>
<p>授予 Reyes 的期权覆盖 120 万股。报告的授予日期是 2001 年 10 月 1 日，当时该公司的股票交易价格约为每股 13 美元，是当年最低的收盘价。一周后，股价为每股 20 美元，一个月后，股价收盘接近每股 26 美元。</p>

<p>Brocade 在其 2002 年的委托书（proxy statement）中向投资者披露了这次授予，表格标题为“上一财政年度的期权授予”，按照 SEC 规定的格式编制。除其他外，该表描述了这次及对高管的其他授予的细节，包括期权覆盖的股份数量、行权价和期权的到期日。分析师（包括那些汇编标准普尔著名的 ExecuComp 数据库的人）使用此表中的信息，根据授予日计算每次期权授予的 Black Scholes 值。在计算价值时，分析师基于公司关于设定行权价程序的陈述，假设期权是按当时价格授予的（at-the-money）。计算出的价值随后被股东、研究人员和媒体广泛用于估算 CEO 的总薪酬。为 Reyes 的 120 万股股票期权授予计算出的 Black Scholes 值（分析师假设是按当时价格授予的）为 1320 万美元。</p>

<p>然而，SEC 已得出结论，授予 Reyes 的期权被回溯日期了，实际授予日的市场价格可能约为每股 26 美元。让我们假设股票在期权实际授予时确实在以每股 26 美元交易。因此，如果 Brocade 遵守其仅授予按当时价格期权的政策，它应该给 Reyes 行权价为每股 26 美元的期权。相反，它给了 Reyes 行权价为每股 13 美元的期权，因此这些期权有 13 美元的内在价值（in the money）。并且它报告这次授予时，就好像它在股价为 13 美元时给了 Reyes 按当时价格的期权。</p>

<p>如果 Brocade 在股价为 26 美元时授予 Reyes 按当时价格的期权，行权价为 26 美元，那么期权授予的 Black Scholes 值将约为 2600 万美元。但由于这些期权有 1300 万美元的内在价值，它们甚至更有价值。根据一项估计，它们价值 2800 万美元。因此，如果分析师被告知 Reyes 在股价为 26 美元时收到了行权价为 13 美元的期权，他们会报告其价值为 2800 万美元，而不是 1320 万美元。简而言之，在这个特定场景中回溯这个期权授予的日期，将使 Brocade 能够多给 Reyes 200 万美元价值的期权（Black Scholes 值），同时报告的金额却少了 1500 万美元。</p>
</blockquote>

<p>虽然股票期权回溯日期现在不再（轻易地）可能，但可能还有其他漏洞或税法的后果使得期权比现金更划算。我一时只能想到一个原因，所以我花了几周时间询问人们（包括多位创始人）关于创业公司可能更喜欢期权而非等值现金的非愤世嫉俗的原因。</p>

<h4 id="tax-benefit-of-isos">激励性股票期权（ISOs）的税收优惠</h4>

<p>在美国，<a href="https://en.wikipedia.org/wiki/Incentive_stock_option" rel="noopener noreferrer">激励性股票期权（ISOs）</a>具有这样的特性：如果持有期在行权日期后一年以及授予日期后两年，期权所有者只需对行权价与股价之间的差额缴纳长期资本利得税，而不是普通收入税。通常，资本利得税率低于普通收入税率。</p>

<p>这并不像听起来那么好，因为行权价与股价之间的差额需缴纳替代最低税（AMT）。我个人认为这不相关，因为我倾向于尽快卖出雇主股票以尽可能分散风险，但如果你有兴趣弄清楚 AMT 如何影响你在行使 ISOs 时的税单，请参阅<a href="https://www.nceo.org/articles/stock-options-alternative-minimum-tax-amt" rel="noopener noreferrer">此解释</a>了解更多信息。对于加州居民，加州在州层面对资本利得的处理也相对较差，这也使得这种差异比你从资本利得税与普通收入税率比较中预期的要小。</p>

<h4 id="tax-benefit-of-qsbs">合格小企业股票（QSBS）的税收优惠</h4>

<p>有一类股票可免于<a href="https://blog.wealthfront.com/qualified-small-business-stock-2016/" rel="noopener noreferrer">联邦资本利得税</a>和许多州的州税（但在加州不行）。这很有趣，但似乎人们很少在符合条件时利用它，而且许多创业公司并不符合条件。</p>

<h4 id="tax-benefit-of-other-options">其他期权的税收优惠</h4>

<p><a href="https://www.irs.gov/taxtopics/tc427.html" rel="noopener noreferrer">美国国税局（IRS）指出</a>：</p>

<blockquote>
<p>大多数非法定期权（nonstatutory options）没有容易确定的公允市场价值。对于没有容易确定的公允市场价值的非法定期权，授予期权时不产生应税事件，但在你行权时，你必须将收到股票的公允市场价值减去支付的金额计入收入。在你出售通过行使期权获得的股票时，会产生应税收入或可抵扣损失。你通常将此金额视为资本利得或损失。</p>
</blockquote>

<h4 id="valuations-are-bogus">估值是虚假的</h4>

<p>股票期权的一个特点是，要符合 ISOs 的资格，行权价必须至少等于公允市场价值。对于上市公司来说这很容易确定，但私营公司股份的公允市场价值有点随意。对于 ISOs，我对要求的理解是公司必须“<a href="https://www.law.cornell.edu/uscode/text/26/422" rel="noopener noreferrer">善意地尝试</a>”确定公允市场价值。对于其他类型的期权，有<a href="http://avc.com/2010/11/employee-equity-the-option-strike-price/" rel="noopener noreferrer">其他法规规定公允市场价值的定义</a>。无论如何，创业公司通常每年一到 N 次聘请外部公司为其普通股进行公允市场价值估算。这至少在假设的“真实”估值和用于期权目的的公允市场价值之间造成了两个可能的差距。</p>

<p>首先，估值更新相对不频繁。我常听到一个说法是，公司的估值很久没更新了，现在公司价值翻了一番，所以你基本上是打了五折。</p>

<p>其次，进行估值的公司缺乏产生“正确”估值的激励。这些公司是由创业公司付费的，而创业公司在法律估值尽可能低时会受益。</p>

<p>我并不真的相信这些事情让期权变得很神奇，因为我从创业公司和创始人那里听到了完全一样的说法，这意味着他们的报价已经考虑了这些因素并相应定价。然而，如果法律估值和“真实”估值之间存在很大差距，并且这使得公司能够有效地提供更高的薪酬（就像股票期权回溯日期那样），我可以看出这如何会促使公司倾向于选择期权。</p>

<h4 id="control">控制权</h4>

<p>即使员工获得了与风险投资家相同类别的股票，如果创始人将员工持有的股权转移给风险投资家，他们的控制权也会减少，因为员工持有的股权分散在相对较多的人手中。</p>

<h4 id="retention">留住人才</h4>

<p>这个答案经常被作为非愤世嫉俗的原因告诉我。其理念是，如果你给员工期权并设置一个条款禁止他们在二级市场出售期权，许多员工将无法在不放弃大部分薪酬的情况下离职。个人认为，这对我来说是一个愤世嫉俗的原因，但并非每个人都这么看。例如，Andreessen Horowitz 的执行合伙人<a href="http://www.benkuhn.net/clawback" rel="noopener noreferrer">Scott Kupor 最近提出了一个方案，如果员工在流动性事件之前离职，将失去所有期权</a>，据称是为了帮助员工。</p>

<p>无论你是否认为雇主能够长期锁定员工是好是坏，期权锁定似乎是一个糟糕的留才机制——用现金支付的公司似乎有更好的留任率。举个例子，Netflix 支付的薪资与 Google 等地方高级职位的总薪酬相当，而且据轶事传闻，他们的员工流失率似乎低于时尚的湾区创业公司。事实上，即使 Netflix 经常大声宣扬如果员工不合适就会让他们走人，但他们的非自愿流失率似乎并不高于时尚的湾区创业公司——他们只是对此更诚实，这是因为他们招聘的卖点不包括你离职时拿到低于市场水平的薪酬。如果你认为这种比较不公平，因为 Netflix 最近的记忆里已不是创业公司，你可以与金融类创业公司比较，例如 Headlands，它与 Uber、Airbnb 和 Stripe 成立于同一时代。他们（以及其他一些金融创业公司）支付高额现金，这似乎并没有导致比给予非流动性期权授予的同龄创业公司更高的流失率。</p>

<p>在那些因此导致员工比原本停留更久的案例中，期权锁定往往对所有相关方都是一笔糟糕的交易。这种情况对员工显然很糟糕，而且平均而言，公司也不想要不快乐的、只等待归属期或流动性事件的人。</p>

<h4 id="incentive-alignment">激励一致性</h4>

<p>另一个常被提及的原因是，如果你给人们期权，他们会更努力地工作，因为当公司表现好时他们也会表现好。这是最激烈地给出的理由（“你不应该信任一个只对支票感兴趣的人”等）。</p>

<p>然而，据我所知，用期权支付报酬几乎完全将工作表现与薪酬脱钩。如果你看看像微软、谷歌、苹果和 Facebook 这样让许多人致富的公司，几乎没有任何变得富有的员工对公司成功起到了关键作用。谷歌和微软各自让数千人致富，但其中绝大多数人只是碰巧在正确的时间出现在正确的地点，并且很可能在另一份不会让他们致富的工作中同样容易。相反，绝大多数创业公司期权包最终价值很少甚至为零，但几乎没有哪个期权最终变得一文不值的员工是导致期权变得一文不值的关键。</p>

<p>如果期权是薪酬的重要组成部分，选择一家将会成功的公司比努力工作重要得多。参考一下，<a href="http://www.nytimes.com/1992/06/28/business/microsoft-s-unlikely-millionaires.html?pagewanted=all" rel="noopener noreferrer">据估计微软到 1992 年创造了大约 10^3 名百万富翁</a>（经通胀调整，为 175 万美元）。随后股价上涨了 20 多倍。微软以让那些并非特别出色的人致富而闻名；据估计，到 90 年代末，他们总共让 10^4 人致富。这些人中的绝大多数与微软竞争对手中类似职位的人没有什么不同。他们只是碰巧中了一张中奖的彩票。这与创始人声称他们通过给予期权所获得的东西相反。如上所述，像 Netflix 这样支付现金的公司，似乎不存在员工生产力的问题。</p>

<p>顺便说一句，通过在微软工作致富的人中有很大一部分是在其 1986 年 IPO 之后加入的。谷歌也是如此，虽然 Facebook 还太年轻，我们无法很好地了解 IPO 后的长期情况，但在 IPO 一两年后（2012 年，5 年前）加入的人为自己做得相当不错。在 IPO 前加入的人做得更好，但如上所述，大多数人的个人财富回报是递减的。使得风险投资运作的同样幂律分布也意味着，仅微软一家公司在 1986 年至 1999 年间通过 IPO 让人们致富的数量，就可能超过同期所有 IPO 前科技公司创造的总和。类似的情况可能在 2004 年至 Facebook IPO 的 2012 年间对谷歌来说也是成立的，即使将因 Facebook IPO 致富的人算作由 IPO 前公司致富的人，你也可以对苹果做类似的计算。</p>

<h4 id="vc-firms-vs-the-market">风险投资公司 vs. 市场</h4>

<p>对于风险投资回报（因此创业公司股权）无法跑赢市场的说法，有几个潜在的反驳论点。</p>

<p>一个论点是，当人们这么说时，他们通常是指风险投资家扣除费用后，风险投资基金的回报没有跑赢市场。作为获得创业期权的员工，你不会（直接）支付风险投资费用，这意味着你可以通过保留风险投资费用来跑赢市场。</p>

<p>另一个论点是，一些投资者（如 YC）似乎一直表现相当不错。如果你加入一家由精明投资者资助的创业公司，你也可以做得很好。要理解这一点，你必须意识到公司价值高于“预期”，而公司没有同样的认识，因为需要公司给你一个没有恰当评估其价值的期权包。为了让你有这种期望并得到一笔好交易，这不仅要求创始人不要对公司成功的概率过于自信，实际上还要求创始人信心不足。虽然这不是不可能的，但我听说的大多数创业公司报价都有相反的问题。</p>

<h3 id="investing">投资</h3>

<p>本节是 2020 年的更新。本文最初撰写时，我没有意识到不是极其富有的人也可以投资创业公司。但搬到旧金山后，我发现实际上投资创业公司非常容易，而且你不需要特别富有（对于程序员来说）就可以这样做——<a href="https://www.freshpaint.io/blog/anatomy-of-a-seed-round-during-covid-19" rel="noopener noreferrer">人们通常在种子轮会接受小支票（小至 5000 美元或更少）</a>。如果你能直接投资种子轮，这比作为早期员工加入是一笔更划算的交易。</p>

<p>截至撰写本文时，公司以 1000 万美元估值进行种子轮融资相当常见。这意味着你需要投资 10 万美元才能获得 1%，这大约相当于你期望作为非常早期员工获得的股权。然而，如果你加入公司，你的股权将分四年归属，你获得的股权类别更差，而且你（通常）获得关于公司股权结构的信息要少得多。作为投资者，你只需要投资 2.5 万美元就能获得相当于早期员工 1 年的股权。此外，你可以投资多家公司，这能给你带来更好的风险调整回报。根据大公司目前支付的水平（高级工程师年薪可能约 38 万美元，资深工程师年薪约 60 万美元），在大公司工作并每年花 2.5 万美元投资创业公司，从财务回报的角度来看，严格优于在创业公司工作。</p>

<h3 id="conclusion">结论</h3>

<p>有许多因素可以使期权比看起来更有价值或更不值钱。从员工的角度来看，使期权比看起来更有价值的因素可以使股权价值比天真计算高出数十个百分点。使期权比看起来更不值钱的因素，其方式大多难以量化。</p>

<p>是使期权相对更有价值的因素占主导，还是使期权相对更不值钱的因素占主导，这是一个实证问题。我的直觉是使期权相对更不值钱的因素更强，但这只是猜测。从公开数据了解这一点的方法是查看成功的创业公司的 S-1 文件。由于本文已经约 5000 字，我会留待另一篇文章，但我会指出，在我初步浏览的少数几家 99 百分位（估值超过 10 亿美元）退出案例中，中位数员工的表现似乎比在 Facebook/Google/Amazon 标准职业道路上的人要差。</p>

<p>从公司的角度来看，有几个因素可以通过向员工发放相对更多的期权、向投资者发行相对更少的股权，让公司保留更多的杠杆/控制权。</p>

<p>这一切对创始人和投资者来说听起来都很好，但我看不出这对员工有什么好处。如果你有我遗漏的其他原因，我很乐意听听。</p>

<p>_如果你喜欢这篇文章，你可能也喜欢<a href="//danluu.com/startup-tradeoffs/" rel="noopener noreferrer">这篇关于在大公司工作与在创业公司工作之间权衡的另一篇文章</a>。</p>

<h3 id="appendix-caveats">附录：注意事项</h3>

<p>许多创业公司并不声称他们的报价在财务上有竞争力。随着时间的推移，我听到的“如果你想致富，你会怎么做？我认为你最好的选择是创办或加入一家创业公司。几个世纪以来，这一直是可靠的致富途径。”越来越少，而“我们在财务上无法与 Facebook 竞争，但是……”越来越多。我从多位创始人那里听说，与创始人股权和工作量相比，作为早期员工加入是一笔极不划算的交易。</p>

<p>一些创业公司提供的报价实际上与大公司的报价具有竞争力。我看到一些试图提供有吸引力的报价的创业公司，对于“资深”人士，他们愿意支付比上市公司高得多的薪水，因为他们理解期权由于其时间线、风险状况和预期价值对员工来说并不好。</p>

<p>报价存在巨大差异，其中很大一部分实际上是随机的。我知道有个人从一家创业公司（通常不给出特别强的报价）获得了比谷歌更优厚的报价，如果你四处打听，你会听到很多这样的案例。即使在平均薪酬不具竞争力的创业公司，创业公司的报价也并非总是低于谷歌/Facebook/亚马逊的报价。</p>

<p>本文中与税收相关的任何内容都是针对美国的。例如，我被告知在加拿大，“你可以推迟缴纳行权时行权价远低于公允市场价值的期权的税款，直到处置，只要公司是加拿大控制并在加拿大运营的”。</p>

<p>你可能会反对我们审视期权时的同样推理逻辑也可以应用于限制性股票单元（RSUs），甚至是上市公司的 RSUs。确实如此，尽管创业公司期权最大的缺点被减轻或不存在了，但现金相比 RSUs 仍然对员工有显著优势。不幸的是，我知道的唯一一家在招聘中利用这一点来发挥优势的非金融公司是 Netflix；如果你能想到其他使用相同薪酬模式的科技公司，请告诉我。</p>

<p>一些创业公司有一个滑动比例，允许你选择不同金额的期权/薪水组合。我还没见过一个报价让你可以将滑块调到 100% 现金和 0% 期权（或 100% 期权和 0% 现金），但总有人愿意给你一个全现金报价。</p>

<p>在当前环境下，审视公开退出案例可能会使数据偏向不太成功的公司。过去几代创业公司中最成功的那些尚未通过收购退出的公司，目前都选择不进行 IPO。有可能，一旦所有数据齐全，加入创业公司的平均回报看起来会大不相同（尽管我怀疑中位数回报不会改变太多）。</p>

<p>顺便说一句，我并不反对接受创业公司的报价，即使它很低。我大学毕业时接受了我拿到的最低报价，我的伴侣最近也接受了她得到的最低报价（与最高报价相差近 2 倍）。有很多理由你可能会接受一个并非财务上最优的报价。然而，我认为你应该知道自己将要面对什么，并且不要接受一个你以为财务上很棒，但实际上只是平庸甚至糟糕的报价。</p>

<h3 id="appendix-non-counterarguments">附录：非反驳论点</h3>

<p>我听到的最常见异议是，大多数创业公司没有足够的钱支付等值的现金，并且无法通过出售“通常”属于他们的员工期权池来筹集那么多钱。也许吧，但这并不是一个反驳论点——它是在论证大多数创业公司的期权价值不足以兑换成等值的金钱，即期权并不像声称的那么值钱。这种论点可以用各种方式表述（例如，支付工资而非期权会增加消耗率，减少跑道，使创业公司默认死亡等），但这种形式的论点本质上等同于承认创业公司期权价值不高，因为如果期权价值足够高，典型的薪酬方案应该与谷歌或 Facebook 典型的“资深”报价一样值钱。</p>

<p>如果你不相信这一点，想象一家典型估值的创业公司，正处于给新员工发放 0.1% 股权期权的阶段。现在想象某个非理性的旁观者愿意达成一笔交易，以 10 亿美元的价格获得公司 0.1% 的股份。是拿这笔钱并从 10 亿美元现金池中支付员工，还是用期权池中 0.1% 的期权片支付员工，哪个更值得？除非你认为手头现金与股权价值之间的比率接近无限大，否则你的答案应该是“是”。像“期权优于现金，因为支付现金会增加消耗率，使创业公司在任何估值下默认死亡”这样的绝对陈述，等同于说正确的比率是无限大。这显然是荒谬的；存在某个正确的比率，我们可能在正确比率是多少上有分歧，但对于典型的创业公司来说，正确的比率不应该无限大。既然这是一个如此常见的异议，如果你有这个异议，我想问你的是，为什么你不主张创业公司应该支付更少的现金和更多的期权？论点是当前的比例恰好是最优的吗？如果是，为什么？此外，为什么在筹集金额大致相同、处于同一阶段的不同公司之间，这个比例变化这么大？所有这些公司给出的都是最优报价吗？</p>

<p>第二常见的异议是，如果你选对了创业公司并使用合适的模型来评估期权，创业公司期权实际上价值很高。也许吧，但如果这是真的，为什么他们不能通过给风险投资家更多真实价值的股权来筹集更多资金，然后支付现金呢？</p>

<p>另一个常见的异议类似于“我认识很多从创业公司赚了 100 万美元的人”。我也是，但我也认识很多人在上市公司工作赚了远超这个数。本文是关于薪酬方案的相对价值，而非绝对价值。</p>

<h4 id="acknowledgements">致谢</h4>

<p>感谢 Leah Hanson、Ben Kuhn、Tim Abbott、David Turner、Nick Bergson-Shilcock、Peter Fraenkel、Joe Ardent、Chris Ball、Anton Dubrau、Sean Talts、Danielle Sucher、Dan McKinley、Bert Muthalaly、Dan Puttick、Indradhanush Gupta 和 Gaxun 提供的评论和修正。</p><p><em>由 mimo-v2.5 模型翻译，花费 16210 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/startup-options/</link>
      <guid isPermaLink="false">https://danluu.com/startup-options/</guid>
      <pubDate>Wed, 7 Jun 2017 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>网络臃肿如何影响网速慢的用户</title>
      <description>[AI 摘要] 本文探讨了现代网页臃肿如何导致在慢速连接下的用户难以甚至无法访问网站，并通过案例和数据说明了问题所在，呼吁重视网页性能优化。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文探讨了现代网页臃肿如何导致在慢速连接下的用户难以甚至无法访问网站，并通过案例和数据说明了问题所在，呼吁重视网页性能优化。</div><p>几年前，我从威斯康星州开车前往华盛顿州，沿途大多住在乡村的酒店里。我预料到农村地区可能缺乏有线网络，网速会较慢，但依然惊讶地发现一大部分网站无法访问。一些样式轻量的博客还可以阅读，学术人员那些自1995年起就没更新过样式的网站也能用。但几乎没有商业网站是可用的（谷歌除外）。当我测量我的连接时，发现带宽大致相当于90年代我用的56k调制解调器。延迟和丢包率比拨号上网的平均水平糟糕得多：延迟在500ms到1000ms之间，丢包率在1%到10%之间。这些数字和我在拨号上网状况差的日子看到的数据差不多。</p>

<p>尽管我的连接只比90年代差一点，但绝大部分网站都无法加载。为什么网站不能在拨号或类似拨号的连接下正常工作呢？如果我试图看YouTube或浏览Pinterest，那确实很难在没有带宽的情况下提供视频和图片。但我的在线兴趣在媒体方面相当“无聊”。我在线上消费的内容几乎都是纯文本，即使它们碰巧用图片和花哨的JavaScript进行了样式化。事实上，我最近尝试使用w3m（一个基于终端的网页浏览器，默认不支持CSS、JavaScript，甚至图片）使用了一周，结果发现我经常访问的网站中只有两个在w3m下不太好用（Twitter和Zulip，至少按照我的使用方式，它们本质上是基于文本的网站）<sup id="fnref:M"><a href="#fn:M" rel="noopener noreferrer">1</a></sup>。</p>

<p>最近，当我在使用不稳定的移动连接尝试阅读一篇Joel on Software的博文时，我再次意识到网页在慢速连接下的表现有多差。HTML加载完成了，但五个CSS请求或十三个JavaScript请求中的一个超时了，导致页面损坏。我没有看到文章，而是看到了<a href="https://twitter.com/danluu/status/823286780560437248" rel="noopener noreferrer">整整三页的侧边栏、菜单和广告</a>，然后才看到标题，因为页面需要某种布局修改才能合理显示。页面的设计常常使得如果某个依赖项未能加载，页面就难以甚至无法阅读。在慢速连接下，至少一个依赖项失败是很常见的。刷新两次后，页面正常加载了，我得以阅读这篇相当引人入胜的关于消除依赖项的博文。</p>

<p>抱怨人们不再像过去那样关心性能，我们正在让臃肿毫无必要地拖慢速度，这属于“老头冲云喊话”的范畴；我可能听起来像那个抱怨他的文字处理器以前只占1MB内存，现在却需要1GB内存的人。当然，那可以优化，但花时间做优化是有实际成本的，而且即使一台300美元的笔记本电脑也配有2GB内存，所以何必费心呢？但情况并不完全相同——关心网页性能的不只是我这样的技术迷。当微软查看实际测量的连接速度时，他们发现<a href="https://blogs.microsoft.com/on-the-issues/2018/12/03/the-rural-broadband-divide-an-urgent-national-problem-that-we-can-solve/" rel="noopener noreferrer">一半的美国人没有宽带速度</a>。见鬼，仅AOL在2015年就有200万拨号上网用户。在美国以外，网速慢的人更多。我最近和本·库恩聊了聊，他花了不少时间在非洲，关于他的网络连接：</p>

<blockquote>
<p>我见过在埃塞俄比亚吉吉加晚上使用移动热点时，ping延迟高达约45秒，丢包率高达50%的情况。（我现在就在这里，目前有150ms的ping延迟且无丢包，但现在是上午10点。）一天中有些时段延迟很少低于10秒，丢包率低于10%。过去大约一年网络好了很多；以前除了清晨，其他时间一直都那么差。</p>

<p>……</p>

<p>Speedtest.net报告下载速度2.6 mbps，上传速度0.6 mbps。我意识到可能不应该在移动数据上运行速度测试，因为带宽非常昂贵。</p>

<p>我们位于埃塞俄比亚的服务器有光纤上联，但它经常宕机，我们会回退到16kbps的卫星连接，不过我想普通人遇到那种情况会直接停止使用互联网。</p>
</blockquote>

<p>如果你觉得<a href="https://1-minute-modem.branchable.com/" rel="noopener noreferrer">在56k连接上浏览网页</a>很糟糕，试试从埃塞俄比亚用16k连接吧！</p>

<p>到目前为止我们看到的都是个例。让我们用各种模拟连接加载一些程序员可能常访问的网站，获取页面加载时间的数据。<a href="https://www.webpagetest.org/" rel="noopener noreferrer">webpagetest</a> 让我们可以看到网站从世界各地加载需要多长时间（以及为什么需要这么长时间）。它甚至允许我们模拟不同类型的连接，并在各种移动设备上加载网站。下表列出的时间是页面“视觉完成”的时间；根据webpagetest的测量，这是指折叠以上内容停止变化的时间。</p>



<div>
<table id="T_3d17f176_ecb9_11e6_bbf7_0cc47ac41d03" align="center">

</table>
</div>

<p>每一行是一个网站。对于同时支持纯HTTP和HTTPS的站点，两者都进行了测试；URL为HTTPS，除非明确指定为HTTP。前两列显示通过线路传输的数据量（MB，包括头部、握手、压缩等）以及建立的TCP连接数。其余列显示在从光纤（FIOS）到较差连接的各种连接上加载页面的时间（秒）。“Bad”具有拨号上网的带宽，但有1000ms的ping延迟和10%的丢包，这大致相当于我在乡村小旅馆使用互联网时的情况。“😱”模拟了来自埃塞俄比亚吉吉加的16kbps卫星连接。行按测量到的数据传输量排序。</p>

<p>测试的超时时间是6分钟；任何超过这个时间的都列为FAIL。加载失败的页面也列为FAIL。从表中可以明显看出几点：</p>

<ol>
<li>很大一部分网站在连接状况差的情况下无法使用。即使在拨号连接状况良好（0%丢包，无延迟尖峰）时，有些网站也无法加载。</li>
<li>有些网站会使用大量数据！</li>
</ol>

<h4 id="the-web-on-bad-connections">在差连接下的网络</h4>
<p>就商业网站而言，谷歌对于网速慢的人来说基本上是最优选择。在拨号上网时，50%的页面加载时间是一分半钟。但至少它能加载——当我在蒙大拿州乡村地区使用缓慢、共享的卫星连接时，几乎所有商业网站都无法加载。我可以通过谷歌缓存查看仅有静态内容的网站，但实时站点根本没有希望加载。</p>

<h4 id="some-sites-will-use-a-lot-of-data">有些网站会使用大量数据</h4>
<p>尽管这里只测试了两个非常大的网站，但有很多网站会使用10MB或20MB的数据。如果你在美国阅读本文，可能你并不在意，但如果你在毛里塔尼亚、马达加斯加或瓦努阿图浏览网页，加载一次codinghorror将花费你<a href="https://web.archive.org/web/20190220011242if_/https://whatdoesmysitecost.com/test/170206_0G_2QG#gniCost" rel="noopener noreferrer">超过人均每日GNI的10%</a>。</p>

<h4 id="page-weight-matters">页面大小很重要</h4>
<p>尽管有<a href="http://idlewords.com/talks/website_obesity.htm" rel="noopener noreferrer">Maciej</a>等人的努力，页面大小不重要的迷思仍在传播。据我所知，HN上关于网页优化的历史最高票链接是一篇题为“极速页面加载 - 全栈开发者指南”的文章。在该页面底部，作者链接了他的另一篇博文，题为“页面大小无关紧要”。</p>
<blockquote>
<p>通常被归咎的罪魁祸首是带宽：低带宽地区（3G，发展中世界）的用户被坑了。
但数学上说不通。Akamai将全球平均连接速度定为3.9兆比特每秒。</p>
</blockquote>
<p>“极速”指南在拨号或慢速移动连接下无法正常显示，因为图片超时。在Reddit上，<a href="https://www.reddit.com/r/web_design/comments/3oppvo/ludicrously_fast_page_loads_a_guide_for_fullstack/" rel="noopener noreferrer">它在高负载下也失败了</a>：“讽刺的是，那个页面加载太慢，我关闭了窗口”，“很多……gif什么也不做，只会让你的浏览体验更糟”，“我甚至没看到那些gif；标题加载后就卡住了”，等等。</p>

<p>“页面大小无关紧要因为平均速度快”这个观点的缺陷在于，如果你将我的公寓楼（配备了1Gbps互联网）里某人的连接速度和使用56k拨号上网的人的速度平均，你会得到500 Mbps的平均速度。这并不意味着拨号上网的人真的能加载一个5MB的网站。3.9 Mbps的平均速度来自Akamai 2014年的报告，但那只是一个平均值。如果你查看Akamai 2016年的报告，你可以发现整个国家超过90%的IP地址都慢于这个速度！</p>
<p>是的，除了页面大小，还有很多其他因素也很重要，是的，确实可能创建一个很小但加载很慢的页面，也可能创建一个很大的页面因为所有权重都不是阻塞的而加载得还行，但总页面大小仍然与加载时间有相当好的相关性。</p>
<p>自发布以来，“极速”指南更新了一些JavaScript，只在你向下滚动足够远时才加载图片。如果你看页面大小数字（如果webpagetest没有被设置为滚动），这会让它在webpagetest上看起来好很多，但对于想阅读页面的慢速连接用户来说，这是更差的用户体验。如果你无论如何都要阅读整个页面，重量就会增加，你再也不能通过加载网站来预加载图片了。相反，如果你在阅读，你必须在每个部分停留几分钟来等待该部分的图片加载。这还算幸运，因为用于加载图片的JavaScript没有加载失败。</p>

<h4 id="the-average-user-fallacy">平均用户谬论</h4>
<p>就像许多人开发时考虑平均连接速度一样，许多人对“用户是谁”有固定的看法。也许他们认为有快连接且富有的客户，也有慢连接且不会花钱的客户。也就是说，粗略地说，平均而言可能是对的，但网站不是在平均值上运行，而是在特定领域运行。杰米·布兰登写道他使用Airbnb的经历：</p>
<blockquote>
<p>我昨晚花了三个小时试图通过过载的wifi和大概率是卫星连接在Airbnb上预订房间。OAuth在糟糕的连接上似乎特别糟糕。Facebook的OAuth完全无法加载，而Google的OAuth让我在“选择账户” -&gt; “请重新输入密码” -&gt; “选择账户”的循环中转了几次。尝试登录的次数太多了，以至于触发了Airbnb的某种两步验证，那也没用（邮件中的确认链接指向一个写着“请登录以查看此页面”的页面），最终我只是被要求发送邮件到account.disabled@airbnb.com，而他们至今未回复。</p>
<p>Airbnb没有测试这些东西尤其令人恼火，因为旅行几乎是这个网站存在的全部意义，他们甚至不能声称服务网络连接差的人无利可图。</p>
</blockquote>

<h4 id="what-about-tail-latency">尾延迟怎么办？</h4>
<p>我原本计划在这篇文章中展示50%分位数、90%分位数、99%分位数等尾加载时间。但50%分位数的结果已经如此糟糕，以至于我不知道展示其他结果是否还有意义。如果你查看90%分位数的结果，你会看到大多数页面在拨号连接下加载失败，而“Bad”和“😱”连接对几乎所有网站来说都是无望的。</p>

<h4 id="http-vs-https">HTTP 与 HTTPS</h4>
<div>
<table id="T_3d17f176_ecb9_11e6_bbf7_0cc47ac41d03" align="center">

</table>
</div>
<p>你可以看到，对于一个不加载很多阻塞资源的小网站，HTTPS明显比HTTP慢，尤其是在慢速连接上。实际上，今天这并不重要，因为几乎没有网站这么小，但如果你设计一个网站时考虑到网速慢的用户真的重要，这是值得注意的。</p>

<h3 id="how-to-make-pages-usable-on-slow-connections">如何让页面在慢速连接下可用</h3>
<p>详细版本是，要真正理解正在发生什么，建议阅读<a href="https://hpbn.co/" rel="noopener noreferrer">高性能浏览器网络</a>，这是一本关于网络性能的优秀书籍，可免费获取。</p>
<p>简短版本是，大多数网站优化如此之差，以至于一个对此一无所知的人也能将主要提供文本和偶尔图片的网站的页面加载时间提高10倍。当我在2013年开始这个博客时，我使用Octopress，因为Jekyll/Octopress是当时最广泛推荐的静态网站生成器。一篇有一两张图片的普通博客文章在有线连接上需要11秒加载，因为Octopress默认在头部包含多个无用的JavaScript文件（用于我从未使用过的功能，如嵌入Flash视频和Delicious集成），这些文件阻塞了页面渲染。仅仅将这些JavaScript包含移到页面底部就使页面加载时间减半，<a href="//danluu.com/octopress-speedup/" rel="noopener noreferrer">进行其他一些调整又使加载时间减少了一个数量级</a>。在我做出这些更改时，我对网页优化一无所知，除了我在一个40分钟关于互联网工作原理的讲座中听到的2分钟关于优化的简短介绍，而我能够在几个小时内将博客速度提高20倍。你可能认为我现在走得太远了，删除了太多CSS，但我为快速连接的人获得了20倍的加速，而且是在做出影响网站外观的更改之前（对于慢速连接，加速要大得多）。</p>
<p>这很正常。许多不同类型博客软件和CMS的流行主题包含如此明显的反优化，以至于任何程序员，即使没有前端经验，只需将<a href="https://www.webpagetest.org/" rel="noopener noreferrer">webpagetest</a>指向他们的网站并查看输出，就能发现巨大的改进空间。</p>

<h3 id="what-about-browsers">浏览器呢？</h3>
<p>虽然很容易责怪页面作者，因为页面端有很多唾手可得的优化机会，但浏览器端同样有很多唾手可得的机会。为什么当我在慢速卫星连接上时，我的浏览器会打开6个TCP连接试图同时下载六张图片？这只会保证这六张图片都会超时！即使我在客户端调整超时，配置为防止DoS攻击的服务器也不会允许长时间不活动的连接。我有时可以通过刷新几次页面（每次等待十分钟）让一些图片加载，但为什么浏览器不替我处理重试呢？想一想，浏览器可以为慢速连接用户做很多优化，但正因为它们没有这样做，目前用户最好的解决方案似乎是：尽可能使用w3m，当w3m不行时再切换到带有广告拦截功能的浏览器。但为什么用户必须使用两个完全不同的程序，其中一个只有电脑极客才会觉得舒服的基于文本的界面呢？</p>

<h3 id="conclusion">结论</h3>
<p>当我在谷歌工作时，有人告诉我一个故事，有一次“他们”完成了一次大规模的优化冲刺，却发现测量到的页面加载时间增加了。当他们深入研究数据时，发现加载时间增加的原因是，在优化之后他们从非洲获得了更多流量。该团队的产品从对慢速连接用户不可用变得可用，这导致许多慢速连接用户开始使用该产品，从而实际上增加了加载时间。</p>
<p>昨晚，在一个关于WebSocket协议的演讲中，加里·伯恩哈特指出，设计WebSocket协议的人做了诸如使用可变长度字段来存储帧长度以节省几个字节之类的事情。相比之下，如果你看看Alexa前100名网站，几乎所有网站都有大量的冗余；这些100个网站使用的总带宽可能大于所有WebSocket连接使用的总带宽之和。尽管如此，如果我们只看本文中测试的三个前35名网站，有两个在传输未压缩的JavaScript，两个将裸域重定向到www子域，还有两个由于没有尽可能压缩图像（在不牺牲质量的情况下）而发送了大量多余信息。如果你看看Twitter，它不在我们的表格中，但上面提到过，他们实际上做了一种<a href="https://twitter.com/danluu/status/705815510479302656" rel="noopener noreferrer">反优化，如果你上传一个甚至没有优化得特别好的PNG文件，他们会将其重新编码为更大的JPEG并产生可见伪影</a>！</p>
<p>“使用bcrypt”已经成为存储密码时合理默认选择的口号。如果“使用webpagetest”也能以同样的方式流行起来，网络世界将会更美好。它并不总是完成工作的最佳工具，但它肯定胜过当前的默认做法。</p>

<h3 id="appendix-experimental-caveats">附录：实验注意事项</h3>
<p>上述测试是通过在AWS West 2区域的一个c4.xlarge虚拟机上使用私有webpagetest镜像，在Chrome浏览器中无其他标签页打开且除webpagetest软件和浏览器外没有其他程序运行的情况下，模拟连接进行首次页面加载来完成的。这在许多方面是不现实的。</p>
<p>相对而言，这对那些边缘存在感较强的网站不利。当我在蒙大拿州乡村地区时，我进行了一些测试，发现我到谷歌的延迟明显好于到其他任何网站。这在测试结果中并未反映出来。此外，这种设置意味着页面几乎肯定会从CDN缓存中提供。对于谷歌和亚马逊这样的网站来说，这应该没有区别，但它会减少那些流量较少、不会“总是”从缓存中提供的网站的页面加载时间。例如，当我的文章不在社交媒体上流行时，55%到75%的流量来自CDN缓存，而当我有文章流行时，比例更接近90%到99%。但测试设置意味着在我和其他博客网站上，测试期间的CDN缓存命中率可能&gt;99%，因为这些博客没有广泛到通常总有缓存副本可用。</p>
<p>所有测试都假设是首次页面加载，但对于谷歌和亚马逊这样的网站来说，假设其许多或大部分资源已缓存是完全合理的。对于像我这样的网站，大部分流量来自社交媒体推荐的首次访问者，测试首次页面加载时间可能是合理的。</p>
<p>c4.xlarge是一台相当强大的机器。今天，大多数页面加载来自移动设备，即使是最快的移动设备也不如c4.xlarge快；大多数移动设备比最快的移动设备慢得多。大多数桌面页面加载也将来自比c4.xlarge慢的机器。虽然结果没有显示，但我也使用t2.micro实例运行了一组测试：对于像我这样的简单网站，差异可以忽略不计，但对于像亚马逊这样的复杂网站，页面加载时间差了多达2倍。正如你可能预料的，对于任何特定网站，随着连接变慢，差异会变小。</p>
<p>正如乔伊·赫斯指出的，许多拨号上网提供商试图通过压缩或其他技巧来减少页面的有效大小，而这些测试都没有考虑这一点。</p>
<p>Firefox、IE和Edge的性能特征通常与Chrome有很大不同。就此而言，不同版本的Chrome也可能有不同的性能特征。我只是使用Chrome，因为它是使用最广泛的桌面浏览器，而且仅运行这一整套测试就花费了超过一整天的虚拟机时间。</p>
<p>模拟的糟糕连接添加了恒定的延迟和固定（10%）的丢包。在现实中，糟糕的连接具有高度可变的延迟，峰值远高于模拟延迟，丢包率高的时段可能持续数分钟、数小时甚至数天。将“😱”放在表格最右侧可能让它看起来像是最糟糕的连接，但丢包情况可能会糟糕得多。</p>
<p>同样，虽然codinghorror碰巧位于表格底部，但它远非加载最慢的页面。举个例子，我最初考虑将slashdot包含在表格中，但它如此之慢，以至于导致总测试运行时间显著增加，因为它在六分钟时超时的次数太多。即使在FIOS上，它也需要15秒才能加载，发出了高达223个请求，使用了100个TCP连接，而大小“只有”1.9MB。令人惊讶的是，slashdot在FIOS上加载时，CPU也会100%占用整整17秒。回想起来，这可能是一个值得包含的好例子，因为正是像slashdot这样病态优化不当的网站，让“页面大小无关紧要”的迷言听起来合理。</p>
<p>比较的网站功能不同。仅看博客，一些博客在首页放置整篇博客文章，在某些方面更方便，但也更慢。商业网站差异更大——它们通常不能合理地是静态网站，必须有相对较大的JavaScript负载才能良好运行。</p>

<h3 id="appendix-irony">附录：讽刺之处</h3>
<p>本文中的主表格大约有50kB的HTML（未压缩或压缩）；这比本文其他所有内容的总和还要大。这个表格大得离谱，因为我使用了一个库（pandas）来生成表格，而不是手动编写脚本，而且我们知道，大多数库的默认设置会产生大量的冗余代码。它甚至没有节省时间，因为我想使用的每个节省时间的内置功能都有bug，迫使我不得不自己编写所有的热力图/渐变/样式代码！由于懒惰，我留下了pandas生成表格的脚手架代码，导致表格看起来比需要的大一个数量级。</p>
<p>这不是对pandas的批评。Pandas在它设计的用途上可能非常好；它只是不为制作精简网站而设计。CSS类名很长，如果你希望避免生成CSS时意外的名称冲突，这是合理的。几乎每个<code>td</code>、<code>th</code>和<code>tr</code>元素都标记了冗余的<code>rowspan=1</code>或<code>colspan=1</code>，对于生成的代码来说，如果你不关心大小，这是合理的。每个单元格都有自己的CSS类，即使许多单元格与其他单元格共享样式；同样，这可能在代码生成时简化了事情。每一份冗余都是完全合理的。不幸的是，据我所知，没有工具能将臃肿的表格变成精简的表格。纯HTML压缩器无法更改类名，因为它不知道某些外部CSS或JS是否依赖于类名。HTML压缩器理论上可以确定不同的单元格具有相同的样式并合并它们，除了前面提到的潜在但不存在的外部依赖项的问题，但这超出了我所知工具的能力范围。</p>
<p>再讽刺一层，考虑一下，虽然我认为50kB的表格是臃肿，但这个页面经过gzip压缩后只有12kB，即使包含了所有冗余。谷歌的AMP目前有超过100kB的阻塞JavaScript必须在页面加载前加载！我没有理由使用AMP页面，因为AMP比我现在使用的纯HTML加几行内嵌CSS和偶尔图片的设置要慢，但结果是，与AMP页面相比，我因为没有使用AMP来“加速”（实际上是减速）我的页面而受到谷歌的惩罚。</p>
<p><small>
感谢Leah Hanson, Jason Owen, Ethan Willis和Lindsey Kuper的评论/修正
</small></p>
<div>
<hr>
<ol>
<li id="fn:M">不包括工作所需的微软内部内容。许多网站仅适用于IE，甚至在Edge上都无法工作。我未在w3m中尝试这些网站，但我怀疑它们不行！事实上，我怀疑甚至不到一半的非IE特定内部网站能在w3m中工作。
 <a href="#fnref:M" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 32581 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/web-bloat/</link>
      <guid isPermaLink="false">https://danluu.com/web-bloat/</guid>
      <pubDate>Wed, 8 Feb 2017 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>HN：精华部分</title>
      <description>&lt;p&gt;&lt;a href=&quot;https://twitter.com/danluu/status/1433297087362486272&quot;&gt;HN&lt;/a&gt; &lt;a href=&quot;https://twitter.com/altluu/status/1449285906360266754&quot;&gt;的&lt;/a&gt; &lt;a href=&quot;https://twitter.com/altluu/status/1452707802921</description>
      <content:encoded><![CDATA[<p><a href="https://twitter.com/danluu/status/1433297087362486272" rel="noopener noreferrer">HN</a> <a href="https://twitter.com/altluu/status/1449285906360266754" rel="noopener noreferrer">的</a> <a href="https://twitter.com/altluu/status/1452707802921652224" rel="noopener noreferrer">评论</a> <a href="https://twitter.com/danluu/status/1584260625920032768" rel="noopener noreferrer">糟透了</a>。 <a href="https://twitter.com/altluu/status/1586623057019699200" rel="noopener noreferrer">对于</a> <a href="https://twitter.com/danluu/status/1346392037365436418" rel="noopener noreferrer">任何</a> <a href="https://twitter.com/danluu/status/1280651255275110400" rel="noopener noreferrer">我</a> <a href="https://twitter.com/danluu/status/1498343295545581568" rel="noopener noreferrer">了解的</a> <a href="https://twitter.com/danluu/status/1512835522002972680" rel="noopener noreferrer">话题</a>， <a href="https://twitter.com/danluu/status/1298477505272147968" rel="noopener noreferrer">绝</a> <a href="https://twitter.com/danluu/status/1277222715456282625" rel="noopener noreferrer">大</a> <a href="https://twitter.com/danluu/status/872676217350172673" rel="noopener noreferrer">多数</a> <a href="https://twitter.com/danluu/status/1484268111687663620" rel="noopener noreferrer">评论</a> <a href="https://twitter.com/danluu/status/1449113998293491716" rel="noopener noreferrer">都是</a> <a href="https://twitter.com/altluu/status/1572989465504915458" rel="noopener noreferrer">相当</a> <a href="https://twitter.com/danluu/status/1271392053314781184" rel="noopener noreferrer">明显地</a> <a href="https://twitter.com/danluu/status/1311217788720001024" rel="noopener noreferrer">错误的</a>。 <a href="https://twitter.com/danluu/status/1449113998293491716" rel="noopener noreferrer">大多数</a>时候，根本没有任何来自该话题知情人士的评论，而排在第一位的评论听起来很合理，但完全不正确。此外，许多评论都毫无理由地刻薄。你经常会听到用类似“这总比另一种可能性好——在那里每个人都互相拍马屁，说‘这太棒了’”之类的话来为刻薄的评论辩护，仿佛刻薄是抵御空洞陈词滥调的某种护身符。我见过有人反对这一点；当被追问时，人们常说不刻薄就不可能或没效率教会别人东西，仿佛告诉别人他们很蠢能帮助他们学习似的。就好像人们是通过看西蒙·考威尔来学习如何解释事物，无法理解没有掺杂人身攻击的解释概念。保罗·格雷厄姆说过，“<a href="https://news.ycombinator.com/item?id=5937839" rel="noopener noreferrer">哦，你永远不应该读 Hacker News 上关于你写的任何东西的评论</a>”。关于HN评论的大多数负面评价都是真的。</p>

<p>然而，我还没有发现一个公共互联网论坛拥有比HN更好的技术评论。在我熟悉的话题上，虽然一个帖子甚至能有一个消息灵通的评论都很罕见，但当这些评论出现时，它们通常会浮到顶部。在其他论坛上，消息灵通的评论要么不存在，要么即使出现，也会被听起来合理但完全错误的评论淹没，并且它们出现的频率甚至比HN还低。</p>

<p>按数量计算，有趣的“技术帖子”可能更多地存在于评论而非链接中。好吧，这取决于你觉得什么有趣，但对我来说确实如此。如果我看到 nkurz 关于底层优化的评论， patio11 关于商业的评论， nostrademons 关于公司运作的评论，我几乎可以肯定我会读到一条有趣的评论。我大概能想到20到30个人，他们不太写博客，但在HN上写出了很棒的评论，而且我怀疑我甚至没有认识到在HN上写出优秀评论的人的一半<sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">1</a></sup>。</p>

<p>我整理了一个非常简短的我喜欢的评论列表，因为评论似乎会丢失。如果你写一篇博客文章，人们会在几年后引用它，但评论大多会消失。我觉得这很可悲——HN上有很多很棒的材料（当然，还有更多不那么棒的材料）。</p>

<h4 id="what-s-the-deal-with-ms-word-s-file-format-https-news-ycombinator-com-item-id-12472505"><a href="https://news.ycombinator.com/item?id=12472505" rel="noopener noreferrer">Word文件格式是怎么回事</a>？</h4>

<blockquote>
<p>基本上，Word文件格式就是内存的二进制转储。我没开玩笑。他们就是把内存里的东西原样写到了磁盘上。我们可以试着推断原因（也许这样更快，也许让代码更小），但我觉得主要原因是原始开发者不知道更好的做法。</p>

<p>后来，当他们试图添加功能时，不得不尝试保持向后兼容性。这就是很多复杂性所在。有很多疯狂的变通方法，如果允许你重新设计文件格式，这些本来很简单。很明显这是管理层强制的，因为没有哪个软件开发者会无缘无故地让自己经历那种折磨。</p>

<p>后来他们添加了快速保存功能（我忘了它实际叫什么）。这种方式在不改变原始文件的情况下向文件追加更改。他们实现这个功能的方式非常巧妙，但也大大复杂化了文件结构。</p>

<p>有一点我觉得必须指出（我记得在slashdot上发布了一篇关于这篇文章的长文），那就是文字处理器的双向文件转换几乎是不可能的。因为文件格式不包含足够的信息来格式化文档。最明显的地方是分页。文件格式没有说明在哪里对文本流进行分页（除非用户明确输入）。它依赖格式化程序来完成。每个文字处理器格式化文本的方式完全不同。例如，Word出了名地错误处理脚注分页。但他们无法更改，因为这会破坏向后兼容性。这就是Word Perfect至今仍能生存的少数原因之一——它是唯一按照美国司法部要求格式化法律文件的文字处理器。</p>

<p>只考虑分页问题，你就能看出问题所在。当读取Word文档时，你必须像Word那样分页——但文件格式没有告诉你那是什么。然后，如果有人修改了文档，你需要重新保存它，你需要用某种方式标记它应该像Word那样分页（即使它可能现在包含了Word中没有的功能）。如果只是分页问题，你也许能做到，但实际上所有问题都是这样。</p>

<p>我建议有兴趣的人阅读（一部分）XML Word文件格式。你会看到大量用于“像Word 95那样格式化”之类事情的标志。这个格式没有说那是什么——因为很明显，文件格式的作者也不知道。它淹没在无望的遗留代码混乱中，没人能弄清楚它现在到底是干什么的。</p>
</blockquote>

<h4 id="fun-with-null-https-news-ycombinator-com-item-id-12007286"><a href="https://news.ycombinator.com/item?id=12007286" rel="noopener noreferrer">NULL的乐趣</a></h4>

<blockquote>
<p>这是另一个关于这个精美特性的例子：</p>
</blockquote>

<pre><code>  #include &lt;stdio.h&gt;
  #include &lt;string.h&gt;
  #include &lt;stdlib.h&gt;
  #define LENGTH 128

  int main(int argc, char **argv) {
      char *string = NULL;
      int length = 0;
      if (argc &gt; 1) {
          string = argv[1];
          length = strlen(string);
          if (length &gt;= LENGTH) exit(1);
      }

      char buffer[LENGTH];
      memcpy(buffer, string, length);
      buffer[length] = 0;

      if (string == NULL) {
          printf("String is null, so cancel the launch.\n");
      } else {
          printf("String is not null, so launch the missiles!\n");
      }

      printf("string: %s\n", string);  // 对于null未定义行为，但实践中有效

      #if SEGFAULT_ON_NULL
      printf("%s\n", string);          // 当使用裸“%s\n”时，对null段错误
      #endif

      return 0;
  }

  nate@skylake:~/src$ clang-3.8 -Wall -O3 null_check.c -o null_check
  nate@skylake:~/src$ null_check
  String is null, so cancel the launch.
  string: (null)

  nate@skylake:~/src$ icc-17 -Wall -O3 null_check.c -o null_check
  nate@skylake:~/src$ null_check
  String is null, so cancel the launch.
  string: (null)

  nate@skylake:~/src$ gcc-5 -Wall -O3 null_check.c -o null_check
  nate@skylake:~/src$ null_check
  String is not null, so launch the missiles!
  string: (null)
</code></pre>

<blockquote>
<p>看起来英特尔的ICC和Clang还没有赶上GCC的优化。如果你依赖这种优化来获得所需的性能，那可就糟了！
但在批评GCC之前，请考虑一下，所有这三个编译器在 string 为 NULL 时执行 printf("string: "); printf("%s\n", string) 都会段错误，尽管将 printf("string: %s\n", string) 作为单个语句则没有问题。你能看出为什么两个单独的语句会导致段错误吗？如果不能，参见这里获取提示：<a href="https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25609" rel="noopener noreferrer">https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25609</a></p>
</blockquote>

<h4 id="how-do-you-make-sure-the-autopilot-backup-is-paying-attention-https-news-ycombinator-com-item-id-11017034"><a href="https://news.ycombinator.com/item?id=11017034" rel="noopener noreferrer">如何确保备份的自动驾驶系统保持专注</a>？</h4>

<blockquote>
<p>良好的工程设计尽可能消除用户做错事的可能性。……你不会设计一个容易被滥用的功能，然后再用说明书来试图防止这种滥用。</p>

<p>澳大利亚曾发生过一起名为“瀑布出轨”的事故[1]。事故发生是因为司机心脏病发作，造成了7人死亡（老实说，这个数字之低简直是奇迹）。根本原因是“死人开关”失灵。</p>

<p>在“瀑布出轨”案例中，司机有两个“死人开关”可用：1）油门手柄必须对抗弹簧保持在小角度旋转；2）地上的一个踏板可以被踩下。你必须执行其中一个动作，目的是让司机可以在两者之间交替，以防手腕或脚抽筋。未能执行任一动作都会触发紧急刹车。</p>

<p>事实证明，这位司机太胖了，以至于当他心脏病发作时，他的腿能够将踏板踩到足够的程度，使紧急系统保持关闭状态。因此，“死人开关”从未触发，而驾驶座上却躺着一个“死人”。</p>

<p>我不太记得“瀑布出轨”系统的具体情况，但对抗这个问题的一种方法是要求踏板保持在完全松开和完全踩下之间的中间位置。其想法是，一条失去知觉的腿会完全踩下踏板，从而触发刹车，而完全松开的踏板也会触发刹车。我不知道他们是否采用了这种系统，但这确实是铁路系统使用的一种方法。</p>

<p>无论如何，汽车也存在同样的问题。如果你失去知觉，你的脚变得松软，根据你的脚相对于踏板的位置以及它对地面的杠杆作用，一条足够重的腿可能会将踏板保持在一定程度的下压状态。</p>

<p>我熟悉的另一个确保司机在驾驶时保持清醒的系统叫做“警觉性系统”。它的工作原理是：定期地，仪表盘上会有一个灯开始闪烁，司机必须确认这一点。如果他们不确认，蜂鸣器警报就会响起。如果他们仍然不确认，火车刹车就会启动，并被认定为司机丧失能力。让我告诉你一些我参与其中的故事。</p>

<p>我们刚开始时，有一个简单的警觉性系统。每30秒左右（例如），司机需要按一个按钮。好吧，酷。但后来司机们变得如此习惯于每30秒按一次按钮，以至于我们发现司机睡着了/打瞌睡了，但仍然每30秒准确地按一次按钮，因为这已经深入他们的潜意识，成为一个纯粹下意识的动作。</p>

<p>于是我们引入了随机时间警觉性系统，时间间隔在30-60秒之间（例如）变化，并且你只能在灯开始闪烁后的一小段时间内确认它。结果，司机们开始睡着/半睡着状态，并在警报响起时立即按下它，每次都这样。</p>

<p>于是我们引入了随机时间、任务关联的警觉性系统，这终于彻底解决了这个问题。现在，司机必须按下一个按钮，或者转动一个旋钮，或者执行一系列不同的活动，并且他们必须在随机选择的时间执行随机选择的活动来确认他们的清醒。直到那时，我们才终于解决了司机警觉性的问题。</p>
</blockquote>

<p><a href="https://news.ycombinator.com/item?id=12013713" rel="noopener noreferrer">另见</a>。</p>

<h4 id="prestige-https-news-ycombinator-com-item-id-11833832"><a href="https://news.ycombinator.com/item?id=11833832" rel="noopener noreferrer">声望</a></h4>

<blockquote>
<p>好奇他为什么需要换到更声望高的职位？大多数人在30多岁时就会意识到，声望是傻子的游戏；这是一种通过授予来自他们并不真正关心的人的赞誉，来诱导人们去做他们并不真正想做、而且不太有趣的事情的方式。</p>
</blockquote>

<h4 id="why-is-fedex-based-in-mephis-https-news-ycombinator-com-item-id-9282104"><a href="https://news.ycombinator.com/item?id=9282104" rel="noopener noreferrer">为什么联邦快递总部在孟菲斯</a>？</h4>

<blockquote>
<p>……我们注意到我们还需要：<br>
(1) 一个合适的、现有的机场作为枢纽。<br>
(2) 枢纽所在地的良好天气，例如相对较少的雪、雾或雨。<br>
(3) 便于使用良好的停机坪空间，即停放和服务飞机以及分拣包裹的地方。<br>
(4) 良好的劳动力供应，例如用于分拣中心。<br>
(5) 相对较低的生活成本以保持低价。<br>
(6) 友好的监管环境。<br>
(7) 候选机场不要太繁忙，例如，不希望到达的飞机盘旋很长时间才能降落。<br>
(8) 机场侧风相对较少，并且有多条跑道可供选择，以防万一有风。<br>
(9) 跑道海拔不要太高，例如，不要高到限制最大总起飞重量，例如，排除丹佛。<br>
(10) 跑道尽头附近没有高大障碍物，例如山脉。<br>
(11) 充足的喷气燃料供应。<br>
(12) 便于18轮卡车进出，以便在卡车和飞机之间交换包裹，例如，这样部分货物可以卡车运到枢纽，储存在那里，然后通过飞机直接运送给下订单的客户，比如在晚上11点之前下订单，第二天上午10点之前送达。<br>
所以，大约有三个候选地点，孟菲斯，以及我记忆中的辛辛那提和堪萨斯城。<br>
孟菲斯机场有一些二战时期的旧机库紧挨着跑道，联邦快递可以使用它们作为分拣中心、飞机维护和总部办公室空间。交易达成——就是孟菲斯了。</p>
</blockquote>

<h4 id="why-etherpad-joined-wave-and-why-it-didn-t-work-out-as-expected-https-news-ycombinator-com-item-id-2320172"><a href="https://news.ycombinator.com/item?id=2320172" rel="noopener noreferrer">为什么Etherpad加入了Wave，以及为什么结果没有预期</a></h4>

<blockquote>
<p>出售给Google的决定是我和我的联合创始人一生中最艰难的决定之一。我们对Wave的愿景感到兴奋，尽管我们看到了产品的缺陷。Wave团队告诉我们，他们希望我们帮助他们让Wave变得更简单、更像Etherpad，我们想我们可以在那方面提供帮助，但最终我们没能成功让Wave变得更简单。我们害怕作为竞争对手的Google：他们有更多的工程师和更多的资金支持这个项目，而且他们运作这个项目更像一个独立的初创公司，而不是一个正常的大公司部门。Wave办公室在澳大利亚，拥有几乎完全的自主权。最后，在AppJet濒临失败1.5年后，能够宣称我们的努力取得了成功，并为我们所有冒险投资的投资者提供可观的回报，这很有诱惑力。</p>

<p>最终，我们加入Wave的决定并没有像我们希望的那样奏效。最大的教训是，拥有更多工程师和资金实际上可能对项目有害多于有益，所以我们为此害怕Wave作为竞争对手是错误的。事后看来这似乎很明显，但当时并非如此。其次，我完全低估了在Wave代码库上迭代的难度。我习惯于在一夜之间重写软件的主要部分。由于Wave使用的软件开发流程，实际上不可能在产品上迭代。我本应更多地调查他们具体的软件工程流程，但我只是因为他们看起来运作得像初创公司，就假设他们能像初创公司一样迭代。许多产品问题整个Wave团队都知道，但我们被一个基于糟糕技术选择和笨重工程流程、阻碍快速迭代的大型复杂代码库所拖累。</p>
</blockquote>

<h4 id="the-accuracy-of-tech-news-https-news-ycombinator-com-item-id-12345365"><a href="https://news.ycombinator.com/item?id=12345365" rel="noopener noreferrer">科技新闻的准确性</a></h4>

<blockquote>
<p>当我掌握有关后来在科技媒体上爆料的内部信息时，我总是对读者对文章的感知与我亲身经历的方式如此不同而感到震惊。在我参与过的初创公司和大型功能发布中，我看到过：高管们在已有整个部门工作了一年的情况下，仍然断然否认他们正在开发某个产品类别；初创公司的成立日期比Crunchbase/Wikipedia列出的日期早1.5年；记者数了他们访问时遇到的人数，并将此报告为“团队规模”，因为公司拒绝公布该信息；从未曝光的融资轮次；被报道为“金额未披露”的收购，但实际上其金额少于创始人在该公司接受带薪工作所能获得的收益；项目开始日期实际上是项目达到当前规模的时候，忽略了之前一个小团队花了一年时间（或其他小团队花了3-4年时间）处理该问题的时间；以及被广泛报道为公司成功核心的算法或其他技术，但公司实际上并未使用。</p>
</blockquote>

<h4 id="self-destructing-speakers-from-dell-https-news-ycombinator-com-item-id-7205875"><a href="https://news.ycombinator.com/item?id=7205875" rel="noopener noreferrer">戴尔自毁式音箱</a></h4>

<blockquote>
<p>作为VLC的主要开发者，我们早就知道这个故事，这只是戴尔在机器上使用了垃圾组件并责怪别人。和他们不可能进行任何讨论。所以让我解释一下……</p>

<p>在这种情况下，VLC只是使用Windows API（DirectSound），并将16位有符号整数（s16）发送到Windows内核。</p>

<p>VLC允许将输入音量放大到超过解码后的音量。这就像重放增益、有缺陷的编解码器、录制质量差的文件或后期放大一样，可能导致饱和。</p>

<p>但如果你用Audacity处理你的mp3文件，增加音量并用WMP播放，或者在你的编解码器输出后添加一个DirectShow滤镜来放大音量，效果也是一样的。例如，很长一段时间里，VLC的ac3和mp3编解码器与参考输出相比音量太低（-6dB）。</p>

<p>最坏的情况是，这会降低动态范围并大量饱和，但不会损坏你的硬件。</p>

<p>VLC不会（也不能）修改输出音量来损坏扬声器。VLC是一个使用官方平台API的软件。</p>

<p>这里的问题是戴尔声卡输出的功率（可以近似为振幅平方的一个因子）是戴尔扬声器无法承受的。简单地说，声卡最大输出10瓦，而扬声器只能承受6瓦，并且它们的BIOS或驱动程序都没有阻止这一点。</p>

<p>而且由于VLC存在于许多机器上，责怪VLC很简单。“相关不等于因果”对廉价的戴尔支持来说似乎太复杂了……</p>
</blockquote>

<h4 id="learning-on-the-job-startups-vs-big-companies-https-news-ycombinator-com-item-id-8278533"><a href="https://news.ycombinator.com/item?id=8278533" rel="noopener noreferrer">在职学习，初创公司 vs 大公司</a></h4>

<blockquote>
<p>在别人的初创公司工作，我学会了如何快速拼凑解决方案。我学会了不确定性，并在不确定是否有效时选择一个方向。我了解到大多数初创公司都会失败，而当它们失败时，最终过得好的人是那些一直在为自己利益着想的人。我学到了很多基本的技术技能，如何快速编写代码、学习新API以及将软件部署到多台机器上。我了解到扩展开发团队的问题出现得有多快，以及应该多早开始投资自动化。</p>

<p>在Google工作，我学会了如何一劳永逸地解决问题，并将这种文化融入组织。我了解到即使在成功的公司里，一切都是暂时的，伟大的产品通常是由许多人通过大量辛勤工作构建的，而不是伟大的灵光一现。我学会了如何为规模设计系统，以及用于构建健壮、高可用、频繁部署系统的许多实践。我了解了研究的价值以及花大量时间解决一个单一重要问题的价值：许多初创公司采用散弹枪方法，一个接一个地参加周末黑客马拉松，却发现没人想要它们，而常常有一些机会没人解决，因为没人愿意投入工作。我学会了如何在团队中工作，并试图理解其他人想要什么。我了解到对大型组织来说真正痛苦的问题是什么。我学会了如何严格研究市场并使用数据来做出产品决策，而不是基于一个人认为最好的东西做决定。</p>
</blockquote>

<h4 id="we-failed-this-person-what-are-we-going-to-do-differently-https-news-ycombinator-com-item-id-4324615"><a href="https://news.ycombinator.com/item?id=4324615" rel="noopener noreferrer">我们辜负了这个人，我们要做哪些不同的事情</a>？</h4>

<blockquote>
<p>我曾参加过公司领导层会议，在那里离职事件只是简单地标记为“遗憾是/否”，根据我的经验，没有任何单个离职会产生影响。大规模的离职会有影响，趋势会有影响，但一个人从未有过影响，即使那个人是创始人。</p>

<p>合理化的说法总是把问题推回给离职员工：“他们倦怠了”，“他们失去了高效工作的能力”，“他们已经向前看了”，“他们只是没能跟上公司的发展”，从来不是“我们辜负了这个人，我们要做哪些不同的事情？”</p>
</blockquote>

<h4 id="aws-s-origin-story-https-news-ycombinator-com-item-id-3102129"><a href="https://news.ycombinator.com/item?id=3102129" rel="noopener noreferrer">AWS的起源故事</a></h4>

<blockquote>
<p>总之，当我还在那里时，SOA（面向服务的架构）的工作正在全面展开。这很痛苦，而且一团糟，因为每个团队的做法都不同，每个API都不同，基于不同的假设，并且用不同的语言编写。</p>

<p>但我想纠正一个误解，即这导致了AWS的诞生。它没有。S3是由它自己的团队从头开始编写的。我在亚马逊工作时，亚马逊零售网站没有任何部分运行在AWS上。我知道，当AWS被隆重推出时，他们说“为亚马逊.com提供动力的服务现在可以为你的业务提供动力了！”或类似的话。这纯粹是谎言。他们共享的只有数据中心和标准硬件配置。即使在我离开的时候，当AWS全力运行（并且可能已经在运行Reddit了），亚马逊.com也没有任何部分运行在AWS上，除了少数几个小型、实验性且相对较新的项目。我相信现在有更多部分已经采用，但AWS一直是一个独立的团队（而且根据我所见，是一个管理得更好的团队）。</p>
</blockquote>

<h4 id="why-is-windows-so-slow-https-news-ycombinator-com-item-id-3368867"><a href="https://news.ycombinator.com/item?id=3368867" rel="noopener noreferrer">为什么Windows这么慢</a>？</h4>

<blockquote>
<p>我（和其他人）花了很多努力让Linux Chrome构建变得更快。例如有多个新实现的构建系统（<a href="http://neugierig.org/software/chromium/notes/2011/02/ninja.h.." rel="noopener noreferrer">http://neugierig.org/software/chromium/notes/2011/02/ninja.h..</a>。），试验gold链接器（例如，测量和调整仍然默认关闭的线程标志<a href="https://groups.google.com/a/chromium.org/group/chromium-dev/.." rel="noopener noreferrer">https://groups.google.com/a/chromium.org/group/chromium-dev/..</a>。），以及挖掘其中的bug和其他文档不足的东西，比如“thin” ar归档文件。</p>

<p>但同样真实的是，那些在Windows方面比我这个Linux学徒更像巫师的人也参与了Chrome Windows构建的工作。如果你问我最初的问题，我会说根本问题在于，在Windows上，你只能使用微软提供的东西，通常无法做得更好。例如，将Chrome构建从Visual Studio迁移出来将是一项巨大的工程，大到很少被考虑。（另一种表述方式是这是IDE问题：你要么得到整个IDE，要么什么都得不到。）</p>

<p>在解决Windows性能差的问题时，人们首先购买了固态硬盘，这甚至从未出现在我的脑海里（“你的系统有足够的RAM，文件系统的内核缓存应该已经在内存中了！”）。但无论出于什么原因，在Linux方面，一些谷歌员工认为值得重写Linux链接器使其快一倍（这项工作早于Chrome），所有Linux开发者现在都从中受益。也许区别在于，当人们为Windows或Mac编写很棒的工具时，他们试图出售它们而不是免费赠送。</p>
</blockquote>

<h4 id="why-is-windows-so-slow-an-insider-view-http-blog-zorinaq-com-i-contribute-to-the-windows-kernel-we-are-slower-than-other-oper"><a href="http://blog.zorinaq.com/i-contribute-to-the-windows-kernel-we-are-slower-than-other-oper/" rel="noopener noreferrer">为什么Windows这么慢，内部人士视角</a></h4>

<blockquote>
<p>我是Windows开发人员，参与NT内核开发。（证明：[编辑：文件名已隐去]的修订版#102的SHA1哈希值是[编辑：哈希值已隐去]。）出于显而易见的原因，我通过Tor发布此消息。</p>

<p>Windows在许多场景下确实比其他操作系统慢，而且差距正在扩大。问题的根源在于社会性。你几乎看不到为了自身荣耀而进行的改进，这在Linux世界里很常见。</p>

<p>诚然，偶尔会看到天真的人试图让事情变得更好。这些人几乎总是失败。我们确实可以并且确实为特定的场景（那些有能力分配资源的人认为影响业务目标的场景）提高了性能，但这项工作是西西弗斯式的。没有正式或非正式的系统性能改进计划。我们开始关注安全问题是因为SP3之前的Windows XP对业务构成了生存威胁。我们的低性能对业务没有构成生存威胁。</p>

<p>看，组件所有者通常对外部补丁怀有公开的敌意：如果你是一个开发者，接受一个外部补丁会让你的主管不高兴（因为需要维护这个补丁，并在发货评审会上为这个计划外的设计变更辩护），让测试人员不高兴（因为测试人员负责确保这个变更不会破坏任何东西，而你刚刚给他们增加了工作），让项目经理不高兴（因为代码变动会影响进度）。你没有动力接受自己团队之外的更改。你总能找到理由说“不”，而说“是”的动力非常小。</p>
</blockquote>

<h4 id="what-s-the-probability-of-a-successful-exit-by-city-https-news-ycombinator-com-item-id-11987223"><a href="https://news.ycombinator.com/item?id=11987223" rel="noopener noreferrer">按城市划分成功退出的概率</a>？</h4>

<p>请参阅链接中的大型表格 :-）。</p>

<h4 id="the-hiring-crunch-https-news-ycombinator-com-item-id-7260087"><a href="https://news.ycombinator.com/item?id=7260087" rel="noopener noreferrer">招聘紧缩</a></h4>

<blockquote>
<p>破纪录：初创公司可能也拒绝了许多工程候选人，这些候选人的表现会与他们现有团队中的任何人一样好甚至更好，因为科技行业的招聘流程是民间传说式的、非理性的。</p>
</blockquote>

<p>摘录太长。请参阅链接！</p>

<h4 id="should-you-leave-a-bad-job-https-news-ycombinator-com-item-id-7789438"><a href="https://news.ycombinator.com/item?id=7789438" rel="noopener noreferrer">你应该离开一份糟糕的工作吗</a>？</h4>

<blockquote>
<p>我是一个42岁非常成功的程序员，到目前为止在我的职业生涯中经历过很多情况，其中许多都极具挫败感。我给你最好的建议就是离开你正在做的事情。真的。即使你声称自己没有条件这样做，你真的有。没关系。你是自由的。好吧，你在帮你男朋友的初创公司，但合适的代价是多少？如果他知道这让你的灵魂备受煎熬，他还会让你这样做吗？</p>

<p>我并不是随便使用“灵魂备受煎熬”这个短语。当它慢慢发生时，就像这些情况一样，很难看清正在发生的事情的规模。但这是一个非常严重的情况，如果任其发展，可能会损害你余生做好工作的潜力。</p>

<p>警告倦怠的评论者是对的。倦怠是一个非常严重的情况。如果你把自己逼到严重倦怠，即使在一份表面上很棒的工作中，你也很难在未来任何工作中保持高效。把倦怠当作身体损伤来对待。我曾经倦怠过一次，花了至少12年才恢复到全部生产力。不要那样做。</p>

<ul>
<li><p>更广泛地说，最好和最有创造力的工作源于快乐和兴奋。如果你失去了对与编程相关的事情感到快乐和兴奋的能力，你就无法做到最好。这个问题与倦怠是分开的、平行的！如果你倦怠了，你可能仍然能在项目/想法的开始短暂地感受到快乐和兴奋，但随着日常工作的现实开始，它们会很快消退。或者，如果你没有倦怠，但也缺乏好奇心，那么你很可能永远不会开始去做那些好的工作。</p></li>
<li><p>你职业生涯中现在的阶段越早，这段时间对你的发展就越重要。程序员通过实践学习。如果你将自己置于一个不断受到挑战并以能力上限工作的环境中，那么几年过去后，你的技能将大大提高。就像去密集学习几年功夫，或者参加海豹突击队训练之类的。但这不仅仅是持续的一次性增长。你完成事情越快，完成得越彻底、越少出错，你能执行的想法就越多，这意味着你未来学习得也越快。从长远来看，编程技能就像复利。现在多一点意味着以后会多很多。现在少一点意味着以后会少很多。</p></li>
</ul>

<p>所以，如果你让自己处于一个并非真正具有挑战性的位置，每天都很沮丧，完成事情很慢，那么你不仅仅是现在过得慢。你正在拉低整个职业生涯的复利曲线。这是一个严重的问题。
如果我能回到我职业生涯的早期，我会无情地砍掉我做过的所有糟糕工作（其中有很多）。</p>
</blockquote>

<h4 id="creating-change-when-politically-unpopular-https-news-ycombinator-com-item-id-5541517"><a href="https://news.ycombinator.com/item?id=5541517" rel="noopener noreferrer">在政治上不受欢迎时创造变革</a></h4>

<blockquote>
<p>一个小故事。一个熟人讲述了修复后院“排水”的故事。他们试图种植一些对过度潮湿敏感的植物，但植物在死亡。不浇水、少浇水似乎都没有改变。它们死了。一位专业园丁建议说，他们的问题是排水问题。于是他们挖了大约3英尺深（那里的土壤非常非常湿），试图建造更好的排水系统。由于他们在山坡上，所以没有考虑地下水位问题。结果发现他们的“问题”是，为他们家和山上房子供水的总水管在他们的房产处压力太大（因为它必须在山顶也保持压力），导致管道接缝漏水，并将大量水抽入了他们房产下的地基。问题不在于他们的花园，而在于城市供水系统设计不良。</p>

<p>虽然我从未被问过是否是工程师，但我经历过类似Rachel在会议上和建议方面的经历。同事会对你形成一个内部价值评估，然后根据那个评估做出回应。如果他们否定了你，他们会忽视你；如果你在公共场合证明他们的评估错了，他们会攻击你。这些都是管理问题，也是这些故事中严重缺乏的。</p>

<p>如果你是一次会议的“负责人”，而有人试图发言却没有得到机会。你有责任让他们发言。作为“老板”，你可以自然地通过你的职位权力打断讨论，从其他成员那里收集更多信息。问一些像“有没有人有顾虑？”这样的问题也很重要，以引出那些有有效意见但太胆怯不敢分享的人。</p>

<p>在高度政治化的环境中，有两种创造变革的方式，一种是公开操作，即为自己收集政治权力，然后运用它来实施变革；另一种是隐蔽操作，即以足够微妙的方式实施变革，以至于政治有机体不会反应（有时被称为“触发抗体”）。</p>

<p>后一种方法的问题在于，如果你在帮助实现积极变革的同时让每个人都不生气，没有人会将其归功于你（这对变革推动者来说是好事，因为如果他们知道抗体就会反应，但如果经理没有认识到它，那就是坏事）。我问我的经理他想要什么是“真实”的，但他（或其他人）未能实现，他给了我一个，18个月后，那个变革就位了。他不相信是我实现了那个变革。我建议他选择一个他希望发生的变革，不告诉我，然后18个月后我们可以看看那个变革是否发生了:-）。但他对组织动力学的理解也不够，不知道在没有变革的源头指回自己的情况下实现变革是否可能。</p>
</blockquote>

<h4 id="how-to-get-tech-support-from-google-https-news-ycombinator-com-item-id-6837249"><a href="https://news.ycombinator.com/item?id=6837249" rel="noopener noreferrer">如何从Google获得技术支持</a></h4>

<blockquote>
<p>严重依赖谷歌产品？✓<br>
在谷歌的客户服务上碰壁？✓<br>
有现成的受众可以利用来引起某个随机谷歌员工的注意？✓<br>
登上Hacker News首页？✓<br>
好消息！你的问题应该在2-5个工作日内得到解决。我们这些依赖谷歌服务的傻瓜只能无助地盯着收件箱，等待对我们支持工单（永远不会有回应）的回复。我觉得如今几乎成了一种[仪式]：严重依赖谷歌服务，结果出了问题，却被晾在一边。</p>
</blockquote>

<h4 id="taking-funding-https-news-ycombinator-com-item-id-7462112"><a href="https://news.ycombinator.com/item?id=7462112" rel="noopener noreferrer">接受融资</a></h4>

<blockquote>
<p>如果我没记错的话，PayPal也非常相似——它以15亿美元被收购，但Max Levchin的份额只有大约3000万美元，Elon Musk的份额只有大约1亿美元。相比之下，许多早期的Web 2.0宠儿（Del.icio.us、Blogger、Flickr）只卖了2000万到4000万美元，但他们的创始人只拿了少量种子轮，因此收购价格的绝大部分归创始人所有。4000万美元收购的75% = 10亿美元收购的3%。</p>

<p>创始人在接受融资时需要考虑这一点。如果你看看那些巨大的科技财富——盖茨、佩奇/布林、奥米迪亚、贝佐斯、扎克伯格、惠利特/帕卡德——它们通常来自在寻求投资时已经盈利或已经处于用户增长曲线快速上升期并有明确变现路径的公司。那些为了争取客户而拼尽全力、需要大量外部资金的公司通常财务结果要差得多。</p>
</blockquote>

<h4 id="stackoverflow-vs-experts-exchange-https-news-ycombinator-com-item-id-3656581"><a href="https://news.ycombinator.com/item?id=3656581" rel="noopener noreferrer">StackOverflow vs. Experts-Exchange</a></h4>

<blockquote>
<p>很多以某种方式参与过Experts-Exchange（EE）的人不理解StackOverflow（SO）。</p>

<p>EE的基本价值流是“专家”为有问题的新手提供有价值的“答案”。在这个等式中，有一个人提问，一个人写答案。</p>

<p>StackOverflow认识到，对于每一个提问的人，会有100-10000人在Google上输入同样的问题，并找到已经写好的答案。在我们的等式中，我们是一个由写答案的人组成的社区，这些答案将被成百上千人阅读。我们的项目更像维基百科——协作地为整个互联网创建资源。</p>

<p>因为这个资源是由社区提供的，所以它属于社区。这就是为什么我们的数据是自由可用的，并在知识共享许可下发布。我们这样做特别是因为EE将社区生成的资源据为己有并决定设置付费墙的负面经历。</p>

<p>许多EE贡献者的态度，比如Greg Young计算出他“免费工作”了半年，并没有被每月在SO上写答案的60万人所共享。当你和他们交谈时，你会发现在StackOverflow上，回答问题是为了学习。它是为了创建一个永久性的成果，让互联网变得更好。它是为了帮助某人在五分钟内解决一个问题，而这个问题他们自己可能要花几个小时来解决。而不是为了免费工作。</p>

<p>一旦EE引入了金钱概念，他们就迫使每个人都将自己的EE工作视为仅仅如此——工作。</p>
</blockquote>

<h4 id="making-money-from-amazon-bots-https-news-ycombinator-com-item-id-2475986"><a href="https://news.ycombinator.com/item?id=2475986" rel="noopener noreferrer">从亚马逊机器人赚钱</a></h4>

<blockquote>
<p>我看到我的一本旧教科书卖了个好价钱，于是我把它和另外两本二手书一起挂了出去。我把价格定得比最低价低1美元，但在一个小时内，两个卖家都把价格改得比我低0.01美元和0.02美元。我又降了两次价，每次降1美元，每次他们都以低一两美分的价格击败我。于是，我所做的就是每小时降几美元，连续降了一天，直到所有人都定价低于5美元。然后我买了他们的书，再把我的价格改回去。</p>
</blockquote>

<h4 id="what-running-a-business-is-like-https-news-ycombinator-com-item-id-4060229"><a href="https://news.ycombinator.com/item?id=4060229" rel="noopener noreferrer">经营企业是什么样子</a></h4>

<blockquote>
<p>虽然我喜欢这里的观点，但我认为危险在于工程师们可能会错误地认为制作披萨是经营一家成功的披萨店的主要限速因素。经营一家成功的披萨店更多的是：去当地酒店推销，留下50份菜单放在前台；雇佣既能在及时送达披萨又不会侵吞你（微薄）利润、同时雇佣成本几乎为零的司机；维护一个足够整洁的厨房以通过当地卫生检查员的年度检查（并处理与之相关的47份不同文件）；能够权衡诸如“我是否应该银行贷款建造一个新的砖炉，这样会让披萨味道更好，但这意味着在未来3年里每月将占用我3000美元的现金流，还是我应该雇佣一个额外的厨师？”等问题；采购食材，确保每天都能以相当稳定的价格获得充足且优质的供应；定价时既要考虑到你所选客户群的本地竞争力，又要为企业产生健康的毛利率；理解为什么健康的毛利率并不意味着健康的净利率，租金仍然需要支付；保持足够好的记录，以便在你无法支付工资之前就能知道你的企业是否在死亡，并且每年能够向税务机关提供相对准确的账目；平衡“中号披萨五折”促销活动与不想蚕食老顾客生意的欲望；等等，等等，顺便说一句，番茄酱应该是酸的但不酸，奶酪应该在烤出微脆外壳的同时融化。</p>

<p>你想以写软件为生吗？谷歌正在招聘。你想经营一个软件企业吗？祝你好运。软件现在只占你工作生活的10%。</p>
</blockquote>

<h4 id="how-to-handle-mismanagement-https-news-ycombinator-com-item-id-7179946"><a href="https://news.ycombinator.com/item?id=7179946" rel="noopener noreferrer">如何处理管理不善</a>？</h4>

<blockquote>
<p>我倾向于这样理解：你的工作不是保护人们（尤其是高级管理层）免受其决策后果的影响。根据你自己的最大利益做出决策；由组织来确保你的利益与他们的利益一致。</p>

<p>谷歌过去有一个严重的问题：代码重构和维护在绩效评估中不受奖励，而发布则受到高度重视，这导致每个人都试图尽可能快地发布东西，没有人去清理留下的烂摊子。最终发布开始变慢，拉里开始问“为什么我们不能拥有好东西？”，每个人都回答“因为你一直付钱让我们累积技术债务。”结果，成立了专门负责代码健康和维护的团队，那些已经在做这些工作的团队得到了更多关注，重构贡献在绩效评估中开始算数。此外，许多对这种情况感到厌烦的前谷歌员工去了Facebook，我听说，他们在Facebook建立了一种文化，在这种文化中，那些辛苦的工程维护工作受到同行的重视。</p>

<p>如果人们只是英勇地牺牲自己，在没人关心的工作上耗尽自己，这一切都不会发生。有时，在有决策权的人意识到问题并开始纠正之前，需要高度可见的后果。如果这些后果从未发生，他们会继续相信这不是一个问题，不会太关注它。</p>
</blockquote>

<h4 id="some-downsides-of-immutability-https-news-ycombinator-com-item-id-9145197"><a href="https://news.ycombinator.com/item?id=9145197" rel="noopener noreferrer">不可变性的一些缺点</a></h4>

<h4 id="taking-responsibility-https-news-ycombinator-com-item-id-8082029"><a href="https://news.ycombinator.com/item?id=8082029" rel="noopener noreferrer">承担责任</a></h4>

<blockquote>
<p>我祖父教我的事情是，你一生都要与你所有的决定共存。当你做出将他人置于风险之中的决定时，你承担着让某人的生活更艰难、甚至可能艰难得多的风险。也许更重要的是，再多的“我很抱歉我做了那个……”也无法挽回它。有时是小事，比如吃了最后一份因为你以为大家都吃过了；有时是大事，比如认定家够近了、而且你足够清醒可以安全到达那里。它们都是我们每天都在做的决定。而随着我年龄增长，那些我希望自己做了不同选择的决定所带来的负担并不会变轻。你可以对自己撒谎，合理化你的选择，但这也无法改变它们。</p>

<p>我年轻时并不明白其中任何一点。</p>
</blockquote>

<h4 id="people-who-aren-t-exactly-lying-https-news-ycombinator-com-item-id-2819426"><a href="https://news.ycombinator.com/item?id=2819426" rel="noopener noreferrer">并非完全撒谎的人</a></h4>

<blockquote>
<p>我花了太长时间才想明白这一点。有些人真的、并且充满热情地相信他们对你说的话，但实际上他们个人无法实现，所以你不能真的指望那个“承诺”。</p>

<p>我过去以为那些人是为了利用别人而撒谎，但随着年龄增长，我开始认识到这些“是的”型的人升职很快。而且对他们中的一些人来说，他们确实相信他们所说的。</p>

<p>作为一名工程师，我发现一旦我能“校准”某人的“是的”程度，我就可以与他们合作，明白他们只做出“一厢情愿的”承诺，而不是“经过深思熟虑的”承诺。</p>

<p>所以当有人，比如史蒂夫·乔布斯，说“我们要把它变成一个开放标准！”时，我的第一个问题是“太好了，我需要你的支持来让它成为开放标准，这样我就可以指望你在有人反对这项努力时运用你的职位影响力来帮助我，对吗？”如果这个问题的答案是否定的，那么他们就是在撒谎。</p>

<p>区别当然是微妙的，但很重要。史蒂夫显然不会去标准会议投票等等，但如果经理鲍勃因差遣5人参加倡导Facetime协议作为开放标准的开放视频聊天工作组而面临会计部门的反对，认为这将超出他的差旅预算，那么鲍勃经理去找史蒂夫说：“我需要你在这里帮忙，这5个人需要去辩论这个标准，防止它被谷歌派去的5个人变成垃圾。”然后史蒂夫给会计部门写了一行字：“别找这个家伙的麻烦，我们需要这个。”那么一切就都好了。但另一方面，如果他说“我们得省钱，派一个人去”，那么我就更同情那些指控他含糊其辞的说法了。</p>
</blockquote>

<h4 id="what-makes-engineers-productive-https-news-ycombinator-com-item-id-5496914"><a href="https://news.ycombinator.com/item?id=5496914" rel="noopener noreferrer">是什么让工程师高效</a>？</h4>

<blockquote>
<p>对于那些在谷歌内部工作的人来说，看看杰夫和桑贾伊的提交历史和代码审查仪表板是很有价值的。就编写的代码量而言，他们实际上并不比一个熟悉代码库的优秀SWE3（软件工程师三级）多多少。</p>

<p>他们之所以被当作摇滚明星，是因为他们能够将这种生产力应用于真正重要的事情上；他们能够挑出问题中真正重要的部分，然后专注于那里，因此最终结果比SWE3写的代码影响更大。SWE3可能会花时间写一堆单元测试，这些测试捕获的bug实际上不会发生，或者从一个系统迁移到另一个系统，而后者并不是一个大的改进，或者陷入一个架构死胡同，最终还得重写。杰夫或桑贾伊（或任何其他在同一水平运作的人）会花时间向客户验证提议的API以确保它满足他们的需求，或者测量子系统的性能以便完全理解他们的构建块，或者在构建系统之前在脑海中模拟系统的运行，以便快速测试替代方案。他们实际上写的代码并不比初级开发人员多（很多时候，他们写的更少），但他们写的代码为他们提供了更多信息，这使他们确保自己写的是正确的代码。</p>

<p>我觉得这一点需要比现在更多地被强调，因为围绕10x（10倍效率）开发者的神话已经形成，这并没有太大帮助。特别是，人们需要意识到，如果你不允许这些开发者自己做架构选择，他们很快就会变成1x开发者（或更差）——他们一开始就很优秀的原因是他们知道如何确定某些工作是否会是无用的，并且从一开始就避免去做。如果你规定他们无论如何都要去做，他们会变得和任何其他开发者一样慢。</p>
</blockquote>

<h4 id="do-the-work-be-a-hero-https-news-ycombinator-com-item-id-2682645"><a href="https://news.ycombinator.com/item?id=2682645" rel="noopener noreferrer">努力工作，成为英雄</a></h4>

<blockquote>
<p>我也听过一次“英雄”演讲。如果有人再提到“英雄”这个词，而当时没有燃烧的建筑，我会立即开始寻找新的工作。在我们的行业中，这似乎普遍是“我们即将利用你，因为项目人员不足、预算和时间不足，而这正是我们计划好的，所以你最好像个牛仔一样挺住。”的代码词。</p>

<p>也许如果你在写《雷神之锤》会有所不同，但我保证，当年销量第43位的游戏也有程序员被“鼓励前进”，用的是死后等待的荣耀故事。</p>
</blockquote>

<h4 id="learning-english-from-watching-movies-https-news-ycombinator-com-item-id-7333625"><a href="https://news.ycombinator.com/item?id=7333625" rel="noopener noreferrer">通过看电影学英语</a></h4>

<blockquote>
<p>有一次我和一个好朋友在这里用英语交谈。<br>
 "你想出去吃烤鸡肉串吗？"<br>
"去你的！"<br>
"……（切换到日语）我最近做了什么很严重的事情冒犯你了吗？"<br>
"没有，当然没有。"<br>
"哦，好吧，我担心了。那么，那个短语，是你只有在极度痛苦、极度想要冒犯我的情况下才会说的，或者我想你可以在朋友之间开玩笑地说，但你我通常都不是那样说话的。"<br>
"我是从一部电影里学来的。我以为它的意思是‘不’。"</p>
</blockquote>

<h4 id="being-smart-and-getting-things-done-https-news-ycombinator-com-item-id-2838149"><a href="https://news.ycombinator.com/item?id=2838149" rel="noopener noreferrer">聪明且能把事做成</a></h4>

<blockquote>
<p>真实的故事：我去听了一位“工程界元老”（这些是被认为非常成功并被员工效仿的低职级工程师）的演讲。这个人说他们来谷歌工作时，被分配了XYZ系统来处理（遗憾的是，我不能透露实际系统）。他们说他们花了几天时间查看这个复杂而破旧的系统，搞不清楚，于是他们写了一个新系统。是的，他们提交了那个。这个人是个编码上帝，不是吗？（讽刺）我问旧系统怎么样了（我知道，但想听听他们的看法），他们说它还在，因为还有几个东西在使用它，但（相当自豪地）几乎所有其他东西都已迁移到他们的新系统了。</p>

<p>所以如果你仔细阅读，这个人创建了一个新系统来“替换”一个他们不理解的现有系统，并让几乎所有人都迁移到了新系统。这让他们变得超级厉害，因为他们有了一个大项目可以写在内部简历上，而一大堆人不得不写新代码来适应从旧系统到这个新系统的转换，这个新系统不完美地重建了旧系统（记住他们不理解原系统），以至于那些依赖于更晦涩部分的系统尚未被转换（因为显然没有人理解依赖代码或旧系统）。</p>

<p>这个人聪明吗？根据一些同行的说法，耀眼的聪明。他们能把事做成吗？当然，他们从头开始编写了XYZ系统的替代品！一个人？你能想象吗？我会雇佣他们吗？除非他们是我候选人库中最后一个合格的人，而且我时间紧迫，否则不会。</p>

<p>这个轶事概括了“聪明且能把事做成”的人的危险一面。</p>
</blockquote>

<h4 id="public-speaking-tips-https-news-ycombinator-com-item-id-6199544"><a href="https://news.ycombinator.com/item?id=6199544" rel="noopener noreferrer">公开演讲技巧</a></h4>

<blockquote>
<p>有些孩子是在足球场上长大的。我是在公开演讲中长大的（实际上是作为言语障碍的行为疗法）。如果你想在短时间内大幅提高：</p>
</blockquote>

<p>摘录太长。请参阅链接。</p>

<h4 id="a-reason-a-company-can-be-a-bad-fit-https-news-ycombinator-com-item-id-3018643"><a href="https://news.ycombinator.com/item?id=3018643" rel="noopener noreferrer">公司可能不适合的一个原因</a></h4>

<blockquote>
<p>我能理解这一点，但我也能理解问题的另一面。有时不是我，而是你。拿一个能把事做成的人来说，突然在你的组织里，他们无法交付了。可能是他们的问题，但也可能是你的问题。</p>

<p>我在谷歌工作时就有过这种经历。我在那里做什么都异常困难。我花了一些时间来评估这一点，因为在那之前我的职业生涯中从未出现过无法推动事情前进的情况，而我真的很想理解这一点。简短的答案是，谷歌培养出了一些人，他们把大部分甚至全部时间都花在阻止变革上。我花了一段时间才弄明白是什么驱使一个人反对变革。</p>

<p>恐惧是风险和安全。人们频繁调动，所以你遇到的情况是：人们负责他们没有构建、不理解所有动态部分的系统，如果他们搞砸了，可能会得到糟糕的评级。处理处于这种情况的人时，要么教育他们并带领他们，要么强推过去。教育需要时间，而在此期间，“老师”什么也做不成。这在进化上有利于强推 :-)</p>

<p>所以你可以雇佣一个能把事做成的人，但如果在你的组织里，把事做成需要他们成为一个混蛋，而他们不愿意那样做，那么他们就不会像你希望的那样成功。</p>
</blockquote>

<h4 id="what-working-at-google-is-like-https-news-ycombinator-com-item-id-3473117"><a href="https://news.ycombinator.com/item?id=3473117" rel="noopener noreferrer">在谷歌工作是什么样子</a></h4>

<blockquote>
<p>我能看出这是由外人写的，因为它关注的是福利，并重复了一些已经成为流行媒体陈词滥调但并不完全准确的说法。</p>

<p>大多数谷歌员工会告诉你，在那里工作最好的事情是有能力与非常聪明的同事一起解决真正困难的问题，并拥有大量可用的资源。我记得我问我的面试官，如果我有很好的20%想法，我是否可以使用谷歌的索引之类的东西，他说：“当然。这是鼓励的。我通常会直接抓取4000台左右的机器，运行MapReduce来测试某个假设。”我的电话筛选员，当我问他那里工作是什么样子时，他说：“这是一个非常聪明的人去变得平庸的地方，”结果证明这既是真的，也是我在那里工作获得的最好的事情之一。</p>
</blockquote>

<h4 id="nsa-vs-black-hat-https-news-ycombinator-com-item-id-6136338"><a href="https://news.ycombinator.com/item?id=6136338" rel="noopener noreferrer">NSA vs. Black Hat</a></h4>

<blockquote>
<p>整个事件都是一场精心策划的媒体操作。基思·亚历山大是信号情报、电子战和情报领域约30年的资深人士，也是美国陆军四星上将——这比你想象的还要厉害。他是真正意义上的间谍头子和政治大师。任何认为他走进凯撒宫会议厅时，对演讲结果没有近乎完美预测的人，都是在自欺欺人。</p>

<p>起哄亚历山大正中他的下怀。这给了他一个机会，让自己相对于批评者显得更合理，而且更普遍地（也是令人担忧地），让NSA相对于NSA监控的反对者显得更合理。他能够“分裂选票”，利用观众的反应，让那些可能对NSA项目有严重保留意见的人为他冷静优雅地处理辱骂而鼓掌；其中许多人可能只是为了抗议起哄者而鼓掌，毕竟起哄者让他们更难跟上亚历山大想说的话。</p>

<p>主旨演讲没有提供严肃的问答环节。问题经过了预审；所有与会者只能投票选择问题。这次演讲不可能产生其他结果，只能是有效地、不受挑战地、全力为NSA的项目辩护。</p>
</blockquote>

<h4 id="are-deadlines-necessary-https-news-ycombinator-com-item-id-2507946"><a href="https://news.ycombinator.com/item?id=2507946" rel="noopener noreferrer">截止日期是必要的吗</a>？</h4>

<blockquote>
<p>有趣的是，当我在谷歌工作时，我发现最令人惊讶的事情之一是几乎完全无法理解“截止日期”的概念。多年来，公司只需要将代码提交到发布分支，然后在几周内部署到“集群”就算发布了。</p>

<p>当然，有“流程”，比如“在某个集群里进行金丝雀测试，并观察几周的结果，然后再向全世界发布。”但完全垂直整合是一种独特的情况。</p>
</blockquote>

<h4 id="debugging-on-windows-vs-linux-https-news-ycombinator-com-item-id-5125078"><a href="https://news.ycombinator.com/item?id=5125078" rel="noopener noreferrer">在Windows上调试 vs. 在Linux上调试</a></h4>

<blockquote>
<p>作为一个尝试转向Linux的非常有经验的游戏开发者，我以前也发表过关于这个的文章（并遭到了反应型Linux拥护者的猛烈抨击）。</p>

<p>主要原因是Linux上的调试很糟糕。gdb用起来很糟糕，所有试图与gdb接口以“改进”它的IDE都做得不好（主要是因为gdb本身不擅长被接口）。需要有人把这个东西从轨道上炸掉，然后从头开始构建一个新的调试器，并提供一个库风格的API，让IDE能够以丰富而微妙的方式检查可执行文件。</p>

<p>生产力至关重要。如果一个合理的调试环境的缺乏让我损失了哪怕5%的生产力，那也太多了，因为游戏需要大量的工作来制作。在项目结束时，我已经没有5%的精力剩下了。它需要一切。（但目前的Linux状况远不止是5%的生产力损耗。我不知道确切是多少，但如果我猜，大概是20%左右。）</p>
</blockquote>

<h4 id="what-happens-when-you-become-rich-https-news-ycombinator-com-item-id-7528556"><a href="https://news.ycombinator.com/item?id=7528556" rel="noopener noreferrer">当你变得富有会发生什么</a>？</h4>

<blockquote>
<p>有趣的是，人们甚至不知道自己对金钱有复杂情绪，直到他们变得“富有”。我看过许多人，也许有上百人，从“工作糊口”变成“天哪，我现在有的钱可以支付我所有现在的账单，也许还有未来的账单。”这还不包括那个住在我们街区、一年中赢得加州彩票的家伙。</p>

<p>它以意想不到的方式影响人们。如果是突然发生的（比如中彩票或IPO突然飙升），可能很难处理。但重要的是要意识到自己正在处理一件特殊事件。就像所爱的人去世或配偶突然离婚一样。</p>

<p>并非每个人都感到“内疚”，也并非每个人都感到“自满”。湾区很多百万富翁和亿万富翁外表上没有变化。但关键是，这种情绪来自于价值观与现实之间的认知失调。你重视什么？现实是什么？</p>

<p>我在谷歌认识的一位女性在开始工作时非常矛盾。她一直觉得，如果她有了比需要的更多的钱，她会帮助她看到的那些无家可归的人。在通过谷歌股票变得富有后，她发现自己想把钱存起来用于未来孩子的教育和需求。她是个坏人吗？之前？之后？如果你把孩子的大学教育费用捐给了当地的食品银行，你的孩子会恨你吗？你的同事会恨你吗，因为你能够解决食品银行当前的食品缺口但你没有？</p>
</blockquote>

<h4 id="microsoft-s-skype-acquisition-https-news-ycombinator-com-item-id-2531332"><a href="https://news.ycombinator.com/item?id=2531332" rel="noopener noreferrer">微软收购Skype</a></h4>

<blockquote>
<p>这是微软的ICQ时刻。在一家公司的核心能力正在变成大宗商品的时候，为它支付过高的价格。有人对Skype有任何忠诚度吗？当然没有。他们会使用内置于他们智能手机、平板电脑、电脑等中的任何视频聊天。他们会使用Facebook最终推出的视频聊天服务或谷歌提供的服务。当有这么多替代品存在并深度集成到他们已经使用的产品/服务中时，没有人会主动寻找Skype。当然，没有人会仅仅因为集成了Skype就去购买微软的产品。谁在乎它是FaceTime、Facebook视频聊天还是谷歌视频聊天？对用户来说都一样。</p>

<p>有了70亿美元，他们本可以以史诗级的公关噱头赠送大约1500万部Windows Mobile手机。它不是坏产品——他们只是需要让人们意识到它的存在。如果他们想把钱冲进马桶，他们还不如让用户参与这个过程？</p>
</blockquote>

<h4 id="what-happened-to-google-fiber-https-news-ycombinator-com-item-id-12793300"><a href="https://news.ycombinator.com/item?id=12793300" rel="noopener noreferrer">Google Fiber怎么了</a>？</h4>

<blockquote>
<p>当Fiber团队非常年轻的时候（基本上是从发布前2周到发布后2周），我曾短暂地在Fiber团队工作过（我从搜索团队借调来，专门是为了帮助他们实现发布目标）。我在那里的时候，瓶颈是当地政府法规，事实上，选择堪萨斯城是因为它有一个统一的城市/县/公共事业监管机构，对谷歌非常有利。要铺设光纤到户，你需要获得电线杆的通行权（这些电线杆由谷歌的竞争对手拥有），或者需要挖掘街道的许可（这需要市政府的大量许可）。无论哪种情况，有线电视和电话公司与当地监管机构关系非常密切，所以你遇到的是你绝对需要批准的敌对守门人。</p>

<p>技术很棒（1G互联网和高清电视！），软件都运行良好，雇佣承包商铺设光纤本身的经济性也确实可行。大问题是监管俘获。</p>

<p>回顾Uber和AirBnB的成功，我认为破解ISP业务的方法是为你的客户提供大规模违法的工具。例如，你可以想象一家ISP初创公司基本上这样说：“这是一个盒子、一根电线和一个其他客户位置的地图。插到他们的插口上，如果你能说服其他人插到你的插口上，我们就会根据你签约的数量给你每月账单打折。”但谷歌通常不愿意违法——他们会严格遵守法律允许的边界，但如果监管机构说“不，你不能那样做”，他们就不会去做，而不是去对抗监管机构。</p>

<p>确实，Fiber正逐步淘汰，转而支持谷歌收购的WebPass，后者基本上做同样的事情，但是用无线代替光纤。WebPass只需要业主的同意，就把城市排除在外。</p>
</blockquote>

<h4 id="what-it-s-like-to-talk-at-microsoft-s-teched-https-news-ycombinator-com-item-id-6100368"><a href="https://news.ycombinator.com/item?id=6100368" rel="noopener noreferrer">在微软TechEd上演讲是什么样子</a></h4>

<blockquote>
<p>我在美国和欧洲的TechEd上演讲过两次，并且两次都在与会者反馈中名列前茅。</p>

<p>我再也不会在TechEd上演讲了，我也告诉了微软同样的事情，同样的原因。活动工作人员要求过多，不体谅演讲者的时间。他们反复将我拖入强制性的虚拟和面对面会议，讨论一些本可以通过电子邮件解决的无关紧要的细节。他们规定了演讲者穿的裤子颜色。管理得太过微观。</p>
</blockquote>

<h4 id="why-did-hertz-suddenly-become-so-flaky-https-news-ycombinator-com-item-id-12855236"><a href="https://news.ycombinator.com/item?id=12855236" rel="noopener noreferrer">赫兹为什么突然变得如此不可靠</a>？</h4>

<blockquote>
<p>赫兹今年早些时候裁掉了几乎所有的一线IT员工。</p>

<p>为了获得遣散费，我们被迫培训在印度的IBM替代者。赫兹的IBM和紧缩战略是新的SMT对资产负债表一团糟的解决方案，但他们通过将高管薪酬提高35%（包括给CIO 600万美元奖金）来犒劳自己。</p>

<p>我个人去了Alphabet公司，获得了大幅加薪，现在我有幸参与真正令人惊叹的工作，所以我过得很好。但直到今天，想到我们曾经出色的赫兹团队，成员非常聪明，由我遇到过的最好的老板领导，真正推动赫兹的创新，却被像昨天的垃圾一样扔掉了，我还是感到难过。</p>
</blockquote>

<h4 id="before-startups-put-clauses-in-contracts-forbidden-they-sometimes-blocked-sales-via-backchannel-communications-https-news-ycombinator-com-item-id-10705646"><a href="https://news.ycombinator.com/item?id=10705646" rel="noopener noreferrer">在初创公司把禁止条款写入合同之前，他们有时会通过后台沟通阻止销售</a></h4>

<blockquote>
<p>不要指望一定能卖掉股票来支付税款。我在非常良好的状态下离开了（我当时是这么认为的），但当我打算出售时，交易被叫停了[1]。幸运的是，我有一个备用计划，我没事[2]。</p>

<p>[1] 与公司的一位投资者达成了口头协议，然后投资者对我沉默了。当我跟进时，他说交易“实在太小了。”我联系了公司寻求帮助，他们说他们实际上告诉了他不要从我这里买。如果他们没有出于某种原因决定告诉我，我永远也不会知道。教训是私营公司股票的市场往往很小，买家更关心他们与公司的关系，而不是拥有你的股份。即使股票条款允许他们购买，他们也可能不买。</p>
</blockquote>

<h4 id="an-amazon-pilot-program-designed-to-reduce-the-cost-of-interviewing-https-news-ycombinator-com-item-id-13076821"><a href="https://news.ycombinator.com/item?id=13076821" rel="noopener noreferrer">亚马逊一个旨在降低面试成本的试点项目</a></h4>

<blockquote>
<p>我和原帖作者一样参加了第一个测试，逻辑推理部分对我来说似乎有点无关紧要，浪费时间。与第二个在线测试相比，那根本不算什么。</p>

<p>第二个测试的环境就像《黑镜》中的场景。他们不仅要求整个过程开启网络摄像头和麦克风，我还必须安装他们的定制软件，以便监考人员可以监控我的屏幕并控制我的电脑。他们打开了macOS系统偏好设置，以便禁用所有截屏快捷键，他们还手动关闭了我正在运行的所有后台服务（甚至包括f.lux！）。</p>

<p>然后他们要求我拿起笔记本电脑，用网络摄像头向他们展示我的房间。他们特别要求看看我桌子上东西以及房间的墙壁和天花板。我的桌子上有一些铅笔和纸，用来做草稿纸，这显然是必要的，他们告诉我那是不允许的。显然这让我有点不高兴，因为我用它来草绘例子和概念。他们还看到我桌上的手机，要求我把它放到手臂够不到的地方。</p>

<p>在那之后，他们告诉我，在测试进行到一半允许的5分钟上厕所休息之前，我不能离开房间。我忘了告诉我室友我在参加这个测试，他玩L4D2（《求生之路2》）在线游戏时有点吵（显然有点分心）。我问监考人员我是否可以短暂离开房间让他安静一下。他们说在厕所休息之前我不能离开，所以我无能为力。后来，我正忙于思考一个问题，调整了坐姿，把脸稍微移出了摄像头视野。监考人员又给我发消息，要求我移动，以便能看到我的整个脸。</p>
</blockquote>

<h4 id="amazon-interviews-part-2-https-news-ycombinator-com-item-id-13077230"><a href="https://news.ycombinator.com/item?id=13077230" rel="noopener noreferrer">亚马逊面试，第二部分</a></h4>

<blockquote>
<p>面试的第一部分与链接的经历完全一样。没有编码问题，只有推理。第二部分我不得不使用ProctorU而不是Proctorio。个人认为这个经历超级奇怪但可以理解，我待会儿会讲到，有人通过我的网络摄像头全程看着我，我的麦克风开着。测试前他们需要检查我的身份证。他们需要我展示我所在的整个房间（那是我的卧室）。我的台式机在我的笔记本电脑后面开着，所以我关掉了我的电脑（我不记得是我主动提出的还是他们要求的），但他们还要求我用东西盖住显示器，这在我关掉后就显得很傻了，所以我用一条毛巾盖住了它们。然后他们用LogMeIn远程连接到我的机器，检查正在运行的程序。我退出了所有个人聊天程序，几乎只运行Chrome窗口。</p>

<p>……</p>

<p>直到我收到录用通知，我才和真正在亚马逊工作的人（通过电子邮件或网络摄像头）交谈过。</p>
</blockquote>

<h4 id="what-s-getting-acquired-by-oracle-like-https-news-ycombinator-com-item-id-13199717"><a href="https://news.ycombinator.com/item?id=13199717" rel="noopener noreferrer">被甲骨文收购是什么感觉</a>？</h4>

<blockquote>
<p>我们公司被甲骨文收购了。我们以为一切都会好。起初没有任何变化。慢慢地，他们开始拧紧螺丝。5年笔记本电脑更换政策。你会得到公司标准的笔记本电脑，并且你会喜欢它。销售？哦，那些家伙可以每两年买新Mac，他们想要什么都行。然后你明白了软件工程师在公司等级制度中的地位。甲骨文将我们产品的平均价格从同一规模交易的10万美元提高到500万美元。我们的销售额从500-700万美元增加到超过4000万美元，工程人员没有增加（15人团队）。但奖金发放时，我们所有人都被排名，有些人什么也得不到。作为顶级绩效者，我得到了一些期权，价值大概5000美元。</p>

<p>甲骨文的存在是为了从财富1000强中榨取最大可能的金钱。其他人都可以滚蛋。你无能的互联网愤怒毫无意义。如果它没有惹恼X公司的CTO，那就不重要。如果它能让那个CTO开出更大的支票，那么它就会受到极大的热情欢迎。</p>

<p>这种文化让很多（但不是所有）优秀的人感到疲惫，然后离开。剩下的是大量的平庸和架构宇航员。产品越复杂越好——这意味着额外的咨询费用！</p>

<p>我的亲戚在一家依赖Micros的公司工作。当甲骨文宣布收购时，我告诉他们立即开始备用计划，因为甲骨文迟早会搞砸他们。几年过去了，这被证明是真的：甲骨文正在慢慢排挤Micros经销商和独立软件供应商，吞噬所有收入，同时提高价格。</p>
</blockquote>

<h4 id="how-do-you-avoid-hiring-developers-who-do-negative-work-https-news-ycombinator-com-item-id-13209317"></h4><p><em>由 mimo-v2.5 模型翻译，花费 41601 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/hn-comments/</link>
      <guid isPermaLink="false">https://danluu.com/hn-comments/</guid>
      <pubDate>Sun, 23 Oct 2016 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>编程书籍推荐与避坑指南</title>
      <description>&lt;p&gt;市面上充斥着各种“程序员必读的12本计算机书单”，这纯属无稽之谈。这个领域如此宽泛，几乎没有哪个主题能成为所有程序员的必读内容。即便某个主题确实至关重要，人们的学习偏好也差异巨大，很难有哪本书能成为所有人眼中该主题的最佳选择。&lt;/p&gt;

&lt;p&gt;这份清单列出了一些主题和书籍，&lt;a href=&quot;https://twitter.com/danluu/status/15748194070583255</description>
      <content:encoded><![CDATA[<p>市面上充斥着各种“程序员必读的12本计算机书单”，这纯属无稽之谈。这个领域如此宽泛，几乎没有哪个主题能成为所有程序员的必读内容。即便某个主题确实至关重要，人们的学习偏好也差异巨大，很难有哪本书能成为所有人眼中该主题的最佳选择。</p>

<p>这份清单列出了一些主题和书籍，<a href="https://twitter.com/danluu/status/1574819407058325505" rel="noopener noreferrer">我本人读过这些书</a>，对这些主题足够熟悉，能说明深入学习它们能带来什么收获，而且我也读过其他相关书籍，可以解释为何要选择这本而非那本。</p>

<h3 id="algorithms-data-structures-complexity">算法 / 数据结构 / 复杂性</h3>

<p>为什么要关注这个领域？嗯，有实用主义的理由：即便你在工作中从不使用这些东西，大多数薪酬最高的公司也会在面试中考察它们。抛开功利面不谈，我发现算法的用处和数学类似。任何特定算法对任何特定问题有用的概率都很低，但对哪类问题可解、哪类问题难以处理、以及近似方法何时有效有一个总体认识，往往很有帮助。</p>

<h4 id="mcdowell-cracking-the-coding-interview-https-www-amazon-com-gp-product-0984782850-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0984782850-linkid-a34501f41a8ccd1ba8d604198c026551">McDowell；<a href="https://www.amazon.com/gp/product/0984782850/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0984782850&amp;linkId=a34501f41a8ccd1ba8d604198c026551" rel="noopener noreferrer">《程序员面试金典》</a></h4>

<p>包含一些与谷歌、脸书、微软等公司初级面试难度匹配的问题和解答，并附有解释。我通常向那些只想通过面试而不想真正学习算法的人推荐这本书。它刚好够应付面试，但并没有真正教你任何知识背后的“为什么”。如果你想真正学习算法和数据结构，请看下面。</p>

<h4 id="dasgupta-papadimitriou-and-vazirani-algorithms-https-www-amazon-com-gp-product-0073523402-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0073523402-linkid-51557d68c39707af447ec02339249dd1">Dasgupta, Papadimitriou, 和 Vazirani；<a href="https://www.amazon.com/gp/product/0073523402/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0073523402&amp;linkId=51557d68c39707af447ec02339249dd1" rel="noopener noreferrer">《算法》</a></h4>

<p>对我来说这本书似乎完美无缺。它将算法分类（例如，分治或贪心），并教你如何识别应该用哪种算法来解决特定问题。作为一本入门书，它涵盖了恰当的主题范围，篇幅适中，适合在几个周末内读完，并且附有适合入门水平的练习题。此外，它在章节中间设置了一些子问题，让你反思那些非显而易见的思想，确保你不会遗漏任何内容。</p>

<p>我知道有些人不喜欢它，因为它相对来说数学味/证明味较浓。如果是你，你可能会更喜欢 Skiena 的书。</p>

<h4 id="skiena-the-algorithm-design-manual-https-www-amazon-com-gp-product-1848000693-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-1848000693-linkid-59bca0c3da96693a0c5384c97f6e59bb">Skiena；<a href="https://www.amazon.com/gp/product/1848000693/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=1848000693&amp;linkId=59bca0c3da96693a0c5384c97f6e59bb" rel="noopener noreferrer">《算法设计手册》</a></h4>

<p>这是 Dasgupta 那本书的更长、更全面、更实用、数学味更淡的版本。它的相似之处在于，它试图教你如何识别问题、使用正确的算法，并清晰地解释该算法。书中穿插了许多“实战故事”，展示了算法在实际编程中的影响，让内容更有说服力。</p>

<h4 id="clrs-introduction-to-algorithms-https-www-amazon-com-gp-product-0262033844-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0262033844-linkid-f7dc38d160a69e72427774682d9c0bc6">CLRS；<a href="https://www.amazon.com/gp/product/0262033844/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0262033844&amp;linkId=f7dc38d160a69e72427774682d9c0bc6" rel="noopener noreferrer">《算法导论》</a></h4>

<p>不知为何，这本书竟然出现在了半数“程序员必读的N本书”书单中，尽管它如此全面和严谨，以至于几乎没有从业者能通读全书。作为算法课程的教科书，它很棒，你会学到精选的主题。作为教科书，它的练习题难度足够高，可用于研究生级别的课程（我研究生算法课约一半的练习题取自 CLRS，另一半取自 Kleinberg &amp; Tardos），这对教科书来说是个优点，但作为大多数人的独立入门读物，这极不实用。</p>

<p>举个例子，书中有一整章讲 Van Emde Boas 树。它们确实很精妙——一种具有 <code>O(lg lg n)</code> 插入、删除以及查找、后继和前驱操作的平衡树状结构是可能的，这有点令人惊讶，但算法入门不该包含 Van Emde Boas 树。</p>

<h4 id="kleinberg-tardos-algorithm-design-https-www-amazon-com-gp-product-9332518645-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-9332518645-linkid-284dc1e6677bb43997fac393456bbc70">Kleinberg &amp; Tardos；<a href="https://www.amazon.com/gp/product/9332518645/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=9332518645&amp;linkId=284dc1e6677bb43997fac393456bbc70" rel="noopener noreferrer">《算法设计》</a></h4>

<p>对 CLRS 的评价同样适用于此——它被广泛推荐为入门书，尽管作为入门书并不合理。就我个人而言，我觉得 Kleinberg 的阐述比 CLRS 更容易理解，但很多人有相反的看法。</p>

<h4 id="demaine-advanced-data-structures-http-courses-csail-mit-edu-6-851-spring14">Demaine；<a href="http://courses.csail.mit.edu/6.851/spring14/" rel="noopener noreferrer">《高级数据结构》</a></h4>

<p>这是一套讲义和笔记，不是书。但如果你想学习一套连贯（但不会全面到难以驾驭）的、在大多数本科课程中不太可能见到的数据结构材料，这是绝佳选择。这些笔记并非设计成独立阅读材料，所以如果你还没接触过这些内容，最好配合视频观看。</p>

<h4 id="okasaki-purely-functional-data-structures-https-www-amazon-com-gp-product-0521663504-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0521663504-linkid-2638d1c24b973d9ddf71f9968a309fea">Okasaki；<a href="https://www.amazon.com/gp/product/0521663504/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0521663504&amp;linkId=2638d1c24b973d9ddf71f9968a309fea" rel="noopener noreferrer">《函数式数据结构》</a></h4>

<p>研读起来很有趣，但与其他算法和数据结构书籍不同的是，我还没能把这本书里的任何东西应用到真正重要的性能问题领域。</p>

<p>读完这本书后的几年里，如果有人告诉我，理解纯函数式惰性数据结构的性能并不难，我就会问他们书中一个让我困惑的证明部分。我说的不是什么偏僻的超级难题，而是正文内容，作者认为太显然而无需解释的部分。没人能解释清楚。推理这类事情比人们通常声称的要难。</p>

<h4 id="dominus-higher-order-perl-http-hop-perl-plover-com-book">Dominus；<a href="http://hop.perl.plover.com/book/" rel="noopener noreferrer">《高阶 Perl》</a></h4>

<p>一本温和的函数式编程入门书，碰巧使用了 Perl。你大概也可以同样轻松地用 Python 或 Ruby 来学习这本书。</p>

<p>如果你一直关注潮流，这本书今天可能看起来有点过时，但这只是因为其中的许多思想已成为主流。如果你想知道为什么应该关注这个人们一直在谈论的“函数式编程”，而你听到的一些口号无法打动你，甚至让你反感（类型即命题，它很好因为它是数学，等等），那么请给这本书一个机会。</p>

<h4 id="levitin-algorithms-https-www-amazon-com-gp-product-0132316811-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0132316811-linkid-26aa7611b1a13ec1c73c2ffe0512a777">Levitin；<a href="https://www.amazon.com/gp/product/0132316811/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0132316811&amp;linkId=26aa7611b1a13ec1c73c2ffe0512a777" rel="noopener noreferrer">《算法》</a></h4>

<p>我是在亚马逊上看到这两段宣传语后下单买的：“其他的学习增强功能包括章节总结、练习提示和详细解答手册。”以及“学生的学习通过练习提示和章节总结得到进一步支持。”其中一段甚至印在了书上，但拿到书后，我能找到的自学资源只有雅虎问答上一些询问哪里能找到提示或解答的帖子。</p>

<p>最后我选择了 Dasgupta 的书，它可以从作者网站上免费获取。</p>

<h4 id="mitzenmacher-upfal-probability-and-computing-randomized-algorithms-and-probabilistic-analysis-https-www-amazon-com-gp-product-0521835402-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0521835402-linkid-d65b4aa027a011a1a5875776aee9b9d7">Mitzenmacher &amp; Upfal；《概率与计算：随机化算法与概率分析》<a href="https://www.amazon.com/gp/product/0521835402/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0521835402&amp;linkId=d65b4aa027a011a1a5875776aee9b9d7" rel="noopener noreferrer">Probability and Computing: Randomized Algorithms and Probabilistic Analysis</a></h4>

<p>这本书我可能比任何其他算法书都获益更多。许多随机化算法<a href="http://danluu.com/2choices-eviction/" rel="noopener noreferrer">很容易移植到其他应用</a>，并且能大大简化问题。</p>

<p>书中对概率论有足够的入门介绍，因此你无需具备任何概率基础。此外，关于尾部界限（例如，切诺夫界）的内容对许多计算机科学理论证明很有用，这在入门概率论教科书中并未涵盖。</p>

<h4 id="sipser-introduction-to-the-theory-of-computation-https-www-amazon-com-gp-product-113318779x-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-113318779x-linkid-aaee0c6fd8df18a1372efdb8295bb426">Sipser；<a href="https://www.amazon.com/gp/product/113318779X/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=113318779X&amp;linkId=aaee0c6fd8df18a1372efdb8295bb426" rel="noopener noreferrer">《计算理论导论》</a></h4>

<p>经典的计算理论入门书。图灵机等。证明常常在“证明概要”的层面给出。许多重要结果（例如，赖斯定理）被推到了练习题中，因此你必须完成关键练习题。不幸的是，大多数关键练习题没有解答，所以你无法检查你的答案。</p>

<p>如果想看更现代的主题选择，可以看看 <a href="http://theory.cs.princeton.edu/complexity/" rel="noopener noreferrer">Aurora &amp; Barak</a> 的书。</p>

<h4 id="bernhardt-computation-https-www-destroyallsoftware-com-screencasts-catalog-introduction-to-computation">Bernhardt；<a href="https://www.destroyallsoftware.com/screencasts/catalog/introduction-to-computation" rel="noopener noreferrer">《计算》</a></h4>

<p>涵盖了计算理论的一些亮点。解释令人愉悦，有些视频我看了不止一遍，纯粹是为了看 Bernhardt 如何解释事物。面向没有计算机科学背景的普通程序员观众。</p>

<h4 id="kearns-vazirani-an-introduction-to-computational-learning-theory-https-www-amazon-com-gp-product-0262111934-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0262111934-linkid-373c1d6780793c4552728116254da17c">Kearns &amp; Vazirani；<a href="https://www.amazon.com/gp/product/0262111934/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0262111934&amp;linkId=373c1d6780793c4552728116254da17c" rel="noopener noreferrer">《计算学习理论导论》</a></h4>

<p>经典，但已过时且错误百出，也没有勘误可用。当我想学习这些材料时，最终拼凑了 <a href="http://www.cs.utexas.edu/~klivans/cl.html" rel="noopener noreferrer">Klivans</a> 和 <a href="https://www.cs.cmu.edu/~avrim/ML14/" rel="noopener noreferrer">Blum</a> 两门课程的笔记。</p>

<h3 id="operating-systems">操作系统</h3>

<p>为什么要关注这个领域？拥有一些操作系统知识可以节省数天甚至数周的调试时间。这是 <a href="http://jvns.ca/" rel="noopener noreferrer">Julia Evans 的博客</a> 的一个常见主题，根据我的经验也是如此。我很难想到有谁构建实用系统且懂一点操作系统知识，却没发现这些知识能节省时间。然而，读操作系统书籍的人存在偏差——往往是从事相关工作的人！如果你做非常高层次的工作，读这些书可能收获不同。</p>

<h4 id="silberchatz-galvin-and-gagne-operating-system-concepts-https-www-amazon-com-gp-product-1118063333-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-1118063333-linkid-c60719f7dd6b3355d7f8535a25d74f07">Silberchatz, Galvin, 和 Gagne；<a href="https://www.amazon.com/gp/product/1118063333/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=1118063333&amp;linkId=c60719f7dd6b3355d7f8535a25d74f07" rel="noopener noreferrer">《操作系统概念》</a></h4>

<p>这是我在威斯康星大学时使用的教材，在 <a href="http://pages.cs.wisc.edu/~remzi/OSTEP/" rel="noopener noreferrer">the comet book</a> 成为标准之前。我猜它还行。它在较高层面涵盖了概念，涵盖了主要观点，但在技术深度、事物如何工作的细节、高级主题和清晰阐述方面有所欠缺。</p>

<h4 id="cox-kasshoek-and-morris-xv6-https-pdos-csail-mit-edu-6-828-2014-xv6-book-rev8-pdf">Cox, Kasshoek, 和 Morris；<a href="https://pdos.csail.mit.edu/6.828/2014/xv6-book-rev8.pdf" rel="noopener noreferrer">《xv6》</a></h4>

<p>这本书很棒！它解释了如何在真实系统中实际实现东西，并且它<a href="https://en.wikipedia.org/wiki/Xv6" rel="noopener noreferrer">自带了一个你可以试用的操作系统实现</a>。通过设计，作者倾向于简单实现而非优化实现，因此使用的算法和数据结构通常与你在生产系统中看到的大不相同。</p>

<p>这本书与那些讨论更现代操作系统工作原理的书（如 Love 的《Linux 内核开发》或 Russinovich 的《Windows 内核原理》）搭配阅读效果很好。</p>

<h4 id="arpaci-dusseau-and-arpaci-dusseau-operating-systems-three-easy-pieces-https-www-amazon-com-gp-product-b0722mjycb-ref-as-li-qf-sp-asin-il-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-b0722mjycb-linkid-ed01591ad6b853092462c17fb987f11e">Arpaci-Dusseau 和 Arpaci-Dusseau；<a href="https://www.amazon.com/gp/product/B0722MJYCB/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B0722MJYCB&amp;linkId=ed01591ad6b853092462c17fb987f11e" rel="noopener noreferrer">《操作系统：三向曲线》</a></h4>

<p>对各种操作系统主题进行了很好的解释。比我所知的任何其他入门操作系统书都更深入。例如，关于文件系统的章节描述了多个真实文件系统的细节，并讨论了 <code>ext4</code> 的主要实现特性。如果我对这本书有一个批评，那就是它非常 *nix 为中心。许多描述的事情只是 *nix 中的实现方式，并非固有，但文本大多没有说明什么是固有的，什么是 *nix 的实现细节。</p>

<h4 id="love-linux-kernel-development-https-www-amazon-com-gp-product-0672329468-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0672329468-linkid-e20733e35b36d9e4fb51b5db3b74058d">Love；<a href="https://www.amazon.com/gp/product/0672329468/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0672329468&amp;linkId=e20733e35b36d9e4fb51b5db3b74058d" rel="noopener noreferrer">《Linux 内核开发》</a></h4>

<p>书名可能有点误导——这基本上是一本关于 Linux 内核如何工作的书：各部分如何组合在一起，使用了什么算法和数据结构等等。我读过第2版，现在已经相当过时了。第3版有一些更新，但<a href="https://lwn.net/Articles/419855/" rel="noopener noreferrer">引入了一些错误和不一致</a>，并且仍然过时（出版于2010年，涵盖2.6.34）。即便如此，它是对现代操作系统如何工作的良好介绍。</p>

<p>这本书的另一个缺点是，每当比较 Linux 和 Windows 时，作者就失去了所有客观性。基本上每次比较，作者都会说 Linux 显然且无可辩驳地做出了正确的选择，而 Windows 在做蠢事。总的来说，我更喜欢 Linux 而非 Windows，但在许多领域 Windows 更优越，也有一些领域两者旗鼓相当但 Windows 多年领先。然而，从这本书里你永远无法知道这些。</p>

<h4 id="russinovich-solomon-and-ionescu-windows-internals-https-www-amazon-com-gp-product-0735648735-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0735648735-linkid-0d5e22fbe4c2130f4f19196ef2082f57">Russinovich, Solomon, 和 Ionescu；<a href="https://www.amazon.com/gp/product/0735648735/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0735648735&amp;linkId=0d5e22fbe4c2130f4f19196ef2082f57" rel="noopener noreferrer">《Windows 内核原理》</a></h4>

<p>关于现代操作系统如何工作的最全面的书。只是恰好是关于 Windows 的。从 *nix 背景出发，我觉得读这本书很有趣，只是为了看看差异。</p>

<p>这绝对不是入门书，你应该在阅读之前具备一些操作系统知识。如果你打算买这本书的实体版，可能要等到第7版发行（2017年初）。</p>

<h4 id="downey-the-little-book-of-semaphores-http-greenteapress-com-wp-semaphores">Downey；<a href="http://greenteapress.com/wp/semaphores/" rel="noopener noreferrer">《信号量小书》</a></h4>

<p>将一个通常在操作系统教科书中只占一两个小节的主题扩展成一本300页的独立书籍。这本书是一系列练习题，有点像《Schemer 小书》，但阐述更多。它从解释信号量是什么开始，然后通过一系列练习构建更高层的并发原语。</p>

<p>当我刚开始编写线程/并发代码时，这本书非常有帮助。我属于<a href="http://danluu.com/butler-lampson-1999/" rel="noopener noreferrer">Butler Lampson 并发学派</a>，也就是说我更喜欢把所有与并发相关的代码塞进一个黑盒子里，由别人来写。但有时你被迫去写这个黑盒子，如果是这样，这本书很好地介绍了编写可能（但希望不是完全）正确的并发代码所需的思维方式。</p>

<p>我希望有人能用这种风格写一本书，但同时更底层也更上层。我很想看到类似的练习，但从几个具有不同内存模型（比如 x86 和 Alpha）的不同架构的指令级原语开始，而不是从信号量开始。如果我今天在写底层线程代码，我很可能使用 <code>c++11</code> 的线程原语，所以我想要一本使用这些而不是信号量的书（如果我是在针对 <code>Win32</code> API 编写线程代码，我可能会用到信号量）。但由于那本书不存在，这似乎是次优选择。</p>

<p>我听说 Doug Lea 的<a href="https://www.amazon.com/gp/product/0201310090/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0201310090&amp;linkId=72b21afe79c9b254cd4383310cff1e42" rel="noopener noreferrer">《Java 并发编程》</a>也相当不错，但我只是快速浏览过。</p>

<h3 id="computer-architecture">计算机体系结构</h3>

<p>为什么要关注这个领域？你将学到的具体事实和琐事在进行低级别性能优化时很有用，但真正的价值在于学会如何权衡性能与其他因素（无论是功耗、成本、尺寸、重量还是其他）之间的关系。</p>

<p>理论上，这种推理应该与专业方向无关，但根据我的经验，搞体系结构的人更可能“理解”这种推理，并进行一些粗略计算，从而避免无谓地丢掉2倍、10倍（或100倍）的性能。这听起来显而易见，但我能想到多家大公司的生产系统正在放弃10倍到100倍的性能，而这些系统运行的规模即使2倍的性能差异也足以支付一位副总裁的薪水——所有这些都是因为人们没有仔细思考其设计的性能影响。</p>

<h4 id="hennessy-patterson-computer-architecture-a-quantitative-approach">Hennessy &amp; Patterson；《计算机体系结构：量化研究方法》</h4>

<p>这本书教你如何在多约束（例如，性能、总拥有成本和功耗）下进行系统设计，以及如何权衡取舍。它恰好主要使用微处理器和超级计算机作为案例。</p>

<p>这本书的新版本有实质性增补，你真的需要最新版本。例如，最新版本增加了（除其他外）一章关于数据中心设计的内容，它回答了诸如<a href="//danluu.com/datacenter-power/" rel="noopener noreferrer">运营支出/资本支出在电力、配电和冷却方面花费了多少，在支持人员和机器上花费了多少</a>，使用低功耗机器对尾延迟和结果质量有何影响（以必应搜索结果为例），以及设计数据中心时还应考虑哪些其他因素等问题。</p>

<p>假设读者有一些背景知识，但这些背景知识在附录中呈现（可在线免费获取）。</p>

<h4 id="shen-lipasti-modern-processor-design-https-www-amazon-com-gp-product-1478607831-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-1478607831-linkid-415aef1c0fccd4f6b5f931dbd63a1852">Shen &amp; Lipasti；<a href="https://www.amazon.com/gp/product/1478607831/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=1478607831&amp;linkId=415aef1c0fccd4f6b5f931dbd63a1852" rel="noopener noreferrer">《现代处理器设计》</a></h4>

<p>介绍了构建高性能奔腾 Pro（1995年）时代微处理器所需的大部分知识。考虑到此类处理器的复杂性，这并非易事。此外，还介绍了一些更高级的思想以及从各种工作负载中提取多少并行性的界限（以及你可能如何进行此类计算）。书中关于<a href="http://pharm.ece.wisc.edu/mikko/oldpapers/asplos7.pdf" rel="noopener noreferrer">值预测</a>的部分异常多，因为作者发明了这个概念，而且在第一版出版时这仍然是热门话题。</p>

<p>就纯 CPU 体系结构而言，这可能是现有最好的书。</p>

<h4 id="hill-jouppi-and-sohi-readings-in-computer-architecture-https-www-amazon-com-gp-product-1558605398-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-1558605398-linkid-c066a06415728c60470dbee9a41c79fa">Hill, Jouppi, 和 Sohi；<a href="https://www.amazon.com/gp/product/1558605398/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=1558605398&amp;linkId=c066a06415728c60470dbee9a41c79fa" rel="noopener noreferrer">《计算机体系结构读物》</a></h4>

<p>出于历史原因阅读，看看我们在解释事物方面进步了多少。例如，比较 Amdahl 关于阿姆达尔定律的论文（两页，仅有一个非显而易见的图表，没有公式）与现代教科书中的阐述（一段文字，一个公式，可能还有一个图表来澄清，但通常足够清晰，不需要额外图表）。</p>

<p>似乎越往前追溯情况越糟糕；由于体系结构是一个相对年轻的领域，这里的内容其实都不难理解。如果你想看看我们在解释事物方面进步有多显著的一个戏剧性例子，可以比较 Maxwell 关于麦克斯韦方程组的原始论文与现代教材对同一内容的阐述。如果你喜欢历史，这很有趣；但如果你只是想学习东西，可能会有点费劲。</p>

<h3 id="algorithmic-game-theory-auction-theory-mechanism-design">算法博弈论 / 拍卖理论 / 机制设计</h3>

<p>为什么要关注这个领域？世界上一些最大的科技公司依靠广告收入运营，而这些广告是通过拍卖销售的。这个领域解释了它们如何以及为何有效。此外，当你试图弄清楚如何设计有效分配资源的系统时，这些材料也很有用。<sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">1</a></sup></p>

<p>特别是，激励相容机制设计（大致来说，就是如何创建系统，使得当人们出于自身私利行事时，系统能提供全局最优结果）应该是任何在公司设计内部激励系统的人的必读内容。如果你曾经在一家“懂这个”的大公司和一家“不懂”的大公司工作过，你会发现那家不懂的公司，有大笔大笔的钱基本上被白白烧掉了，因为设定激励的人创建了极其浪费的系统。这个领域为你提供背景知识，让你理解什么样的机制会带来什么样的结果；阅读案例研究会给你一个非常长（且有趣）的错误清单，这些错误可能造成数百万甚至数十亿美元的损失。</p>

<h4 id="krishna-auction-theory-https-www-amazon-com-gp-product-0123745071-ref-as-li-tl-ie-utf8-camp-1789-creative-9325-creativeasin-0123745071-linkcode-as2-tag-abroaview-20-linkid-cc4354c8796c223d9612f5dc12e6afd8">Krishna；<a href="https://www.amazon.com/gp/product/0123745071/ref=as_li_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0123745071&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=cc4354c8796c223d9612f5dc12e6afd8" rel="noopener noreferrer">《拍卖理论》</a></h4>

<p>据我上次所知，这是市场上唯一一本全面、现代的拍卖理论入门书。第一章就涵盖了经典的<a href="https://en.wikipedia.org/wiki/Vickrey_auction" rel="noopener noreferrer">第二价格拍卖结果</a>，然后继续涵盖风险规避、投标环、相互依存的价值、多重拍卖、非对称信息以及其他现实世界的问题。</p>

<p>相对枯燥。除非你已经对这个主题感兴趣，否则不太可能激起动力。需要理解基本的概率和微积分。</p>

<h4 id="steighlitz-snipers-shills-and-sharks-ebay-and-human-behavior-https-www-amazon-com-gp-product-0691127131-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0691127131-linkid-a0ed27b464ee65f5551f60e3a796cf26">Steighlitz；<a href="https://www.amazon.com/gp/product/0691127131/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0691127131&amp;linkId=a0ed27b464ee65f5551f60e3a796cf26" rel="noopener noreferrer">《狙击手、诱饵和鲨鱼：eBay与人类行为》</a></h4>

<p>似乎是为外行设计的拍卖理论趣味入门书。不需要数学背景，并将数学内容放在小号字体中。涵盖了大约 Krishna 那本书1/10的内容，也许更少。读起来很有趣。</p>

<h4 id="crampton-shoham-and-steinberg-combinatorial-auctions-ftp-cramton-umd-edu-ca-book-cramton-shoham-steinberg-combinatorial-auctions-pdf">Crampton, Shoham, 和 Steinberg；<a href="ftp://cramton.umd.edu/ca-book/cramton-shoham-steinberg-combinatorial-auctions.pdf" rel="noopener noreferrer">《组合拍卖》</a></h4>

<p>讨论了诸如 FCC 频谱拍卖如何演变成现在这样，以及机制设计中的“漏洞”如何导致数亿甚至数十亿美元的损失等问题。这是一本每章由不同作者撰写的书之一。尽管如此，它仍然保持了连贯性，我不介意通读它。它的自包含性足够好，你可能无需先读 Krishna 就能读它，但我不推荐这样做。</p>

<h4 id="shoham-and-leyton-brown-multiagent-systems-algorithmic-game-theoretic-and-logical-foundations-http-www-masfoundations-org">Shoham 和 Leyton-Brown；<a href="http://www.masfoundations.org/" rel="noopener noreferrer">《多智能体系统：算法、博弈论和逻辑基础》</a></h4>

<p>书名是这本书最糟糕的地方。除此之外，它是算法博弈论的一个不错的入门介绍。本书涵盖了基本的博弈论、拍卖理论以及其他计算机科学人士可能还不熟悉的经典主题，然后涵盖了计算机科学与这些主题的交叉部分。假设读者没有特定的背景知识。</p>

<h4 id="nisan-roughgarden-tardos-and-vazirani-algorithmic-game-theory-https-www-amazon-com-gp-product-0521872820-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0521872820-linkid-7caf296d20541c2fc320dd423ecff19a">Nisan, Roughgarden, Tardos, 和 Vazirani；<a href="https://www.amazon.com/gp/product/0521872820/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0521872820&amp;linkId=7caf296d20541c2fc320dd423ecff19a" rel="noopener noreferrer">《算法博弈论》</a></h4>

<p>对算法博弈论各种结果的综述。需要相当多的背景知识（建议先读 Shoham 和 Leyton-Brown 的书）。例如，第五章基本上是 Devanur, Papadimitriou, Saberi, 和 Vazirani 的 JACM 论文《通过凸规划的原始-对偶算法实现市场均衡》的拓展，增加了一些动机和相关问题。阐述很好，结果也很有趣（如果你对这类东西感兴趣的话），但如果你想通读一本书来获得该领域的入门介绍，这可能不是你想要的选择。</p>

<h3 id="misc">杂项</h3>

<h4 id="beyer-jones-petoff-and-murphy-site-reliability-engineering-https-www-amazon-com-gp-product-149192912x-ref-as-li-qf-sp-asin-il-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-149192912x-linkid-3bfad60d002c35ba1809816e159efd77">Beyer, Jones, Petoff, 和 Murphy；<a href="https://www.amazon.com/gp/product/149192912X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=149192912X&amp;linkId=3bfad60d002c35ba1809816e159efd77" rel="noopener noreferrer">《站点可靠性工程》</a></h4>

<p>描述了谷歌如何处理运维工作。具有典型的谷歌口吻，这对许多有“传统”运维背景的人来说是令人反感的，并且假设许多事情只能用 SRE 模型来做，而实际上在没有完全采用 SRE 的情况下也能做到。</p>

<p>更长的描述，请参见<a href="//danluu.com/google-sre-book/" rel="noopener noreferrer">这篇关于谷歌 SRE 书籍的22页笔记</a>。</p>

<h4 id="fowler-beck-brant-opdyke-and-roberts-refactoring-https-www-amazon-com-gp-product-0201485672-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0201485672-linkid-72ba04417a765056c7e41111361dcfba">Fowler, Beck, Brant, Opdyke, 和 Roberts；<a href="https://www.amazon.com/gp/product/0201485672/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0201485672&amp;linkId=72ba04417a765056c7e41111361dcfba" rel="noopener noreferrer">《重构》</a></h4>

<p>在我读它的时候，单凭关于代码异味的章节就值回票价了。但这本书非常成功，以至于重构和代码异味的思想已成为主流。</p>

<p>Steve Yegge 有一段<a href="https://sites.google.com/site/steveyegge2/ten-great-books" rel="noopener noreferrer">关于这本书的精彩推荐</a>：</p>

<blockquote>
<p>2003年10月，当我第一次读这本书时，我感到一种可怕的寒意，就像你突然意识到自己上班5年来一直忘了拉裤子拉链一样。第二天我随口问了一圈：“是的，呃，你读过那本，嗯，《重构》，当然，对吧？哈哈，我问是因为我很久以前就读过了，当然不是现在。”在我调查的20人中，只有1人读过。谢天谢地，我们所有人都忘了拉裤链，不只是我。</p>

<p>……</p>

<p>如果你是一位经验相对丰富的工程师，你会认出书中80%或更多的技术是你已经弄明白并开始习惯性使用的。但它给所有这些技术都命了名，并客观地讨论了它们的优缺点，我觉得这非常有用。而且它推翻了两三条我从最早当程序员起就珍视的准则。不注释代码？局部变量是万恶之源？这家伙是疯子吗？读它，自己判断！</p>
</blockquote>

<h4 id="demarco-lister-peopleware-https-www-amazon-com-gp-product-0321934113-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0321934113-linkid-37e2eb92615c6926852e31fa057c3df5">Demarco &amp; Lister；<a href="https://www.amazon.com/gp/product/0321934113/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0321934113&amp;linkId=37e2eb92615c6926852e31fa057c3df5" rel="noopener noreferrer">《人件》</a></h4>

<p>我大学时读这本书觉得很有说服力。它甚至有<a href="//danluu.com/dunning-kruger/" rel="noopener noreferrer">各种研究</a>支持他们的说法。没有截止日期比有截止日期好。独立办公室比格子间好。基本上所有与我交谈过的开发人员都同意这些。</p>

<p>但几乎每一家成功的公司都是以相反的方式运作的。就连微软也在将建筑从独立办公室改造成开放式布局。有没有可能这些事情其实没那么重要？如果它们真的那么重要，为什么像 Fog Creek 这样坚信此道的公司没有大肆碾压他们的竞争对手？</p>

<p>这本书符合我的偏见，我很希望这本书是对的，但元证据让我想带着批判性的眼光重读一遍，并查阅原始资料。</p>

<h4 id="drummond-renegades-of-the-empire-https-www-amazon-com-gp-product-0609604163-ref-as-li-qf-sp-asin-il-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0609604163-linkid-367bcc7edf76352c97b6050c04af4aaa">Drummond；<a href="https://www.amazon.com/gp/product/0609604163/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0609604163&amp;linkId=367bcc7edf76352c97b6050c04af4aaa" rel="noopener noreferrer">《帝国的叛逆者》</a></h4>

<p>这本书解释了微软激进的文化是如何形成的。引言写道：</p>

<blockquote>
<p>微软不一定招募盖茨的克隆体（尽管公司园区里有很多），而是招募那些与盖茨共有某些显著特质的人——傲慢、好斗和高智商。</p>

<p>……</p>

<p>盖茨以嘲笑他人的想法是“愚蠢的”，或者更糟，“随机的”而闻名，只是为了看看他或她如何为某个立场辩护。这种敌对的管理技巧不可避免地在指挥链中蔓延，并创造了一种冲突文化。</p>

<p>……</p>

<p>微软培育了一种达尔文主义秩序，资源经常被掠夺和囤积以获取权力、财富和声望。一位去度假的经理回来时可能会发现他的地盘被竞争对手袭击，他的项目被置于不同的指挥之下或完全取消。</p>
</blockquote>

<p>关于在微软面试：</p>

<blockquote>
<p>“你喜欢微软什么？”
“Bill 很牛，”St. John 说。“我喜欢牛。我喜欢杀死竞争对手和主宰市场的感觉。”</p>

<p>……</p>

<p>他不确定自己表现如何，觉得自己可能卡壳了，然后问他是否是“善于与人打交道的人”。
“不，我觉得大多数人都是白痴，”St. John 回答。</p>
</blockquote>

<p>这些答案正是微软想要的。它们带来了一份强力的录用通知和激烈的追求。</p>

<p>关于微软的开发者布道：</p>

<blockquote>
<p>曾几何时，微软的布道师通常也负责通过出现在竞争对手的会议上、确保加入标准委员会然后干扰标准制定、以及试图影响媒体来颠覆竞争对手。</p>

<p>……</p>

<p>“我们是微软内部负责操翻微软竞争对手的团队。”</p>
</blockquote>

<p>如果你考虑去微软工作，请读这本书。尽管书中描述的事件已过去很久，但你今天仍能在微软看到这种文化的痕迹。</p>

<h4 id="bilton-hatching-twitter-https-www-amazon-com-gp-product-1591847087-ref-as-li-qf-sp-asin-il-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-1591847087-linkid-19754f59bce55c3f588419feffb68c87">Bilton；<a href="https://www.amazon.com/gp/product/1591847087/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=1591847087&amp;linkId=19754f59bce55c3f588419feffb68c87" rel="noopener noreferrer">《孵化推特》</a></h4>

<p>一本有趣的书，讲述了推特早期发生的背后捅刀、管理不善和随意解雇的故事。我说随意，指的是有报道称关键工程师被解雇，只是为了向其他重要人物显示现任管理层仍然掌权。</p>

<p>我不认识当时在推特工作的人，但我认识许多在下一代初创企业早期工作过的人，有几家公司的人有过极其相似的经历。如果你考虑去一家时髦的初创公司工作，请读这本书。</p>

<h4 id="galenson-old-masters-and-young-geniuses-https-www-amazon-com-gp-product-0691133808-ref-as-li-qf-sp-asin-il-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0691133808-linkid-9c6bc8a67b097a8f79bc4b10e0243fa0">Galenson；<a href="https://www.amazon.com/gp/product/0691133808/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0691133808&amp;linkId=9c6bc8a67b097a8f79bc4b10e0243fa0" rel="noopener noreferrer">《老大师与年轻天才》</a></h4>

<p>这本书是关于艺术以及生产力如何随年龄变化的，但如果其论点成立，它可能也适用于编程。Galenson 应用统计学来确定艺术的“伟大程度”，然后用此来推断艺术家的生产力如何随年龄增长而变化。我没有时间详细研究数据，所以在有更多空闲时间之前我不得不保持怀疑，但即使对持怀疑态度的人来说，这也是有趣的读物。</p>

<h3 id="math">数学</h3>

<p>为什么要关注这个领域？从纯粹的投资回报率角度看，我怀疑学习数学对99%的工作来说是否“值得”。据我所知，我比大多数程序员更常使用数学，但我也用得不那么频繁。但拥有合适的数学背景有时会派上用场，而且我真的很享受学习数学。你的体验可能不同。</p>

<h4 id="bertsekas-introduction-to-probability-https-www-amazon-com-gp-product-188652923x-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-188652923x-linkid-31f59f124c2c8a7ee7917b01c7fbed52">Bertsekas；<a href="https://www.amazon.com/gp/product/188652923X/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=188652923X&amp;linkId=31f59f124c2c8a7ee7917b01c7fbed52" rel="noopener noreferrer">《概率导论》</a></h4>

<p>倾向于直观解释而非 epsilon-delta 严格性的本科入门教材。对于任何希望进行更严格推导的人，书后有一些练习题会更详细地探讨。</p>

<p>包含许多带有可用解答的练习题，这使其成为一本很好的自学教材。</p>

<h4 id="ross-a-first-course-in-probability-https-www-amazon-com-gp-product-013603313x-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-013603313x-linkid-ba9a3635504a6aadc9f40d3faf3c8785">Ross；<a href="https://www.amazon.com/gp/product/013603313X/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=013603313X&amp;linkId=ba9a3635504a6aadc9f40d3faf3c8785" rel="noopener noreferrer">《概率论基础教程》</a></h4>

<p>这是那种定期推出新版本以迫使学生购买新书的书之一（目前亚马逊售价高达174美元）<sup id="fnref:C"><a href="#fn:C" rel="noopener noreferrer">2</a></sup>。我在威斯康星学概率时这是标准教材，我真的想不出有谁觉得它有用。避开。</p>

<h4 id="brualdi-introductory-combinatorics-https-www-amazon-com-gp-product-0136020402-ref-as-li-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0136020402-linkid-9910ad28e52b0c86afa82f449644458a">Brualdi；<a href="https://www.amazon.com/gp/product/0136020402/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0136020402&amp;linkId=9910ad28e52b0c86afa82f449644458a" rel="noopener noreferrer">《组合数学导论》</a></h4>

<p>Brualdi 是一位出色的讲师，是我本科期间最好的老师之一，但这本书错误百出，也不是特别清晰。自那时起这本书已经出了两个新版本，但根据亚马逊评论，书中仍然有很多错误。</p>

<p>关于替代的入门教材，我听说 <a href="https://www.amazon.com/gp/product/B00F5QU8W4/ref=as_li_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B00F5QU8W4&amp;linkId=77558d398300005cc19a20780da38822" rel="noopener noreferrer">Camina &amp; Lewis 的书</a>不错，但我自己没读过。另外，<a href="http://www.amazon.com/Combinatorial-Problems-Exercises-Chelsea-Publishing/dp/0821842625" rel="noopener noreferrer">Lovasz</a> 是一本很好的组合数学书，但它算不上入门。</p>

<h4 id="apostol-calculus-https-www-amazon-com-gp-product-0471000051-ref-as-li-qf-sp-asin-il-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0471000051-linkid-cf7635e3dcc9e368a8ca72ee7a8af3a0">Apostol；<a href="https://www.amazon.com/gp/product/0471000051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0471000051&amp;linkId=cf7635e3dcc9e368a8ca72ee7a8af3a0" rel="noopener noreferrer">《微积分》</a></h4>

<p>第一卷涵盖了微积分 I + 微积分 II 的内容。第二卷涵盖了线性代数和多变量微积分。它在多变量微积分之前先讲线性代数，这使得多变量微积分更容易理解。</p>

<p>从编程角度来看这也很有道理，因为我从微积分中获得的大部分价值在于它在近似等方面的应用，而这在按此顺序教授时清晰得多。</p>

<p>如果没有教授或助教的帮助，这本书可能是一本艰难的入门书。Spring SUMS 系列通常非常适合自学入门各个领域，但我没读过他们的微积分入门书，所以无法推荐。</p>

<h4 id="stewart-calculus-https-www-amazon-com-gp-product-0538497815-ref-as-li-qf-sp-asin-il-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0538497815-linkid-e25774196137c043bb638fdf39bcf473">Stewart；<a href="https://www.amazon.com/gp/product/0538497815/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0538497815&amp;linkId=e25774196137c043bb638fdf39bcf473" rel="noopener noreferrer">《微积分》</a></h4>

<p>又一本为了赚钱而推出带有微小更改的新版本的书。这是威斯康星非荣誉微积分课程的标准教材，结果是我教了很多人用 Apostol 中涵盖的方法进行复杂的积分运算，这些方法对许多人来说直观得多。</p>

<p>这本书的方法是，对于一类问题，你应该匹配到众多可能公式中的一个，然后套用公式。Apostol 更多地教你一些技巧和直觉，可以应用于各种问题。我不确定你为什么会买它，除非是课程要求。</p>

<h3 id="hardware-basics">硬件基础</h3>

<p>为什么要关注这个领域？人们经常声称，<a href="https://news.ycombinator.com/item?id=12095869" rel="noopener noreferrer">要成为一名优秀的程序员，你必须理解你使用的每一个抽象</a>。这纯属胡扯。现代计算对任何人来说都太复杂了，无法真正全面了解底层发生的事情。事实上，现代计算能够做到它所能做到的事情的原因之一就是，即使你对你操作层面之下的大部分技术栈没有深入理解，你也能高效工作。</p>

<p>话虽如此，如果你对软件之下有什么感到好奇，这里有几本书可以让你入门。</p>

<h4 id="nisan-shocken-nand2tetris-http-www-nand2tetris-org">Nisan &amp; Shocken；<a href="http://www.nand2tetris.org/" rel="noopener noreferrer">《Nand到俄罗斯方块》</a></h4>

<p>如果你只想读一本书，这可能是最佳选择。这是一本“101”级别的入门书，深入到门电路和布尔逻辑。正如书名所示，它将从<a href="https://en.wikipedia.org/wiki/NAND_gate" rel="noopener noreferrer">与非门</a>带你到一个可运行的俄罗斯方块程序。</p>

<h4 id="roth-fundamentals-of-logic-design-https-www-amazon-com-gp-product-0534378048-ref-as-li-qf-sp-asin-il-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0534378048-linkid-c60cb3be0ffa6e190224ffce81ece8f8">Roth；<a href="https://www.amazon.com/gp/product/0534378048/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0534378048&amp;linkId=c60cb3be0ffa6e190224ffce81ece8f8" rel="noopener noreferrer">《逻辑设计基础》</a></h4>

<p>比你在《Nand到俄罗斯方块》中看到的更详细地介绍门电路和逻辑设计。书中充满练习题，似乎是为自学设计的。请注意，上面的链接指向第5版。有更新、更贵的版本，但它们似乎改进不多，新材料有很多错误，而且贵得多。</p>

<h4 id="weste-harris-and-bannerjee-cmos-vlsi-design-https-www-amazon-com-gp-product-0321547748-ref-as-li-qf-sp-asin-il-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0321547748-linkid-6019b50b8c3474e0939bd14e6eabf6bb">Weste; Harris, 和 Bannerjee；<a href="https://www.amazon.com/gp/product/0321547748/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0321547748&amp;linkId=6019b50b8c3474e0939bd14e6eabf6bb" rel="noopener noreferrer">《CMOS VLSI设计》</a></h4>

<p>在布尔门电路之下一层，你会接触到 VLSI，一个历史术语（超大规模集成），在今天已经没有太大意义了。</p>

<p>比替代方案更广泛、更深入，阐述清晰。探索了设计空间（例如，关于加法器的部分不只是随意提到几种不同类型，而是探讨了所有可以做出的权衡）。同时，既有习题又有解答，这使其非常适合自学。</p>

<h4 id="kang-leblebici-cmos-digital-integrated-circuits">Kang &amp; Leblebici；《CMOS数字集成电路》</h4>

<p>这是很久以前威斯康星的标准教材。它难懂到助教基本上重新解释了项目和考试所需的几乎所有内容。我觉得它作为参考书还行，但不是一本很好的学习教材。</p>

<p>与 West 等人的书相比，Weste 在设计权衡方面花了更多功夫（例如，在创建并行前缀树加法器时，在设计空间中处于某个特定点到底意味着什么？）。</p>

<h4 id="pierret-semiconductor-device-fundamentals-https-www-amazon-com-gp-product-0201543931-ref-as-li-qf-sp-asin-il-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-0201543931-linkid-bb2f39f1f5776a37b62a2c7e5841d065">Pierret；<a href="https://www.amazon.com/gp/product/0201543931/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0201543931&amp;linkId=bb2f39f1f5776a37b62a2c7e5841d065" rel="noopener noreferrer">《半导体器件基础》</a></h4>

<p>在 VLSI 之下一层，你将了解晶体管的实际工作原理。</p>

<p>对固态器件的解释非常漂亮。文本准确地把握了你需要了解的基础知识（例如，能带图），然后利用这些基础加上清晰的解释，让你很好地理解不同类型的结和器件是如何工作的。</p>

<h4 id="streetman-bannerjee-solid-state-electronic-devices-https-www-amazon-com-gp-product-013149726x-ref-as-li-qf-sp-asin-il-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-013149726x-linkid-467c5ec3606c2fd43280ac8b81cc4c44">Streetman &amp; Bannerjee；<a href="https://www.amazon.com/gp/product/013149726X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=013149726X&amp;linkId=467c5ec3606c2fd43280ac8b81cc4c44" rel="noopener noreferrer">《固态电子器件》</a></h4>

<p>涵盖了与 Pierret 相同的材料，但似乎用数学公式替代了 Pierret 所追求的直观理解。</p>

<h4 id="ida-engineering-electromagnetics-https-www-amazon-com-gp-product-3319078054-ref-as-li-qf-sp-asin-il-tl-ie-utf8-tag-abroaview-20-camp-1789-creative-9325-linkcode-as2-creativeasin-3319078054-linkid-700743f63b21bf95abcaa8b4f80dee0d">Ida；<a href="https://www.amazon.com/gp/product/3319078054/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=3319078054&amp;linkId=700743f63b21bf95abcaa8b4f80dee0d" rel="noopener noreferrer">《工程电磁学》</a></h4>

<p>在晶体管之下一层，你将接触电磁学。</p>

<p>厚度是其他</p><p><em>由 mimo-v2.5 模型翻译，花费 35033 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/programming-books/</link>
      <guid isPermaLink="false">https://danluu.com/programming-books/</guid>
      <pubDate>Sun, 16 Oct 2016 08:06:34 +0000</pubDate>
    </item>
    <item>
      <title>招聘与柠檬市场</title>
      <description>[AI 摘要] 本文探讨了招聘优秀开发者的市场是否因信息不对称而沦为“柠檬市场”，并分析了招聘经理面临的常见障碍。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文探讨了招聘优秀开发者的市场是否因信息不对称而沦为“柠檬市场”，并分析了招聘经理面临的常见障碍。</div><p>Joel Spolsky 有一篇关于"寻找优秀开发者"的经典博文，他在文中推广了这样一个观点：优秀的开发者根本找不到，其推论是，如果你能找到某人，那他就不算优秀。Joel 写道：</p>
<blockquote>
<p>优秀的软件开发者——实际上，每个领域最优秀的人——几乎从不流入招聘市场。</p>
<p>一位平均意义上的优秀开发者，整个职业生涯大概只会申请，总共，大约，四份工作。</p>
<p>……</p>
<p>如果你运气好，真的非常走运，他们会在开放的招聘市场上出现一次，比如说，当他们的配偶决定去安克雷奇接受医学实习时，他们真的会把简历发给他们认为在安克雷奇少数几个愿意工作的公司。</p>
<p>但大多数情况下，优秀的开发者（这几乎是个同义反复），嗯，是优秀的（好吧，这就是个同义反复），并且，通常，潜在的雇主会很快认识到他们的优秀，这意味着，基本上，他们可以去任何他们想去的地方工作，所以他们真的不会投出很多简历或申请很多工作。</p>
<p>这听起来像是你想雇佣的那种人吗？应该是的。这个规则的推论——优秀的人从不流入市场的规则——是，糟糕的人——那些严重不合格的人——会经常出现在市场上。他们总是被解雇，因为他们做不了自己的工作。他们的公司会失败——有时是因为任何雇佣他们的公司可能也会雇佣很多不合格的程序员，所以累积起来就是失败——但有时是因为他们确实如此不合格以至于毁了公司。是的，这种事会发生。</p>
<p>谢天谢地，这些病态不合格的人很少得到工作，但他们确实不断地申请，而当他们申请时，他们会去 Monster.com 一下子勾选 300 或 1000 份工作，试图中彩票。</p>
<p>我猜想，敏锐的读者会指出，我忽略了最大的一个群体，那些扎实、有能力的人。他们比优秀的人更多地出现在市场上，但比不称职的人少，总的来说，他们会在你的 1000 份简历堆中以小数量出现，但最主要的是，现在帕洛阿尔托几乎每一位桌上放着 1000 份简历的招聘经理，都会看到完全相同的 970 份简历，来自同样那批申请帕洛阿尔托所有工作的 970 名不称职人士中的一小部分，他们可能一生都会如此，而只有 30 份简历值得考虑，其中也许，很少，有一位是优秀的程序员。好吧，也许连一个都没有。</p>
</blockquote>
<p>Joel 的论点基本上是，与"糟糕"的开发者相比，"优秀"的开发者不会拥有那么多的工作机会，因为公司会努力留住"优秀"的开发者。Joel 还假设，公司可以很容易地识别潜在的"优秀"开发者。但这两个说法很难调和。如果识别潜在的"优秀"开发者如此容易，为什么不去尝试招募他们呢？同样有理由认为"优秀"的开发者在市场上是供大于求的，因为他们有更好的机会，而"糟糕"的开发者才会死守他们的工作。这种逆向选择在衰退的公司中很常见；我在 IBM 的实习期以及其他地方都见到过这种情况<sup id="fnref:I"><a href="#fn:I" rel="noopener noreferrer">1</a></sup>。</p>
<p>"优秀"的开发者在市场上应该是供大于求还是供不应求？如果我们听听关于招聘的抱怨轶事，我们可能会问，开发者的市场是否是一个柠檬市场。这个想法可以追溯到 Akerlof 1970 年获得诺贝尔奖的论文《"柠檬"市场：质量不确定性和市场机制》。Akerlof 以二手车销售为例，将市场分为好的二手车和坏的二手车（坏车被称为"柠檬"）。如果没有办法区分好车和柠檬车，好车和柠檬车将以相同的价格出售。因为买家无法区分好车和坏车，他们愿意支付的价格是基于市场上平均质量的价格。由于车主知道自己的车是不是柠檬车，非柠檬车的车主就不会出售，因为平均价格被柠檬车的存在拉低了。这导致了一个反馈循环，使得市场上只剩下柠檬车。</p>
<p>这个模型当然与 Joel 的模型不同。Joel 的模型假设"优秀"的开发者是"黏性强"的——他们在每份工作上都会待很长时间。这基于两个假设；第一，潜在的雇主很容易识别谁是"优秀"的，第二，一旦某人被识别为"优秀"，他们现在的雇主会不惜一切代价留住他们（就像在柠檬市场中一样）。但仅第一个假设就足以阻止开发者就业市场成为柠檬市场。如果你能辨别一个<em>潜在的</em>员工是优秀的，你只需去给他们提供目前薪水的两倍（我见过实际发生这种情况）。你需要信息不对称才能创造柠檬市场，而 Joel 假设不存在信息不对称。</p>
<p>如果我们抛开 Joel 的论点，看看就业市场，确实存在信息不完全，但现有的和潜在的雇主都拥有不完全的信息，而谁的信息更好则差异很大。实际上，潜在雇主比现有雇主拥有更好信息的情况非常普遍！</p>
<p>举个例子，我曾与一位合作过的人，我们称他为 Bob，他通过做必要的苦力工作挽救了两个不同的项目，防止了项目彻底崩溃。这两个项目都被宣布为成功，晋升随之而来，他们进行了一场大规模的公关活动，涉及在所有常规媒体如《连线》、《财富》等上发表文章。这为那些擅长邀功的人带来了极好的效果，但对 Bob 来说却并非如此。事实上，我最近合作过的另一个人向我提到，管理层一直在问他为什么 Bob 做简单的任务要花那么长时间。答案是 Bob 忙于确保他负责的服务在发布时不会发生全球性中断，但在 Bob 所在的组织里，这不是能获得功劳的事情。结果是，Bob 的人脉圈知道他很优秀，这使得他很容易在任何其他地方以市场价找到工作。但他的管理层完全不知情，而且根据我目前看到的报价，他们支付他的薪水大约只有他能在别处赚到的一半。公司内部信息传递如此之差，以至于外部管理层对某人的生产力有比内部管理层更清晰看法的情况屡见不鲜。我脑海中有一个特别的例子，但光是想想 Bob 这类原型，我就知道有四个人目前处于类似情况。这得益于我目前所在的公司在这种功能失调方面臭名昭著，但这种情况到处都会发生。当我曾在一家小公司工作时，我们经常从那些太无知以至于不知道自己拥有何种人才的大公司雇佣优秀的工程师。</p>
<p>"优秀"的开发者具有黏性的观点还存在另一个问题，那就是它假设公司有能力按需组建开发者愿意为之工作的团队。通常情况并非如此。举个例子，我曾经加入一个团队，技术负责人强烈反对使用版本控制或编写测试。由于这些（以及其他）实践，五个开发者花了一年时间才为一个简单的问题生产出约一万行勉强能用的代码。此外，那是一个高压锅环境，人们被期望每周工作80小时以上，项目经理会以羞辱的方式让大家加班。在一年内，我加入时团队中的七个人里有三个离开了；其中两个去了不同的公司。公司不想失去这两个人，但它没有能力创造一个能留住他们的环境。</p>
<p>在我加入那个团队前后，我的一个朋友加入了一个非常优秀的团队。他们做的工作对世界有实质性影响，他们有自由和创造的空间，工作的很大一部分涉及学习新奇有趣的东西，等等。每当我听说有人在找工作时，我都会把那个团队推荐给他们。那个团队在可预见的未来已经满了，因为任何认识那个团队的人都把人推荐进去了。但如果你看看那个一年内流失了七个成员中三个的团队，它正在大量招聘。这种动态的结果是，作为一名开发者，如果你加入一个随机团队，你极有可能加入一个人员流动率很高的团队。此外，如果你知道一个好团队，它很可能已经满了。</p>
<p>Joel 的模型隐含地假设，按比例来看，功能失调的开发者比功能失调的工作环境多得多。</p>
<p>在我参加的上一次会议上，我问了我遇到的大多数人两个问题：</p>
<ol>
<li>你知道有没有不严重功能失调的公司？</li>
<li>你知道有没有特别优秀且正在招聘的团队？</li>
</ol>
<p>没有一个人告诉我他们的公司符合标准（1）。一些人建议说，也许 Dropbox 还可以，或者也许 Jane Street 还可以，但答案的形式是"我认识那里的一些人，而且到目前为止我还没听到什么可怕的恐怖故事，另外我偶尔会听到好消息"，而不是"那家公司很棒，你绝对应该去那里工作"。大多数人说他们不知道有什么公司不是<a href="//danluu.com/wat/" rel="noopener noreferrer">一团糟</a>。</p>
<p>少数人对问题（2）有建议，但最常见的回答类似于"哈哈，没有，如果我知道的话我自己就去那里工作了"。第二常见的回答是类似"我认识 Google Brain 团队的一些人，听起来很棒"的形式。有几个团队以是优秀的工作场所而闻名，但事实上它们如此稀少，以至于基本上不可能在那些团队中找到工作。少数人确实知道一些他们强烈推荐且正在招聘的实际团队，但这很罕见。比找到一个我愿意与之共事且考虑换工作的开发者要罕见得多。如果我反过来问他们是否认识任何正在找工作的优秀开发者，答案通常是"有"<sup id="fnref:L"><a href="#fn:L" rel="noopener noreferrer">2</a></sup>。</p>
<p>"优秀"的开发者不可能被找到的观念还有一个问题，那就是开发者（和公司）不是一成不变的。因为我很幸运能在允许人们真正发展的环境中工作，我见过很多人从默默无闻变得非常出色。因为大多数公司在帮助员工发展方面几乎不投入什么，所以在这里你不需要付出太多努力就能做得很好。</p>
<p>另一方面，我见过整个开发者团队因为环境变化而流入市场。举个例子，我曾经认识<em>很多</em>在公司 X 为<a href="https://www.linkedin.com/in/marcyun" rel="noopener noreferrer">Marc Yun</a> 工作的人。那种地方人员流失率很低，因为人们真的很享受在那里工作。然后 Marc 离开了。在接下来的两年里，我认识的在那里工作的所有人，真的，都离开了。这一变化既在寻找团队的就业市场上制造了一个柠檬，又把一批优秀开发者推向了市场。这种事情一直都在发生，现在比过去更频繁，因为当今并购活动频繁的环境。</p>
<p>开发者招聘是一个柠檬市场吗？嗯，这取决于你的意思。开发者和招聘经理都拥有不完全的信息。一个方向的柠檬市场是否会使另一个方向更好或更差并不明显。加入新团队的不确定性使得开发者不太愿意离开现有团队，这使得招聘开发者更难。但开发者经常加入他们不喜欢的团队这一事实使得招聘开发者更容易。净效应是什么？我不知道。</p>
<p>从我的立场来看，找到一个好的经理/团队似乎非常困难，我不知道有什么可复制的策略能做到这一点；我非常同情那些找不到合适位置的人，因为我明白这有多难。但我见过可复制的招聘策略，所以我并不那么同情那些抱怨招聘"优秀"开发者不可能的招聘经理。</p>
<p>当一位招聘经理抱怨招聘时，在我迄今为止见过的每一个案例中，该招聘经理都有以下问题之一：</p>
<ol>
<li><p>他们薪水太低。上次我找工作时，我发现同一家可能雇佣我的公司<em>在同一个地理区域内</em>的薪酬差异高达6倍。基本上所有公司都认为自己有竞争力，即使他们处于薪酬范围的底端。我不知道为什么，但公司似乎总是认为自己给的很好，即使他们甚至离正确的范围还很远。和我交谈过的几乎每个人都告诉我，他们支付给的薪水和任何<em>合理的</em>公司一样多。当然，有些公司给的稍微多一点，但他们是出价过高了！你实际上可以从 Joel 的文章中看到这一点——在他写我引用的那篇文章时，他谈到了 Fog Creek 的薪酬有多好。几年后，他抱怨 Google 为没有经验的大学毕业生出价过高，而最近<a href="https://twitter.com/danluu/status/784929052519837696" rel="noopener noreferrer">他基本上表示你不想为薪水高的公司工作</a>。</p></li>
<li><p>他们拒绝了优秀的甚至"优秀"的开发者<sup id="fnref:H"><a href="#fn:H" rel="noopener noreferrer">3</a></sup>。早些时候，我声称我认识很多正在找工作的优秀开发者。你可能会问，如果有那么多优秀的开发者在找工作，为什么这么难找到他们呢？Joel 声称，从1000份简历中，大约有30人会是"扎实"的，970人会是"不称职"的。在我看来，更像是有400人是扎实的，20人是真正优秀的。只是几乎每个人都使用相同的过滤器，所以最终所有人都在争夺他们认为是扎实的30人。当人们对什么真正导致简历被过滤掉进行随机试验时，结果往往发现那些与工作表现间接相关或无关的特质会产生巨大影响。例如，<a href="http://www-2.rotman.utoronto.ca/facbios/file/RiveraTilcsik.pdf" rel="noopener noreferrer">这项对律师事务所招聘的研究</a>发现，仅凭男性身份和简历上有"上流社会"的标志（帆船、马球和古典音乐，而不是田径、踢野球和乡村音乐），其他不变，就能使面试邀请增加4倍。</p>
<p>我工作的第一家公司，<a href="https://www.amazon.com/gp/product/B01FSZU6FK/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01FSZU6FK&amp;linkId=d15e514c6ecefa224be8f05d4d5837e3" rel="noopener noreferrer">Centaur</a>，其现场面试流程比 Google 和 Facebook 等公司的电话筛选还要宽松。如果你听信 Joel 这样的人，你会认为 Centaur 充满了傻瓜，但在行业超过十年（包括在 Google 工作）后，Centaur 在我工作过的所有地方中，开发者的平均和中位生产力水平都是最高的。</p>
<p>Matasano 以使用不同的过滤标准获得不同的人员而闻名，从而解决了他们的招聘问题。尽管他们的策略取得了巨大成功，但几乎每个人都坚持使用标准策略，选择有名牌背景的人，并运行基本上与其他人相同的面试流程，<a href="http://danluu.com/programmer-moneyball/" rel="noopener noreferrer">推高热门人群的价格，而忽略其他人</a>。</p>
<p>如果我看看我认识的目前需求旺盛的开发者，很大一部分人经历过一段多年就业不足、几乎乞求有趣工作的时期。这些人很容易被雇佣，如果你能找到他们的话。</p></li>
<li><p>他们试图为某种稀有技能组合进行招聘。目前，如果你试图雇佣一个有深度学习经验的人，以及，嗯，任何其他东西，你的日子会不好过。</p></li>
<li><p>他们的功能失调程度远甚于他们自己的认识。我认识一位招聘经理，他抱怨招聘有多难。他没有意识到的是，他团队中的每个人都非常不开心，而且有相当一部分人会向朋友提供反向推荐，告诉他们远离。</p>
<p>这是一个极端的例子，但当VP或创始人对招聘为何如此困难感到困惑，而员工却认为这个地方平庸甚至糟糕时，这种情况相当常见。</p></li>
</ol>
<p>在这些问题中，（1）薪水低是最常见也是最容易解决的。</p>
<p>在过去几年里，Oracle 和阿里巴巴在西雅图建立了新的云计算团队。这是一个竞争相对激烈的地区，这两家公司都有不利于招聘的声誉<sup id="fnref:O"><a href="#fn:O" rel="noopener noreferrer">4</a></sup>。如果你相信那些关于招聘有多难的抱怨，你不会认为有一家公司，更不用说两家，能够在西雅图建立整个云团队。两家公司都通过为具有类似经验的人支付远高于竞争对手的报价解决了这个问题。阿里巴巴以提供如此慷慨的报价而闻名，以至于当我与微软谈判我的录用通知时，微软告诉我，他们会匹配除阿里巴巴之外任何公司的报价。我相信 Oracle 和阿里巴巴在过去几年里雇佣了数百名工程师。</p>
<p>大多数公司不需要雇佣接近数百人；他们可以在不雇佣那么多开发者导致整个市场薪酬上扬的情况下支付有竞争力的薪水，但他们仍然拒绝这样做，同时抱怨招聘有多难。</p>
<p>（2）过滤掉优秀的潜在员工，似乎是"从没因为雇佣IBM而被解雇"的现代版本。如果你雇佣一个有热门背景且擅长传统编码面试的人，结果不行，谁能怪你呢？而且没人会注意到你错过的所有其他人。和（1）一样，这几乎是每个人都认为自己做得很好的事情，他们会说"我们必须降低标准才能雇佣更多人，没人希望那样"。但我从未在一家不把很多最终在其他地方表现出色的人过滤掉的地方工作过。我曾试图让一些被低估的程序员<sup id="fnref:U"><a href="#fn:U" rel="noopener noreferrer">5</a></sup>在我工作的地方被雇佣，但我实际上从未成功过。有一次，我没能成功推荐雇佣的人，在经历了大约四年后（就业不足）在 Google 找到了工作（并在那里成为了明星）。那个人然后把我雇佣到了 Google。没有雇佣那个人不仅失去了我那位才华横溢的朋友，最终也失去了我！</p>
<p>顺便说一句，这说明了 Joel 关于"优秀"开发者从不申请工作的观点存在问题。通常会有一段很长的时期，一个"优秀"的开发者极难被雇佣，即使通过知道他们优秀的人脉网络也不行，因为他们不符合人们想象中"优秀"开发者的样子。此外，Google 曾深入研究过哪些招聘渠道能产生好的结果，发现推荐和内部推荐实际上并没有产生太多信号。虽然人们会推荐"优秀"的开发者，但他们也会推荐糟糕的。大多数公司设立的推荐奖金计划会扭曲激励，使得推荐效果比你预期的要差。由于这个和其他问题，许多公司并不特别看重推荐，而"优秀"的开发者仍然像其他人一样经历正常的招聘流程。</p>
<p>（3）需要奇怪的技能组合，可以通过雇佣拥有所需一半或三分之一专业知识的人并通过培训来解决。在这个问题上似乎不需要太多说服，而且我经常看到这种情况发生。</p>
<p>（4）功能失调<a href="//danluu.com/learning-to-program/#fixing-totally-broken-danluu-com-wat-situations" rel="noopener noreferrer">似乎很难解决</a>。如果我知道怎么做，我就去当经理了。</p>
<p>作为一名开发者，在我看来，我所知道的那些环境良好且薪水优厚的团队在招聘上没有问题，而那些招聘困难的团队可以相当容易地解决这个问题。但我有偏见。我不是招聘经理。可能有某位招聘经理在想："每个向我抱怨找不到好团队的开发者都有这四个明显的问题之一；要是我的问题也这么容易解决就好了！"</p>
<p><small>
感谢 Leah Hanson、David Turner、Tim Abbott、Vaibhav Sagar、Victor Felder、Ezekiel Smithburg、Juliano Bortolozzo Solanho、Stephen Tu、Pierre-Yves Baccou、Jorge Montero、Alkin Kaz、Ben Kuhn 和 Lindsey Kuper 的评论和更正。
</small></p>
<p>如果你喜欢这篇文章，你可能也会喜欢<a href="http://danluu.com/tech-discrimination/" rel="noopener noreferrer">这篇关于科技招聘中不可能存在歧视的虚假性声明的文章</a>。</p>
<div>
<hr>
<ol>
<li id="fn:I">留下来的人描述的环境主要缺少他们想要一起工作的中层人员。有一些在那里待了很久、会一直待到退休的老员工，以及随机入职的应届毕业生。但是，与他们的竞争对手相比，拥有5-15年经验的人相对较少。我认识的坚持最久的人待到了第8年，但当他发现团队中另一位有能力的成员也在面试离职时，他也开始考虑离职；两人中谁都不想成为团队中唯一做事的人，于是他们都争先恐后地离开。
<a href="#fnref:I" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:L">这一节听起来有点像我在找工作。我没有在找工作，但如果我的伴侣在西雅图以外找到工作，我可能最终会被迫这样做。
<a href="#fnref:L" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:H"><p><a href="https://www.youtube.com/watch?v=r8RxkpUvxK0" rel="noopener noreferrer">Moishe Lettvin 有一个我很喜欢的演讲</a>，他谈到有一次他在一个招聘委员会中，他们拒绝了每一位候选人，结果发现那些"候选人"实际上是他们自己面试的匿名化版本！</p>
<p>关于他刚开始在微软面试的部分对微软员工来说应该很熟悉。和往常一样，他在毫无准备和预警的情况下被投入了面试。他完全不知道该做什么，结果写出了不太好的面试反馈。"经典的微软风格"，他的经理把面试反馈转发给了整个团队，并说"不要这样"。"经典的微软风格"是 Moishe 的原话，但我也观察到了同样的事情。我想谈谈我们倾向于进行<em>极度指责性</em>的复盘，以及这如何扭曲了激励，但这可能需要单独写一篇文章。</p>
<p>好吧，我就讲一个故事，以纪念最近离开我前团队去 Google 的一个人。在他加入后不久，他周末在办公室（在他团队中很常见）。另一团队的一位经理在聊天中 ping 他，请他批准一些来自另一团队的代码。新人，想提供帮助，就批准了代码。周一，新人与他的导师谈了谈，导师建议他不要那样帮助其他团队。后来，发生了与那段代码相关的中断。经典的微软风格，另一位团队的经理成功地将中断的责任从他的团队推给了新人。</p>
<p>注意，这个人不在我的3/7统计数据中，因为他是在我之后不久加入的，而且我并不是刻意选择一个离职率最高的时间窗口。</p>
<a href="#fnref:H" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:O">有一段时间，Oracle 声称西雅图办公室的文化与主线 Oracle 文化完全不同，但据我所知，他们忍不住要让西雅图团队 Oracle 化，而且那部分说辞不再令人信服。
<a href="#fnref:O" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:U"><p>这个脚注是回应 Ben Kuhn 的问题，他问我，哪些类型的开发者被低估了，你会如何找到他们？我认为这个群体足够多样化，没有一种简单的方法可以找到所有人。有像"Bob"这样的人，他们做着关键工作但根本不被注意。还有那些面试很糟糕的人，比如<a href="https://www.linkedin.com/in/jeshua-smith-1a873858" rel="noopener noreferrer">Jeshua Smith</a>。我相信他只得到过一次不是优秀的绩效评估（那个学期，他的经理说他只能给出一个最高评级，而且只给他的两个顶级员工中的一个也不公平，所以他给了他们俩平均评级）。在他工作过的每一个地方，他都被公认为一个你可以带着难题或问题去找他的人，而且更高职位的工程师也经常向他求助。我曾试图让他在我工作的两家公司被雇佣，他两次面试都失败了。他不擅长面试。据我所知，他的面试表现差点让他无法得到现在的工作，但他的推荐人如此众多且有力，以至于他现在的公司决定冒险一试。但他之所以有那些推荐人，是因为他原来的组织一直在瓦解。他的新公司从他原来的公司<em>吸纳了很多人</em>，所以新公司里有很多认识他的人。他几乎在其他任何地方都得不到面试机会。我试图推荐但失败的另一个人，我称之为 Ashley，她在 Google 的招聘筛选阶段因为技术不够强而被拒绝，尽管我内部推荐她是我认识的最强的程序员之一。但她来自一个不符合招聘人员认为程序员应有的样子的"非传统"背景。非传统是个有趣的词，因为似乎大多数程序员都有"非传统"背景，但你明白我的意思。</p>
<p>这里的多样性足够大，没有一种方法可以找到所有这些人。有一个更像 Matasano 而不是 Google、Microsoft、Facebook 或几乎任何你能说出的 YC 初创公司的过滤流程，可能是一个好的开始。</p>
<a href="#fnref:U" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 12244 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/hiring-lemons/</link>
      <guid isPermaLink="false">https://danluu.com/hiring-lemons/</guid>
      <pubDate>Sun, 9 Oct 2016 09:44:14 +0000</pubDate>
    </item>
    <item>
      <title>我一个周末就能做出来！</title>
      <description>[AI 摘要] 文章反驳了“大公司产品可以轻松快速构建”的观点，以搜索为例，说明性能优化、多语言支持、安全及组织协调等隐藏复杂性导致大型服务需要庞大团队。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章反驳了“大公司产品可以轻松快速构建”的观点，以搜索为例，说明性能优化、多语言支持、安全及组织协调等隐藏复杂性导致大型服务需要庞大团队。</div><p>我想不出有哪家大型软件公司没有经常遭遇这样的网络评论：“那些员工到底在干什么？他们那个产品我自己就能做出来。” <a href="https://bitquabit.com/post/one-which-i-call-out-hacker-news/" rel="noopener noreferrer">本杰明·波拉克</a>和<a href="https://blog.codinghorror.com/code-its-trivial/" rel="noopener noreferrer">杰夫·阿特伍德</a>曾指出Stack Overflow上就有这种人。但<a href="http://nickcraver.com/blog/2016/03/29/stack-overflow-the-hardware-2016-edition/" rel="noopener noreferrer">Stack Overflow相对明显地精简高效</a>，所以普遍的回应是类似这样的：“哦，当然也许Stack Overflow是精简的，但FooCorp肯定臃肿不堪。”而由于大多数人对FooCorp内部运作知之甚少，对于任何具体的FooCorp来说，这听起来像是个合理的说法。毕竟，什么产品可能需要成百上千，甚至数千名工程师呢？</p>

<p>几年前，在rapgenius SEO争议之后，一些人呼吁有人能写出更好的搜索引擎。亚历克斯·克莱默回应说，<a href="http://blog.nullspace.io/building-search-engines.html" rel="noopener noreferrer">也许打造一个比谷歌更好的搜索引擎并非易事</a>。考虑到谷歌5000亿美元市值中有多少来自搜索业务，以及数十家（或数百家？）竞争对手为了分一杯羹投入了多少资金，我认为搜索并非一个简单的问题，这听起来合情合理。但在亚历克斯帖子的评论中，有多人回应说Lucene基本上和谷歌做的一样，而且Lucene几年内就将超越谷歌的能力。从那时起已经过去足够长的时间，我们可以回顾并说，Lucene的进步还没有大到让通过组装Lucene集群的初创公司能威胁到谷歌的地步。如果说有什么变化的话，那就是创建一个有竞争力的谷歌搜索替代方案的成本增加了。</p>

<p>对于打造一个可行的谷歌竞争对手，我相信排名比索引更难，但即使我们只看索引，也存在像Twitter这样包含大约一万亿页面的单一领域，我猜我们能找到大约一万亿个这样的域。如果你尝试配置任何现成的搜索索引来存储数万亿个条目的索引，以处理谷歌百分之一的负载，并设定，比如说，100毫秒的延迟预算（大部分延迟应来自排名，而非索引），我认为你会发现这并非易事。而且，如果你用谷歌搜索Twitter，你可以观察到，至少对于某些用户或推文而言，谷歌对Twitter的索引速度足够快，从用户角度看几乎是实时的。任何尝试在高负载下对大型语料库使用Lucene进行实时索引的人也会发现这并不简单。你可能会说这并不完全公平，因为确实能找到一些未被主流搜索引擎索引的推文，但如果你想决定索引什么或不索引什么，嗯，在一般情况下，这也是个非平凡的问题。而我们在这里只讨论了索引，索引是构建搜索引擎相对容易的部分之一。</p>

<p>真正关心盈利的企业会投入大量时间（因此，大量工程师）来优化系统，即使该系统的最小可行产品可以在一个周末内构建出来。还有大量研究发现，对于某些业务来说，在相当宽泛的延迟范围内，降低延迟对收入有显著影响。提升性能的另一个好处是降低成本。企业应该持续增加工程师进行优化，直到增加一名工程师的成本等于该工程师带来的边际收入增益加上节省的成本。这通常比人们意识到的需要多得多的工程师。</p>

<p>而这仅仅是性能问题。功能同样重要：当我与任何公司任何产品的工程师交谈时，他们通常会发现，一些看似微不足道的单一功能就能为收入增加整数个百分点。就像性能一样，人们低估了在工程师不再能为自己带来回报之前，可以向一个产品添加多少工程师。</p>

<p>此外，功能通常比外部人士意识到的复杂得多。如果以搜索为例，我们如何确保不同形式的日期和电话号码给出相同的结果？国际化怎么办？每种语言都有需要处理的独特怪癖。在法语中，“l'foo”应该经常匹配“un foo”，反之亦然，但90年代的美国搜索引擎实际上并未正确处理这一点。中文查询的分词怎么办？中文单词之间没有空格，句子也没有唯一的分词方式。日语怎么办？查询中很容易包含四种不同的字母系统。如何处理主要从右向左阅读，但部分从左向右阅读的阿拉伯语？<a href="https://r12a.github.io/scripts/tutorial/part3" rel="noopener noreferrer">而这甚至不是处理阿拉伯语最复杂的部分</a>！在一个周末项目的MVP中忽略这些东西是可以的，但在真正的业务中忽略它们就意味着忽略大部分市场！其中一些问题通过开源项目得到了不错的解决，但许多问题涉及开放的研究问题。</p>

<p>还有安全性！如果你不通过雇佣安全人员来“臃肿化”你的公司，你最终会像hotmail或yahoo一样，你的产品因其被黑的频率而非其他任何功能而闻名。</p>

<p>到目前为止，我们看到的一切都是技术问题。与组织问题相比，技术问题相对直接。分布式系统被认为很难，因为实际系统可能会丢弃约0.1%的消息，损坏更小比例的消息，并出现从微秒到毫秒的延迟。当我与高层交谈，并将他们认为自己传达的信息与我的同事认为他们传达的信息进行比较时，我发现丢失消息的比率远高于50%，每条信息都被损坏，延迟可能长达数月或数年<sup id="fnref:A"><a href="#fn:A" rel="noopener noreferrer">1</a></sup>。当人们想象构建某样东西需要多长时间时，他们通常想象的是一个运作完美且100%时间用于编码的团队。但这不可能扩展。问题不在于是否会存在低效，而在于低效程度有多大。一家能够消除组织低效的公司将是比任何科技初创公司都更伟大的创新。但在计算一家公司“应该”有多少员工时，人们通常假设该公司是一个高效的组织。</p>

<p>本文恰好以搜索为例，因为我遇到了一些声称Lucene即将超越谷歌能力的人，但本文内容与搜索并无特殊关联。如果你与几乎任何领域的人交谈，你都会听到关于人们严重低估该领域问题复杂性的故事。这里的关键不是小型团队不可能构建出比谷歌搜索更好的东西。完全有可能有人会拥有像PageRank那样伟大的创新，并且一个小型团队可以将其转化为一家可行的公司。但是，一旦这家公司度过风投融资的超高速增长阶段并希望最大化其利润，除非公司想因为硬件和软件低效而每年损失数亿或数十亿美元，否则它最终将拥有一个数千人的平台组织，就像谷歌一样。而且公司会希望处理泰语、阿拉伯语、中文和日语，每一种都不简单。公司还希望有相对良好的安全性。还有数百个用户甚至意识不到的小功能，每个都带来显著的收入增长。很明显，公司应该外包其计费，但当你与自行处理计费的公司交谈时，他们会指出某些能将转化率提高个位数或两位数百分比的单一功能，这些是Stripe或Braintree无法提供的。超过一定规模后，那个五十人的计费团队完全物有所值。然后还有销售，大多数工程师甚至没有想到<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">2</a></sup>；适用于优化的完全相同推理也适用于销售——只要增加一名销售人员的边际收益超过成本，你就应该预期公司会持续增加销售人员，这通常会导致销售团队比工程团队更大。还有研究，根据定义，这涉及大量未成功的尝试！</p>

<p>并不是说所有这些事情对于运行一项服务是绝对必要的；而是几乎每一个大型服务，如果不认真处理这些事情，都在把钱白白丢掉。这让我想到我们在不可靠系统中常见的一个谬误，人们构建“快乐路径”，认为快乐路径是“真正的工作”，错误处理可以以后再添加。<a href="//danluu.com/postmortem-lessons/" rel="noopener noreferrer">对于可靠的系统，错误处理的工作量比快乐路径更大</a>。对于大型服务也是如此——所有这些人们不认为是“真正工作”的东西，比核心服务本身的工作量更大<sup id="fnref:W"><a href="#fn:W" rel="noopener noreferrer">3</a></sup>。</p>

<h3 id="correction">更正</h3>

<p>我经常做小的调整并添加新信息而不作说明，但本文的原版本有一个错误，移除这个错误的变化足够大，我认为值得指出。我原本有一个使用Lucene索引整个网络的成本的粗略计算，但数字基于一些论文的结果和在一家商业搜索引擎工作的人士的评论。当我试图复现论文中的结果时，我发现<a href="https://twitter.com/danluu/status/814167684954738688?lang=ro" rel="noopener noreferrer">轻而易举就能获得比某篇论文报告的性能好几个数量级的结果</a>，而当我试图追踪那些商业搜索引擎人士评论的原始资料来源时，发现这些评论背后并无实验证据支持，因此我移除了这个例子。</p>

<p><small>
我正在尝试以意识流的方式撰写博客文章，不做太多编辑。这篇文章<a href="//danluu.com/bimodal-compensation/" rel="noopener noreferrer">以及我的上一篇文章</a>都是这样写出来的。<a href="https://twitter.com/danluu" rel="noopener noreferrer">请告诉我</a>，与我的“常规”文章相比，你对这些文章的看法如何！</small></p><small>

</small><p><small>感谢Leah Hanson、Joel Wilder、Kay Rhodes、Heath Borders、Kris Shamloo、Justin Blank和Ivar Refsdal提供的更正。
</small></p>
<div>

<hr>

<ol>
<li id="fn:A">最近，我很好奇为什么一个因产出不可靠服务而臭名昭著的组织会生产如此多不可靠的服务。当我询问原因时，我发现高层管理害怕发出任何关于可靠性的积极信息，因为他们担心人们会以此为借口拖延进度。高层大约一年前改变了他们关于可靠性的信息，但如果你与个人贡献者交谈，他们仍然相信信息是功能是第一优先级，为了更可靠而放慢功能开发速度对你的职业发展不利（并且基于谁获得晋升，个人贡献者似乎是正确的）。也许再过一年，这个组织才会真正将信息传达给负责晋升的人，再过几年，足够多的考虑到可靠性的软件才会编写出来，他们才会真正拥有可靠的服务。也许。那只是直接影响。二阶影响是，他们的政策导致许多关心可靠性的人去了更关心可靠性而非展示炫酷新功能的公司。他们可能需要十年时间才能解决这个问题。也许。这变得更加困难，因为该组织所在公司以产品经理驱动功能至高无上而闻名。如果这个声誉有可能改变，可能需要数十年。
 <a href="#fnref:A" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:S">对于许多产品，销售团队比工程团队更重要。如果我们打造一个媲美谷歌搜索的产品，我们可能最终也需要销售具有竞争力的云服务所需的基础设施。谷歌实际上尝试过在没有认真组建企业销售团队的情况下做这件事，结果AWS和Azure基本上分割了企业市场。
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:W">这并不是说没有浪费，或者不同公司浪费程度不同。我到处都能看到浪费，但通常不是外部人士认为的浪费。每当我读到外部人士描述我工作过的公司哪里存在浪费时，他们几乎总是错的。在其他地方工作的我的朋友们也描述了相同的动态。
 <a href="#fnref:W" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 5880 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/sounds-easy/</link>
      <guid isPermaLink="false">https://danluu.com/sounds-easy/</guid>
      <pubDate>Mon, 3 Oct 2016 08:14:27 +0000</pubDate>
    </item>
    <item>
      <title>开发者薪酬是双峰分布的吗？</title>
      <description>[AI 摘要] 文章探讨了开发者薪酬是否像法律行业一样呈现双峰分布，并分析了程序员高薪背后的可能原因和局限性。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章探讨了开发者薪酬是否像法律行业一样呈现双峰分布，并分析了程序员高薪背后的可能原因和局限性。</div>自<a href="https://danluu.com/google-wage-fixing/" rel="noopener noreferrer">谷歌等公司的薪资压制性不雇佣协议</a>终结以来，开发者的薪酬已急剧上涨，以至于与法律、咨询等传统高薪领域相匹敌，甚至可能更高。在软件行业，一家高薪科技公司的“资深”开发人员年薪可达35万美元，而“资深”可能指“毕业三年的人”，一名被认为表现优异的工程师获得七位数的收入也并不少见。

这些领域的收入分布都呈现出明显的双峰特征。程序员是否会面临同样的命运？让我们看看能找到什么数据。首先，我们看看<a href="http://www.nalp.org/uploads/0812Research.pdf" rel="noopener noreferrer">美国法律就业协会的数据</a>，它展示了法律薪资何时变为双峰分布。

<h3 id="lawyers-in-1991">1991年的律师</h3>

<p><img src="https://danluu.com/images/bimodal-compensation/law-1991.png" alt="1991年第一年律师薪资。中位数4万美元，上限接近9万美元" width="1280" height="914"></p>

<p>中位数薪资为4万美元，数字缓慢下降，直至约9万美元。根据美国劳工统计局的数据，1991年的9万美元相当于2016年的16万美元。这是个相当不错的起薪。</p>

<h3 id="lawyers-in-2000">2000年的律师</h3>

<p><img src="https://danluu.com/images/bimodal-compensation/law-2000.png" alt="2000年第一年律师薪资。中位数5万美元；双峰分布，峰值在4万美元和12.5万美元" width="1280" height="908"></p>

<p>到2000年，分布已变为双峰。较低的峰值在名义（未通胀调整）数字上与之前大致相同，这意味着在实际（通胀调整后）数字上低得多，同时在12.5万美元左右出现了一个较高的峰值，几乎所有人都低于13万美元。2000年的13万美元相当于2016年的18万美元。左侧峰值从1991年的约3万美元移到了2000年的约4万美元；两者换算到2016年都大约是5.5万美元。处于右侧模式的人情况更好，而处于左侧模式的人则大致持平。

</p><p>我不再用更多图表赘述观点，但如果你查看更新的数据，可以看到两个模式之间的中间区域已经变得空心化，加剧了该领域内部的不平等。作为一个职业，律师受到了自动化的严重冲击，在实际数字上，今天排名前5%的报价并不比2000年好多少。但由于双峰分布，排名第50%甚至75%的报价情况更糟。</p>

<h3 id="programmers-in-2015">2015年的程序员</h3>

<p>关于律师说得够多了！程序员的情况呢？不幸的是，很难获得这方面的可靠数据。从轶事来看，我确实感觉我们正走上同样的道路。不幸的是，几乎所有公开的数据源，如H1B数据，都只有薪资数字，而非总薪酬数字。由于上端的薪酬很大一部分是奖金和股票，我找到的大多数数据集无法捕捉真实情况。</p>

<p>一个值得注意的例外是Dan Zhang和Jesse Collins记录的应届毕业生薪酬数据：</p>

<p><img src="https://danluu.com/images/bimodal-compensation/cs-2015.png" alt="2016年第一年程序员薪酬。薪酬范围从5万美元到25万美元" width="1280" height="1006"></p>

<p>这里确实存在很大范围，虽然技术上是双峰分布，但不像在法律和商业领域那样中间存在巨大鸿沟。请注意，这些数据主要来自学士毕业生，少数硕士毕业生。有时薪资更高的博士毕业生数据未被包含。</p>

<p>你知道更好的（更大的）数据源吗？这是来自“黑客松黑客”Facebook小组的约100个数据点，时间是2015年。Dan和Jesse也有2014年的数据，但如果能获得更长时间范围和更多数据就好了。此外，这些数据明显偏向高端——如果你查看所有经验级别程序员的全国平均值，平均值比这个数据集中应届毕业生的平均值低得多。这里的数据与我在争夺人才时听到的数字相符，但“正在与微软谈判offer的人群”也不具代表性。</p>

<p>如果我们有更具代表性的数据，可能会看到在4万到6万美元区间有更多数据点，同时我们这里的数据也存在，这将使数据看起来呈双峰分布。也有可能我们在4万到6万美元区间看到很多点，在7万到8万美元区间有更多点，在9万美元以上区间也有一些点，等等，我们会看到平滑的下降而非两个明显的峰值。</p>

<p>抛开我们掌握的有限数据，从整体环境来看，程序员的薪酬“应该”是双峰分布的吗？大多数其他具有双峰薪酬结构的领域，其薪酬结构与我们在编程领域看到的大相径庭。例如，顶尖的律师事务所和咨询公司采用“不进则退”的结构，这实际上是一种锦标赛，它扭曲了薪酬并确实使得薪酬最终呈现双峰分布的可能性大增。此外，竞争性公司对所有第一年员工支付相同的费率，他们通过匹配看起来付得最多的公司来确定这个费率。例如，今年，<a href="http://abovethelaw.com/2016/06/breaking-ny-to-180k-cravath-raises-associate-base-salaries/" rel="noopener noreferrer">Cravath宣布将为第一年律师支付18万美元，许多其他公司也纷纷跟进</a>。像大多数高端公司一样，Cravath的薪资表完全基于经验：</p>

<ul>
<li>0年经验：18万美元</li>
<li>1年经验：19万美元</li>
<li>2年经验：21万美元</li>
<li>3年经验：23.5万美元</li>
<li>4年经验：26万美元</li>
<li>5年经验：28万美元</li>
<li>6年经验：30万美元</li>
<li>7年经验：31.5万美元</li>
</ul>

<p>在软件行业，薪酬往往是逐案处理的，这使得我们不太可能像在法律行业那样看到尖锐的峰值。如果我必须猜测，我会说虽然程序员薪酬的离散度在增加，但它不是双峰分布的，但我确实没有合适的数据集来下定论。如果你有更好的数据，请指给我看。</p>

<h3 id="appendix-a-please-don-t-send-me-these">附录A：请不要发送我这些数据</h3>

<ul>
<li>H-1B：主要是薪资。</li>
<li>Stack Overflow调查：仅薪资。此外，数据因调查对Web领域的严重偏向而扭曲——我停止做调查是因为他们的工作描述没有一个与我整栋楼里的任何人匹配，我知道其他人也因同样原因停止。</li>
<li>Glassdoor：在是否包含股票薪酬方面令人困惑地不一致。某些公司的数字似乎包含，但其他公司的不包含。</li>
<li>O'Reilly调查：侧重薪资。</li>
<li>美国劳工统计局：不提供细分的分布数据。</li>
<li>美国国税局：他们肯定有数据，但不共享。</li>
<li>IDG：只有平均值。</li>
<li>公司内部数据：范围太窄。</li>
<li>薪酬调查公司如PayScale：当我与这些公司的人交谈时，他们承认对大公司的薪酬了解甚少，但这正是推动市场顶端的动力（金融除外）。</li>
<li>Twitter上的#talkpay：数字偏低<sup id="fnref:T"><a href="#fn:T" rel="noopener noreferrer">1</a></sup>。</li>
</ul>

<h3 id="appendix-b-why-are-programmers-well-paid">附录B：为什么程序员薪酬高？</h3>

<p>既然我们手头有程序员和律师的薪酬数据，我们来分析一下。编程的报酬如此之高，以至于显得有点荒谬。如果你观察其他薪酬相似的职业，会发现存在多种构成进入壁垒或进入抑制的因素。</p>

<p>看法律，你必须赢得声望彩票并进入顶尖学校，这将花费数十万美元（尽管有可能获得全额奖学金，但顶尖学校中只有相对一小部分学生获得全额奖学金）。然后你必须赢得成绩彩票，获得足够好的成绩进入顶级律所。然后你必须继续赢得锦标赛以避免被踢出局，这需要牺牲任何个人生活的表象。咨询、投资银行等领域也类似。薪酬似乎与牺牲程度成正比（例如，投资银行家薪酬更高，但工作时间比律师更长，私募股权的工时和薪酬介于投行和律所之间，等等）。</p>

<p>医学从牺牲角度来看似乎稍微好一些，因为存在一个限制进入该领域的卡特尔，但医学院和住院医师培训的结合仍然比大多数在Facebook和Google等地方的工作要残酷得多。</p>

<p>编程也没有限制程序员数量的许可机构，也没有同样的声望过滤器，即你必须上顶尖学校才能获得高薪工作。当然，有很多初创公司基本上只雇佣来自MIT、斯坦福、CMU等少数顶尖学校的人，每当我查看初创公司时，都会看到如下招聘信息（以下内容来自一家在Slate Star Codex上长期打广告的公司）：</p>

<blockquote>
<p>我们14人的团队包括6名MIT校友、3名前谷歌员工、1名沃顿商学院MBA、1名MIT计算机科学硕士、1名CMU计算机科学校友和1名“20 under 20”蒂尔研究员。候选人常说我们是他们见过的最强大的团队。</p>
<p>我们不是适合所有人的。我们是一家你妈妈可能永远没听说过的B2B SaaS公司。我们每周工作6天非常努力，因为我们相信移动的未来，我们想要赢。</p>
</blockquote>

<p>痴迷于声望的地方是存在的。但在编程领域，根据声望标记来衡量人似乎是硅谷初创公司的事，而不是顶级薪酬公司的事。大公司（<a href="https://danluu.com/startup-tradeoffs/" rel="noopener noreferrer">其薪酬远高于初创公司</a>）很少根据声望来筛选人。你不仅不需要正确的学位来自正确的学校，你也不需要正确的学位类型，甚至不需要任何学位。虽然没有学位变得越来越少见，但我仍然遇到没有经验且没有学位或学位不相关（如社会学或哲学）的新员工。</p>

<p>没有其他同样高薪领域所具有的进入壁垒，程序员怎么可能薪酬如此之高？一种可能是程序员短缺。如果是这种情况，你会期望更多程序员进入该领域，从而降低薪酬。最近计算机科学专业的招生人数创下了新高，所以这可能已经在发生。另一种可能是编程在某些方面独特地困难，但这在我看来似乎不太可能。编程似乎并不比电气工程或化学工程本质上更难，而且在过去十年里也肯定没有变得难得多，但在这段时间里，编程的薪酬从与其他大多数工程领域相似变成了高得多。我上次与一家电气工程公司谈判offer时，他们告诉我他们的副总裁挣得没我多，而我在一家相对于同行付薪较低的软件公司工作。没有理由相信我们不会看到人员从工程领域流向编程，直到薪酬达到平衡。</p>

<p>另一种可能性是，美国移民法充当了保护主义壁垒来支撑程序员的薪酬。似乎这不可能持久（为什么不会出现真正有价值的非美国公司？），但它目前看起来确实有点道理。当我在谷歌时，令我惊讶的一件事是，他们在华盛顿或科罗拉多付给你的钱与在硅谷差不多，但在伦敦却付得少得多。每当这类讨论出现时，人们总是提出“事实”，即SV的薪资实际上并不像听起来那么好，因为生活成本太高，但公司不仅在西雅图会匹配SV的报价，在匹兹堡这样的地方也会。对此我最好的猜测是，中西部的某个人可以可信地威胁要搬到SV并在那里的任何公司找到工作，而伦敦的人则不能<sup id="fnref:M"><a href="#fn:M" rel="noopener noreferrer">2</a></sup>。虽然我们似乎不太可能放松当前的移民限制，但我们的移民限制已经并继续导致那些原本会在美国创办公司的人在别处创办公司。鉴于美国并没有垄断想创办初创公司的人，而且我们尽力阻止想在美国创办初创公司的人留下，最终必然会有在美国以外创立的Facebook和Google，像美国公司内部竞争一样去争夺程序员。</p>

<p>另一个我最近常听到的理论是，大型公司的程序员薪酬高是因为<a href="https://en.wikipedia.org/wiki/O-ring_theory_of_economic_development" rel="noopener noreferrer">克雷默的O型环模型</a>所描述的现象。该模型假设生产率是乘积性的。如果你的同事更好，你就更有生产力，能创造更多价值。如果是这种情况，你会期待一种同类匹配，即你最终会得到付薪更高的高技能公司和付薪更低的低技能公司。这个模型有一种直观的吸引力，但它无法解释为什么编程薪酬比（例如）电气工程薪酬具有更高的离散度。随着开源的普及，利用公司外高效人员的工作比在大多数领域要容易得多。这个模型应该比大多数工程领域更不适用于编程，但薪酬的离散度却更高。</p>

<p>一个相关的理论，出于类似原因也不可能是正确的，即高薪软件工程师是超级精英中的精英，仅仅因为他们生产力高而获得更高报酬。如果你看看美国劳工统计局说的存在多少程序员（几百万）以及高薪科技公司在美国雇佣了多少工程师（二三十万），高薪软件公司根本不可能只由前1%组成。即使他们的筛选是完美的（而它们被广泛认为是个笑话），他们也不可能优于第90百分位。现实地说，更可能的情况是，一家高薪科技公司的程序员中位数略高于第50百分位。</p>

<p>我最常听到的理论是“软件正在吞噬世界”。理论是：程序员薪酬高并且会继续高，因为软件重要且只会越来越重要。尽管这是最常听到的理论，但与其他领域相比，这似乎不合逻辑。你也可以在90年代末对微处理器设计或光纤光学说同样的话。这些领域今天都比90年代更重要，对处理能力和带宽的需求比以往任何时候都大，而对软件的需求实际上依赖于这些。然而，光学工程就业市场仍未从互联网泡沫中复苏，微处理器设计工程师市场在复苏后，付给经验丰富的博士的工资仍低于Facebook的计算机科学应届毕业生。</p>

<p>此外，任何依赖于市场条件的固有属性、整体经济、编程影响等来论证高程序员薪酬的论点，如果你看看是什么真正推高了程序员薪酬，似乎都不正确。FB拒绝参与<a href="https://danluu.com/google-wage-fixing/" rel="noopener noreferrer">谷歌/苹果基本上成为行业标准的薪资操纵协议</a>，这意味着FB支付的薪酬高于其他主要科技公司。当薪资操纵协议被解除后，其他公司“不得不”接近匹配FB的薪酬，以避免人才流向FB以及彼此之间流失。当它们这样做时，FB不断提高薪酬标杆，薪酬持续改善。[2022年更新] 这在福利和薪酬结构的变化中表现得最为明显，FB做出改变，谷歌几乎立即跟进，其他公司则稍后采纳变化，就像FB取消了归属悬崖，谷歌在几周内也做了同样的改变，这一变化逐渐蔓延到整个行业。也有一些公司支付给程序员的薪酬与FB相当或更高，如Netflix和各种金融公司，但主要科技公司通常不会匹配这些地方的报价，因为它们规模太小，不足以吸引足够多的程序员引起担忧，但FB规模庞大，雇佣的人数足以引起谷歌的担忧，而谷歌匹配FB，两者加起来，其规模足以引起其他主要科技公司的担忧。</p>

<p>因为薪酬增长的机制是武断的（FB本可以不存在，或者完全掌控FB的人扎克伯格可能决定不同的薪酬政策），程序员的薪酬如此之好是相当武断的。</p>

<p>总之，高程序员薪酬对我来说似乎是个谜，并且很乐意听到一个令人信服的理论来解释为什么编程“应该”比其他类似领域薪酬更高，或者为什么它应该与进入壁垒高得多的领域薪酬持平。</p>

<h3 id="update">更新</h3>

<p>Eric Roberts观察到，<a href="https://cs.stanford.edu/people/eroberts/CSCapacity.pdf" rel="noopener noreferrer">计算机科学专业招生在低迷后需要很长时间才能恢复，导致计算机科学学位持有者与需求之间存在巨大缺口</a>。</p>

<p><img src="https://danluu.com/images/bimodal-compensation/cs-degrees.png" alt="从低迷到招生恢复存在超过十年的滞后" width="1800" height="1031"></p>

<p>2001年泡沫破裂导致计算机科学专业招生严重下滑。计算机科学专业招生直到2014年才再次达到之前的峰值，如果你对图形进行拟合，并将峰值外推，招生人数达到历史趋势又花了一两年时间。如果我们没有数据，发现有五年的延迟也不会令人惊讶。对于那些在四年内毕业的人（而不是五年或更久），大多数人不会在大二中期或后期之后转专业，所以这已经导致了两到三年的延迟。而在低迷之后，需要一些时间恢复，所以我们预计至少还需要两到三年。Roberts认为，额外的延迟来自许多其他因素，包括尽管事情看起来还行，但工作很快会被外包出去的恐惧，以及大学反应缓慢。</p>

<p><a href="https://danwang.co/why-so-few-computer-science-majors/" rel="noopener noreferrer">Dan Wang</a>指出，根据Stack Overflow调查，四分之三的开发者拥有学士学位（或更高）。如果从统计上看“很难”在没有计算机科学学位的情况下获得高薪工作，并且2001年低迷存在超过十年的余波，这可能解释了为什么程序员薪酬如此之高。当然，我们大多数人都认识行业内没有学位的人，但没有文凭似乎更难找到入门级职位。</p>

<p>目前尚不清楚这对未来意味着什么。即使缺乏适当文凭的候选人是程序员薪酬高的主要驱动力，也不清楚过去几年创纪录的计算机科学专业招生对未来薪酬意味着什么。可能创纪录的招生意味着我们应该预期薪酬会回落到需要类似技能的其他领域（如电气工程）所见的水平。也有可能招生持续落后需求十年，而创纪录的招生只是跟上了十年前的需求，在这种情况下，我们可能预期较高的薪酬会持续下去（只要其他因素，如从美国以外招聘，不过度影响）。由于存在如此多的延迟，另一种可能性是招生已经或将会超过需求，我们应该预期程序员薪酬会下降。甚至不清楚Roberts的论文是否可以作为当前高薪的合理解释，因为Roberts也发现在80年代存在巨大的能力瓶颈，虽然有些程序员薪酬非常高，但薪酬“非常高”的程序员比例似乎比今天小得多。仅谷歌就雇佣了3万名工程师。如果其中2万名是美国程序员，并且估计美国有300万名程序员，仅谷歌就雇佣了美国程序员的0.6%。如果你加上其他已知提供有竞争力薪酬的大公司（亚马逊、Facebook等），那将是美国所有程序员中的很大一部分，我相信这与80年代的情况大不相同。</p>

<p>我收到的对这篇文章最常见的回应是，我们应该预期程序员薪酬高，因为软件无处不在，未来至少会有同样多的软件。同样的推理也可以应用于比软件更基础的电气工程（因为软件需要硬件），然而电气工程的薪酬与程序员的薪酬不在同一水平。高薪程序员无法在没有微处理器的情况下完成工作，而且处理器的销量比以往任何时候都多，但像英特尔和高通这样的地方“资深”人士的薪酬包甚至不到谷歌或Facebook的一半。你也可以对那些从事供水和污水系统工作的人提出类似的论点，但那些人的薪酬似乎也不在程序员的范围内。任何“X的价格高是因为X重要”的论点都隐含地假设存在某种力量限制了X的供给。“X重要”或“我们需要很多X”的说法遗漏了故事的一半。像“X重要”或“X很难”这样的主张存在的另一个问题是，这些说法对于薪酬低得多的行业似乎并不见得更不真实。如果你对程序员薪酬高的解释同样适用于任何“传统”工程学科，那么你需要解释为什么其他领域不应该同样高薪。</p>

<p>我听到的第二常见的评论是，程序员当然薪酬高，软件公司如此有价值，这使得高薪成为必然。但公司盈利并不意味着员工必然获得高薪。有人用这个论点给我发了一个链接，指向<a href="http://www.businessinsider.com/apple-facebook-alphabet-most-profitable-companies-per-employee-2017-12" rel="noopener noreferrer">这个每员工利润最高的公司列表</a>。名单上有一些付薪相当高的软件公司，如Alphabet（谷歌）和Facebook，但我们也看到像高通、思科、台积电（现在收购了ARM，可以认为软银也算）这样的硬件公司，它们的薪酬甚至不如那些不盈利或勉强盈利、未来也没有大幅盈利路径的软件公司。此外，名单上软件公司的薪酬与每员工利润的相关性并不强。</p>

<p>举一个我熟悉的例子，因为我是在麦迪逊长大的，Epic Systems的高管建立了一家公司，创造了如此多的财富，其创始人估计净资产为36亿美元，这比科技领域最成功的创始人还要多。但Epic的一线工程师薪酬明显低于与硅谷争夺人才的科技公司，甚至低于那些从未赚过钱的科技公司。是什么阻止了一些赚取相似利润的软件公司将几乎所有的创造财富都送到高层？典型答案是生活成本，但正如我们所看到的，在这种情况下，这比平时更说不通，因为谷歌在与Epic同城的麦迪逊设有办公室，而谷歌支付给典型开发者的薪酬是Epic的两倍多。如果有一些简单的生活成本调整，你会预期谷歌在麦迪逊的付薪比在多伦多或伦敦低，但似乎正好相反。这并非麦迪逊独有——例如，你可以在奥斯汀找到一些成功的软件公司，它们支付的薪酬大约是亚马逊和Facebook在同一城市支付的一半，高层管理为自己赚取了很好的生活，而一线工程师生活也不错，但远不及他们如果搬到亚马逊或Facebook等公司所能获得的。</p>

<p>所有这些理论的共同之处在于，它们同样适用于其他领域，因此，如所述，它们不能成为程序员薪酬高于这些其他领域的原因。有人可能认为编程具有许多这些因素的独特组合，或者其中一个因素应该预期比任何其他领域都强烈得多，但我还没有看到有人提出这样的论据。相反，人们只是做出明显错误的陈述，比如“编程真的很难”（在这场讨论中，只有当它是现存最困难的领域并且比其他工程领域难得多时，这个理由才有效）。
</p><div>
<hr>
<ol>
<li id="fn:T">人们常常担心薪酬调查会因人们想炫耀而偏高，但现实似乎是数字偏低，因为人们对于听起来像在炫耀感到尴尬。我有个理论，你可以从其他商品的价格中看到这一点。例如，如果你看房价，它们通常可以根据位置、平方英尺、设施等预测。但在街区拥有最大房子会受到显著惩罚，我怀疑原因与拥有最高薪酬的人不成比例地不参与#talkpay相同：人们不想承认他们拥有最高薪酬、最大的房子或开最豪华的车。嗯，有些人确实会，但平均来说，炫耀那些东西被认为是俗气的。
 <a href="#fnref:T" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:M">有些公司会做一个有趣的举动，将新员工先在加拿大安置一年，然后再将他们引入美国，这使他们进入竞争较小的签证流程。但这足够麻烦，以至于大多数员工都对此望而却步。
 <a href="#fnref:M" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 11922 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/bimodal-compensation/</link>
      <guid isPermaLink="false">https://danluu.com/bimodal-compensation/</guid>
      <pubDate>Tue, 27 Sep 2016 06:33:26 +0000</pubDate>
    </item>
    <item>
      <title>我是如何学会编程的</title>
      <description>[AI 摘要] 本文详述了作者从电气工程师转变为程序员的非典型、充满偶然的学习与职业历程。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文详述了作者从电气工程师转变为程序员的非典型、充满偶然的学习与职业历程。</div><p>塔维什·阿姆斯特朗（Tavish Armstrong）有一篇很棒的文档，<a href="https://github.com/tarmstrong/longcv/blob/master/bio.md" rel="noopener noreferrer">他在其中描述了自己如何以及何时习得了他所掌握的编程技能</a>。我喜欢这个想法，因为我发现人们进入编程领域的路径远比刻板印象所揭示的要多样化得多，并且我认为了解进入编程领域有多种可能路径是很有用的。</p>

<p>就个人而言，我在从事编程工作之前，当了十年的电气工程师。当我与人们谈论这段经历时，他们常常想从中提炼出一个连贯的叙事。也许是我的数学背景给了我能够应用于许多问题的工具，也许是我的硬件背景让我对性能和测试有了很好的理解，又或者两者的结合使我非常适合硬件/软件协同设计问题。<a href="https://www.youtube.com/watch?v=RoEEDKwzNBw" rel="noopener noreferrer">人们喜欢一个好故事</a>。一个人们似乎喜欢的叙事是：我是个善于解决问题的人，而且这种解决问题的能力是普遍适用的。但现实是混乱的。电气工程对我来说似乎再自然不过，我毫不费力就掌握了它。编程对我来说却很不自然，有好几年都完全搞不懂。如果你相信程序员要么“有天赋”要么“没有”这种常见叙事，那我显然属于“没有”的那一类。然而，我现在靠编程谋生，而且人们似乎对我所做的工作还挺满意的。</p>

<p>这是怎么发生的呢？嗯，如果追溯到最初，在成为硬件工程师之前，我花了不少时间做那些失败的孩童项目（比如写一个井字棋游戏和AI），并且完全没有“领悟”编程。我确实有时能从我的数学或硬件技能中获得很多价值，但我怀疑我可以在不到一年的时间内教会别人我实际可用的数学和硬件技能。花五年在学校和十年在工业界才掌握这些技能，对于达到我今天的位置来说，是一条迂回曲折的路。令人惊讶的是，我发现我的路径比大多数同事的都要直接，这揭穿了大多数程序员都是早慧的天才少年、很早就接触编程的叙事。</p>

<p>而且，尽管我每天只用到我所学技术技能的一小部分，但我发现我有一套一直在使用的元技能集。这套元技能集没什么深奥的，但因为我经常在新的（对我来说）问题领域工作，我发现我的元技能集比我的实际技能更有价值。我认为，通过写一篇博客文章来传达元技能（如沟通能力）的重要性，就像<a href="https://byorgey.wordpress.com/2009/01/12/abstraction-intuition-and-the-monad-tutorial-fallacy/" rel="noopener noreferrer">通过说“单子就像墨西哥卷饼”来解释单子是什么</a>一样，是做不到的。话虽如此，我还是打算讲讲这个故事。</p>

<h3 id="ineffective-fumbling-1980s-1996">无效的摸索（1980年代 - 1996年）</h3>

<p>我和我的许多朋友试了很多次，都没能学会编程。我们尝试了BASIC，能写一些简单的循环，使用条件判断，打印到屏幕上，但从未弄清楚如何做任何有趣或有用的事情。</p>

<p>我们在学校接触过某种和乐高相关的编程东西，但我们都只知道如何按照说明去做，无法举一反三。虽然这很有趣，但和电子游戏一样没什么教育意义，影响力也类似。</p>

<p><a href="https://www.linkedin.com/in/jeshua-smith-1a873858" rel="noopener noreferrer">我们中的一位</a>得到了<a href="https://www.amazon.com/gp/product/1568301839/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=1568301839&amp;linkId=408c9bc67c8e6b0a405ce7ccdd7ed7b4" rel="noopener noreferrer">一本游戏编程书</a>。我们读了它，尝试做了几件事，但毫无进展。</p>

<h3 id="high-school-1996-2000">高中时期（1996 - 2000年）</h3>

<p>我们这种无效的摸索一直持续到高中。由于对游戏的兴趣，我开始对基准测试感兴趣，这最终引导我了解了CPU和CPU微架构。那是在谷歌的早期，在谷歌学术出现之前，也大多数CS/EE论文能在网上免费找到之前，所以这些资料主要来自爱好者网站。幸运的是，那时互联网还比较年轻，我常去的网站上的用户也是如此。许多硬件资料是面向（甚至由）像我这样的人撰写的，这使得它易于理解。不幸的是，很多编程资料是由专业程序员编写并面向他们的，比如<a href="http://www.azillionmonkeys.com/qed/optimize.html" rel="noopener noreferrer">Paul Hsieh的优化指南</a>。当时也有一些面向初学者的编程指南，但我和我的朋友们没有碰巧发现它们。</p>

<p>我们在高中有编程课：一门涵盖Visual Basic的入门课和一门教授C++的<a href="https://en.wikipedia.org/wiki/Advanced_Placement_exams" rel="noopener noreferrer">AP课程</a>。两门课都由不太懂编程或如何教编程的人教授。我的班级里有几个已经会编程的孩子，<a href="https://www.topcoder.com/members/po/" rel="noopener noreferrer">当TopCoder网站上线时，他们通过参加编程竞赛能赚到不少钱</a>，但他们未能通过入门班的测试，因为测试包括VB6 IDE的截图，你需要正确识别每个按钮的作用才能得分。这门课的教学效果，从考前测试是识别IDE的UI元素这一点就能略知一二。</p>

<p>第二年的AP课效果也差不多。大约课程进行到一半时，几个学生组织了一个自学小组，使用一本替代教材，因为这门课显然没有为AP考试做准备。我通过了AP考试，因为那是那种即使不完全掌握内容也有可能通过的选择题考试。</p>

<p>尽管我没学到多少东西，但如果没有AP课程，我可能就无法从高中毕业了。我挂了足够多的单科，差点学分不够毕业。我拿到那些必要的学分有两个原因：首先，很多老师有个约定，如果你在AP考试中取得好成绩，他们就会让你在该门课上及格（通常是A，有时是B）。即使这样还不够，如果不是我的化学老师在发现我AP化学考得好之后也把我的分数改成了及格分<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">1</a></sup>。</p>

<p>除了没从高中退学，我不确定我的AP课还给了我什么。我的AP CS课实际上对我的编程学习产生了净负面影响，因为AP考试让我免修了大学的前两门入门CS课（一门编程导论和一门数据结构课程）。回想起来，我应该选修那些入门课的，但我没有，这导致我的知识存在巨大空白，几乎用了十年时间才真正弥补。</p>

<h3 id="college-2000-2003">大学时期（2000 - 2003年）</h3>

<p>因为我差点高中毕不了业，所以不可能进入一所“好”大学。幸运的是，我在威斯康星州长大，州内有一所“好”学校，它使用一个公式来决定谁能自动被录取：GPA的分数线取决于标准化考试成绩，任何标准化考试成绩高于某个标准的人，无论GPA如何，都会被录取。在迎新会上，我与一位负责招生的人交谈，得知我这一届是他们最后一次使用这个公式。</p>

<p>我主修了计算机工程和数学，原因现在回想起来相当糟糕。我完全不知道自己真正想学什么。我选择了计算机工程或工程力学，因为这两个听起来都“很难”。</p>

<p>我尝试了多种方法想出更好的专业选择标准。最认真的一次是我花了一周时间与教授交谈，试图了解不同领域日常生活的样子。这种方法有两个关键缺陷。首先，大多数教授并不知道在工业界工作是什么样子；现在我在工业界工作并与学术界的人交谈，我看到大多数没有在工业界工作过的学者对实际情况有很多误解。其次，即使我设法得到了不同领域的准确描述，事实证明有大量研究表明人类基本上无法预测自己会喜欢哪些活动。最终，我通过抛硬币做出了决定。</p>

<h4 id="math">数学</h4>

<p>我原本没打算主修数学，但我大一的微积分入门课太有趣了，最终我加上了数学专业。这能发生，是因为我的一位高中朋友把荣誉微积分课程的申请表递给了我，他认为我可能会感兴趣（他已经学完了整个微积分课程以及线性代数）。这门课的教授以异常快的进度讲授内容：他在学期中就完成了原本应该是一学年的微积分教材内容，然后在剩下的学期里讲授他的研究。这门课以定理证明为导向，不涉及我之前与数学相关联的那些讨厌的死记硬背。那是我人生中第一次觉得上学是件令人兴奋的事，让我真的很期待去上数学课。后来我才发现，非荣誉微积分课程包含大量死记硬背，因为工程学院要求我回去重修我因已在入门微积分课上学过内容而跳过的微积分II。</p>

<p>如果我没有朋友把荣誉微积分的申请表递到我手里，我可能就不会主修数学，也可能永远不会发现任何值得去上的课。即使就这样，我大学里最引人入胜的教授都是数学教授<sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">2</a></sup>，我基本上翘了其他课程的课。我不知道有多少是因为数学课规模小得多，因此更能根据班上学生的需求定制（当时计算机工程非常热门，课程人满为患），有多少是因为这些教授确实是优秀的老师。</p>

<p>尽管我偶尔会用到我学过的数学知识，但大部分价值在于让我有信心相信自己能够学习并掌握解决任何特定问题所需的数学知识。</p>

<h4 id="engineering">工程</h4>

<p>在我的工程课程中，我学会了如何<a href="http://danluu.com/teach-debugging/" rel="noopener noreferrer">调试</a>以及计算机如何工作到晶体管级别。我花了很多时间翘课，在图书馆阅读感兴趣的主题，包括计算机算术和电路设计。我仍然对<a href="https://www.amazon.com/gp/product/1568811608/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=1568811608&amp;linkId=ebede6c98dc79cb83fa8695049df4dc4" rel="noopener noreferrer">Koren的《计算机算术算法》</a>、<a href="https://www.amazon.com/gp/product/078036001X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=078036001X&amp;linkId=e806434c2ef1eeef9e3d5963ecb68393" rel="noopener noreferrer">Chandrakasan等人的《高性能微处理器电路设计》</a>有着美好的回忆。我也开始阅读论文；我在图书馆花了很多时间阅读物理和工程论文，这些论文大多对我来说都难以理解。一个显著的例外是系统论文，我发现它们读起来很容易。我清楚地记得读过<a href="http://www.hpl.hp.com/techreports/1999/HPL-1999-77.html" rel="noopener noreferrer">Dynamo论文</a>（这是惠普关于JIT的论文，不是同名的亚马逊近期工作），但我想不起当时读过的其他论文了。</p>

<h4 id="internships">实习</h4>

<p>我做过两次实习，一次在美光（Micron），我“参与”了闪存工作；另一次在IBM，我参与了POWER6项目。美光的实习是糟糕实习的教科书式例子。当我报到时，我的经理很惊讶自己会有一个实习生，并且没有给我安排任何工作。过了一段时间（也许一天），他给我找了个任务：按手机上的按钮。他设法找到一部使用美光闪存芯片的手机；他把手机递给我，告诉我要测试它，然后就走开了。</p>

<p>在手机上戳了一两个小时，没有发现任何明显的问题后，我四处走动，找到那些有任务可以交给我做的人。大多数任务只是比“测试”手机（通过猛按按钮）稍微不那么手动一点，但我完成了一项并非完全无趣的任务，即验证闪存芯片的控制器是否行为正确。与其他任务不同，这个任务适合自动化，我能够编写一个Perl脚本来替我完成测试。</p>

<p>我选择Perl是因为有人桌上有一本Perl书可以借给我，当时我觉得这似乎是个足够好的理由。我打电话给我的一个朋友，告诉他这门伟大的“新”语言，我们一起实现了《文艺复兴时代》（Age of Renaissance），一款我们高中时玩过的桌游。我们没有完成，但Perl用起来足够简单，让我们觉得可以写出真正能做一些有趣事情的程序。</p>

<p>除了学习Perl，我还学会了我可以向人们要书来读，于是我实习的剩余时间大半都花在了一边留意一项手动任务，一边阅读人们放在那里的书上。大多数书籍都与模拟电路设计或闪存有关，所以我就学了那些。具体的知识对我的职业生涯没有真正用处，但我学到了两个有用的元认知点。</p>

<p>第一，在工作中花时间阅读或学习（在大多数团队中）不会有人阻止你。美光尽力阻止实习生学习，默认策略是禁止实习生访问互联网（经理可以推翻该策略，但我的经理没有），但当实习生的另一个任务是随机按手机按钮时，不会有人特意阻止他们阅读书籍。</p>

<p>第二，我了解到，有许多工程问题我们无需知道原因就能解决。我读的一本书是关于当时闪存研究的综述。那时，闪存依赖一些已被充分表征但并未真正理解的行为。关于底层物理机制如何运作存在理论，但确定哪种理论正确仍是一个悬而未决的问题。</p>

<p>第二年，我在IBM进行了一次更有教育意义的实习。我被分配到POWER6的一个逻辑设计团队，由于他们不知道如何安排我，就让我负责验证他们编写的逻辑。他们有一个相对较新的工具，叫做<a href="//danluu.com/testing/#fn:S" rel="noopener noreferrer">SixthSense，你可以把它看作是加强版的QuickCheck</a>。我学到的明显技能是如何使用高级测试框架编写测试，但我学到的更有价值的元技能是：编写测试用例生成器和检查器，往往比大多数地方那种冒充自动化测试的手动测试用例编写更有效率。</p>

<p>我在IBM第一次接触的另一件事是版本控制（不幸的是，用的CVS）。回想起来，我有点惊讶，不仅在我的所有课程中从未使用过版本控制，而且我从未遇到过任何其他使用版本控制的学生。我在IBM的实习是在本科和研究生之间，因此我在获得理学学士学位时从未使用过或见过任何人使用版本控制。</p>

<h4 id="computer-science">计算机科学</h4>

<p>我上了几门CS课程。第一门是算法，这门课教得很糟，因此分数被大幅调整，结果我尽管什么都没学到，却得了A。这门课不涉及任何编程，虽然我本可以在课余时间做一些实现，但我当时对工程更感兴趣，没有尝试应用任何课程内容。</p>

<p>第二门课是数据库。有几个编程项目，但都是那种给你一些框架，你只需实现几个关键方法就能让程序跑起来的项目，因此即使完全不懂编程也能做得不错。我参与了一场“看谁上课次数最少”的比赛，什么都没学，勉强得了个B。</p>

<h3 id="grad-school-2003-2005">研究生时期（2003 - 2005年）</h3>

<p>本科毕业后，我决定去读研究生，原因有几个有点傻。一个是“为什么不呢？”的组合，加上大多数教授给出的论点：如果你本科毕业后不立即去读研究生，以后就很难再回去读书了。但人们不回去读书的原因是他们获得了更多信息（他们既知道上学是什么样，也知道工作是什么样），而他们几乎总是选择工作！另一个主要原因是我觉得有硕士学位能找到更有趣的工作。这显然不一定是错的，但对于从事电气工程和编程的人来说，似乎普遍不成立。</p>

<p>我不知道我今天是否用到了任何在研究生阶段学到的东西，无论是直接意义上还是元认知意义上。我遇到了一些很棒的教授<sup id="fnref:W"><a href="#fn:W" rel="noopener noreferrer">3</a></sup>，也交了一些好朋友，但我觉得这不是对时间的良好利用，原因在于我在19或20岁时做出的两个糟糕决定。我没有选择一所有很多人在我感兴趣领域工作的学校，而是选择了一所给我奖学金但只有一个我真正感兴趣的领域在工作的学校。而那个人在我开始前就离开了。</p>

<p>我最终研究了光学，虽然学习一个新领域很有趣，但这段经历对我来说没有特别的价值，我本可以学习我更感兴趣的领域同样获得乐趣。</p>

<p>虽然我名义上在研究光学，但我仍花了很多时间学习不相关的东西。有一次，我决定应该学习Lisp或Haskell，可能是因为保罗·格雷厄姆（Paul Graham）写的东西。我在图书馆找不到Lisp教材，但找到了一本Haskell教材。在我完成了练习之后，我仍然不知道如何完成任何实际的事情。但我确实学会了列表推导式，并养成了使用高阶函数的习惯。</p>

<p>根据网络评论和建议，我形成了学习更多语言会教会我如何成为优秀程序员的想法，因此我学习了Python和Ruby的入门书籍。就我所能判断的，这基本上没有教我任何有用的东西，我本来应该去学习某个特定领域（比如算法或网络）而不是学习多种语言。</p>

<h3 id="first-real-job-2005-2013">第一份正式工作（2005 - 2013年）</h3>

<p>在研究生快结束时，我主要寻找并找到了电气/计算机工程的工作。一个显著的例外是谷歌，他们打电话给我，要飞我去山景城面试。我告诉他们可能找错人了，因为他们甚至没有进行电话筛选，于是他们提出进行电话面试。我参加电话面试时以为自己会失败，因为我没有任何CS背景，结果如我所料失败了。回想起来，我应该要求面试硬件职位，但当时我不知道他们有硬件职位，尽管多年来他们一直在组装自己的服务器并设计一些自己的硬件。</p>

<p>总之，我最终进入了一家名为<a href="https://www.amazon.com/gp/product/B01FSZU6FK/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01FSZU6FK&amp;linkId=d15e514c6ecefa224be8f05d4d5837e3" rel="noopener noreferrer">Centaur</a>的小芯片公司。我当时很犹豫是否接受这份工作，因为面试是我所有公司面试中最简单的<sup id="fnref:G"><a href="#fn:G" rel="noopener noreferrer">4</a></sup>，这让我怀疑他们的招聘标准是否较低，因此工程师相对较弱。结果证明，平均而言，那是我共事过的最好的一群人。我当时没有意识到，但后来这让我明白，那些声称因为面试超级难就有天才工程师的公司完全是在胡扯，很多公司推动的面试难度攀比更多是一种声望游戏，而非其他。</p>

<p>但我有点扯远了——我的第一个职位是他们称为“回归调试”的工作，包括调试新生成的测试和回归测试的失败。这份工作的主要目标是让新员工深入了解x86架构的细节。当时，Centaur的测试非常依赖于通过在总线上注入真实指令、中断等进行芯片级测试，因此调试测试失败能教会新员工关于x86的一切。</p>

<p>英特尔的x86手册有几千页长，仅靠它不足以实现一个兼容的x86芯片。当Centaur制造第一款x86芯片时，他们严格遵循英特尔手册，并将所有未定义行为留给具体的实现者。当他们拿到第一款芯片并尝试时，他们发现一些编译器生成的代码依赖于那些在x86上技术上未定义、但在英特尔芯片上恰好总是相同的行为。虽然这技术上是编译器的bug，但你不能发布一个与现有软件不兼容的芯片，因此从那时起，Centaur在实现x86芯片时，都确保芯片与英特尔芯片的精确行为匹配，包括匹配那些官方未定义的行为<sup id="fnref:E"><a href="#fn:E" rel="noopener noreferrer">5</a></sup>。</p>

<p>之后的几年里，我对x86有了百科全书般的知识，能够凭记忆设置控制寄存器和<a href="https://en.wikipedia.org/wiki/Model-specific_register" rel="noopener noreferrer">MSR</a>的位。我在未来任何工作中都没有用到这些知识，但不惧怕底层硬件的元技能经常派上用场，尤其是在我遇到编译器或芯片bug时。如果你说你发现了硬件bug，人们会像看疯子一样看你，但由于我们对英特尔芯片的精确行为特征如此仔细，我们经常发现bug，然后讨论我们应该匹配bug还是匹配规范（英特尔手册）。</p>

<p>从回归调试经历中我学到的另一件事是对自动化终身的热爱。调试通常涉及大量的<a href="//danluu.com/teach-debugging/" rel="noopener noreferrer">机械步骤</a>。在我对x86了解足够多，调试变得无聊后，我开始自动化调试。那时，我知道如何编写简单脚本，但并不真正懂编程，因此无法完全自动化这个过程。然而，我能够自动化到足够程度，使得99%的失败，我只需扫一眼快速摘要就能找出bug是什么，而不是花费可能数小时去调试。这把我之前一份全职的工作变成了每天大约30-60分钟就能完成的事（不包括那些涉及我尚不熟悉的x86冷僻角落，或者我的脚本无法给出有用摘要的bug的日子）。</p>

<p>那时，我做了两件之前在实习中学到的事。第一，我开始在工作中阅读。我从关于编程的在线评论开始，但这类内容不多，所以我问是否可以报销书籍费用并在工作中阅读。这看起来完全正常，因为很多其他人也这样做，而且至少有两个人平均每周阅读一本以上技术书籍，包括一个人平均每2-3天阅读一本技术书籍。</p>

<p>我保持在每周一本到每月一本之间的阅读节奏。我读了很多工程书籍，传递了一些我现在不再使用的知识（因为我大部分时间在写软件）；一些“大理念”的软件工程书籍，如《设计模式》和《重构》，我当时并没有真正领会，因为我只是在写脚本；以及大量关于不同编程语言的书籍，这些似乎对我没有什么影响。</p>

<p>那时我读过的唯一一本以对我而言明显的方式改变了我编写软件方式的书是<a href="https://www.amazon.com/gp/product/0465050654/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0465050654&amp;linkId=48b09d6605f7fcaa58922dcf4060a7df" rel="noopener noreferrer">《设计心理学》（The Design of Everyday Things）</a>。这本书的核心思想是，虽然人们会因为无法使用难以理解的界面而自责，但我们应该指责设计师设计了糟糕的界面，而不是指责用户不会使用。</p>

<p>如果你遇到一扇门，错误地尝试拉而不是推（或反过来），并且有空闲时间，试试观察其他人是如何使用这扇门的。每当我这样做时，我都会看到大约一半尝试开门的人用错了。那就是设计缺陷！</p>

<p>《设计心理学》让我对API和用户体验反馈更加敏感，也更加无法容忍那些说“没问题——大家都知道<code>foo</code>和<code>bar</code>的参数必须以相反顺序给出”或者“拜托！大家都知道你只需要点击菜单<code>X</code>，选择<code>Y</code>，导航到选项卡<code>Z</code>，打开<code>AA</code>，转到选项卡<code>AB</code>，然后将设置滑到<code>AC</code>”的程序员。</p>

<p>我不认为所有这些阅读完全是浪费时间，但我本来应该选择计算机科学或电气工程的几个子领域去深入学习，而不是阅读奥莱利（O'Reilly）或曼宁（Manning）出版的那种类型的书。</p>

<p>并不是说这些书没用，而是几乎所有这些书都是为了让任何随机程序员无需特定背景就能理解而写的，你读第50本针对随机程序员的书所能获得的东西是有限的。在我看来，大多数非学术会议也有同样的问题。作为一个演讲者，你想做一个对听众中每个人都有效的演讲，但这样做的一个副作用是，许多演讲对于参加过几次会议的有经验的程序员来说教育价值相对较小。</p>

<p>我认为从所有这些阅读中我也收获了积极的东西，但我还不知道如何找出那些是什么。</p>

<p>作为我阅读的结果，我还做了两件回顾起来相当有害的事情。</p>

<p>一是我真正迷上了函数式编程，并尽可能到处使用函数式风格。不可变性、高阶X（X可以是任何可能的值）等等。结果是我能快速编写和修改代码，但除了少数几个同样喜欢函数式编程的同事外，其他人根本看不懂。</p>

<p>第二个大的负面影响是我确信Perl给我们带来了很多问题。我们有很难理解和修改的Perl脚本。它们经常是几千行代码，只有一两个函数，没有测试，使用了你能想到的所有晦涩Perl特性。静态类型！魔法符号！隐式一切！你能想到的，我们都用上了。对我来说，压垮骆驼的最后一根稻草是：我在两个没有显式传递参数和返回值的函数之间插入了一个新函数——结果破坏了脚本，因为其中一个函数将一个值返回到一个隐式变量中，而下一个函数读取了这个变量。在两个紧密耦合的函数之间插入另一个函数，我破坏了脚本。</p>

<p>之后，我说服了一群人使用Ruby，并自己也开始使用。问题是我说服了团队的一半人这么做。另一半人继续使用Perl，导致了语言碎片化。更糟糕的是，在另一个小组，他们也受够了Perl，但开始使用Python，导致公司同时拥有Perl、Python和Ruby代码。</p>

<p>Centaur有一项明确政策，即不告诉人们如何做任何事，这排除了制定团队或公司范围标准的可能性。在这种环境下，使用一种“更好”的语言似乎是自然的事，但我没有意识到碎片化的代价，直到后来在职业生涯中看到一家公司利用标准化取得了良好效果。</p>

<p>总之，当我导致可怕的碎片化时，我也自动化了我大部分的回归调试工作。我厌倦了花80%的工作时间阅读，开始寻找其他事情做，在Centaur的整个工作期间我都一直在这样做。我喜欢学习新东西，所以我做了几乎所有与芯片设计相关的事情。我唯一没做的是电路设计（电路设计的技术主管不想让非专业人士干涉他的领域）以及几个角色，有人告诉我“Dan，如果你真的想做那个也可以，但我们付你那么多钱不是让你全职做那个的。”</p>

<p>如果不是我经常参加面试（大约每年一次，即使我对工作很满意），我可能会怀疑做这么多不同的事情是否在阻碍我的职业发展，因为大型芯片公司几乎只培养专家。但在面试中，我发现我的经验受到重视，因为他们无法在内部获得这些经验。讽刺的是，我收到的每一个职位邀请都会把我变成专家。大型芯片公司声称希望员工流动并尝试不同事物，但深入探究其含义时，会发现他们喜欢让一个人在一个非常狭窄的岗位上工作两三年，然后转到下一个非常狭窄的岗位。</p>

<p>有那么一段时间，我怀疑自己是否注定要么最终去一家大公司担任高度专业化的职位，要么在Centaur待一辈子（这并非坏归宿——Centaur的工作流失率远低于我工作过的任何地方，因为人们非常喜欢它）。但我后来发现，构建硬件加速器的软件公司实际上为硬件工程师提供了通才职位，而软件公司也有程序员的通才职位，不过这可能已经无关紧要了，因为大多数软件人可能会认为我是一个极其细分领域的专家。</p>

<p>无论在许多硬件相关岗位上花费大量时间让你认为我是个通才还是专家，我都掌握了许多技能，这些技能在我<a href="http://www.anandtech.com/show/10340/googles-tensor-processing-unit-what-we-know" rel="noopener noreferrer">从事硬件加速器工作</a>时派上了用场，但它们并不真正适用于我今天从事的纯软件项目。不过，我学到的许多元技能迁移得相当好。</p>

<p>如果我必须选择我在那时学到的最有用的三个元技能，我会说是调试、错误跟踪和学会如何处理难题。</p>

<p>调试是一项有趣的技能，因为每个人都认为自己会调试。对我来说，我甚至不会说我在Centaur学会了调试，而是学会了坚持。非确定性的硬件bug比非确定性的软件bug糟糕得多，以至于我总是相信自己能追踪到软件bug。在最坏的情况下，当一个bug在日志中没有被捕获，在调试器中也无法捕获时，我总是可以添加跟踪信息，直到bug变得明显。同样的事情在硬件中也成立，但“重新编译”以添加跟踪信息每次“重新编译”需要3个月；与那种经历相比，追踪一个需要三个月才能弄清楚的软件bug感觉简直太愉快了。</p>

<p>错误跟踪是另一项每个人都认为自己拥有的元技能，但当我审视大多数项目时，发现他们真的不知道自己有什么bug，而且由于未能有效分类bug，经常丢失bug。直到离开Centaur，看到那些不懂如何跟踪bug的团队，我才意识到自己培养了这项技能。在Centaur，根据项目阶段，我们会从零个到一千个未解决的bug。与我密切合作的人会保持一个关于哪些bug是开放的心智模型；当时这看起来完全正常，而且有很多人这样做，这让人们对项目状态、哪些领域提前完成、哪些领域落后了保持一致的看法变得容易。</p>

<p>在Centaur之外，我发现能找到一个跟踪主要未解决bug的人就已经很幸运了。在团队待上一段时间之前，人们通常对将一个重大问题放入bug跟踪而不是立即修复感到不安，因为他们太习惯于bug被遗忘，以至于不信任bug跟踪系统。但这正是错误跟踪的意义所在！我将此视为类似于那些测试覆盖率极低、预发布系统非常不可靠的团队，他们不相信自己能进行修改，因为他们没有信心在问题到达生产环境之前被捕获。这对生产力是巨大的拖累，但在看到替代方案之前，人们并没有真正意识到这一点。</p>

<p>也许我学到的最重要的元技能是学会如何解决大问题。当我加入Centaur时，我看到人们在解决我甚至不知道如何着手的问题。有些像Glenn Henry这样的人，一位曾在IBM处于计算前沿时期的IBM同事，还有Terry Parks，Glenn称他是他在IBM认识的最好的工程师。并不是说他们是10倍工程师；他们不是工作得更快。事实上，Glenn是个二指禅打字员，我的打字速度可能是他的10倍，并且能比他更快解决那些受限于打字速度的琐碎问题。但Glenn、Terry和其他一些高手知道如何处理那些我甚至无法开始着手的问题。</p>

<p>我无法引用某个特别的顿悟时刻。这只是八年的工作积累。当我寻找问题要解决时，Glenn经常会递给我一个比我自认为可能解决的问题稍难一点的问题。我会告诉他我觉得自己解决不了，他会告诉我试试看，大约80%的时候我都能解决。我们这样重复了大约五六年，直到我不再告诉Glenn我觉得自己解决不了。即使我不知道它何时发生，我知道我最终开始认为自己是一个能够解决我们遇到的任何开放问题的人。</p>

<h3 id="grad-school-again-2008-2010">再次研究生时期（2008 - 2010年）</h3>

<p>在Centaur工作期间的某个时候，我转为兼职，在当地的大学上了一段时间课并做了一些研究。出于我记不清的原因，我把时间分配在软件工程和计算机科学理论之间。</p>

<p>我读了很多软件工程论文，得出的结论是，我们对于什么能使团队（甚至个人）富有成效知之甚少，并且该领域在近期不太可能有可操作的答案。我还署名了几篇论文，我认为这些论文对人类知识状态没有做出有意义的贡献。</p>

<p>在计算机科学理论方面，我修了一些研究生级别的理论课程。那确实是教育性的，我第一次真正“领悟”了算法以及复杂性理论等。我本可以在一篇我不认为对人类知识状态有实质性贡献的论文上署名，但我的潜在合著者也有同感，所以我们没有写出来。</p>

<p>我最初再次尝试读研是因为我考虑攻读博士学位，但我发现我所做的工作并不比在Centaur的工作更“有趣”，并且在看到该项目人员的工作结果后，我决定获得博士学位为我提供真正价值的可能性不到1%，于是我全职回到了Centaur。</p>

<h3 id="rc-https-www-recurse-com-scout-click-t-b504af89e87b77920c9b60b2a1f6d5e8-spring-2013"><a href="https://www.recurse.com/scout/click?t=b504af89e87b77920c9b60b2a1f6d5e8" rel="noopener noreferrer">RC</a>（2013年春季）</h3>

<p>在Centaur工作八年后，我想做一些除微处理器之外的事情。我在其他硬件公司有足够的朋友，知道如果我换到另一家硬件公司，除了知名度之外，基本上在各个方面都会降级，所以我开始申请软件工作。</p>

<p>在申请工作的过程中，我听说了<a href="https://www.recurse.com/scout/click?t=b504af89e87b77920c9b60b2a1f6d5e8" rel="noopener noreferrer">RC</a>。它听起来很棒，甚至可能好得难以置信：当我给朋友们看人们对此的评论时，他们认为这些评论是假的。这是一次很棒的经历，我能理解为什么那么多人对它赞不绝口，以至于真实的评论听起来好得不真实。它对很多人来说是变革性的；我听到很多诸如“我在这里三个月学到的比在学校N年学到的还多”或“我完全精疲力竭了，这是我一年来第一次变得高效”之类的感叹。它对我不是变革性的，但那是我度过的最愉快的三个月之一，我甚至还学到了一两件事。</p>

<p>从学习的角度来看，我从RC获得的主要收获是来自<a href="https://github.com/majek" rel="noopener noreferrer">Marek</a>的反馈，我和他一起工作了大约两个月。虽然Centaur的自由和缺乏监督对于培养我独立工作的能力很棒，但我基本上没有得到任何关于我工作的反馈<sup id="fnref:F"><a href="#fn:F" rel="noopener noreferrer">6</a></sup>，因为他们在我在那里时不做代码审查，而且我在绩效评估中也从未真正得到过任何可操作的反馈。</p>

<p>Marek在结对编程时非常擅长给予反馈，与他共事改掉了一些我的坏习惯，并教会了我一些解决问题的新方法。在元层面，RC比大多数地方更注重结对编程，这让我第一次进行了结对编程。我之前没有意识到与某人结对编程在了解他们的工作方式以及是什么使他们高效方面有多么有效。从那以后，我邀请了许多超级高效的程序员结对编程，每次都有所收获。</p>

<h3 id="second-real-job-2013-2014">第二份正式工作（2013 - 2014年）</h3>

<p>我恰逢其时，正好赶上一个项目从<a href="https://www.linkedin.com/in/andrew-phelps-31438b6" rel="noopener noreferrer">Andy Phelps的个人20%时间项目</a>转变为后来被称为<a href="https://www.google.com/patents/WO2016186801A1" rel="noopener noreferrer">Google</a> <a href="https://www.google.com/patents/US20160342889" rel="noopener noreferrer">TPU</a>的过渡期。</p>

<p>据我所知，纯粹是运气，我成为了这个项目的第二个工程师，而不是第五个或第十个。我得以见证一个项目从概念阶段发展成现实的样子。在Centaur，我某种程度上也经历过这种感觉，但我参与的每个项目要么是CPU的一部分，要么是旨在改善CPU开发的工具。这是我第一次从一个非平凡项目的初始阶段就参与其中，而不仅仅是参与项目的一部分，而是整个项目。</p>

<p>无论使用什么方法论，这本来就是教育性的，但由于设计方式的独特，这是一个特别棒的学习经历。我们从对核心算法的长时间讨论开始。在找到了一个能提供可接受性能的算法后，我们在开始认真实现之前，为每个主要模块编写了设计文档。</p>

<p><a href="https://twitter.com/codinghorror/status/724541041827713024" rel="noopener noreferrer">现在许多人认为编写设计文档是浪费时间</a>，但经历这个耗时数月的过程有几个大优势。第一个是，协作设计过程教会了团队中每个人彼此的技巧。这有点像结对编程带来的技能转移，但应用于设计。这对我来说很棒，因为作为一个只有十年经验的人，我是房间里经验最少的人之一。</p>

<p>第二个优势是，在设计阶段迭代速度要快得多，放弃一个设计只意味着擦掉白板。一旦你开始编码，迭代设计可能意味着丢弃代码；对于基础设施项目，这很容易损失人年，甚至数十人年的工作。自从参与TPU项目以来，我见过几个类似规模的项目团队坚持尽快获得“可运行”的代码。在每一种情况下，这都导致了大规模的延迟，因为大量代码必须重写，在少数情况下，项目存在根本性缺陷，导致团队不得不从头开始。</p>

<p>我理解在产品类项目中，因为你无法预知某样东西会有多受欢迎，你可能希望尽快发布一个<a href="https://en.wikipedia.org/wiki/Minimum_viable_product" rel="noopener noreferrer">MVP</a>并进行迭代，但对于纯基础设施项目，在设计阶段通常就可以预测其有用程度。</p>

<p>我从这份工作中获得的另一个重要收获是，当一家公司真正努力让工程师高效时，能够达到什么程度。我在Centaur反复看到的情况是，有人进来，环顾四周，发现工具是生产力的巨大消耗，然后进行大量改进。他们随后会对自己已经大幅改进了情况感到满意，然后转向其他问题。然后下一个新员工进来，有同样的反应，做同样的事情。结果是，工具在那段时间改进了很多，但还没有达到让新人满意的程度。谷歌是我工作过的地方中，唯一一个许多工具相比之下看起来像魔法的地方<sup id="fnref:magic"><a href="#fn:magic" rel="noopener noreferrer">7</a></sup>。当然，人们抱怨许多工具不稳定，文档不足，很多已经过时。都是事实。但这种情况比我工作过的任何其他公司都要好。这似乎并没有真正成为谷歌业务的竞争优势，但它让开发体验变得非常愉快。</p>

<h3 id="third-real-job-2015-2017">第三份正式工作（2015 - 2017年）</h3>

<p>这是一次令人惊讶的经历。我觉得我离它太近了，无法真正知道我从这次经历中获得了什么，所以完整填写这一节是个待办事项。</p>

<p>一个真正有趣的事情是，有很多我曾认为是“完成任务的基本要求”的东西，现在看来是可以没有的。一个例子是版本控制。我一直强烈支持使用版本控制，但我与一位强烈反对版本控制的技术主管共事的项目仍然基本上成功了。在我们开始使用版本控制之前，有很多争论，但处理没有版本控制的后果以及人们没有真正同步更改，每周只花我一两天时间手动合并更改到我的私人仓库以确保构建稳定工作。这显然远非理想，但在整个团队范围内，其代价不足以导致成败之别。</p>

<h3 id="rc-https-www-recurse-com-scout-click-t-b504af89e87b77920c9b60b2a1f6d5e8-2017-present"><a href="https://www.recurse.com/scout/click?t=b504af89e87b77920c9b60b2a1f6d5e8" rel="noopener noreferrer">RC</a>（2017年至今）</h3>

<p>上一份工作后我想要一个有趣的休息，所以我回到了RC去做有趣的编程相关事情并充电。我没有写下我大部分工作内容（例如，对《Terra Mystica》8万局游戏的分析、MTA（纽约市）地铁数据分析等）。我写了一些东西，比如<a href="//danluu.com/input-lag/" rel="noopener noreferrer">计算机</a>、<a href="//danluu.com/term-latency/" rel="noopener noreferrer">终端</a>、<a href="//danluu.com/keyboard-latency/" rel="noopener noreferrer">键盘</a>和<a href="//danluu.com/web-bloat/" rel="noopener noreferrer">网站</a>的延迟分析。</p>

<p>我在RC期间让我思考的一件事是，为什么靠写作获得<a href="//twitter.com/danluu/status/931018170420400128" rel="noopener noreferrer">高薪</a>这么难。似乎对“好”写作有很多需求，但公司似乎不太愿意为那些会编程但想写作的人创造职位。Steve Klabnik通过他的写作对Rust产生了巨大影响，可能比大多数项目中中位数程序员的影响更大，但我的印象是，为了做这件真正有用且重要的事，他的收入可能比他作为程序员能赚到的要少。</p>

<p>我尝试过在几个地方推销这种角色，到目前为止的回应大多是以下两者的结合：</p>

<ul>
<li>我们重视写作！我觉得全职甚至兼职写作没有意义，但你可以加入我的团队，我们支持写作，你可以作为20%时间项目或在业余时间写作！</li>
<li>呃，我们可以想办法，但为什么一个会编程的人会想写作呢？</li>
</ul>

<p>这两种回应都没有让我觉得写作在这些团队中会被视为和编程同等重要，即使这些团队的写作重视程度相对大多数团队更高。有一些“开发者布道师”角色涉及写作，但当我阅读拥有这个头衔的人写的工程博客时，大多数文章似乎都是伪装得很好的新闻稿（显然也有例外，但即使博客有有趣的工程输出，有趣的内容也常常与伪新闻稿交织在一起）。除了无聊，这种事情似乎也很无效。在我工作过的一家公司，我统计了他们的开发者布道师博客与我个人博客的流量，有很多月份我的博客流量超过了他们所有托管的布道师博客流量总和。我觉得程序员更愿意读解释/分析/历史而不是公关材料并不奇怪，但似乎很难说服正确的人相信这一点，所以我可能会在之后回去做编程工作。走着瞧。</p>

<p>顺便说一句，这并不是说我不喜欢编程或不认为编程重要。只是写作似乎被低估了，以至于通过写作产生超大影响力相对容易。但同样的力量，在让人容易产生超大影响力的同时，也让人很难<a href="//danluu.com/startup-tradeoffs/" rel="noopener noreferrer">获得高薪</a>！</p>

<h3 id="what-about-the-bad-stuff">那些不好的事呢？</h3>

<p>当我回想我的职业生涯时，感觉它是一连串幸运事件的延续。我倒霉过几次，但我不太清楚从那些倒霉经历中能吸取什么教训。</p>

<p>例如，我认为我的成长环境有轻微的虐待倾向。我记得有些晚上睡不着，因为每次一闭眼就会梦见父亲。白天醒着的感觉也不好。这显然不好，回顾起来似乎与我搬出去之前的学业问题有相当直接的关系，但我不知道我能给年轻时的自己什么有用的建议。不要出生在虐待家庭？如果人们能控制这件事，他们本来就会做的。</p>

<p>或者选一个更近的例子，我曾加入过一个在<a href="http://www.joelonsoftware.com/articles/fog0000000043.html" rel="noopener noreferrer">Joel测试</a>中得分只有1分的团队。Joel测试现在被认为过时了，因为它为“你有测试人员吗？”和“你在写新代码前修复bug吗？”这类问题评分，这些问题在今天大多数开发者看来并非最佳实践。那些没有争议的项目中，许多似乎如此显而易见，根本不值得问，比如：</p>

<ul>
<li>你使用源代码控制吗？</li>
<li>你能一步完成构建吗？</li>
<li>你至少每天进行构建吗？</li>
<li>你有一个错误数据库吗？</li>
</ul>

<p>对于任何关心这类事情的人来说，加入一个最多只完成Joel清单上一项（而且这一项还不是上述任何一项）的团队显然不是个好主意。在一个得分1分的团队中获得第一手经验，并没有给我任何新信息来重新考虑我的观点。</p>

<p>你可能会说我应该问这些问题。确实如此！我应该问，我将来可能也会问。然而，当我被雇佣时，那位反对版本控制和其他形式自动化的技术主管还没有被雇佣，所以即使我问了也不会发现这一点。此外，即使他已经被雇佣了，我仍然不确定我是否会发现这一点——这是我加入一个团队后，发现招聘过程中大多数事实陈述都是不真实的唯一一次。我确保提出了具体、实际的问题，询问项目状态、流程、已进行的实验等，但结果发现答案完全是谎言。在那个团队时，每天队员之间都会拿招聘宣传有多假开玩笑！</p>

<p>我可以尝试在未来通过要求事实主张的具体证据来预防类似问题（例如，如果有人声称流失率是X，我可以要求访问人力资源数据库进行核实），但考虑到我的时间有限，以及被直接告知谎言的概率相对较低，我认为在考虑一份工作时，我将继续优先了解其他信息，并接受未来仍有微小概率会陷入类似情况。</p>

<p>当我回顾经历过的与职业相关的不好的事情时，几乎都归结为两类：明显不好且基本无法避免的事情，或者明显不好但在有限资源下我不知道如何合理避免的事情。我看不出这有什么可学的。这并不是说我没有犯过错误并从中学习。我犯了很多错误，也确实因此改变了很多做事方式！但我最糟糕的经历都来自那些我不知道如何合理预防的事情。</p>

<p>这似乎也适用于我认识的大多数人。例如，我经常看到的一种情况是，我的一个朋友最终遇到一位认为经理就是分配奖励和惩罚的人的经理（与相信经理应该使团队尽可能高效的人，或相信经理应该帮助人们成长的人相对）。当你有这样的经理时，常见的失败模式是你被分配到不适合的工作，然后你可能因为工作不合适而做得不好。如果你要求一份更适合的工作，会被拒绝（为什么在你做得不好时，应该奖励你做你想做的事？你应该通过做更多你不喜欢的事来受罚），这会导致一个螺旋式下降，最终以这个人离职或被解雇告终。在我最近看到的案例中，被解雇对当事人和其最亲密的同事来说都很意外：我的朋友设法找到了一个适合自己的角色，尽管管理层百般阻挠；当管理层决定解雇我的朋友时，他们甚至没有咨询新项目的同事，而同事们认为我的朋友做得很好，而且已经好几个月了！</p>

<p>我听到很多这样的故事，我很乐意倾听，因为我喜欢故事，但我不知道这里有什么可操作的建议。避开那些喜欢惩罚员工而不是帮助员工的经理？显而易见，但无法操作。</p>

<h3 id="conclusion">结论</h3>

<p>我看到的最常见的职业建议是“你应该做我所做的，因为我成功了”。它通常表达方式不同，但核心就是这个意思。这基本上从来不起作用。当我和朋友、熟人交流时，很明显我的职业生涯在许多方面都不寻常，但不清楚为什么。</p>

<p>举个例子，我几乎总是有一个支持我的经理，他不仅愿意让我自学任何我想学的东西，还愿意花大量时间和精力帮助我作为工程师成长。我交谈过的大多数人从未遇到过这种情况。为什么有这种差异？我毫无头绪。</p>

<p>一个<a href="https://www.youtube.com/watch?v=RoEEDKwzNBw" rel="noopener noreferrer">故事</a>可能是：有两次我遇到了不支持我的经理，我很快就找到了其他职位，而我的很多朋友会在不适合的角色上待很多年。也许我可以把它包装成这样的寓意：你应该比你想象的更快地离开角色，但我陷入的两个糟糕情况，都是因为我离开一个角色太快，所以建议不能是“倾向于比你认为的更快离开角色”。也许故事的寓意应该是“更快地离开糟糕的角色，更久地留在好的角色里”，但这太显而易见，甚至不值得一提。这可能被认为不显而易见，因为人们确实会留在让他们痛苦的角色里，但当我想起这样的人时，他们属于两类：要么因为外在原因卡住了（例如需要等待签证到期），要么知道应该离开但无法让自己去做。对于前者没有太多可做的，对于后者，知道应该离开不是问题。我能想到的任何策略要么在一般情况下是不正确的，要么显而易见到没有讨论的必要。</p>

<p>另一个故事可能是：我学到了很多有价值的元技能，所以你也应该学习这些技能。但你可能不应该学。我掌握的特定元技能对我来说很有用，因为它们是我能轻松在工作场所学到的技能（通常是因为我有很好的导师），并且是因为我真正坚信这样做。你的情况和核心信念可能与我不同，你必须自己弄清楚学习什么是合理的。</p>

<p>又一个故事可能是：虽然很多机会来自偶然，但我有很多机会是因为我花了很多时间创造可能的机会。当我把这篇文章的草稿传给一些朋友时，基本上每个人都告诉我，我在叙述中过分强调了运气，我所有的幸运突破都来自于努力工作和尝试创造机会的结合。虽然从某种意义上说这是真的，但我的许多机会也来自做出完全错误的决定。</p>

<p>例如，我最终进入Centaur是因为我以一个糟糕的理由拒绝了在IBM工作的机会！实习结束时，我的经理试图说服我留下成为全职员工，但我拒绝了，因为我要去读研究生。但我之所以读研究生，是因为我想获得微处理器逻辑设计的职位，我以为仅凭学士学位无法获得这个职位。但如果我没有拒绝我的经理，我本可以获得那个职位！我只是忘记了当初决定读研究生的原因，并错误地使用了缓存的决定作为拒绝这份工作的理由。纯粹是运气，结果这恰好奏效了，我得到了比我在实习期决定接受IBM工作的任何同事都更好的机会。我“主要”是运气好还是准备充分？很难说；也许甚至不可能知道。</p>

<p>职业生涯没有你需要的日志基础设施来确定单个决策的影响。至少在编程领域如此。现在许多运动追踪逐次比赛数据的方式，使得尝试确定在任何特定比赛或赛季的成功中有多少是运气，多少是技能成为可能。</p>

<p>以棒球为例，这是一项被较好理解的运动。如果我们看看我们今天对表现的统计理解，很明显，20年前几乎没有人真正知道是什么因素让球员成功。我特别感兴趣的一件事是，我们现在对哪些因素是根本性的，哪些因素归结为运气有了更好的理解，而这与几乎任何人20年前的想法都大相径庭。我们现在看一个投手，可以这样说：“他本赛季运气不好，但他的foo、bar和baz数据都很好，所以看起来是比赛击球方面的坏运气，而不是技能下降”，我们也可以这样说：“他本赛季表现不错，但他的基本数据没有变化，所以他未来的表现可能不会比本赛季之前更好”。20年前我们无法做出这样的论断。而这是一项逐次比赛视频似乎从很久以前就可用，逐次比赛数据记录了一个世纪的运动。</p>

<p>在这项一切都被测量的运动中，直到相对较近的时候，我们才能区分由于运气导致的波动和由于技能变化导致的波动。然后是编程，通常认为测量人们的表现是不可能的，而评估人们表现的现状是你询问五个人关于某人的评论，然后汇总这些评论。如果我们仅在现在才能对一项运动员工作的每个细节都可获得的运动中，对运气和其他因素所造成的差异发表看法，我们怎么可能接近于对编程职业这样模糊的事情做出类似区分？</p>

<p>总之，生活是混乱的，我没有任何建议。</p>

<h3 id="appendix-a-meta-skills-i-d-like-to-learn">附录 A：我想学习的元技能</h3>

<h4 id="documentation">文档编写</h4>

<p>我曾与<a href="https://www.linkedin.com/in/jared-davis-a8b62611b" rel="noopener noreferrer">Jared Davis，一位文档高手</a>共事，他的文档如此之好，以至于在询问模块所有者之前，我会先去找他了解模块是如何工作的。据我所知，他是为了让自己更容易理解他正在尝试理解的东西而撰写文档的，但他的文档如此优秀，以至于对整个公司来说都是一股力量倍增器。</p>

<p>后来在谷歌，我注意到初始设计文档的质量与项目的成功率之间存在着奇妙的强相关性。从那以后，我努力为我的项目撰写扎实的设计文档和文档，但我还有很长的路要走。</p>

<h4 id="fixing-totally-broken-danluu-com-wat-situations">修复<a href="//danluu.com/wat/" rel="noopener noreferrer">完全崩溃的</a>情况</h4>

<p>到目前为止，我只遇到过情况远好于平均水平的团队，以及情况远差于平均水平的团队。你可能认为，由于远差于平均水平的团队有太多唾手可得的改进机会，在糟糕的团队中改进情况应该更容易，但事实恰恰相反。问题很多的地方之所以有问题，是因为某些东西使解决问题变得困难。</p>

<p>当我加入那个在Joel测试中得1分的团队时，花了数月时间游说才让每个人都使用版本控制。</p>

<p>我从未见过一个环境从“坏”变成“好”，我很想知道那是什么样子以及如何发生的。约西·克雷宁（Yossi Kreinen）的论文认为，<a href="http://yosefk.com/blog/people-can-read-their-manager-mind.html" rel="noopener noreferrer">只有管理层才能修复崩溃的情况</a>。这可能是真的，但在我没有任何相反证据的情况下，我还不太愿意相信它。</p>

<h3 id="appendix-b-other-how-i-became-a-programmer-stories">附录 B：其他“我是如何成为程序员”的故事</h3>

<p><a href="https://www.mail-archive.com/kragen-tol@canonical.org/msg00184.html" rel="noopener noreferrer">Kragen</a>。描述了27年学习编程的过程。重点强调发展的概念阶段（例如，理解如何使用提供的函数 vs. 理解你可以编写任意函数）。</p>

<p><a href="http://jvns.ca/blog/2015/02/17/how-i-learned-to-program-in-10-years/" rel="noopener noreferrer">Julia Evans</a>。2004年开始在TI-83上编程。断断续续编程直到大学（2006-2011年），此后一直担任职业程序员。强调“旅程”以及提高需要多长时间。</p>

<p><a href="http://pgbovine.net/how-i-learned-programming.htm" rel="noopener noreferrer">Philip Guo</a>。一个非传统的学习编程故事，如果你知道Philip的职业道路是MIT -&gt; Stanford -&gt; Google，这可能会让你感到惊讶。</p>

<p><a href="https://github.com/tarmstrong/longcv/blob/master/bio.md" rel="noopener noreferrer">Tavish Armstrong</a>。从四年级到大学。强调特定技术（如LaTeX或Python）。</p>

<p><a href="https://caitiem.com/2013/03/30/origin-story-becoming-a-game-developer/" rel="noopener noreferrer">Caitie McCaffrey</a>。在AP计算机科学课上开始编程。强调兴趣如何引领到编程职业。</p>

<p><a href="http://mattdeboard.net/2011/11/23/how-i-became-a-programmer/" rel="noopener noreferrer">Matt DeBoard</a>。在导师的帮助下花了12周学习Django。强调没有编程背景也可能成为程序员的事实。</p>

<p><a href="https://www.kchodorow.com/blog/2010/11/30/how-i-became-a-programmer/" rel="noopener noreferrer">Kristina Chodorow</a>。在大学开始。强调替代路径（数学、研究生院）。</p>

<p><a href="http://michaelrbernste.in/2014/12/11/you-are-learning-haskell-right-now.html" rel="noopener noreferrer">Michael Bernstein</a>。多年学习Haskell的故事。强调达到最低熟练程度花了多长时间。</p>

<p><small>
感谢Leah Hanson, Lindsey Kuper, Kelley Eskridge, Jeshua Smith, Tejas Sapre, Joe Wilder, Adrien Lamarque, Maggie Zhou, Lisa Neigut, Steve McCarthy, Darius Bacon, Kaylyn Gibilterra, Sarah Ransohoff, @HamsterRaging, Alex Allain 和 "biktian" 的评论/批评/讨论。
</small></p>
<div>

<hr>

<ol>
<li id="fn:S">如果你碰巧有Swanson先生的联系方式，我很想发一封感谢信。
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:B">Wayne Dickey, Richard Brualdi, Andreas Seeger, 和一位我记不起名字的客座教授。
 <a href="#fnref:B" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:W">我强烈推荐Andy Weiner教的任何课程，以及我旁听数学物理时的那位老师，但我不记得他是谁，也不确定课程的准确名称。
 <a href="#fnref:W" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:G">除了一个政府实验室，他们仅凭一次非技术的校园面试就给了我录用通知。我相信那实际上是我找工作时做的第一次面试，但他们在面试季结束且我已经接受另一份录用后很久才回复我。我不确定这是因为他们是按候选人名单顺序联系，只在N个人拒绝后才联系到我，还是他们的录用通知有六个月的延迟。
 <a href="#fnref:G" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:E"><p>因为英特尔没有理由让竞争对手了解它正在做什么，这导致在匹配新功能时存在相当大的延迟。他们通常会公布足够的信息让你实现基本功能，但边缘情况下的行为可能有所不同。我们曾经有一个bug（在发布前就发现并修复了，但仍然是个问题），我们从eBay上买了一个工程样品，并根据这个工程样品实现了一些新功能。这导致了一个MWAIT的bug，使得Windows挂起；英特尔在发布工程样品和发布最终版本之间改变了MWAIT的行为。</p>

<p>我最近看到一篇帖子声称通过从eBay购买一些工程样品可以获得很好的性价比。别这么做。工程样品经常有bug。有时那些bug是真正的bug，有时只是英特尔改变了主意。无论哪种情况，你都不想在生产系统上运行工程样品。
 <a href="#fnref:E" rel="noopener noreferrer"><sup>[返回]</sup></a></p></li>
<li id="fn:F">我偶尔通过把解决的问题拿给别人看并询问他们是否有更好的想法来获得反馈，但这远不如我在这里谈论的那种反馈深入。
 <a href="#fnref:F" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:magic"><p>举一个具体的例子，<a href="http://moishelettvin.blogspot.ca/2006/11/windows-shutdown-crapfest.html" rel="noopener noreferrer">看看微软的版本控制</a>，来自一位曾在Windows Vista工作的人：</p>

<blockquote>
<p>在小的编程项目中，有一个中央代码仓库。构建通常每天从这个中央仓库生成。程序员随时将他们的更改添加到这个中央仓库，因此每日构建是产品当前状态的一个相当好的快照。</p>

<p>在Windows中，这种模型失效了，因为开发人员太多了，无法访问一个中央仓库。因此Windows有一个仓库树：开发人员签入节点，并定期将节点中的更改向上集成一级。以不同的周期性，更改从根节点向下集成到树中的节点。在我工作的节点中，它距离根节点有4层之隔。集成的周期性随着接近根节点而呈指数级衰减且不可预测，最终导致我的代码需要1到3个月才能到达根节点，而到达其他节点则需要更长时间。还必须注意的是，我的团队、Shell团队和内核团队之间唯一的共同祖先是根节点。
</p></blockquote>

<p>谷歌和微软都维护着他们自己分叉的Perforce，因为那是当时最可扩展的源代码控制系统。谷歌后来构建了Piper，一个分布式版本控制系统（在分布式系统的意义上，而不是在git的意义上），解决了扩展问题，尽管开发体验远不如微软痛苦。但那个选项在微软并不真正可行。在上面引用的文章的评论中，一位当时的微软经理评论说，可能的选项是：</p>

<blockquote>
<ol>
<li>联合源代码树，支付正向和反向集成税（主要是延迟发现构建中断），或者……</li>
<li>消除Windows各部分之间大量不必要的依赖，特别是循环依赖。</li>
<li>1和2都做。#1成为胜利方案，很大程度上是因为它可以由一个小组在明确的时间内执行。#2将需要驱赶所有Windows开发人员（以及PM、经理、UI设计师……），并且可能是一个无界的问题。</li>
</ol>
</blockquote>

<p>另一个人评论说，他在一个分支团队上，将单向延迟从几个月降到了几周。这当然是个改进，但为什么没有人构建像Piper这样的系统呢？我向当时在微软的人问过这个问题，我得到的答案类似于“当我们开始使用Perforce时，它比我们之前用的快得多，以至于人们没想到我们还能做得更好得多”，以及“Perforce比xcopy快得多，看起来像魔法一样”。</p>

<p>这种普遍现象，即人们因为当前系统已经比前一个系统有了巨大改进，而不尝试做出重大改进，是我以前见过甚至自己做过的事情。这个例子恰好使用了微软和谷歌，但请不要过度解读。有些系统情况正好相反，谷歌的某个系统相比微软的同一个系统显得异常笨拙。
 <a href="#fnref:magic" rel="noopener noreferrer"><sup>[返回]</sup></a></p></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 31083 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/learning-to-program/</link>
      <guid isPermaLink="false">https://danluu.com/learning-to-program/</guid>
      <pubDate>Mon, 12 Sep 2016 08:41:26 +0000</pubDate>
    </item>
    <item>
      <title>并发缺陷笔记</title>
      <description>[AI 摘要] 本文综述了并发缺陷的研究文献，包括单机和分布式系统中的发现，以及相关检测工具。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文综述了并发缺陷的研究文献，包括单机和分布式系统中的发现，以及相关检测工具。</div><p>并发缺陷重要吗？从文献中我们知道，<a href="http://danluu.com/postmortem-lessons/" rel="noopener noreferrer">分布式系统中报告的大多数缺陷</a>都有非常简单的原因，并且可以通过简单的测试捕获，即使我们只关注导致严重故障的缺陷，如集群丢失或数据损坏。文件系统文献也反映了这一结果——<a href="http://danluu.com/file-consistency/" rel="noopener noreferrer">一个简单的检查器，查找完全未实现的错误处理，可以发现数百个严重的数据损坏缺陷</a>。大多数缺陷都是简单的，至少如果按缺陷数量衡量的话。但如果按调试时间衡量，情况就有点不同了。</p>

<p>仅从个人经验来看，我花在调试复杂非确定性故障上的时间比所有其他类型缺陷加起来还要多。事实上，我花在调试某些单个非确定性缺陷（数周或数月）上的时间比所有其他类型缺陷加起来还要多。非确定性缺陷很罕见，但它们可能极难调试，并且是生产力杀手。严重的非确定性缺陷调试时间太长，以至于在工具和预防方面进行相对较大的投入可能是值得的<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">1</a></sup>。</p>

<p>让我们看看学术文献对非确定性缺陷的说法。文献众多，因此我们通过关注一个相对研究充分的领域来缩小范围：并发缺陷。我们将从单机并发缺陷的文献开始，然后探讨分布式并发缺陷。</p>

<h3 id="fonseca-et-al-dsn-10-http-concurrency-mpi-sws-org-dsn2010-concurrencybugs-pdf"><a href="http://concurrency.mpi-sws.org/dsn2010-concurrencybugs.pdf" rel="noopener noreferrer">Fonseca 等，DSN '10</a></h3>

<p>他们研究了 2003 年至 2009 年间的 MySQL 并发缺陷，并发现以下情况：</p>

<h4 id="more-non-deadlock-bugs-63-than-deadlock-https-en-wikipedia-org-wiki-deadlock-bugs-40">非死锁缺陷（63%）多于<a href="https://en.wikipedia.org/wiki/Deadlock" rel="noopener noreferrer">死锁</a>缺陷（40%）</h4>

<p>注意这些数字总和超过 100%，因为一些缺陷被标记了多个原因。这大致与 Lu 等 ASPLOS '08 论文（我们稍后会看）一致，该论文发现他们检查的缺陷中有 30% 是死锁缺陷。</p>

<h4 id="15-of-examined-failures-were-semantic">15% 的检查故障是语义性的</h4>

<p>论文将语义故障定义为“应用程序向用户提供的结果违反了应用程序的预期语义”。作者还发现“绝大多数语义缺陷（92%）产生了应用程序语义的微妙违规”。根据其性质，这些故障很可能被低估——死锁很容易被发现，但微妙的数据损坏却容易被忽视。</p>

<h4 id="15-of-examined-failures-were-latent">15% 的检查故障是潜伏的</h4>

<p>论文将潜伏定义为“不会立即向用户显现的缺陷”。不出所料，论文发现潜伏故障与语义故障密切相关；92% 的潜伏故障是语义性的，反之亦然。92% 这个数字使这个发现听起来比实际情况更精确——它只是指 12 个语义故障中有 11 个是潜伏的，反之亦然。这本来很容易就是 11/11（100%）或 10/12（83%）。</p>

<p>这很有趣，但很难从中看出结果是否适用于非数据库项目，甚至非 MySQL 项目。</p>

<h3 id="lu-et-al-asplos-08-http-web1-cs-columbia-edu-junfeng-10fa-e6998-papers-concurrency-bugs-pdf"><a href="http://web1.cs.columbia.edu/~junfeng/10fa-e6998/papers/concurrency-bugs.pdf" rel="noopener noreferrer">Lu 等，ASPLOS '08</a></h3>

<p>他们研究了 MySQL、Firefox、OpenOffice 和 Apache 中的并发缺陷。一些发现如下：</p>

<h4 id="97-of-examined-non-deadlock-bugs-were-atomicity-violation-or-order-violation-bugs">97% 的检查的非死锁缺陷是原子性违规或顺序违规缺陷</h4>

<p>在研究的 74 个非死锁缺陷中，51 个是原子性缺陷，24 个是顺序缺陷，2 个被归类为“其他”。</p>

<p>原子性违规的一个例子是 MySQL 中的这个缺陷：</p>

<p>线程 1：</p>

<pre><code>if (thd-&gt;proc_info)
  fputs(thd-&gt;proc_info, ...)
</code></pre>

<p>线程 2：</p>

<pre><code>thd-&gt;proc_info = NULL;
</code></pre>

<p>对于不熟悉 C 或 C++ 的人来说，<code>thd</code> 是一个指针，<code>-&gt;</code> 是通过指针访问字段的运算符。线程 1 的第一行检查字段是否为空。第二行调用 <code>fputs</code>，该函数写入字段。意图是仅当 <code>proc_info</code> 不是 <code>NULL</code> 时才调用 <code>fputs</code>，但没有任何东西阻止另一个线程在线程 1 的第一行和第二行之间将 <code>proc_info</code> 设置为 <code>NULL</code>。</p>

<p>像大多数缺陷一样，这个缺陷事后看来很明显，但如果我们查看原始缺陷报告，<a href="http://bugs.mysql.com/bug.php?id=3596" rel="noopener noreferrer">可以看到当时并不明显</a>：</p>

<blockquote>
<p>描述：我刚刚注意到，在最新的 bk 树中，MySQL 在 InnoDB 代码中经常崩溃……
如何重现：我仍然不知道这个崩溃为什么发生。</p>
</blockquote>

<p>对于大型代码库来说很常见，一旦诊断出缺陷，修复它比最初看起来更复杂。这个缺陷在 2004 年部分修复，<a href="https://bugs.mysql.com/bug.php?id=38883" rel="noopener noreferrer">在 2008 年重新出现并再次修复</a>。<a href="https://bugs.mysql.com/bug.php?id=38816" rel="noopener noreferrer">另一个缺陷的修复在 2009 年导致了回归</a>，也在 2009 年修复。该修复引入了<a href="https://bugs.mysql.com/bug.php?id=60682" rel="noopener noreferrer">一个在 2011 年发现的死锁</a>。</p>

<p>一个顺序缺陷的例子是 Firefox 中的以下缺陷：</p>

<p>线程 1：</p>

<pre><code>mThread=PR_CreateThread(mMain, ...);
</code></pre>

<p>线程 2：</p>

<pre><code>void mMain(...) {
  mState = mThread-&gt;State;
  }
</code></pre>

<p><code>Thread 1</code> 使用 <code>PR_CreateThread</code> 启动 <code>Thread 2</code>。<code>Thread 2</code> 假定，因为启动它的行赋给了 <code>mThread</code>，所以 <code>mThread</code> 是有效的。但 <code>Thread 2</code> 可能在线程 1 赋给 <code>mThread</code> 之前开始执行！作者指出，他们称其为顺序缺陷而不是原子性缺陷，即使如果线程 1 中的行是原子的，这个缺陷本可以被防止，因为他们的“缺陷模式分类基于根本原因，而不考虑可能的修复策略”。</p>

<p>“其他”缺陷的一个例子（仅有的两个研究之一）是 MySQL 中的这个缺陷：</p>

<p>线程 1...n：</p>

<pre><code>rw_lock(&amp;lock);
</code></pre>

<p>看门狗线程：</p>

<pre><code>if (lock_wait_time[i] &gt; fatal_timeout)
  assert(0);
</code></pre>

<p>当工作量超过预期时，这可能导致虚假崩溃。注意，研究没有查看性能缺陷，因此如果锁争用导致事情变慢但看门狗没有杀死程序，这不会被认为是缺陷。</p>

<p>一个可能值得另文讨论的题外话是，硬件通常内置死锁或活锁检测，当检测到锁条件时，硬件通常会尝试将事情推入可以继续正常执行的状态。在检测并打破死锁/活锁之后，错误通常会以一种在实验室中被捕获时会被注意到的方式记录，但外部客户不会看到。出于某种原因，这种策略在软件世界中似乎很少见，尽管在软件中应该比在硬件中更容易实现。</p>

<p><a href="https://en.wikipedia.org/wiki/Deadlock" rel="noopener noreferrer">死锁当且仅当以下四个条件都为真时发生</a>：</p>

<ol>
<li>互斥：至少一个资源必须以不可共享的方式被持有。在任何给定时刻，只有一个进程可以使用该资源。</li>
<li>持有并等待或资源持有：一个进程当前持有一个或多个资源，并请求其他进程持有的附加资源。</li>
<li>非抢占：资源只能由持有它的进程自愿释放。</li>
<li>循环等待：一个进程必须等待另一个进程持有的资源，而后者又在等待第一个进程释放资源。</li>
</ol>

<p>这些条件没有任何东西是硬件或软件所特有的，在软件中构建可以退避和重放以放松（2）的机制比在硬件中更容易。无论如何，回到研究结果。</p>

<h4 id="96-of-examined-concurrency-bugs-could-be-reproduced-by-fixing-the-relative-order-of-2-specific-threads">96% 的检查并发缺陷可以通过固定两个特定线程的相对顺序来重现</h4>

<p>这对测试来说听起来是个好消息。仅测试线程对之间的顺序比测试所有线程之间的所有顺序更易于处理。类似地，92% 的检查缺陷可以通过固定四个（或更少）内存访问的顺序来重现。然而，这里存在一种抽样偏差——只有能够重现的缺陷才能被分析根本原因，而只需要两个线程之间顺序或仅几个内存访问的缺陷更容易重现。</p>

<h4 id="97-of-examined-deadlock-bugs-were-caused-by-two-threads-waiting-for-at-most-two-resources">97% 的检查死锁缺陷是由两个线程等待最多两个资源引起的</h4>

<p>此外，22% 的检查死锁缺陷是由一个线程获取另一个线程持有的资源引起的。作者指出，对获取和释放序列的成对测试应该能够捕获大多数死锁缺陷，对线程顺序的成对测试应该能够捕获大多数非死锁缺陷。该说法作为书面语似乎合理；其暗示几乎是所有缺陷都可以通过某种成对测试来捕获，但由于研究缺陷的样本偏差，我对此持怀疑态度。</p>

<p>我见过具有多个活动部件的缺陷花费数月时间追踪。我见过的最糟糕的缺陷消耗了近一人年的时间。这类缺陷大多不会进入此类研究，因为很少有工作允许有人花时间追踪如此难以捉摸的缺陷。有多少这样的缺陷仍然存在是一个开放性问题。</p>

<h4 id="caveats">注意事项</h4>

<p>请注意，所有研究的程序都是用 C 或 C++ 编写的，而且这项研究早于 C++11。转向 C++11 并使用原子操作和作用域锁可能会显著改变这些数字，更不用说转向完全不同的并发模型了。有一些关于不同并发模型如何影响缺陷率的学术工作，但不清楚这项工作如何概括为像所研究的那样大型和成熟的代码库，而且大型和成熟的代码库本质上很难进行涉及更改基本原语的随机试验。作者指出，39% 的检查缺陷可以通过使用事务内存来防止，但如果使用事务内存，不清楚可能会引入多少其他缺陷。</p>

<h3 id="tools">工具</h3>

<p>还有其他关于单机并发缺陷特征的论文，但为了节省空间，我将跳过它们。也有关于分布式并发缺陷的论文，但在我们开始之前，让我们看看文献中用于发现单机并发缺陷的一些工具。我发现这些论文相当有趣，尤其是模型检查工作，但现实地说，如果有什么可用的，我可能不会从头开始构建一个工具，所以让我们看看有什么可用的。</p>

<h4 id="hapset">HapSet</h4>

<p>使用运行时覆盖率来生成尚未覆盖的交错。这来自 NEC 实验室；在谷歌上搜索 <code>NEC labs HapSet</code> 会返回论文，<a href="https://www.google.com/patents/US20120089873" rel="noopener noreferrer">一些专利列表</a>，但没有明显的工具下载。</p>

<h4 id="chess-https-chesstool-codeplex-com"><a href="https://chesstool.codeplex.com/" rel="noopener noreferrer">CHESS</a></h4>

<p>为每次运行生成线程的唯一交错。他们声称，通过不跟踪状态，检查器比其他情况下简单得多，并且他们能够避免跟踪状态的许多缺点，但无法在这个小段落中正确描述；如果您感兴趣，请阅读论文！支持 C# 和 C++。该页面声称它需要 Visual Studio 2010，并且仅测试过 32 位代码。我还没尝试过在现代 *nix 编译器上运行，但根据我的经验，需要 Visual Studio 2010 意味着要在现代版本的 Visual Studio 上运行需要适度的努力，而在现代版本的 gcc 或 clang 上运行则需要大量努力。快速谷歌搜索表明这可能有专利限制<sup id="fnref:P"><a href="#fn:P" rel="noopener noreferrer">2</a></sup>。</p>

<h4 id="maple-https-github-com-jieyu-maple"><a href="https://github.com/jieyu/maple" rel="noopener noreferrer">Maple</a></h4>

<p>使用覆盖率来生成尚未覆盖的交错。对 pthreads 进行插桩。源代码在 GitHub 上。这个工具可能仍然可用，我可能会在某个时候试用一下，但它至少依赖一个旧的、显然没有维护的工具（PIN，来自英特尔的二进制插桩工具）。谷歌（Bing？）搜索 Maple 或 PIN 会返回许多人无法编译工具的结果，更不用说使用该工具了。</p>

<h4 id="pacer">PACER</h4>

<p>使用 FastTrack 算法进行采样，以保持开销足够低“以便在生产软件中考虑”。讽刺的是，这是在 Jikes RVM 之上实现的，这不太可能在实际生产软件中使用。我唯一能找到的实际可下载工具的参考是<a href="https://github.com/pangloss/pacer" rel="noopener noreferrer">一个完全不同的 pacer</a>。</p>

<h4 id="conlock-magiclock-magicfuzzer">ConLock / MagicLock / MagicFuzzer</h4>

<p>有一系列工具来自一个小组，声称使用各种技术获得良好结果，但据我所知，这些工具的源代码都不可用。有一个页面声称有 MagicFuzzer 的可用版本，但链接到一个未指定平台的二进制文件，并且链接 404 了。</p>

<h4 id="omen-wolf">OMEN / WOLF</h4>

<p>我找不到这些工具的页面（除了它们的论文），更不用说下载链接了。</p>

<h4 id="sherlock-atomchase-racageddon">SherLock / AtomChase / Racageddon</h4>

<p>另一个系列工具，显然不可用。</p>

<h3 id="tools-you-can-actually-easily-use">您可以实际轻松使用的工具</h3>

<h4 id="valgrind-drd-helgrind">Valgrind / DRD / Helgrind</h4>

<p>插桩 pthreads 并且易于使用——只需用适当的选项（<code>-drd</code> 或 <code>-helgrind</code>）在二进制文件上运行 valgrind。如果使用 C++11 线程，可能需要<a href="http://valgrind.org/docs/manual/drd-manual.html#drd-manual.C++11" rel="noopener noreferrer">一些调整</a>。</p>

<h4 id="clang-thread-sanitizer-http-clang-llvm-org-docs-threadsanitizer-html-tsan"><a href="http://clang.llvm.org/docs/ThreadSanitizer.html" rel="noopener noreferrer">clang thread sanitizer</a> (TSan)</h4>

<p>可以找到数据竞争。当<a href="http://stackoverflow.com/questions/11970428/how-to-understand-happens-before-consistent" rel="noopener noreferrer">发生前</a>被违反时会标记。适用于 pthreads 和 C++11 线程。易于使用（只需将 <code>-fsanitize=thread</code> 传递给 clang）。</p>

<p>由于易于使用且实际可用，一个副作用是<a href="http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/43217.pdf" rel="noopener noreferrer">TSan 在现实世界中产生了非常大的影响</a>：</p>

<blockquote>
<p>一个有趣的事件发生在开源 Chrome 浏览器中。已知崩溃的高达 15% 归因于同一个缺陷 [5]，这被证明很难理解——Chrome 工程师花了超过 6 个月时间追踪这个缺陷而没有成功。另一方面，TSAN V1 团队在 30 分钟的运行中找到了这个缺陷的原因，甚至不知道这些崩溃。崩溃是由几个引用计数器上的数据竞争引起的。一旦找到原因，就迅速进行了相对简单的修复并修补，随后该缺陷被关闭。</p>
</blockquote>

<h4 id="clang-wthread-safety">clang <code>-Wthread-safety</code></h4>

<p>静态分析，使用共享状态上的注解来确定状态是否未被正确保护。</p>

<h4 id="findbugs-https-en-wikipedia-org-wiki-findbugs"><a href="https://en.wikipedia.org/wiki/FindBugs" rel="noopener noreferrer">FindBugs</a></h4>

<p>用于 Java 的通用静态分析，具有许多功能。有 <code>@GuardedBy</code> 注解，类似于 <code>-Wthread-safety</code>。</p>

<h4 id="checkerframework-http-types-cs-washington-edu-checker-framework"><a href="http://types.cs.washington.edu/checker-framework/" rel="noopener noreferrer">CheckerFramework</a></h4>

<p>用于编写检查器的 Java 框架。有许多不同的检查器。特别对于并发，使用 <code>@GuardedBy</code>，如 FindBugs。</p>

<h4 id="rr-http-rr-project-org"><a href="http://rr-project.org/" rel="noopener noreferrer">rr</a></h4>

<p>用于调试的确定性重放。易于获取和使用，并且似乎得到积极维护。在 gdb 中添加了时间旅行调试支持。</p>

<h4 id="drdebug-https-software-intel-com-en-us-articles-pintool-drdebug-pinplay-https-sites-google-com-site-pinplaypldi2016tutorial"><a href="https://software.intel.com/en-us/articles/pintool-drdebug" rel="noopener noreferrer">DrDebug</a>/<a href="https://sites.google.com/site/pinplaypldi2016tutorial/" rel="noopener noreferrer">PinPlay</a></h4>

<p>通用工具包，可以为您提供用于调试的确定性重放。还提供“动态切片”，类似于监视点：它可以告诉您哪些语句影响了一个变量，以及哪些语句受变量影响。目前仅限于 Linux；声称即将支持 Windows 和 Android。</p>

<h3 id="other-tools">其他工具</h3>

<p>这不是一个详尽的列表——有大量关于此的文献，而且在这个领域，坦率地说，我可能没有时间自己实现一个工具，因此阅读更多论文以发现我必须自己实现的技术没有多大价值<sup id="fnref:I"><a href="#fn:I" rel="noopener noreferrer">3</a></sup>。然而，我有兴趣听到其他可用工具。</p>

<p>我发现这一点很有趣的是，几乎所有学术工具的论文都声称做一些新颖的事情，使它们能够发现其他工具没有发现的缺陷。然后他们在某个代码库上运行他们的工具，并证明该工具能够发现新的缺陷。但由于几乎没有人去在任何代码库上运行旧工具，您永远不知道较新的工具是否只发现了较旧工具能够捕获的缺陷的一个子集。</p>

<p>此外，您会看到论文声称新颖的循环（活锁？）。论文 I 将声称它做 X。论文 II 将声称它因为不做 X 而新颖，不像论文 I。然后论文 III 将声称它因为做 X 而新颖，不像论文 II。</p>

<h3 id="distributed-systems">分布式系统</h3>

<p>现在我们已经看了一些关于单机并发缺陷的文献，那么分布式并发缺陷呢？</p>

<h3 id="leesatapornwongsa-et-al-asplos-2016-http-ucare-cs-uchicago-edu-pdf-asplos16-taxdc-pdf"><a href="http://ucare.cs.uchicago.edu/pdf/asplos16-TaxDC.pdf" rel="noopener noreferrer">Leesatapornwongsa 等，ASPLOS 2016</a></h3>

<p>他们研究了 Cassandra、MapReduce、HBase 和 Zookeeper 中的 104 个缺陷。让我们看一些缺陷示例，这将澄清研究中使用的术语，并更容易理解主要发现。</p>

<h4 id="message-message-race">消息-消息竞争</h4>

<p><a href="http://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/YARN.html" rel="noopener noreferrer">此图表</a>仅供参考，以便我们对 MapReduce 中不同部分如何组合有高层想法：</p>

<p><img src="https://danluu.com/images/concurrency-bugs/mapreduce_block_diagram.gif" alt="MapReduce 框图"></p>

<p><a href="https://issues.apache.org/jira/browse/MAPREDUCE-3274" rel="noopener noreferrer">在 MapReduce 缺陷 #3274 中</a>，资源管理器向节点管理器发送任务启动消息。不久之后，应用程序主节点向同一节点管理器发送任务终止抢占。意图是任务终止消息杀死用任务启动消息启动的任务，但任务终止可能赢得竞争并在任务启动之前到达。这个例子恰好是一个来自不同节点的两个消息竞争到达单个节点的情况。</p>

<p>例如，在<a href="https://issues.apache.org/jira/browse/MAPREDUCE-5358" rel="noopener noreferrer">MapReduce 缺陷 #5358</a> 中，应用程序主节点向运行推测任务的节点管理器发送终止消息，因为另一个任务副本已完成。但在节点管理器收到消息之前，节点管理器的任务完成，导致发送完成消息给应用程序主节点，由于在任务完成后收到完成消息而引起异常。</p>

<h4 id="message-compute-race">消息-计算竞争</h4>

<p>一个例子是<a href="https://issues.apache.org/jira/browse/MAPREDUCE-4157" rel="noopener noreferrer">MapReduce 缺陷 #4157</a>，其中应用程序主节点从资源管理器注销。然后应用程序主节点进行清理，但该清理与资源管理器通过节点管理器向应用程序的容器发送终止消息竞争，导致应用程序主节点被终止。注意，这被归类为竞争而不是原子性缺陷，我们很快会谈到。</p>

<p>计算-计算竞争可能发生，但它们超出了本研究的范围，因为本研究只查看分布式并发缺陷。</p>

<h4 id="atomicity-violation">原子性违规</h4>

<p>根据本研究的定义，原子性缺陷被定义为“每当消息在需要原子性的一组事件（本地计算或全局通信）中间到达时，而不是消息在事件之前或之后到达时”。根据此定义，我们上面查看的消息-计算竞争不是原子性缺陷，因为即使消息在“计算”开始之前到达，它仍然是缺陷。此定义还意味着发生在必须是原子的块内部的硬件故障不被视为原子性缺陷。</p>

<p>我理解您为什么要将这些缺陷定义为单独类型的缺陷，但这有点违反直觉，因为我认为所有这些都是不同种类的原子性缺陷，因为它们是因破坏需要原子性的东西而引起的不同缺陷。</p>

<p>无论如何，根据本研究的定义，<a href="https://issues.apache.org/jira/browse/MAPREDUCE-5009" rel="noopener noreferrer">MapReduce 缺陷 #5009</a> 是原子性缺陷。节点管理器正在向 HDFS 提交数据的过程中。资源管理器终止任务，这不会改变提交状态。任何时候节点尝试重新运行提交任务，该任务都会被应用程序管理器终止，因为认为提交已经在进行中。</p>

<h4 id="fault-timing">故障时序</h4>

<p>故障被定义为“组件故障”，如崩溃、超时或意外延迟。论文中一度提到“硬件故障如机器崩溃”，这似乎表明一些可能被认为是软件故障的故障在本研究中被定义为硬件故障。</p>

<p>总之，根据本研究的定义，<a href="https://issues.apache.org/jira/browse/MAPREDUCE-3858" rel="noopener noreferrer">MapReduce 缺陷 #3858</a> 是故障时序问题的一个例子。节点管理器在提交结果时崩溃。当任务重新运行时，后续的提交尝试都失败。</p>

<h4 id="reboot-timing">重启时序</h4>

<p>在本研究中，重启与其他故障分开分类。<a href="https://issues.apache.org/jira/browse/MAPREDUCE-3186" rel="noopener noreferrer">MapReduce 缺陷 #3186</a> 说明了一个重启缺陷。</p>

<p>资源管理器向应用程序主节点发送作业。如果资源管理器在应用程序主节点发送提交消息回资源管理器之前重启，资源管理器会丢失其状态并抛出异常，因为它收到了意外的完成消息。</p>

<p>一些主要发现如下：</p>

<h4 id="47-of-examined-bugs-led-to-latent-failures">47% 的检查缺陷导致了潜伏故障</h4>

<p>与 DSN '10 论文发现 15% 的检查多线程缺陷是潜伏故障相比，这是一个相当大的差异。这可能是真实的差异，而不仅仅是由于混淆变量，但从数据中很难判断。</p>

<h4 id="63-of-examined-bugs-were-related-to-hardware-faults">63% 的检查缺陷与硬件故障有关</h4>

<p>这与研究“本地”并发缺陷的结果有很大不同。我想知道其中有多少只是因为人们在非分布式软件中很少甚至不关心为硬件故障提交和修复缺陷。</p>

<h4 id="64-of-examined-bugs-were-triggered-by-a-single-message-s-timing">64% 的检查缺陷由单个消息的时序触发</h4>

<p>44% 是顺序违规，20% 是原子性违规。此外，超过 90% 的缺陷涉及三条消息（或更少）。</p>

<p>32% 的检查缺陷是由于故障或重启时序。注意，根据研究的定义，破坏需要原子性的块的硬件故障或重启不被视为原子性缺陷——在这里，原子性缺陷是消息在需要原子性的计算中间到达的缺陷。</p>

<h4 id="70-of-bugs-had-simple-fixes">70% 的缺陷有简单的修复</h4>

<p>30% 通过忽略时序不当的消息修复，40% 通过延迟或忽略消息修复。</p>

<h4 id="bug-causes">缺陷原因？</h4>

<p>在回顾缺陷后，作者提出了导致缺陷的常见谬误：</p>

<ol>
<li>一跳比两跳快</li>
<li>零跳比一跳快</li>
<li>原子块不能被打破</li>
</ol>

<p>关于 (3)，作者指出不仅仅是硬件故障或重启会打破原子块——系统可以发送终止或抢占消息来打破原子块。我在事后分析中常见的一个谬误没有列出，类似于“<a href="https://en.wikipedia.org/wiki/Byzantine_fault_tolerance" rel="noopener noreferrer">坏节点显然是坏的</a>”。一个经典的例子是当系统开始通过快速丢弃查询来“处理”查询时，导致负载均衡器将流量转移到坏节点，因为它处理流量如此之快。</p>

<p>我最喜欢这类缺陷的一个实际系统缺陷来自一个基于环的存储系统，其中节点可以对邻居进行健康检查，并在健康检查失败时声明其邻居应被丢弃。一个节点变坏，丢弃其所有存储，并开始报告其邻居为坏节点。其邻居注意到坏节点很坏，但由于坏节点丢弃了其所有存储，它速度超快，并能在好邻居报告坏节点之前报告好邻居。在驱逐其直接邻居后，坏节点获得了新邻居，并与之竞争，出于同样的原因再次获胜。这重复进行，直到整个集群死亡。</p>

<h3 id="tools-1">工具</h3>

<h4 id="mace">Mace</h4>

<p>一组语言扩展（基于 C++），帮助您构建分布式系统。Mace 有一个模型检查器，可以检查消息的所有可能事件顺序，与崩溃、重启和超时交错。Mace 模型检查器实际上是可用的，但据我所知，它需要使用 Mace 框架，而大多数分布式系统不是用 Mace 编写的。</p>

<h4 id="modist">Modist</h4>

<p>另一个检查不同顺序的模型检查器。仅运行独立操作的一个交错（部分顺序约简）以避免检查冗余状态。也与超时交错。与 Mace 不同，不注入重启。似乎不可用。</p>

<h4 id="demeter">Demeter</h4>

<p>与 Modist 类似，它是一个注入相同类型故障的模型检查器。使用不同的技术来减少状态空间，我不知道如何简洁地总结。有关详细信息，请参阅论文。似乎不可用。在谷歌上搜索 Demeter 会返回一些用于建模 X 射线吸收的软件？</p>

<h4 id="samc">SAMC</h4>

<p>另一个模型检查器。可以注入多个崩溃和重启。使用对系统的某些理解来避免冗余的重新排序（例如，如果一系列消息对何时注入重启不变，系统会尝试避免在每条消息之间注入重启）。似乎不可用。</p>

<h4 id="jepsen">Jepsen</h4>

<p>与非分布式并发缺陷的情况一样，有大量关于学术工具的文献，其中大部分似乎是研究生代码，尚未发布。</p>

<p>当然还有 Jepsen，它没有任何附带的学术论文，但可能比任何其他工具在现实世界中产生更大的影响，因为<a href="https://github.com/aphyr/jepsen" rel="noopener noreferrer">它实际上可用并得到维护</a>。还有<a href="https://github.com/Netflix/SimianArmy/wiki/Chaos-Monkey" rel="noopener noreferrer">混沌猴子</a>，但如果我理解正确的话，与其他列出的工具不同，它不试图创建可重现的故障。</p>

<h3 id="conclusion">结论</h3>

<p>这是您应该有结论的地方吗？我没有结论。我们看了一些文献，发现了一些关于缺陷的有趣信息，但不一定可操作。我们读到了一些有趣的工具，但实际不可用。然后有一些基于旧技术的可用且有用的工具。</p>

<p>例如，clang TSan 内部的想法，使用“发生前”来发现数据竞争，可以追溯到很久以前。有<a href="http://www.cs.columbia.edu/~junfeng/10fa-e6998/papers/hybrid.pdf" rel="noopener noreferrer">一篇 2003 年的论文</a>，讨论了“结合两种先前已知的竞争检测技术——基于锁集的检测和基于发生前的检测——以获得比单独基于锁集的检测更少的误报”。这实际上是 TSan v1 所做的，但对于 TSan v2，他们意识到该工具如果只使用发生前会更有影响力，因为这避免了误报，这意味着人们实际上会使用该工具。然而，这不太可能成为一篇被大量引用的论文。对于任何看过<a href="http://lcamtuf.coredump.cx/afl/README.txt" rel="noopener noreferrer">AFL</a> 工作方式的人来说，这个故事应该很熟悉。AFL 非常实用，并在现实世界中产生了非常大的影响，主要是通过摒弃近期文献中的花哨技术。</p>

<p>如果您必须有一个结论，也许结论是像 Kyle Kingsbury 或 Michal Zalewski 这样的个人对行业产生了巨大的影响，您也可以选择一个在测试中服务不足的领域，并对整个行业产生令人好奇的巨大影响。</p>

<h4 id="unrelated-miscellania">无关杂项</h4>

<p><a href="https://github.com/rose" rel="noopener noreferrer">Rose Ames</a> 要求我多讲一些“大公司”故事，所以这里有一系列故事解释了为什么我有一段时间没有发布博客文章。直接原因是我的副总裁对我的写作有负面评论。但原因有点长。一部分是通常的情况，我个人收到的评论非常正面，但我的经理收到的评论相反，因为因为喜欢某人的写作而给他们的经理发邮件很奇怪，但如果他们的写作确实触动了神经，您可能会发邮件。</p>

<p>这解释了为什么我的管理层中有人收到关于我写作的邮件，但它没有解释为什么邮件会发到我的副总裁那里。那是因为几个月前我换了一个团队，我要加入的组织超员了，没有空缺。我听说了关于他们超员多少的矛盾数字，从 10 或 20 人到 10% 或 20%（该组织相当大，10% 会比 20 多得多），以及关于原因的矛盾故事（诚实的错误与某些团体意识到招聘紧缩即将到来并尽可能多地招聘以从该组织的其余部分获取所有要求）。无论如何，出于某种原因，我将工作的组织至少多雇了一个人，并实施了招聘冻结。由于我的新经理无法将我雇用到该组织，他转入了一个有空缺的组织并将我雇用到新组织。新组织恰好是一个销售组织，这意味着我技术上现在在销售部门工作；这对我的日常生活有一些影响，因为一些资源和技术讲座只有产品组的人才能访问，但这是另一个故事。总之，由于我不完全理解的原因，我在我的新经理之前被雇用到该组织，并且在组织结构图更新的几个月里，我被显示为在我的副总裁之下，这意味着任何想给我经理发邮件的人都会在目录中查找我，并意外地给我的副总裁发邮件。</p>

<p>似乎没有单独一封邮件是件大事，但由于我与副总裁互动不多，并且不想仅仅因为那个写东西在公司内部引起反对而为人所知，我暂停了一段时间的博客。我也不想让我的经理只知道我，但我与我的经理频繁互动，至少我不会只因那件事而被知道。</p>

<p>我也想知道这些给我经理/副总裁的邮件是否在我当前的雇主比在以前的雇主更有可能发生。我在其他雇主那里从未发生过（据我所知），但这里发生的总次数很低，可能只是巧合。</p>

<p>话又说回来，我刚刚阅读了一个非常有洞察力的内部博客的档案，并看到一条说明，提到该系列博客文章正在内部发布，因为作者因为<a href="https://news.ycombinator.com/item?id=11761437" rel="noopener noreferrer">Sinofsky</a> 对发布与官方路线矛盾的帖子而受到批评，最终导致作者同意就 Sinofsky 权限范围内的任何事情向 Sinofsky 发送电子邮件评论，而不是公开发布评论。但现在 Sinofsky 已经离职，作者想分享那些本来会成为内部帖子的电子邮件。</p>

<p>这种事在这里似乎不是罕见事件。在我看到关于 Sinofsky 的事情的同时，我遇到了一个讨论，关于产品经理是否有权要求某人从应用商店删除负面评论。显然，产品经理发现有人在某个应用商店对产品经理的产品写了负面评价，并给评分者发了邮件，告诉他们必须删除评论。不清楚产品经理如何知道评分者为我们工作（他们是否为找到的每个负面评分搜索内部目录？），但他们不知怎么发现了并提出了要求。大多数人认为产品经理越界了，但有一部分人（除了产品经理）认为员工在公共场合不应该说任何可能被解释为负面的话。</p>

<p>我觉得现在我看到的这种事情比在其他公司多，但公司太大了，无法知道任何人的个人经历是否具有普遍性。总之，在组织结构图显示我直接向我的实际经理报告后，我可能会开始写博客，也许我的经理会收到一些邮件。或者可能不会。</p>

<p><em>感谢 Leah Hanson、David Turner、Justin Mason、Joe Wilder、Matt Dziubinski、Alex Blewitt、Bruno Kim Medeiros Cesar、Luke Gilliam、Ben Karas、Julia Evans、Michael Ernst 和 Stephen Tu 的评论/更正。</em></p>
<div>

<hr>

<ol>
<li id="fn:S">如果您要调试缺陷。我知道一些在初创公司工作的人，如果缺陷看起来需要超过几个小时来调试，他们会放弃，因为他们的待办事项列表太长，他们负担不起时间。考虑到他们的权衡，这可能是正确的决定，但并非对每个人都正确。
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:P">关于美国专利法的有趣之处：您因故意侵犯专利而承担三倍赔偿。一个直接影响是，我三个全职雇主中有两个强烈建议我不要阅读专利，因此我避免阅读明显无意义的专利。而“无意义”，我指的是像<a href="https://www.google.com/patents/US6368227" rel="noopener noreferrer">如何在秋千上荡秋千</a>这样的专利。
 <a href="#fnref:P" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:I">我理解导致这种情况的激励措施，我不怨恨研究人员通过响应这些激励措施来追求职业成功，但作为<a href="http://sei.pku.edu.cn/~yaoguo/PhDReading07/parnas-review.pdf" rel="noopener noreferrer">一个低级的从业者</a>，如果激励措施不同，那肯定会很好。
 <a href="#fnref:I" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 19627 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/concurrency-bugs/</link>
      <guid isPermaLink="false">https://danluu.com/concurrency-bugs/</guid>
      <pubDate>Fri, 5 Aug 2016 03:32:26 +0000</pubDate>
    </item>
    <item>
      <title>一些值得关注的编程博客</title>
      <description>[AI 摘要] 这篇文章提供了一份值得阅读的编程博客列表，涵盖了多样化的技术主题和写作风格。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 这篇文章提供了一份值得阅读的编程博客列表，涵盖了多样化的技术主题和写作风格。</div><p>这是一份“每个程序员必读的N个技术博客”列表，只不过“程序员”这个词太宽泛了，而且人们对有用写作风格的需求差异太大，任何此类列表（如果想要它对所有人都有帮助）都无法包含非零数量的条目。因此，这里列出了一些你可能想读的内容，以及你可能想读（或不想读）它们的原因。</p>

<h4 id="aleksey-shipilev-https-shipilev-net"><a href="https://shipilev.net/" rel="noopener noreferrer">Aleksey Shipilev</a></h4>

<p>如果你想真正理解JVM是如何工作的，这是互联网上最好的资源之一。</p>

<h4 id="bruce-dawson-https-randomascii-wordpress-com"><a href="https://randomascii.wordpress.com/" rel="noopener noreferrer">Bruce Dawson</a></h4>

<p>一位Windows程序员对性能的探索。通常隐含地展示了Linux上没有同等公开可用工具的出色工具演示。</p>

<h4 id="chip-huyen-https-huyenchip-com-blog"><a href="https://huyenchip.com/blog/" rel="noopener noreferrer">Chip Huyen</a></h4>

<p>内容混合了机器学习会议总结、数据分析（例如，<a href="https://huyenchip.com/2019/08/21/glassdoor-interview-reviews-tech-hiring-cultures.html" rel="noopener noreferrer">基于glassdoor上的面试数据</a>或<a href="https://huyenchip.com/2020/01/18/tech-workers-19k-compensation-details.html" rel="noopener noreferrer">levels.fyi上的薪酬数据</a>的分析），以及对行业的普遍评论。</p>

<p>是罕见的拥有数据驱动行业观点文章的博客之一。</p>

<h4 id="chris-fenton-http-www-chrisfenton-com"><a href="http://www.chrisfenton.com/" rel="noopener noreferrer">Chris Fenton</a></h4>

<p>计算机相关项目，我指的是像<a href="http://www.chrisfenton.com/homebrew-cray-1a/" rel="noopener noreferrer">重建Cray-1A</a>和<a href="http://www.chrisfenton.com/the-numbotron/" rel="noopener noreferrer">制造机械计算机</a>这样的东西。更新不频繁，大概是由于创作需要大量工作，但几乎总是很有趣。</p>

<p>博文往往是高层次的，更像是演示文稿而不是设计文档，但如果你想要更多细节，通常可以找到源代码。</p>

<h4 id="cindy-sridharan-https-twitter-com-copyconstruct"><a href="https://twitter.com/twitter.com/copyconstruct" rel="noopener noreferrer">Cindy Sridharan</a></h4>

<p>在Twitter上比在博客上更活跃，但有审阅论文的博文，也有一些关于“大”话题的文章，比如<a href="https://medium.com/@copyconstruct/distributed-tracing-weve-been-doing-it-wrong-39fc92a857df" rel="noopener noreferrer">分布式追踪</a>和<a href="https://medium.com/@copyconstruct/testing-in-production-the-hard-parts-3f06cefaf592" rel="noopener noreferrer">在生产环境中测试</a>。</p>

<h4 id="dan-mckinley-http-mcfunley-com"><a href="http://mcfunley.com/" rel="noopener noreferrer">Dan McKinley</a></h4>

<p>有很多关于工程公司应如何运营的优秀材料。他有很多听起来像常识的想法，例如<a href="http://mcfunley.com/choose-boring-technology" rel="noopener noreferrer">选择无聊的技术</a>，直到你意识到，找到如此合理意见的博客实际上并不常见。</p>

<p>主要是提炼出的智慧（而不是，例如，代码的详细解释）。</p>

<h4 id="eli-bendersky-http-eli-thegreenplace-net-archives-all"><a href="http://eli.thegreenplace.net/archives/all" rel="noopener noreferrer">Eli Bendersky</a></h4>

<p>我认为这是“<a href="http://eli.thegreenplace.net/tag/c-c" rel="noopener noreferrer">C++博客</a>”，但它的范围比那要广泛得多。范围太广，我无法总结，但如果非要我描述的话，可能会说它是一系列对各种主题（通常是，但不总是，相对底层）的深入探讨，以及对书籍（通常是，但不总是，技术类）的短评。</p>

<p>书评通常易于阅读，但编程博文通常是代码和阐述的混合，确实需要你集中注意力；通常不是轻松的读物。</p>

<h4 id="erik-sink-https-ericsink-com-index-html"><a href="https://ericsink.com/index.html" rel="noopener noreferrer">Erik Sink</a></h4>

<p>我认为Erik是过去20年来<a href="https://twitter.com/danluu/status/1340059909090549761" rel="noopener noreferrer">对技术文化最具持续洞察力的作家</a>。如果你看看他开始写博客时也在写博客的人，<a href="https://twitter.com/danluu/status/1336499111789436931" rel="noopener noreferrer">Steve Yegge</a>的很多作品和Erik的一样经得起时间考验，但Steve没有持续写作。</p>

<p>如果你看看那个时代的流行作家，我认为他们往往<a href="https://twitter.com/danluu/status/784929052519837696" rel="noopener noreferrer">没有</a> <a href="https://web.archive.org/web/20210912095555/https://twitter.com/rakyll/status/1413996396814823431" rel="noopener noreferrer">真正</a> <a href="https://twitter.com/danluu/status/1172593136503160832" rel="noopener noreferrer">经受住</a>时间的考验。</p>

<h4 id="fabian-giesen-https-fgiesen-wordpress-com"><a href="https://fgiesen.wordpress.com/" rel="noopener noreferrer">Fabian Giesen</a></h4>

<p>涵盖各种技术主题。侧重于计算机架构、压缩、图形和信号处理，但你也会发现许多其他主题。</p>

<p>文章往往技术性强，不是轻松读物，它们通常解释概念或思想（而不是选边站队写观点文章）。</p>

<h4 id="fabien-sanglard-http-fabiensanglard-net"><a href="http://fabiensanglard.net/" rel="noopener noreferrer">Fabien Sanglard</a></h4>

<p>对游戏相关主题的深入技术探讨，例如<a href="http://fabiensanglard.net/doomIphone/doomClassicRenderer.php" rel="noopener noreferrer">对《毁灭战士》源代码的解读</a>，<a href="http://fabiensanglard.net/cuda/index.html" rel="noopener noreferrer">Nvidia GPU架构的历史</a>，或<a href="http://fabiensanglard.net/rayTracing_back_of_business_card/index.php" rel="noopener noreferrer">对名片大小的光线追踪器的解读</a>。</p>

<h4 id="fabrice-bellard-http-bellard-org"><a href="http://bellard.org/" rel="noopener noreferrer">Fabrice Bellard</a></h4>

<p>不完全是博客，但每次有新项目出现在首页时，都非常惊人。例如QEMU、FFMPEG、一个能在PC上运行的4G LTE基站、<a href="http://bellard.org/jslinux/" rel="noopener noreferrer">一个可以启动Linux的JavaScript PC模拟器</a>等等。</p>

<h4 id="fred-akalin-https-www-akalin-com"><a href="https://www.akalin.com/" rel="noopener noreferrer">Fred Akalin</a></h4>

<p>对计算机科学相关数学主题的解释（有一些与计算机科学没有直接关系）。</p>

<h4 id="gary-bernhardt-https-twitter-com-garybernhardt"><a href="https://twitter.com/garybernhardt" rel="noopener noreferrer">Gary Bernhardt</a></h4>

<p>又一个“不完全是博客”，但它比大多数博客更具信息量，更不用说更有趣了。这是我所知道的关于<a href="https://twitter.com/garybernhardt/status/676560697262604288" rel="noopener noreferrer">现代软件普遍存在的问题</a>的最佳“博客”。</p>

<h4 id="jaana-dogan-https-rakyll-org"><a href="https://rakyll.org/" rel="noopener noreferrer">Jaana Dogan</a></h4>

<p><a href="https://rakyll.org/" rel="noopener noreferrer">rakyll.org</a>上有关于Go的博文，其中一些相当深入，例如<a href="https://rakyll.org/generics-proposal/" rel="noopener noreferrer">关于Go泛型提案的一系列笔记</a>，而<a href="https://medium.com/@rakyll" rel="noopener noreferrer">Jaana的Medium博客</a>上有一些关于Go的文章，以及关于分布式系统中各种主题的文章。</p>

<p>此外，<a href="https://twitter.com/rakyll" rel="noopener noreferrer">Jaana的Twitter</a>上有我称之为“对行业智力上诚实的批评”的内容，我认为这在Twitter上对行业的批评中是不常见的。更典型的是人们为了得分而牺牲细微差别，甚至远离正确性，这就是我认为有必要指出这些诚实批评的原因。</p>

<h4 id="jamie-brandon-https-scattered-thoughts-net"><a href="https://scattered-thoughts.net/" rel="noopener noreferrer">Jamie Brandon</a></h4>

<p>我很高兴我设法说服了Jamie，鉴于他的偏好，尝试全职写博客来养活自己是有意义的。自从<a href="https://twitter.com/danluu/status/1354239729483407360" rel="noopener noreferrer">Jamie开始接受捐款</a>以来，直到今天，这个博客一直是绝对的 powerhouse，发表了像<a href="https://scattered-thoughts.net/writing/select-wat-from-sql/" rel="noopener noreferrer">这一系列</a>关于SQL问题的文章，<a href="https://scattered-thoughts.net/writing/an-opinionated-map-of-incremental-and-streaming-systems/" rel="noopener noreferrer">这一系列</a>关于流式系统问题的文章，在技术项目如<a href="https://github.com/jamii/dida" rel="noopener noreferrer">dida</a>和<a href="https://github.com/jamii/imp" rel="noopener noreferrer">imp</a>上的出色工作等等。</p>

<p>Jamie能否说服我尝试把写博客作为全职工作还有待观察。</p>

<h4 id="janet-davis-http-blogs-whitman-edu-countingfromzero"><a href="http://blogs.whitman.edu/countingfromzero/" rel="noopener noreferrer">Janet Davis</a></h4>

<p>这是一个关于一位教授从格林内尔搬到惠特曼并<a href="http://blogs.whitman.edu/countingfromzero/2015/07/22/how-i-got-here/" rel="noopener noreferrer">从零开始建立计算机科学项目</a>的故事。如果你对组织如何形成或计算机科学教育感兴趣，这些存档文章是很好的读物。</p>

<h4 id="jeff-preshing-https-preshing-com"><a href="https://preshing.com/" rel="noopener noreferrer">Jeff Preshing</a></h4>

<p>主要是与C++和Python相关的技术内容，但也包括对程序员通常有用的主题，例如<a href="https://preshing.com/20150402/you-can-do-any-kind-of-atomic-read-modify-write-operation/" rel="noopener noreferrer">读-改-写操作</a>、<a href="https://preshing.com/20121105/arithmetic-encoding-using-fixed-point-math/" rel="noopener noreferrer">定点数学</a>和<a href="https://preshing.com/20120930/weak-vs-strong-memory-models/" rel="noopener noreferrer">内存模型</a>。</p>

<h4 id="jessica-kerr-http-blog-jessitron-com"><a href="http://blog.jessitron.com/" rel="noopener noreferrer">Jessica Kerr</a></h4>

<p>Jessica可能更以她的<a href="http://jessitron.com/talks.html" rel="noopener noreferrer">演讲</a>而非她的博客闻名？她的演讲很棒！我最喜欢的可能是<a href="https://www.youtube.com/watch?v=yhguOt863nw" rel="noopener noreferrer">这个以易于理解的方式解释不同并发模型的演讲</a>，但<a href="http://blog.jessitron.com/2015/01/systems-thinking-about-wit.html" rel="noopener noreferrer">博客上也有很多我喜欢的内容</a>。</p>

<p>和她的演讲一样，图表通常能阐明一个概念，让事后看来显而易见的事情变得明显。</p>

<h4 id="john-regehr-http-blog-regehr-org"><a href="http://blog.regehr.org/" rel="noopener noreferrer">John Regehr</a></h4>

<p>我认为这是“<a href="http://blog.regehr.org/archives/213" rel="noopener noreferrer">C语言比你想象的更难，即使你认为C已经很难</a>的博客”，尽管博客实际上涵盖的内容远不止于此。一些常见主题包括模糊测试、编译器优化和一般的测试。</p>

<p>文章往往概念性强。有代码示例时，通常很容易阅读，但也有一些奇怪行为的例子，除非你是那种能熟记C标准的人，否则很难快速浏览。</p>

<h4 id="juho-snellman-https-www-snellman-net-blog"><a href="https://www.snellman.net/blog/" rel="noopener noreferrer">Juho Snellman</a></h4>

<p>有很多<a href="https://www.snellman.net/blog/archive/2015-08-25-tcp-optimization-in-mobile-networks/" rel="noopener noreferrer">关于网络的文章</a>，通常<a href="https://www.snellman.net/blog/archive/2016-02-01-tcp-rst/" rel="noopener noreferrer">写得即使在网络背景知识很少的情况下也能理解</a>。我希望更多拥有这种知识（对系统的深入了解，而不仅仅是网络知识）的人能为普通读者写下解释。还有一些有趣的非网络内容，比如<a href="https://www.snellman.net/blog/archive/2015-05-11-okcupid-for-voting-the-finnish-election-engines/" rel="noopener noreferrer">这篇关于芬兰选举的文章</a>。</p>

<h4 id="julia-evans-http-jvns-ca"><a href="http://jvns.ca/" rel="noopener noreferrer">Julia Evans</a></h4>

<p>据我所知，主题是“朱莉娅最近学到的东西”，可以是任何内容，从<a href="http://jvns.ca/blog/2015/02/22/how-gzip-uses-huffman-coding/" rel="noopener noreferrer">霍夫曼编码</a>到<a href="http://jvns.ca/blog/2014/10/22/working-remote-8-months-in/" rel="noopener noreferrer">如何在远程工作中保持快乐</a>。当文章的主题是我不熟悉的时候，我会学到新东西。当文章的主题是我熟悉的时候，它们会提醒我这个主题是令人兴奋的，包含很多惊奇和奥秘。</p>

<p>许多文章问题多于答案，更像是对主题的实时博客探索，而不是对主题的解释。</p>

<h4 id="karla-burnett-https-karla-io"><a href="https://karla.io/" rel="noopener noreferrer">Karla Burnett</a></h4>

<p>内容混合了安全相关主题和实用编程知识的解释。例如，<a href="https://karla.io/files/ichthyology-wp.pdf" rel="noopener noreferrer">这篇关于网络钓鱼的文章</a>，包含了一系列有趣的案例研究，展示了即使在人们接受过反网络钓鱼培训后，网络钓鱼仍然有效的例子，这是安全类文章的例子。例如，<a href="https://karla.io/2016/06/13/dont-panic.html" rel="noopener noreferrer">这篇关于通过tracert打印文本的文章</a>。这篇<a href="https://karla.io/2016/04/30/ssh-for-fun-and-profit.html" rel="noopener noreferrer">关于编写SSH客户端的文章</a>和<a href="https://karla.io/2015/06/28/brain-teaser-2.html" rel="noopener noreferrer">这篇文章</a>是实用编程解释的例子。</p>

<p>尽管博客以安全为导向，但文章是为普通读者写的，不假设具备特定的安全专业知识。</p>

<h4 id="kate-murphy-https-kate-io"><a href="https://kate.io/" rel="noopener noreferrer">Kate Murphy</a></h4>

<p>主要是小而独立的探索，比如<a href="https://kate.io/blog/2017/08/22/weird-python-integers/" rel="noopener noreferrer">这个Python整数行为是怎么回事</a>，<a href="https://kate.io/blog/making-your-own-exploding-git-repos/" rel="noopener noreferrer">如何用一个简单的仓库让Git爆炸</a>，或者<a href="https://kate.io/blog/simple-hash-collisions-in-lua/" rel="noopener noreferrer">如何在Lua中生成哈希冲突</a>？</p>

<h4 id="kavya-joshi">Kavya Joshi</h4>

<p>我通常更喜欢文本形式的技术解释，而不是视频，但她的阐述如此清晰，以至于我把这些演讲列入了这个博客列表。例如，<a href="https://www.youtube.com/watch?v=5erqWdlhQLA" rel="noopener noreferrer">对Go竞态检测器的解释</a>，<a href="https://www.youtube.com/watch?v=Ivss-VtmbDY" rel="noopener noreferrer">对性能建模有用的简单数学</a>，和<a href="https://www.youtube.com/watch?v=BRvj8PykSc4" rel="noopener noreferrer">时间</a>。</p>

<h4 id="kyle-kingsbury-https-aphyr-com"><a href="https://aphyr.com/" rel="noopener noreferrer">Kyle Kingsbury</a></h4>

<p>Kyle 90%的博文都是对<a href="https://aphyr.com/posts/322-call-me-maybe-mongodb-stale-reads" rel="noopener noreferrer">分布式系统测试的解释，这些测试暴露了真实系统（我们大多数人依赖的系统）中的bug</a>。另外10%是<a href="https://aphyr.com/posts/319-clojure-from-the-ground-up-debugging" rel="noopener noreferrer">对编程的思考，其严谨性与Kyle关于分布式系统的文章一样</a>。可能是有史以来最具教育意义的编程博客。</p>

<p>对于我们这些没有分布式系统背景的人来说，理解文章通常需要一点谷歌搜索，尽管文章中有详尽的解释。大多数新文章现在都在<a href="http://jepsen.io/" rel="noopener noreferrer">jepsen.io</a>上。</p>

<h4 id="laura-lindzey-https-lindzey-github-io-index-html"><a href="https://lindzey.github.io/index.html" rel="noopener noreferrer">Laura Lindzey</a></h4>

<p>更新非常不频繁（大约每年一次），内容是Laura一直在研究的东西，从<a href="https://lindzey.github.io/blog/2019/11/26/origami-butterfly/" rel="noopener noreferrer">折纸PCB</a>到<a href="https://lindzey.github.io/blog/2015/07/27/a-brief-introduction-to-ice-penetrating-radar/" rel="noopener noreferrer">冰层穿透雷达</a>。</p>

<h4 id="laurie-tratt-https-tratt-net-laurie-blog"><a href="https://tratt.net/laurie/blog/" rel="noopener noreferrer">Laurie Tratt</a></h4>

<p>这个博客自2004年以来一直在运营，这些年有所变化。最近，它发表了一些关于基准测试的最佳文章：</p>

<ul>
<li><a href="https://tratt.net/laurie/blog/entries/why_arent_more_users_more_happy_with_our_vms_part_1.html" rel="noopener noreferrer">VM性能，第1部分</a>

<ul>
<li>彻底反驳了这样一种观点：你可以运行一个语言VM一段时间进行预热，然后在它们变得稳定后取一些数值</li>
</ul></li>
<li><a href="https://tratt.net/laurie/blog/entries/why_arent_more_users_more_happy_with_our_vms_part_2.html" rel="noopener noreferrer">VM性能，第2部分</a></li>
<li><a href="https://tratt.net/laurie/blog/entries/minimum_times_tend_to_mislead_when_benchmarking.html" rel="noopener noreferrer">为什么基准测试中不使用最小时间</a>

<ul>
<li>“每个人”认真对待性能的人都知道这一点，通常被认为太显然而不值得写，但这在基准测试中仍然是一种广泛使用的技术，尽管它只适用于有限的情况</li>
</ul></li>
</ul>

<p>这个博客并非纯技术性，<a href="https://tratt.net/laurie/blog/entries/alternative_sources_of_advice.html" rel="noopener noreferrer">这篇关于建议的博文也很出色</a>。如果这些文章听起来不吸引你，值得<a href="https://tratt.net/laurie/blog/archive.html" rel="noopener noreferrer">查看存档</a>，看看劳伦斯过去更频繁地写的主题是否合你口味。</p>

<h4 id="marc-brooker-https-brooker-co-za-blog"><a href="https://brooker.co.za/blog/" rel="noopener noreferrer">Marc Brooker</a></h4>

<p>混合了<a href="https://brooker.co.za/blog/2012/01/17/two-random.html" rel="noopener noreferrer">理论</a>和来自亚马逊EBS分布式系统工程师的<a href="https://brooker.co.za/blog/2016/01/03/correlation.html" rel="noopener noreferrer">智慧</a>。理论文章通常相对简短且易于理解；一点也不令人生畏，不像有时候理论文章那样。</p>

<h4 id="marek-majkowski-https-idea-popcount-org"><a href="https://idea.popcount.org/" rel="noopener noreferrer">Marek Majkowski</a></h4>

<p>这曾经是一个关于Marek正在做的随机实验的博客，<a href="https://idea.popcount.org/2013-01-30-bitsliced-siphash/" rel="noopener noreferrer">比如这篇关于位切片SipHash的文章</a>。自从Marek加入Cloudflare后，这已经变成了Marek在Cloudflare网络堆栈工作中学到的东西的列表，比如<a href="https://blog.cloudflare.com/the-curious-case-of-slow-downloads/" rel="noopener noreferrer">这个关于调试慢速下载的故事</a>。</p>

<p>文章往往相对较短，但包含足够的技术细节，不是轻松读物。</p>

<h4 id="nicole-express-https-nicole-express"><a href="https://nicole.express/" rel="noopener noreferrer">Nicole Express</a></h4>

<p>对旧系统的探索，通常与游戏相关。例如<a href="https://nicole.express/2021/alf-2-alf-harder.html" rel="noopener noreferrer">这篇关于世嘉Master System上Alf的碰撞检测的文章</a>，<a href="https://nicole.express/2021/composite-conflict-completed.html" rel="noopener noreferrer">这篇关于从复合视频获得优质输出的文章</a>，和<a href="https://nicole.express/2020/neo-geo-cd-zee.html" rel="noopener noreferrer">这篇关于Neo Geo CDZ的文章</a>。</p>

<h4 id="nikita-prokopov-https-tonsky-me"><a href="https://tonsky.me/" rel="noopener noreferrer">Nikita Prokopov</a></h4>

<p>Nikita有两个博客，主题相关。<a href="https://tonsky.me/" rel="noopener noreferrer">主博客</a>上有长篇文章，通常是关于现代软件如何糟糕。然后是<a href="https://grumpy.website/" rel="noopener noreferrer">grumpy.website</a>，它给出了软件糟糕的例子。</p>

<h4 id="nitsan-wakart-http-psy-lob-saw-blogspot-com"><a href="http://psy-lob-saw.blogspot.com/" rel="noopener noreferrer">Nitsan Wakart</a></h4>

<p>关于为JVM编写快速代码，你想知道的比你想象的更多，从<a href="http://psy-lob-saw.blogspot.com/2016/03/gc-nepotism-and-linked-queues.html" rel="noopener noreferrer">GC如何影响数据结构</a>到<a href="http://psy-lob-saw.blogspot.com/2014/08/the-volatile-read-suprise.html" rel="noopener noreferrer">volatile读取的微妙之处</a>。</p>

<p>文章通常包含大量Java代码，但要点通常是语言无关的。</p>

<h4 id="oona-raisanen-http-www-windytan-com"><a href="http://www.windytan.com/" rel="noopener noreferrer">Oona Raisanen</a></h4>

<p>信号处理的冒险。从<a href="http://www.windytan.com/2016/02/barcode-recovery-using-priori.html" rel="noopener noreferrer">模糊条形码的修复</a>到弄清楚<a href="http://www.windytan.com/2014/02/mystery-signal-from-helicopter.html" rel="noopener noreferrer">直升机的那些信号意味着什么</a>。如果我早知道信号和系统可以如此有趣，我就会在课堂上更认真听讲了。</p>

<h4 id="paul-khuong-http-www-pvk-ca"><a href="http://www.pvk.ca/" rel="noopener noreferrer">Paul Khuong</a></h4>

<p><a href="http://www.pvk.ca/Blog/2013/11/22/the-weaknesses-of-sbcls-type-propagation/" rel="noopener noreferrer">一些关于Lisp的内容</a>，<a href="http://www.pvk.ca/Blog/2014/04/13/k-ary-heapsort/" rel="noopener noreferrer">一些关于底层优化的内容</a>，并且<a href="http://www.pvk.ca/Blog/2015/06/27/linear-log-bucketing-fast-versatile-simple/" rel="noopener noreferrer">趋势是走向底层优化</a>。</p>

<p>文章通常是相对较长、自成一体的技术思想解释，几乎没有废话。</p>

<h4 id="rachel-kroll-http-rachelbythebay-com-w"><a href="http://rachelbythebay.com/w/" rel="noopener noreferrer">Rachel Kroll</a></h4>

<p>来自一位长期SRE的多年<a href="http://rachelbythebay.com/w/2015/09/07/noleap/" rel="noopener noreferrer">调试故事</a>，以及关于<a href="https://rachelbythebay.com/w/2012/02/29/derailing/" rel="noopener noreferrer">大公司荒唐事</a>的故事。许多故事来自Lyft、Facebook和Google。它们被匿名化了，但如果你了解这些公司，你能分辨出是哪一个。</p>

<p>匿名化的程度通常意味着，除非你熟悉与故事中类似的系统的操作，否则这些故事真的没有意义。</p>

<h4 id="sophie-haskins-http-blog-pizzabox-computer"><a href="http://blog.pizzabox.computer/" rel="noopener noreferrer">Sophie Haskins</a></h4>

<p>一个关于修复旧“披萨盒”电脑的博客，文章通常描述让这些机器重新工作所付出的努力。</p>

<p>一个例子是HP 712（1994年时价格大约在5000到15000美元的“低成本”PA-RISC工作站，最终由于1995年以Pentium Pro开始的Intel工作站冲击而命运注定），<a href="https://blog.pizzabox.computer/posts/hp712-nextstep-part-1/" rel="noopener noreferrer">修复过程在这里第1部分描述</a>，然后<a href="https://blog.pizzabox.computer/posts/hp712-nextstep-part-2/" rel="noopener noreferrer">在第2部分描述</a>。</p>

<h4 id="vyacheslav-egorov-http-mrale-ph"><a href="http://mrale.ph/" rel="noopener noreferrer">Vyacheslav Egorov</a></h4>

<p>由V8团队编译器开发者<a href="http://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html" rel="noopener noreferrer">深入解释</a>V8如何工作以及<a href="http://mrale.ph/blog/2014/07/30/constructor-vs-objectcreate.html" rel="noopener noreferrer">各种构造如何被编译器优化</a>。如果我早知道编译器如此有趣，我在大学时就会选修编译器课程了。</p>

<p>经常把被认为困难的主题用让它们看起来容易的方式解释。在适当的时候有很多图表，对所有棘手的部分都有详细的阐述。</p>

<h4 id="whitequark-https-whitequark-org"><a href="https://whitequark.org/" rel="noopener noreferrer">whitequark</a></h4>

<p>她的主站有很多她制作或参与的各种有趣的工具，许多与FPGA或开源硬件相关，但也有一些完全不同。<a href="https://lab.whitequark.org/" rel="noopener noreferrer">Whitequark的实验室笔记本</a>有非常广泛的各种结果，从未记录的硬件特性，到相当严肃的家庭化学实验，再到关于编程和硬件开发的各种片段（通常是底层的，但并非总是如此）。</p>

<p>她也在<a href="https://twitter.com/whitequark/" rel="noopener noreferrer">Twitter上相当活跃</a>，有一些关于硬件/固件/底层编程的评论，以及一组主题范围太广以至于不易总结的多样化话题。</p>

<h4 id="yossi-kreinin-http-yosefk-com-blog"><a href="http://yosefk.com/blog/" rel="noopener noreferrer">Yossi Kreinin</a></h4>

<p>自<a href="http://yosefk.com/blog/a-better-future-animated-post.html" rel="noopener noreferrer">作者开始做艺术</a>以来，大部分时间处于休眠状态，但存档中有很多关于硬件、底层软件和一般编程相关主题（并非严格编程）的优秀内容。</p>

<p>90%的时间，当我想要写一篇关于软件人员对硬件常见误解的文章时，<a href="http://yosefk.com/blog/a-better-future-animated-post.html" rel="noopener noreferrer">Yossi已经写过这篇文章了</a>，并且<a href="http://yosefk.com/blog/high-level-cpu-follow-up.html" rel="noopener noreferrer">因此受到了很多抨击</a>，所以我不必写了:-)。</p>

<p>我也很喜欢Yossi的职业建议，比如<a href="http://yosefk.com/blog/do-call-yourself-a-programmer-and-other-career-advice.html" rel="noopener noreferrer">对Patrick McKenzie的回应</a>和<a href="http://yosefk.com/blog/people-can-read-their-manager-mind.html" rel="noopener noreferrer">这篇关于经理如何得到他们想要的而不是他们要求的博文</a>。</p>

<p>他在<a href="https://twitter.com/yossikreinin" rel="noopener noreferrer">Twitter上很活跃</a>，经常发表对管理和行业的极其愤世嫉俗和尖刻的看法。</p>

<h4 id="this-blog">这个博客？</h4>

<p>常见主题包括：</p>

<ul>
<li><a href="//danluu.com/file-consistency/" rel="noopener noreferrer">这个通常被认为是简单的事情比你想象的更难</a></li>
<li><a href="//danluu.com/edit-binary/" rel="noopener noreferrer">这个通常被认为是困难的事情比你想象的更容易</a></li>
<li><a href="//danluu.com/dunning-kruger/" rel="noopener noreferrer">这个显而易见的事实</a> <a href="//danluu.com/tech-discrimination/" rel="noopener noreferrer">根本不</a> <a href="//danluu.com/integer-overflow/" rel="noopener noreferrer">显而易见</a></li>
<li><a href="//danluu.com/postmortem-lessons/" rel="noopener noreferrer">人类就是人类</a>：人类会犯错误，<a href="//danluu.com/google-sre-book/" rel="noopener noreferrer">系统必须设计得考虑到这一点</a></li>
<li><a href="//danluu.com/postmortem-lessons/" rel="noopener noreferrer">计算机会出故障</a>，<a href="//danluu.com/limplock/" rel="noopener noreferrer">系统必须设计得考虑到这一点</a></li>
<li><a href="//danluu.com/broken-builds/" rel="noopener noreferrer">是只有我这样</a>，<a href="//danluu.com/everything-is-broken/" rel="noopener noreferrer">还是东西真的都坏了</a>？</li>
<li><a href="//danluu.com/infinite-disk/" rel="noopener noreferrer">嘿</a>！<a href="//danluu.com/intel-cat/" rel="noopener noreferrer">看看这组论文</a>！<a href="//danluu.com/clwb-pcommit/" rel="noopener noreferrer">让我们谈谈它们的背景和为什么它们很有趣</a></li>
</ul>

<h4 id="the-end">结束</h4>

<p>这个列表也不包括那些主要不是关于编程的博客，所以例如不包括<a href="http://www.benkuhn.net/" rel="noopener noreferrer">Ben Kuhn出色的博客</a>。</p>

<p>总之，这就是目前的全部内容，但这个列表基本上是我一时想到的，所以我会在想到更多博客时添加更多。我也会在找到新博客时用我正在读的内容更新这个列表。请、请、请<a href="https://twitter.com/danluu" rel="noopener noreferrer">推荐其他我可能喜欢的博客</a>，不要因为我可能已经知道而假设我知道某个博客。举个例子，直到几年前我还不知道Jeff Atwood或Zed Shaw是谁，他们可能是当时最著名的编程博主。即使有了像HN和reddit这样的集中式链接聚合器，随着博客列表和博客作为对话（而不是当前博客作为独白的实践）的衰落，博客发现已经变得偶然和随机。另外，请不要因为我通常读的博客类型不同，就假设我不想读某个东西。我很想读更多来自UX或前端人员的内容；我只是不知道去哪里找这类东西！</p>

<p><em>最后更新：2021年7月</em></p>

<h4 id="archive">存档</h4>

<p>以下是我放入存档部分的博客，因为它们很少或从不更新。</p>

<h4 id="alex-clemmer-http-blog-nullspace-io"><a href="http://blog.nullspace.io/" rel="noopener noreferrer">Alex Clemmer</a></h4>

<p><a href="http://blog.nullspace.io/building-search-engines.html" rel="noopener noreferrer">这篇关于为什么制作一个谷歌搜索竞争对手是经典Alex Clemmer风格的文章</a>。文章审视了一个普遍相信的立场（网络搜索并不那么难，应该有人想出一个更好的谷歌），并解释了为什么这不是一个显然正确的立场。这也是他在其他地方评论的共同主题，比如在<a href="https://news.ycombinator.com/item?id=9598224" rel="noopener noreferrer">MS的栈排名</a>、<a href="https://news.ycombinator.com/item?id=10416015" rel="noopener noreferrer">在Windows上实现POSIX</a>、<a href="https://news.ycombinator.com/item?id=10229434" rel="noopener noreferrer">Windows代码库的规模</a>、<a href="https://news.ycombinator.com/item?id=8867387" rel="noopener noreferrer">Bond</a>和<a href="https://news.ycombinator.com/item?id=9735791" rel="noopener noreferrer">Bing</a>上的评论。</p>

<p>他有点像现代的迷你MSFT，因为他对MS及相关事业的评论一针见血。</p>

<h4 id="allison-kaptur-http-akaptur-com"><a href="http://akaptur.com/" rel="noopener noreferrer">Allison Kaptur</a></h4>

<p>对各种领域的探索，通常与Python相关，比如<a href="http://akaptur.com/blog/2013/11/15/introduction-to-the-python-interpreter/" rel="noopener noreferrer">这一系列关于Python解释器的文章</a>和<a href="http://akaptur.com/blog/2014/08/02/the-cpython-peephole-optimizer-and-you/" rel="noopener noreferrer">这一系列关于CPython窥孔优化器的文章</a>。此外，关于更广泛主题的思考，如<a href="http://akaptur.com/blog/2013/07/24/systematic-debugging/" rel="noopener noreferrer">调试</a>和<a href="http://akaptur.com/blog/2015/10/10/effective-learning-strategies-for-programmers/" rel="noopener noreferrer">学习</a>。</p>

<p>通常很详细，带有需要阅读和理解的内联代码（在通常相当清晰的阐述帮助下）。</p>

<h4 id="david-dalrymple-http-davidad-github-io"><a href="http://davidad.github.io/" rel="noopener noreferrer">David Dalrymple</a></h4>

<p>混合了从<a href="http://davidad.github.io/blog/2014/02/18/kernel-from-scratch/" rel="noopener noreferrer">在学习汇编后不久从零开始编写一个64位内核</a>到<a href="http://davidad.github.io/blog/2014/03/12/the-operating-system-is-out-of-date/" rel="noopener noreferrer">计算机系统高层概述</a>的各种内容。很少更新，文章很少，但每篇文章都值得深思。</p>

<h4 id="epita-systems-lab-https-blog-lse-epita-fr"><a href="https://blog.lse.epita.fr/" rel="noopener noreferrer">EPITA系统实验室</a></h4>

<p>底层。这个博客中一个相对高层文章的好例子是<a href="https://blog.lse.epita.fr/articles/74-getting-back-determinism-in-the-lfh.html" rel="noopener noreferrer">这篇关于Windows低碎片堆的文章</a>。像<a href="https://blog.lse.epita.fr/articles/75-sstpinball.html" rel="noopener noreferrer">如何入侵弹球机</a>和<a href="https://blog.lse.epita.fr/articles/77-lsepc-intro.html" rel="noopener noreferrer">如何设计一个386兼容开发板</a>这样的文章是典型的。</p>

<p>文章通常非常详细，带有原理图/电路图。这是相对较重的阅读，我阅读这个博客时会尽量手边放好纸笔。</p>

<h4 id="greg-wilson-http-neverworkintheory-org"><a href="http://neverworkintheory.org/" rel="noopener noreferrer">Greg Wilson</a></h4>

<p>总结了一些（应该）影响人们编写软件方式的论文，比如<a href="http://neverworkintheory.org/2014/10/08/simple-testing-can-prevent-most-critical-failures.html" rel="noopener noreferrer">这篇关于分布式系统中失败原因的论文</a>或<a href="http://neverworkintheory.org/2015/08/23/perception-productivity.html" rel="noopener noreferrer">这篇关于是什么让人感觉高效的论文</a>。不经常更新，但<a href="http://third-bit.com/index.html" rel="noopener noreferrer">Greg仍然在他的个人网站上写博客</a>。</p>

<p>文章往往是扩展摘要，引诱你去读论文，而不是对方法和结果的详细解释。</p>

<h4 id="gustavo-duarte-http-duartes-org-gustavo-blog"><a href="http://duartes.org/gustavo/blog/" rel="noopener noreferrer">Gustavo Duarte</a></h4>

<p>对<a href="http://duartes.org/gustavo/blog/post/what-does-an-idle-cpu-do/" rel="noopener noreferrer">Linux如何工作以及其他底层主题</a>的解释。这个博客似乎处于暂停状态，但“0xAX”似乎用<a href="https://0xax.gitbooks.io/linux-insides/content/index.html" rel="noopener noreferrer">linux-insides</a>项目接替了工作。</p>

<p>如果你读过Love关于Linux的书，Duarte的解释类似，但更侧重于思想而非实现。它们也更侧重于提供图表和上下文。“0xAX”比Love或Duarte更专注于逐行讲解代码。</p>

<h4 id="huon-wilson-http-huonw-github-io"><a href="http://huonw.github.io/" rel="noopener noreferrer">Huon Wilson</a></h4>

<p>在Huon还在开发Rust时，对各种Rust相关事情的解释。不经常更新了，但内容对于对Rust技术细节感兴趣的人来说仍然很棒。</p>

<h4 id="kamal-marhubi-http-kamalmarhubi-com"><a href="http://kamalmarhubi.com/" rel="noopener noreferrer">Kamal Marhubi</a></h4>

<p>对各种主题的技术探索，带有系统化的倾向。例如<a href="http://kamalmarhubi.com/blog/2015/11/17/kubernetes-from-the-ground-up-the-scheduler/" rel="noopener noreferrer">Kubernetes</a>。<a href="http://kamalmarhubi.com/blog/2015/11/21/using-strace-to-figure-out-how-git-push-over-ssh-works/" rel="noopener noreferrer">Git push</a>。<a href="http://kamalmarhubi.com/blog/2016/04/13/rust-nix-easier-unix-systems-programming-3/" rel="noopener noreferrer">Rust中的系统调用</a>。此外，<a href="http://kamalmarhubi.com/blog/2015/05/28/the-excellence-uniform-build-and-test-infrastructure/" rel="noopener noreferrer">一些关于编程的思考</a>。</p>

<p>技术探索通常涉及足够多的细节，以至于你需要坐下来阅读，而不是在手机上浏览。</p>

<h4 id="mary-rose-cook-http-maryrosecook-com-blog"><a href="http://maryrosecook.com/blog/" rel="noopener noreferrer">Mary Rose Cook</a></h4>

<p><a href="http://maryrosecook.com/blog/post/git-from-the-inside-out" rel="noopener noreferrer">对技术主题的冗长而非常详细的解释</a>，<a href="http://maryrosecook.com/blog/post/little-lisp-interpreter" rel="noopener noreferrer">混合在其中</a>有<a href="http://maryrosecook.com/blog/post/the-fibonacci-heap-ruins-my-life" rel="noopener noreferrer">各种其他文章</a>。</p>

<p>主题选择兼收并释，解释的详细程度让你能很好地理解该主题。解释通常足够详细，即使你是初学者程序员，也不会错过要点。</p>

<h4 id="rebecca-frankel">Rebecca Frankel</h4>

<p>据我所知，Rebecca没有编程博客，但如果你把她看似随意地在其他人文章下的评论当作博客，那是最好的编程博客之一。她过去经常在<a href="//danluu.com/open-social-networks/" rel="noopener noreferrer">Piaw的</a> <a href="//danluu.com/mit-stanford/" rel="noopener noreferrer">Buzz</a>上发表大量评论（可能还有其他地方，虽然我不知道在哪里），偶尔在其他地方也能看到评论，比如在<a href="http://steve-yegge.blogspot.com/2008/06/done-and-gets-things-smart.html" rel="noopener noreferrer">Steve Yegge关于杰出工程师的这篇博文</a><sup id="fnref:R"><a href="#fn:R" rel="noopener noreferrer">1</a></sup>上。我希望我能写得像那样。</p>

<h4 id="russell-smith-http-qqrs-github-io"><a href="http://qqrs.github.io/" rel="noopener noreferrer">Russell Smith</a></h4>

<p>自制的电子项目，从<a href="http://qqrs.github.io/blog/2013/05/03/vim-on-a-mechanical-typewriter/" rel="noopener noreferrer">机械打字机上的vim</a>到<a href="http://qqrs.github.io/blog/2015/08/21/proofing-spirits-with-homemade-electrobalance/" rel="noopener noreferrer">制造电子天平来测试烈酒</a>。</p>

<p>文章往往有相当多的细节，甚至到解释电路部分的图表，但文章不像规格书那么详细。但通常会有链接到能教你足够知识来复制项目的资源，如果你想的话。</p>

<h4 id="rwt-http-www-realworldtech-com"><a href="http://www.realworldtech.com/" rel="noopener noreferrer">RWT</a></h4>

<p>我发现存档是有趣的读物，可以了解<a href="http://www.realworldtech.com/alpha-ev8-wider/" rel="noopener noreferrer">过去二十年人们对微处理器和计算机架构的思考</a>。看到<a href="http://www.realworldtech.com/benchmark-examiner/" rel="noopener noreferrer">我们15年前的基准测试争议</a>今天还在重复，有时是同样的参与者，这可能有点令人沮丧。如果有的话，我想说今天流传的<a href="https://twitter.com/shipilev/status/709982588673388544" rel="noopener noreferrer">平均基准测试</a>比15年前你看到的要差，尽管整个行业从那以后学到了很多关于基准测试的知识。</p>

<h4 id="walpurgusriot-https-web-archive-org-web-20140118064249-http-walpurgisriot-github-io-blog"><a href="https://web.archive.org/web/20140118064249/http://walpurgisriot.github.io/blog" rel="noopener noreferrer">walpurgusriot</a></h4>

<p>walpurgusriot的作者似乎已经放弃了这个github账户，转而使用另一个用户名（而且一个占坑者似乎已经拿走了她旧的账户名），但这曾经是一个半经常更新的博客，结合了对编程的短篇探索和对行业的思考。就散文的纯粹质量而言，这是我读过的最好的科技博客之一；技术内容和对行业的思考也很棒。</p>

<p><em>这篇文章的灵感来自于朱莉娅·埃文斯关于她阅读的博客的两篇文章，以及<a href="https://www.ocf.berkeley.edu/~abhishek/chicmath.htm" rel="noopener noreferrer">芝加哥本科数学参考书目</a>，我认为这是我遇到过的最有用的一套书评。</em></p>

<p><em>感谢Bartłomiej Filipek和Sean Barrett, Michel Schniz, Neil Henning, 和Lindsey Kuper的评论/讨论/修正。</em></p>
<div>

<hr>

<ol>
<li id="fn:R"><p>引文如下，因为我从分析数据中看到，相对较少的人会点击任何单个链接，人们似乎特别不可能点击链接去阅读博客上的评论，即使评论很棒：</p>

<blockquote>
<p>关键在于“主要”，我描述的是动机，而不是自我评估。问题是，是什么在驱动你？是什么让你工作？如果只是为了表明你很好，那么你就不会好。还必须有别的东西，否则它无法让你通过达到那个水平所需的集中十年训练。</p>

<p>看看我们都认为Steve Yegge在谈论的那个人的历史。他于1990年（以优异成绩）毕业，1999年加入谷歌。所以他在达到谷歌明星水平之前工作了很长时间。当我在谷歌时，我周日下午和一位类似的超级明星一起闲逛。没有其他人会在周日可靠地出现；但他总是出现，所以我可以指望有人说话。在某些周日，即使他明显有合理的理由感觉不舒服，他仍然来上班。为什么他不像任何正常人那样回家？并不是他试图证明自己；他早就证明了。是什么在驱动他？</p>

<p>我唯一能描述的方式是一个词：愤怒。他在每个周日做什么？他在审查各种被初级程序员提议为标准的API，他总能发现它们的问题。他会谈论，或者说，愤怒地谈论这些周日下午的某个愚蠢的事情，某人试图将其标准化，以及它有什么问题，如何需要修复等等。他总是对此非常愤怒。</p>

<p>是什么让他在感觉不舒服、头晕目眩，而且没有人，即使是Larry和Sergey，他们以传奇的不耐烦著称，即使是他们，我的意思是没有人，如果他就回家睡觉，没有人会认为他不行的时候来上班？他似乎不是被野心驱动，而是被恐惧驱动：如果他停止关注，一些他认为愚蠢错误的东西可能会通过他，成为标准，而这是无法忍受的，想到这一点就让他对错误感到如此愤怒，以至于他必须保持清醒并阻止它发生，无论他当时感觉多么糟糕。</p>

<p>它让我想起保罗·格雷厄姆的评论：“我所说的好人是什么意思？我们在创业期间学到的一个最好的技巧是决定雇佣谁的规则。你能把这个人描述成一种动物吗？……我的意思是有人对自己的工作有点过于认真；有人做他们做的事做得如此之好，以至于他们直接超越专业，跨越到痴迷。</p>

<p>具体含义取决于工作：一个拒绝接受“不”的销售员；一个宁愿熬夜到凌晨4点也不愿带着有bug的代码去睡觉的黑客；一个会直接打电话给《纽约时报》记者手机的公关人员；一个当东西偏移两毫米就感到身体疼痛的平面设计师。”</p>

<p>我认为这种描述的一个必然结果是，如果你真的想成为“一种动物”，你需要在自己身上培养的，部分是野心，但也部分是自我认知。正如保罗·格雷厄姆所说，有不同种类的动物。痴迷的平面设计师可能不太关心一个本可以更好但未能达标的API，而编程超级明星可能路过或创造一个糟糕的平面设计却丝毫没有不安。</p>

<p>因此，关键问题是：你正在做你最关心的事情吗？如果是错误的，对你来说是无法忍受的吗？只有深植于心的愤怒才能推动你达到超级明星的水平。到达那里太痛苦了；仅仅想变得优秀的欲望是不够的。如果不在你身上，就不在你身上。你必须被原始的愤怒所驱动。不如此则不行。</p>

<p>或者它在你身上，但只是不在这个领域。你必须找到你关心的东西，不仅仅是你关心的东西，而是你非常关心的东西：你无法伪造它。</p>

<p>(另外，如果你真的有它，你仍然必须仔细选择你的老板。无论你有多优秀，找到可以共事的人可能并非易事。这里还有更多要说的话；但我必须留到另一个评论。)</p>

<p>另一个澄清我的断言“如果你在怀疑自己是否优秀，那么你就不是”或许应该说“如果你需要别人向你保证你是优秀的，那么你就不是”。这些“动物”的一个特征是，他们是如此痴迷的完美主义者，以至于他们自己的内部标准远远超出任何人能强加给他们的标准，以至于任何普通人（即普通老板）都无法评估他们。正如Steve Yegge所说，他们不去参加面试。他们确实互相评估——据报道，在谷歌，超级明星们都会极其严苛地审查彼此的代码——但我不认为他们关心任何不在他们圈子或与他们同等水平的人的判断。</p>

<p>我同意Steve Yegge的断言，即存在一小部分非常重要的（小）群体，他们就是另一个层次，而普通聪明勤奋的人就是不一样。这里是另一种解释为什么应该存在量子跳跃的方式——也许我一直在用这个讨论来建立这个想法：这就是还在试图在别人管理的考试中取得好成绩的人，和那些找到了为自己打分的能力的人之间的区别，比别人可能强加给他们的更仔细，更痴迷于完美主义。</p>

<p>学校，尽管它教了很多东西，可能对人有一个不好的持久影响：它让人们认为好人会在考试中得到A，更好的人会得到A+，最好的人会得到A++。然后你进入现实世界，你的老板有点像超级教授，接管了考试的评分工作，告诉你他们是好是坏。</p>

<p>但问题是，在现实世界中，最有价值、最有效的人并不是那些试图在你给他们的考试中取得A+++的人。最好的人是那些能够自己出考题的人，题目比你想象的要难，而你必须和他们一样学习十年才能知道如何给他们的答案评分。</p>

<p>顺便说一句，这是精英管理理念的一个问题。学校给了你一个可以达到顶点的功绩阶梯。但它无法一直到达顶端，因为必须有人测量横档。在顶端，你不仅根据你在阶梯上的高度被评判。你还根据你“为自己打分”的能力被评判；也就是说，你的可信度。人们开始问你是否会执行自己的标准，即使没有人强加它们。他们必须！因为在顶端，人们被赋予的工作责任之重，一旦你搞砸了，没有人能纠正你。我给你描绘了一个正在工作到生病的人的形象，实际上，试图给每个人的工作打分。最终，他只能做这么多，他确实想回家睡觉。这意味着他想要他手下的人不仅优秀，而且值得信任，不需要被评分。最终，看守者必须看守自己。</p>
</blockquote>
 <a href="#fnref:R" rel="noopener noreferrer"><sup>[return]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 24337 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/programming-blogs/</link>
      <guid isPermaLink="false">https://danluu.com/programming-blogs/</guid>
      <pubDate>Mon, 18 Apr 2016 07:06:34 +0000</pubDate>
    </item>
    <item>
      <title>Google SRE 书</title>
      <description>[AI 摘要] 一位工程师对《Google SRE》一书的详细阅读笔记和个人思考。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 一位工程师对《Google SRE》一书的详细阅读笔记和个人思考。</div><p><a href="https://www.amazon.com/gp/product/149192912X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=149192912X&amp;linkId=2a578b357abc8b995368a039dd517601" rel="noopener noreferrer">这本书</a> 开篇讲述了一个故事：在阿波罗计划时代，玛格丽特·汉密尔顿曾带着她年幼的女儿来到 NASA。在一次模拟任务中，她的女儿按错了一些键，导致一个发射前程序在模拟任务期间意外运行，从而引起了任务崩溃。汉密尔顿提交了一份变更请求，希望添加错误检查代码以防止类似错误再次发生，但该请求被拒绝，因为这类错误情况理论上不应该发生。</p>

<p>然而，在接下来的阿波罗 8 号任务中，那个确切的错误状况发生了。这个本可以通过一个简单检查就能避免的潜在致命问题，让 NASA 的工程师们花费了 9 个小时才解决。</p>

<p><em>这听起来很熟悉——我已经记不清有多少开发者的事后分析报告有着同样的基本结构了。</em></p>

<p><em>这对我是一种笔记实验，体现在两个方面。首先，我通常用纸笔做笔记，然后扫描保存。其次，我通常不把笔记发布在网上，但 <a href="http://scattered-thoughts.net/" rel="noopener noreferrer">Jamie Brandon 的读书笔记</a> 启发了我尝试这样做。我的手写笔记是一系列要点，可能无法很好地转换成 Markdown。一个问题在于我的 Markdown 渲染器不支持超过一级的嵌套，因此结构会被人为扁平化。可能还有其他问题，让我们来看看是什么！如果还不明显，我的旁注都以斜体表示。</em></p>

<h3 id="chapter-1-introduction">第 1 章：简介</h3>

<p><em>本章的所有内容都在后面有更详细的阐述。</em></p>

<p>雇佣人员来管理系统稳定性的两种方法：</p>

<h4 id="traditional-approach-sysadmins">传统方法：系统管理员</h4>

<ul>
<li>组合现有组件并部署以产生服务</li>
<li>响应事件和更新</li>
<li>随着服务增长而扩大团队以吸收增加的工作量</li>
<li>优点

<ul>
<li>易于实施，因为它是标准做法</li>
<li>有大量人才可供招聘</li>
<li>有大量可用的软件</li>
</ul></li>
<li>缺点

<ul>
<li>变更管理和事件处理需要人工干预，导致团队规模随系统负载增长而增长</li>
<li>运维从根本上与开发对立，这可能导致对变更的病态抵制，从而引发开发者同样病态的回应，比如将“发布”重新归类为“增量更新”、“标志切换”等。</li>
</ul></li>
</ul>

<h4 id="google-s-approach-sres">Google 的方法：SRE</h4>

<ul>
<li>由软件工程师负责运维</li>
<li>候选人应能通过或接近通过正常的开发招聘标准，并可能具备开发者中罕见的额外技能（例如，L1-L3 网络或 UNIX 系统内部原理）。</li>
<li>职业发展与开发职业路径相当</li>
<li>结果

<ul>
<li>SRE 会觉得手动执行任务很无聊</li>
<li>具备自动化任务所需的技能</li>
<li>做与运维团队相同的工作，但用自动化代替人工劳动</li>
</ul></li>
<li>为避免因人工劳动陷阱导致团队规模随服务负载增长，Google 限制 SRE 的“运维”工作量不超过 50%</li>
<ul>
<li>这是上限。实际运维工作量预计要低得多</li>
</ul>
</ul>
<li>优点

<ul>
<li>扩展成本更低</li>
<li>规避了开发/运维的分裂</li>
</ul></li>
<li>缺点

<ul>
<li>招聘困难</li>
<li>可能非常规，需要管理层支持（例如，产品团队可能反对因错误预算耗尽而停止当季度发布的决定）</li>
</ul></li>


<p><em>我真的不明白这如何成为规避开发/运维分裂的例子。我能看到在某种程度上确实如此，但因错误预算用完而停止所有发布的例子，与“系统管理员”例子中团队抵制发布的情况似乎并无根本不同。看起来 SRE 拥有更多的政治资本，而且在给定的具体例子中，SRE 可能更合理，但没有理由认为系统管理员不能同样合理。</em></p>

<h4 id="tenets-of-sre">SRE 的信条</h4>

<ul>
<li>SRE 团队负责延迟、性能、效率、变更管理、监控、应急响应和容量规划</li>
</ul>

<h4 id="ensuring-a-durable-focus-on-engineering">确保持久的工程关注</h4>

<ul>
<li>50% 运维上限意味着额外的运维工作会被转给产品团队</li>
<li>这为产品团队提供了反馈机制，同时也降低了负载</li>
<li>目标每次 8-12 小时的值班轮次最多 2 个事件</li>
<li>所有严重事故都要进行事后分析，即使它们没有触发警报</li>
<li>无指责的事后分析</li>
</ul>

<p><em>每次轮班最多 2 个事件，但平均是多少？每周从 SRE 团队发送给开发团队的值班事件预期有多少？</em></p>

<p><em>如何从有指责的事后分析文化转变为无指责的文化？现在每个人都应该进行无指责的事后分析，<a href="//danluu.com/wat/" rel="noopener noreferrer">每个人都会声称他们这样做。有点像拥有良好的测试和部署实践</a>。我很幸运曾在值班轮换中从未收到过警报，但当我与最近加入并正在值班的人交谈时，他们讲述的故事并不那么美好，充满了指责、互相推诿和责任转嫁。每个人都应该保持无指责这一事实，似乎让指出指责行为变得更难，而不是更容易。</em></p>

<h4 id="move-fast-without-breaking-slo">在不破坏 SLO 的前提下快速行动</h4>

<ul>
<li>错误预算。对于几乎所有事情来说，100% 都是错误的目标</li>
<li>从 5 个 9 到 100% 的可靠性，对大多数用户来说差别不大，但需要巨大的努力</li>
<li>设定一个承认权衡并留有错误预算的目标</li>
<li>错误预算可以用于任何事情：启动功能等</li>
<li>错误预算允许讨论如何通过渐进式发布和 1% 的实验来保持可容忍的错误水平</li>
<li>SRE 团队的目标不是“零停机”——SRE 和产品开发者在激励上是一致的，即花掉错误预算以获得最大的功能迭代速度</li>
</ul>

<p><em>虽然没有明确说明，但对于需要“快速行动”的团队来说，持续远低于错误预算可能被解读为团队在可靠性上投入了过多精力的迹象。</em></p>

<p><em>我非常喜欢这个想法，但当我与 Jessica Kerr 讨论时，她对此表示反对，因为也许你只是因为运气好才低于错误预算，一个严重的事件就可能耗尽你未来十年的错误预算。后续问题是：你如何能对风险模型有足够的信心，以至于可以有意消耗错误预算以更快行动，而不用担心一个下游（时间上）的坏事件会使你超支？Nat Welch（前 Google SRE）对此回应说，你可以通过模拟灾难和其他测试来建立信心。</em></p>

<h4 id="monitoring">监控</h4>

<ul>
<li>监控永远不应需要人类去解读警报域的任何部分</li>
<li>三种有效的监控输出

<ul>
<li>警报：人类需要立即采取行动</li>
<li>工单：人类最终需要采取行动</li>
<li>日志：无需操作</li>
<li>注意，例如，图表是一种日志</li>
</ul></li>
</ul>

<h4 id="emergency-response">应急响应</h4>

<ul>
<li>可靠性是 MTTF（平均故障间隔时间）和 MTTR（平均恢复时间）的函数</li>
<li>对于评估响应，我们关心的是 MTTR</li>
<li>人类会增加延迟</li>
<li>不需要人类响应的系统将具有更高的可用性，因为 MTTR 更低</li>
<li>拥有“剧本”可将 MTTR 降低 3 倍

<ul>
<li>拥有能够响应所有事情的英雄通才行得通，但拥有剧本效果更好</li>
</ul></li>
</ul>

<p><em>我个人同意，但我们真的很喜欢我们的值班英雄。我想知道我们如何能培养文档化的文化。</em></p>

<h4 id="change-management">变更管理</h4>

<ul>
<li>70% 的停机是由线上系统变更引起的。缓解措施：

<ul>
<li>实施渐进式发布</li>
<li>监控</li>
<li>回滚</li>
</ul></li>
<li>将人类从循环中移除，避免在重复性任务中出现标准人为问题</li>
</ul>

<h4 id="demand-forecasting-and-capacity-planning-http-www-amazon-com-gp-product-0596518579-ref-as-li-tl-ie-utf8-camp-1789-creative-9325-creativeasin-0596518579-linkcode-as2-tag-abroaview-20-linkid-ek2pbwyshyk26giv">需求预测与<a href="http://www.amazon.com/gp/product/0596518579/ref=as_li_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0596518579&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=EK2PBWYSHYK26GIV" rel="noopener noreferrer">容量规划</a></h4>

<ul>
<li>直接明了，但令人惊讶的是，有大量团队/服务没有这样做</li>
</ul>

<h4 id="provisioning">配置</h4>

<ul>
<li>增加容量比负载转移风险更大，因为它通常涉及启动新实例/位置，对现有系统进行重大更改（配置文件、负载均衡器等）</li>
<li>成本高昂，应仅在必要时执行；且必须快速完成

<ul>
<li>如果你不清楚实际需求而过度配置，那会浪费金钱</li>
</ul></li>
</ul>

<h4 id="efficiency-and-performance">效率与性能</h4>

<ul>
<li>负载会减慢系统速度</li>
<li>SRE 配置以满足具有特定响应时间目标的容量目标</li>
<li>效率 == 金钱</li>
</ul>

<h3 id="chapter-2-the-production-environment-at-google-from-the-viewpoint-of-an-sre">第 2 章：从 SRE 视角看 Google 的生产环境</h3>

<p><em>本章没有笔记，因为我已经很熟悉了。待办事项：也许可以回去更仔细地阅读本章。</em></p>

<h3 id="chapter-3-embracing-risk">第 3 章：拥抱风险</h3>

<ul>
<li>例如：如果一个用户在智能手机上有 99% 的可靠性，他无法分辨 99.99% 和 99.999% 的可靠性</li>
</ul>

<h4 id="managing-risk">管理风险</h4>

<ul>
<li>可靠性与成本非线性。多获得一个可靠性增量可能要付出 100 倍的代价

<ul>
<li>与冗余设备相关的成本</li>
<li>构建可靠性功能而非“普通”功能的成本</li>
<li>目标：使系统足够可靠，但又不要太可靠！</li>
</ul></li>
</ul>

<h4 id="measuring-service-risk">衡量服务风险</h4>

<ul>
<li>标准实践：识别指标以代表要优化的系统属性</li>
<li>可能的指标 = 正常运行时间 / (正常运行时间 + 停机时间)

<ul>
<li>对于全球分布式服务来说有问题。正常运行时间到底是什么意思？</li>
</ul></li>
<li>总可用性 = 成功请求数 / 总请求数

<ul>
<li>显然，并非所有请求都相等，但总可用性是一阶近似的可接受指标</li>
</ul></li>
<li>通常设定季度目标</li>
</ul>

<h4 id="risk-tolerance-of-services">服务的风险承受能力</h4>

<ul>
<li>通常并非客观明显</li>
<li>SRE 与产品负责人合作，将业务目标转化为明确的目标</li>
</ul>

<h4 id="identifying-risk-tolerance-of-consumer-services">识别消费者服务的风险承受能力</h4>

<p><em>待办事项：也许可以在第二遍时详细阅读</em></p>

<h4 id="identifying-risk-tolerance-of-infrastructure-services">识别基础设施服务的风险承受能力</h4>

<h5 id="target-availability">目标可用性</h5>

<ul>
<li>运行中的示例：Bigtable

<ul>
<li>一些消费者服务直接从 Bigtable 提供数据——需要低延迟和高可靠性</li>
<li>一些团队将 Bigtable 用作离线分析的后端存储——更关心吞吐量而非可靠性</li>
</ul></li>
<li>通用性地满足所有需求成本过高

<ul>
<li>例如：Bigtable 实例</li>
<li>低延迟的 Bigtable 用户希望队列深度低</li>
<li>面向吞吐量的 Bigtable 用户希望队列深度中到高</li>
<li>在这两种情况下，成功和失败是截然相反的！</li>
</ul></li>
</ul>

<h5 id="cost">成本</h5>

<ul>
<li>划分基础设施并提供不同级别的服务</li>
<li>除了明显的好处外，它还允许服务将提供不同级别服务的成本外部化（例如，预期延迟导向的服务比吞吐量导向的服务更昂贵）</li>
</ul>

<h4 id="motivation-for-error-budgets">错误预算的动机</h4>

<p><em>本节没有笔记，因为我已经认同所有观点。如果参与相关辩论，也许可以回去重读。</em></p>

<h3 id="chapter-4-service-level-objectives">第 4 章：服务级别目标</h3>

<p><em>注意：跳过术语部分的笔记。</em></p>

<ul>
<li>示例：Chubby 计划内停机

<ul>
<li>Google 发现 Chubby 的性能一直超出其 SLO，而且全局 Chubby 停机会导致 Google 出现异常严重的停机</li>
<li>Chubby 太可靠了，以至于团队错误地假设它永远不会宕机，从而没有设计能处理 Chubby 故障的系统</li>
<li>解决方案：当 Chubby 在一个季度内远超其 SLO 时，就全局性地将其停机，以“告诉”团队 Chubby 可能宕机</li>
</ul></li>
</ul>

<h4 id="what-do-you-and-your-users-care-about">你和你的用户关心什么？</h4>

<ul>
<li>指标太多：难以关注</li>
<li>指标太少：可能忽略重要行为</li>
<li>不同类型的服务应有不同的指标

<ul>
<li>面向用户的服务：可用性、延迟、吞吐量</li>
<li>存储服务：延迟、可用性、持久性</li>
<li>大数据服务：吞吐量、端到端延迟</li>
</ul></li>
<li>所有系统都关心正确性</li>
</ul>

<h4 id="collecting-indicators">收集指标</h4>

<ul>
<li>通常可以从服务器自然收集，但有时需要客户端指标。</li>
</ul>

<h4 id="aggregation">聚合</h4>

<ul>
<li>使用分布而非平均值</li>
<li>用户研究表明，人们通常更喜欢较慢的平均速度但更好的尾部延迟</li>
<li>标准化常用定义，例如，1 分钟平均、集群任务平均等

<ul>
<li>可以有例外，但合理的默认值使事情更容易</li>
</ul></li>
</ul>

<h4 id="choosing-targets">选择目标</h4>

<ul>
<li>不要基于当前性能选择目标

<ul>
<li>当前性能可能需要英雄般的努力</li>
</ul></li>
<li>保持简单</li>
<li>避免绝对化

<ul>
<li>谈论“无限”规模或“永远”可用是不合理的</li>
</ul></li>
<li>最小化 SLO 数量</li>
<li>完美可以等待

<ul>
<li>总可以随时间重新定义 SLO</li>
</ul></li>
<li>SLO 设定预期

<ul>
<li>保留安全边际（内部 SLO 可以比外部 SLO 定义得更宽松）</li>
</ul></li>
<li>不要过度达成目标

<ul>
<li>参见上面的 Chubby 例子</li>
<li>另一个例子是确保系统在负载较低时不会过快</li>
</ul></li>
</ul>

<h3 id="chapter-5-eliminating-toil">第 5 章：消除苦差事</h3>

<p>Carla Geisser 说：“如果一个人类操作员在系统正常运行期间需要接触你的系统，那你就有 bug。正常运行的定义会随着系统的增长而变化。”</p>

<ul>
<li>定义：苦差事

<ul>
<li>不仅仅是“我不想做的工作”</li>
<li>手动的</li>
<li>重复的</li>
<li>可自动化的</li>
<li>战术性的</li>
<li>没有持久价值</li>
<li>与服务增长成 O(n) 关系</li>
</ul></li>
<li>调查发现平均 33% 是苦差事

<ul>
<li>数字低至 0% 高达 80%</li>
<li>苦差事 &gt; 50% 是管理者应更均衡地分配苦差事负荷的信号</li>
</ul></li>
<li>苦差事总是坏的吗？

<ul>
<li>可预测和重复的任务可以令人平静</li>
<li>可以产生成就感，可以是低风险/低压力的活动</li>
</ul></li>
</ul>

<p><em>关于苦差事为何有害的部分。跳过该部分的笔记。</em></p>

<h3 id="chapter-6-monitoring-distributed-systems">第 6 章：监控分布式系统</h3>

<ul>
<li>为什么要监控？

<ul>
<li>分析长期趋势</li>
<li>随时间比较或进行实验</li>
<li>警报</li>
<li>构建仪表板</li>
<li>调试</li>
</ul></li>
</ul>

<p><em>正如 Alex Clemmer 常说的，我们的问题不在于移动太慢，而在于构建了错误的东西。我想知道我们如何能从今天的状况发展到拥有足够的工具，以便在构建新系统时做出明智的决策。</em></p>

<h4 id="setting-reasonable-exceptions">设定合理的预期</h4>

<ul>
<li>监控并非易事</li>
<li>一个 10-12 人的 SRE 团队通常有 1-2 人负责构建和维护监控</li>
<li>随着工具/库/集中监控基础设施的改进，这个数字随时间下降</li>
<li>总体趋势是更简单/更快的监控系统，以及更好的事后分析工具</li>
<li>避免“神奇”系统</li>
<li>复杂的依赖层级（例如，“如果数据库慢，就报警数据库，否则报警网站”）效果有限。

<ul>
<li>主要（或仅）用于系统中非常稳定的部分</li>
</ul></li>
<li>为人类生成警报的规则应易于理解并代表明确的故障</li>
</ul>

<p><em>避免神奇系统是否也包括避免机器学习？</em></p>

<ul>
<li>大量白盒监控</li>
<li>一些关键部分的黑盒监控</li>
<li>四个黄金信号

<ul>
<li>延迟</li>
<li>流量</li>
<li>错误</li>
<li>饱和度</li>
</ul></li>
</ul>

<p><em>来自 Bigtable 和 Gmail 的有趣示例未被转录。关于保持警报简单的许多重要信息也未被转录。</em></p>

<h4 id="the-long-run">长期考量</h4>

<ul>
<li>长期和短期可用性之间经常存在张力</li>
<li>有时可以通过英雄般的努力修复不可靠的系统，但这是倦怠风险，也是故障风险</li>
<li>在短期可靠性上承受可控的影响通常是更好的权衡</li>
</ul>

<h3 id="chapter-7-evolution-of-automation-at-google">第 7 章：Google 自动化的发展</h3>

<ul>
<li>“自动化是力量倍增器，不是万能药”</li>
<li>自动化的价值

<ul>
<li>一致性</li>
<li>可扩展性</li>
<li>MTTR</li>
<li>更快的非修复操作</li>
<li>节省时间</li>
</ul></li>
</ul>

<p><em>笔记中跳过了多个有趣的案例研究和解释。</em></p>

<h3 id="chapter-8-release-engineering">第 8 章：发布工程</h3>

<ul>
<li>这是 Google 的一个特定工作职能</li>
</ul>

<h4 id="release-engineer-role">发布工程师角色</h4>

<ul>
<li>发布工程师与 SWE 和 SRE 合作，定义软件的发布方式

<ul>
<li>让开发团队专注于开发工作</li>
</ul></li>
<li>定义最佳实践

<ul>
<li>编译器标志、构建 ID 标签格式等</li>
</ul></li>
<li>发布自动化</li>
<li>团队间的模型各异

<ul>
<li>可能是“绿灯即推送”并部署每个构建</li>
<li>可能是每小时构建和部署</li>
<li>等等</li>
</ul></li>
<li>整体构建

<ul>
<li>构建相同的修订号应始终产生相同的结果</li>
<li>自包含——这包括对使用的所有内容进行版本控制，直到编译器</li>
<li>可以针对旧修订版进行选择性修复以修复生产软件</li>
</ul></li>
<li>几乎所有的变更都需要代码审查</li>
<li>分支

<ul>
<li>所有代码都在主分支中</li>
<li>发布版从主分支分出</li>
<li>修复可以从主分支到分支</li>
<li>分支永远不合并回主分支</li>
</ul></li>
<li>测试

<ul>
<li>CI</li>
<li>发布过程创建审计跟踪，运行测试并显示测试通过</li>
</ul></li>
<li>配置管理

<ul>
<li>看似简单，<a href="//danluu.com/postmortem-lessons/" rel="noopener noreferrer">可能导致不稳定</a></li>
</ul></li>
<li>许多可能的方案（都涉及将配置存储在源代码控制中并进行严格的配置审查）</li>
<li>使用主干进行配置——配置在头部维护并立即应用

<ul>
<li>最初用于 Borg（及 Borg 之前的系统）</li>
<li>二进制发布和配置变更解耦！</li>
</ul></li>
<li>将配置文件和二进制文件包含在同一包中

<ul>
<li>简单</li>
<li>紧密耦合二进制和配置——对于配置文件少或配置很少更改的项目来说可以</li>
</ul></li>
<li>将配置打包为“配置包”

<ul>
<li>与代码相同的整体构建原则</li>
</ul></li>
<li>发布工程不应是事后才考虑的！</li>
<li>应在开发周期开始时就预算资源</li>
</ul>


<h3 id="chapter-9-simplicity">第 9 章：简洁</h3>

<ul>
<li>稳定性 vs. 敏捷性

<ul>
<li>可以通过冻结使事情稳定——需要平衡两者</li>
<li>可靠的系统可以提高敏捷性</li>
<li>可靠的发布使更容易将变更与错误联系起来</li>
</ul></li>
<li>“无聊”的美德！</li>
<li><a href="https://en.wikipedia.org/wiki/No_Silver_Bullet" rel="noopener noreferrer">本质性 vs. 偶然性复杂性</a>

<ul>
<li>SRE 应在引入偶然性复杂性时提出反对</li>
</ul></li>
<li>代码是负担

<ul>
<li>删除死代码或其他膨胀代码</li>
</ul></li>
<li>最小化 API

<ul>
<li>较小的 API 更易于测试、更可靠</li>
</ul></li>
<li>模块化

<ul>
<li>API 版本控制</li>
<li>与代码相同，应避免像 misc/util 这样的类</li>
</ul></li>
<li>发布

<ul>
<li>小规模发布更容易度量</li>
<li>如果我们将 100 个变更一起发布，就无法判断发生了什么</li>
</ul></li>
</ul>

<h3 id="chapter-10-altering-from-time-series-data">第 10 章：从时间序列数据进行警报</h3>

<h4 id="borgmon">Borgmon</h4>

<ul>
<li>类似于 <a href="https://prometheus.io/" rel="noopener noreferrer">Prometheus</a></li>
<li>用于日志的通用数据格式</li>
<li>数据用于仪表板和警报</li>
<li>规范了一种遗留数据格式“varz”，允许通过 HTTP 查看指标

<ul>
<li>要手动查看指标，请访问 <a href="http://foo:80/varz" rel="noopener noreferrer">http://foo:80/varz</a></li>
</ul></li>
<li>添加指标只需在代码中进行一次声明

<ul>
<li>添加新指标的用户成本低</li>
</ul></li>
<li>Borgmon 定期从每个目标获取 /varz

<ul>
<li>还包括合成数据，如健康检查、名称是否解析等</li>
</ul></li>
<li>时间序列存储区

<ul>
<li>数据存储在内存中，并定期检查点写入磁盘</li>
<li>固定大小分配</li>
<li>GC 在空间满时驱逐最旧条目</li>
<li>概念上是一个二维数组，一个轴是时间，另一个轴是项目</li>
<li>每个数据点 24 字节 -&gt; 1M 个唯一时间序列，以 1 分钟间隔持续 12 小时 = 17 GB</li>
</ul></li>
<li>Borgmon 规则

<ul>
<li>代数表达式</li>
<li>从其他时间序列计算时间序列</li>
<li>规则在线程池上并行求值</li>
</ul></li>
<li>计数器 vs. 仪表

<ul>
<li>定义：计数器是非递减的</li>
<li>定义：可以取任何值</li>
<li>计数器优于仪表，因为仪表根据采样间隔可能丢失信息</li>
</ul></li>
<li>警报

<ul>
<li>Borgmon 规则可以触发警报</li>
<li>有最小持续时间以防止“抖动”</li>
<li>通常设置为两个持续时间周期，这样错过的收集不会触发警报</li>
</ul></li>
<li>扩展

<ul>
<li>Borgmon 可以从其他 Borgmon 接收时间序列数据（使用二进制流协议而非基于文本的 varz 协议）</li>
<li>可以有多个层级的过滤器</li>
</ul></li>
<li>探测器

<ul>
<li>监控用户所见的黑盒监控</li>
<li>可以通过 varz 查询，或直接向 Alertmanager 发送警报</li>
</ul></li>
<li>配置

<ul>
<li>规则定义和被监控目标之间分离</li>
</ul></li>
</ul>

<h3 id="chapter-11-being-on-call">第 11 章：值班</h3>

<ul>
<li>典型响应时间

<ul>
<li>面向用户或其他时间关键型任务：5 分钟</li>
<li>不太时间敏感的任务：30 分钟</li>
</ul></li>
<li>响应时间与 SLO 相关

<ul>
<li>例如：一个季度 99.99% 相当于 13 分钟停机；显然响应时间不能超过 13 分钟</li>
<li>SLO 较宽松的服务可以有 10 多分钟（或更长？）的响应时间</li>
</ul></li>
<li>主值班 vs. 副值班

<ul>
<li>工作分配因团队而异</li>
<li>在某些团队，副值班是主值班的后备</li>
<li>在其他团队，副值班处理非紧急/非分页事件，主值班处理分页</li>
</ul></li>
<li>平衡值班

<ul>
<li>定义：数量：值班时间占总时间的百分比</li>
<li>定义：质量：值班期间发生的事件数量</li>
</ul></li>
</ul>

<p><em>这很棒。我们应该这样做。人们有时会连续几次遇到非常糟糕的值班轮换，考虑到值班轮换的低频性，没有理由期望它在一两年内随机平衡。</em></p>

<ul>
<li>数量平衡

<ul>
<li>&gt;= 50% 的 SRE 时间用于工程</li>
<li>在其余时间中，最多 25% 用于值班</li>
</ul></li>
<li>偏好多站点团队

<ul>
<li>夜班对健康有害，多站点团队可以消除夜班</li>
</ul></li>
<li>质量平衡

<ul>
<li>平均而言，处理一次事件（包括根本原因分析、补救、撰写事后分析、修复错误等）需要 6 小时。</li>
<li>=&gt; 在 12 小时的值班轮次中不应有超过 2 个事件</li>
<li>为保持在上限之内，希望分页分布非常平，中位数为 0</li>
</ul></li>
<li>补偿——值班的额外报酬（休假或现金）</li>
</ul>

<h3 id="chapter-12-effective-troubleshooting">第 12 章：有效的故障排除</h3>

<p><em>本章没有笔记。</em></p>

<h3 id="chapter-13-emergency-response">第 13 章：应急响应</h3>

<ul>
<li>测试引发的紧急情况

<ul>
<li><a href="http://queue.acm.org/detail.cfm?id=2371516" rel="noopener noreferrer">SRE 打破系统以观察会发生什么</a></li>
</ul></li>
<li>示例：希望找出对分布式 MySQL 数据库的隐藏依赖

<ul>
<li>计划：阻止访问 1/100 的数据库</li>
<li>响应：依赖服务报告无法访问关键系统</li>
<li>SRE 响应：SRE 中止演练，尝试回滚权限更改</li>
<li>回滚尝试失败</li>
<li>尝试恢复对副本的访问成功</li>
<li>在 1 小时内恢复正常运行</li>
<li>做得好的地方：依赖团队立即上报问题，能够恢复访问</li>
<li>我们学到的：对系统及其与其他系统的交互理解不足，未遵循事故响应流程通知客户停机，未在测试环境中测试回滚程序</li>
</ul></li>
<li>变更引发的紧急情况

<ul>
<li>变更可能导致故障！</li>
</ul></li>
<li>示例：周五推送的滥用预防基础设施配置更改触发了崩溃循环 bug

<ul>
<li>几乎所有面向外部的系统都依赖于此，变得不可用</li>
<li>许多内部系统也有依赖关系，变得不可用</li>
<li>警报在几秒钟内开始响起</li>
<li>配置推送后 5 分钟内，推送变更的工程师回滚了变更，服务开始恢复</li>
<li>做得好的地方：监控立即触发，事件管理运作良好，即使许多系统宕机，带外通信系统也让人们了解情况，运气好（推送变更的工程师正在关注实时通信渠道，而这不属于发布流程）</li>
<li>我们学到的：推送到金丝雀环境没有触发相同问题，因为它没有遇到特定的配置关键字组合；推送被认为是低风险的，经历了不太严格的金丝雀流程；停机期间警报过于嘈杂</li>
</ul></li>
<li>流程引发的紧急情况</li>
</ul>

<p><em>流程引发的例子没有笔记。</em></p>

<h3 id="chapter-14-managing-incidents">第 14 章：管理事故</h3>

<p><em>这个领域我们似乎做得确实不错。本章没有笔记。</em></p>

<h3 id="chapter-15-postmortem-culture-learning-from-failure">第 15 章：事后分析文化：从失败中学习</h3>

<p><em><a href="//danluu.com/postmortem-lessons/" rel="noopener noreferrer">我强烈认同本章的大部分内容</a>。没有笔记。</em></p>

<h3 id="chapter-16-tracking-outages">第 16 章：跟踪停机</h3>

<ul>
<li>Escalator：集中系统，跟踪对警报的确认（ACK），必要时通知其他人等。</li>
<li>Outalator：提供多个队列的通知时间交错视图

<ul>
<li>还保存相关邮件，并允许将一些消息标记为“重要”，可以折叠非重要消息等</li>
</ul></li>
</ul>

<p><em>我们版本的 Escalator 看起来不错。不过，我们确实可以使用类似 Outalator 的东西。</em></p>

<h3 id="chapter-17-testing-for-reliability">第 17 章：为可靠性而测试</h3>

<p><em>老生常谈。本节没有笔记。不过，我们确实可以做得更好。</em></p>

<h3 id="chapter-18-software-engineering-in-sre">第 18 章：SRE 中的软件工程</h3>

<ul>
<li>示例：Auxon，容量规划自动化工具</li>
<li>背景：传统容量规划周期

<ul>
<li>1) 收集需求预测（提前数个季度到数年）</li>
<li>2) 规划分配</li>
<li>3) 审查计划</li>
<li>4) 部署和配置资源</li>
</ul></li>
<li>传统方法缺点

<ul>
<li>许多事情可能影响计划：效率提高、采用率提高、集群交付日期推迟等</li>
<li>即使是小的更改也需要重新检查分配计划</li>
<li>大的更改可能需要完全重写计划</li>
<li>劳动密集且容易出错</li>
</ul></li>
<li>Google 解决方案：基于意图的容量规划

<ul>
<li>指定需求，而非实现</li>
<li>将需求编码并自动生成容量计划</li>
<li>除了节省人力，求解器可以做出优于人类生成的解决方案 =&gt; 节省成本</li>
</ul></li>
<li>示例阶梯，展示越来越多基于意图的规划

<ul>
<li>1) 希望在集群 X、Y 和 Z 中有 50 个核心——为什么是那些资源在那些集群中？</li>
<li>2) 希望在区域中的任何 3 个集群中拥有 50 核的规模——为什么需要那么多资源，为什么是 3 个？</li>
<li>3) 希望以 N+2 冗余满足需求——为什么是 N+2？</li>
<li>4) 希望达到 5 个 9 的可靠性。可能会发现 N+2 不够</li>
</ul></li>
<li>发现最大的收益来自转向 (3)

<ul>
<li>一些复杂的服务可能追求 (4)</li>
</ul></li>
<li>将约束放入工具允许跨集群一致地进行权衡

<ul>
<li>而不是做出单独的临时决策</li>
</ul></li>
<li>Auxon 输入

<ul>
<li>需求（例如，“服务必须在每个大陆 N+2 冗余”，“前端服务器距后端服务器不超过 50ms”）</li>
<li>依赖关系</li>
<li>预算优先级</li>
<li>性能数据（服务如何扩展）</li>
<li>需求预测数据（注意，像 Colossus 这样的服务从依赖服务派生出预测）</li>
<li>资源供应和定价</li>
</ul></li>
<li>输入进入求解器（混合整数或线性规划求解器）</li>
</ul>

<p><em>关于为什么是 SRE 软件、如何启动团队等没有笔记。待办事项：如果将来直接相关，重读本章后半部分并做笔记。</em></p>

<h3 id="chapter-19-load-balancing-at-the-frontend">第 19 章：前端负载均衡</h3>

<p><em>本节没有笔记。在高层目标上似乎与我们的相似，本章也没有深入细节。值得注意的是，他们在[已编辑]方面与我们不同。有关低级细节的更多信息，请参见 <a href="https://www.usenix.org/system/files/conference/nsdi16/nsdi16-paper-eisenbud.pdf" rel="noopener noreferrer">Maglev 论文</a>。</em></p>

<h3 id="chapter-20-load-balancing-in-the-datacenter">第 20 章：数据中心内的负载均衡</h3>

<ul>
<li>流量控制</li>
<li>需要避免不健康的任务</li>
<li>不健康任务的简单流量控制

<ul>
<li>跟踪到后端的请求数量</li>
<li>当达到阈值时将后端视为不健康</li>
<li>缺点：通常很糟糕</li>
</ul></li>
<li>基于健康的流量控制

<ul>
<li>后端任务可以处于三种状态之一：{健康、拒绝连接、跛鸭}</li>
<li>跛鸭状态仍可接收连接，但会向所有客户端发送背压请求</li>
<li>跛鸭状态简化了干净关闭</li>
</ul></li>
<li>定义：子集划分：限制客户端任务可以与之交互的后端任务池</li>
<li>客户端在 RPC 系统中维护到后端的连接池

<ul>
<li>使用池可以减少延迟，无需在需要时进行建立/拆除</li>
<li>非活动连接相对便宜，但并非免费，即使在“非活动”模式下（减少健康检查、用 UDP 代替 TCP 等）</li>
</ul></li>
<li>选择正确的子集

<ul>
<li>通常：20-100，根据工作负载选择</li>
</ul></li>
<li>子集选择：随机

<ul>
<li>利用率差</li>
</ul></li>
<li>子集选择：轮询

<ul>
<li>顺序被打乱；每轮有自己的排列</li>
</ul></li>
<li>负载均衡

<ul>
<li>子集选择用于连接均衡，但我们仍然需要均衡负载</li>
</ul></li>
<li>负载均衡：轮询

<ul>
<li>实践中，观察到最负载和最轻负载之间存在 2 倍差异</li>
<li>实践中，最昂贵的请求可能比最便宜的请求贵 1000 倍</li>
<li>此外，请求存在随机不可预测的变化</li>
</ul></li>
<li>负载均衡：最小负载轮询

<ul>
<li>顾名思义：在最小负载的后端中轮询</li>
<li>负载似乎是按连接数衡量的；可能不总是最佳指标</li>
<li>这是每个客户端的，而非全局的，因此可能将请求发送给来自其他客户端的许多请求的后端</li>
<li>实践中，对于大型服务，发现最负载的任务使用的 CPU 是最轻负载任务的两倍；类似于普通轮询</li>
</ul></li>
<li>负载均衡：加权轮询

<ul>
<li>与上述相同，但用其他因素加权</li>
<li>实践中，比最小负载轮询的负载分布好得多</li>
</ul></li>
</ul>

<p><em>我想知道 Heroku 在回复 <a href="http://genius.com/James-somers-herokus-ugly-secret-annotated" rel="noopener noreferrer">Rap Genius</a> 时所说的“经过广泛的研究和实验，我们尚未找到任何理论模型或实际实现，能在支持多个并发连接的 Web 后端上超越简单路由到随机路由的简单性和鲁棒性”是什么意思。</em></p>

<h3 id="chapter-21-handling-overload">第 21 章：处理过载</h3>

<ul>
<li>即使有“好的”负载均衡，系统也会过载</li>
<li>典型策略是提供降级响应，但在极高负载下可能不可行</li>
<li>将容量建模为 QPS 或请求函数（例如，请求读取的键数）容易出错

<ul>
<li>这些通常变化缓慢，但可能因单次签入而快速变化</li>
</ul></li>
<li>更好的解决方案：直接测量可用资源</li>
<li>CPU 利用率<em>通常</em>是配置的好信号

<ul>
<li>使用 GC 时，内存压力会转化为 CPU 利用率</li>
<li>对于其他系统，可以配置其他资源，使 CPU 成为可能的限制因素</li>
<li>在 CPU 过度配置过于昂贵的情况下，需考虑其他资源</li>
</ul></li>
</ul>

<p><em>像那样普遍过度配置 CPU 有多少成本？</em></p>

<ul>
<li>客户端节流

<ul>
<li>当客户达到配额时，后端开始拒绝请求</li>
<li>即使被拒绝，请求仍然消耗资源——如果没有节流，后端的大部分资源可能花在拒绝请求上</li>
</ul></li>
<li>关键性

<ul>
<li>似乎是优先级但名字不同？</li>
<li>RPC 系统中的一等概念</li>
<li>客户端节流为每个关键性级别保持单独的统计信息</li>
<li>默认情况下，关键性会通过后续 RPC 传播</li>
</ul></li>
<li>处理过载错误

<ul>
<li>如果 DC 过载，将负载卸载到其他 DC</li>
<li>如果 DC 正常但某些后端过载，将负载卸载到其他后端</li>
</ul></li>
<li>客户端在收到过载响应时重试

<ul>
<li>每个请求的重试预算 (3)</li>
<li>每个客户端的重试预算 (10%)</li>
<li>客户端重试失败会导致“过载；勿重试”响应返回给上游</li>
</ul></li>
</ul>

<p><em>拥有“勿重试”响应是“显而易见”的，但在实践中相对罕见。许多真实系统存在失败重试导致更多重试在堆栈中向上传播的问题。这在跨越硬件/软件边界时尤其明显（例如，文件系统读取导致 DVD/SSD/机械硬盘上的许多重试，失败，然后在文件系统级别重试），但在纯软件中似乎也普遍存在。</em></p>

<h3 id="chapter-22-addressing-cascading-failures">第 22 章：应对级联故障</h3>

<ul>
<li>典型的故障场景？

<ul>
<li>服务器过载</li>
</ul></li>
<li>示例：有两个服务器

<ul>
<li>一个过载，故障</li>
<li>另一个现在接收所有流量，也故障了</li>
</ul></li>
<li>资源耗尽

<ul>
<li>CPU/内存/线程/文件描述符等</li>
</ul></li>
<li>示例：资源间的依赖

<ul>
<li>1) Java 前端有调优不佳的 GC 参数</li>
<li>2) 前端因 GC 用尽 CPU</li>
<li>3) CPU 耗尽减慢请求</li>
<li>4) 增加的队列深度使用更多 RAM</li>
<li>5) 整个前端的固定内存分配意味着可用于缓存的内存减少</li>
<li>6) 命中率降低</li>
<li>7) 更多请求进入后端</li>
<li>8) 后端耗尽 CPU 或线程</li>
<li>9) 健康检查失败，开始级联故障</li>
<li>在停机期间难以确定原因</li>
</ul></li>
<li>注意：避免服务错误服务器的策略可能使情况恶化

<ul>
<li>可用后端减少，它们接收过多请求，然后变得不可用</li>
</ul></li>
<li>防止服务器过载

<ul>
<li>负载测试！必须有真实的环境</li>
<li>提供降级结果</li>
<li>过载时廉价且提前失败</li>
<li>让更高级别的系统拒绝请求（在反向代理、负载均衡器和任务级别）</li>
<li>执行容量规划</li>
</ul></li>
<li>队列管理

<ul>
<li>队列在稳定状态下无用</li>
<li>排队的请求消耗内存并增加延迟</li>
<li>如果流量大致稳定，最好保持较小的队列大小（例如，线程池大小的 50% 或更少）</li>
<li>例如：Gmail 使用无队列服务器，在线程满时故障转移</li>
<li>对于突发性工作负载，队列大小应是线程数、每个请求时间、突发大小/频率的函数</li>
<li>另见，<a href="http://queue.acm.org/detail.cfm?id=2839461" rel="noopener noreferrer">自适应 LIFO 和 CoDel</a></li>
</ul></li>
<li>优雅降级

<ul>
<li>注意测试优雅降级路径很重要，也许可以通过定期让一组小服务器接近过载来测试，因为这条路径在正常情况下很少被触发</li>
<li>最好保持简单且易于理解</li>
</ul></li>
<li>重试

<ul>
<li>始终使用随机指数退避</li>
<li>参见上一章，仅在单一级别重试</li>
<li>考虑设置服务器范围的重试预算</li>
</ul></li>
<li>截止时间

<ul>
<li>不要在截止时间已过时执行工作（级联故障的常见主题）</li>
<li>在每个阶段，检查截止时间是否已过</li>
<li>截止时间应被传播（例如，即使通过 RPC）</li>
</ul></li>
<li>双模延迟

<ul>
<li>示例：长截止时间的问题</li>
<li>假设前端有 10 个服务器，每个有 100 个线程（总共 1k 线程）</li>
<li>正常操作：1k QPS，请求耗时 100ms =&gt; 100 个工作线程被占用 (1k QPS * .1s)</li>
<li>假设 5% 的操作未完成，且有 100s 的截止时间</li>
<li>这消耗了 5k 线程 (50 QPS * 100s)</li>
<li>前端超订阅 5 倍。成功率 = 1k / (5k + 95) = 19.6% =&gt; 80.4% 错误率</li>
</ul></li>
</ul>

<p><em>使用截止时间而非超时很棒。我们真的应该对此更系统化。</em></p>

<p><em>通过设置合理的截止时间来防止系统充满无用的僵尸请求是“显而易见”的，但许多真实系统似乎具有任意超时值（30s、60s、100s 等），而不是根据负载/级联故障分配的截止时间。</em></p>

<ul>
<li>尽量避免层间通信

<ul>
<li>更简单，避免了可能的级联故障路径</li>
</ul></li>
<li>测试级联故障

<ul>
<li>对组件进行负载测试！</li>
<li>负载测试不仅揭示故障点，还能找出在负载下会完全崩溃的组件</li>
<li>确保单独测试每个组件</li>
<li>测试非关键后端（例如，确保搜索的拼写建议不妨碍关键路径）</li>
</ul></li>
<li>应对级联故障的即时步骤

<ul>
<li>增加资源</li>
<li>暂时停止健康检查失败/宕机</li>
<li>重启服务器（仅在会有所帮助时——例如，GC 死亡螺旋或死锁）</li>
<li>丢弃流量——极端措施，最后手段</li>
<li>进入降级模式——需要事先在服务中构建此功能</li>
<li>消除批处理负载</li>
<li>消除不良流量</li>
</ul></li>
</ul>

<h3 id="chapter-23-distributed-consensus-for-reliability">第 23 章：用于可靠性的分布式共识</h3>

<ul>
<li>我们如何就以下问题达成一致…

<ul>
<li>哪个进程是一组进程的领导者？</li>
<li>一组中的进程集合是什么？</li>
<li>一条消息是否已成功提交到分布式队列？</li>
<li>一个进程是否持有特定的租约？</li>
<li>数据存储中特定键的值是什么？</li>
</ul></li>
<li>示例 1：脑裂

<ul>
<li>服务在不同机架中有复制的文件服务器</li>
<li>必须避免同时写入一组中的两个文件服务器以避免数据损坏</li>
<li>每对文件服务器有一个领导者和一个跟随者</li>
<li>服务器通过心跳相互监控</li>
<li>如果一个服务器无法联系另一个，它会发送 STONITH（将另一个节点一枪爆头）</li>
<li>但如果网络慢或数据包丢失会怎样？</li>
<li>如果两个服务器都发出 STONITH 会怎样？</li>
</ul></li>
</ul>

<p><em>这让我想起我最喜欢的分布式数据库事后分析报告之一。数据库配置为一个环，其中每个节点与其“邻域”中的 5 台服务器通信并复制数据。如果邻域中的一些机器宕机，其他服务器会加入邻域，数据会相应复制。</em></p>

<p><em>听起来不错，但在服务器出现故障并认为不存在数据且其所有邻居都坏了的情况下，它返回结果的速度比任何邻居都快，同时告诉邻居它们都坏了。由于坏服务器没有数据，它非常快，并且可以比邻居更快地报告邻居坏了。哎呀！</em></p>

<ul>
<li>示例 2：需要人工干预的故障转移

<ul>
<li>一个高度分片的数据库有一个每个分片的主库，复制到另一个数据中心的备用库</li>
<li>外部健康检查决定主库是否应该故障转移到备用库</li>
<li>如果主库看不到备用库，它会使自己不可用以避免“示例 1”中的问题</li>
<li>这增加了运维负担</li>
<li>问题相关联，并且在人们忙于其他问题时相对可能遇到问题</li>
<li>如果有网络问题，没有理由认为人类会比系统中的机器对世界状态有更好的看法</li>
</ul></li>
<li>示例 3：错误的组成员算法

<ul>
<li><em>顾名思义。本节没有笔记</em></li>
</ul></li>
<li>不可能性结果

<ul>
<li><a href="https://en.wikipedia.org/wiki/CAP_theorem" rel="noopener noreferrer">CAP</a>：P 在真实网络中不可能，因此选择 C 或 A</li>
<li><a href="http://the-paper-trail.org/blog/a-brief-tour-of-flp-impossibility/" rel="noopener noreferrer">FLP</a>：异步分布式共识无法在不可靠网络中保证进展</li>
</ul></li>
</ul>

<h4 id="paxos-http-research-microsoft-com-en-us-um-people-lamport-pubs-paxos-simple-pdf"><a href="http://research.microsoft.com/en-us/um/people/lamport/pubs/paxos-simple.pdf" rel="noopener noreferrer">Paxos</a></h4>

<ul>
<li>提议序列，可能被多数进程接受也可能不被接受

<ul>
<li>不被接受 =&gt; 失败</li>
<li>每个提议的序列号，在整个系统中必须唯一</li>
</ul></li>
<li>提议

<ul>
<li>提议者向接受者发送序列号</li>
<li>如果接受者尚未看到更高的序列号，则同意</li>
<li>提议者可以使用更高的序列号再次尝试</li>
<li>如果提议者收到多数同意，它通过发送带有值的提交消息进行提交</li>
<li>接受者在同意时必须将数据记录到持久存储</li>
</ul></li>
</ul>

<h4 id="patterns">模式</h4>

<ul>
<li>分布式共识算法是低级原语</li>
<li>可靠的复制状态机

<ul>
<li>数据配置/存储、锁、领导者选举等的基本构建块</li>
<li>参见这些论文：<a href="https://www.cs.cornell.edu/fbs/publications/SMSurvey.pdf" rel="noopener noreferrer">Schnieder</a>, <a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.174.8238&amp;rep=rep1&amp;type=pdf" rel="noopener noreferrer">Aguilera</a>, <a href="http://www.cnds.jhu.edu/pub/papers/psb_ladis_08.pdf" rel="noopener noreferrer">Amir &amp; Kirsch</a></li>
</ul></li>
<li>可靠的复制数据和配置存储

<ul>
<li>非基于分布式共识的系统通常使用时间戳：有问题，因为无法保证时钟同步</li>
<li>参见 <a href="http://research.google.com/archive/spanner.html" rel="noopener noreferrer">Spanner 论文</a> 了解使用分布式共识的示例</li>
</ul></li>
<li>领导者选举

<ul>
<li>等同于分布式共识</li>
<li>领导者的工作可以由一个进程执行或分片，领导者选举模式允许像编写简单程序一样编写分布式系统</li>
<li>例如，GFS 和 Colussus 使用此模式</li>
</ul></li>
<li>分布式协调和锁服务

<ul>
<li>屏障用于 MapReduce 中，以确保 Map 完成后 Reduce 才继续</li>
</ul></li>
<li>分布式队列和消息传递

<ul>
<li>队列：可以容忍工作节点故障，但系统需要确保已领取的任务被处理</li>
<li>可以使用租约而非从队列中移除</li>
<li>使用 RSM 意味着即使队列宕机，系统也可以继续处理</li>
</ul></li>
<li>性能

<ul>
<li>传统观点认为共识算法不能用于高吞吐量低延迟系统是错误的</li>
<li>分布式共识是 Google 许多系统的核心</li>
<li>规模使这对 Google 比大多数其他公司更糟，但它仍然有效</li>
</ul></li>
<li>Multi-Paxos

<ul>
<li>强领导者进程：除非尚未选出领导者或发生故障，否则只需一轮往返即可达成共识</li>
<li>注意，组中的另一个进程可以随时提议</li>
<li>可能会来回振荡并产生伪活锁</li>
<li>不仅限于 Multi-Paxos</li>
<li>标准解决方案是选举一个提议者进程或使用轮换提议者</li>
</ul></li>
<li>扩展读密集型工作负载

<ul>
<li>示例：Photon 允许从任何副本读取</li>
<li>从陈旧副本读取需要额外工作，但不会产生错误的不正确结果</li>
<li>为保证读取是最新的，执行以下操作之一：</li>
<li>1) 执行只读共识操作</li>
<li>2) 从保证最新的副本读取数据（稳定领导者可以提供此保证）</li>
<li>3) 使用仲裁租约</li>
</ul></li>
<li><a href="http://www.pdl.cmu.edu/PDL-FTP/associated/CMU-PDL-14-105_abs.shtml" rel="noopener noreferrer">仲裁租约</a>

<ul>
<li>副本可以被授予对系统中某些（或全部）数据的租约</li>
</ul></li>
<li>Fast Paxos

<ul>
<li>旨在广域网上更快</li>
<li>每个客户端可以直接向一组接受者成员发送 <code>Propose</code>，而不是通过领导者</li>
<li><a href="http://www.sysnet.ucsd.edu/sysnet/miscpapers/hotdep07.pdf" rel="noopener noreferrer">不一定比经典 Paxos 更快</a>——如果到接受者的 RTT 较长，我们就是将一条通过慢速链路的消息加上 N 条通过快速链路的并行消息，替换为 N 条通过慢速链路的消息</li>
</ul></li>
<li>稳定领导者

<ul>
<li>“几乎所有设计时考虑性能的分布式共识系统都使用单个稳定领导者模式或轮换领导系统”</li>
</ul></li>
</ul>

<p><em>待办事项：完成本章？</em></p>

<h3 id="chapter-24-distributed-cron">第 24 章：分布式 cron</h3>

<p><em>待办事项：回去更详细地阅读，做笔记。</em></p>

<h3 id="chapter-25-data-processing-pipelines">第 25 章：数据处理管道</h3>

<ul>
<li>示例：MapReduce 或 Flume</li>
<li>方便且易于理解正常情况，但很脆弱

<ul>
<li>初始安装通常没问题，因为工作大小、分块、参数都经过仔细调整</li>
<li>随着时间的推移，负载发生变化，导致问题</li>
</ul></li>
</ul>

<h3 id="chapter-26-data-integrity">第 26 章：数据完整性</h3>

<ul>
<li>定义未必明显

<ul>
<li>如果接口 bug 导致 Gmail 无法显示邮件，从用户的角度来看，这与数据丢失是一样的</li>
<li>99.99% 的正常运行时间意味着每年 1 小时停机。对大多数应用来说可能没问题</li>
<li>2GB 文件中 99.99% 的字节正确意味着 200K 字节损坏。对大多数应用来说可能不行</li>
</ul></li>
<li>备份并非易事

<ul>
<li>可能混合了事务性和非事务性备份和恢复</li>
<li>业务逻辑的不同版本可能同时在线运行</li>
<li>如果服务是独立版本控制的，可能有很多版本组合</li>
<li>副本是不够的——副本可能同步损坏</li>
</ul></li>
<li>对 Google 19 次数据恢复工作的研究

<ul>
<li>最常见的用户可见数据丢失是由软件错误导致的删除或引用完整性丢失引起</li>
<li>最困难的情况是几周甚至几个月后才发现的低级损坏</li>
</ul></li>
</ul>

<h4 id="defense-in-depth">纵深防御</h4>

<ul>
<li>第一层：软删除

<ul>
<li>用户应该能够删除他们的数据</li>
<li>但这也意味着用户将能够意外删除他们的数据</li>
<li>还有，账户被入侵等</li>
<li>错误也可能由 bug 导致意外删除</li>
<li>软删除会延迟实际删除一段时间</li>
</ul></li>
<li>第二层：备份

<ul>
<li>需要弄清楚在恢复期间可以丢失多少数据、恢复需要多长时间以及备份需要回溯多远</li>
<li>希望备份永远回溯，因为损坏可能数月（甚至更长）未被发现</li>
<li>但代码和模式的更改可能使旧备份的恢复成本高昂</li>
<li>Google 通常有 30 到 90 天的窗口，具体取决于服务</li>
</ul></li>
<li>第三层：早期检测

<ul>
<li>带外完整性检查</li>
<li>很难正确做到这一点！</li>
<li>正确的更改可能导致检查器失败</li>
<li>但放宽检查可能导致故障被遗漏</li>
</ul></li>
</ul>

<p><em>两个有趣的案例研究没有笔记。</em></p>

<h3 id="chapter-27-reliable-product-launches-at-scale">第 27 章：大规模可靠产品发布</h3>

<p><em>本章没有特别的笔记。许多材料已被其他章节涵盖或至少暗示。但在考虑发布策略之前，至少值得看看示例检查表项目和行动项。另见附录 E，发布协调检查表。</em></p>

<h3 id="chapters-28-32-various-chapters-on-management">第 28-32 章：各种管理章节</h3>

<p><em>没有这些章节的笔记。</em></p>

<h3 id="notes-on-the-notes">笔记的笔记</h3>

<p><em>我非常喜欢这本书。如果你关心构建可靠系统，通读这本书并看看你周围的团队没有做什么，似乎是一个很好的练习。话虽如此，这本书并不完美。对我来说，两个主要的缺点源于同一个问题：这是一本由不同作者撰写的章节合集。有些编辑比其他编辑更好，这意味着有些章节更清晰，而且由于这些章节似乎设计为可以作为独立章节阅读，如果你从头到尾通读，书中有相当多的重复。根据你计划如何使用这本书，这可能是积极的，但对我来说是消极的。但即使考虑到缺点，我也要说这是过去一年中我读过的最有价值的技术书，我只在这些笔记中涵盖了大约 20% 的内容。如果你真的喜欢这些笔记，你可能想阅读<a href="https://www.amazon.com/gp/product/149192912X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=149192912X&amp;linkId=2a578b357abc8b995368a039dd517601" rel="noopener noreferrer">完整的书</a>。</em></p>

<p><em>如果你觉得这些笔记太枯燥，也许可以试试<a href="http://nostalgebraist.tumblr.com/post/142489665564/brazenautomaton-nostalgebraist-the-book-bad" rel="noopener noreferrer">这组关于一本完全不同书籍的、更有趣的笔记</a>。如果你觉得这些笔记只是略微枯燥，也许可以试试<a href="http://danluu.com/postmortem-lessons/" rel="noopener noreferrer">这组关于事后分析中常见错误类型的笔记</a>。无论如何，<a href="https://twitter.com/danluu" rel="noopener noreferrer">我欢迎对这些笔记的反馈</a>。对我来说，整理笔记是一种尝试。如果人们觉得这些有用，我会尝试更频繁地整理我阅读的书籍的笔记。如果没有，我可能会尝试不同的笔记方法或其他类型的帖子。</em></p><p><em>由 mimo-v2.5 模型翻译，花费 29090 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/google-sre-book/</link>
      <guid isPermaLink="false">https://danluu.com/google-sre-book/</guid>
      <pubDate>Mon, 11 Apr 2016 08:00:58 +0000</pubDate>
    </item>
    <item>
      <title>我们只招最潮的人</title>
      <description>[AI 摘要] 本文批评科技公司过度青睐来自“潮”公司的应聘者，忽视了有扎实能力但背景非主流的人才，导致招聘效率低下且成本高昂。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文批评科技公司过度青睐来自“潮”公司的应聘者，忽视了有扎实能力但背景非主流的人才，导致招聘效率低下且成本高昂。</div><p>我的一个熟人，我们叫他Mike吧，最近被微软的一个合同岗位裁员了，这事儿在我认识的不少人身上都发生过。和我一样，Mike有11年行业经验。但和我不一样的是，他不太认识那些潮公司的很多人，所以我把他的简历转给了我在一些急缺人手的公司工作的工程师朋友们。我的工程师朋友们觉得Mike的简历挺好的，但大多数招聘人员在简历筛选阶段就拒绝了他。</p>

<p>当我问起他被拒绝的原因时，得到的典型回复是：</p>

<ol>
<li>技术经验涉及不相关的技术</li>
<li>“经验太杂，做过支付、移动、数据分析和用户体验。”</li>
<li>合同工通常技术不是最强的</li>
</ol>

<p>这是一个招聘人员通过一位工程师转述给我的回复；那位工程师对招聘人员的回复感到难以置信。我们给这家公司起个名字，叫TrendCo（潮流公司）。它是成千上万家声称拥有世界级工程师、只招最优秀人才的公司之一。这是一家特定的公司，但它代表了一大类公司以及Mike所得到的那些回复。</p>

<p>总之，（1）是“Mike是.NET开发者，而我们不喜欢有Windows经验的人”的暗语。</p>

<p>我熟悉TrendCo的技术栈，多位员工告诉我那是“一团糟”。他们的核心系统峰值QPS不到1千，这曾导致他们在负载下宕机。Mike曾处理过能应对高出几个数量级负载的系统，但他的经验显然被视作“不相关”。</p>

<p>（2）让人难以理解。我在TrendCo面试过，他们的卖点之一就是作为一个初创公司，你可以做很多不同的事情。TrendCo几乎只招通才，但Mike对他们来说似乎“通才过头了”。</p>

<p>（3），结合（1），揭示了TrendCo对Mike的真正不满。他不是他们想要的类型。TrendCo的典型员工是来自大约五所“顶尖”学校的应届毕业生，经验在0到2年之间。他们招了一些有经验的人，但不多，而且大多数有经验的员工简历上都有些潮的东西，而不是像微软这样的老派公司。</p>

<p>无论你是否认为“设定一种类型并拒绝不符合类型的人”有什么问题，正如<a href="https://news.ycombinator.com/item?id=11290662" rel="noopener noreferrer">Thomas Ptacek指出的</a>，如果你想要的类型正是其他人争相抢夺的类型，“那你就是在和市场上最富有（或获得最多资金）的科技公司竞争人才”。</p>

<p>如果你查看<a href="https://docs.google.com/spreadsheets/u/1/d/1UnLz40Our1Ids-O0sz26uPNCF6cQjwosrZQY4VLdflU/htmlview?pli=1&amp;sle=true#" rel="noopener noreferrer">应届毕业生招聘数据</a>，FB给零经验的应届生提供的薪资超过10万美元/年，10万美元签约奖金，以及15万美元的RSU（限制性股票单位），总包年化超过16万美元，第一年可达24万美元。谷歌的待遇包包括超过10万美元的工资、约1万美元的浮动签约奖金和18.7万美元的RSU。这略低于FB，但远高于大多数声称只招最优秀人才的公司愿意支付给应届生的薪酬。请记住，<a href="https://danluu.com/startup-tradeoffs/#fn:C" rel="noopener noreferrer">对于竞争激烈的人才，薪酬可能高得多</a>，而且如果你不是看过当今竞争性offer的招聘经理，<a href="https://news.ycombinator.com/item?id=11314449" rel="noopener noreferrer">有经验人才的薪酬可能比你想象的要高</a>。</p>

<p>通过追求拥有最受欢迎资质的人，TrendCo把自己的选择范围缩小到了要么支付天价薪酬，要么提供没有竞争力的薪酬包。TrendCo选择了后者，这部分解释了为什么他们高级开发者的比例相对较低——薪酬差距随着资历增长而扩大，当你提供的年薪比竞争对手低15万美元时，必须提出极具说服力的方案才能让人选择TrendCo。而且，随着人们经验增多，他们越来越不可能相信推销中关于股票期权价值的那部分说辞。</p>

<p>需要明确的是，我并非对拥有潮流背景的人有任何意见。我认识很多这样的人，他们面试技巧无可挑剔，上次找工作时拿到了5-10个强有力的offer。我曾与这样的人共事：他刚毕业，总包超过20万美元/年，而且物有所值。但请想一想。他有六家公司的强力offer，最多只能接受一家。算上午餐和电话面试，这些公司平均各花了八小时面试他。因为太想招他，那些认真的公司平均又额外花了五个小时的工程师时间试图说服他接受offer。由于这些公司平均只有1/6的几率招到此人，他们至少需要付出预期（8+5）*6 = 78小时的工程师时间<sup id="fnref:T"><a href="#fn:T" rel="noopener noreferrer">1</a></sup>。拥有优秀背景的人通常确实很棒，但非常难招。招那些被低估的人要容易得多，尤其是如果你不支付市场价的话。</p>

<p>我从招聘的双方都见过这种对拥有潮流背景的人的过度关注，无论从哪方看都很荒谬。</p>

<p>在招聘的推荐方，我曾试图让一家我所在的初创公司雇用我遇到过的最有趣、最有创造力的程序员，他因大学GPA低而多年处于屈才状态。我们拒绝了雇用他，我被告知他的低GPA意味着他不可能很聪明。多年后，谷歌给了他机会，他此后表现一直非常出色。实际上，是他劝我加入谷歌的，<a href="//danluu.com/tech-discrimination/" rel="noopener noreferrer">在谷歌，我曾试图雇用我认识的最高效的程序员之一，结果被招聘人员以不够技术为由迅速拒绝</a>。</p>

<p>在招聘的候选人方，我经历过抢手和几乎无人问津的阶段。因为我在威斯康星大学读的本科（那是声称是前10的计算机/工程学院的25所学校之一），毕业时招聘人员踏破了我的门槛。但这很傻——我就读威斯康星大学并不能说明我什么；我只是碰巧在威斯康星州长大。如果我是在犹他州长大的，很可能就会在犹他州上学。当我与就读犹他大学和博伊西州立大学等学校的人交流时，他们的教育和我基本相同。威斯康星作为工程学院的排名来自拥有做出色研究的教授，而这与<a href="//danluu.com/teach-debugging/" rel="noopener noreferrer">实际教学本科生的有效性</a>充其量只有微弱的相关性。尽管接受的工程教育与数百所其他学校能提供的相同，我找工作面试却非常容易。</p>

<p>我在那份好工作上干了7.5年，是在<a href="https://www.amazon.com/gp/product/B01FSZU6FK/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01FSZU6FK&amp;linkId=d15e514c6ecefa224be8f05d4d5837e3" rel="noopener noreferrer">Centaur</a>。Centaur在奥斯汀那些老牌硬件公司中口碑相当不错，我在本地硬件公司找工作也很顺利。但我没听说过任何软件圈的人知道Centaur，结果大多数软件公司连面试机会都不给我。甚至有几次我有非常强的内部推荐，招聘人员仍然不愿意和我谈，我觉得这很有趣，而我的朋友们则感到沮丧。</p>

<p>当我能获得面试机会时，表现往往也不佳。一个典型的拒绝理由是类似“我们这里每天处理数百万笔交易，我们真的需要更有相关经验的人，这样他们不需要太多上手时间就能处理这些事情”。后来谷歌给了我机会，我成了一个认真投入深度学习性能项目的第二个人，这个项目在我加入前不久才从20%时间项目变成全职。<a href="https://www.google.com/patents/US20160342889" rel="noopener noreferrer">我们打造了当时世界上最快的深度学习系统</a>。据我所知，他们现在已经到了该项目的第N代，但即使是我们构建的第一代东西，在每机架性能和每美元性能上也多年优于任何其他生产系统（当然，不包括该项目的后续版本）。</p>

<p>在谷歌期间，经常有招聘人员联系我询问工作机会。而现在，我在乏味的老微软，收到招聘人员联系的频率就低得多了。我一直在考虑找新工作<sup id="fnref:P"><a href="#fn:P" rel="noopener noreferrer">2</a></sup>，我想知道如果我这样做，我会变得多“潮”。不相关的技术经验？有！杂乱的经验？有！合同工？嗯，不是。但三项里占两项也不错了。</p>

<p>我的重点并非关于我自己。而是说，有这么一个人<sup id="fnref:M"><a href="#fn:M" rel="noopener noreferrer">3</a></sup>，在不同时间对雇主的吸引力差异巨大，主要由于与实际生产力关系不大的表面因素。这在那些最终进入谷歌的人中是一个非常普遍的故事。如果你在他们去谷歌之前雇用了他们，你可能捡了个大便宜！但除了谷歌，没人愿意冒这个险。花更多钱招一个已知能力的人有其道理，但像TrendCo这样不愿这样做的公司，通过只追求拥有潮流简历的人来削弱自己的招聘管道，如果你在一个人去谷歌之前不愿雇用，之后却愿意，那你主要知道的就是这人白板算法测验水平高于平均（或者某天走运）。</p>

<p>我并不是特指像TrendCo这样的初创公司。乏味的老派公司也有他们对“潮流背景”的定义。我一个急需招人的朋友，对于我推荐的一些简历束手无策，因为他所在的部门不允许雇用任何没有学位的人。我认识的另一个人也面临类似情况，因为他所在的部门有一条明确的规则，导致他们拒绝那些当前未受雇的人。</p>

<p>这些决定不仅对公司的招聘效果非最优，还造成了就业结果中的路径依赖，使得个人的好（或坏）事件可能影响其数十年。你可以在各种领域的职业生涯收入文献中看到类似的影响<sup id="fnref:C"><a href="#fn:C" rel="noopener noreferrer">4</a></sup>。</p>

<p><a href="https://news.ycombinator.com/item?id=7260087" rel="noopener noreferrer">Thomas Ptacek有句名言</a>：“我们面试那些唯一工作经验是‘业务线.NET开发’的人，结果他们向我们展示了如何编写利用椭圆曲线部分随机数偏差攻击的漏洞利用程序，涉及傅里叶变换和需要运行6小时的BKZ格规约步骤。”如果你在一家不会仅因某人不潮就断然拒绝的公司工作，你会听到很多这样的故事。我合作过的一些最优秀的人上过你闻所未闻的学校，在你闻所未闻的公司工作过，直到他们最终进入谷歌。有些人仍在那些你闻所未闻的公司工作。</p>

<p>如果你读过<a href="https://zachholman.com/talk/firing-people" rel="noopener noreferrer">Zach Holman</a>，你可能记得当他说自己被解雇时，有人回应：“如果雇主决定解雇你，那么你不仅在工作中失败了，作为人也失败了。”许多人将就业状态和资历视为个人内在价值的衡量标准。但这些成功标志，更不用说成功本身，很大程度上是运气的结果。</p>

<h3 id="solutions">解决方案？</h3>

<p>我能理解为什么会发生这种情况。在个体层面，我们容易犯<a href="https://en.wikipedia.org/wiki/Fundamental_attribution_error" rel="noopener noreferrer">基本归因错误</a>。在组织层面，快速发展的组织将大量时间花在面试上，而减少面试时间最明显的方法就是只面试拥有“好”资质的人。不幸的是，当所有人都在争夺同一批小池子里的人时，这反而适得其反。</p>

<p>这里是一些初步的想法。我期待更好的建议！</p>

<h4 id="moneyball">金钱球（Moneyball）</h4>

<p>Billy Beane和Paul Depodesta接手奥克兰运动家队，一支预算远不及顶级球队的棒球队，通过发现并“签约”那些统计数据上被低估、性价比高的球员，打造了可能是棒球界最好的球队。让我真正感到惊奇的是，他们公开谈论这样做，然后Michael Lewis写了一本关于此事的书，书名就叫<a href="https://www.amazon.com/gp/product/0393324818/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;camp=1789&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=0393324818&amp;linkId=65d86d3a72b4c1ba73e8d3d52796eae1" rel="noopener noreferrer">《点球成金》（Moneyball）</a>。尽管广为人知，但花了数年时间才有足够多的竞争对手意识到这一点，使得运动家队的策略不再具有巨大优势。</p>

<p>你可以在软件招聘中看到完全相同的情况。Thomas Ptacek谈论他们在Matasano如何高效地雇用不寻常的人已经有至少半十年，甚至更久。谷歌的大佬们经常谈论他们拥有的招聘数据和哪些方法无效。我相信他们多年前就谈过专注于顶尖学校并不有效，并不能找到绩效更好的员工，但这并没有阻止TrendCo将招聘重点放在顶尖学校上。</p>

<h4 id="training-mentorship">培训 / 指导</h4>

<p>你经常听到关于“金钱球”的讨论，但出于某种原因，人们对……“训练球”？“练习球”？无论你怎么称呼那些把不是“最优秀”的人培养成“最优秀”的人，兴趣就没那么大。</p>

<p>这也是一个通过体育镜头很容易看到影响的例子，因为有大量良好的表现数据。既然现在是篮球赛季，例如，看看大学篮球，我们可以找出一些项目，它们定期将平凡的输入转化为良好的输出。而这是在一个所有球队都被期望教练和训练球员的竞争环境中实现的。</p>

<p>说到科技公司，大多数竞争者甚至没有在尝试。在中大型公司，你只会得到几天的“入职培训”，主要是法律套话和文书工作，以及偶尔的“培训”，通常是为合规目的提供的一系列视频和选择题，而不是真正教人任何东西。而且你会被分配一个导师，而这位导师很可能不会提供任何实际的指导。初创公司往往更糟！做到比这些更好并不难。</p>

<p>考虑到公司在招聘和留住“最优秀人才”上花费了多少钱<a href="https://news.ycombinator.com/item?id=11314449" rel="noopener noreferrer"></a>，你会期望他们至少（非零地）花一部分钱在培训上。同样奇怪的是，公司在招聘时没有更注重培训和指导。我在特定岗位上学到的具体东西对我价值巨大，但这几乎总是要么是偶然的，要么是我刻意去做的。大多数公司不关注这些。当然，招聘人员会告诉你“你在这里学到的东西会比在谷歌多得多，这会让你更有价值”，暗示这值得每年15万美元的降薪，但如果你问他们具体做了什么来创造比谷歌更好的学习环境，他们从来没有一个好答案。</p>

<h4 id="process-tools-culture">流程 / 工具 / 文化</h4>

<p>我在两家公司工作过，它们都有几乎无限的资源可以投入工具建设。其中一家，我们叫他们ToolCo，非常认真对待工具，大量投资于工具。人们用“神奇”、“我见过最好的”、“我不敢相信这居然可能”这样的短语来描述那里的工具。我明白为什么。例如，如果你想要构建一个数百万行代码的项目，他们的构建系统会让你在5到20秒内完成（假设你没有启用<a href="https://en.wikipedia.org/wiki/Interprocedural_optimization" rel="noopener noreferrer">LTO</a>或其他无法并行化的东西）<sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">5</a></sup>。在日常工作流程中，你会使用多个看起来神奇的工具，因为它们远超外部可用的工具。</p>

<p>另一家公司，我们叫他们ProdCo<a href="http://yosefk.com/blog/people-can-read-their-managers-mind.html" rel="noopener noreferrer">口头上重视工具，但实际上并不真正重视</a>。人们用“世界级的糟糕软件”、“我的效率只有在其他任何地方的一半”、“我不敢相信这居然可能”这样的短语来描述ProdCo的工具。ProdCo有一篇关于新构建系统的论文；他们声称的并行化/缓存加速、入职时间和可靠性数字，比ToolCo的相应数字至少差两个数量级。而且，根据我的经验，实际数字比论文中的声称更糟。在ProdCo工作一天，你会使用多个在多个维度上比ToolCo相应工具差数个数量级的工具。这类事情会累积起来，很容易造成比“只招最优秀的人”更大的差异。</p>

<p>流程和文化也很重要。我曾在一个不使用版本控制也没有bug跟踪器的团队工作过。对于<a href="http://www.joelonsoftware.com/articles/fog0000000043.html" rel="noopener noreferrer">Joel测试</a>中每个理所当然的项目，都有一些团队做出了错误的选择。</p>

<p>虽然我只在一个完全通不过Joel测试的团队工作过（得分1/12），但我工作过的每个团队都有技术上微不足道（但有时文化上很难）但显而易见的缺陷。我在谷歌时，我们团队位于不同地点的两个部分之间存在严重的沟通问题。我的解决方法笨得简单：我开始为我们所有的本地会议和讨论撰写会议纪要，并就纪要中让他们感到惊讶的地方向远程团队提问。这是任何人都能做的事，却极大地提高了整个团队的生产力。我几乎从未见过一个环境，不能通过如此琐碎的事情大幅度提高生产力。有时人们不同意（例如，花了数月时间才让那个不用版本控制的团队使用版本控制），但这是另一篇文章的话题了。</p>

<p>程序员在大多数公司被严重低估和未充分利用。<a href="//danluu.com/wat/" rel="noopener noreferrer">雇用“最优秀的人”然后削弱他们</a>有什么意义？你可以通过雇用普通的人并为他们的成功创造条件来获得更好的结果，<a href="https://twitter.com/patio11/status/706884144538648576" rel="noopener noreferrer">而且这便宜得多</a>。</p>

<h3 id="conclusion">结论</h3>

<p>当我开始编程时，我经常听说程序员很务实，不像那些穿西装打领带的精英主义者。你甚至可以穿T恤上班！但如果你认为程序员不势利，试试穿西装打领带去面试。你必须额外努力证明你不是文化上的不合适。我们喜欢认为自己与所有那些以貌取人的行业不同，但我们做着同样的事情，只是我们不说人们因为不打领带而不合适，而是说他们因为打了领带不合适；我们不说人们因为没有正确的背景而不聪明……等等，这完全是一回事。</p>

<p><strong>另见：<a href="//danluu.com/hiring-lemons/" rel="noopener noreferrer">开发者招聘与柠檬市场</a></strong></p>

<p><small>感谢Kelley Eskridge、Laura Lindzey、John Hergenroeder、Kamal Marhubi、Julia Evans、Steven McCarthy、Lindsey Kuper、Leah Hanson、Darius Bacon、Pierre-Yves Baccou、Kyle Littler、Jorge Montero、Sierra Rotimi-Williams和Mark Dominus的讨论/评论/修正。</small></p>
<div>

<hr>

<ol>
<li id="fn:T"><p>这个估计是保守的。数学计算只有在假设你从不错误拒绝潮人候选人，并且不需要面试那些你“正确”认定的非优秀候选人时，才会得出78小时。如果加上为这些人额外花费的时间，数字会变得大得多。而且如果你是TrendCo，不给高级IC（个人贡献者）20万美元/年，更不用说应届毕业生，你可能需要将那个数字乘以至少10倍，以考虑需求旺盛的人大幅降薪为你工作的概率降低。</p>

<p>顺便说一句，如果你做些类似的数学计算，你会发现人们谈论的“零误报”是扯淡。将误报风险降至零的唯一方法就是不雇用任何人。如果你雇用任何人，你就是在权衡解雇一个坏员工的成本与花费工程师时间面试的成本。</p>
 <a href="#fnref:T" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:P">我认为这通常是好的做法，至少对于像我这样职业生涯相对早期的人来说是这样。了解自己的选择是好的，即使你没有行使它们。当我在<a href="https://www.amazon.com/gp/product/B01FSZU6FK/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;tag=abroaview-20&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01FSZU6FK&amp;linkId=d15e514c6ecefa224be8f05d4d5837e3" rel="noopener noreferrer">Centaur</a>时，我大约每年进行一轮面试，那些面试让我非常清楚自己在Centaur是多么幸运。我得到了比在其他地方能得到的更多的责任和更广泛的工作种类，不必处理那么多无谓的事情，而且薪酬也相当不错。不过，我偶尔还是会面试，你也应该这样做！如果你担心浪费招聘公司的时间，当我投机性地去面试时，我总是非常明确地表示我目前工作很开心，不太可能换工作，大多数公司对此并不介意，仍然想进行面试。
 <a href="#fnref:P" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:M"><p>这真的不是特指我个人。就在我无法让任何公司跟我谈的同一时期，我一个比我优秀得多的程序员朋友全职找了六个月工作。他最终在Cloudflare找到了工作，是编写他们DNS系统的团队一半的成员，现在是非无限资源公司中DDoS缓解领域的世界顶尖专家之一。那个人在加入Cloudflare之前甚至不是搞网络的。他是一个才华横溢的通才，从广泛使用的JavaScript库到我见过的最酷的玩具系统项目，什么都创造过。他可能能搞定你正头疼的任何问题领域，并且表现卓越。哦，加上他写的博客文章和做的演讲，他是Cloudflare最有效的招聘人员之一。</p>

<p>或者Aphyr，一位世界最受尊敬的分布式系统验证工程师，他在不到十年前大学毕业时，<a href="https://twitter.com/aphyr/status/762042095330877440" rel="noopener noreferrer">投出的所有求职申请都没有得到任何回复</a>。</p>
 <a href="#fnref:M" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:C"><p>我不会做文献综述，因为有太多研究将职业生涯收入与外部冲击联系起来，但我会引用一个我发现的有趣结果，<a href="http://citec.repec.org/d/eee/labeco/v_17_y_2010_i_2_p_303-316.html" rel="noopener noreferrer">Lisa Kahn 2010年发表在《劳动经济学》上的论文</a>。</p>

<p>有很多研究表明，对于某些特定的负面冲击（如经济衰退），毕业时遭遇负面冲击会降低终身收入。但大多数研究显示，随着时间的推移，这种影响会减小。当Kahn以全国失业率作为经济状况的代表时，也发现了同样的情况。但当Kahn查看<em>州一级的失业率</em>时，她发现这种影响实际上随着时间的推移而加剧。</p>

<p>关于长期结果的整体证据是模棱两可的。如果你深入研究，会发现有的研究显示收入在“仅仅”15年后恢复正常，造成大但实质上一次性的一次性收入损失；而有的研究显示影响随着时间的推移而恶化。这些结果在技术上大多并不矛盾，因为它们考察了人们第一份工作时经济困难的不同原因，结果的差异可能是因为不同的情况无法推广。但“好”的结果是，单个坏的挫折后，收入需要15年才能正常化。即使对文献非常乐观的解读也表明，外部事件能够并确实对人们的职业生涯产生非常大的影响。如果你想要“坏”情况的界限估计，请参考例如<a href="http://www.nber.org/papers/w14278" rel="noopener noreferrer">Guiso、Sapienza和Zingales的论文，该论文声称一个城市今天的生产力与该城市在公元1000年是否有主教有关</a>。</p>
 <a href="#fnref:C" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:B">在入职培训期间，构建系统的后端宕机了，所以我尝试在我的本地机器上构建一个入门教程。一小时后，构建只完成了2%，我放弃了。我认识一个人，他试图在一个长周末内在本地机器上构建一个真正的、大型的、生产级的代码库，当他回来时还远远没有完成。<p></p>
 <a href="#fnref:B" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 12634 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/programmer-moneyball/</link>
      <guid isPermaLink="false">https://danluu.com/programmer-moneyball/</guid>
      <pubDate>Mon, 21 Mar 2016 07:23:44 +0000</pubDate>
    </item>
    <item>
      <title>《哈利·波特与理性之道》su3su2u1评论</title>
      <description>&lt;p&gt;&lt;em&gt;以下内容转自现已失效的 &lt;a href=&quot;http://su3su2u1.tumblr.com/&quot;&gt;su3su2u1 Tumblr博客&lt;/a&gt;。鉴于su3su2u1的身份曾引发争议，特此声明：我并非su3su2u1本人，展示此内容既不代表认可也不代表赞同其观点。&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;harry-potter-and-the-methods-of-rationality</description>
      <content:encoded><![CDATA[<p><em>以下内容转自现已失效的 <a href="http://su3su2u1.tumblr.com/" rel="noopener noreferrer">su3su2u1 Tumblr博客</a>。鉴于su3su2u1的身份曾引发争议，特此声明：我并非su3su2u1本人，展示此内容既不代表认可也不代表赞同其观点。</em></p>

<h3 id="harry-potter-and-the-methods-of-rationality-full-review">《哈利·波特与理性之道》全文评论</h3>

<p>特里·普拉切特去世时，我开了一瓶比我年长的醇美苏格兰威士忌，整个下午都在品味它，所以这篇评论可能会有些凌乱，日后需要整理。</p>

<p>满分5星，我给HPMOR评1.5星。现在进入正题（这几乎肯定会很长）</p>

<h4 id="the-good">优点</h4>

<p>HPMOR包含一些巧妙地对原著进行的改编，以适应尤德科夫斯基设定的世界观：</p>

<p>例如在HPMOR中，"梅林禁令"禁止巫师记录强力咒语，因此斯莱特林将蛇怪放入密室以传承魔法知识。预言"黑魔王将标记他为己出"的应验方式，是伏地魔给哈利泽尔（Hariezer）与他当年相同的评分。</p>

<p>尤德科夫斯基博览群书，故事中穿插着大量真实有趣的科学参考。如果你搜索并研究每一个引用，会学到很多。问题在于故事中大多数科学参考都是错误的，如果不深究，你可能会接受许多错误观念。</p>

<p>动作场景的文笔相当不错，节奏明快流畅，读来确实令人愉悦。</p>

<h4 id="the-bad">缺点</h4>

<h4 id="stilted-repetitive-writing">生硬重复的写作</h4>

<p>故事中许多对话读起来像笨拙的操纵尝试，充满过度生硬的措辞。"尊贵古老的家族"、"阳光将军"、"混沌将军"等短语被反复使用。文笔显得沉闷。事件发生时行文会流畅些，但故事中很少发生实质事件。</p>

<h4 id="bad-ideas">糟糕的理念</h4>

<p>HPMOR充斥着我认为极其可疑的观念——故事中唯一值得重视的特质（隐含及明确）是智力，而智力的主要用途是操纵。这导致了令人窒息的精英主义。罗恩和海格在故事中基本上被无视（罗恩被明确视为无用，海格则被隐含否定），因为他们不够聪明，而哈利泽尔明确区分了NPC式人物与真实人物。</p>

<p>世界设定本身就在强化这些论断——巫师世界毫无逻辑可言，角色行为常常荒谬（"像NPC"）只为衬托哈利泽尔。</p>

<p>最荒谬的例子是巫师世界的司法基于两大基石：政客决定所有巫师犯罪的罪责，以及血债系统。所有声称被夺魂咒控制而获赦的前食死徒，似乎都欠哈利泽尔血债，因此在巫师司法眼中他凌驾于法律之上。他在赫敏的审判中利用了这一点。</p>

<h4 id="bad-pedagogy">糟糕的教学</h4>

<p>哈利泽尔在讲解读者应学习的科学概念时常出错。几乎所有明确的科学引用都是错误的，且充斥着过度术语。</p>

<p>部分错误可能是故意的——哈利泽尔设定为11岁。但这是糟糕的教学。读者的理性指南完全不可靠。更奇怪的是，主要反派伏地魔也被多次用作作者传声筒。因此其教学内容轻则错误，重则完全不可信。</p>

<p>隐含层面，哈利泽尔主要依赖的解题方法是亚里士多德科学。他观察事物，苦思冥想，便知答案。这是极其糟糕的隐含教学。</p>

<h4 id="bad-plotting">糟糕的情节设计</h4>

<p>故事进程中，哈利泽尔从主动变得被动。故事开始时他有明确的积极目标——用科学揭示魔法奥秘。但随着故事发展，他完全丧失了这一目标，变成情节的旁观者——他参加奇洛的游戏，像其他学生一样上学。当伏地魔开始将哈利泽尔纳入计划时，他完全被动应对，诸如此类。</p>

<p>直到故事接近尾声赫敏死去，哈利泽尔才重拾积极目标（终结死亡），却未采取任何行动去实现它。他被动应对一切，而伏地魔在他毫无实际贡献的情况下击败死亡并复活了赫敏。</p>

<p>对于一个本应充满能动性的角色，他极少以主动方式行使它。</p>

<h4 id="nothing-has-consequences-boring">毫无后果（无聊！）</h4>

<p>这引出了情节设计的另一个问题——故事中没有任何事件产生后果。出错的事不会对故事产生任何持久影响，导致所有情节最终都索然无味。例如故事早期哈利泽尔用时间转换器解决最简单的问题。斯内普问你不知道的魔药问题？时间旅行。恶霸抢走无意义的饰品？时间旅行，诸如此类。这些违规行为导致他的时间转换器被麦格教授锁定。尽管如此哈利泽尔仍继续用时间转换器解决所有问题——情节引入了另一个愿意通过"斯莱特林邮件"用时间转换器传递信息的学生，且完全匿名。</p>

<p>另一个过分例子是奇洛的战斗游戏——战斗游戏的奖励在第35章左右由奇洛颁发，之后还有多次战斗游戏！读者完全知道这些比赛无关紧要——奖励已提前颁发！意义何在？读者已知奖励已发放，为何还要关心后续赛程？</p>

<p>当哈利泽尔欠下卢修斯·马尔福债务时，这从未以任何方式制约他。他负债，邓布利多告诉他这很糟，他完全未处理问题。两周后，赫敏死亡，债务被取消。</p>

<p>当赫敏死去时哈利泽尔毫无作为，几周后伏地魔复活了她。发生的任何事都无关紧要。</p>

<p>最接近长期后果的是哈利泽尔帮助贝拉特里克斯·布莱克越狱——但我们之后再也没见过贝拉特里克斯。</p>

<p>哈利泽尔从未主动解决问题，只是被动前行，抱怨人类需要击败死亡，直到问题被他人解决。</p>

<h4 id="mystery-dramatic-irony-and-genre-savvy">悬疑、反讽与类型自觉</h4>

<p>若你读过原著，你随时知道故事走向。伏地魔附身奇洛，哈利泽尔是魂器，奇洛觊觎魔法石，等等。虽有些许改动，但故事轮廓完全遵循原著。因此所有悬疑只是反讽。</p>

<p>这本身尚可接受，但存在巨大张力，因为哈利泽尔被塑造成"类型自觉"，偶尔会说"这类故事的主角会这样做"或"我理解书中那些神秘预言"。故事在讽刺它本身全盘接受的陈词滥调。按理说哈利泽尔读过足够多类似小说，本不该发生这种反讽——他应该和读者一样知情。然而……</p>

<p>作者几乎在呐喊："哈利的黑暗面源于他是魂器，这多偷懒啊？"然而，哈利的黑暗面确实源于他是魂器。</p>

<p>更糟的是，叙述不断讽刺原著情节，同时却"借用"了原著的情节。</p>

<h4 id="huge-tension-between-the-themes-lessons-and-the-setting">主题/教训与设定的巨大冲突</h4>

<p>本书主要主题与故事设定存在重大冲突。</p>

<p>一个主题是秘密科学需要隐藏危险秘密——这体现在哈利泽尔建立"贝叶斯阴谋"的方式，以及他和奇洛对核武器的态度（他们明确主张能造核弹的聪明人不会使用它），在小说结尾哈利泽尔想解除部分魔法保密的计划被他"不毁灭世界"的誓言挫败。</p>

<p>不幸的是，这种保密性同样被描绘为导致巫师世界停滞不前、阻碍魔法传播。这种保密性很可能就是巫师世界未能终结死亡、制造无数魔法石的原因。</p>

<p>另一个主要主题是战胜死亡/无来世。但这是个有魔法的奇幻故事。存在幽灵、通往来世的大门、能与死者交谈的石头等。故事试图掩饰，但根本矛盾并未消失。一些读者甚至认为哈利泽尔在故事中关于来世的观点是错误的——因为他们感受到了这种矛盾，并用我之前的观点（不可靠的教学）将责任归咎于哈利泽尔。在故事中，真正终结死亡的角色同时也是反派。哈利泽尔的尝试被描绘得如此危险，足以毁灭世界。</p>

<p>最后——这个故事的主要主题是贝叶斯推理的至高无上。不幸的是，正如nostalgebraist明确指出的，一个存在魔法的世界意味着你的非魔法贝叶斯先验毫无价值。基于该先验反复推理会导致轻率结论，且可能错误——但故事中这方法却屡试不爽。世界再次以明显方式对抗着故事主题。</p>

<h4 id="let-s-talk-about-hermoine">谈谈赫敏</h4>

<p>这个故事中最明确的女权主义情节是赫敏建立SPHEW，一个致力于创造更多女巫英雄的团体。该团体起初成功，后来力不从心，哈利泽尔被召唤来收拾残局（在奇洛帮助下）。</p>

<p>该情节结尾，哈利泽尔和邓布利多进行长谈，讨论是否该让赫敏和朋友们玩她们那套对抗欺凌的小游戏——这感觉像事后剥夺角色能动性。当然，女性得以体验幻想，但全凭真正英雄的恩赐。</p>

<p>故事结局时，赫敏成为半独角兽/半巨魔的不死生物。她打算用这种力量做什么？大体上成为哈利泽尔的实验室助手，被他派去执行任务。感觉赫敏并未真正被允许以有意义的方式发展自己的能动性。</p>

<p>并非说这是刻意为之（这个故事中唯一真正拥有主动能动性的角色可能是奇洛）——但感觉女性在此处于劣势。</p>

<h4 id="sanderson-s-law-of-magic">桑德森魔法法则</h4>

<p>我未读过桑德森的作品，但有人给我指出他的魔法第一法则</p>

<p>桑德森魔法第一法则：作者用魔法解决冲突的能力与读者对该魔法的理解程度成正比。核心思想是，如果你的魔法设定有清晰规则，作者应能自由地用它解决问题——若你的魔法像甘道夫那样神秘模糊，就不该用魔法解决所有冲突，但若设定了严谨规则，角色偶尔用魔法解决问题也无妨。我不完全认同这条法则，但它提出了好观点——若读者不懂你的魔法，解决方案可能显得突兀。</p>

<p>尤德科夫斯基从未清晰阐明大部分魔法规则，却仍通过魔法（及魔法与科学的混合）解决所有问题。我们不知道扫帚如何运作，但显然绑在火箭上就能操控火箭，你不会掉下来，还能比其他扫帚飞得快得多。</p>

<p>当他发布期末考试时，这变得尤为棘手——许多解决方案都依赖之前定义模糊的魔法侧面。尤德科夫斯基自己的解答依赖同样定义模糊的变形术。</p>

<p>当他不用这类方法解决问题时，就反复依赖时间转换器。如同反复用火焰喷射器打苍蝇。</p>

<p>再加上世界被描绘成"疯狂的"，这感觉就是懒惰的冲突解决。</p>

<h4 id="conclusion">结论</h4>

<p>一部基本可遗忘、过度冗长的极客力量幻想，包含少许科学（大部分错误）和大量糟糕理念。1.5星。</p>

<p><em>单章评论如下</em>。</p>

<h3 id="hpmor-1">HPMOR 1</h3>

<p>午餐时，我开始阅读HPMOR的第一章。几点说明：</p>

<p>这远没有我记忆中那么糟，文笔不算出色但尚可...要么是过去几年经过编辑，要么是我眼光不如从前。</p>

<p>有一段很奇怪，哈利试图用这句话化解父母的争吵：</p>

<blockquote>
<p>"妈妈，"哈利说，"如果你想和爸爸赢得这场争论，去翻翻《费曼物理学讲义》第一卷第二章。里面有句话讲哲学家们大谈科学绝对需要什么，全错了，因为科学的唯一规则是最终仲裁者是观察——你只需观察世界并报告所见。"</p>
</blockquote>

<p>这显得格外突兀，因为没人争论科学是什么。</p>

<p>除此之外，这基本上是个还不错的短章。哈利和父亲怀疑魔法可能存在，于是给霍格沃茨回信请求教授来展示魔法。</p>

<h3 id="hpmor-2-in-which-i-remember-why-i-hated-this">HPMOR 2：我想起为何讨厌它了</h3>

<p>这章让我翻白眼翻到头痛。本章中麦格教授现身施展魔法，先让哈利父亲飘起来，再变成猫。看到第一项时，哈利抛出贝叶斯理论，称"更新一个概率无穷小的事件"平淡无奇；看到第二项时，哈利泽尔·波特（Hariezer Yudotter）给我们来了段术语轰炸：</p>

<p>"你变成了猫！一只小猫！你违反了能量守恒！这不是任意规则，它蕴含于量子哈密顿量形式！否定它会破坏幺正性，然后你就能进行超光速通讯了！"</p>

<p>首先，这显然是糟糕的写作。多数读者从这恐怖句子中一无所获。他甚至把超光速缩写为FTL，以保持可理解词汇的密度最小化。</p>

<p>其次，这是糟糕的物理学，原因如下：</p>

<ul>
<li>悬浮早已违反能量守恒，你刚才还说它平淡无奇——真有你的哈利泽尔
</li><li>涉及能量守恒的深层物理学不是量子力学，而是热力学。哈利泽尔本该来段关于永动机的术语轰炸。要理解悬浮如何违反能量守恒，想象用胡佛水坝般的发电机，施法将坝底所有水提升回坝顶形成闭环。只要有巫师搬水，就能永远发电。
给读者的练习——设计一台由变形者驱动的永动机（提示：想象滑轮系统挂着两个车厢。一边是大象，另一边是人。大象下降，人上升。到底部时大象变成人，到顶部时人变成大象。滑轮最终会怎样？）
</li><li>与能量守恒更相关的不是幺正性，如引文所暗示。物理学有个深刻定理（艾米·诺特提出），告诉我们能量守恒实际上意味着物理具有时间平移不变性。这意味着时间中没有特殊点，明天的定律基本和昨天、今天相同。（旁提——这就是为何我们不必担心大爆炸时能量不守恒，若时间起点特殊，无人期望那时能量守恒）。
量子力学中的幺正性基本上是概率守恒的花哨说法。你完全可以在没有能量守恒的情况下保持幺正性。技术旁提——容易证明若幺正算子是时间平移不变的，则存在与之对易的算子，通常称为哈密顿量。若无此假设，我们失去哈密顿量但仍保持幺正性。
</li><li>这些都与超光速通讯关系不大——若刚发现无限能源，超光速只是最小担忧。</li>
</ul>

<p>我曾教本科生，常有积极的大学新生（碰巧基础力学不太好）来和我讨论弦理论为何是错的。感觉总像在和物理填字游戏对话。这章让我重温那些尴尬时刻。</p>

<p>抱歉在此点上赘述，但我觉得这概括了尤德科夫斯基写作中的一个问题——当他涉足自己不精通的领域时，往往让真正的领域专家头疼。</p>

<p>章节摘要——麦格教授来访并施展魔法，哈利相信魔法存在，他们出发去买哈利的书。</p>

<h3 id="never-read-comments">千万别看评论</h3>

<p>我读了HPMOR某章的评论，强烈建议别这么做。我希望能和几位评论者聊聊，温和劝他们别做糟糕的财务决策。</p>

<p>可怜、迷茫的随机网友——你捐给MIRI/LessWrong的钱无法拯救世界。即使你接受他们（相当幼稚的）所有假设，MIRI也是个效率极低的研究机构——十多年发表的同行评审论文比普通物理研究生在读期间还少。你捐给MIRI的大部分钱将用于生产博客文章和同人小说。如果你接受这点，那就继续花钱吧，但别相信这钱能拯救世界。</p>

<h3 id="hpmor-3-uneventful-inoffensive">HPMOR 3：平淡无奇，不冒犯人</h3>

<p>这章比前几章更糟。哈利泽尔（我意识到这个混搭词没我自以为的聪明，但我会继续用）进入对角巷时，评论道：</p>

<blockquote>
<p>就像穿过《高级龙与地下城规则书》的魔法物品区（他不玩这游戏，但他喜欢读规则书）。</p>
</blockquote>

<p>不知为何，这话让我怒火中烧。</p>

<p>行走时麦格告诉哈利泽尔关于伏地魔的事，提到其他国家未援助英国。这促使哈利泽尔立即误用旁观者效应的概念（留给读者的练习：适用于个体的社会心理现象是否也适用于集体实体（如国家）？解释个人不作为的社会心理现象是否也能解释组织的不作为？）</p>

<p>本章基本如此。平淡无奇——轻微误用科学内容，短途穿越对角巷，标准伏地魔情节。章节以非常刻意的伏笔结束：</p>

<blockquote>
<p>（他脑海深处有个微弱的困惑，感觉那个故事有不对劲；本该是哈利艺术的一部分注意到这细微之处，但他分心了。因为可悲的规律是，当你最需要理性艺术时，往往最容易遗忘它。）</p>
</blockquote>

<p>但凡哈利多参加些CFAR研讨会……</p>

<h3 id="hpmor-4-in-which-for-the-first-time-i-wanted-the-author-to-take-things-further">HPMOR 4：我首次希望作者更进一步</h3>

<p>首先，我确实比前几章更喜欢这章，因为它开始尝试提供我想要的故事内容。现在，我的抱怨开始了：</p>

<p>LessWrong序列中反复出现的（我认为有些恼人的）主题是：除了贝叶斯理性主义者，世界是疯狂的。同样的主题出现在HPMOR这章，世界被尤德科夫斯基设定为疯狂，以便哈利泽尔告诉你原因。</p>

<p>哈利泽尔注意到巫师世界使用银币和金币时，询问汇率，并让银行妖精估算将一大块银子铸成硬币的费用，妖精说要请示上级，哈利泽尔请他估算，妖精估价约为银子价值的5%。</p>

<p>这让哈利泽尔意识到他可以：</p>

<ol>
<li>拿金币在麻瓜世界买银子</li>
<li>把银子带到古灵阁铸成硬币</li>
<li>把银币换成金币，得到比初始更多的金币，循环直到麻瓜市场价格使其无利可图</li>
</ol>

<p>（当然，故事内的解释充满术语）</p>

<p>这有些意思，首次展现了我想在这类故事中看到的——巫师世界中儿童故事不会涉及的细节。斯特罗斯写了一整本书探索未来社会的货币/经济（《海王星之子》，还不错），尤德科夫斯基这里有很多沃土可挖掘。</p>

<p>在一个巫师能用魔法点石成金的世界，如何防止伪造？也许硬币用的是只有妖精知道的特殊黄金（也许妖精像戴比尔斯囤积钻石一样囤积这种黄金）。</p>

<p>也许妖精精密地用魔法创造和销毁货币以维持货币挂钩。也许是完美通胀——不靠银行分发硬币，人们口袋里的硬币随机会自行增殖。</p>

<p>相反，我们得到了愚蠢、疯狂的系统（别怪罗琳——尤德科夫斯基乐于脱离原著，且这类细节在罗琳创作的类型中确实不会讨论），理性主义者哈利泽尔轻松"获胜"。这部分不算糟，但显得懒惰。</p>

<p>简短评述写作风格——依然奇怪生硬，我怀疑它向不熟悉的人解释想法的效果。例如，哈利泽尔沉思时麦格说了什么，哈利泽尔回应：</p>

<blockquote>
<p>"嗯？"哈利说，心不在焉。"等等，我在做费米估算。"
"什么？"麦格教授说，听起来有点警觉。
"是种数学方法。以恩里科·费米命名。一种在脑中快速估算粗略数值的方法……"</p>
</blockquote>

<p>也许哈利泽尔说"等等，我在估算金库有多少金子"会更自然。然后我们不听"这是数学东西"，而是跟随哈利泽尔仔细构建估算过程（目前估算挤在一个难读的段落里）。</p>

<p>这是吹毛求疵，当然，但故事至此充满这类瑕疵。</p>

<p>章节摘要——哈利去古灵阁取钱。</p>

<h3 id="hpmor-5-in-which-the-author-assures-us-repeatedly-this-chapter-is-funny">HPMOR 5：作者反复向我们保证这章很好笑</h3>

<p>这章again基本不冒犯人，尽管有奇怪的基调转变。大部分内容为搞笑而夸张展开。对基本归因谬误的描述其实不错，但它以这段拙劣对话引入：</p>

<p>哈利抬头看着戴尖帽的女巫严厉表情，叹道："我猜我说'基本归因谬误'你也不会懂是什么意思。"</p>

<p>这类事似乎是尴尬的教学。若读者不懂，哈利泽尔现在对读者和尤德科夫斯基当前用于衬托的角色一样恼火。</p>

<p>现在，本章大部分内容是哈利泽尔独自买长袍时遇到并与德拉科·马尔福交谈。哈利泽尔厌倦了别人见面时说"天啊，你是哈利·波特！"，得知马尔福的名字后惊呼："天啊，你是德拉科·马尔福！"马尔福认为这是对他假想名声的正常反应，一段略有趣的对话就此展开。这是个相当聪明的想法。</p>

<p>不幸的是，它被文学版的喜剧笑声轨道破坏。担心读者不确定是否该笑，尤德科夫斯基在文中穿插了这类语句：</p>

<blockquote>
<p>德拉科的侍从发出像被扼住的声音，但继续工作。
其中一个助手——那个似乎认出哈利的助手——发出压抑的呛声。
马尔金的一个助手不得不转身面朝墙壁。
马尔金夫人沉默地看了四秒，然后爆笑起来。她靠在墙上喘不过气地笑，这引得两个助手也笑了起来，其中一个笑得跪倒在地。</p>
</blockquote>

<p>读者不断被告知店员觉得太好笑几乎忍不住。感觉像作者不断呐喊"懂了吗伙计们？这很好笑！"</p>

<p>就文笔而言，向夸张喜剧的基调转变有点突兀，且几乎没有预警（本章早前也有段对话是为搞笑），一切如同往常般生硬。例如麦格走进长袍店时恰好听到马尔福说出些荒谬的话，哈利告诉她：</p>

<blockquote>
<p>"他在特定情境下，那些行为有内在逻辑——"</p>
</blockquote>

<p>幸运的是，哈利泽尔被打断，没开始解释什么是笑话。</p>

<p>章节摘要——哈利泽尔买长袍，与马尔福交谈。</p>

<h3 id="hpmor-6-yud-lets-it-all-hang-out">HPMOR 6：尤德倾囊相授</h3>

<p>导读暗示故事在第5章后真正启动。若这是"真正启动"的样貌，我担心很快会停止阅读。除了我对第2章的咆哮，之前基本轻松不冒犯。在这里，我发现自己强烈反感。我们从上章的夸张喜剧转向充满奇怪黑暗小咆哮的章节。</p>

<p>如前所述，我认为尤德科夫斯基与哈利之间的界限相当模糊（因此我烦人地使用哈利泽尔）。本章中，这条界限完全消失，我们读到这样的段落：</p>

<blockquote>
<p>哈利一直害怕成为那种从未取得成就、余生吹嘘自己十岁时多领先的神童。但大多数成年天才也从未有所成就。历史上可能每出现一个真正的爱因斯坦，就有一千个同等聪明的人。因为那些天才从未获得成就绝对必需的那样东西——他们从未找到一个重要问题。</p>
</blockquote>

<p>有数十个这样的段落可直接从哈利泽尔的友好AI文章中摘录粘贴到HPMOR。这有点令人不安，部分原因是它迫使我直面尤德科夫斯基其他作品中浪费了多少时间。</p>

<p>章节开头很强，哈利泽尔开始对魔法袋做实验。他要115枚金币，金币出现，但要90+25枚则不会。他尝试用其他语言说金币等。不幸的是，这导致他说：</p>

<blockquote>
<p>"我刚刚推翻了所有假设！它怎么知道'115加隆的袋子'可以但'90加25加隆的袋子'不行？它会数数但不会加法？它理解名词，但不理解某些意思相同的名词短语？……规则似乎有点一致但毫无意义！我甚至不问一个袋子如何具备语音识别和自然语言理解能力，而最好的人工智能程序员努力三十五年，用最快的超级计算机也做不到，"</p>
</blockquote>

<p>问题是——要写一个解析器完美表现哈利泽尔描述的袋子行为非常容易。你只需为不同语言中"金币"的众多单词设置查找表。完全不高深。它表现奇怪完全因为它不是在做自然语言处理！我希望后续章节我们会重访这个袋子来理清这事。我重申，正是这类东西（至少对我来说）构成这个故事的前提——充实这个古怪世界的规则。</p>

<p>紧接着，故事发生了真正怪异的转折。哈利泽尔看到一个魔法急救包，想买下来。为了让超级理性主义哈利相形见绌，麦格立刻变得极其愚蠢，试图劝阻他购买。注意，她不是说"哦，学校里到处都是魔法急救包"或"有巫师看护大难不死的男孩，如果出事他们能用咒语治愈你"之类合理的话，而只是说他永远用不上。</p>

<p>这让哈利长篇大论计划谬误，他说为抵消它，他总假设最坏结果。（给哈利和读者的注释：计划谬误特指个人或组织计划完成任务时发生的事。哈利试图克服的更准确说是乐观偏差。）</p>

<p>这导致麦格开始轻描淡写地暗示（毫无来由）哈利可能是受虐儿童。哈利泽尔回应了这个故事：</p>

<blockquote>
<p>"我们社区发生过几起抢劫案，妈妈让我把借的平底锅还给两条街外的邻居，我说不想去因为可能被抢，她说，'哈利，别那样说！'好像想这事会让它发生，所以我不说就安全了。我试图解释我为何没被说服，她还是让我送过去了。我太小，不懂抢劫犯盯上我的统计概率有多低，但我不小了，知道不想某事并不能阻止它发生，所以我真的很害怕。"
……
"我知道这听起来没什么，"哈利辩护道，"但那是人生关键的转折点，明白吗？……那时我意识到所有本该保护我的人都疯了，无论我怎么恳求他们都不会听。"
所以，世界是疯狂的，通过这个奇怪小故事过滤。</p>
</blockquote>

<p>然后麦格问哈利要不要买猫头鹰，哈利说不买，他怕忘记喂食等。这再次促使麦格暗示哈利受过虐待，引出哈利一段奇怪咆哮，关于虚假虐童指控如何毁掉家庭（这是真的，但天啊，这是适合这种咆哮的类型吗？这章到底怎么回事？）</p>

<p>最终麦格暗示哈利一定受过虐待因为他太怪，也许有人施咒抹去了他的记忆（哈利提出压抑记忆是伪科学后咒语出现，这又是真的，但为何发生在这故事里？）</p>

<p>哈利用"理性艺术"（字面是"哈利的理性技能开始启动"）提出另一种解释：</p>

<blockquote>
<p>"我太聪明了，教授。我和普通孩子无话可说。成年人不够尊重我，不愿真正和我交谈。而且坦白说，即使他们谈，也不会有理查德·费曼聪明，所以我宁愿读理查德·费曼写的东西。我很孤独，教授。我一生都很孤独。也许这和被关在地窖有同样效果。而且我太聪明，无法像孩子天生那样仰望父母。父母爱我，但他们觉得不必回应理性，有时我觉得他们才是孩子——不听话却对我的整个存在拥有绝对权威。我尽量不怨恨，但也尽量对自己诚实，所以，是的，我怨恨。"</p>
</blockquote>

<p>那段奇怪的来回之后，本章继续，哈利泽尔去买魔杖，然后从对话开始怀疑伏地魔可能还活着。当麦格不想告诉他更多时，"一种可怕的清明降临脑海，以钢铁般的现实主义规划可能战术并评估后果。"</p>

<p>这导致哈利泽尔敲诈麦格——如果她告诉他预言的事，他就不会告诉别人伏地魔还活着。这是充满奇怪内容的章节中又一个奇怪片段。</p>

<p>最后他们去买箱子，但金币不够（给读者的注释：这里本是计划谬误的绝佳例子）。但幸运的是哈利泽尔从金库多拿了些。他没简单说"哦，我多带了些"，而是说：</p>

<blockquote>
<p>所以——假设我有办法不回古灵阁就从金库取更多加隆，但涉及我违背乖孩子角色。即使你必须走出麦格教授角色利用这点，我能相信你吗？</p>
</blockquote>

<p>于是他用逻辑说服她，诸如此类，他们买下了箱子。</p>

<p>这章让我极不舒服。麦格行为非常奇怪，作为哈利泽尔所有咆哮的衬托，当哈利泽尔和尤德科夫斯基之间的界限完全消失时，感觉有点奇怪的个人色彩。</p>

<p>哦对了，还有一段关于未成年施法禁令的对话</p>

<p>"啊，"哈利说。"这听起来是很明智的规则。我很高兴看到巫师世界认真对待这类事。"</p>

<p>我不禁联想到尤德希望AI采取的预防措施。</p>

<p>摘要：哈利完成购买学习用品（我希望如此）。</p>

<h3 id="hpmor-7-uncomfortable-elitism-and-rape-threats">HPMOR 7：令人不适的精英主义与强奸威胁</h3>

<p>简要提醒：像往常一样我用手机打字，所以奇怪的拼写检查错误几乎肯定存在。不过我也喝得挺醉（离开学术界的一大特权是我能像喝水一样喝比我年龄一半还久的拉弗格威士忌。坏处是不再教学，只能通过批评糟糕同人小说来倾泻智慧，而读者大概就一个人）</p>

<p>这章延续了上章奇怪的基调转变并加以发挥。</p>

<p>我们终于前往霍格沃茨，所以章节以书中经典的9¾站台情节开场。接着转向令人不适的精英主义基调：哈利请罗恩·韦斯莱叫他"斯普先生"以保持匿名，罗恩有点困惑地说"好的哈利。"这一失误让哈利泽尔立刻认定罗恩是白痴。在后续短暂对话中，他内心多次称罗恩愚蠢，然后试图向罗恩解释魁地奇为何是愚蠢游戏。</p>

<p>据我对（非常粗略的）原著理解，像板球一样，魁地奇比赛持续数周甚至数月。在持续数周的比赛中，一队领先15球完全可能。在其中一本书里，我记得一场重要比赛的胜负取决于追球手而非金色飞贼。这不是全盘辩护魁地奇，但它并非必然容易树靶。我觉得魁地奇受到的嘲笑部分源于非英国/非印度观众可能无法欣赏板球这类持续数周、高得分的运动。</p>

<p>无论如何，哈利泽尔攻击魁地奇的方式是以罗恩为代价，感觉像书呆子嘲笑运动员喜欢体育。但这只是令人窒息的书呆子精英主义的铺垫。德拉科过来，哈利泽尔迅速重燃这段萌芽的友谊，我们得到关于罗恩的这段对话：</p>

<blockquote>
<p>"如果你不喜欢他，"德拉科好奇地问，"为什么不直接走开？"
"嗯……他妈妈帮我想办法从国王十字车站到这站台，所以很难让他滚蛋。不是我恨这个罗恩，"哈利说，"我只是，只是……"哈利搜寻着词语。
"找不到他存在的理由？"德拉科提议。
"差不多吧。</p>
</blockquote>

<p>真是令人窒息、不适的书呆子精英主义。</p>

<p>既然哈利泽尔和德拉科重新搭档，他们可以进行许多令人不适的对话。首先，德拉科分享了些稍私人的事，引出这段：</p>

<blockquote>
<p>"你为什么告诉我这个？感觉有点……私密……"
德拉科认真地看着哈利。"我有位导师说过，人们通过了解彼此的私密事建立亲密友谊，而大多数人无法结交亲密朋友是因为他们太羞于分享真正重要的事。"德拉科摊开手掌邀请道。"轮到你了？"</p>
</blockquote>

<p>哈利泽尔认为这是社会心理学互惠概念的巧妙运用（即你为某人做事，他们也可能为你做事）。总之，这段交流只为引出这段，感觉像无谓的震撼：</p>

<blockquote>
<p>"嘿，德拉科，你猜什么比交换秘密更能成为朋友？杀人。"
"我有个导师说过，"德拉科承认道。他把手伸进长袍，轻松自然地挠了挠。"你心里有人选了？"
哈利将《唱唱反调》重重拍在野餐桌上。"那个想出这标题的家伙。"
德拉科呻吟道。"不是家伙。是女孩。一个十岁女孩，你能信吗？她妈妈死后疯了，她爸爸拥有这家报纸，确信她是预言家，所以不知道时就问卢娜·洛夫古德并相信她说的任何话。"
……
德拉科咆哮道。"她对马尔福家有种变态的迷恋，她爸爸在政治上反对我们，所以刊登她的每句话。等我长大，我就强奸她。"</p>
</blockquote>

<p>所以哈利泽尔是在开玩笑（后来明确），但到底怎么回事？这些升级的友谊测试很做作，互惠在你不立即提出要求时才有效，这就是为什么在杂货店拿到免费样品时，柜台人员不会说"喜欢吗？买这果汁否则我们不做朋友了。"整个对话感觉笨拙，哈利泽尔不断告诉我们他们在使用的操纵技巧。这不像对话，更像两个刚听完烂营销研讨会的人尝试所学。还带着强奸。</p>

<p>之后，德拉科长篇大论巫师世界法律体系被马尔福等富人控制，促使哈利泽尔告诉我们只有启蒙运动衍生的国家才有法律秩序（从评论看最初有些种族主义内容后来被编辑掉了）。注意：巫师世界拥有字面意义的魔药真相药剂，但我们该相信我们的启蒙法律体系更好？这似乎是奇怪、不必要的叙事选择。</p>

<p>接着，哈利泽尔试图用这段话将德拉科拉到科学阵营：</p>

<blockquote>
<p>科学不是挥舞魔杖念咒语运作的，它通过深刻理解宇宙运作方式，知道确切该做什么来让宇宙按你意愿行事。如果魔法像对人施钻心咒让他们按你意愿行事，那么科学就像深入了解他们，能说服他们那本就是他们的主意。这比挥舞魔杖难得多，但魔杖失败时它有效，就像钻心咒失效时你仍可尝试说服人。</p>
</blockquote>

<p>我不明白为何用说服/营销作为科学的闪光比喻，除非这是本章主题。"懂科学你就能像控制他们一样操纵人"似乎是个广泛且基本错误的声明。而这紧随哈利向德拉科解释登月之后。科学能带你上他妈的月球，也许这就够了。</p>

<p>本章还引入了滑稽茶，一个有点聪明的双关饮料。打开罐子时，你总会在喝完前喷出一口。我不确定这个新魔法引入的目的，希望哈利泽尔有空探索它（真的，希望哈利泽尔开始探索魔法规则的任何东西。我读了整整7章，这同人小说对科学只是空谈，却未用它探索魔法）。</p>

<p>章节摘要：哈利泽尔到达9¾站台，莫名抛弃罗恩，视其为低等生物。与德拉科的对话被框定为哈利泽尔和德拉科之间明确的对话即操纵，且非常笨拙，但幸运的是哈利泽尔向我们保证这是真正的巧妙操纵，反复说这样的话：</p>

<blockquote>
<p>哈利忍不住注意到，与德拉科相比，他抵抗操纵/挽回面子/炫耀的尝试显得多么笨拙、尴尬、不优雅。</p>
</blockquote>

<p>给感兴趣的读者作业：下次你见新朋友时，分享些令人尴尬的私事，然后立即要求对方互惠，明确说"这会让我们成为好朋友"。看看效果如何。</p>

<p>人们到底看中什么？若非我们明显该认同哈利泽尔，这不会那么糟——他视德拉科为他想拉拢的人，并立即无视某人（毫无"贝叶斯更新"），视其为非人类。老实说，我惊讶有人读过这章。但为后世，我愿跋涉。再来两杯威士忌，然后开始第8章。我可能需要酒精才能继续。</p>

<p>附注：我有意识地未提所有"统治世界"的哈利泽尔引用，但每章可能有3到4次。起初像烂笑话，但反复出现太多次，我认为哈利泽尔的明确目标可能不是由好奇心驱动（弄清魔法规则），而是权力驱动（找出魔法规则以统治世界）。他向德拉科保证自己是拉文克劳，但若写得一致也许他无需如此？哈利泽尔不问问题（像我想象的拉文克劳那样），他给答案。至今他一直断定巫师世界没什么可教他。他找到的算术书只到三角函数等。他确实只表现出有限的好奇心。为何一个好奇心驱动、科学家角色会如此渴望给德拉科·马尔福留下印象并操纵他，如文中所写，这对我很模糊。这看起来不像科学的赞歌，更像某种奇怪版的《人性的弱点》。</p>

<h3 id="a-few-observations-regarding-hariezer-yudotter">关于哈利泽尔·波特的几点观察</h3>

<p>昨晚醉读第8、9、10章后（我很快会发文），我换台时不知怎的选了一集老电视剧《凡人琐事》（斯蒂夫·厄凯尔主演，稍安勿躁，这会有关联）。</p>

<p>那集里，酷小子埃迪打台球被坑，厄凯尔进来救场，因为他懂三角函数和几何，成了台球大师。</p>

<p>我想这或许是科学崇拜者的共同梦想——若我懂所有科学，我将所向披靡。哈利泽尔·波特是这种梦想的许愿实现角色。哈利泽尔并非真由好奇心驱动，他想通过学习更多科学增强超能力。这就是为何我们读了10章他妈的，波特还没真正探索太多魔法科学（目前我统计到10章里只有懒散的一段探索袋子能做什么）。这就是为何他不断把项目描述为"统治世界"。令人沮丧的是，这显然不是待克服的缺陷，而是波特"超凡"的一部分。</p>

<p>我有科学博士学位，它赋予我这些现实超能力：</p>

<ol>
<li>我能修管道、自己做房屋维修等。</li>
<li>我用乐高和树莓派做了个能玩井字棋的机器人（我的机器人伙伴，我猜）</li>
<li>通过我在某些书中学到的技术（在虚构世界中哈利泽尔用其成为操纵大师），我能优化网页广告，使高达3%的人点击（老实说，现实中的影响力。不是汉尼拔·莱克特而是广告商试图多榨出零点一成转化率），公司有时为此付我钱</li>
<li>若你给我大量数据，我能让计算机找出规律，公司有时为此付我钱。</li>
</ol>

<p>基本就这些。当我在科学界工作时，我花了近十年计算寻找希格斯玻色子相关的各种背景过程，并帮助设计了一些理论家现在用来快速计算新过程的软件。这就是科学家从事的项目类型，多数日子既艰辛又枯燥，且无明显"工具效用"——但我真的很想知道希格斯场是否存在。</p>

<p>这就是为何我觉得波特角色不像科学家——他想更强、更有权、统治世界，但似乎不在乎答案是什么。有动力固然好，但最重要的是，你必须好奇。</p>

<h3 id="hpmor-8-back-to-the-inoffensive-chapters-of-yestesteryear">HPMOR 8：回到昔日平淡无奇的章节</h3>

<p>又一次戏剧性基调转变，我们回到基本不冒犯人的章节。</p>

<p>本章又一次教学，这次是确认偏差（尽管尤德科夫斯基/哈利泽尔称之为"正面偏差"），但再次，教学选择很奇怪。哈利泽尔向赫敏讲解时，她想：</p>

<blockquote>
<p>她开始反感男孩那优越的语气……但这次要于找出她做错了什么。</p>
</blockquote>

<p>所以尤德科夫斯基知道他的哈利泽尔有居高临下的语气，但他继续了。所以作为读者，若我已懂内容，我能站在真理与正义一边，和哈利泽尔一起对傻瓜居高临下；或者，我不懂内容，那赫敏就是我的替身，我必须忍受居高临下以学习。</p>

<p>通常，当你想教人东西时，立即让他们处于防御状态不是好主意——我从未站在班级前或辅导某人时说：</p>

<blockquote>
<p>"可悲的是你可能完全照书做了……除非你读极好的书，否则它们不会完全教你如何正确做科学……</p>
</blockquote>

<p>尤德科夫斯基知道他的语气令人生厌并指出这点。所以我疑惑——这故事真在教人东西吗？还是只是让已懂些内容的人感觉优于哈利泽尔的众多陪衬？人们是去读序列以便从哈利泽尔陪衬转变为哈利泽尔视角吗？（非修辞问题，若有想法请告知）。</p>

<p>至于本章其余部分——很高兴看到赫敏作为人类被认可，不像罗恩。有段奇怪情节，纳威请格兰芬多级长帮忙找他的蟾蜍，级长拒绝（为什么？这对叙事有何目的？）。</p>

<p>章节摘要：哈利泽尔在去霍格沃茨的火车上遇到纳威和赫敏。仍无魔法规则的实际探索。没有原著的趣味糖果。</p>

<h3 id="hpmor-9-and-10">HPMOR 9 和 10</h3>

<p>编辑：我醉酒犯了个错，<a href="http://thinkingornot.tumblr.com/post/104047979906/chapters-9-and-10" rel="noopener noreferrer">见此回应</a>。我认为我的原论点仍成立，因为分院帽对敲诈企图的回应是：</p>

<blockquote>
<p>我知道你不会真威胁暴露我的本质，让这事件永远重复。这违背你道德的部分太强烈，无论你那部分想赢争论的短期需求如何。</p>
</blockquote>

<p>所以分院帽不是说"我不在乎这个"，而是说"你不会这样做。" 但我的论点已大幅削弱。</p>

<p>编辑结束</p>

<p>好了，拉弗格在流淌，我再次装备好高谈阔论。</p>

<p>这些章节其实是一章分两部分。我将用它们稍反驳尤德科夫斯基的友好AI概念。有个概念叫"正交性"，说AI目标可完全独立于其智能。所以你可以说"增加幸福"，这超级优化者就能用微小快乐分子笑脸铺满宇宙，因为它擅长优化但无法评估目标。仅为此后章设舞台。</p>

<p>本章中，哈利被分院。分院帽碰头时，哈利怀疑它是否自我意识，由于些未解释的魔法帽属性，这瞬间让帽子有了自我意识。帽子发现有自我意识很不舒服，哈利泽尔担心帽子被摘下时会摧毁智慧生命。帽子向我们保证它只在乎分院。正如哈利泽尔指出的：</p>

<blockquote>
<p>它[帽子]仍只被赋予它自己奇怪的目标……</p>
</blockquote>

<p>尽管如此，哈利泽尔设法敲诈帽子——他威胁告诉所有其他孩子帽子是否自我意识。帽子让步了。</p>

<p>那么反复获得自我意识如何影响帽子分院的目标？它没有。敲诈应该失败。尤德科夫斯基想象一旦有了自我意识，帽子就无法不获得些新目标。连尤德科夫斯基都想象获得自我意识会影响你的目标。</p>

<p>本章还有些奇怪个人化的时刻，尤德科夫斯基其他写作与HPMOR间的界限完全模糊。</p>

<p>摘要：哈利被分到拉文克劳。</p>

<p>我极度沮丧的是我读了10章这东西，我们仍没有任何关于魔法规则的实验。</p>

<h3 id="hpmor-11">HPMOR 11</h3>

<p>第11章是"omake"。这是我个人的眼中钉，因为我内心是个暴躁老人。动漫文化用日语词，我们有完美好的英语词，却借用它们（kawaii/kawaisa是个大例子）。Omake是另一个。我并非不喜欢日语（我有时会说），只是普遍不喜欢不必要的外来词。我知道这是我的错，但我太想因此责怪这同人小说。</p>

<p>无论如何，我跳过额外内容，因为我只能承受这么多。</p>

<h3 id="hpmor-12">HPMOR 12</h3>

<p>本章无甚内容。邓布利多做晚宴后演讲。</p>

<p>哈利泽尔打开滑稽茶罐，在邓布利多以胡言乱语开始晚宴演讲时按要求喷出。他考虑施个咒语让他的幽默感非常特定，然后就能用滑稽茶统治世界。</p>

<p>章节摘要：开饭并用餐。</p>

<h3 id="hpmor-13-bill-and-teds-excellent-adventure">HPMOR 13：比尔和泰德的奇妙冒险</h3>

<p>《比尔和泰德的奇妙冒险》有场戏接近尾声时，他们意识到时间机器赋予他们超人能力。他们需要越狱，所以同意之后拿到钥匙并穿越时间藏好，钥匙突然出现。大喊记住垃圾桶后，他们就有垃圾桶弄晕警卫，诸如此类。他们能为所欲为。</p>

<p>总之，本章就是这个点子，但长得多。例外是我们不知道有时间机器（实际我不确定是不是，但我这比尔和泰德粉丝说就是，我下章才知道。若我是贝叶斯理性主义者，我会说赔率是π×10^^^^3对我有利）。</p>

<p>哈利泽尔醒来发现纸条说他是游戏一部分。他所见之处仿佛魔法般出现更多纸条，扣除各种游戏"分数"，些肖像有信息给他。纸条引他到一群恶霸殴打些赫奇帕奇，派突然出现供哈利泽尔攻击。最终纸条告诉他去麦格办公室，章节结束。</p>

<p>我假设下章哈利泽尔会得到时间机器，未来的哈利泽尔用它在上一章设置"游戏"戏弄过去的哈利泽尔。这是个聪明章节。</p>

<p>这章其实不错，但世界真正需要的是哈利·波特/比尔和泰德的奇妙冒险交叉小说。</p>

<h3 id="hpmor-14-lets-talk-about-computability">HPMOR 14：谈谈可计算性</h3>

<p>这章在我脑中制造了伯恩斯先生三傻症。太多想说的，不知从何说起！</p>

<p>首先，我上章稍有错。不是时间机器，而是时间机器加隐形斗篷。比尔和泰德没误导我。</p>

<p>进入正题——哈利泽尔得到时间机器（他活26小时，所以需要时间转换器调整睡眠），这促使这段：</p>

<blockquote>
<p>我说，麦格教授，你知道时间逆转的普通物质看起来像反物质吗？对，它确实像！你知道一千克反物质遇到一千克物质会湮灭，爆炸相当于4300万吨TNT吗？你意识到我自己重41千克，爆炸会产生一个巨大冒烟的陨坑，那里曾是苏格兰吗？</p>
</blockquote>

<p>值得肯定——这是正确的物理学。事实上，把量子场论中所有反物质视为正常物质在时间中倒退完全可能（虽有点过时笨拙）。例如看此图：</p>

<p><img src="https://danluu.com/images/su3su2u1/gamma.png"></p>

<p>若想象时间从图底向顶移动，我们看到两个电子在时间中前行，交换光子并改变方向。</p>

<p>但现在想象时间从左向右移动——我们看到一个电子和一个正电子相遇湮灭，然后从光子形成新电子对。但我们可以说，我们看到的其实是一个电子在时间中前行，一个电子在时间中倒退。它们"消失"的点其实是前行电子改变方向开始倒退的点。</p>

<p>这可能很困惑，若有人想看更长帖子，我清醒时可以试试。但需强调这点——我需要你知道的要点：我们目前最好的物理理论可被诠释为粒子在时间中改变方向，哈利泽尔知道这点并正确指出。</p>

<p>为何重要？因为一段后他说：</p>

<blockquote>
<p>你知道直到此刻我脑海深处一直有可怕的压抑想法，唯一剩余答案是我整个宇宙是《模拟世界》书中的计算机模拟，但现在连这也被排除了，因为这个小玩意不是图灵可计算的！图灵机能模拟回到过去定义时刻并从中计算不同未来，预言机能依赖低阶机器的停机行为，但你说的是现实以某种方式用尚未……发生……的信息自洽地一次性计算。</p>
</blockquote>

<p>这完全是胡说（这又是糟糕教学，要么你懂图灵可计算意味着什么，要么淹没在术语里）。本讨论中，图灵可计算意味着"能用计算机计算"。我们目前最好的物理理论（哈利泽尔已知的）允许他抱怨的这种事。量子力学和量子场论都是图灵可计算的。这不是说哈利泽尔的时间机器不需要改点物理——你肯定需要，但它几乎肯定是可计算的。</p>

<p>可计算并不意味着快速可计算（甚至可行可计算）。新宇宙可能无法在多项式时间计算（量子场论可能不行，至少其中一个问题，费米子符号问题，就不行）。</p>

<p>我不认为时间机器让P=NP。有时间机器能加速计算（你可以等计算完成，然后把答案送回时间）。但哈利泽尔的时间机器有限，它只能用于总共返回6小时，且每天只能用3次，所以我认为它通常无法在多项式时间内解决NP完全问题（6小时领先时间过后，事情按原始缩放进行）。若你对计算复杂性一窍不通，我想若有人问，我可以在非波特帖中解释。</p>

<p>但我的观点是——作者据说是AI理论家。他如何搞砸可计算性的东西？这应该是家常便饭。</p>

<p>关于这章我有更多要说。很快会有另一篇帖子。</p>

<p>编辑：我原没懂P=NP论点，但现在懂了（感谢Nostalgebraist），思路是你说"我要计算某个NP问题并带答案回来"，然后嗖，另一个你从时间机器跳出，递给你写着答案的纸条。现在你有6小时验证计算，然后嗖回去把答案给你过去的自己。</p>

<p>但NP中任何问题在P中可检查，所以任何小到能在6小时内检查的问题（包括NP中大量问题）现在能立即计算。这不是通用P=NP，但比我想象的应用更广泛。</p>

<h3 id="hpmor-14-continued-comed-tea-newcomb-s-problem-science">HPMOR 14 续：滑稽茶、纽康姆问题、科学</h3>

<p>LessWrong的一个奇怪执念是个旧决策理论问题叫纽康姆悖论。这样：一个持续正确预测的超级智能挑战你游戏。有两个盒子，A和B。你可以只拿一个或两个都拿。</p>

<p>盒A里有10美元，盒B里超级智能已放入10,000美元，当且仅当它预测你只拿盒B。你该拿哪个盒子？</p>

<p>这个悖论的原因是一群人（称他们为因果主义者）可能决定，因为超级智能已做出预测，你不妨两个都拿。你无法改变过去的预测。</p>

<p>其他人（称他们为LessWrongian）可能说："嗯，超级智能总是对的，所以显然我拿盒B会得更多钱。" 这派包括LessWrongian。尤德科夫斯基本人曾试图形式化一个选择盒B的决策理论，允许原因在时间中倒退传播。</p>

<p>第三类人（称他们为"su3su2u1主义者"）可能说"这问题有问题。超级智能的概念可能根本不成立，取决于你对决策如何产生的模型。" 原因是——想象人类决策可能很嘈杂。例如，若我抛公平硬币决定拿哪个盒子。现在超级智能只有50%几率成功预测我拿哪个，这与前提矛盾。</p>

<p>还有简单方法证明问题可能有问题。想象我们有另一个同等水准的超级智能（称第一个为1，第二个为2）。1向2提供同样游戏，现在若预测1把钱放盒B，2就两个都拿；若1没放钱，2只拿盒B。显然，要么智能1错，要么智能2错，这与前提矛盾，所以这概念必然不一致（注意，你可以通过使盒子透明将任何人变成超级智能2号）。</p>

<p>总之，尤德科夫斯基有个钟爱的决策理论，他试图形式化允许原因在时间中倒退传播。他喜欢这方法，因为你能每次都得到LessWrongian式的纽康姆答案。问题是，他的形式主义因我提出的完美预测者不一致问题而存在各种不一致。</p>

<p>我为何提这个？因为哈利泽尔在本章决定滑稽茶必然是通过在值得喷水的事情发生前让你喝它来运作。茶预测了幽默，然后用魔法让你喝它。当然，他没做任何实验验证这假设（讽刺的是几章前他还就只做一次实验向赫敏说教）。</p>

<p>所以毫不奇怪，故事中迄今最常用的魔法物品体现了尤德科夫斯基最钟爱的决策理论问题。</p>

<p>本章最后一点——哈利泽尔对我们说，关于大脑理解时间旅行的能力：</p>

<p>现在，他首次面临可能成为永久的谜团……完全可能他的大脑永远无法理解，因为他的大脑由老式线性时间神经元构成，而这被证明是现实的贫乏子集。</p>

<p>这似乎误解了数学及其与科学的关系。我无法可视化四维弯曲空间，肯定不如我可视化二维三维物体。但这不妨碍我将其描述为数学对象并处理。</p>

<p>时间已非常奇异且无法可视化。但数学允许我们超越大脑可视化的局限，创建符号和语言，让我们处理任何能形式化且有连贯规则的事物。它惊人地强大。</p>

<p>我从未想过会看到哈利泽尔·波特——几章前还声称科学能让我们完美操纵和控制人（比钻心咒更强之类的）——争论科学/数学无法处理线性时间。</p>

<p>我希望这是个时刻，在后续章节看到波特成长，重新审视这最后的假设。我希望他做些实验验证滑稽茶假设。目前看来实验是哈利泽尔让周围人做的事（以便他们看到他的方式），但对他纯逻辑就够了。</p>

<p>章节摘要：我喝三杯威士忌。哈利泽尔得到时间机器。</p>

<h3 id="hpmor-15-in-which-once-again-i-want-more-science">HPMOR 15：再次，我想要更多科学</h3>

<p>我写了长篇但网络本周早些时候吞噬了它，所以这是第二遍。预先道歉，本帖主要是我推测些魔法科学。</p>

<p>本章开始期待已久的魔法课程。今日课程主题主要是不要将常见物品变形为食物或饮料。</p>

<blockquote>
<p>"波特先生，假设学生将木块变形为一杯水，你喝了它。变形失效时你认为会发生什么？"停顿。"抱歉，我不该问你，波特先生，我忘了你拥有异常悲观的想象力——"
"我没事，"哈利说，吞咽困难。"所以第一个答案是我不知道，"教授赞许地点头，"但我想象可能有……木头在我胃里，在我血液里，若任何水被吸收到身体组织——会是木浆还是实心木，或者……"哈利对魔法的理解失败了。他无法理解木头如何首先变成水，所以无法理解水分子被普通热运动打乱、魔法失效、映射逆转后会发生什么。</p>
</blockquote>

<p>我们得到类似的警告关于变形为任何气体或液体：</p>

<blockquote>
<p>你绝对绝对不能将任何东西变形为液体或气体。不要水，不要空气。不像水的东西，不像空气的东西。即使不是用来喝的。液体蒸发，小部分进入空气。</p>
</blockquote>

<p>不幸的是，再次，我希望作者更进一步。探索些真正的科学！若那木水在体内变回木头会发生什么？</p>

<p>所以让我们一起长距离短距离推测下，试着猜猜可能发生什么。首先，我们假设魔法在转变时吸收任何主要能量差异并平滑任何问题。否则当你用巫术将大木分子放入小水分子位置时，分子相互排斥（称为位阻不匹配）会突然产生大量能量，可能引发各种问题（比如人爆炸）。</p>

<p>为开始回答，我们必须选择转变规则。假设每个水分子变成一个"木分子"（木头在分子尺度定义模糊，它由大量东西构成。但那东西主要是叫多糖的长链碳氢化合物）。</p>

<p>所以你喝水，它很快被身体吸收（任何留在肠道未吸收的就变成更多膳食纤维）。一段时间后，它会扩散全身，被细胞吸收，然后这些非常分散的水分子会变成多糖。幸运的是你的身体可能知道如何处理这，多糖已遍布细胞。也许有点意外，你可能没事。我觉得对许多有机物，用一种有机分子替换另一种可能不太伤你。当然，若你换成毒物，那就另当别论。</p>

<p>现在，我有点作弊——我可能选择另一个你肯定会死的规则。想象将整片木屑换成每个水分子。你会被撕碎。魔法的细节在这里很重要，所以也许未来章节会给我们重访所需的信息。</p>

<p>若我们不用木头，用无机物如金子会怎样？若水分子变成元素金（且你不会因上述位阻不匹配爆炸），只要金不电离你就会没事。元素金极其稳定，需要相当多金才能产生重金属中毒。</p>

<p>另一方面，若它电离你可能会死。金盐（在你体内分裂为离子金+其他东西）的半数致死量（杀死半数服用者的剂量）仅每公斤几毫克，所以70公斤的人无法承受超过5克左右的盐，即更少的离子金。所以在这种情况下，咒语失效后你就会开始中毒。几小时后，你可能开始出现肝衰竭症状（黄疸等）。</p>

<p>水化学/物理很难，所以我不知道金原子是否会电离。更大的金晶体肯定不会，他们正研究用金纳米颗粒做医学，这也大多无毒。然而，单个原子仍可能电离。</p>

<p>若我们不喝水呢？若我们只靠近蒸发的液体？结果没多大事。蒸发是缓慢过程，扩散也是。</p>

<p>扩散常数通常是几厘米²/秒，扩散是随时间平方根前进的缓慢过程（移动两倍远需要4倍时间）。</p>

<p>所以即使变形为水持续一小时，蒸发的单个水分子移动将不到100厘米！所以除非你站得脸非常靠近杯子，你不太可能遇到甚至一个蒸发分子。即使你脸紧贴杯子，那分子可能只是被吸入又呼出。你肺部有很多解剖死腔，那里不进行交换，且活跃区域优化了氧气吸收。</p>

<p>那么变形为气体呢？那里会发生什么？再次，这取决于我们如何选择魔法规则。制造气体时，它在室温和压强下来吗？若如此，这设定了密度。然后你可以带入与原物等量但分子极少的气体，或带入等量分子但密度极大的气体。</p>

<p>分子量相等时，你会得到数百升稀薄气体。你的肺只能容纳约5升，所以你会比水得到小得多的剂量（最多百分之几），水所有分子都被身体吸收。此外，你的肺不会吸收大部分气体，很多会被呼回，进一步降低剂量。</p>

<p>若与原物体积相等，那气体会非常稀少，扩散论证适用——除非你非常靠近制造气体的地方，你不太可能吸入任何。</p>

<p>推测性魔法科学猜测到此结束。若我让你无聊了，抱歉。</p>

<p>总之——我承认这章足够有趣让我花时间思考若体内未变形的东西会发生什么。其实不坏章节，但它仍显得有点懒。我们得到些对液体蒸发的模糊担忧（科学！）但没做数量级估算它是否重要（不重要，除非你制造的液体沸腾）。有许多科学理念作者可以玩，但都被搁置一边。</p>

<p>至于本章其余内容，哈利泽尔被赫敏比下去，她表现优于他且已读完课本。一场成绩竞赛启动。</p>

<h3 id="hpmor-16-shades-of-ender-s-game">HPMOR 16：安德的游戏的影子</h3>

<p>抱歉HPMOR的长暂停，有时我正职召唤。</p>

<p>本章主要包括哈利泽尔的首节黑魔法防御术课。我们首次见到超级能干的奇洛（尽管像原著其实是超级能干的伏地魔）。</p>

<p>课程以令人惊讶的反学术情绪开场——奇洛长篇大论你无需学习防御巫师世界特定威胁，因为你可以魔法逃跑或直接用阿瓦达索命咒，所以整个"部颁"课程和"无用"课本是不必要的。当然，这出自表面上的反派之口，所以不确定我们该对此情绪多反感（尽管哈利泽尔鼓掌）。</p>

<p>之后我们进入课程。教完一个攻击咒语后，奇洛让赫敏（她掌握最快）攻击另一个学生。她拒绝，所以奇洛转向马尔福，他爽快答应用魔杖射击赫敏。</p>

<p>然后奇洛让哈利泽尔当众出丑，事情变得有点怪。当被问及日常物品的非常规战斗用途时，哈利泽尔列出一长串古怪杀人方式，奇洛指出对哈利泽尔·波特而言没有防御——他只寻求摧毁敌人。这感觉非常《安德的游戏》（哈利泽尔赢了，这让他危险），且是愚蠢时刻。</p>

<p>章节摘要：怪异的反学术黑魔法防御术课。我们再次得到魔法，但无魔法规则。</p>

<h3 id="hpmor-17-introducing-dumbledore-and-some-retreads-on-old-ideas">HPMOR 17：引入邓布利多与些旧观念复述</h3>

<p>本章以哈利泽尔尝试用时间转换器验证NP完全问题开场，如前章节讨论。因为是旧话题，我们不复述。</p>

<p>从此处我们进入首次飞行课，过程类似书，但带点精英主义。哈利泽尔抛出这段：</p>

<blockquote>
<p>他第一次尝试就能掌握的东西不可能让赫敏困惑，若真有且是飞行扫帚而非任何智力活动，哈利就死定了。</p>
</blockquote>

<p>这感觉有点像早前对罗恩的彻底否定。所以反运动员哈利泽尔——不愿被认为擅长飞行扫帚——未参与抢夺纳威的记忆球比赛，而是全班陷入对峙，魔杖指向对方。所以哈利泽尔挑战持有记忆球的斯莱特林进行奇怪决斗。以恰当的比尔和泰德方式使用时间转换器，他藏了个假记忆球并赢了。这都是老套路，我开始担心太阳底下无新事——更多时间转换器，更多哈利泽尔获胜（以防我们没懂，有段与麦格的对话，哈利泽尔再次意识到他甚至没考虑不赢）。</p>

<p>然后我们见到邓布利多，他被写成懒人版的疯狂。他会说些睿智的话，丢句《指环王》引文，然后立即做些疯狂事。一刻他试图解释哈利可以信任他，下一刻他点燃一只鸡（没错，这发生了）。在一个令人困惑的时刻，他给哈利泽尔一块大石头，发生这段对话：</p>

<blockquote>
<p>"那么……为什么我非得拿这块石头？"
"我想不出理由，真的，"邓布利多说。
"……你想不出。"
邓布利多点点头。"但仅因我想不出理由，不代表没理由。"
"好吧，"哈利说，"我甚至不确定该不该说，但那确实不是处理我们承认无知的宇宙运作方式的正确方法。"</p>
</blockquote>

<p>现在，若有人给你块沉重大石头说"带着以防万一"，你如何开始告诉他们错了？这是哈利泽尔的方式：</p>

<blockquote>
<p>如何正式表达……嗯……假设你有一百万个盒子，只有一个盒子装着钻石。你有一盒钻石探测器，每个探测器在钻石面前总会响，在没钻石的盒子前有一半时间响。若你用二十个探测器检测所有盒子，平均剩下一个假候选和一个真候选。然后只需一两个探测器就能剩一个真候选。要点是当有很多可能答案时，你需要的大部分证据用于从数百万可能性中定位真假设——首先让它引起你注意。判断两三个可信候选所需的证据量相对小得多。所以若你没有证据就跳到某个可能性上，你就跳过了大部分工作。</p>
</blockquote>

<p>感谢上帝哈利泽尔能用他的高级推理能力做个钻石在盒子里的类比，解释为何没理由带石头的想法很蠢。这是本章的理性概念——说真的，尤德科夫斯基这次似乎根本没努力。</p>

<p>章节摘要：哈利泽尔嘲笑飞行扫帚，些（现成标准）时间转换器诡计，哈利泽尔遇到比智慧更疯狂的邓布利多</p>

<h3 id="hpmor-18-what">HPMOR 18：什么？</h3>

<p>我想讨论过去几章奇怪的反大学/学校制度暗流，但我开始读第18章时它让我崩溃了。</p>

<p>这章绝对荒谬。我们首次见到斯内普，他的行为如你对原著的预期。他讽刺地问哈利泽尔一堆哈利泽尔不知道答案的问题。</p>

<p>这导致哈利泽尔发飙：</p>

<blockquote>
<p>全班完全僵住。
"禁闭一个月，波特，"西弗勒斯说，笑容更灿烂。
"我不承认你作为教帘认可你的权威，我不会服你的任何禁闭。"
人们停止呼吸。
西弗勒斯的笑容消失了。"那你会被——"他的声音戛然而止。
"开除，你本想说？"哈利泽尔，另一方面，现在正冷冷地微笑。"但你似乎怀疑自己执行威胁的能力，或害怕后果。我呢，既不怀疑也不害怕找所没那么虐待的教授的学校。或者我该雇私人家教，像我习惯的那样，按我全速学习。我金库有足够钱。关于我击败的黑魔王的赏金之类。但霍格沃茨有我相当喜欢的教授，所以我觉得想办法摆脱你更容易。"</p>
</blockquote>

<p>想想这个——斯内普所做的唯一事情是说句刻薄话和问哈利泽尔一系列他不知道答案的问题。</p>

<p>情况持续升级，直到哈利泽尔把自己锁在壁橱，用隐形斗篷和时间转换器逃出教室。</p>

<p>这导致与校长会面，哈利泽尔威胁发起报纸运动反对斯内普（找家报纸对"些学生认为教授对他们太苛刻，例如他连续问哈利泽尔·波特三个难题"的故事感兴趣）</p>

<p>每个人都认真对待这威胁，好像它能造成真正伤害。哈利泽尔反复说他在保护学生免受虐待。他们认真对待这威胁到哈利泽尔与斯内普和邓布利多谈判停战的程度。斯内普同意减少纪律要求，哈利泽尔同意道歉。</p>

<p>本章中哈利泽尔从未考虑他剥夺了其他学生该死的魔药课。在他思考斯内普为何保有职位时，他从未考虑斯内普可能精通魔药/是真正的魔药教授。</p>

<p>整章基本是愚蠢的权力斗争，要求本章中每个人都行为荒谬愚蠢。哈利泽尔发两岁孩子般的脾气，其他人则满足他。</p>

<p>正面是麦格锁住了哈利泽尔的时间转换器，所以希望这设备消停会儿，它已是好几章每个问题的"聪明"解决方案。</p>

<p>再来一章这么糟的我可能不得不终止这项目。</p>

<h3 id="hpmor-19-i-can-t-even-what">HPMOR 19：我无语……什么？……</h3>

<p>我……这……什么……</p>

<p>这里我本可说很多，关于角色不一致、事件荒谬牵强等。但算了——本章关键事件是：奇洛其实是武术大师（因为当然，谁在乎类型一致性或不必要的细节，堆更多"超凡"）。他声称从武术（在神秘道场，因为当然）学的教训是哈利泽尔需要学的（由他与斯内普的遭遇证明）——如何输。</p>

<p>奇洛如何教哈利</p><p><em>由 mimo-v2.5 模型翻译，花费 86614 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/su3su2u1/hpmor/</link>
      <guid isPermaLink="false">https://danluu.com/su3su2u1/hpmor/</guid>
      <pubDate>Tue, 1 Mar 2016 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>su3su2u1 物理学 tumblr 存档</title>
      <description>[AI 摘要] 本文是已关闭博客的存档，通过路径与作用量等概念，以非数学方式向读者介绍量子力学和相对论的核心思想。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文是已关闭博客的存档，通过路径与作用量等概念，以非数学方式向读者介绍量子力学和相对论的核心思想。</div><p>这些文章存档自现已关闭的 <a href="http://su3su2u1.tumblr.com/" rel="noopener noreferrer">su3su2u1 tumblr</a>。</p>

<h3 id="a-roundabout-approach-to-quantum-mechanics">迂回走进量子力学</h3>

<p>我希望这将是一个系列的开篇，概述一些来自量子力学的思想。我会尽量保持轻松，避免充斥数学公式——这意味着我并没有真正在教你物理。我是在向你传授某种物理的“风味”。我最初在这里写道“你不能指望仅仅品尝过冰淇淋就能制作它”，但我觉得更好的描述可能是“你不能指望仅仅听人描述过冰淇淋的味道就能制作它”。而且请，请，请提问！我已经习惯了在（尝试）教学时获得即时反馈，所以如果读者们无法从中有所收获，我想停止或改变点什么。</p>

<p>现在，不幸的是，我无法在不先谈论经典物理的情况下直接开始讲量子力学。大多数人自认为了解经典力学，因为他们是在母亲膝上就学到了它，但是经典物理的表述方式实在太多、太多了，大多数物理专业的学生直到学了量子力学之后才会看到一些真正重要的表述（特别是哈密顿力学和拉格朗日力学）。这很愚蠢，但与此同时大学只有四年。我不可能教你所有这些庞大的主题，但我将需要依赖粒子和光的一些性质。并且与入门的牛顿力学不同，我想聚焦于“路径”。与其问诸如“一个粒子从这里以某个速度出发，它会去往何处？”之类的问题，我更想聚焦于“一个粒子从这里出发，最终到达那里。它走了哪条路径？”</p>

<p>所以我们今天从光开始，谈论一个我相当喜欢的主题。早些时候，在“智者狙击”了好几代数学家之前，费马提出了一个优美的光学表述——</p>

<p>光总是走耗时最少的路径</p>

<p>我听到一个反对意见：“那不就是走直线吗？”我们必须将这个洞见与光在不同介质中以不同速度传播的概念结合起来。例如，我们知道光在水中的速度会降低约 1.3 倍。</p>

<p>所以，让我们来看一个实际问题：你看到一条鱼在水中游动（提前为这些图表道歉）：</p>

<p><img src="https://danluu.com/images/su3su2u1/image_001.jpg"></p>

<p>我画出了（难以看清的）你的眼睛和鱼之间的虚线直线。</p>

<p>但光并不是这么走的——存在一条能节省光一些时间的路径。光在空气中的速度比在水中快，所以它可以在空气中走更远，而在水中走到鱼的路径更短。</p>

<p><img src="https://danluu.com/images/su3su2u1/image_002.jpg"></p>

<p>这是更现实的光路——它在接触水面时发生弯曲——它这样做是为了在水中点和空气点之间走耗时最少的路径。留给数学读者的一个练习——你可以定量地推导出来，仅仅从最小时间原理出发就能推导出斯涅尔定律（折射定律）。</p>

<p>再举一个更实际的例子：透镜。它们是如何工作的？</p>

<p><img src="https://danluu.com/images/su3su2u1/image_003.jpg"></p>

<p>所以中间那部分是透镜，我们看到的是从点 1 离开并到达点 2 的光路（或者反过来，我想）。</p>

<p>透镜中间最厚，所以虚线路径在那里被减慢得最多。路径 b 更长，但它在透镜中停留的时间更短——这意味着通过仔细设计透镜的形状，我们可以使路径 b 的时间与虚线路径的时间相等。</p>

<p>路径 a 是最长的路径，它仅仅擦过透镜，因此几乎没有被减慢，所以它也可以被设计成与虚线路径（和路径 b）花费相同的时间。</p>

<p>所以，如果我们仔细设计我们的透镜，所有接触透镜的耗时最短的路径最终都会聚焦回一个点。</p>

<p>以上就是光的最小时间原理。等我下次再发布这个话题时，我们将讨论粒子。</p>

<p>现在，这类文章需要花费一些精力，所以请，请，请告诉我你是否从中有所收获。</p>

<p>编辑：如果你没有从中得到任何东西，因为它令人困惑，请提问。提很多问题，任何你想问的问题都行。</p>

<h3 id="more-classical-physics-of-paths">更多关于路径的经典物理</h3>

<p>经过一些思考，这些文章的结构可能会先讨论光，然后转向物质，逐个主题进行。这可能不是最好的结构，但至少给了我一个组织思路的框架。</p>

<p>如同所有的物理文章，请提问。我在这里对我的读者不太了解，所以任何反馈都很感激。另外，在清晰性和准确性之间存在某种不确定性原理。我可以做到非常清晰或非常准确，但无法同时做到两者。我希望在这里走中间路线。</p>

<p>上次，我提到几何光学可以通过一个简单的原理来表述：光走耗时最少的路径。这与你习惯的许多物理理论有点不同——通常问题会表述为“爱丽丝从位置 x 以速度 v 扔出一个橄榄球，鲍勃需要站在哪里才能接住？”也就是说，我们从初始位置和速度开始。基于路径的问题通常是“一个粒子从位置 x_i, t_i 开始，在位置 x_f, t_f 结束，它走了哪条路径？”</p>

<p>对于经典的非相对论力学，基于路径的表述相当简单：我们构造一个称为“拉格朗日量”的量，它定义为动能减去势能（KE - PE）。回想一下，动能是 1/2 mv²，其中 m 是粒子的质量，v 是速度，势能取决于具体问题。如果我们沿着一条路径将每一时刻的拉格朗日量加起来，会得到一个称为作用量（S 是作用量的常用符号，不知为何）的量，而粒子走的是作用量最小的路径。如果你懂微积分，我们可以将其表述为</p>

<p>[S = \int KE-PE dt ]</p>

<p>作用量的单位是能量*时间，这在后面的文章中会很重要。</p>

<p>信不信由你，牛顿的所有定律都包含在这个最小化原理中。例如，考虑一个不受外界影响（没有势能）运动的粒子。这样的粒子必须在它所走的路径上最小化其 v²。</p>

<p>任何偏离直线的运动都会导致路径长度增加，因此粒子平均需要移动得更快才能到达目的地。我们要最小化 v²，所以我们可以立即推断出粒子将走直线路径。</p>

<p>但它的速度呢？粒子是否应该非常缓慢地移动以减少行进中的 v²，然后在接近终点时“踩油门”？或者以恒定速度移动？很容易证明最小作用量对应的是恒速路径（你自己试试看！）。这给了我们牛顿第一定律。</p>

<p>你也可以考虑一个球被垂直抛向空中的情况。它应该走什么路径？现在我们有势能 mgh（其中 h 是高度，g 是重力常数）。但记住，我们在作用量中减去势能——所以粒子可以通过爬得更高来降低其作用量。</p>

<p>在重力场中沿最小作用量的路径，粒子将在高处 h 移动得慢，以便在低作用量区域花更多时间，并在 h 减小时加速（它需要有一个足够大的平均速度以确保准时到达目的地）。如果你懂变分法，你可以计算出所需的关系，你会发现这恰好恢复了牛顿的关系（粒子的加速度 = g）。</p>

<p>为什么要费心用这种表述？它让许多问题变得更容易。有时指定所有的力很棘手（想象一个珠子在一个金属环上滑动。环约束珠子沿圆环运动，所以力恰好是防止珠子离开环所需的任何力。但如果使用正确的坐标，能量可以非常容易地写出）。而且在某些对称性下它优雅得多（这个话题我留给另一篇文章）。</p>

<p>所以总结这两篇文章——光走耗时最少的路径，粒子走作用量最小的路径。（一种理解方式是，光的拉格朗日量是常数。这意味着降低作用量的唯一方法是找到耗时最短的路径）。这是我后面需要的要点——在经典物理中，粒子走作用量最小的路径。</p>

<p>我感觉这比以前的文章更令人困惑，因为很难计算具体的例子。如果有问题请提问。</p>

<h3 id="semi-classical-light">半经典光</h3>

<p>和往常一样，数学在 tumblr 仪表盘上无法正确渲染，但在博客上可以。这篇文章包含了整个系列的核心论点，所以努力理解这个论证非常重要。</p>

<p>回想一下我在第一篇文章中写的，几何光学的一个特别优雅的表述是费马原理：</p>

<p>光走耗时最少的路径</p>

<p>但是，一个年轻的实验物理学家（双关语非常明显！）说，看当我让光通过两个狭缝时，我得到这样的图案：</p>

<p><img src="https://danluu.com/images/su3su2u1/image_004.jpg"></p>

<p>光一定是波。</p>

<p>“等等，等等，等等！”我能听到你在说。为什么这个双缝实验意味着光是波？</p>

<p>让我们谈谈波的关键特征——当波相遇时，它们可以以不同的方式组合：</p>

<p>TODO：链接失效。</p>

<p>所以当物理学家想要表示波时，我们需要考虑的不仅是波的高度，还有波的相位。波可以处于“波峰”、“波谷”或两者之间的任何位置。</p>

<p>我们使用的技术称为“相量”（不要与光束混淆）。我们将波表示为在圆圈中旋转的小箭头：</p>

<p>TODO：链接失效。</p>

<p>箭头的长度 A 称为振幅，代表波的高度。角度 (\theta) 代表波的相位。（我们当中数学造诣较高的人会认出这些是形式为 (Ae^{i\theta}) 的复数）。用这些箭头，我们可以捕捉到波的所有相加/相减/部分相加的特征：</p>

<p>TODO：链接失效。</p>

<p>那么我们如何用它来解释双缝实验呢？首先，我们假设所有来自同一光源的光具有相同的振幅。光具有特征周期 T。光从“波谷”回到“波谷”需要 T 秒。</p>

<p>在我们的相量图中，这意味着我们可以将 t 秒后光的相位表示为：</p>

<p>[\theta = \frac{2\pi t}{T} ]</p>

<p>注意，这里我们用弧度表示角度。2π 是一个完整的圆。这样当 t = T 时，我们刚好转了一圈。</p>

<p>我们也知道光以速度 c 传播（c 是“光速”，毕竟）。所以当光走完长度为 L 的路径时，它传播的时间可以很容易地计算为 (\frac{L}{c})。</p>

<p>现在，让我们看一些可能的路径：</p>

<p><img src="https://danluu.com/images/su3su2u1/image_005.jpg"></p>

<p>光从左边的点出发，穿过两个狭缝，到达点 X。现在，对于屏幕中央的点 X，两条路径长度相等。这意味着波到达时没有相位差，它们叠加在一起。我们预计屏幕中心会有一个亮点（而且我们确实得到了）。</p>

<p>现在，让我们看看屏幕上更靠上的点：</p>

<p><img src="https://danluu.com/images/su3su2u1/image_006.jpg"></p>

<p>当我们远离中心时，路径长度不同，到达的光存在相位差：</p>

<p>[\theta_1 - \theta_2= \frac{2\pi }{cT} \left(L_1 - L_2\right) ]</p>

<p>那么会发生什么？当我们沿墙壁向上移动时，长度距离变大，相位差增大。每次相位差是 π 的倍数时，我们得到相消干涉，出现暗点。每次是 2π 的倍数时，我们得到亮斑。这完全符合杨的结果。</p>

<p>但是等等，我听到一个聪明的学生插话了（我们称他为费曼，但在这里更合适的叫法可能是惠更斯）。费曼说：“如果有三个狭缝呢？”</p>

<p>那么，我们就必须将三个不同狭缝的相量相加。这是更多的代数运算，但当它们都对齐时，是亮斑；当它们都抵消时，是暗点，等等。我们甚至可以有两两抵消而一个不抵消的地方。</p>

<p>“但是，如果我开第四个孔呢？”我们把四个相量相加。“第五个？”我们把五个相量相加。</p>

<p>“如果我钻无数个孔呢？那屏幕就不存在了！难道我们不应该恢复几何光学吗？”</p>

<p>啊！非常聪明！但我们确实恢复了几何光学。想想如果我们把无数条路径相加会发生什么。我们本质上是在将无数条具有相同振幅的随机相量相加：</p>

<p><img src="https://danluu.com/images/su3su2u1/image_007.jpg"></p>

<p>所以我们预计所有这些随机路径会相互抵消。</p>

<p>但是有一个巨大的例外。</p>

<p>那些随机的角度是因为当我们取一条任意路径时，光在该路径上花费的时间是随机的。</p>

<p>但是，在最小值附近会发生什么？如果我们参数化我们的随机路径，在最小值附近，行程时间与参数的关系图看起来像这样：</p>

<p><img src="https://danluu.com/images/su3su2u1/image_008.jpg"></p>

<p>图在最小值附近变得平坦，所以所有那些小 X 具有大致相同的相位，这意味着所有那些相量会叠加在一起。所以最小值路径得到强烈增强，而所有其他路径则相互抵消。</p>

<p>所以现在我们有了一条关于光的规则：</p>

<p>要计算光如何向前传播，我们对光走每一条可能路径所关联的相量求和。</p>

<p>但是，当我们有非常非常多的路径时，我们可以做一个近似。在有非常多路径的情况下，唯一不会被抵消的、唯一重要的是耗时最短的路径。</p>

<h3 id="semi-classical-particles">半经典粒子</h3>

<p>回想一下上一篇文章，我们改进了对光的理解。我们提出，光是波，这意味着</p>

<p>光走两点之间所有可能的路径，光的相位取决于光所走路径的时间。</p>

<p>进一步，这意味着：</p>

<p>在存在非常非常多路径的情况下，几乎所有路径的贡献会相互抵消。只有耗时最短的路径对结果有贡献。</p>

<p>敏锐的读者可以看出我们要往哪里走。我们已经知道经典粒子走作用量最小的路径，所以我们可以猜测一个新的规则：</p>

<p>粒子走两点之间所有可能的路径，粒子的相位取决于粒子所走路径的作用量。</p>

<p>回想一下，我们在上一篇文章中形式化这一点的方式是光的相位可以用公式</p>

<p>[\theta = \frac{2\pi}{T} t]</p>

<p>计算。我们想为粒子建立一个类似的公式，但不是时间，它必须依赖于作用量，但粒子相当于“周期”的部分我们该怎么做呢？最简单的猜测可能是常数。我们称这个常数为 h，普朗克常数（因为这就是它）。它必须具有与作用量相同的单位，即能量*时间。</p>

<p>[\theta = \frac{2\pi}{h} * S]</p>

<p>在物理学中使用稍微不同的常数 (\hbar = \frac{h}{2\pi}) 非常常见，因为它频繁出现。</p>

<p>[\theta = \frac{S}{\hbar}]</p>

<p>所以我们有了这个理论——也许粒子真的是波！我们只需让一个粒子通过双缝，就会看到像光一样的图案！</p>

<p>所以我们设置好双缝实验，向屏幕发射一个粒子，啪。我们在另一边接收到一个点。嗯？我以为我们会看到波。所以我们一次又一次地重复实验，得到了这个结果</p>

<p><img src="https://danluu.com/images/su3su2u1/image_009.jpg"></p>

<p>所以我们确实得到了预期的图案，只是随着时间的推移逐渐形成。我们该如何理解这一点？</p>

<p>嗯，有一件事似乎很明显——大量实验的结果与我们的预测非常吻合。所以我们可以将我们规则的结果解释为概率，而不是传统的完全确定的预测。但概率必须是正数，所以我们说概率与我们振幅的平方成正比。</p>

<p>所以让我们重新表述我们的规则：</p>

<p>要预测粒子在时间 t 到达点 x 的概率，我们对粒子可以走的每一条路径取一个相量，其相位取决于沿路径的作用量，并将它们全部相加。振幅的平方给出概率。</p>

<p>现在，信不信由你，这个规则与薛定谔方程（我们有些人熟知并喜爱）完全等价，也与你在入门量子书中能找到的几乎所有内容等价。它只是另一种表述。但你会注意到我在标题中称它为“半经典”——那是因为本科量子力学并没有真正涵盖完全量子的系统，但那是以后帖子要讨论的话题。</p>

<p>如果你熟悉尤德科夫斯基关于量子力学的系列文章或入门教科书，你可能习惯于将量子力学想象成随时间变化的构型空间中的概率幅团块。在这种表述中，我们的振幅与穿过时空的路径相关联。</p>

<p>下次我再有心情写的时候，我们会谈谈这个路径规则到底有多奇怪，也许还有基于路径思考的一些优势。</p>

<h3 id="basic-special-relativity">基础狭义相对论</h3>

<p>无需微积分或光，仅使用代数推导狭义相对论。注意——我基本上是在整理一些讲座笔记，所以这只是一个概要。</p>

<p>这个推导基于一个我认为伽利略首先表述的关键原理——</p>

<p>物理定律在任何惯性参考系中都是相同的，或者不存在检测绝对运动的方法。
像所有的相对论推导一样，这将涉及一个思想实验。在我们的实验中，有一列火车从一个站台移动到另一个站台。同时，一架玩具飞机也从一个站台飞到另一个站台（最初，我在这里开了一个《飞机、火车和汽车》的玩笑，但现在的孩子们看不懂这个引用了…::叹气::）</p>

<p>有两个事件，事件 1——一切都从站台的左侧开始。事件 2——一切都到达站台的右侧。整个过程中，从站台的角度看，火车以恒定速度 v 移动（对称性告诉我们，这也意味着从火车的角度看，站台以速度 v 移动）。</p>

<p>我们将从两个不同的视角来看这两个事件——站台的视角和火车的视角。目标是找出一组方程，使我们能够关联不同视角之间的量。</p>

<p>这是一个糟糕的图表</p>

<p><img src="https://danluu.com/images/su3su2u1/image_010.jpg"></p>

<p>点是玩具飞机，盒子是火车。L 是站台自身视角下的长度。l 是火车自身视角下的长度。T 是从站台的视角看火车穿过站台所需的时间。t 是从火车的视角看站台穿过火车所需的时间。</p>

<p>从站台的视角看，很容易看出火车的长度 l’ = L - vT。玩具飞机的速度 w = L/T。</p>

<p>从火车的视角看，站台的长度 L’ = l + vt，玩具飞机的速度 u = l/t。</p>

<p>所以总结一下</p>

<p>观察者 | 事件间流逝的时间 | 火车的长度 | 飞机的速度</p>

<p>站台   | T                                                  | l’ = L-vT         | w = L/T</p>

<p>火车   |  t                                                  |L’ = l+vt           | u/t</p>

<p>现在，我们再次利用对称性和我们的伽利略原理。由对称性，</p>

<p>l’/l = L’/L = R</p>

<p>根据伽利略原理，函数 R 只能依赖于 v。如果不这样，我们就能检测到绝对运动。我们可能想直接假设 R 是 1，但如果我们这样做就不够严谨了。</p>

<p>所以我们这样做——我们想用 u、v 和 R（仅依赖于 v）来写出 w 的公式。这将告诉我们如何将火车参考系中的速度与站台参考系中的速度联系起来。</p>

<p>我将跳过代数，但你可以利用上面的关系自己推导出来</p>

<p>w = (u+v)/(1+(1-R)u/v)  = f(u,v)</p>

<p>这里我用 f 来命名那个函数。</p>

<p>我会再编辑更多内容，现在发布以避免丢失我打好的东西。</p>

<h3 id="more-special-relativity-and-paths">更多狭义相对论与路径</h3>

<p>如果你没有读过我上一篇关于相对论的文章，这不会太有意义。数学在 tumblr 仪表盘上无法渲染，而是去博客看。</p>

<p>上次，我们推导了长度收缩的公式（并请你推导时间膨胀的公式）。但通常有用的是给出不同参考系之间事件关联的公式。我们的思想实验有两个事件——</p>

<p>事件 1，火车的后端、站台的后端和玩具都在同一个地方。</p>

<p>事件 2——火车的前端、站台的前端和玩具都在同一个地方。</p>

<p>从玩具的参考系看，这些事件发生在同一位置，所以事件之间只有时间差。我们称这个差为 (\Delta\tau)。我们总是用它表示“在相同位置发生的事件之间的时间”（只有一个参考系中事件会发生在同一地点），它被称为固有时。</p>

<p>现在，玩具火车看到站台以速度 -w 移动，站台的长度是 RL。所以这个关系就是时间 = 距离 / 速度。</p>

<p>[\Delta\tau^2 = R^2L^2/w^2 = (1-\frac{w^2}{c^2})L^2/w^2 ]</p>

<p>现在，我们可以通过注意从站台的视角看，L²/w² 是两个事件之间的时间，并且这两个事件相距 L，来操作等式右边。我们称站台参考系中事件之间的时间为 (\Delta t)，事件之间的距离为 L，我们一般称之为 (\Delta x)。</p>

<p>[\Delta\tau^2  = (1-\frac{w^2}{c^2})L^2/w^2 = (\Delta t^2 - \Delta x^2/c^2) ]</p>

<p>注意速度 w 已从等式的最终版本中消失——这对任何参考系都成立，因为固有时是唯一的（每个参考系都有不同的时间测量，但只有一个测量固有时），我们有了一个参考系无关的测量。</p>

<p>现在，让我们将这与之前讨论的路径概念联系起来。路径力学的一个优势是，如果我们能创建一个洛伦兹不变的作用量，那么我们得到的力学也将是不变的。所以一种我们可能考虑这样做的方式是通过观察固有时——（记住 S 是作用量）。注意负号——没有它就没有最小值只有最大值。</p>

<p>[ S \propto -\int d\tau = -C\int d\tau ]</p>

<p>现在 C 必须具有能量的单位，作用量才能有正确的单位。</p>

<p>现在，一些粗略的物理数学</p>

<p>[ S = -C\int \sqrt(dt^2 - dx^2/c^2) = -C\int dt \sqrt(1-\frac{dx^2}{dt^2}/c^2) ]</p>

<p>[S = -C\int dt \sqrt(1-v^2/c^2) ]</p>

<p>所以最后一步是注意我们可以对 \sqrt(1-v^2/c^2) 做近似，如果 v 远小于 c，则为 (1-1/2v²/c²)</p>

<p>所以总的来说，对于小 v</p>

<p>[S = C\int dt (\frac{v^2}{2c^2} - 1)]</p>

<p>所以如果我们选择常数 C 为 mc²，我们得到</p>

<p>[S = \int dt (1/2 mv^2 - mc^2)]</p>

<p>我们认识到第一项就是我们之前的动能！第二项只是一个常数，所以不会影响最小值在哪里。这给了我们对粒子路径规则的新理解——粒子走固有时最长的路径（这种对力学的理解最容易推广到广义相对论）</p>

<h3 id="special-relativity-and-free-will">狭义相对论与自由意志</h3>

<p>想象一下，就在你正在辩论是否要在 tumblr 上发布某些东西的时候，仙女座星系的一些外星人正围坐在会议桌旁讨论仙女座的事情。</p>

<p>那么，你现在的状态（在决定要 tumblr 什么）和那些外星人之间的“时空距离”是多少？</p>

<p>嗯，仙女座和我们的距离大约是 250 万光年。所以“时空距离” tau（使用我们上次的公式）是 250 万年。到目前为止，一切顺利：</p>

<p>现在，想象一个外星人，开会迟到了，正跑进来。他跑得大约 1 米/秒。我们知道对他来说长度会收缩，时间会膨胀。所以对他来说，地球上的时间实际上是更晚的——使用</p>

<p>(\Delta \tau^2 = \Delta t^2 - \Delta x^2/ c^2)</p>

<p>并使用我们的长度收缩公式，我们可以计算出根据我们在仙女座的跑步者，现在地球上的时间大约比今天晚 9 天。</p>

<p>所以与委员会在仙女座开会的同时，你正在决定要 tumblr 什么。根据那个跑步者，那是 9 天后，你已经发布了你在想的东西以及其他几十样东西。</p>

<p>所以你对自己发布什么真的有多少自由意志？（这个论证最初由 Rietdijk 和 Putnam 提出）。</p>

<h3 id="we-are-doing-taylor-series-in-calculus-and-it-s-really-boring-what-would-you-add-from-physics">我们在微积分中学习泰勒级数，真的很无聊。你会从物理中添加什么？</h3>

<p>首先，很抱歉这么久才回复。</p>

<p>总之，物理学中有一种现象，几乎所有事物都被建模为弹簧（简谐运动无处不在！）。你可以在谐振的讨论中看到这一点。波运动可以理解为弹簧耦合在一起，等等，许多系统都表现出波——当你说话时，微小的空气扰动像波一样传播出去，就像在池塘里扔一块小石头，或者晃动一根跳绳。这些都非常不同的系统，为什么我们会看到如此相似的行为？</p>

<p>为什么会这样？嗯，想象一个处于平衡状态的系统，将它从平衡位置轻轻推动一下。如果平衡位置在某个参数 a，我们轻轻推动它离开平衡位置（所以 x-a = epsilon）</p>

<p>[E(x-a)]</p>

<p>现在，我们可以进行泰勒展开——但我们注意到在平衡时能量处于最小值，所以泰勒展开的线性项为 0</p>

<p>[E(\epsilon)=  E(a) + \frac{d^2E}{dx^2}1/2 \epsilon^2 + … ]</p>

<p>现在，势能中的常数无关紧要，所以第一个重要的项是平方势能，也就是一个弹簧。</p>

<p>所以泰勒级数 -&gt; 一切都是弹簧。</p>

<h3 id="why-field-theory">为什么需要场论？</h3>

<p>到目前为止，我们在我量子类别的早期文章中学到</p>

<ol>
<li><p>经典理论可以用路径规则描述，粒子走“作用量最小”的路径。</p></li>
<li><p>我们可以通过让粒子走所有路径，并将每条路径的相位设为沿路径的作用量（除以 hbar）来将经典理论转变为量子理论。</p></li>
</ol>

<p>我们也学过，通过选择一个相对论性的作用量（对于粒子，一个与“固有时”成正比的作用量），我们可以使经典理论符合狭义相对论。</p>

<p>所以一个明显的尝试构建狭义相对论量子理论的方法是从狭义相对论性的作用量开始，并对我们用于经典理论的路径求和。</p>

<p>你可以这样做——它几乎成功了！如果你进行从我们最初的、非相对论路径到标准教科书量子力学的数学过渡，你会发现你得到了薛定谔方程（或者如果你更精通，你可以得到所谓的泡利方程，虽然没人谈论它，但基本上是薛定谔方程 + 电子有自旋这一事实）。</p>

<p>如果你尝试从一个相对论性作用量来做，你会得到一个称为克莱因-戈登方程的方程（或者如果你更精通，你可以得到狄拉克方程）。不幸的是，这遇到了麻烦——可能会出现奇怪的负概率，以及解的普遍怪异性。</p>

<p>所以我们做错了——答案是仅仅使作用量符合狭义相对论不变性是不够的。</p>

<p>让我们看一些路径：</p>

<p><img src="https://danluu.com/images/su3su2u1/image_011.jpg"></p>

<p>所以这张图中的虚线代表光锥——光从该点离开传播的速度。所有的路径最终都在光锥内，但有些路径走到了光锥外。这会导致非常奇怪的情况，让我们从两个不同的参考系看一条光锥外的路径：</p>

<p><img src="https://danluu.com/images/su3su2u1/image_012.jpg"></p>

<p>所以我们看到的是，在第一个参考系（左图）中正常的路径，在第二个参考系中看起来很奇怪——因为光锥外事件的顺序不是固定的，有些参考系看到路径在时间上倒退。</p>

<p>所以我们立即看到了问题。当我们切换到相对论理论时，我们没有包括所有路径——要真正包括所有路径，我们需要包括那些显然在时间上倒退的路径。这非常奇怪！注意如果我们正向运行时间，在 X’ 观察者看来，在路径的某些点上，有两个粒子（一个在时间上倒退，一个在时间上前进）。</p>

<p>费曼的天才之处在于他证明了我们可以将这些时间上倒退的粒子视为时间上前进的反粒子。所以 X’ 观察者</p>

<p>所以实际上我们的路径集看起来像</p>

<p><img src="https://danluu.com/images/su3su2u1/image_013.jpg"></p>

<p>注意，我们不仅有连接两点的路径，而且有完全不相关的从同一点出发并结束的环路——这些路径现在成为可能了！</p>

<p>所以要计算概率，我们不能只看连接点 x₀ 和 x₁ 的路径！可能存在从未接触 x₀ 和 x₁ 的奇怪环路路径，但它们仍然重要！从费曼的视角看，粒子和反粒子对可以形成，传播一段时间，然后湮灭。</p>

<p>所以作为一种簿记工具，我们引入一个场——它在空间中的每个点都有一个值。要计算场的作用量，我们不能只看路径——相反，我们必须将空间中每个点的场值（及其一些导数）加起来。</p>

<p>所以我们旧的作用量是仅对时间求和（S 是作用量，L 是拉格朗日量）</p>

<p>[S = \int dt L ]</p>

<p>我们新的作用量必须对空间和时间求和。</p>

<p>[S = \int dt d^x l ]</p>

<p>所以现在我们的拉格朗日量是拉格朗日密度。</p>

<p>而且我们不能只限制在路径上——我们必须对场的每一种可能构型求和。</p>

<p>所以这就是为什么我们需要场论来结合相对论和量子力学。下次讲一些含义。</p>

<h3 id="field-theory-implications">场论的含义</h3>

<p>首先是如果我们采用费曼的诠释，我们的场论没有固定的粒子数——取决于构型中奇怪的环路，它可以有几乎任意数量的粒子。所以不包括时间上倒退路径的问题的一种表述是，我们需要允许粒子数涨落。</p>

<p>另外，我知道你们中有些人想“这些场是什么？”嗯——这并不那么奇怪。想想电磁场。如果周围没有电荷，电磁场的解是什么？它们就是光波。还记得这篇文章吗？还记得某些特殊路径对所有路径求和最重要吗？类似地，某些场构型对构型求和最重要。那些是经典场论的解。</p>

<p>所以如果我们从没有电荷的电磁场论开始，那么最重要的解是光子（光波）。所以我们可以概述近似的层次：</p>

<p>对所有构型求和 -&gt;（半经典）走所有路径的光子 -&gt;（完全经典）只走经典路径的粒子。</p>

<p>类似地，对于任何粒子</p>

<p>对所有构型求和 -&gt;（半经典）走所有路径的粒子 -&gt;（完全经典）只走经典路径的粒子。</p>

<p>这就是为什么大多数量子力学课程只涵盖波动力学而从未完全进入量子力学。</p>

<h3 id="planck-length-time">普朗克长度/时间</h3>

<p>回答 somervta 的问题。普朗克单位的意义是什么。</p>

<p>让我们从一个我们有直觉的更简单的例子开始——让我们分析简单的氢原子（经典的量子力学问题）。但不做物理，我们只做量纲分析——我们预期氢原子的能量会是多大？</p>

<p>让我们从更简单的东西开始——我们预期氢原子会有什么样的距离？它的半径应该有多大？</p>

<p>嗯，首先——涉及哪些物理？我将氢原子建模为在电场中运动的电子，而且我预计需要量子力学，所以我需要 hbar（普朗克常数）、e（电子电荷）、库仑常数（称之为 k）和电子质量。我能用这些构造出一个长度吗？</p>

<p>让我们试试——k*e² 是能量乘以长度。hbar 是能量 * 时间，所以如果我们相除，我们可以得到 hbar/(k*e²)，它的单位是时间/长度。再乘以另一个 hbar，我们得到 hbar²/(k*e²)，它的单位是质量 * 长度。所以除以电子质量，我们得到一个量 hbar²/(m*k*e²)。</p>

<p>这具有长度的单位，所以我们可能会猜测氢原子的重要长度尺度是我们的这个量（其值约为 53 皮米，这大约是原子氢的正确尺度）。</p>

<p>我们也可以通过注意到</p>

<p>能量 ~ k*e²/r 并使用我们的 r 尺度来估计氢原子的能量。</p>

<p>能量 ~ m*k²*e⁴/(hbar²) ~27 eV。</p>

<p>这大约是实际基态能量的两倍，但它的量级肯定是正确的。</p>

<p>现在普朗克注意到的是，如果你问“量子引力的长度尺度是什么？”你会得到常数 G、c 和 hbar。结果证明，你可以用这些构造一个长度尺度（sqrt (hbar*G/c³)）。所以就像氢原子一样，我们预期这给出了量子效应可能开始对引力重要的特征长度（或者引力效应对量子力学重要）。</p>

<p>那么普朗克能量和普朗克质量，是类似的特征质量和能量尺度。</p>

<p>它有点像“我的长度需要多小，量子引力才可能重要？”但这只是一个猜测，真的。普朗克能量是你需要探测那种长度尺度所需的能量（更高的能量探测更小的长度），等等。</p>

<p>这回答了你的问题吗？</p>

<h3 id="more-physics-answers">更多物理问答</h3>

<p>回答 bgaesop 的问题：</p>

<blockquote>
<p>暗物质/暗能量这整个事情怎么就不能证明万有引力理论是错的呢？</p>
</blockquote>

<p>那么让我们从暗能量开始——首先要指出的是暗能量并不是新东西，这个想法可以追溯到爱因斯坦的宇宙学常数。当广义相对论的宇宙学含义首次被理解时，爱因斯坦很讨厌宇宙看起来不稳定的事实。但后来他注意到他的场方程并不是完全通用的——他可以添加一项，一个常数。当哈勃首次注意到宇宙在膨胀时，爱因斯坦去掉了这个常数，但在完全通用的方程中它一直存在。从来没有好的论据说明它为什么应该为零（尽管一些理论（如超对称）部分引入是为了迫使常数为零，当所有人都认为它为零的时候）。</p>

<p>暗能量真的只是意味着那个常数有一个非零值。现在，我们不知道它为什么应该非零。那是更深层次理论的责任——就广义相对论而言，它只是方程中的一个常数。</p>

<p>至于暗物质，那就更复杂了。最初的观测是，你无法仅用可观测物质使星系的旋转曲线正确。所以有些人说“也许有一种新型的不相互作用物质”，而另一些人说“让我们修改引力吧！稍微改变理论可能就能修复曲线，而且尺度如此之大，你可能不会注意到理论的修改。”</p>

<p>所以我们有两个相互竞争的理论，我们需要一种好的方法来区分它们。一些聪明的科学家想到观察两个碰撞的星系——想法是普通物质会相互碰撞并卡在碰撞的中心，但暗物质会直接穿过。所以你会看到两个巨大的暗物质团块彼此远离（你可以通过重物质弯曲光线，即引力透镜，来推断它们的存在），中间是一团可见物质。在子弹星系团中，我们看到的正是如此。</p>

<p>现在，你仍然可以尝试修改引力以符合结果，但你得到的理论开始看起来相当怪异，我认为没有修改理论成功过（尽管暗物质的解释相当自然）。</p>

<h3 id="in-the-standard-model-what-are-the-fundamental-beables-things-that-exist-and-what-are-kinds-of-properties-do-they-have-that-is-not-how-much-mass-do-they-have-but-they-have-mass">在标准模型中，什么是基本的“可存在者”（存在的事物），它们具有哪些种类的属性（即，不是“它们有多少质量”，而是“它们有质量”）？</h3>

<p>这个比较棘手，因为我不认为我们确切知道什么是“可存在者”（假设你像贝尔那样使用 beable）。</p>

<p>问题在于场论是用势来表述的——进入作用量的场是电磁势，而不是电磁场。在经典电磁理论中，我们可能会说电磁场是一个可存在者（贝尔的例子），但势不是。</p>

<p>但在场论中，我们用势来计算一切——我们将势的某些状态视为“光子”。</p>

<p>在电子层面，我们有一个比波函数更普遍的场构型——不同的构型代表波函数的不同组合（一个构型可能代表某个 3 粒子波函数，另一个可能代表单粒子波函数，等等）。</p>

<p>在玻姆类型的理论中，可存在者是实际的粒子位置，我们可以对场论做类似的事情——假设场只是簿记工具。但这会遇到问题，因为看起来不太像粒子的场构型是可能的，并且可以对你的理论产生影响。所以你想赋予场一些实在性。</p>

<p>另一个问题是场构型本身不是唯一的——对称性将不同的场构型联系起来，使得非常不同的构型意味着相同的物理状态。</p>

<p>这其中很多可以追溯到我们还没有一个现实的公理化场论。</p>

<p>但具体来说，假设场是“真实的”，那么你有费米子场，它们具有 1/2 的自旋、电弱荷、强荷和与希格斯场的耦合。这些代表左手或右手的电子、μ子、中微子等。</p>

<p>你有规范场（强场、电弱场），它们代表你的力载玻色子（光子、W、Z 玻色子、胶子）。</p>

<p>还有一个希格斯场，它与电弱场有耦合，并且它在空间中处处非零，这个常数值被称为它的真空期望值。</p>

<h3 id="what-s-the-straight-dope-on-dark-matter-candidates">关于暗物质候选者的最新进展是什么？</h3>

<p>那么，首先有两种潜在的暗物质。热暗物质和冷暗物质。一种明显的暗物质形式是中微子——它们只通过弱力相互作用，而且我们知道它们存在！所以这看起来非常有希望，直到你算出来。因为中微子非常轻（接近无质量），它们中的大多数会以接近光速的速度运动。这是“热”暗物质，它不具备正确的性质。</p>

<p>所以我们真正想要的是冷暗物质。我认为天文学家有一些关于普通重子暗物质的想法（褐矮星之类的东西）。我对那些不太了解。</p>

<p>粒子物理学家更愿意谈论我们所谓的热遗迹。在早期宇宙的某个时刻，当物质密集且炽热时，粒子会在不同类型之间相互转化（电子-正电子变成夸克，变成无论什么东西）。随着宇宙冷却，在某个时刻电弱力会分裂成弱力和电力，一些弱粒子会“冻结出来”。我们可以计算这一点，结果发现假设的“弱力冻结”粒子的密度与暗物质的密度非常接近。这些被称为热遗迹。所以我们想要的是通过弱力相互作用的粒子（这样热遗迹具有正确的密度）并且比中微子重（这样它们不会太热）。</p>

<h4 id="from-susy">来自超对称</h4>

<p>结果证明创建这类模型基本上太容易了。有很多不同的超对称模型，但所有这些模型都会为每个现有粒子产生一个重的“超伙伴”。所以你可以做的一件事是假设超对称，然后添加一个额外的对称性（他们通常选择 R 宇称），额外对称性的目标是防止最轻的超伙伴衰变。所以通常最轻的伙伴与弱力相关（通常它是希格斯、Z 玻色子和光子某些组合的伙伴。由于它们都具有相同的量子数，它们会混合成不同的质量本征态）。这些被称为中性伴子。因为它们是弱相互作用粒子的超伙伴，它们将是弱相互作用的，并且由于 R 宇称而被迫稳定。所以砰，暗物质候选者。</p>

<p>当然，我们从未见过任何超伙伴，所以…</p>

<h4 id="from-guts">来自大统一理论</h4>

<p>其他暗物质候选者可能来自大统一理论。标准模型有点奇怪——希格斯场将两个不同的粒子结合成费米子（左手电子 + 右手电子等）。这个规则的一个例外是中微子。只有左手的中微子存在，它们的质量是马约拉纳质量。</p>

<p>但有些人注意到，如果你添加一个右手的中微子，你可以做一些有趣的事情——首先是在每一代中都有一个右手的中微子，你可以将每一代非常干净地嵌入 SO(10)。没有额外的中微子，你可以嵌入 SU(5)，但有点丑陋。这有一个额外的好处，即 SO 群通常没有规范反常。</p>

<p>另一件事是如果这个中微子很重，那么你可以通过跷跷板机制解释为什么其他费米子质量如此之轻。</p>

<p>现在，SO(10) 预测这个右手的中微子不通过标准模型的力相互作用，但由于规范群更大，我们从破缺的大统一理论中得到了更多的力/玻色子。这些额外的玻色子几乎总是导致质子衰变出现问题，所以你必须想办法安排，使质子稳定，但你仍然可以在早期宇宙中产生足够的惰性中微子来解释暗物质。我认为有足够的自由度可以使这基本可行，尽管更新的 LHC 约束可能让这有点困难。</p>

<p>显然我们没有看到任何大统一理论的额外玻色子，或者质子衰变等等。</p>

<h4 id="from-axions">来自轴子</h4>

<p>（注意：轴子的产生机制与其他热遗迹有点不同）</p>

<p>标准模型 QCD/SU(3) 规范理论存在一个真正的谜题。当该理论最初设计时，物理学家使用了与 CP 对称性相容的最一般的拉格朗日量。但弱力违反 CP，所以 CP 显然不是一个好的对称性。那么为什么我们不需要在 QCD 中包含 CP 破坏项呢？</p>

<p>所以 Peccei 和 Quinn 就像“嗯，也许这个项应该在那里，但看我们可以添加一个新场，它与 CP 破坏项耦合，然后添加一些对称性迫使该场接近 0。”这没问题，但该对称性会有一个相关的戈德斯通玻色子，而我们本应发现一个无质量的粒子。</p>

<p>所以你将全局的 Peccei-Quinn 对称性提升为规范对称性，然后戈德斯通玻色子变成有质量的，你拯救了局面。但你得到了这个遗留下来的有质量的“轴子”粒子。所以砰，暗物质候选者。</p>

<p>像所有其他暗物质候选者一样，这也有问题。QCD 有瞬子解，而这些解会破坏 Peccei-Quinn 对称性。试图修复它会破坏规范对称性（所以你又回到了全局对称性和一个被排除的无质量轴子）。所以这不是一个精确对称性，事情变得有点紧张。</p>

<p>所以这些是我能想到的大类。你可以组合不同的（超对称 SU(5) 大统一理论粒子等）。</p>

<p>我意识到没有太多背景这会很难理解，所以如果其他人感兴趣，请提出具体的问题，我可以尝试整理细节。</p>

<p>另外，我有一篇关于规范理论的文章，作为我量子系列的一部分，很快会发布。</p>

<h3 id="if-your-results-are-highly-counterintuitive">如果你的结果高度违反直觉…</h3>

<p>它们几乎肯定是错误的。</p>

<p>有一次，当我还是一个年轻、天真的数据科学家时，我着手一个项目，研究个别理赔员及其效率。他们处理了多少索赔低于预期成本？有多少索赔准备金充足？基本上，风险管理得如何？</p>

<p>我发现了一些惊人的事情！部门中最初级的几个人非常出色，在几乎所有指标上都近乎完美。一些最资深的人员表现参差不齐。他们在大多数指标上明显低于平均水平！大部分理赔资金都花在这些表现不佳者身上！大数据已经证明公司的一个整个部门都是胡闹！</p>

<p>别急。任何有保险经验的人（或有一半脑子的人，或不那么傲慢地认为物理学是最好的心态的人）会立即意识到一些事情——初级人员处理的索赔类型会不同。经理认为可以由刚入行的人轻松处理的所有事情都交给了新人。简单案件，没有麻烦，评估成本，支付成本，完成。</p>

<p>有很多复杂性的案件（也许责任不确定，奇怪的事故等）交给了资深人员。当然结果看起来更差，每个索赔的方差更大使得风险管理更加困难。我才是那个傻瓜，误解了自己的结果！</p>

<p>第二个例子发生在一家健康保险公司，我监督的一名员工以为他颠覆了医学，因为他发现一种标准治疗化疗方案的疗效比一种更少见的、“更轻的”替代方案更差。从第一次经历中吸取了教训，我和他一起深入研究了数据，我们发现使用较少见替代方案的唯一病例是癌症早期发现并通过手术切除、尚处于局部阶段的病例。</p>

<p>自那次经历以来，我与寻求雇佣我的初创公司、寻求投资的初创公司（有时还有寻求被我工作的公司雇佣的大数据公司）交谈，我一次又一次地看到这个错误。“看看这个惊人的违反直觉的大数据结果！”</p>

<p>最近的一个是在一本贸易杂志上，某个新公司声称一个在某个法官面前赢了 22 场官司的小商场律师必然比在同一法官面前赢得更少的白鞋律所更好。（虽然在我工作过的大多数公司里，如果案件甚至到了审判阶段，说明有些事情出错了——每个人都争取和解。所以根据审判胜率来判断是愚蠢的第二个原因）。</p>

<h3 id="locality-fields-and-the-crown-jewel-of-modern-物理">定域性、场与现代物理学的皇冠明珠</h3>

<p>抱歉，这篇文章还没有完成。我会编辑以替换“待续”部分。</p>

<p>上次，我们讨论了需要一个场论，将一个数学场与空间中的任何点联系起来。今天，我们将讨论我们的场可能是什么样子。我们会发现一些令人惊讶的东西！</p>

<p>我也想强调定域性，为此，让我们将我们的时空视为一个格点，而不是通常的连续空间。</p>

<p><img src="https://danluu.com/images/su3su2u1/image_014.jpg"></p>

<p>那是一个格点。现在想象它是 4 维的而不是 2 维的。</p>

<p>现在，一个场构型涉及在空间中的每个点放置一个我们的相量。</p>

<p>所以这是一个场构型：</p>

<p><img src="https://danluu.com/images/su3su2u1/image_015.jpg"></p>

<p>为了使我们的作用量是定域的（从而符合狭义相对论），我们坚持一个格点上的作用量只取决于该点的场和相邻点的场。</p>

<p>我们还需要确保保持我们从早期文章中知道的对称性——我们知道相量的振幅才重要，我们有改变相位角的对称性。</p>

<p><img src="https://danluu.com/images/su3su2u1/image_016.jpg"></p>

<p>中心点的邻居，用虚线表示。</p>

<p>我们可以通过相减（求导）来比较相邻点。</p>

<p><img src="https://danluu.com/images/su3su2u1/image_017.jpg"></p>

<p>抱歉图片模糊。中间相量 - 左边相量 = 某个其他相量。</p>

<p>我们最后需要捕捉的是对称性——记住相量的角度对预测无关——概率都与振幅（相量的长度）相关。最简单的方法是坚持调整场中所有相量的角度，每个地方都调整：</p>

<p><img src="https://danluu.com/images/su3su2u1/image_018.jpg"></p>

<p>抱歉有我手的影子</p>

<p>总之，这张图片显示了所有相量的变换。这可行，但看起来很奇怪——考虑这样一个构型：</p>

<p><img src="https://danluu.com/images/su3su2u1/image_019.jpg"></p>

<p>这是两个分开的局部场构型——我们可以将其解释为两个粒子。但如果我们只对左边的粒子做实验，我们真的需要调整右边粒子所有场的相位角吗？</p>

<p>也许我们真正想要的是一个局域对称性。一种我们可以独立地旋转任何一点相量的相位角（并且如果愿意，所有点都不同地旋转）的对称性。</p>

<p><em>待续</em></p><p><em>由 mimo-v2.5 模型翻译，花费 24926 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/su3su2u1/physics/</link>
      <guid isPermaLink="false">https://danluu.com/su3su2u1/physics/</guid>
      <pubDate>Tue, 1 Mar 2016 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>采样与跟踪</title>
      <description>&lt;p&gt;Perf 可能是Linux上使用最广泛的通用性能调试工具。在第二梯队有多种工具，与perf一样，它们都是采样分析器。采样分析器非常出色。它们通常易于使用，且相比大多数替代方案具有较低的开销。然而，采样分析器无法有效调试大量性能问题，而这些问题正变得越来越重要。&lt;/p&gt;

&lt;p&gt;例如，考虑一次谷歌搜索查询。下图展示了一个查询的处理流程。每个黑色方框代表一个机柜的机器，每条线表示从一台机器到另一</description>
      <content:encoded><![CDATA[<p>Perf 可能是Linux上使用最广泛的通用性能调试工具。在第二梯队有多种工具，与perf一样，它们都是采样分析器。采样分析器非常出色。它们通常易于使用，且相比大多数替代方案具有较低的开销。然而，采样分析器无法有效调试大量性能问题，而这些问题正变得越来越重要。</p>

<p>例如，考虑一次谷歌搜索查询。下图展示了一个查询的处理流程。每个黑色方框代表一个机柜的机器，每条线表示从一台机器到另一台机器的远程过程调用（RPC）。</p>

<p></p>

<p><img src="https://danluu.com/images/intel-cat/search_query.png" width="640" height="352"></p>

<p>图表显示单个搜索查询进入，它向一百多台机器（绿色所示）发出RPC调用，每台机器又向更低层次（蓝色所示）的另一组请求发送请求。更低层次的每个请求又发出一组RPC调用，由于信息过多无法有效可视化，因此图中未显示。在最后一个叶子层，机器执行1ms-2ms的工作，并返回结果，结果在返回途中被传播和合并，直到搜索结果组装完成。在此期间，在任何一台叶子机器上，将有20-100个其他搜索查询触及同一台机器。单个查询可能触及数千台机器以获取结果。如果我们查看RPC的延迟分布，可以预期，由于RPC数量如此之多，任何特定查询都会遇到99%分位数的最坏情况（尾部）延迟；<a href="http://research.google.com/pubs/pub40801.html" rel="noopener noreferrer">实际上比99%分位数更糟糕</a>。</p>

<p>这种延迟直接转化为金钱。如今人们已普遍认识到，增加用户延迟会降低广告点击率，降低用户完成交易并购买商品的可能性，降低用户日后返回成为回头客的可能性，等等。过去十到十五年，尾部延迟是影响用户延迟的重要因素，而用户延迟直接转化为金钱这一认识，已从谷歌等大公司渗透到普遍认知中。但调试工具并未跟上。</p>

<p>采样分析器作为最常见的性能调试工具，在调试由尾部延迟引起的问题方面众所周知地糟糕，因为它们将事件聚合为平均值。但尾部延迟根据定义就不是平均值。</p>

<p>关于此内容，让我们看看<a href="https://www.youtube.com/watch?v=QBu2Ae8-8LM" rel="noopener noreferrer">这个内容广泛的 Dick Sites 演讲</a><sup id="fnref:D"><a href="#fn:D" rel="noopener noreferrer">1</a></sup>，其中涵盖了，除其他外，<a href="http://www.pdl.cmu.edu/SDI/2015/slides/DatacenterComputers.pdf" rel="noopener noreferrer">Dick 和其他人创建的性能跟踪框架</a>。通过捕获发生的“每一个”事件，它使我们能够轻松调试原本难以追踪的性能异常。我们将通过三个不同的 bug 来了解谷歌跟踪框架可以解决哪类问题。</p>

<p>首先，我们可以从另一个角度来看我们刚才看到的搜索查询：给定一个发出一定数量RPC的顶级查询，获取响应需要多长时间？</p>

<p><img src="https://danluu.com/images/perf-tracing/1_93.png" width="596" height="370"></p>

<p>时间从左向右进行。每行是一个RPC，蓝色条形图显示RPC发出和完成的时间。我们可以看到，第一个RPC发出并返回后，又有93个RPC发出。当这93个RPC中最后一个完成时，搜索结果被返回。我们可以看到其中两个RPC比其他RPC花费的时间明显更长；最慢的RPC决定了搜索查询的结果。</p>

<p>要调试此问题，我们需要几样东西。因为慢速查询中绝大多数RPC是正常的，只有几个是慢的，我们需要比采样分析器显示的聚合数据更多的东西。我们需要能够具体显示慢RPC中发生了什么。此外，由于奇怪的性能事件可能难以重现，我们希望有一个足够廉价的工具，可以一直运行，使我们能够在事后查看任何特定性能不佳的情况。在演讲中，Dick Sites 提到他们使用的跟踪框架的开销预算约为CPU的1%。</p>

<p>此外，我们需要一个时间粒度远小于我们正在调试对象的粒度的工具。采样分析器通常以约1kHz的频率运行（采样间隔1ms），这对于一次性事件（如仍在1ms内执行的慢RPC）提供的信息很少。有些工具可以显示采样分析器输出的类似跟踪的信息，但分辨率太低，这些工具对大多数性能问题没有帮助。虽然可以调高如perf等工具的采样率，但我们无法获得调试所关注问题所需的分辨率。</p>

<p>回到框架，要调试此类问题，我们可能需要查看更放大的视图。这是一个活动不多的示例（只有tcpdump和一些使用<a href="http://linux.die.net/man/2/recvmsg" rel="noopener noreferrer">recvmsg</a>的数据包处理），只是为了说明放大时我们能看到什么。</p>

<p><img src="https://danluu.com/images/perf-tracing/tcpdump.png" width="756" height="369"></p>

<p>水平轴是时间，每行显示CPU正在执行的内容。不同的颜色表示正在运行不同的东西。非常高的切片是内核模式执行，细黑线是空闲进程，中等高度的切片是用户模式执行。我们可以看到CPU0主要在用户模式进程中处理传入网络流量，有18次切换到内核模式。CPU1可能一半时间空闲，有许多跳入内核模式，为tcpdump处理中断。CPU2几乎完全空闲，除了定时器中断触发时有一小段活跃时间。</p>

<p>发生的情况是，每次数据包到达时，都会触发一个中断来通知tcpdump该数据包。然后数据包被传递到在CPU0上调用<code>recvmsg</code>的进程。注意运行tcpdump并不便宜，如果你在服务器满负载时开启它，实际上会消耗服务器7%的资源。这仅仅是转储网络流量，就已经是我们为跟踪所有内容预算的7倍！如果我们详细查看，我们会发现Linux的TCP/IP堆栈具有很大的指令足迹，像tcpdump这样的工作负载会持续进入并将其从L1i和L2缓存中清除。</p>

<p>总之，现在我们已经看到了放大跟踪时的简单示例，让我们看看如何调试之前看到的那个慢RPC。</p>

<p><img src="https://danluu.com/images/perf-tracing/CPU_RPC_view.png" width="832" height="534"></p>

<p>这里我们有一台机器上同一时间段的两种视图。顶部是每个CPU一行，底部是每个RPC一行。查看顶部一组，我们可以看到有些地方单个CPU空闲，但CPU大部分时间都很忙。查看底部一组，我们可以看到40个不同搜索的部分，其中大多数耗时约50us，只有少数几个耗时更长，比如被红色箭头固定的那一个。</p>

<p><img src="https://danluu.com/images/perf-tracing/LOCK_THREAD_view.png" width="816" height="552"></p>

<p>我们还可以通过查看锁被持有和线程执行的情况来跟踪同一时间段。线程和锁之间的弧线显示特定线程被阻塞、等待特定锁的时间。查看此图，我们可以看到等待锁所花费的时间有时比实际执行任何内容的时间要长得多。箭头固定的线程就是执行那个慢RPC的同一线程。这里有点难看清发生了什么，所以让我们聚焦于那个单独的慢RPC。</p>

<p><img src="https://danluu.com/images/perf-tracing/CPU_RPC_zoom.png" width="856" height="538"></p>

<p>我们可以看到这个RPC执行时间很少，而等待时间很长。我们还可以看到，试图使用传统性能测量工具找到等待原因会相当困难。<a href="http://stackoverflow.com/questions/24021967/finding-performance-issue-that-may-be-due-to-thread-locking-possibly" rel="noopener noreferrer">根据stackoverflow，你应该使用采样分析器</a>！但像OProfile这样的工具没用，因为它们只会告诉我们RPC主动执行时发生了什么。我们真正关心的是我们的线程被什么阻塞以及为什么。</p>

<p>让我们不要遵循stackoverflow的建议，而是再次查看此RPC的第二种视图。</p>

<p><img src="https://danluu.com/images/perf-tracing/LOCK_THREAD_iso.png" width="802" height="536"></p>

<p>我们可以看到，这个RPC不仅大部分时间在等待锁，实际上大部分时间都在等待同一个锁，只在等待之间有短暂的执行时间。由此，我们可以查看锁长时间等待的原因。此外，如果我们放大两个锁等待之间的时间段，我们可以看到一些有趣的现象。</p>

<p><img src="https://danluu.com/images/perf-tracing/super_zoom.png" width="705" height="438"></p>

<p>线程在调度后开始执行需要50us。注意等待时间比执行时间长得多。等待是因为<a href="http://eli.thegreenplace.net/2016/c11-threads-affinity-and-hyperthreading/" rel="noopener noreferrer">设置了亲和性策略，这会导致调度器尝试将线程调度回同一个核心</a>，这样核心缓存中的任何数据仍然有效，从而获得最佳的缓存局部性，这意味着线程必须等待先前调度的线程完成。这很直观，但如果我们考虑，例如，<a href="http://users.atw.hu/instlatx64/GenuineIntel00506E3_Skylake_NewMemLat.txt" rel="noopener noreferrer">2.2GHz Skylake</a>，缓存延迟为6.4ns，到L2和L3缓存的延迟分别为21.2ns。改变亲和性策略以加速此类事情是否值得？从单个跟踪中无法判断，但使用生成此数据的跟踪框架，你可以计算应该更改策略。</p>

<p>在演讲中，Dick指出，考虑到实际的工作集大小，等待最多10us调度到共享同一L2缓存的另一个CPU是值得的，等待最多100us调度到共享同一L3缓存的另一个CPU也是值得的<sup id="fnref:O"><a href="#fn:O" rel="noopener noreferrer">2</a></sup>。</p>

<p>从此跟踪中还可以观察到的另一件事是，如果你关心类似于谷歌搜索的工作负载，基本上所有标准基准测试都很糟糕，运行<a href="//danluu.com/intel-cat/#spec" rel="noopener noreferrer">N份spec的标准技术</a>很糟糕。这不是稻草人谬误。今天学术论文中人们仍然这样做，一些芯片公司使用SPEC来基准测试他们的移动设备！</p>

<p>总之，这是一个性能问题，我们能够看到发生了什么是因为有能力同时查看多件事情（CPU调度、线程调度和锁）。让我们看一个更简单的单线程单机示例，跟踪框架仍然有益：</p>

<p><img src="https://danluu.com/images/perf-tracing/gmail.png" width="906" height="515"></p>

<p>这是来自大约2004年Gmail的跟踪。每行显示处理一封电子邮件所需的过程。好吧，除了最后5行；最后一封显示的电子邮件处理时间太长，显示所有处理需要5行空间。如果我们查看每封正常的电子邮件，它们在调用的函数（即颜色）和所需时间方面看起来大致相同。最后一封不同。它开始时与所有其他电子邮件相同，但随后出现了所有这些其他额外内容，这些只发生在慢速电子邮件中。</p>

<p>电子邮件本身不是问题——所有这些额外内容是为重新索引刚到达的电子邮件中的单词而进行的批处理工作，这些工作是跨多封电子邮件批处理的。这张图促使Gmail开发人员将该批处理工作移至另一个线程，将尾部延迟从1800ms降低到100ms。这是另一个使用标准分析工具难以追踪的性能bug。我经常想知道为什么从Gmail发送到Gmail时邮件几乎总是很快出现，而从Outlook发送工作邮件到Outlook时有时需要几分钟。我的主要原因是，Outlook开发人员比Gmail开发人员更难追踪此类尾部延迟bug。</p>

<p>在讨论我们需要哪些可见性来追踪这些问题之前，让我们再看最后一个性能bug。这有点剧透，但在这个bug中，看到整台机器在任何时间点正在做什么将是关键。</p>

<p><a rel="noopener noreferrer"></a></p>

<p><img src="https://danluu.com/images/perf-tracing/disk_tail.png" width="765" height="501"></p>

<p>这是存储机器上64kB读取操作的磁盘延迟直方图，单位为ms。此图中有两组峰值。左侧蓝色的是合理的，右侧红色的则不合理。</p>

<p>从左到右看合理的峰值，首先是RAM中缓存内容在0ms处的峰值。接下来是3ms处的峰值。对于7200rpm硬盘传输64kB来说这太快了；获取磁头下随机点的时间已经是<code>(1/(7200/60)) / 2 s = 4ms</code>。那一定是通过PCIe从磁盘缓存传输数据所需的时间。下一个峰值在接近25ms处，是寻道并从磁盘读取64kB所需的时间。</p>

<p>这些数字看起来不算太差，但99%分位数延迟高达696ms，并且在250ms、500ms、750ms、1000ms等处存在峰值。而且这些都是无法重现的——如果你回头再次读取一个慢速块，或者甚至重放相同的读取序列，慢速读取通常会变快。这很奇怪！什么可能导致如此长时间的延迟？在演讲中，Dick Sites说“你们每个人都猜一个原因，你会发现你们都错了”。</p>

<p><img src="https://danluu.com/images/perf-tracing/13disks.png" width="852" height="150"></p>

<p>这是一台机器中十三个磁盘的跟踪图。蓝色块是读取，红色块是写入。黑线表示从CPU发起事务到事务完成的时间。有些黑线没有块，因为有些事务命中缓存，不需要实际的磁盘活动。如果我们等待一段可以看到尾部延迟的时间并放大一点，我们将看到：</p>

<p><img src="https://danluu.com/images/perf-tracing/13disks_tail.png" width="902" height="319"></p>

<p>我们可以看到一段正常时期，然后某种相变进入一个周期，机器上<em>所有磁盘</em>的磁盘活动（5）之间有250ms的间隔（4）。这持续了九分钟。然后又是一次相变，磁盘延迟恢复正常。它是机器范围的而非特定于磁盘，这是一个重要线索。</p>

<p>利用这些信息，Dick询问了可能是什么原因导致整台机器出现250ms的周期性延迟，发现原因是内核对超过使用配额的进程进行CPU节流。为了执行配额，内核将所有相关线程置休眠状态，直到下一个250ms的整数倍。当时钟的四分之一秒指针转动时，它会唤醒所有线程，如果这些线程仍然使用过多CPU，线程将被再次置休眠另一个四分之一秒。脱离此模式的相变发生在偶然情况下，某个四分之一秒间隔内没有太多请求，内核停止节流线程时。</p>

<p>发现问题后，一位工程师发现谷歌25%的磁盘服务器每天平均发生半小时高延迟期，高延迟期最长可达23小时。这种情况已经发生了三年<sup id="fnref:Y"><a href="#fn:Y" rel="noopener noreferrer">3</a></sup>。Dick Sites说修复这个bug支付了他十年的工资。这是另一个传统采样分析器很难处理的bug。关键见解是减速是相关的且机器范围的，这不是你在分析器中能看到的。</p>

<p>你可能有一个问题，这是因为现有分析器的缺陷，还是分析器可以提供足够的信息，使你不需要使用跟踪工具来追踪罕见、长尾的性能bug？我一直在和Xi Yang讨论这个问题，他有一篇ISCA 2015<a href="http://research.microsoft.com/pubs/244803/preprint.pdf" rel="noopener noreferrer">论文</a>和<a href="https://github.com/yangxi/papers/blob/master/SHIM-ISCA-slides.pptx" rel="noopener noreferrer">演讲</a>描述了他的部分工作。自论文发表以来，他和合作者已经做了更多工作，但该论文仍然包含了关于分析器能被推到何种程度的宝贵信息。正如Xi在他的演讲中所解释的，采样分析器的根本限制之一是你能采样多频繁。</p>

<p><img src="https://danluu.com/images/perf-tracing/perf_sample_1k_100k_10m.png" width="616" height="424"></p>

<p>这是</p><p><em>由 mimo-v2.5 模型翻译，花费 12421 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/perf-tracing/</link>
      <guid isPermaLink="false">https://danluu.com/perf-tracing/</guid>
      <pubDate>Sun, 24 Jan 2016 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>我们在2015年看到了一些非常糟糕的英特尔CPU漏洞，未来我们还应该预期看到更多</title>
      <description>[AI 摘要] 文章指出，2015年出现多个严重英特尔CPU漏洞，并预测因芯片复杂度增加及验证投入减少，未来将出现更多类似漏洞。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章指出，2015年出现多个严重英特尔CPU漏洞，并预测因芯片复杂度增加及验证投入减少，未来将出现更多类似漏洞。</div><p>2015年对英特尔来说是相当不错的一年。他们的季度收益报告每个季度都超出了预期。对于持续呈指数级增长的严肃服务器市场，他们仍然是唯一的选择；从两大云服务商的收益报告中，我们可以看到AWS和Azure分别增长了80%和100%。这种增长有效地抵消了英特尔在桌面市场持续下滑所遭受的损失。有一段时间，<a href="//danluu.com/new-cpu-features/#update" rel="noopener noreferrer">云服务商似乎有可能通过将计算任务转移到FPGAs上来规避英特尔税</a>，但英特尔收购了两家主要的FPGA供应商之一，再加上他们的制造优势，他们似乎能够像主导高端服务器CPU市场一样主导高端FPGA市场。此外，他们因反竞争行为被处以14.5亿美元的罚款，这远少于他们从反竞争行为中获得的利益<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">1</a></sup>。</p>

<p>然而，在工程/漏洞方面，情况看起来并不那么乐观。我们已经看到了一些相当严重的CPU漏洞，看起来我们未来应该预期会看到更多。除非漏洞严重到认识的人都在匆忙打补丁以应对其潜在影响，否则我不会跟踪英特尔的漏洞，即便如此，我今年还是在最后一个季度听说了两个严重的漏洞。首先，<a href="http://xenbits.xen.org/xsa/advisory-156.html" rel="noopener noreferrer">Ben Serebrin和Jan Beulic发现的漏洞</a>，它允许客户虚拟机以某种方式故障，导致CPU陷入微代码无限循环，从而允许任何虚拟机对其宿主机进行拒绝服务攻击。</p>

<p></p>

<p>主要云服务商非常幸运，这个漏洞是由一名谷歌工程师发现的，而且谷歌决定在公开披露之前与竞争对手分享其对该漏洞的了解。黑客花费大量时间试图搞垮主要服务。我实际上对那些花时间攻击我工作公司的人的执着和聪明才智印象非常深刻。如果在我们基础设施的深处，有一段运行在<a href="https://en.wikipedia.org/wiki/Deferred_Procedure_Call" rel="noopener noreferrer">DPC</a>的代码因为某种哈希冲突而容易受到拖慢攻击，即使需要漫长而隐晦的事件序列才能实现，也会有人找到并利用它。如果这个CPU微代码循环漏洞被这些黑客发现，那么在最不方便的时候，大多数云托管服务将遭受重大损失<sup id="fnref:C"><a href="#fn:C" rel="noopener noreferrer">2</a></sup>。</p>

<p>在Serebrin/Beulic漏洞被发现后不久，一群人发现运行<a href="http://www.mersenne.org/download/" rel="noopener noreferrer">prime95</a>（一个常用的基准测试和烧机工具）会导致<a href="http://mersenneforum.org/showthread.php?t=20714" rel="noopener noreferrer">他们的整个系统死机</a>。<a href="https://communities.intel.com/thread/96157?start=15&amp;tstart=0" rel="noopener noreferrer">英特尔对此的回应是</a>：</p>

<blockquote>
<p>英特尔已经确定了一个可能影响第6代英特尔®酷睿™系列产品的潜在问题。此问题仅在特定的复杂工作负载条件下发生，例如运行Prime95等应用程序时可能遇到的情况。在这些情况下，处理器可能会挂起或导致不可预测的系统行为。</p>
</blockquote>

<p>这几乎没有透露到底发生了什么。如果你查看他们的勘误列表，你会发现这是典型的做法，除了他们通常甚至不会命名用于触发漏洞的应用程序。例如，<a href="http://www.intel.com/content/dam/www/public/us/en/documents/specification-updates/4th-gen-core-family-desktop-specification-update.pdf" rel="noopener noreferrer">其中一个当前的勘误列表</a>有这样的条目：</p>

<ul>
<li>某些AVX指令组合可能导致不可预测的系统行为</li>
<li>应导致#DF的AVX Gather指令可能导致意外的系统行为</li>
<li>处理器可能在高活动期间经历与LLC相关的虚假机器检查</li>
<li>页面故障可能报告错误的故障信息</li>
</ul>

<p>正如我们所看到的，“不可预测的系统行为”可能意味着我们彻底完蛋。机器检查也不好——它们会导致Windows蓝屏死机和Linux内核恐慌。页面故障上的错误地址可能比单纯的崩溃更糟糕，如果你仔细查看列表，可以找到许多其他听起来很吓人的漏洞。</p>

<p>请记住，英特尔的勘误列表包含以下免责声明：</p>

<blockquote>
<p>勘误在产品生命周期内保留在规范更新中，或者直到某个特定步进不再可用。在这种情况下，从规范更新中移除的勘误将被归档，并可根据要求提供。</p>
</blockquote>

<p>一旦他们停止制造某个步进（相当于软件的小版本号），他们就保留删除勘误的权利，除非你对英特尔足够重要，否则你将无法找到你的旧步进有哪些勘误。</p>

<p>总之，回到2015年。我们在最后一个季度看到了至少两个严重的英特尔CPU漏洞<sup id="fnref:A"><a href="#fn:A" rel="noopener noreferrer">3</a></sup>，而且几乎可以肯定还有更多漏洞潜伏着。当我在一家生产英特尔兼容CPU的公司工作时，我们做了大量的测试和表征工作；作为一个刚离开校园、之前认为CPU基本上都能正常工作的人，我很惊讶于我们能够发现这么多漏洞。即使我从未从事过表征和竞争分析方面的工作，我仍然在正常工作过程中，为了验证那些对我来说似乎不直观的事情，亲自发现了多个英特尔CPU漏洞。事实证明，对我来说似乎不直观的事情，有时对英特尔工程师来说也是不直观的。随着越来越多的服务迁移到云端，以及系统挂起和重置漏洞的影响增加，我们将看到更多黑客投入时间寻找CPU漏洞。当人们意识到发现这些漏洞比看起来容易得多时，我们应该预期会看到更多此类漏洞。曾经，一个CPU系列可能每年只有一个漏洞，严重漏洞每几年甚至十年才发生一次，但我们已经过了那个阶段。部分原因是“不可预测的系统行为”从一类迫使你重新计算的烦人漏洞，变成了任何拥有AWS账户的人都可以用来攻击随机云托管服务的攻击向量，但主要原因是CPU变得越来越复杂，使得测试和有效审计变得更加困难，而英特尔似乎正在削减验证工作的投入。讽刺的是，我们拥有旨在帮助我们提高安全性的硬件虚拟化，但虚拟化如此复杂<sup id="fnref:V"><a href="#fn:V" rel="noopener noreferrer">4</a></sup>，以至于硬件虚拟化实现可能会暴露那些原本不会存在的“不可预测的系统行为”漏洞。这并不是说毫无希望——原则上，可以设计CPU，使得一个核心上的挂起漏洞不会导致整个系统崩溃。但这需要在各个层面做大量工作（缓存目录、非核心部分等都必须修改以在核心挂起时运行，操作系统调度器也是如此）。之所以没有人做这项工作，是因为它以前看起来并不重要。</p>

<p>你经常会听到软件人员说这些事情无关紧要，因为它们（有时）可以打补丁。但是，许多设备永远不会得到修补，这意味着硬件安全漏洞将在其整个生命周期内使某些设备易受攻击。即使你不关心消费者，严重漏洞对CPU厂商来说也非常不利。在我工作过的一家公司，我们曾经有一个漏洞逃过了验证，在产品发布后才被发现。一家原始设备制造商在那之后大约五年都不愿意和我们交谈，而其他继续与我们合作的原始设备制造商不得不使用我们的微代码补丁重新认证他们的部件，并且他们肯定会让我们知道这有多昂贵。英特尔有足够的影响力，使得原始设备制造商不能在发现漏洞后就一走了之，但他们没有无限的政治资本，每一个严重的漏洞都会消耗政治资本，即使它可以打补丁。</p>

<p>这并不是说我们应该试图达到零漏洞。开发速度和漏洞率之间总是存在权衡，最佳点可能不是零漏洞。但是我们现在经常看到具有安全影响的严重漏洞，这极大地改变了权衡。像<a href="https://en.wikipedia.org/wiki/Pentium_FDIV_bug" rel="noopener noreferrer">FDIV漏洞</a>这样的东西，你可以争辩说从统计学上讲，任何不运行数值分析代码的特定用户受到的影响都不大，但安全漏洞则不同。攻击者不会运行随机代码，所以你不能仅仅说某种条件不太可能发生。</p>

<h3 id="update">更新</h3>

<p>在写下这篇文章后，一位自称是前英特尔员工的人说“即使你有特权访问权限，你也一无所知”，而Reddit上的一位匿名评论者<a href="https://www.reddit.com/r/programming/comments/41k3yx/we_saw_some_really_bad_intel_cpu_bugs_in_2015_and/cz2zwag" rel="noopener noreferrer">发表了这条评论</a>：</p>

<blockquote>
<p>作为一个在英特尔SOC验证小组工作到2014年左右的人，我可以告诉你，是的，你将看到比FDIV漏洞之后直到最近这段时期更多的英特尔CPU漏洞。</p>

<p>为什么？</p>

<p>让我来设定场景：那是2013年底。英特尔对输掉与ARM的移动CPU之战感到恐慌。与所有验证小组开会。负责验证的主管说了类似这样的话：“我们需要加快速度。英特尔的验证时间比我们的竞争对手长得多。我们需要尽一切努力缩短这些时间……我们不能永远生活在90年代初FDIV漏洞的阴影下，我们需要继续前进。我们的竞争对手比我们移动得快得多”——这是我的复述。房间里许多工程师都记得FDIV漏洞以及随后20年前给英特尔带来的问题。我们中许多人感到震惊，一位高层人士会暗示我们需要在验证上偷工减料——当然，这没有明说，但这就是隐含的信息。2013年底的那次会议对我们许多在场的人来说，标志着英特尔的一次巨变。而且这看起来不像是一个好的巨变。我们中的一些人选择在情况好的时候离开。作为一个在英特尔SOC验证小组工作到2014年左右的人，我可以告诉你，是的，你将看到比FDIV漏洞之后直到最近这段时期更多的英特尔CPU漏洞。</p>
</blockquote>

<p>我无法通过我个人认识的另一个来源确认这个故事，尽管另一位匿名评论者说“我于2013年中离开了INTC。来自验证部门。这……与我的经历相比是准确的。”另一位匿名人士，一个我认识的人，没有听说那个演讲，但发现在大约那个时候，“速度”成了一个流行词，管理层花了很多时间谈论英特尔需要更多的“速度”来与ARM竞争，这似乎证实了这种情绪，即使不是实际的演讲。</p>

<p>我还从形式化方法的人员那里听说，在第一条评论提到的时间段内，有一批形式化验证人员离职了。我听过的一个故事是人们离开是因为担心自己变得多余。我被告知，当时有提前退休方案在流传，人们强烈怀疑会裁员。我听过的另一个故事是，由于英特尔专注于与ARM的移动之战，事情变得非常奇怪，人们想在情况变得更糟之前离开。但很难说这是否意味着什么，因为英特尔一直有很多人流失到苹果，因为苹果提供更好的薪酬方案和不那么混乱的承诺。</p>

<p>我还收到了关于漏洞的匿名故事。一位在HPC工作的人告诉我，当他们采购Haswell部件时，一个小道消息告诉他们，对于超过12核的变体，性能会显著下降。当他们尝试构建12核和16核系统时，他们发现在各种工作负载中，12核系统的性能明显更好。这不是更好的每核性能——而是更好的绝对性能。增加4个核心反而降低了并行工作负载的性能！无论是单路还是双路基准测试都是如此。</p>

<p>还有<a href="http://www.tomshardware.com/forum/id-2830772/skylake-build-randomly-freezing-crashing.html" rel="noopener noreferrer">一个似乎英特尔还没有弄清楚的空闲/低活动期间神秘挂起漏洞</a>。</p>

<p>然后还有<a href="https://bugzilla.kernel.org/show_bug.cgi?id=103351" rel="noopener noreferrer">这个Broadwell漏洞，如果你不禁用低功耗状态，它会导致Linux挂起</a>。</p>

<p>当然，英特尔并不是唯一一家有漏洞的公司——Robert Swiecki发现的<a href="https://lists.debian.org/debian-security/2016/03/msg00084.html" rel="noopener noreferrer">AMD漏洞</a>不仅允许虚拟机使其宿主机崩溃，还允许虚拟机接管宿主机。</p>

<p>我甚至可能还没听说过所有最近的漏洞和关于验证/验证的故事。欢迎将其他报告发送给我。</p>

<h3 id="more-updates">更多更新</h3>

<p>许多人注意到<a href="https://forum.synology.com/enu/viewtopic.php?f=7&amp;t=119727&amp;start=60" rel="noopener noreferrer">存储设备和交换机</a>出现异常故障率。这似乎与<a href="https://www.servethehome.com/intel-atom-c2000-series-bug-quiet/" rel="noopener noreferrer">英特尔Atom漏洞有关</a>。我觉得这很有趣，因为Atom是一个相对简单的芯片，因此也是一个相对容易验证的芯片。当第一代Atom发布时，英特尔的人员似乎对芯片只需要很少的内部迭代就能推出可用的量产芯片感到自豪，这是由芯片的复杂性决定的。现代Atom更复杂，但并没有<em>那么</em>复杂。</p>

<p>英特尔Skylake和Kaby Lake有一个<a href="https://lists.debian.org/debian-devel/2017/06/msg00308.html" rel="noopener noreferrer">超线程漏洞，非常严重，以至于Debian建议用户禁用超线程以避免该漏洞</a>，该漏洞可能“导致虚假错误，例如应用程序和系统行为异常、数据损坏和数据丢失”。</p>

<p>在AMD方面，可能有一个与最近任何英特尔CPU漏洞一样严重的漏洞。如果你阅读那个链接的帖子，你会看到一位AMD代表要求人们禁用SMT、OPCache控制并更改LLC设置，以可能缓解或缩小一个严重的崩溃漏洞。在<a href="https://forums.gentoo.org/viewtopic-t-1061546-postdays-0-postorder-asc-start-150.html" rel="noopener noreferrer">另一个帖子</a>中，你可以找到有人报告一个带有“u-op cache crc mismatch”的#MC异常。</p>

<p>尽管AMD在论坛中的回应是这些是孤立的问题，但phoronix能够通过运行一个由编译多个开源程序组成的压力测试来重现崩溃。他们报告说，他们在一个小时的尝试编译中获得了53个段错误。</p>

<p>一些FreeBSD人员也注意到了看似不相关的崩溃，并能够通过在高地址运行代码然后触发中断来重现。这可能导致挂起或崩溃。这似乎与第一个报告的Ryzen问题无关的原因是，在禁用SMT的情况下很容易重现。</p>

<p>Matt Dillon发现了一个由DragonflyBSD触发的AMD漏洞，并提交了一个微小的补丁来修复它：</p>

<blockquote>
<p>在Ryzen中存在一个与内核iretq到接近用户地址空间末尾（用户栈顶部）的高用户%rip地址相关的漏洞。这是针对该问题的临时解决方案。</p>

<p>原始的%rip for sigtramp是0x00007fffffffffe0。将其向下移动到fa0还不够。向下移动到f00将漏洞从几乎立即发生变为需要几个小时才能重现。向下移动到be0需要一天才能重现。向下移动到0x00007ffffffffba0（本次提交）在过夜测试中存活了下来。</p>
</blockquote>

<h3 id="meltdown-spectre-https-meltdownattack-com-update"><a href="https://meltdownattack.com/" rel="noopener noreferrer">Meltdown / spectre</a> 更新</h3>

<p>这是一类有趣的攻击，利用推测执行加上侧信道攻击将特权信息泄露到用户进程中。似乎至少有一些攻击可以在浏览器中通过JavaScript完成。</p>

<p>关于前几篇更新中关于英特尔最近对待验证态度的评论，<a href="https://news.ycombinator.com/item?id=16059635" rel="noopener noreferrer">另一位自称是前英特尔员工的人证实了上面的声明</a>：</p>

<blockquote>
<p>作为前英特尔员工，这与我的经历非常吻合。我并非在验证部门工作（实际上是作为Altera的一部分加入的），但“速度”绝对是一个流行词，高层管理对复杂挑战的方法是纯粹的恐慌。日程安排的延误根本不被容忍——因此验证中的问题对你的项目来说是生存威胁，你的项目很容易被取消。而且，由于公司规模庞大，达成质量和完整性的方式非常官僚，很少反映真正的工程基础。</p>
</blockquote>

<h3 id="2024-update">2024年更新</h3>

<p>距离我写这篇文章已近十年，严重的CPU漏洞仍在不断涌现。例如，最近这个漏洞是由RAD工具发现的：</p>

<blockquote>
<p>英特尔处理器不稳定导致Oodle解压缩失败</p>

<p>我们认为这是一个硬件问题，主要影响英特尔13900K和14900K处理器，13700、14700和其他相关处理器也可能受影响。只有这些处理器的一小部分会表现出这种行为。问题似乎是由BIOS设置与这些处理器的高时钟频率和高功耗相结合导致的，导致系统在高负载下不稳定和行为不可预测……任何在多线程上大量使用处理器的程序都可能导致崩溃或不可预测的行为。在RealBench、CineBench、Prime95、Handbrake、Visual Studio等中都出现过崩溃。这个问题也可能表现为GPU错误消息，例如虚假的“显存不足”错误，尽管它是由CPU引起的。</p>
</blockquote>

<p>有人可能会争辩说这是一个配置漏洞，但从典型用户的角度来看，他们观察到的就是他们的CPU导致了崩溃。而且，现实地说，英特尔知道他们的CPU正在使用这些设置的系统中出货。缓解措施涉及做诸如将以下设置更改为““SVID行为”→“英特尔故障安全”、“长时间功耗限制”→如果设置更高则降至125W（“处理器基础功率”在ARK上）、“短时间功耗限制”→如果设置更高则降至253W（对于13900/14900 CPU，其他CPU有其他限制！“最大睿频功率”在ARK上）”等事情。</p>

<p>如果他们希望他们的CPU不因此问题而崩溃，他们本可以也应该强制执行这些设置以及其他一些设置。相反，他们将其留给了BIOS设置，于是我们有了今天。</p>

<p>历史上，英特尔在验证、确认和测试方面比AMD严肃得多，我们在他们的产出中看到了这一点。曾经，当许多爱好者网站对AMD（在K7时代）感到兴奋时，谷歌停止使用AMD并基本上禁止购买AMD CPU，因为它们漏洞太多，导致许多难以调试的问题。但随着时间的推移，英特尔分配的相对验证/确认/测试工作量水平已经下降，英特尔似乎在真正严重漏洞的频率上已经追上甚至可能超过了AMD。考虑到英特尔当前的市场地位，面临来自AMD、ARM和Nvidia的巨大压力，似乎不太可能在可预见的未来扭转这一局面。Nvidia历来比AMD或英特尔漏洞更多，所以英特尔在成为漏洞最多的主要芯片制造商方面还有相当大的空间。考虑到Nvidia是英特尔最大的威胁之一，以及英特尔对其他当时漏洞更多的厂商威胁的回应方式，似乎我们应该预期在未来十年内会有更高速率的严重漏洞。</p>

<p>在具体的漏洞方面，有巨大的压力要求其运营方式更像一家“快速行动，打破常规”的软件公司，而不是传统的、保守的CPU制造商，原因有几个。当你制造CPU时，它的运行速度最终会有些随机，除了测试之外没有可靠的方法来知道它能运行多快，所以CPU公司会对CPU进行一系列测试以查看它能达到多快。这个测试时间实际上相当昂贵，因此做了大量工作来尝试找到最小的测试集来正确确定CPU可以运行的频率。在这里削减成本的一个简单方法是减少测试，即使较小的测试集不能完全保证CPU能以其销售速度运行。</p>

<p>影响这一点的另一个因素是，被销售为名义上更快的CPU可以卖更高的价格，因此也有压力将CPU推至接近其极限。我们可以看到利润率普遍下降的一个方法是查看CPU的可超频程度。人们通常对他们的超频CPU感到满意，如果他们运行一些测试，如prime95、压力测试等，并且他们的部件没有崩溃，但这远不足以确定CPU是否真的能处理用户可能抛出的所有工作负载，但如果你真的试图认真测试一个CPU（在英特尔竞争对手公司工作时，我们会定期这样做），英特尔和其他CPU公司已经真正推动了他们声称的CPU速度相对于实际速度的极限，这有时导致销售的CPU被推过了它们的能力。</p>

<p>关于超频，正如RAD的Fabian Giesen指出的：</p>

<blockquote>
<p>这些并不被认可，如果你尝试更换，它将被视为超频，但它被作为平台的主要功能出售，评测网站也在开启它的状态下进行测试。</p>
</blockquote>

<p>Daniel Gibson回复说：</p>

<blockquote>
<p>嗯，在我的主板上（ASUS ROG Strix B550-A Gaming——显然是游戏硬件，但属于中等价位范围），我必须明确启用XMP/EXPO配置文件，DDR4-RAM才能以全速运行——这是DDR4-3200，CPU（Ryzen 5950X）官方支持的。否则它以DDR4-2400速度运行，我想？还是2133？我忘了，至少明显更低。</p>
</blockquote>

<p>对此，Fabian指出：</p>

<blockquote>
<p>正确。有趣的事实：启用EXPO技术上会使你的保修失效……这很伟大；CPU和RAM都将此列为支持，但官方并不支持。</p>

<p>人们可能会称之为敲诈，如果他们倾向于使用这样尖锐的语言。</p>
</blockquote>

<p>英特尔以前并不正式非正式地支持这类事情。而且，更一般地说，历史上，CPU制造商在能够避免的情况下，非常不愿意发布在预期使用时存在不可忽视的崩溃和数据损坏风险的部件，但越来越多的此类漏洞仍在发生。其中一些最终变得相当公开，比如这个，因为有人发布了像上面RAD报告这样的报告。还有一些被大公司悄悄报告给CPU制造商，通常带有一些保密协议，大公司获得了更换的CPU，英特尔或其他制造商悄悄地为该问题发布了固件修复。而且肯定有一些漏洞根本没有被发现，除非你将偶尔的数据损坏或崩溃算作被发现。</p>

<h3 id="cpu-internals-series">CPU内部系列</h3>

<ul>
<li><a href="//danluu.com/new-cpu-features/" rel="noopener noreferrer">自80年代以来的新CPU特性</a></li>
<li><a href="//danluu.com/branch-prediction/" rel="noopener noreferrer">分支预测简史</a></li>
<li><a href="//danluu.com/integer-overflow/" rel="noopener noreferrer">实际代码中分支和整数溢出检查的开销</a></li>
<li><a href="https://danluu.com/cpu-bugs/" rel="noopener noreferrer">CPU漏洞</a></li>
<li><a href="//danluu.com/hardware-unforgiving/" rel="noopener noreferrer">为什么CPU开发如此艰难</a></li>
<li><a href="//danluu.com/why-hardware-development-is-hard/" rel="noopener noreferrer">Verilog糟透了，第1部分</a></li>
<li><a href="//danluu.com/pl-troll/" rel="noopener noreferrer">Verilog糟透了，第2部分</a></li>
</ul>

<p><small>
感谢Leah Hanson、Jeff Ligouri、Derek Slager、Ralph Corderoy、Joe Wilder、Nate Martin、Hari Angepat、JonLuca De Caro、Jeff Fowler以及许多匿名消息提供者的评论/更正/讨论。
</small></p>
<div>

<hr>

<ol>
<li id="fn:S">就像<a href="https://danluu.com/google-wage-fixing/" rel="noopener noreferrer">苹果、谷歌、Adobe等公司的工资操纵协议</a>一样，法律体系正在传递一个明确的信息：企业应该从事非法和不道德的行为，因为他们最终被处以的罚款只占其收益的一小部分。这与应用于个人的贝克尔式政策相反，在那种政策下，刑期被大幅提高，理由是，由于许多罪犯没有被抓住，被抓住的罪犯应该受到严厉惩罚作为威慑机制。理论是罪犯会理性地计算犯罪的预期刑期，并将其与犯罪的预期价值进行权衡。例如，如果被抓住的几率是1%，我们将预期刑期从6个月增加到50年，罪犯将计算预期刑期已从2天变为6个月，从而降低犯罪的有效价值并导致犯罪减少。我们现在有几十年的证据表明，长刑期将威慑犯罪的理论要么在经验上是错误的，要么其效果非常小；事实证明，那些冲动犯罪的人在犯罪之前并不会深入研究量刑指南。讽刺的是，对于贝克尔理论可能更站得住脚的白领企业犯罪，贝克尔理论并没有被应用。
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:C">我觉得奇怪的一点是攻击所需的努力水平是多么非线性。谷歌、微软和亚马逊面临着定期、持续的攻击，如果它们不能轻松地缓解<a href="http://status.linode.com/incidents/mmdbljlglnfd" rel="noopener noreferrer">那种已经严重影响了Linode可用性数周的低复杂度攻击</a>，它们将无法生存。如果你与湾区各种独角兽公司的人交谈，你会发现它们中的很多在测试期间对外部API调用过于频繁时意外地DoS了自己。在一个复杂的攻击者找到Azure中的一个漏洞导致1%的虚拟机中断一小时所需的时间内，同一个攻击者可能能够完全使十家独角兽公司宕机更长时间。然而，这些攻击者却高度专注于最坚固的目标。为什么？
 <a href="#fnref:C" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:A">陷入微代码无限循环的故障也影响AMD处理器，但基本上没有人用AMD芯片运行云。我指出英特尔的例子是因为英特尔漏洞的影响更大，而不是因为英特尔漏洞更多。在漏洞方面，英特尔的记录比AMD好得多。IBM是我所知的唯一一家在硬件验证方面比英特尔更严肃的大型微处理器公司，但如果你有一台运行AIX的IBM系统，我可以告诉你一些让你毛骨悚然的故事。此外，他们验证小组的有效性尚不清楚，因为十多年来他们一直在失去经验丰富的人员而无法替换，但那是另一篇文章的主题。
 <a href="#fnref:A" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:V">参见<a href="https://github.com/vishmohan/vmlaunch" rel="noopener noreferrer">此代码</a>了解如何使用英特尔API进行此操作的一个简单示例。该示例被简化了，非常简化，以至于除了作为学习辅助外并无太大用处，但它仍然是大约1000行的底层代码。
 <a href="#fnref:V" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 13412 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/cpu-bugs/</link>
      <guid isPermaLink="false">https://danluu.com/cpu-bugs/</guid>
      <pubDate>Sun, 10 Jan 2016 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>异常行为的正常化</title>
      <description>[AI 摘要] 本文探讨了科技公司中危险实践如何逐渐被接受为正常规范的现象，并分析了其原因和可能的解决方案。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文探讨了科技公司中危险实践如何逐渐被接受为正常规范的现象，并分析了其原因和可能的解决方案。</div><p>你是否曾提起某些在你看来完全正常的事情，却招致对方的惊讶？当我在工作中描述某些人人都认为理所当然的事情时，这种情况常有发生。不知何故，对话伙伴的表情会从友善的微笑转变为惊恐的怪相。以下是几个有代表性的例子。</p>

<p>有一家公司或许是我工作过的最棒的地方，它结合了Valve和Netflix的优点。同事们非常出色，你几乎可以完全自由地做任何事。但作为这种文化的副作用，他们在第一年就会流失近一半的新员工，有些是自愿离开，有些是被迫离职。这完全正常，对吧？以下是我曾工作过的地方的人们认为完全正常的一些轶事。通常，这些事不仅正常，还值得称赞。</p>

<p>有一家公司对基础设施极度保密。例如，有一个团队担心，如果向硬件供应商报告漏洞，漏洞会被修复，而竞争对手就能使用这些修复。解决方案是：请求获取固件并自己修复漏洞！最近，我知道公司外部有一群人试图复现该公司今年早些时候发表论文中的算法。他们发现无法复现结果，并且论文中的算法导致了不寻常的不稳定性；当被问及此事时，其中一位作者回应说“嗯，我们有一些调整没有纳入论文”，并拒绝分享这些调整，也就是说，该公司故意发表无法复现的结果以避免泄露细节，这很正常。这家公司通过严格解雇泄密者的政策来强制执行保密。这在入职培训时就会通过举例说明（例如，那个泄露将在特定办公室举办演唱会的人），并且在全公司大会上宣布因泄密而被解雇的消息。这些政策的结果是，我知道有多个人害怕转发关于医疗保险更新信息等邮件给配偶，担心转发错误邮件而被解雇；相反，他们用另一台电脑重新输入邮件再转发，或者用手机给邮件拍照。</p>

<p>还有一个办公室，我曾问起为什么我几乎从未见过两个特定的人同时出现在一个房间。我被告知他们之间有长达十年的积怨，而且情况实际上已经好转——多年来，他们确实无法待在同一个房间，因为其中一人会变得非常生气并做出令人后悔的事情，但现在情况已经缓和到两人可以偶尔出现在办公室的同一侧翼，甚至同一个房间。他们也不是无关紧要的人。他们是办公室仅有的两个团队的经理。</p>

<p>还有一家公司的文化如此奇特，以至于当我坐下来写一篇关于它的文章时，我发现我不仅比写其他任何单篇帖子写的都多，而且比所有其他帖子加起来写的还多（现在已经超过10万字，相当于一本中等篇幅的书）。正是在这家公司，最近有人向我解释，不使用数据做决策，而是利用人脉关系，这是多么棒，而且基于数据做决策的想法本来就是神话；没人这么做。也是在这家公司，他们为了说服我加入而告诉我的四件事都是假的，而这份工作最终变成了我明确表示不想做的事情。当我加入这家公司时，我的团队好几个月都不使用版本控制，让大家使用版本控制确实经历了一番斗争。虽然我赢得了这场斗争，但我却输掉了让人们在提交代码前运行构建，更不用说运行测试的斗争，因此构建每天会崩溃好几次。当我提到我认为这对我们的生产力是个问题时，我被告知这没关系，因为它对每个人都产生同样的影响。既然唯一重要的是我的等级排名生产力，我不应该在乎它是否影响整个团队，事实上这对每个人来说都是正常的，所以无需担忧。</p>

<p>还有一家公司发起了多项大规模计划，以招募更多女性进入工程岗位，但女性在招聘筛选中仍会因为“技术不够强”而被拒绝，比如被问到“你的经验是在算法方面还是仅仅在编码方面？”。我原以为我的带有强力推荐的内部推荐会避免这种情况，但并没有。</p>

<p>还有一家公司，我曾参与一个四人项目，预算数亿美元，年影响达十亿美元，但为一些花费数百美元的事物请求，通常需要数月时间或被拒绝。</p>

<p>你可能会想，我是否只是在一些异常混乱的地方工作过。当然，这些公司通常被认为是不错的工作场所，其中两家还被评为最佳工作场所之一，但也许我只是碰巧在那些被高估的地方工作。但当我听到其他公司如何运作的故事时，我也有同样的经历，即使是那些享有盛誉的工程公司，只不过这次是我感到惊讶，而我的对话伙伴认为他们的故事很正常。</p>

<p>有些公司使用<a href="https://github.com/box/flaky" rel="noopener noreferrer">@flaky</a>，包括旧金山湾区绝大多数使用Python的独角兽公司。如果你不知道这是什么，这是一个库，允许你为那些有时通过有时失败的烦人不稳定测试添加Python注解。当我询问来自三家不同公司的多位同事和前同事对此的看法时，他们都猜它是多次运行测试，如果任何一次运行失败则报告失败。接近，但不完全准确。技术上可以用@flaky做那个，但实际上它被用来多次运行测试，只要任何一次运行通过就报告通过。创建@flaky的公司本质上是一家存储基础设施公司，这个库在其最大竞争对手那里被广泛使用。</p>

<p>还有一家以拥有出色工程实践而闻名的公司，我上次查看时，其可靠性只有两个9（99%），原因完全可以从他们的工程实践中预见。这是我连续提到的第二件无法匿名化的事情，因为有多家公司都符合这个描述。这里，我不是在谈论那些试图成为下一个reddit或twitter的公司，它们显然有一个9（90%）的可靠性也没关系。我说的是那些销售平台并依赖这些平台的公司，一次中断会导致依赖公司在中断期间暂停运营。多家构建基础设施的公司都发现会导致两个9可靠性的实践。</p>

<p>据我所知，很多公司发生的事情是，他们一开始几乎完全专注于产品增长。这完全合理，因为公司在成立时价值几乎为零；他们不会费心去做那些保护他们免受损失的事情，比如良好的运维实践或真正的安全保障，因为没什么可损失的（嗯，除了在不可避免的安全漏洞发生时丢失用户数据，如果你和独角兽公司的安全人员交谈，你就会知道这些漏洞确实会发生）。</p>

<p>结果是一种文化，在这种文化中，人们高度关注增长而忽视风险。这种文化即使在公司成长为价值超过十亿美元后往往也会持续存在，而此时公司已经有东西可以失去了。任何从谷歌、亚马逊或其他拥有扎实运维实践的地方进入这些公司的人都会感到震惊。通常，他们试图修复问题，然后在发现无法产生影响时离开。</p>

<p>谷歌可能拥有当今所有科技公司中最好的运维和安全实践。说你应该像谷歌一样认真对待这些事情很容易，但看看他们是如何达到这一点的很有启发性。如果你查看代码库，你会看到各种服务名称以z结尾，变量名也奇怪地大量以z结尾。我被告知这是因为曾经有人想添加监控。让<code>google.com/somename</code>暴露监控数据不太安全，所以他们加了个z。<code>google.com/somenamez</code>。为了安全。在这家公司，如今已成为全球安全最佳的公司。他们现在如此擅长安全，以至于我交谈过的几个人（都是在此事发生后加入的）都极力否认这曾经发生过，即使他们给出的理由并不合理（例如，避免名称冲突），而且我从当时在场的消息来源那里得知了这件事。</p>

<p>谷歌之所以能从在名称末尾添加z发展到拥有世界最佳安全，并非因为某人发表了激动人心的演讲或写了一篇令人信服的文章。他们是在经历了几次尴尬之后才做到的，这让那些想要“正确”做事的人有了杠杆来修复根本性的流程问题。在我所知的几乎所有拥有良好实践的公司，故事都是类似的。微软在安全领域多年都是笑柄，直到多次灾难性的漏洞迫使他们认真对待安全。这听起来很简单，但如果你和当时在场的人交谈，就会知道这个改变是残酷的。尽管有来自高层的授权，但来自那些认为公司在2003年没有浪费时间在安全等实践上就达到了今天高度的人的激烈政治抵制。为什么要改变已经奏效的东西？</p>

<p>你可以在每个行业看到这种事情。技术人员经常提到的一个经典例子是医生和护士洗手。众所周知，细菌存在，正确洗手可以大大降低细菌传播的几率，从而显著降低医院死亡率。尽管如此，训练有素的医生和护士仍然经常做不到。干预是必要的。提醒人们洗手的标志可以挽救生命。但当人们站在洗手站前强制要求路过的其他人洗手时，可以挽救更多生命。人们可以忽视标志，但不能忽视被强制洗手。</p>

<p>这反映了许多科技公司引入更好实践的尝试。如果你告诉人们他们应该这样做，这会有一些帮助。如果你通过代码审查来强制执行更好的实践，这会有很大帮助。</p>

<p>数据清楚地表明，人类非常不擅长花时间去做那些众所周知可以明确降低罕见但灾难性事件风险的事情。我们会合理化认为走捷径是正确的、合理的事情。对此有一个术语：异常行为的正常化。它在医疗保健、航空、机械工程、航空航天工程和土木工程等多个其他背景下得到了充分研究，但我们没有在软件背景下看到对它的讨论。事实上，我从未在软件背景下使用过这个术语。</p>

<p>是否有可能从别人的错误中学习，而不是自己犯每一个错误？行业现状让这听起来不太可能，但让我们试试看。<a href="http://www.ncbi.nlm.nih.gov/pmc/articles/PMC2821100/" rel="noopener noreferrer">John Banja有一篇关于医疗保健中异常行为正常化的优秀总结论文</a>，其中有我们可以尝试应用于软件开发的经验教训。需要注意的是，因为Banja关注的是患者结果，所以与devops故障模式有密切的类比，但异常行为正常化也发生在文化背景中，其类比性不那么直接。</p>

<p>论文的第一部分详细描述了医疗保健和其他领域的一些灾难。这是一个典型的例子：</p>

<blockquote>
<p>作者作为专家证人参与的一起灾难性疏忽案件涉及一名麻醉师在应外科医生要求关闭呼吸机（用于拍摄患者腹部X光片）。呼吸机只应关闭几秒钟，但麻醉师忘记重新打开，或者以为打开了但实际没有。患者长时间缺氧导致全身性缺氧，陷入植物人状态。她从未恢复，在9天后断开人工呼吸，两天后死亡。后来发现，手术室的麻醉警报和监测设备被故意设置为“无限期暂停”模式，因此麻醉师没有被提醒注意呼吸机问题。悲剧的是，旨在预防这种恐怖事件的设备被禁用了，可能是因为手术室人员认为持续的哔哔声令人烦恼和恼火。</p>
</blockquote>

<p>因为通知太多、太烦人而关闭或忽略通知？错误的手动操作？这可能直接来自我想到的不止一家公司的事后分析报告，只不过结果是悲剧性的死亡，而不是数百万美元的损失。如果你<a href="//danluu.com/postmortem-lessons/" rel="noopener noreferrer">阅读大量科技事后分析报告</a>，你会觉得Banja论文中的每个例子都很熟悉，即使细节不同。</p>

<p>该部分总结道：</p>

<blockquote>
<p>这些灾难通常揭示的因素通常具有“<a href="http://www.jstor.org/stable/223506?seq=1#page_scan_tab_contents" rel="noopener noreferrer">漫长的孵化期，其特点是规则违规、被忽视的累积事件以及关于风险的文化信念，这些共同阻止了可能避免有害后果的干预</a>”。此外，特别引人注目的是，多个规则违规和失误如何汇聚在一起，导致灾难的发生。</p>
</blockquote>

<p>再次强调，这可能来自一篇关于技术故障的文章。这使得下一部分关于这些失败为何发生的内容值得一看。给出的原因是：</p>

<h4 id="the-rules-are-stupid-and-inefficient">规则愚蠢且低效</h4>

<p>论文中的例子是关于给新生儿送药。为了防止“药物转移”，护士需要在电脑上输入密码才能打开药物抽屉，取出药物并给予正确剂量。为了确保第一个护士没有偷药，如果有任何药物剩余，应由另一名护士见证该过程，然后在电脑上输入他们的密码以表明他们见证了药物被正确处理。</p>

<p>这听起来很熟悉。有多少技术事后分析是以“有人因为步骤低效而跳过了一些步骤”开始的，例如，“程序员强制推送了错误的配置或代码，因为他们确信不会出问题，跳过了暂存/测试”？臭名昭著的<a href="https://azure.microsoft.com/en-us/blog/final-root-cause-analysis-and-improvement-areas-nov-18-azure-storage-service-interruption/" rel="noopener noreferrer">2014年11月Azure中断</a>就是因此发生的。大约在同一时间，Azure一家竞争对手的一名开发人员绕过了不应推送未通过测试的配置的规则，因为他们知道配置不可能有问题。当这导致金丝雀部署开始失败时，他们又绕过了不能在失败状态下从金丝雀部署到暂存环境的规则，因为他们知道自己的配置不可能有问题，所以失败一定是其他原因造成的。事后分析显示，配置在技术上是正确的，但暴露了底层软件中的一个错误；幸运的是，该配置暴露的潜在错误没有Azure错误那么严重。</p>

<p>人类不擅长推理故障如何级联，因此我们制定了关于何时可以安全部署的明确规则。但让我们觉得规则愚蠢且低效的，正是让我们难以推理何时可以安全部署的同一点。</p>

<h4 id="knowledge-is-imperfect-and-uneven">知识不完善且不均衡</h4>

<p>人们不会自动知道什么应该是正常的，当新人入职时，他们同样可能学会已经变得正常化的偏离流程，也可能学会合理的流程。</p>

<p><a href="http://jvns.ca" rel="noopener noreferrer">Julia Evans</a> 向我描述了这是如何发生的：</p>

<p><em>新人加入</em><br>
<strong>新人</strong>：WTF WTF WTF WTF WTF<br>
<strong>老手</strong>：是的我们知道，我们也担心这个<br>
<strong>新人</strong>：WTF WTF wTF wtf wtf w...<br>
<em>新人习惯了</em><br>
<em>新人2加入</em><br>
<strong>新人2</strong>：WTF WTF WTF WTF<br>
<strong>新人</strong>：是的我们知道。我们也担心这个。<br></p>

<p>真正阴险的是，人们真的会接受WTF的想法，并且在他们的职业生涯中可能将其传播到其他地方。有一次，在一个<a href="//danluu.com/julialang/" rel="noopener noreferrer">经常出问题的开源项目</a>上做了一些工作后，被告知构建失败是正常的，他们已经做得比平均水平好了，我进行了统计，发现该项目基本上是同类别中最差的，并写了一篇关于<a href="//danluu.com/broken-builds/" rel="noopener noreferrer">可以以相对较低的努力实现几乎总是通过的构建</a>的文章。我收到的最常见评论是：“哇，那个人一定是和超级明星程序员一起工作。但让我们现实点。我们每周至少会破坏几次构建几次”，好像运行测试（甚至尝试编译）在提交代码之前需要超人的能力。但一旦人们相信某种偏离是正常的，他们通常会对这个想法非常投入。</p>

<h4 id="i-m-breaking-the-rule-for-the-good-of-my-patient">我是为了患者的利益而违反规则</h4>

<p>论文中的例子是有人违反了寻找静脉时应戴手套的规则。他们的理由是戴手套使得寻找静脉更加困难，可能导致他们不得不多次用针刺婴儿。这很难反驳。没有人想给婴儿造成额外的痛苦！</p>

<p>我能想到的第二严重的故障发生在有人注意到数据库服务出现速度变慢时。他们向服务推送了一个修复程序，并且为了防止服务降级扩散，他们忽略了应该进行适当、缓慢、分阶段部署的规则。相反，他们将修复程序推送到所有机器。这很难反驳。没有人希望客户的服务降级！不幸的是，这个修复暴露了一个导致全局故障的错误。</p>

<h4 id="the-rules-don-t-apply-to-me-you-can-trust-me">规则不适用于我/你可以相信我</h4>

<blockquote>
<p>大多数人都认为自己是善良正派的人，因此他们能理解自己的许多违规行为是对问题情境完全合理且符合伦理的反应。他们认为自己没有做错什么，当面对相反证据时，他们通常会愤怒并激烈地为自己辩护。</p>
</blockquote>

<p>随着公司的发展，他们最终必须实施安全措施，防止每位员工都能访问基本上所有内容。在大多数公司，当这种情况发生时，有些人会非常不满。“你不信任我吗？如果你信任我，为什么你要撤销我对X、Y和Z的访问权限？”</p>

<p>Facebook 著名地<a href="http://www.wsj.com/articles/SB10001424052702304898704577478483529665936" rel="noopener noreferrer">允许所有员工访问每个人的资料</a>很长时间，你甚至可以找到HN评论表明一些招聘人员会明确提到这是在Facebook工作的福利之一。我能想到不止一家备受推崇的独角兽公司，即使在第一次或第二次严重的安全漏洞之后，每个人仍然可以访问基本上所有内容。很难获得政治资本来限制人们访问他们认为自己需要或有权知道的东西。许多时尚初创公司的核心价值观包括“信任”和“透明”，这使得很难反驳普遍访问。</p>

<h4 id="workers-are-afraid-to-speak-up">员工不敢发声</h4>

<p>有些人我根本不给他们反馈，因为我无法判断他们是否会好好接受，而一旦你说出某些话，就不可能收回。在论文中，作者举了一个手写字迹潦草且在人们要求澄清他写的内容时变得刻薄的医生的例子。结果，人们选择猜测而不是询问。</p>

<p>在大多数公司文化中，人们对给予反馈感到奇怪。每个人都有这样的故事：一个项目在本应终止后又拖延了数月或数年，因为没有人愿意提供明确的反馈。即使在那些不鼓励刻薄、鼓励反馈的文化中，这也是一个问题：友善的文化似乎在发声方面与刻薄的文化有同样多的问题，甚至更多。在某些地方，人们害怕发声，因为他们会被刻薄的人攻击。在其他地方，他们害怕是因为他们会被贴上刻薄的标签。这是个难题。</p>

<h4 id="leadership-withholding-or-diluting-findings-on问题">领导层扣留或淡化关于问题的发现</h4>

<p>在论文中，这表现为随着信息流向上传递，缺陷和弱点被淡化。一个例子是主管可能采取次优行动以避免在上级面前显得难堪。</p>

<p>我第一次看到这种情况发生时非常震惊。那大概是我毕业半年或一年的时候。我看到我们正在做一些明显不是最优的事情，并向小组的资深人士提出。他告诉我他不同意我的看法，但如果我们按照我的方式做，并且出了故障，那将是非常尴尬的。他承认我的方式降低了故障发生的可能性，而没有使故障的技术后果变得更糟，但更重要的是我们不能尴尬。现在我工作了十年，我更理解人们如何以及为什么玩这种游戏，但我仍然觉得很荒谬。</p>

<h4 id="solutions">解决方案</h4>

<p>假设你注意到你的公司存在一个我听到大多数公司员工抱怨过的问题：人们因救火和扑灭火灾而获得晋升，而不是因预防火灾；人们因发布新功能而获得晋升，而不是因进行关键维护和修复漏洞。你如何改变这种情况？</p>

<p>最简单的选择是你自己做正确的事，而无视周围发生的事情。这会有一些积极影响，但你的影响范围必然有限。其次，你可以说服你的团队做正确的事：我曾为一些我认为非常重要且具有持久性的实践这样做过几次，这样一旦事情启动，我就不用继续花费精力去说服人们。</p>

<p>但如果激励机制与你背道而驰，就需要持续且可能不可持续的努力来让人们坚持做正确的事。在这种情况下，问题就变成了说服某人改变激励机制，然后确保改变按设计进行。如何说服人们值得讨论，但篇幅长且杂乱，超出了本文的范围。至于让改变奏效，我见过许多“显而易见”的错误重复出现，无论是在我工作过的地方，还是在我了解很多内部政治的地方。</p>

<p>小公司比较容易。当我在一家100人的公司工作时，层级是个人贡献者（IC）-&gt;团队负责人（TL）-&gt;CEO。仅此而已。CEO非常放手，但如果他想让某件事发生，它就会发生。关键是他了解每个人在做什么，并且基本上可以实时调整奖励。如果你为公司做了很棒的事情，你很可能会加薪。不是在九个月后下一个绩效评估周期到来时，而是基本上立即。并非所有小公司都能有效做到这一点，但在正确的领导下，他们可以。这对大公司来说是不可能的。</p>

<p>在大公司A（LCA），他们遇到了我们讨论的问题，并且有一项指令要求更好地奖励那些做关键但低可见度基础工作的人。员工太多，指令者无法直接对所有薪酬和晋升决策做出判断，但他可以审查调查数据、抽查决策并提供反馈，直到事情规范化。我的主观看法是，该公司从未在枯燥的维护工作和光鲜的新项目之间实现平等，但已经足够接近，以至于那些希望确保事情正确进行的人不必为此严重损害自己的职业生涯。</p>

<p>在大公司B（LCB），IC们同意，过度奖励创建新功能而非做关键基础工作是有问题的。当我与经理交谈时，他们也常常同意。然而，获得晋升的人仍然不成比例地是那些发布光鲜新东西的人。我看到管理层在LCB尝试了一些文化和流程变革。大多情况下，这些变革以拥有显赫头衔的人的声明形式出现。对于真正重要的事情，他们可能会制作一个视频，并通过让人们在观看后进行多项选择测验来强制执行。我在其他IC中观察到的净效应是，人们谈论管理层如何脱离IC的日常生活。但是，由于异常行为正常化的同样原因，这些信息似乎无法传达给高层管理。</p>

<p>有点好笑的是，这最终变成了一个关于激励的问题。作为一个行业，我们花了很多时间思考如何激励消费者做我们想做的事。但我们建立的激励系统被普遍认为激励我们做了错误的事情，而且我们是通过一系列传话游戏和盲目模仿来做的。当微软占主导地位时，我们复制他们的面试流程，问脑筋急转弯问题。现在谷歌占主导地位，我们复制他们的面试流程，问算法问题。如果你环顾那些比谷歌年轻的时髦公司，大多数基本上复制了他们的评级/分级系统，只做了微调。好消息是，与许多公司之前模仿的公司不同，谷歌对他们的大多数流程进行了深思熟虑并做出了数据驱动的决策。坏消息是谷歌在许多方面是独一无二的，这意味着他们的推理往往不能推广，而且<a href="//danluu.com/why-ecc/" rel="noopener noreferrer">人们常常在谷歌内部已经淘汰实践后很久还在盲目模仿</a>。</p>

<p>这种扩散也发生在技术决策上。Stripe <a href="https://www.hakkalabs.co/articles/trouble-with-event-processing-try-the-method-that-stripe-has-mastered-with-mongodb" rel="noopener noreferrer">在Mongo上构建了可靠的消息队列</a>，所以我们就在<a href="https://aphyr.com/posts/284-jepsen-mongodb" rel="noopener noreferrer">Mongo</a>上构建<a href="http://stackoverflow.com/questions/9274777/mongodb-as-a-queue-service" rel="noopener noreferrer">可靠的消息队列</a><sup id="fnref:J"><a href="#fn:J" rel="noopener noreferrer">1</a></sup>。这简直是盲目模仿的典范<sup id="fnref:P"><a href="#fn:P" rel="noopener noreferrer">2</a></sup>。</p>

<p>论文中有专门的小节讨论如何防止异常行为的正常化，我建议完整阅读。</p>

<ul>
<li>注意微弱的信号</li>
<li>抵制不合理的乐观冲动</li>
<li>教员工如何进行情感上不舒服的对话</li>
<li>系统操作员需要感到安全才能发声</li>
<li>认识到监督和监控永无止境</li>
</ul>

<p>让我们看看其中的第一个，“注意微弱的信号”，与一个例子——新人加入公司时的“WTF WTF WTF”反应——是如何相互作用的。</p>

<p>如果一位副总裁决定某件事出了问题，人们通常会听从。这是一个强烈的信号。当人们不听时，副总裁知道可以拉动哪些杠杆来让事情发生。但当新人加入时，他们不知道可以拉动哪些杠杆来让事情发生，或者应该和谁交谈（这几乎是定义性的）。他们发出微弱的信号，很容易被忽视。等到他们对系统有足够的了解能发出强烈的信号时，他们已经适应了。</p>

<p>“注意微弱的信号”听起来确实是个好建议，但我们该怎么做呢？强烈信号很少，因此容易注意到。微弱信号很多。我们如何过滤掉那些不重要的？我们如何让整个团队或组织真正去做？这类问题无法以通用的方式回答；这需要真正的思考。我们主要把这种思考放在了别处。初创公司花很多时间思考增长，虽然他们都会告诉你他们非常关心工程文化，但显示的偏好表明他们并非如此。除少数例外，大公司也差不多。在LCB，我查看了竞争分析幻灯片，它们令人惊叹。他们关注数百个产品的每一个细节，确保从入门到与竞争产品的互操作性，一切都尽可能对用户友好。如果任何单个屏幕比任何竞争对手更复杂或混乱，人们就会不满并试图修复。这相当令人印象深刻。然后当LCB为我所在组织的员工办理入职时，三分之一的人缺少别名/账户、办公室或电脑中的至少一项，这种情况可能持续数周或数月。竞争分析幻灯片谈论了入职的重要性，因为你只有一次机会留下第一印象，然后员工入职时的印象是公司根本不在乎他们，日常流程普遍出问题是正常的。LCB甚至无法正确办理员工入职基础，更不用说像文化适应这样复杂的事情了。这是可以理解的——外部指标如用户增长或流失率是可衡量的，而诸如如何判断你是否在进行文化适应使人不忽视微弱信号之类的目标则更软性且更难确定，但这并不意味着它们不那么重要。人们写了很多关于使用更高级的语言或技术如TDD或敏捷将使你的团队更有效率，但拥有强大的工程文化是更大的力量倍增器。</p>

<p><small>
感谢 Sophie Smithburg 和 Marc Brooker 向我介绍“异常行为的正常化”这个术语，感谢 Kelly Eskridge、Leah Hanson、Sophie Rapoport、Sophie Smithburg、Julia Evans、Dmitri Kalintsev、Ralph Corderoy、Jamie Brandon、Egor Neliuba 和 Victor Felder 的评论/修正/讨论。
</small></p>

<p>

</p>
<div>

<hr>

<ol>
<li id="fn:J"><p>人们似乎认为我在这里开玩笑。我能理解为什么，但试试搜索 <code>mongodb message queue</code>。你会发现诸如“MongoDB中的副本集非常适合实现自动故障转移和冗余”之类的陈述。我所知的每家做过这件事并具备一定规模的公司都认为这至少不是最优的，但你找不到讨论这一点的博客文章或演讲。你看到的都是他们初次尝试时处于蜜月期的文章和演讲。这在许多技术中都很常见。即使人们私下会告诉你所有问题，你在公开场合也大多只能找到热烈推荐。今天，如果你做上述搜索，你会看到大量讨论在Mongo上构建消息队列有多棒的文章，这篇脚注，以及Kyle Kingsbury的几篇博客文章，这取决于你的具体搜索词。</p>

<p>如果出现急性故障，你可能会看到事后分析报告，但虽然我们会对“网站宕机30秒”做事后分析，我们很少对“这需要比其他方案多10倍的运维工作，这是千刀万剐”、“我们把这个东西架构得很差，现在做本应简单的改动非常困难”或“我们的竞争对手用少一个数量级的努力完成了同样的事情”做事后分析。我有时会通过向所有相关人员委婉地询问发生了什么来做非正式的事后分析，但更多是为了我自己，因为我不确定人们是否真的想听到全部真相。如果这次努力催生了一轮晋升，这尤其敏感，而项目越混乱，晋升似乎越常见。项目越大，可见度和晋升就越多，即使该项目本可以用更少的努力完成。</p>
 <a href="#fnref:J" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:P">我花了很多时间询问事情为何如此，既在事情进展顺利的领域，也在事情进展糟糕的领域。在事情进展糟糕的地方，每个人都有想法。但在事情进展顺利的地方，就像上面提到的轻度放手CEO的小公司，几乎没有人知道为什么事情进展顺利。这是魔法。如果你问，人们会字面上告诉你这似乎和他们工作过的其他地方非常相似，只是事情神奇地变好了，而他们不明白原因。但这不是魔法。这是很少有人理解的艰苦工作。我见过多次的情况是，当一位副总裁离开时，公司会成为一个糟糕得多的工作场所，人们会慢慢意识到这位副总裁不仅在支持直接下属方面做得非常出色，还确保他们下属的每个人都很开心。在改变之前很难察觉，但如果你没有看到任何明显的问题，要么是你没有注意，要么是某人或某些人投入了大量工作确保事情顺利运行。
 <a href="#fnref:P" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 14573 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/wat/</link>
      <guid isPermaLink="false">https://danluu.com/wat/</guid>
      <pubDate>Tue, 29 Dec 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>大公司与初创企业对比</title>
      <description>[AI 摘要] 本文对比了在大公司和初创企业工作的利弊，重点探讨薪酬、工作趣味性和职业发展机会。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文对比了在大公司和初创企业工作的利弊，重点探讨薪酬、工作趣味性和职业发展机会。</div><p>有一个流传已久的 meme：你应该加入初创企业，因为钱更多，工作技术含量更高。Paul Graham 说，赚钱的最佳方式是“创办或加入初创企业”，这“几百年来一直是一种可靠的致富途径”，你可以“把一生的收入压缩到几年内”。Michael Arrington 说，你会<a href="https://www.jwz.org/blog/2011/11/watch-a-vc-use-my-name-to-sell-a-con/" rel="noopener noreferrer">成为历史的一部分</a>。Joel Spolsky 说，加入大公司最终会导致你<a href="http://www.joelonsoftware.com/items/2008/05/01.html" rel="noopener noreferrer">玩桌上足球，求别人看你的代码</a>。Sam Altman 说，如果你加入微软，<a href="http://blog.samaltman.com/advice-for-ambitious-19-year-olds" rel="noopener noreferrer">你不会构建有趣的东西，可能也无法与聪明人共事</a>。他们都声称，如果你去初创企业工作，你会学到更多，有更好的选择。其中一些链接已有十年历史，但相同的观点仍在流传，那些特定的文章至今仍被引用。</p>

<p></p>

<p>让我们逐一审视这些观点。</p>

<ol>
<li>你在初创企业会赚更多钱</li>
<li>在大公司不会做有趣的工作</li>
<li>你在初创企业会学到更多，之后有更好的选择</li>
</ol>

<h3 id="1-earnings">1. 收入</h3>

<p>数字会因情况而异，但我们可以做一个粗略的计算，然后根据情况调整。美国的中位收入约为<a href="https://en.wikipedia.org/wiki/Personal_income_in_the_United_States" rel="noopener noreferrer">每年 3 万美元</a>。我将使用一个略显粗糙的零阶一生收入近似值：3 万美元 * 40 = 120 万美元。Google/FB/Amazon 的应届毕业生，如果拿到的是低估的报价，总薪酬（工资 + 奖金 + 股权）约为 13 万美元/年。<a href="http://www.glassdoor.com/Salary/Google-Senior-Software-Engineer-Salaries-E9079_D_KO7,31.htm" rel="noopener noreferrer">根据 Glassdoor 当前数据</a>，在 Google 晋升到 T5/高级工程师的人，总薪酬应约为 25 万美元/年。这些是相当保守的数字<sup id="fnref:C"><a href="#fn:C" rel="noopener noreferrer">1</a></sup>。</p>

<p>一个不算特别成功，也不算特别失败的人可能在五年内晋升为高级工程师<sup id="fnref:5"><a href="#fn:5" rel="noopener noreferrer">2</a></sup>。对于我们的保守基线，假设我们永远无法超越高级工程师进入薪酬真正飙升的职级。我们可以预期收入（包括股票的总薪酬，但不含福利）如下：</p>

<table>
<thead>
<tr>
<th>年份</th>
<th>总薪酬</th>
<th>累计</th>
</tr>
</thead>

<tbody>
<tr>
<td>0</td>
<td>13 万</td>
<td>13 万</td>
</tr>

<tr>
<td>1</td>
<td>16 万</td>
<td>29 万</td>
</tr>

<tr>
<td>2</td>
<td>19 万</td>
<td>48 万</td>
</tr>

<tr>
<td>3</td>
<td>22 万</td>
<td>70 万</td>
</tr>

<tr>
<td>4</td>
<td>25 万</td>
<td>95 万</td>
</tr>

<tr>
<td>5</td>
<td>25 万</td>
<td>120 万</td>
</tr>

<tr>
<td>…</td>
<td>...</td>
<td>...</td>
</tr>

<tr>
<td>9</td>
<td>25 万</td>
<td>220 万</td>
</tr>

<tr>
<td>…</td>
<td>…</td>
<td>…</td>
</tr>

<tr>
<td>39</td>
<td>25 万</td>
<td>970 万</td>
</tr>
</tbody>
</table>

<p>看起来需要六年才能赚到美国一份职业收入的总额。如果你想调整因几年内赚取大量收入而增加的税收负担，可以额外加一年。如果你决定住在湾区或纽约市，可能再加一到两年。如果你决定不退休，40 年职业生涯的终身收入将接近 1000 万美元。</p>

<p>一个常见但错误的反对意见是，你的收入会被湾区的生活成本吞噬。这不仅是错误的，而且实际上恰恰相反。你可以在湾区之外为这些公司工作；大多数公司会在你工作地点的生活成本接近美国中位数时，通过在硅谷或西雅图总部以外的卫星办公室工作，支付可能少 10% 的薪水（至少在美国是这样——在美国以外，薪水通常低得多，原因对我而言并不合理）。这些地区小公司的市场薪资往往很低。当我在波特兰和麦迪逊等地面试时，大多数小公司提供的薪酬与同一家大公司能给我的薪酬之间有 3 到 5 倍的差距。在奥斯汀等地，市场更厚实一些，差距是 2 到 3 倍。在 90 分位数公司，薪资差异在旧金山湾区以外更大，而非更小。</p>

<p>另一个反对意见是，<a href="http://www.bls.gov/ooh/computer-and-information-technology/computer-programmers.htm" rel="noopener noreferrer">大多数公司的大多数程序员赚不到这种钱</a>。如果三四年前你告诉我，有一条职业道路在几年后赚到 25 万美元/年是完全正常的，做的工作本质上与我当时做的工作相似，我不确定我会相信。我认识的人中没有赚那种钱的，除了我所在公司的 CEO。嗯，还有那些进入医学或金融领域的人。</p>

<p>那时和现在的唯一区别是我接受了一份大公司的工作。当我接受那份工作时，我在入职培训中听到的普遍说法基本上是：“我从没想过能在 Google 找到工作，但招聘人员给我发了邮件，我想不如回复一下。”出于某种原因，女性尤其容易有这种想法。总之，我向多个认为自己无法在某家时髦大公司找到工作的人讲述了这个轶事，他们最终都申请并获得了工作。如果你最终在 Google 这样的地方工作，你会意识到大多数人都只是像你我一样的普通程序员。如果有什么不同，我得说 Google 在平均选择性上甚至不如我工作过的初创企业。当你只需要总共雇佣 100 人，而其中一半是你在一家大公司担任技术研究员时合作过的人，然后在另一家公司担任高级副总裁，你可以慢慢招聘，非常挑剔。大公司每周会雇佣超过 100 人，这意味着它们只能这么挑剔。</p>

<p>尽管有关于在 Google/FB 等公司找到工作有多难的炒作，你的机会并不差，而且<a href="http://www.bloomberg.com/news/articles/2015-12-17/big-ipo-tiny-payout-for-many-startup-workers?trk=pulse-det-art_view_ext" rel="noopener noreferrer">肯定比在初创企业发财的机会好</a>，Patrick McKenzie 有一个方便的作弊表：<a href="http://www.kalzumeus.com/2011/10/28/dont-call-yourself-a-programmer/" rel="noopener noreferrer"></a></p>

<blockquote>
<p>掷 d100。（不是正确的极客？抱歉。那么 rand(100) 吧。）<br>
<strong>0~70</strong>：你的股权赠与一文不值。<br>
<strong>71~94</strong>：你的股权赠与是一笔一次性付款，让你赚到与你在大公司拿更高薪水和更好福利工作所放弃的钱差不多。<br>
<strong>95~99</strong>：你的股权赠与是一笔改变人生的金额。你不会觉得自己富有——你不是你认识的最富有的人，因为过去几年与你共事的许多人现在按定义比你更富有——但你的家庭将不再因为你没有像正规的 $你的群体 那样进入 $偏爱领域 而烦恼。<br>
<strong>100</strong>：你就在下一个 Google 工作，富得超乎贪婪的梦想。恭喜。<br>
敏锐的读者会注意到，d100 或 rand(100) 实际上不会出现 100。</p>
</blockquote>

<p>对于一个更严肃、给出大致相同结果的分析，<a href="https://80000hours.org/2014/05/how-much-do-y-combinator-founders-earn/" rel="noopener noreferrer">80000 hours 发现 YC 创始人在 5-9 年后的平均价值为 1800 万美元</a>。这听起来很棒！但有几件事需要记住。首先，YC 公司与平均水平的初创企业相比异常成功。其次，在他们的分析中，80000 hours 指出 80% 的金钱属于 0.5% 的公司。另外 22% 的价值足以让创始人股权超越为大公司工作，但这留下了 77.5% 的情况并非如此。</p>

<p>如果你是员工而不是创始人，数字看起来更糟。如果你是非常早期的员工，你能得到的股权赠与大约是创始人的十分之一就已经很幸运了。如果我们猜测 30% 的 YC 初创企业在雇佣第一个员工之前就失败了，那么平均股权赠与为 1800 万美元 / 0.7 = 约 260 万美元。这对于 5-9 年的工作来说太低了，你真的需要在 0.5% 之中，回报才会显著优于为大公司工作，除非初创企业支付非常慷慨的薪水。</p>

<p>从某种意义上说，这些数字过于乐观了。即使公司成功并有可靠的退出，也有很多事情可能让你的股权赠与一文不值。这方面的统计数据很难获得，但根据轶事，这在收购中似乎是常见情况。</p>

<p>此外，你只需要工作四年的说法通常是错误的。为了保留你的<a href="//danluu.com/startup-options/" rel="noopener noreferrer">彩票</a>直到它开奖（或消失），你可能需要待更久。早期初创企业最常见的股权形式是<a href="https://en.wikipedia.org/wiki/Incentive_stock_option" rel="noopener noreferrer">激励性股票期权（ISOs）</a>，根据定义，你在离开后最多 90 天内到期。如果你早期加入，四年后离开，如果你想让彩票有机会开奖，你将不得不行使你的期权。如果公司还没有获得高估值，你可能只需支付大约美国中位年收入的钱来行使期权。如果公司看起来像火箭船，风险资本家涌入，你将面临巨额税单，所有这一切只是为了得到一张彩票。</p>

<p>例如，假设你早期加入公司 X，当时公司估值 100 万美元，你获得了 1% 股权的期权，因此行使所有期权的成本仅为 1 万美元。也许你走运了，四年后，公司估值 10 亿美元，你的期权只被稀释到 0.5%。太好了！只需 1 万美元，你就可以行使你的期权，然后出售你得到的股权，价值 500 万美元。但公司尚未上市，所以如果你行使期权，你将面临 500 万美元的税单，等到公司实际首次公开募股（IPO）时，你的股票可能值从 0 到大笔钱不等。在某些情况下，你可以以低于其“价值”的几分之一出售你的非流动性股权，但我理解公司越来越普遍地添加条款，在公司 IPO 之前限制你出售股权的能力。即使你的合同没有禁止你在二级市场出售期权的条款，<a href="https://news.ycombinator.com/item?id=10705646" rel="noopener noreferrer">公司有时也会使用幕后通信阻止你出售期权</a>。</p>

<p>当然不是每家公司都这样——我听说 Dropbox 多年来一直慷慨地以当前估值回购人们的期权，现在他们发放限制性股票单位（RSUs）而不是期权，Pinterest 现在给人们七年时间在离开后行使期权——但这样的故事很罕见，所以值得注意。结果是，人们被激励留在大多数初创企业，即使他们不再喜欢这份工作。与我在备受推崇、高估值初创企业的朋友聊天，听起来他们中有很大一部分是僵尸员工，只是敷衍了事，等待流动性事件。对大公司的一个常见批评是他们有很多混日子的老员工，但大多数大公司会让你在第一年后的任何时间离开，并按比例获得你股权的一部分<sup id="fnref:M"><a href="#fn:M" rel="noopener noreferrer">3</a></sup>。初创企业才是人们被激励留下来的地方，即使他们不再关心工作。</p>

<p>在大公司，一旦你进入，我们很可能在六年内赚到一份职业收入。这虽然不如声称的在初创企业三四年就能做到那么好，但大公司的风险在你得到工作后非常低。在初创企业领域，我们有一张彩票，看起来大约有 0.5% 的机会为非常早期的员工开奖。当保罗在 2004 年写这篇文章时，初创企业的预期价值可能明显更好，但大公司薪酬的增长速度比中位数初创企业的薪酬快得多。我们现在处于世界上有史以来对程序员最好的就业市场。这可能会在某个时候改变。一旦情况改变，走初创企业路线的相对回报可能会看起来好得多，但现在，在大公司像发糖果一样发钱时，攒点现金似乎不是个坏主意。</p>

<p>另外需要注意的是，通过在大公司工作并投资初创企业，有可能获得初创企业工作的上行空间。截至本次更新（2020 年中期），公司以约 1000 万美元的估值筹集种子轮，并接受低至 5000 美元的支票是很常见的。这意味着，用 10 万美元，你可以获得与你作为非常早期员工加入时一样多的公司股权，如果你还不是非常资深或在行业内知名的话，甚至可能是第一位员工。但你通过投资获得的股票比员工股权条款更好，甚至不考虑归属，而且由于你的投资不需要归属，你立即获得，通常你需要待四年才能让你的员工股权归属，所以你实际上只需每年投资 2.5 万美元就能获得非常早期员工的股权好处。你不仅可以获得更好的风险调整后回报（通过分散投资），如果你在大公司工作并每年投资 2.5 万美元，你也会比在初创企业工作有更多的收入。</p>

<h3 id="2-interesting-work">2. 有趣的工作</h3>

<p>我们已经确定大公司会支付你体面的薪水。但生活不仅仅是为了赚钱。毕竟，你每周工作 40 小时（或更多）。大公司的工作有多有趣？Joel 声称大公司不解决有趣的问题，Google 正在<a href="http://www.joelonsoftware.com/items/2008/05/01.html" rel="noopener noreferrer">为终极飞盘经验比 Python 经验多的孩子支付难以维持的薪水，他们的主要工作是在谷歌园区玩桌上足球</a>。Sam Altman 对微软说了类似但更温和的话。每三条 Michael O. Church 的评论中就有一条是关于 Google 如何欺骗大量过度合格的程序员去做没人想做的工作。基本上，每篇面向应届毕业生的 HN 或 Reddit 建议帖都会有多个人指出，你在初创企业的经验比在大公司苦干学到的更好。</p>

<p>大公司工作无聊的说法过于宽泛和绝对，甚至不可能成立。这取决于你想做什么样的工作。当我查看我发现论文比例很高的会议时，我发现最有趣的东西在大公司和学术界之间大致平均分布，偶尔有初创企业的论文。例如，看今年的 ISCA，学术界与工业界的论文比例为 2:1（所有工业论文都来自大公司）。但看实际论文，很大一部分学术论文是重现大公司未发表的工作，有时甚至是多年前的工作。如果我只看我个人感兴趣的新工作，比例大约是 1:1。有些情况下，初创企业在同一领域工作但没有发表论文，但这相当罕见，大公司做了更多未发表的研究。我用论文作为我喜欢的那种工作的代理。还有很多领域发表论文不是惯例，但大公司做了大部分前沿工作。</p>

<p>当然，这取决于你想做什么。我并不熟悉前端工作的格局，但在我看来，大公司不像在大规模系统方面那样做了绝大多数非学术的前沿工作。我记得有一条 HN 评论，Jonathan Tang 描述了他如何创建自己的前端工作：他有个想法，告诉他的经理，然后得到了批准去实现。在大公司做这种事是可能的，但人们在小公司追求这类想法似乎更容易。如果你的兴趣是产品，小公司似乎是更好的选择（不过，我离那个领域很远，所以我的知识是二手的）。</p>

<p>但如果你对大规模系统感兴趣，在我上两份工作中，我见过投机性研究项目，试点预算达到九位数被批准。在一个产品的推介中，推介甚至不是说项目会给公司赚钱。而是说一个特定的研究领域对公司很重要，这个基础设施项目将使公司能够更快地在该研究领域前进。因为公司是每年数十亿美元的公司，项目只需要让指针移动一小部分就值得了。因此，一个旨在加速另一个研究项目进展的研究项目被批准了。内部，这种事情通常由政治决定，有些人会说这使得它不值得。但如果你能忍受大公司的政治，初创企业根本没有资源资助非核心业务的研究问题。许多在初创企业是难题的问题在大公司只是好奇心。</p>

<p>相反的一面是，初创企业很容易做到的实验，成熟公司却做不到。当我在<a href="http://www.sigecom.org/" rel="noopener noreferrer">EC</a>好几年前，Facebook 还相对较新时，Google 广告拍卖团队的人对 FB 团队说，FB 正在做的实验是如果他们足够小也会做的，但他们不能因为现在有太多钱通过他们的拍卖就改变广告拍卖的结构。与我们讨论的所有其他事情一样，这里存在权衡，真正的问题是如何权衡各部分，而不是哪一方在所有方面都更好。</p>

<p>Michael O. Church 的说法稍弱一些：大公司有酷炫的东西可以做，但在你为无聊问题付出代价之前，你不会被允许做它们。一个温和的说法是，做有趣的工作取决于运气，最初就进入你感兴趣的项目，但这里的关键是大多数公司可以很好地估计你会有多幸运。Google 以其盲分配过程而闻名，我认识多个人最终去了微软，因为他们面临选择：微软的一个好项目还是 Google 的盲分配，但即使是 Google 也已将此改变到一定程度，在提供录用时给你多个团队选择并不罕见。从这个意义上说，大公司与初创企业没有太大不同。确实有些初创企业基本上只有你感兴趣的工作（例如，如果你对构建分布式数据库感兴趣，早期分布式数据库初创企业）。但在任何更大、更不专业的初创企业，都会有你感兴趣的工作和不感兴趣的工作，这取决于你是否确定你的录用允许你做你感兴趣的工作。</p>

<p>需要注意的是，如果根据（1），你有谈判好薪酬的杠杆，你也有谈判你想做工作的杠杆。我们正处于可能是程序员有史以来最好的就业市场。这明天可能会改变，但在改变之前，你有很多权力去做你想做的工作。</p>

<h3 id="3-learning-experience">3. 学习/经验</h3>

<p>关于初创企业经验更有价值的说法呢？我们没有数据进行严格的定量比较，但在定性上，初创企业一切都在着火，你通过扑灭火灾获得很多广度，但你没有时间深入探索问题。</p>

<p>我职业生涯的前七年在一家初创企业，我很喜欢。完全是混乱，这给了我能力去做各种不同的事情，并承担比在大公司更多的责任。我做了从为内部分布式系统添加容错到负责项目的四分之一，该项目在 x86 芯片上添加 ARM 指令，创建了当时最快的 ARM 芯片，以及唯一能在 ARM 和 x86 之间动态切换的芯片<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">4</a></sup>。那是一次很棒的学习经历。</p>

<p>但我在大公司也有很棒的学习经历。在 Google，我的“入门”项目是加入一个之前只有一人的项目，阅读未完成的设计文档，提供反馈，然后开始实施。该项目的动力是人们担心图像识别问题可能需要 Google 将其拥有的机器数量翻倍，如果发生某种不太可能但并非不可能的情况。那与我的初创企业经历没有太大不同，除了有设计文档的那部分，以及削减基础设施成本每年可能节省数十亿美元而不是数百万美元。</p>

<p>那个项目是比初创企业的等效项目更好还是更差的学习经历？在初创企业，该项目可能仍然是两个人的项目，我会学到当你在时间和资源不足的情况下赶项目时学到的所有东西，自己做了一半。相反，我最终负责了项目的一小部分，只对其余部分提供了反馈，而且我对于充实架构有重要发言权仅仅是运气（时机）。我当然没有获得与自己实施一半时相同的理解水平。另一方面，更大的团队意味着我们实际上有时间做设计审查和代码审查。</p>

<p>如果你关心影响力，在大公司更容易产生大的绝对影响力，因为大公司运营的规模。如果我为像我之前工作过的初创企业那样规模的公司实施我现在所做的东西，影响力可能只有大约每月 1 万美元。这不容小觑，但不足以覆盖我的薪水。但同样的事情在大公司价值超过 1000 倍。大公司只是因为规模更大而有更多机会产生高影响。推论是，初创企业规模足够小，即使对世界的绝对影响较小，也更容易对公司本身产生影响。我做的任何事对大公司都不是生死攸关的，但当我在初创企业工作时，感觉我们所做的可能改变公司生存的几率。</p>

<p>至于在大公司或初创企业工作后是否有更好的选择，如果你想在初创企业工作，你在初创企业有经验可能有更多选择。如果你想做那些由大公司主导的问题，你在这些领域的经验在大公司更好。这里没有正确答案。</p>

<h3 id="conclusion">结论</h3>

<p>薪酬权衡随着时间推移发生了很大变化。当保罗·格雷厄姆在 2004 年写作时，他将 8 万美元/年作为“优秀黑客”可能赚取的合理基线。考虑到通货膨胀，<a href="http://data.bls.gov/cgi-bin/cpicalc.pl?cost1=80000&amp;year1=2004&amp;year2=2015" rel="noopener noreferrer">这大约是现在的 10 万美元/年</a>。但“优秀黑客”的总薪酬是 25 万美元+/年，甚至不算免费食物和真正可靠的保险等福利。权衡已大幅倾斜向大公司。</p>

<p>有趣的工作权衡也随着时间发生了很大变化，但变化是……双峰的。AWS 和 Azure 的存在意味着，以前需要数百万美元服务器和运营专业知识的想法，现在可以用几乎没有固定成本和低边际成本来完成。你在早期初创企业可以做的事情范围很大，而且仍在增长。但与此同时，如果你看 Google 和 MS 在顶级系统会议上发表的工作，初创企业比以往任何时候都更无法重现依赖规模的工作（而且很多最有趣的工作没有发表）。根据你对什么样的工作感兴趣，大公司的情况可能相对更好或更差。</p>

<p>无论如何，现实是不同类型公司之间的差异小于同类型公司之间的差异。无论我们谈论的是初创企业 vs 大公司还是移动游戏 vs 生物技术，这都是正确的。这是递归的。一个公司不同经理和团队之间的差异很容易大于公司之间的差异。如果有人告诉你应该为某种类型的公司工作，这个建议保证在很多时候是错误的，无论是风险资本家倡导你应该为初创企业工作，还是图灵奖得主告诉你应该在研究实验室工作。</p>

<p>至于我，嗯，我不认识你，你最终在大公司、初创企业还是介于两者之间，对我无关紧要。无论你决定什么，我希望你足够了解你的经理，知道他们支持你，你的团队足够了解你喜欢与他们一起工作，你的项目足够了解你发现它有趣。大公司有一类功能障碍在初创企业不寻常<sup id="fnref:N"><a href="#fn:N" rel="noopener noreferrer">5</a></sup>，初创企业也有<a href="http://totalgarb.tumblr.com/tagged/startupbullshit" rel="noopener noreferrer">自己的功能障碍类型</a>。你应该弄清楚相关的权衡是什么，以及你想签约哪种功能障碍。</p>

<h3 id="related-advice-elsewhere">其他相关建议</h3>

<p><a href="//danluu.com/startup-options/" rel="noopener noreferrer">我自己关于期权 vs 现金的文章</a>。</p>

<p><a href="https://web.archive.org/web/20141122200442/https://medium.com/@jocelyngoldfein/the-innovation-dead-end-ee25f8aa090f" rel="noopener noreferrer">Jocelyn Goldfein 关于大公司 vs 小公司的文章</a>。</p>

<p><a href="http://www.kalzumeus.com/2011/10/28/dont-call-yourself-a-programmer/" rel="noopener noreferrer">Patrick McKenzie 关于提供商业价值 vs 技术价值的文章</a>，以及<a href="http://yosefk.com/blog/do-call-yourself-a-programmer-and-other-careadvice.html" rel="noopener noreferrer">Yossi Kreinin 的回应</a>。</p>

<p><a href="http://yosefk.com/blog/do-you-really-want-to-be-making-this-much-money-when-youre-50.html" rel="noopener noreferrer">Yossi Kreinin 关于激情 vs 金钱的文章</a>，以及<a href="http://yosefk.com/blog/stock-options-a-balanced-approach.html" rel="noopener noreferrer">对这篇关于遗憾最小化文章的反驳</a>。</p>

<p><em>更新：这篇文章的反馈相当分化。大公司的人通常同意，但数字对他们来说似乎偏低，尤其是应届毕业生。即使对于生活在生活成本与美国中位数相似的地方的人来说也是如此。另一方面，许多人强烈认为这篇文章中的数字基本上是不可能的。很多人真的投入在认为自己赚的钱差不多是可能的观念中。如果你已经决定少赚钱对你来说是正确的权衡，那没问题，我对此没有意见。但如果你真的认为你不能赚那么多钱，不相信我，我建议与一家高薪大公司的数十万工程师之一交谈。</em></p>

<p><em>更新 2：这篇文章最初写于 2015 年，当时 25 万美元这个数字对于在 Google 或 FB 的“高级”人员来说是保守但不合理的。如果我们看 2017 年的情况，人们<a href="https://twitter.com/danluu/status/942452212445405185" rel="noopener noreferrer">低一个级别的经常拿到 25 万美元</a>，更好的估计可能是 30 万美元或 35 万美元。我对未来开发薪酬可能比大多数人更悲观，但现在情况看起来相当好，而且一个抹除大公司开发薪酬的事件似乎可能会对几乎所有现有初创企业的期权包造成更糟糕的影响。</em></p>

<p><em>更新 3：增加了 2020 年能够投资初创企业的注释。直到 2019 年左右，我才知道没有大量财富也可以这样做。</em></p>

<p><small>
感谢 Kelly Eskridge、Leah Hanson、Julia Evans、Alex Clemmer、Ben Kuhn、Malcolm Matalka、Nick Bergson-Shilcock、Joe Wilder、Nat Welch、Darius Bacon、Lindsey Kuper、Prabhakar Ragde、Pierre-Yves Baccou、David Turner、Oskar Thoren、Katerina Barone-Adesi、Scott Feeney、Ralph Corderoy、Ezekiel Benjamin Smithburg、@agentwaj 和 Kyle Littler 的评论/更正/讨论。
</small></p>
<div>

<hr>

<ol>
<li id="fn:C"><p>特别是，Glassdoor 的数字对于平均水平似乎偏低。我怀疑这是因为他们的平均值被较旧的数字拖低了，而薪酬在过去七年里飙升了。Glassdoor 上的平均数字甚至与我两年前在中西部卫星办公室从其他人那里听到的平均数字不符，而且自那以后市场急剧上涨。最近，在高端，我认识一个刚毕业的人，总薪酬接近 25 万美元/年（四年 35 万美元股权，5 万美元签约奖金，外加慷慨的工资）。像往常一样，他收到了几个薪酬水平不同的报价，然后 Facebook 加入并抬高了他。认真竞争人的公司匹配了报价，就这样了。这包括西雅图和奥斯汀的投标，匹配了硅谷的投标。如果你在谈判报价，关键不是成为某种超级天才。只需足够好，知道市场支付多少，并拥有多个报价。这个人值每一分钱，这就是他得到报价的原因，但我认识几个同样优秀的人，只赚一半，因为他们只得到一个报价，没有杠杆。</p>

<p>总之，这个脚注的重点只是经验丰富工程师的总薪酬可以远高于文中提到的数字。在接下来的分析中，请记住我使用的是保守数字，而对于经验丰富工程师的激进估计会高得多。仅举一例，在 Google，高级是 11 个级别中的第 5 级，实际从第 3 级开始。在微软，它是从一个奇怪的从 59 到 70 多然后跳到 80 的尺度上的第 63 级（或者类似的东西，我总是忘记细节，因为这个尺度太傻了）。高级并不是一个特别高的级别，处于高级的人通常总薪酬远高于 25 万美元/年。注意，这些数字也不包括过去几年时髦大公司股票增长高于市场水平的增值。如果你真的接受了这个交易，你的 RSU 可能已经大幅升值。</p>
 <a href="#fnref:C" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:5"><p>这取决于公司。在像 Facebook 和 Google 这样认真努力留住人的地方，这是真的。在像 IBM、National Instruments (NI) 和 Epic Systems 这样甚至不尝试的地方，这几乎是完全错误的。在微软这样尝试但以最倒退的方式尝试的地方，这主要是错误的。</p>

<p>微软（和其他中等水平公司）会给你一个还可以的报价，并匹配来自其他公司的好报价。这本身就有问题，因为它激励在微软面试的人也在其他地方面试。但更糟糕的问题是他们留住员工时也这样做。如果你在微软待很长时间并且不是少数在快速通道上通往“合伙人”的人之一，你的薪酬最终会严重低于市场水平，有时甚至相差两倍。当你意识到这一点，并在其他地方面试时，微软会匹配外部报价，但在被低估了多年（取决于你待了多久，可能有数十万或数百万美元）之后，承诺在市场上待一年然后又被低估似乎并不那么吸引人。激励结构看起来好像是设计来让那些介于平均和优秀之间的人离开。我看到这发生在多个人身上，我还认识多个因为这个确切原因计划离开的人。他们的经理总是对此感到惊讶，但不应该；这是完全可预测的。</p>

<p>IBM 的策略实际上比微软的策略更合理。你可以通过支付低薪来节省很多钱。这有道理。但为什么要花很多钱把人招进来，然后激励他们离开呢？虽然我合作的顶级人员薪酬很高并且似乎对此感到满意，但没有足够多的人可以依赖他们做所有事情。</p>
 <a href="#fnref:5" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:M">有些公司在这方面更好。像微软这样的老牌公司有时有年度归属，但很多像 Google 这样更年轻的公司，在你度过第一年后有更平滑的归属时间表。然后是亚马逊，它后置报价，知道他们流失率高，不会支付很多。
 <a href="#fnref:M" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:S">遗憾的是，由于后来出现的业务原因，我们最终没有发布这个。
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:N"><p>我在大公司 X 的第一次互动是与一名员工在入职培训时，那名员工告诉我，我不能进入入职培训，因为我没有在名单上。我问如何上名单，被告知需要我经理的一封电子邮件才能上名单。这发生在大约早上 7:30，因为入职培训 7:30 开始，然后持续半天，原因似乎没人知道（我问了很多人，一直到人力资源副总裁）。当我问是否可以晚点回来时，被告知如果一小时内进不去，就得下周回来。我还问，我在某个系统中被列为有特定经理是否是我应该参加入职培训的证据，被告知我必须在名单上。于是我给我的经理发了邮件，但他当然没有回复，因为谁会在早上 7:30 查邮件呢？幸运的是，我经理之前给了我他的号码，告诉我如果需要任何东西可以打电话，能够进入入职培训而不必下周再来早上 7:30 似乎值得，所以我给他打了电话。自然，他要求和入职培训的门卫说话；当我转达时，入职培训的人告诉我，他不能通电话——你看，他只能接受电子邮件，不能通电话，甚至不能只是澄清一些事情。入职培训开始五分钟，我就已经目瞪口呆了。但真的，我应该觉得自己幸运——另一个“没有在名单上”的人没有他经理的电话号码，据我所知，他不得不下周早上 7:30 回来参加入职培训。我问入职培训的人这种情况多久发生一次，他告诉我“很少见，每周只有一两次”。</p>

<p>这种经历大约每半小时重复一次，持续了整个入职培训。我没有从其他入职培训站点被遗漏，但当我询问时，我发现每个站点都有错误，经常遗漏人。我最喜欢的是一个站点，有人站在输入队列，递给你一张纸。这张纸通知你，该站点的机器将给你一个错误，并附有要做什么的说明。当错误发生时，你必须遵循纸上的说明，而不是机器上的说明。</p>

<p>这些经历基本上占用了我第一周的全部时间。现在我已经过了入职培训，进入日常事务，每周几次都有超现实的卡夫卡式经历。而且我基本上弄清楚了如何在这个系统中导航（通常，认识正确的人并请他们介入可以解决问题）。我发现真正有趣的不是实际经历，而是我与在这里待过一段时间的大多数人交谈时，他们认为<a href="//danluu.com/wat/" rel="noopener noreferrer">事情根本不可能以其他方式进行</a>，并且事情不可能得到改善。奇怪的是，在这里待了同样长时间但非常资深的人倾向于同意公司有其大公司功能障碍的份额。我希望能有足够的数据来判断因果关系是哪个方向（是意识到功能障碍的人更有可能待到非常资深，还是非常资深给了你一个视角，让你看到更多功能障碍）。更奇怪的是，公司投入大量精力给人以事情尽可能好的印象。在入职培训中，我们得到了一个历史版本，听起来好像公司从图形用户界面到网络开创了一切，多次声称我们拥有世界上最好的 X，即使 X 明显很平庸。我不清楚公司确保大多数员工不了解我们自己的产品和流程的缺点有什么好处。</p>

<p>无论如何，事情不可能得到改善的态度不仅限于行政问题。我的一个朋友需要找一个函数来做 Linux 上很简单的一行代码，但在我们的操作系统上却相当复杂。他的第一次尝试是使用 boost，但事实证明，在我们的操作系统上这样做的文档足够复杂，boost 做错了并且有一个 bug 存在了多年。几天后，72 行代码，他设法创建了一个函数来实现他的目标。因为他不确定是否遗漏了什么，他将代码审查转发给两位非常资深的工程师（比杰出工程师低一级）。他们不确定，转发给 CTO，CTO 说他没有看到在我们的操作系统上使用现有 API 更简单的方法来实现同样的事情。</p>

<p>后来，我的朋友与操作系统团队的某人发生了激烈讨论，那人坚持认为如何做到的文档非常清晰，不可能更清晰，API 也不可能更容易。尽管这如此困难，以至于 boost 错误了七年，两位非常资深的工程师觉得不够自信审查代码而提交给 CTO。</p>

<p>我打算在这里停下，不是因为我没有更多这样的事件，而是因为重述半年的大公司故事比我的博客还长。不仅比这篇文章或任何单篇文章长，而且比博客上其他所有内容加起来还长，这大约有 10 万多字。每页单词数的典型估计在 250 到 1000 之间，所以我的超现实经历率大约在每六个月 100 到 400 页之间。我不确定这个率是否本质上不同于你在<a href="http://totalgarb.tumblr.com/tagged/startupbullshit" rel="noopener noreferrer">初创企业</a>可能得到的率，但故事的风味不同，你应该已经对此有所了解。</p>
 <a href="#fnref:N" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 18953 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/startup-tradeoffs/</link>
      <guid isPermaLink="false">https://danluu.com/startup-tradeoffs/</guid>
      <pubDate>Thu, 17 Dec 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>文件很复杂</title>
      <description>[AI 摘要] 本文探讨文件系统在崩溃一致性、错误处理和数据可靠性方面存在的复杂性和可靠性问题。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文探讨文件系统在崩溃一致性、错误处理和数据可靠性方面存在的复杂性和可靠性问题。</div><p>我已经很多年没使用过桌面电子邮件客户端了。它们都无法处理我收到的大量邮件，而不至少偶尔损坏我的邮箱。Pine、Eudora 和 Outlook 都损坏过我的收件箱，迫使我从备份中恢复。为什么桌面邮件客户端不如 Gmail 可靠？我的 Gmail 账户处理的邮件量比我在桌面客户端上处理的任何时候都多，而且还允许从全球多个位置同时访问。分布式系统有一个不公平的优势，它们可以以一种桌面客户端无法实现的方式来抵御整个磁盘故障，但我遇到的所有文件损坏问题都不是由整个磁盘故障引起的。为什么我的桌面应用程序使用体验如此糟糕？</p>

<p></p>

<p>那么，可能会发生什么类型的故障呢？崩溃一致性（即使在崩溃发生时也能保持一致状态）可能是最容易考虑的属性，因为我们可以假设从文件系统到磁盘的一切都正常工作；我们先考虑这一点。</p>

<h3 id="crash-consistency">崩溃一致性</h3>

<p>Pillai 等人在 OSDI '14 上发表了一篇<a href="https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-pillai.pdf" rel="noopener noreferrer">论文</a>和<a href="https://www.usenix.org/sites/default/files/conference/protected-files/osdi14_slides_pillai.pdf" rel="noopener noreferrer">演讲</a>，详细讨论了在不损坏或丢失数据的情况下保存数据有多么困难。</p>

<p>让我们看一个如何保存数据以抵御崩溃的简单例子。假设我们有一个包含文本 <code>a foo</code> 的文件，我们想将其更新为 <code>a bar</code>。pwrite 函数看起来就是为此目的设计的。它接受一个文件描述符、我们要写的内容、长度和偏移量。所以我们可能会尝试</p>

<pre><code>pwrite([file], “bar”, 3, 2)  // 在偏移量 2 处写入 3 个字节
</code></pre>

<p>会发生什么？如果一切正常，文件将包含 <code>a bar</code>，但如果在写入过程中发生崩溃，我们可能会得到 <code>a boo</code>、<code>a far</code> 或任何其他组合。请注意，你可能希望将其视为扇区或块上的示例，而不是字符/字节。</p>

<p>如果我们想要原子性（这样我们最终得到的是 <code>a foo</code> 或 <code>a bar</code>，而不是介于两者之间的任何东西），一种标准技术是在<a href="http://www.cburch.com/cs/340/reading/log/index.html" rel="noopener noreferrer">撤销日志</a>文件中保存我们即将更改的数据副本，修改“真实”文件，然后删除日志文件。如果发生崩溃，我们可以从日志中恢复。我们可能会写类似</p>

<pre><code>creat(/dir/log);
write(/dir/log, “2,3,foo”, 7);
pwrite(/dir/orig, “bar”, 3, 2);
unlink(/dir/log);
</code></pre>

<p>这应该允许通过撤销日志从崩溃中恢复而不损坏数据，至少如果我们使用 <code>ext3</code> 并确保使用 <code>data=journal</code> 挂载磁盘。但如果我们像大多数人一样使用默认设置<sup id="fnref:D"><a href="#fn:D" rel="noopener noreferrer">1</a></sup>，我们就没那么幸运了——使用默认的 <code>data=ordered</code>，<code>write</code> 和 <code>pwrite</code> 系统调用可能会被重排序，导致对 <code>orig</code> 的写入在对日志的写入之前发生，这违背了拥有日志的目的。我们可以解决这个问题。</p>

<pre><code>creat(/dir/log);
write(/dir/log, “2, 3, foo”);
fsync(/dir/log);  // 不允许写入在 pwrite 之后被重排序
pwrite(/dir/orig, 2, “bar”);
fsync(/dir/orig);
unlink(/dir/log);
</code></pre>

<p>这应该能强制事情按正确的顺序发生，至少如果我们使用 ext3 的 <code>data=journal</code> 或 <code>data=ordered</code> 模式。如果我们使用 <code>data=writeback</code>，在写入或 <code>fsync</code> 到日志期间发生崩溃可能会使 <code>log</code> 处于一种状态：文件大小已针对“bar”的写入进行了调整，但数据尚未写入，这意味着日志将包含随机垃圾数据。这是因为在 <code>data=writeback</code> 模式下，元数据是<a href="https://en.wikipedia.org/wiki/Journaling_file_system" rel="noopener noreferrer">日志化的</a>，但数据操作不是，这意味着数据操作（如向文件写入数据）与元数据操作（如调整文件大小以进行写入）之间没有顺序保证。</p>

<p>我们可以通过在创建日志文件时添加校验和来解决这个问题。如果 <code>log</code> 的内容不包含有效的校验和，那么我们就知道遇到了上面描述的情况。</p>

<pre><code>creat(/dir/log);
write(/dir/log, “2, 3, [checksum], foo”);  // 向日志文件添加校验和
fsync(/dir/log);
pwrite(/dir/orig, 2, “bar”);
fsync(/dir/orig);
unlink(/dir/log);
</code></pre>

<p>这至少在 ext3 的当前配置上是安全的。但根据文件系统，如果不对父目录执行 fsync，日志可能永远不会被创建。</p>

<pre><code>creat(/dir/log);
write(/dir/log, “2, 3, [checksum], foo”);
fsync(/dir/log);
fsync(/dir);  // 对日志文件的父目录执行 fsync
pwrite(/dir/orig, 2, “bar”);
fsync(/dir/orig);
unlink(/dir/log);
</code></pre>

<p>这应该能防止在任何 Linux 文件系统上发生损坏，但如果我们想确保文件确实包含“bar”，我们需要在最后再添加一次 fsync。</p>

<pre><code>creat(/dir/log);
write(/dir/log, “2, 3, [checksum], foo”);
fsync(/dir/log);
fsync(/dir);
pwrite(/dir/orig, 2, “bar”);
fsync(/dir/orig);
unlink(/dir/log);
fsync(/dir);
</code></pre>

<p>这导致了一致的行为，并保证我们的操作在完成后确实修改了文件，前提是我们假设 <code>fsync</code> 确实刷新到磁盘。OS X 和一些版本的 ext3 中的 fsync 并没有真正刷新到磁盘。OS X 需要 <code>fcntl(F_FULLFSYNC)</code> 才能刷新到磁盘，而一些版本的 ext3 只有在<a href="https://en.wikipedia.org/wiki/Inode" rel="noopener noreferrer">inode</a> 发生更改时才会刷新到磁盘（由于 inode mtime 具有一秒的粒度，这在对同一文件写入时最多每秒发生一次），这是一种优化。</p>

<p>即使我们假设 fsync 向磁盘发出刷新命令，一些磁盘也会忽略刷新指令，原因与 OS X 和一些版本的 ext3 中 fsync 功能受限的原因相同——为了在基准测试中表现更好。处理这个问题超出了本文的范围，但<a href="http://www.researchgate.net/profile/Vijay_Chidambaram/publication/220958003_Coerced_Cache_Eviction_and_discreet_mode_journaling_Dealing_with_misbehaving_disks/links/54d0f0190cf29ca811040c8a.pdf" rel="noopener noreferrer">Rajimwale 等人在 DSN '11 上发表的论文</a>及相关研究涵盖了该问题。</p>

<h3 id="filesystem-semantics">文件系统语义</h3>

<p>当作者们检查 ext2、ext3、ext4、btrfs 和 xfs 时，他们发现编写代码以保持一致性的方式存在显著差异。他们编写了一个工具来收集块级文件系统跟踪，并用它来确定哪些属性在特定文件系统上不成立。作者们谨慎地指出，他们只能确定属性何时不成立——如果他们没有发现违反属性的情况，这并不能保证该属性成立。</p>

<p><img src="https://danluu.com/images/file-consistency/fs_properties.png" alt="不同的文件系统具有非常不同的属性"></p>

<p>X 表示该属性被违反。原子性属性基本上是你所期望的，例如，对于单扇区覆盖没有 X 表示写入单个扇区是原子的。作者们指出，单扇区覆盖的原子性有时来自他们使用的磁盘属性，在某些磁盘上运行这些文件系统可能无法获得单扇区原子性。排序属性也基本上如它们的名字所暗示的那样，例如，在“覆盖 -&gt; 任何操作”行中的 X 表示覆盖可能与某些操作重排序。</p>

<p>在创建了测试文件系统属性的工具之后，他们又创建了一个工具来检查是否有任何应用程序依赖于任何可能不正确的文件系统属性。因为不变量是特定于应用程序的，作者为每个测试的应用程序编写了检查器。</p>

<p><img src="https://danluu.com/images/file-consistency/program_bugs.png" alt="一切都坏了"></p>

<p>作者发现大多数测试的应用程序都有问题，包括你非常希望其能正常工作的应用程序，如 LevelDB、HDFS、Zookeeper 和 git。在一次演讲中，一位作者指出，sqlite 的开发者对这些问题有非常深入的理解，但这仍然不足以防止所有错误。该演讲者还指出，版本控制系统在这方面特别糟糕，开发者的态度相当松懈，这使得作者很容易在他们的工具中找到很多问题。
最常见的错误类别是错误地假设系统调用之间的排序。第二常见的错误类别是假设系统调用是原子的<sup id="fnref:A"><a href="#fn:A" rel="noopener noreferrer">2</a></sup>。这些本质上是人们在进行多线程编程时遇到的相同问题。正确推理重排序行为并正确插入屏障是困难的。但尽管共享内存并发性被认为是一个需要极大关注的难题，但写入文件的方式却不同，尽管在很多方面它实际上更难。</p>

<p>这里需要注意的是，虽然 btrfs 的语义本身并不比 ext3/ext4 不可靠，但更多的应用程序在 btrfs 上损坏数据，因为开发者不习惯针对允许目录操作重排序的文件系统进行编码（ext2 可能是最近一个广泛使用且允许这种重排序的文件系统）。当人们开始使用具有字节级原子性的 NVRAM 驱动器时，我们可能会看到类似水平的错误暴露。人们几乎总是只运行一些测试来看看事情是否有效，而不是确保他们根据 POSIX 文件系统允许的内容进行编码。</p>

<p>硬件内存排序语义通常<a href="//danluu.com/new-cpu-features/#memory-concurrency" rel="noopener noreferrer">有很好的文档</a>，可以简单地确定哪些操作可以与哪些其他操作重排序，以及哪些操作是原子的。相比之下，<a href="http://man7.org/linux/man-pages/man5/ext4.5.html" rel="noopener noreferrer">ext 手册页</a>是这样描述其三种数据模式的：</p>

<blockquote>
<p>journal：所有数据在写入主文件系统之前都提交到日志中。</p>

<p>ordered：这是默认模式。所有数据在元数据提交到日志之前都直接强制输出到主文件系统。</p>

<p>writeback：数据顺序未被保留——数据可能在元数据提交到日志之后才写入主文件系统。<strong>据传言</strong>这是吞吐量最高的选项。它保证内部文件系统完整性，但可能允许旧数据在崩溃和日志恢复后出现在文件中。</p>
</blockquote>

<p>手册页确实提到了传言。这就是我们拥有的文档水平。如果我们回顾之前的例子，我们需要在 <code>write(/dir/log, “2, 3, foo”)</code> 和 <code>pwrite(/dir/orig, 2, “bar”)</code> 之间添加 <code>fsync</code> 以防止重排序，我认为从手册页的描述中并不能明显看出 <code>fsync</code> 的必要性。如果你看看上面的硬件内存排序“手册页”，它明确定义了排序语义，当然不会依赖传言。</p>

<p>这并不是说文件系统语义没有任何文档。在 <a href="http://lwn.net/" rel="noopener noreferrer">lwn</a> 和 LKML 之间，可以很好地了解事情是如何运作的。但深入挖掘所有这些信息非常困难，因此对于<a href="http://austingroupbugs.net/view.php?id=672" rel="noopener noreferrer">关于事情如何运作的长时间、不确定的讨论仍然很常见</a>。很多信息是错误的，即使信息在发布时是正确的，也常常过时了。</p>

<p>在挖掘档案时，我经常看到一篇 2005 年的帖子被引用作为证据，声称 OS X <code>fsync</code> 与 Linux <code>fsync</code> 相同，而 OS X <code>fcntl(F_FULLFSYNC)</code> 甚至比 Linux 上的任何可用选项更安全。即使在当时，我认为这对于 2.4 内核也不成立，但对于 2.6 内核成立。但自 2008 年左右，带有 ext3 的 Linux 2.6 每次 fsync 都会执行完整的磁盘刷新（如果磁盘支持，并且文件系统没有被特殊配置为关闭 barriers）。</p>

<p>另一个问题是，你经常还会看到类似<a href="http://lkml.iu.edu/hypermail/linux/kernel/0908.3/01481.html" rel="noopener noreferrer">这样</a>的交流：</p>

<p><strong>开发者 1</strong>：个人而言，我关心元数据一致性，ext3 文档表明日志保护其完整性。但除了在损坏的存储设备上，这并不成立，你仍然需要在那里运行 fsck。<br>
<strong>开发者 2</strong>：正如 ext3 作者多年来多次指出的那样，你仍然需要定期运行 fsck。<br>
<strong>开发者 1</strong>：这在文档哪里有？<br>
<strong>开发者 2</strong>：在 linux-kernel 邮件列表存档中。<br>
<strong>开发者 3</strong>：可能是在六到八年前的电子邮件中，由我发表的。<br></p>

<p>这在文档哪里？哦，在某个六到八年前的邮件列表帖子中（也就是从现在起 12-14 年前）。我并不是要挑剔文件系统开发者。我读过的 fs 开发者的帖子相比于 LKML 的名声是相当有礼貌的；他们慷慨地花了很多时间回答基本问题，我对专家级 fs 开发者在面对提问者时的耐心印象深刻，但外人很难翻阅十五年的邮件列表帖子来弄清楚哪些仍然有效，哪些已被淘汰！</p>

<p>在他们的 OSDI 2014 演讲中，我们讨论的论文作者指出，当他们报告发现的错误时，开发者的回应常常是“POSIX 不允许文件系统这样做”，却无法指出任何具体的 POSIX 文档来支持他们的说法。如果你关注过 Kyle Kingsbury 的 Jepsen 工作，这可能听起来很熟悉，只不过开发者说的是“文件系统不会那样做”而不是“网络不会那样做”。我认为这是可以理解的，鉴于存在如此多的错误信息。我自己不是文件系统开发者，如果我在本文中至少没有一个错误，我会感到有点惊讶。</p>

<h3 id="filesystem-correctness">文件系统正确性</h3>

<p>我们已经在正确保存数据方面遇到了很多复杂性，而这只是涉及内容的皮毛。到目前为止，我们假设磁盘工作正常，或者至少文件系统能够通过 <a href="https://en.wikipedia.org/wiki/S.M.A.R.T." rel="noopener noreferrer">SMART</a> 或其他某种监控来检测磁盘何时出错。在我开始研究之前，我一直以为情况就是这样，但这个假设被证明是完全错误的。</p>

<p><a href="http://research.cs.wisc.edu/wind/Publications/iron-sosp05.pdf" rel="noopener noreferrer">Prabhakaran 等人在 SOSP 05 上发表的论文</a>详细研究了文件系统如何响应磁盘错误。他们创建了一个故障注入层，允许他们注入磁盘故障，然后运行诸如 <code>chdir</code>、<code>chroot</code>、<code>stat</code>、<code>open</code>、<code>write</code> 等操作，看看会发生什么。</p>

<p>在 ext3、reiserfs 和 NTFS 之间，reiserfs 在处理错误方面做得最好，它似乎是设计过程中唯一将错误视为一等公民的文件系统。它在读取时大致一致地将错误传播给用户，并在写入失败时调用 <code>panic</code>，这会触发重启和恢复。这种总体策略使文件系统能够优雅地处理读取失败并避免写入失败时的数据损坏。然而，作者发现了一些不一致性和错误。例如，reiserfs 不能正确处理间接块上的读取错误并泄漏空间，并且某种类型的写入失败不会阻止 reiserfs 更新日志和提交事务，这可能导致数据损坏。</p>

<p>reiserfs 是好的情况。作者发现 ext3 在大多数情况下忽略了写入失败，并在大多数读取失败的情况下使文件系统变为只读。这似乎与你想要的策略几乎相反。忽略写入失败很容易导致数据损坏，并且如果读取错误是瞬态错误（瞬态错误很常见），将文件系统重新挂载为只读是一种过度的反应。此外，ext3 在这三个文件系统中进行的检查最少，最有可能无法检测到错误。在一次演讲中，一位作者评论说 ext3 代码中有很多这样的注释：“我真的希望这里不会发生写入错误”，而这些地方都没有处理错误。</p>

<p>NTFS 介于两者之间。作者发现它有许多内置的检查，并且在将错误传播给用户方面做得很好。然而，像 ext3 一样，它忽略了写入失败。</p>

<p>这篇论文对确切故障模式有更详细的描述，但由于许多错误已经被修复，这些细节大多具有历史意义。</p>

<p>如果能看到这篇论文的更新版本，那就太好了，在一次演讲中，观众中有人问是否有更及时的信息。演讲者回答说他们很想知道现在的情况，但在学术界很难做这种工作，因为研究生不想重复已经做过的工作，考虑到他们面临的激励，这相当合理。做复制研究工作量很大，通常几乎与原始论文一样多，而复制研究通常几乎得不到学术认可。这是许多激励机制与产生现实世界影响严重不匹配的情况之一。</p>

<p><a href="http://usenix.org/legacy/event/fast08/tech/full_papers/gunawi/gunawi_html/index.html" rel="noopener noreferrer">Gunawi 等人在 FAST 08 上发表的论文</a>是另一个如今很值得看到被复制研究的论文。那篇论文延续了我们刚刚看过的论文，使用一个简单的静态分析工具来检查不同文件系统中的错误处理代码，找到错误被丢弃的情况。论文中对“被丢弃”的定义非常宽松——像下面这样的代码</p>

<pre><code>if (error) {
    printk(“I have no idea how to handle this error\n”);
}
</code></pre>

<p>被视为<em>没有</em>丢弃错误。如果程序的执行流程不依赖于返回错误代码的函数所返回的错误代码，则认为错误被忽略。</p>

<p>使用该工具，他们发现大多数文件系统都丢弃了很多错误代码：</p>

<p></p><p></p>

<p><br></p><div><p></p>

<p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><table align="CENTER">

<tbody><tr><td align="CENTER"><p></p>

<p></p></td>

<td align="CENTER" colspan="2"> <b>By % Broken</b></td>

<td align="CENTER" colspan="2"> <b>By Viol/Kloc</b></td>

</tr>

<tr><td align="CENTER"><p></p>

<p>Rank </p></td>

<td align="LEFT"> FS </td>

<td align="RIGHT"> Frac. </td>

<td align="LEFT" colspan="2"> FS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Viol/Kloc</td>

</tr>

<tr><td align="CENTER"><p></p>

<p>1 </p></td>

<td align="LEFT"> IBM JFS </td>

<td align="RIGHT"> 24.4 </td>

<td align="LEFT"> ext3 </td>

<td align="RIGHT"> 7.2 </td>

</tr>

<tr><td align="CENTER"><p></p>

<p>2 </p></td>

<td align="LEFT"> ext3 </td>

<td align="RIGHT"> 22.1 </td>

<td align="LEFT"> IBM JFS </td>

<td align="RIGHT"> 5.6 </td>

</tr>

<tr><td align="CENTER"><p></p>

<p>3 </p></td>

<td align="LEFT"> JFFS v2 </td>

<td align="RIGHT"> 15.7 </td>

<td align="LEFT"> NFS Client </td>

<td align="RIGHT"> 3.6 </td>

</tr>

<tr><td align="CENTER"><p></p>

<p>4 </p></td>

<td align="LEFT"> NFS Client </td>

<td align="RIGHT"> 12.9 </td>

<td align="LEFT"> VFS </td>

<td align="RIGHT"> 2.9 </td>

</tr>

<tr><td align="CENTER"><p></p>

<p>5 </p></td>

<td align="LEFT"> CIFS </td>

<td align="RIGHT"> 12.7 </td>

<td align="LEFT"> JFFS v2 </td>

<td align="RIGHT"> 2.2 </td>

</tr>

<tr><td align="CENTER"><p></p>

<p>6 </p></td>

<td align="LEFT"> MemMgmt </td>

<td align="RIGHT"> 11.4 </td>

<td align="LEFT"> CIFS </td>

<td align="RIGHT"> 2.1 </td>

</tr>

<tr><td align="CENTER"><p></p>

<p>7 </p></td>

<td align="LEFT"> ReiserFS </td>

<td align="RIGHT"> 10.5 </td>

<td align="LEFT"> MemMgmt </td>

<td align="RIGHT"> 2.0 </td>

</tr>

<tr><td align="CENTER"><p></p>

<p>8 </p></td>

<td align="LEFT"> VFS </td>

<td align="RIGHT"> 8.4 </td>

<td align="LEFT"> ReiserFS </td>

<td align="RIGHT"> 1.8 </td>

</tr>

<tr><td align="CENTER"><p></p>

<p>9 </p></td>

<td align="LEFT"> NTFS </td>

<td align="RIGHT"> 8.1 </td>

<td align="LEFT"> XFS </td>

<td align="RIGHT"> 1.4 </td>

</tr>

<tr><td align="CENTER"><p></p>

<p>10 </p></td>

<td align="LEFT"> XFS </td>

<td align="RIGHT"> 6.9 </td>

<td align="LEFT"> NFS Server </td>

<td align="RIGHT"> 1.2 </td>

</tr>

</tbody></table><p></p>

<p></p></div><p></p>

<p><br></p>

<p><a rel="noopener noreferrer"></a></p>

<p>他们在被忽略的错误旁边发现的注释包括：“我们应该传回任何错误吗？”，“错误，跳过块，希望最好。”，“无法将 ext3_mark_inode_dirty() 返回的错误报告给用户空间。所以忽略它。”，“注意：待办：记录错误处理器。”，“我们对此错误无能为力。”，“此时忽略错误。除了尝试继续前进，我们无能为力。”，“Retval ignored?”，以及“待办：处理失败。”</p>

<p>需要注意的一点是，在许多情况下，忽略错误更多是架构问题的征兆，而不是错误本身（例如，ext3 在检查点期间忽略写入错误是因为它没有任何恢复机制）。但即便如此，论文作者发现了许多真正的错误。</p>

<h3 id="error-recovery">错误恢复</h3>

<p>每个广泛使用的文件系统都有在错误条件下引起问题的错误，这就引出了两个问题。恢复工具能否稳健地修复错误，错误发生频率如何？它们如何处理从这些问题中的恢复？<a href="http://usenix.org/legacy/events/osdi08/tech/full_papers/gunawi/gunawi_html/index.html" rel="noopener noreferrer">Gunawi 等人在 OSDI 08 上发表的论文</a>对此进行了研究，发现 fsck 这个用于检查和修复文件系统的标准工具，“以不正确的顺序检查和修复某些指针……文件系统甚至可能无法挂载”。</p>

<p>到目前为止，我们知道即使底层文件系统是正确的，以确保文件稳健性的方式编写文件也相当困难，底层文件系统会有错误，尝试修复文件系统损坏可能会进一步损坏或破坏它。错误发生的频率如何？</p>

<h3 id="error-frequency">错误频率</h3>

<p><a href="http://bnrg.eecs.berkeley.edu/~randy/Courses/CS294.F07/11.1.pdf" rel="noopener noreferrer">Bairavasundaram 等人在 SIGMETRICS '07 上发表的论文</a>发现，根据具体型号的不同，5% 到 20% 的磁盘在两年内至少会出现一次错误。有趣的是，其中许多是孤立的错误——38% 出现错误的磁盘只有一次错误，80% 的错误少于 50 次。<a href="https://www.usenix.org/legacy/events/fast08/tech/full_papers/bairavasundaram/bairavasundaram_html/main.html" rel="noopener noreferrer">一项后续研究</a>研究了数据损坏，发现仅通过校验和检测到的静默数据损坏发生在每年 0.5% 的磁盘上，其中一个非常糟糕的型号在一年内有 4% 的磁盘出现损坏。</p>

<p>同样值得注意的是，他们发现某些型号的磁盘在磁盘之间的错误率具有很高的局部性。例如，有一个型号的磁盘在一个特定扇区具有非常高的错误率，这使得许多形式的 RAID 几乎无法用于冗余。</p>

<p>这是另一项值得复制研究的研究。<a href="https://www.backblaze.com/blog/hard-drive-reality-q3-2015/" rel="noopener noreferrer">大多数关于磁盘的研究都关注整个磁盘的故障率</a>，但如果你担心的是数据损坏，那么未故障磁盘中的错误比磁盘故障更令人担忧，后者很容易检测和缓解。</p>

<h3 id="conclusion">结论</h3>

<p>文件很复杂。<a href="//danluu.com/butler-lampson-1999/#parallelism" rel="noopener noreferrer">Butler Lampson 曾说过</a>，当他们在 PARC 提出线程、锁和条件变量时，他们认为他们创建了一个任何人都可以使用的编程模型，但现在已经有了几十年的证据表明他们是错误的。我们积累了大量证据表明人类非常不擅长推理这类问题，这些问题与编写与当前文件系统交互的正确代码时遇到的问题非常相似。Lampson 建议，已知最好的通用解决方案是将所有并行性打包到尽可能小的框中，然后让一个向导编写框中的代码。转换到文件系统，这相当于说，作为应用程序开发者，安全地写入文件足够困难，应该通过某种库和/或数据库来完成，而不是直接进行系统调用。</p>

<p>如果你想有一个好的默认选项，Sqlite 在可靠性方面相当不错。然而，有些人发现如果他们只想要一个基于文件的抽象，它太笨重了。他们真正想要的是文件抽象的一种多填充（polyfill），它可以在所有文件系统上工作，而不需要理解每个文件系统不同配置（甚至不同版本）之间的差异。由于那还不存在，当没有现有的库足够时，你需要对数据进行校验和，因为你将会遇到静默错误和损坏。唯一的问题是你是否检测到这些错误，以及你的记录格式在损坏发生时是只破坏一条记录，还是破坏整个数据库。据我所知，大多数桌面电子邮件客户端开发者选择了后者，即在损坏发生时销毁你所有的邮件。</p>

<p>这些研究也强化了<a href="//danluu.com/everything-is-broken/" rel="noopener noreferrer">传统测试是不够的</a>这一观点。有多个案例中，论文的作者编写了一个相对简单的工具并发现了大量的错误。你不需要任何深奥的计算机科学魔法来编写这些工具。发现文件系统错误处理中大量错误的论文中的错误传播检查器只有 4000 行代码。如果你阅读论文，你会看到作者观察到该工具由于其简单性而有很多缺点，但尽管有这些缺点，它仍然能够发现很多真正的错误。我在上一份工作中写了一个类似的工具来强制执行一些不变量，它实际上只有两页代码。它甚至没有真正的解析器（它只是逐行遍历文件并进行一些正则表达式匹配来检测仅使用状态机和正则表达式就能检测到的简单错误），但它发现的错误足够多，以至于在第一次运行时就收回了开发时间的成本。</p>

<p>我见过的几乎每个软件项目都有很多唾手可得的测试果实。非常基本的<a href="//danluu.com/testing/" rel="noopener noreferrer">随机测试</a>、<a href="//danluu.com/pl-troll/" rel="noopener noreferrer">静态分析</a>和<a href="https://aphyr.com/tags/jepsen" rel="noopener noreferrer">故障注入</a>几乎在你第一次使用时就能在开发时间方面收回成本。</p>

<h3 id="appendix">附录</h3>

<p>我可能只涵盖了我引用的论文中材料的不到 20%。以下是一些你可以在这些论文和其他论文中找到的其他有趣信息。</p>

<p><a href="https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-pillai.pdf" rel="noopener noreferrer">Pillai 等人，OSDI '14</a>：这篇论文比本文更详细地介绍了崩溃一致性所需的内容。它还提供了关于应用程序如何确切失败的相当多的细节，包括表明每个跟踪中嵌入了哪些错误假设的跟踪图。</p>

<p><a href="http://static.usenix.org/legacy/events/fast12/tech/full_papers/Chidambaram.pdf" rel="noopener noreferrer">Chidambara 等人，FAST '12</a>：相同的文件系统原语负责一致性和排序。作者提出了替代原语，将这些关注点分离，在保持安全性的同时允许更好的性能。</p>

<p><a href="https://www.researchgate.net/publication/220958003_Coerced_Cache_Eviction_and_Discreet_Mode_Journaling_Dealing_with_Misbehaving_Disks" rel="noopener noreferrer">Rajimwale 等人，DSN '01</a>：你可能不应该使用忽略刷新指令的磁盘，但如果你使用了，这里有一个协议使用普通的文件系统操作强制这些磁盘刷新。正如你可能预料的那样，这方面的性能相当差。</p>

<p><a href="http://research.cs.wisc.edu/wind/Publications/iron-sosp05.pdf" rel="noopener noreferrer">Prabhakaran 等人，SOSP '05</a>：这篇论文比本文更详细地介绍了文件系统对错误的响应。作者还讨论了 JFS，一个 IBM 用于 AIX 的文件系统。尽管它是为高可靠性系统设计的，但它并不比替代方案特别可靠。相关材料在 <a href="http://research.cs.wisc.edu/adsl/Publications/pointer-dsn08.pdf" rel="noopener noreferrer">DSN '08</a>、<a href="http://research.cs.wisc.edu/adsl/Publications/trust-storagess06.pdf" rel="noopener noreferrer">StorageSS '06</a>、<a href="http://research.cs.wisc.edu/adsl/Publications/vmdep-dsn06.pdf" rel="noopener noreferrer">DSN '06</a>、<a href="http://research.cs.wisc.edu/adsl/Publications/parity-fast08.pdf" rel="noopener noreferrer">FAST '08</a> 和 <a href="http://research.cs.wisc.edu/adsl/Publications/envyfs-usenix09.pdf" rel="noopener noreferrer">USENIX '09</a> 等论文中有更多介绍。</p>

<p><a href="http://usenix.org/legacy/event/fast08/tech/full_papers/gunawi/gunawi_html/index.html" rel="noopener noreferrer">Gunawi 等人，FAST '08</a>：同样，关于错误何时被丢弃以及他们如何编写工具的细节比本文多得多。他们还有一些调用图，粗略地给出了文件系统涉及的复杂性。XFS 的调用图特别混乱，一位作者在一次演讲中提到，一位 XFS 开发者说 XFS 工作起来很有趣，因为他们利用了每一个可能的优化机会，无论这让事情变得多么混乱。</p>

<p><a href="http://bnrg.eecs.berkeley.edu/~randy/Courses/CS294.F07/11.1.pdf" rel="noopener noreferrer">Bairavasundaram 等人，SIGMETRICS '07</a>：有很多关于磁盘错误局部性和磁盘错误随时间概率的信息，本文没有涵盖。<a href="http://research.cs.wisc.edu/adsl/Publications/corruption-fast08.pdf" rel="noopener noreferrer">FAST08 中的一篇后续论文有更多细节</a>。</p>

<p><a href="http://usenix.org/legacy/events/osdi08/tech/full_papers/gunawi/gunawi_html/index.html" rel="noopener noreferrer">Gunawi 等人，OSDI '08</a>：这篇论文详细介绍了 fsck 何时不起作用。在一次演讲中，一位作者提到 fsck 是唯一侮辱过他的程序。显然，如果你有一个指向超级块的损坏指针，fsck 会破坏超级块（可能使磁盘无法挂载），告诉你类似“你这个笨蛋，你一定是在已挂载的磁盘上运行了 fsck”，然后放弃。在论文中，作者使用声明式模型重新实现了基本上所有的 fsck，并发现声明式版本更短、更容易理解、更容易扩展，代价是稍微慢一些。</p>

<p>内存错误超出了本文的范围，但<a href="//danluu.com/why-ecc/" rel="noopener noreferrer">内存损坏</a>会导致磁盘损坏。这特别烦人，因为内存损坏可能导致你对错误数据进行校验和并写入错误的校验和。也有可能损坏内存中的指针，这通常会导致非常糟糕的情况发生。参见 <a href="http://research.cs.wisc.edu/adsl/Publications/zfs-corruption-fast10.pdf" rel="noopener noreferrer">Zhang 等人在 FAST '10 上的论文</a>，了解更多关于 ZFS 如何受此影响的信息。有传言说 ZFS 对内存损坏是安全的，因为它有校验和，但那篇论文发现内存中保存的关键内容没有校验和，并且内存错误在真实场景中可能导致数据损坏。</p>

<p>sqlite 开发者在<a href="https://www.sqlite.org/howtocorrupt.html" rel="noopener noreferrer">文档</a>和<a href="https://www.sqlite.org/testing.html" rel="noopener noreferrer">测试</a>方面都很认真。如果我想编写一个可靠的桌面应用程序，我会先阅读 sqlite 文档，然后与一些核心开发者交谈。如果我想编写一个可靠的分布式应用程序，我会先在 Google 找份工作，然后阅读 GFS、Colossus、Spanner 等的设计文档和<a href="//danluu.com/postmortem-lessons/" rel="noopener noreferrer">事后分析报告</a>。开玩笑的，但也不完全是。</p>

<p>我们完全没有看形式化方法，但已有各种尝试来形式化验证文件系统的属性，例如 <a href="https://sibylfs.github.io/" rel="noopener noreferrer">SibylFS</a>。</p>

<p>这个列表并不打算详尽无遗。它只是我读过的一些我认为有趣的东西的列表。</p>

<p><strong>更新</strong>：很多人读了这篇帖子并建议，在第一个文件示例中，你应该使用一个更简单的协议：将要修改的文件复制到临时文件，修改临时文件，然后将临时文件重命名以覆盖原始文件。事实上，这可能是我在这篇帖子上收到的最常见的评论。如果你认为这解决了问题，我会请你暂停五秒钟，考虑一下这可能带来的问题。</p>

<p>这带来的主要问题是：</p>

<ul>
<li>在崩溃时重命名不是原子的。POSIX 说重命名是原子的，但这只适用于正常操作，不适用于崩溃。</li>
<li>即使这种技术有效，性能也非常差</li>
<li>你如何处理硬链接？</li>
<li>元数据可能会丢失；在某些文件系统下，有时可以通过 ioctls 保留这些信息，但现在你有了仅用于非崩溃情况的文件系统特定代码</li>
<li>等等。</li>
</ul>

<p>这么多人认为这是一个简单的解决方案，这证明了这个问题是人们容易低估的，即使他们被明确警告人们倾向于低估这个问题！</p>

<p><strong><a href="//danluu.com/filesystem-errors/" rel="noopener noreferrer">本文重现了这些论文在 2017 年现代文件系统上的一些结果</a>。</strong></p>

<p><strong><a href="//danluu.com/deconstruct-files/" rel="noopener noreferrer">这个演讲（文字记录）包含了许多新的结果，并更详细地讨论了硬件问题</a>。</strong></p>

<p><small>
感谢 Leah Hanson、Katerina Barone-Adesi、Jamie Brandon、Kamal Marhubi、Joe Wilder、David Turner、Benjamin Gilbert、Tom Murphy、Chris Ball、Joe Doliner、Alexy Romanov、Mindy Preston、Paul McJones、Evan Jones 和 Jason Petersen 的评论/更正/讨论。
</small></p>
<div>

<hr>

<ol>
<li id="fn:D">事实证明<a href="https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Storage_Administration_Guide/ch-ext3.html" rel="noopener noreferrer">一些商业支持的发行版</a>只支持 <code>data=ordered</code>。哦，当我说 <code>data=ordered</code> 是默认值时，那只适用于 2.6.30 之前。在 2.6.30 之后，有一个配置选项 <code>CONFIG_EXT3_DEFAULTS_TO_ORDERED</code>。如果未设置，该选项，该默认值变为 <code>data=writeback</code>。
 <a href="#fnref:D" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:A"><p>需要覆盖原子性的案例被记录为已知问题，所有此类案例都假设单块原子性而不是多块原子性。相比之下，多个应用程序（LevelDB、Mercurial 和 HSQLDB）存在严重的数据损坏错误，这些错误源于假设追加是原子的。</p>

<p>这似乎是一种常用更新协议的间接结果，其中修改通过追加记录到日志，然后通过覆盖写入记录的数据。应用程序开发者仔细检查和处理实际数据中的错误，但日志文件中的错误经常被忽视。</p>

<p>论文中还讨论了其他几类错误，如果你从事文件编写应用程序的工作，我建议阅读论文以获取详细信息。
 <a href="#fnref:A" rel="noopener noreferrer"><sup>[返回]</sup></a></p></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 19707 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/file-consistency/</link>
      <guid isPermaLink="false">https://danluu.com/file-consistency/</guid>
      <pubDate>Sat, 12 Dec 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>为何使用ECC？</title>
      <description>[AI 摘要] 本文反驳了反对ECC内存的观点，并论证了其在服务器和消费者设备中的重要性。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文反驳了反对ECC内存的观点，并论证了其在服务器和消费者设备中的重要性。</div><p>Jeff Atwood，或许是阅读量最大的编程博主，发表了一篇<a href="http://blog.codinghorror.com/to-ecc-or-not-to-ecc/" rel="noopener noreferrer">反对使用ECC内存</a>的文章。我的理解是，他的主要论点是：</p>

<ol>
<li>谷歌在1999年构建服务器时并未使用ECC</li>
<li>大多数RAM错误是硬错误，而非软错误</li>
<li>由于硬件改进，RAM错误很罕见</li>
<li>如果ECC真的重要，它应该被广泛使用，而不仅仅是在服务器中。为这种可选功能付费是"极其企业化的"</li>
</ol>

<p></p>

<p>让我们逐一审视这些论点：</p>

<h2 id="1-google-didn-t-use-ecc-in-1999">1. 谷歌在1999年未使用ECC</h2>

<p>在谷歌将这些非ECC机器投入生产后不久，他们就意识到这是一个严重的错误，节省的成本不值得。如果你认为盲目模仿谷歌的做法是个好主意，因为它就是谷歌，那么你可能会做以下事情：</p>

<h4 id="a-put-your-servers-into-shipping-containers">A. 将你的服务器放入集装箱中。</h4>

<p>至今仍有一些文章讨论这是多么棒的主意，尽管这只是谷歌一次被判定为不成功的实验。事实证明，即使是谷歌的实验也并非总是成功。事实上，他们在早期对"登月计划"的执着意味着他们比大多数公司有更多的失败实验。复制他们的失败实验并不是一个特别好的策略。</p>

<h4 id="b-cause-fires-in-your-own-datacenters">B. 在你的数据中心引发火灾</h4>

<p>文章的部分内容讨论了这些服务器有多棒：</p>

<blockquote>
<p>有些人可能会看到这些早期的谷歌服务器，并认为它们是一个业余的消防隐患。我不会。我看到了一种富有远见的理解，即廉价的商品硬件将如何塑造今天的互联网。当我看到这台服务器时，我感到非常亲切；这正是我在相同情况下会做的事情。</p>
</blockquote>

<p>最后一部分是对的。但第一部分也有一点道理。当谷歌开始设计自己的主板时，有一代产品存在再增长<sup id="fnref:R"><a href="#fn:R" rel="noopener noreferrer">1</a></sup>问题，导致了不止一起的火灾。</p>

<p>顺便说一句，如果你点击进入Jeff的文章并查看引用所指的照片，你会发现主板有很大的弯曲。这造成了问题，并在下一代中得到了解决。你还可以观察到布线相当混乱，这同样造成了问题，也在下一代中得到了修复。还有其他问题。<abbr title="当某人看到答案钥匙后说“我也会想到那个”，当他们的答案完美时，这通常是可信的。但当他们看到一个具体的、不完美的答案后才这么说，他们会犯完全相同错误的可能性就有点低了">Jeff在这里的论点似乎是，如果他在当时的情况下，他会看到早期谷歌工程师所看到的完全相同的机会，而且既然谷歌这样做了，那它一定是正确的事情，即使它看起来不是。</abbr>但是，许多让它看起来不是正确的事情，实际上让它就不是正确的事情。</p>

<h4 id="c-make-servers-that-injure-your-employees">C. 制造会伤害你员工的服务器</h4>

<p>谷歌的一代服务器因其极其锋利的边缘而声名狼藉，获得了由"剃刀片和仇恨"制成的名声。</p>

<h4 id="d-create-weather-in-your-datacenters">D. 在你的数据中心制造天气</h4>

<p>从与许多大型科技公司的人交谈来看，他们中的大多数似乎都遇到过气候控制问题，导致数据中心出现云雾。你可能会说这是谷歌复制西雅图天气以挖走微软员工的巧妙计划。或者，也可能是创造字面意义上的云计算的计划。也许不是。</p>

<p>请注意，这些都是谷歌尝试过然后改变了的事情。犯错然后纠正错误在每个成功的工程组织中都很常见。如果你打算盲目模仿一种工程实践，你至少应该模仿当前的工程实践，而不是<a href="//danluu.com/butler-lampson-1999/" rel="noopener noreferrer">1999年做过的事情</a>。</p>

<p>当谷歌在1999年使用没有ECC的服务器时，他们发现了许多最终归因于内存损坏的症状，包括一个搜索索引实际上向查询返回随机结果。这里的实际故障模式很有启发性。我经常听说在这些机器上忽略ECC是可以的，因为单个结果中的错误是可以容忍的。但是，即使你可以容忍偶尔的错误，忽略错误也意味着你会使自己面临完全损坏的风险，除非你进行了非常仔细的分析，确保单个错误只会污染单个结果。在对文件系统进行的研究中，已经反复表明，尽管人们努力创建能够抵抗单个错误的健壮系统，但要做到这一点极其困难，并且基本上每个经过大量测试的文件系统都可能因单个错误而发生大规模故障（<a href="//danluu.com/file-consistency/" rel="noopener noreferrer">如果你感兴趣，可以看看威斯康星州Andrea和Remzi研究小组的输出</a>）。我并不是在贬低文件系统开发者。他们做那种分析的能力比99.9%的程序员都要强。只是这个问题已被反复证明是如此困难，以至于人类无法有效地推理它，而用于此类分析的自动化工具仍然远非一键式过程。在他们的<a href="http://www.morganclaypool.com/doi/abs/10.2200/S00516ED2V01Y201306CAC024" rel="noopener noreferrer">仓库规模计算</a>一书中，谷歌讨论了纠错和检测，并引用ECC作为他们显而易见应该使用硬件纠错的有力例证<sup id="fnref:P"><a href="#fn:P" rel="noopener noreferrer">2</a></sup>。</p>

<p>谷歌拥有出色的基础设施。根据我所听说的其他大型科技公司的基础设施情况，谷歌的听起来是世界上最好的。但这并不意味着你应该复制他们所做的一切。即使你看看他们的好主意，对大多数公司来说复制它们也没有意义。他们<a href="//danluu.com/intel-cat/" rel="noopener noreferrer">创建了一个替代Linux工作窃取调度器的程序，它使用了硬件运行时信息和静态跟踪，以便他们能够利用英特尔服务器处理器中的新硬件，该硬件允许你在核心之间动态划分缓存</a>。如果在其整个机队中使用，这可以轻松地在一周内为谷歌节省比stackexchange在其整个历史中在机器上花费的更多资金。这是否意味着你应该复制谷歌？不，除非你已经摘取了所有较低垂的果实，其中包括确保你的核心基础设施是用高度优化的C++编写的，而不是Java或（天哪）Ruby。问题是，对于绝大多数公司来说，使用一种会带来20倍性能损失的语言是一个完全合理的决定。</p>

<h2 id="2-most-ram-errors-are-hard-errors">2. 大多数RAM错误是硬错误</h2>

<p>反对ECC的论点引用了<a href="http://selse.org//images/selse_2012/Papers/selse2012_submission_4.pdf" rel="noopener noreferrer">一项关于DRAM错误研究的这一部分</a>（加粗部分是Jeff的）：</p>

<blockquote>
<p>我们的研究有几个主要发现。首先，我们发现大约<strong>70%的DRAM故障是重复性（例如，永久性）故障，而只有30%是暂时性故障。</strong>其次，我们发现，大的多位故障，例如影响整个行、列或存储体的故障，占所有DRAM故障的40%以上。第三，我们发现，近5%的DRAM故障影响板级电路，如数据（DQ）或选通（DQS）线。最后，我们发现，chipkill功能将系统的DRAM故障率降低了36倍。</p>
</blockquote>

<p>这似乎暴露了对这项研究意义缺乏理解，因为这段引文听起来不像是反对ECC的论点；它听起来像是支持"chipkill"的论点，这是ECC的一种特定类别。先不谈这个，Jeff的文章指出硬错误是软错误的两倍，然后提到他们在拿到机器时会运行memtest。首先，2:1的比例并不大到你可以直接忽略软错误。其次，文章暗示Jeff认为硬错误基本上是不可变的，不会在一段时间后浮现，这是不正确的。你可以认为电子设备的磨损方式与机械设备的磨损方式相同。机制不同，但效果相似。事实上，如果你比较芯片的可靠性分析与其他类型的可靠性分析，你会发现它们经常使用同一族的分布来建模故障。而且，如果硬错误是不可变的，它们通常会被制造商在测试中发现，制造商可以比消费者更容易地发现错误，因为他们拥有可以比你在家用服务器或家用电脑中更高效地测试内存的电路连接点。第三，Jeff的推理路线暗示ECC不能帮助检测或纠正硬错误，这不仅是不正确的，而且直接与引文相矛盾。</p>

<p>那么，你打算多久在你的机器上运行一次memtest来尝试捕捉这些硬错误，而你愿意容忍多少数据损坏？ECC的一个关键用途不是纠正错误，而是发出错误信号，以便在发生静默损坏之前更换硬件。没有人会同意每天关闭机器上的所有内容来运行memtest（那比直接购买ECC内存更昂贵），即使你能说服人们这样做，它也不会像ECC那样捕捉到那么多错误。</p>

<p>我在一家拥有大约1000台机器的公司工作时，我们注意到我们出现了奇怪的一致性检查失败，大约半年后我们意识到这些失败在某些机器上更容易发生。这些故障相当罕见，平均每周可能只有几次，所以花了相当长的时间来积累数据，又花了更多时间才有人意识到发生了什么。在不知道原因的情况下，分析日志以确定错误很可能是由单位翻转引起的也并非易事。我们很幸运，由于我们使用的过程的一个副作用，校验和是在一个单独的进程中、在不同的机器上、在不同的时间计算的，这样错误就不能损坏结果并将损坏传播到校验和中。如果你只是尝试使用内存中校验和来保护自己，很有可能你会在已经损坏的数据上执行校验和操作，并计算出损坏数据的有效校验和，除非你正在做一些非常花哨的、带有自身校验和的计算（如果你对纠错如此认真，你可能无论如何都会使用ECC）。总之，在完成分析后，我们发现memtest无法检测到任何问题，但在坏机器上更换RAM使错误率降低了一到两个数量级。大多数服务没有我们所拥有的这种校验和机制；那些服务会简单地将损坏的数据静默写入持久存储，并且永远不会注意到问题，直到客户投诉。</p>

<h2 id="3-due-to-advances-in-hardware-manufacturing-errors-are-very-rare">3. 由于硬件制造的进步，错误非常罕见</h2>

<p>Jeff说：</p>

<blockquote>
<p>我确实认真质疑ECC是否如我们被告知的那样对服务器[运营]至关重要，我认为数据显示现代、非ECC RAM已经极其可靠……来自信誉良好供应商的现代商用计算机部件非常可靠。他们的趋势显示，从2012年起，基本的PC部件变得更可靠了，而不是更差。（我也可以为SSD可靠性的提高作证，因为在我们12台拥有24个以上驱动器的服务器中，我们3年来没有出现过一次服务器SSD故障……</p>
</blockquote>

<p>并引用了一项研究。</p>

<p>文章中的数据不足以支持这一断言。请注意，由于RAM使用量正在增加并继续以快速的指数速度增长，RAM故障率必须以更快的指数速度下降才能实际降低数据损坏的发生率。此外，随着芯片不断缩小，特征变得更小，使得“2”中讨论的那种磨损问题更为常见。例如，在20nm时，一个DRAM电容可能只能容纳约50个电子，随着下一代DRAM和事物的继续缩小，这个数字会变得更小。</p>

<p><a href="http://selse.org//images/selse_2012/Papers/selse2012_submission_4.pdf" rel="noopener noreferrer">Atwood引用的2012年研究</a>有一张关于十个随机选择的故障节点（6%的节点至少有一次故障）上纠正错误（所有错误的一个子集）的图表：</p>

<p><img src="https://danluu.com/images/why-ecc/one_month_ecc_errors.png"></p>

<p>我们谈论的是一个典型的故障节点上10到1万次错误，而这是从一篇论证你不需要ECC的文章中精选的研究。请注意，这里的节点只有16GB的RAM，比现代服务器通常拥有的内存少一个数量级，而且这是在比我们现在更不易受噪声影响的较老工艺节点上进行的。对于任何习惯于处理可靠性问题并只想知道FIT率的人来说，该研究发现的FIT率在每Mbit 0.057到0.071次故障之间（与Atwood的断言相反，这并不是一个低得惊人的数字）。如果你取最乐观的FIT率0.057，并为一台没有太多RAM的服务器（这里，我使用128GB，因为我现在看到的服务器通常有128GB到1.5TB的RAM）进行计算，你会得到一个期望值：0.057 * 1000 * 1000 * 8760 / 1000000000 = 每台服务器每年0.5次故障。请注意，这是故障，而不是错误。从上面的图表可以看出，一个故障很容易在一个月内造成数百或数千次错误。另一件需要注意的事情是，有多个节点在研究开始时没有错误，但在后期出现了错误。所以，事实上，Jeff链接的精选研究与Jeff关于可靠性的说法相矛盾。</p>

<p>太阳/甲骨文公司几十年前就臭名昭著地遇到了这个问题。晶体管和DRAM电容变得越来越小，就像现在一样，内存使用量和缓存也在增长，就像现在一样。由于更小的晶体管对暂时性干扰的抵抗力更差，制造难度也更大，并且片上缓存更多，绝大多数服务器供应商决定在他们的缓存中添加ECC。太阳公司决定节省几美元，跳过了ECC。直接结果是，一些太阳公司的客户报告了零星的数据损坏。太阳公司花了多年时间才推出带有ECC缓存的新架构，并且要求客户签署保密协议才能获得更换芯片。当然，永远无法掩盖这种事情，当事情暴露时，太阳公司生产可靠服务器的声誉受到了永久性的打击，就像他们试图<a href="https://danluu.com/anon-benchmark/" rel="noopener noreferrer">通过在服务条款中加入禁止基准测试的条款来掩盖糟糕的性能结果</a>一样。</p>

<p>这里需要注意的另一件事是，当你为ECC付费时，你不仅仅是在为ECC付费，你还在为经过更彻底验证的部件（CPU、主板）付费。你可以在磁盘故障率中轻松看到这一点，而且我见过许多人在他们自己的私人数据集中观察到这一点。在公共数据方面，我相信Andrea和Remzi的小组几年前在SIGMETRICS上发表了一篇论文，显示SATA驱动器读取故障的可能性是SCSI驱动器的4倍，静默数据损坏的可能性是10倍。即使来自同一制造商的驱动器，这种关系也成立。没有特别的理由认为SCSI接口应该比SATA接口更可靠，但这不是关于接口的问题。这是关于购买高可靠性服务器部件与消费类部件的问题。也许你并不特别在意磁盘可靠性，因为你对所有东西都做了校验和，可以轻松检测到磁盘损坏，但有些损坏更难检测。</p>

<p>[2024年更新，近十年后]：回顾来看，我们可以看到Jeff的断言，即商品部件是可靠的，“来自信誉良好供应商的现代商用计算机部件非常可靠”，仍然不成立。根据Firefox的真实用户数据，<a href="https://fosstodon.org/@gabrielesvelto/112401643131904845" rel="noopener noreferrer">Gabriele Svelto估计大约10%到20%的Firefox崩溃是由内存损坏引起的</a>。追踪此类问题的各种游戏公司也报告称，相当一部分用户崩溃似乎是由数据损坏引起的，尽管我没有手头任何这些公司的估算。一个更直接的论点是，如果你与那些运行大量ECC内存的大公司的人交谈，并查看ECC错误率，即使ECC内存的错误率通常低于随机非ECC内存，也会发现相当多的错误被ECC内存检测到。这种论点经常被提出（这里，十年前已经详细阐述过，当我最近在Twitter工作时查看过这个问题，而且内存技术并没有发生革命性的变化来降低十年论文中讨论的ECC需求），但通常与那些说“好吧，那些位可能根本不重要”、“大多数内存最终没有被读取”等话的人没有共鸣。查看真实的崩溃情况并注意到静默数据损坏的数量预计会远高于崩溃率，似乎能引起那些对查看数据中心原始FIT率不感兴趣的人的共鸣。</p>

<h2 id="4-if-ecc-were-actually-important-it-would-be-used-everywhere-and-not-just-servers">4. 如果ECC真的重要，它应该被广泛使用，而不仅仅是在服务器中。</h2>

<p><a href="https://danluu.com/cocktail-ideas/" rel="noopener noreferrer">一种重新表述的方式是将其视为一种鸡尾酒会有效市场假说。这不可能重要，因为如果是这样，我们就会拥有它。</a>当然这是不正确的，并且有许多对消费者有益的东西我们并没有，比如<a href="https://danluu.com/car-safety/" rel="noopener noreferrer">设计为安全而不仅仅是在碰撞测试中获得最高分的汽车</a>。从服务器市场和消费市场的角度来看，这个论点可以重新表述为“如果这个功能对服务器真的重要，它就会被用在非服务器上”，这是不正确的。服务器与非服务器可用功能的主要驱动因素是可以添加的、服务器购买者愿意支付高价的功能，以实现服务器和非服务器部件之间的价格歧视。这实际上是大型云供应商面临的更令人讨厌的问题之一——硬件供应商能够抬高具有服务器功能部件的价格，因为这些功能在服务器应用中比在桌面应用中更有价值。大多数家庭用户并不介意，这为硬件供应商提供了一种机制，可以从购买服务器的人那里提取更多资金，同时为消费者提供廉价部件。</p>

<p>云供应商通常有足够的谈判能力以成本价获得部件，但这只适用于有多个可行供应商的情况。为数不多的没有可行竞争者的领域包括CPU和GPU。CPU供应商曾多次尝试进入服务器市场，但到目前为止的每次尝试都存在致命缺陷，从早期阶段就显而易见地注定要失败（而且这些通常是为期5年的项目，所以在注定失败的项目上花费了大量时间）。高通的努力一直备受炒作，但当我与高通认识的人交谈时，他们都告诉我当前的芯片基本上只是用于练习，因为高通需要从他们从IBM挖来的所有人那里学习如何构建服务器芯片，而下一个芯片才是第一个有希望具有竞争力的芯片。我对高通以及ARM构建良好服务器部件的努力寄予厚望，但这些努力离结出果实还有一段距离。</p>

<p>当前的ARM（和POWER）选项（不包括苹果令人印象深刻的ARM芯片的假设变体）在每TCO美元性能方面几乎完全不适合大多数服务器工作负载，这有点偏离主题，所以我将留待另一篇文章，但重点是英特尔拥有市场力量让人们为服务器功能支付额外费用，而且他们确实这么做了。此外，有些功能确实比对那些只有几GB内存、几瓦功率预算并期望定期随机崩溃和重启的移动设备更重要。</p>

<h2 id="conclusion">结论</h2>

<p>你应该购买ECC RAM吗？这要看情况。对于服务器来说，考虑到成本，这可能是个不错的选择，尽管很难真正进行成本效益分析，因为你真的很难弄清楚静默数据损坏的成本，或者花半年开发人员时间追踪间歇性故障、最后发现是由使用非ECC内存引起的成本。</p>

<p>对于普通的桌面使用，我支持ECC，但如果你没有<a href="https://www.reddit.com/r/programming/comments/adoux/coding_horror_and_blogsstackoverflowcom/" rel="noopener noreferrer">设置定期备份</a>，做备份可能比ECC具有更好的投资回报率。但一旦你设置了绝对基础的东西，对于消费类机器来说，ECC有相当有力的理由。例如，如果你有备份但没有ECC，你很容易将损坏的数据写入主存储，并将该损坏的数据复制到备份中。但更普遍地说，运营数据中心的大公司可能比消费者更好地检测数据损坏，并且更有可能在更高层进行纠错，从而使他们能够从数据损坏中恢复，因此对于消费者的理由可以说比对服务器的更强，而对服务器的理由已经强到通常被认为是理所当然的。消费者通常不使用ECC的一个主要原因是，这并不是对他们不值得，而是他们根本不知道如何在崩溃和数据损坏发生时归因。一旦你开始这样做，就像谷歌和其他大公司所做的那样，即使你有更高层次的多级纠错在运行，ECC也立即显得物有所值。这类似于<a href="https://danluu.com/deconstruct-files/" rel="noopener noreferrer">我们在文件中看到的情况</a>，大型科技公司为他们的数据中心编写的软件比编写消费软件的大型科技公司（即使在同一家公司内通常也是如此）能更好地处理数据损坏。对用户来说，Web应用程序损坏其数据的成本与桌面应用程序损坏其数据的成本没有太大不同，不同之处在于当他们的Web应用程序损坏数据时，很明显是公司的错，这改变了公司的激励措施。</p>

<h3 id="appendix-security">附录：安全性</h3>

<p>如果你允许任何形式的代码执行，甚至是沙盒执行，都存在像<a href="https://en.wikipedia.org/wiki/Row_hammer" rel="noopener noreferrer">rowhammer</a>这样的攻击，它可能允许用户导致数据损坏，并且已经发生过允许权限提升的情况。ECC并不能完全缓解这种攻击，但它使其变得更加困难。</p>

<p><small>
感谢Prabhakar Ragde、Tom Murphy、Jay Weisskopf、Leah Hanson、Joe Wilder和Ralph Corderoy的讨论/评论/纠正。另外，感谢（或者可能是反感谢）Leah说服我应该把这个临时的口头评论写成一篇博客文章。对于任何错误、缺少参考文献和生硬的文字表示歉意；这基本上是一半对话的转录，我没有像我通常那样解释术语、提供参考文献或以那种详细程度检查事实。
</small></p>
<div>

<hr>

<ol>
<li id="fn:R"><p>我能想到的一个更有趣的例子，至少对我来说，是神奇的自愈保险丝。虽然有很多实现方式，但你可以将芯片上的保险丝想象成一个电阻器。如果你让一些电流通过它，你应该会得到一个连接。如果你让大量电流通过它，你会加热电阻器并最终摧毁它。这通常用于熔断芯片上的功能，或用于设置时钟频率等事情，其想法是一旦保险丝熔断，就无法解除熔断。</p>

<p>曾经有一家半导体制造商，在某个特定的工艺世代中有点急于求成，将公差控制得有点太紧。几个月（或几年）后，保险丝两端的连接可能重新生长，导致保险丝解除熔断。如果你幸运的话，保险丝可能是时钟乘数器的高位，如果更改会使芯片变砖。如果你不走运，它可能是导致静默数据损坏的东西。</p>

<p>我从多家公司的不同人那里听说了那家制造商在特定工艺世代中的问题，所以这不是孤立的情况。当我说这很有趣时，我的意思是在酒吧里听到这个故事时很有趣。当你在测试一年后发现你的一些芯片因为保险丝设置不合理而出现故障，而你必须重新设计芯片并推迟发布3个月时，这可能就不太有趣了。顺便说一句，这个保险丝再增长的事情是另一类可以通过ECC缓解的错误的例子。</p>

<p>这不是谷歌遇到的问题；我提到这个是因为我交谈过的很多人都对硬件故障的方式感到惊讶。
 <a href="#fnref:R" rel="noopener noreferrer"><sup>[返回]</sup></a></p></li>

<li id="fn:P"><p>如果你不想翻阅整本书，大部分相关段落如下：</p>

<p>在一个可以在软件层面容忍一定数量故障的系统中，对硬件层提出的最低要求是其故障总是被检测到并及时报告给软件，以便软件基础设施能够包含它并采取适当的恢复行动。不一定需要硬件透明地纠正所有故障。这并不意味着此类系统的硬件在设计时不具有纠错功能。每当在合理的成本或复杂性范围内可以提供纠错功能时，支持它往往是值得的。这意味着如果硬件纠错极其昂贵，系统可以选择使用只提供检测功能的更便宜版本。现代DRAM系统就是一个很好的例子，其中强大的纠错功能可以以非常低的额外成本提供。</p>
<p>放松对硬件错误检测的要求则要困难得多，因为这意味着每个软件组件都将承担检查自身正确执行的负担。在其历史早期，谷歌不得不处理缺少甚至奇偶校验的DRAM的服务器。生产网络搜索索引本质上涉及使用多台机器在很长一段时间内进行一个非常大的洗牌/归并排序操作。2000年，谷歌网络索引的一次月度更新未能通过发布前检查，因为测试查询的子集似乎返回了看似随机的文档。经过调查后，在新的索引文件中发现了一个模式，该模式对应于数据结构中某个位置的位卡在零，这是一个有问题的副作用，是由于大量数据流经一个有缺陷的DRAM芯片造成的。索引数据结构中添加了一致性检查以最大程度地减少此类问题再次发生，并且没有再报告此类问题。但是请注意，此变通方法并不能保证索引过程中的100%错误检测，因为并非所有内存位置都被检查——例如，指令就没有被检查。它之所以有效，是因为索引数据结构比计算中涉及的所有其他数据大得多，拥有这些自检数据结构使得很可能识别出有缺陷DRAM的机器并将其排除在集群之外。谷歌的下一代机器包含了内存奇偶校验检测，一旦具有ECC的内存价格降至有竞争力的水平，所有后续代都使用了ECC DRAM。</p>
 <a href="#fnref:P" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 12791 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/why-ecc/</link>
      <guid isPermaLink="false">https://danluu.com/why-ecc/</guid>
      <pubDate>Fri, 27 Nov 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>计算机科学研究中的成果对比：1999年与2015年</title>
      <description>[AI 摘要] 本文对比了1999年和2015年计算机系统研究中各项技术的成功程度，指出多数技术评价提升，仅RISC降级。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文对比了1999年和2015年计算机系统研究中各项技术的成功程度，指出多数技术评价提升，仅RISC降级。</div><p>1999年，巴特勒·兰普森（Butler Lampson）就《计算机系统研究的过去与未来》<a href="http://research.microsoft.com/pubs/68591/computersystemsresearch.pdf" rel="noopener noreferrer">发表演讲</a>。以下是他在1999年关于“哪些技术行之有效”的观点。</p>

<table>
<thead>
<tr>
<th>是</th>
<th>或许</th>
<th>否</th>
</tr>
</thead>
<tbody>
<tr>
<td>虚拟内存</td>
<td>并行计算</td>
<td>权限能力</td>
</tr>
<tr>
<td>地址空间</td>
<td>RISC</td>
<td>复杂类型系统</td>
</tr>
<tr>
<td>分组网络</td>
<td>垃圾回收</td>
<td>函数式编程</td>
</tr>
<tr>
<td>对象/子类型</td>
<td>代码复用</td>
<td>形式化方法</td>
</tr>
<tr>
<td>关系数据库与SQL</td>
<td></td>
<td>软件工程</td>
</tr>
<tr>
<td>事务处理</td>
<td></td>
<td>远程过程调用（RPC）</td>
</tr>
<tr>
<td>位图与图形界面</td>
<td></td>
<td>分布式计算</td>
</tr>
<tr>
<td>Web</td>
<td></td>
<td>安全性</td>
</tr>
<tr>
<td>算法</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>

<p><br>基本上，1999年所有标记为“是”的技术至今仍然重要。对于“或许”类别，我们来看：</p>

<h3 id="parallelism">并行计算</h3>
<p>不幸的是，这仍然是个“或许”。随着<a href="https://en.wikipedia.org/wiki/Dennard_scaling" rel="noopener noreferrer">登纳德缩放定律的终结</a>和持续增长的计算需求，芯片现在为程序员暴露了大量并行性。并发处理变得更容易应对，但要真正挖掘出接近全部可用性能，其难度与1999年相比并未显著降低。</p>
<p>2009年，<a href="https://channel9.msdn.com/Shows/Going+Deep/E2E-Erik-Meijer-and-Butler-Lampson-Abstraction-Security-Embodiment" rel="noopener noreferrer">埃里克·梅耶与巴特勒·兰普森讨论过这个问题</a>，兰普森当时评论道，他们在PARC提出线程、锁和条件变量时，原以为这能让程序员利用并行性，但数十年的证据表明他们错了。兰普森进一步指出，并行编程需要将所有并行性封装在一个小盒子里，然后由一位“巫师”来编写其中的代码。自2009年以来，情况并未有多少改变。</p>
<p>另外请注意，我评判所有这些技术的标准是一致的。当你说某项技术行不通时，总会有人跳出来说：“不对，这里有个博士论文证明有人曾成功实现过，”或者“这九个程序证明Idris实际上在大规模生产系统中被广泛使用。”我认同兰普森的观点：如果绝大多数程序员根本无法使用某类技术，那么该类技术很可能并未取得成功。</p>
<p>关于并行计算的近期进展，<a href="//danluu.com/intel-cat/" rel="noopener noreferrer">英特尔最近增加了一些特性</a>，使得通过在同一台机器上协调调度多个应用程序来利用简单并行性变得更容易，但除了少数几家大公司，目前还没有人真正利用这一点。他们还最近为软件事务内存（STM）增加了硬件支持，但在设计大规模系统时，STM对可用性究竟有多大帮助仍不明确。</p>

<h3 id="risc-danluu-com-risc-definition"><a href="//danluu.com/risc-definition/" rel="noopener noreferrer">RISC</a></h3>
<p>如果1999年这算是“或许”，那么现在肯定是“否”。在80年代和90年代，许多人（可能是大多数人）相信RISC将席卷全球，而x86注定失败。1991年，苹果、IBM和摩托罗拉联手创建PowerPC（PPC）芯片，意图在消费市场击败英特尔。他们开放了萨默塞特（Somerset）芯片设计中心，并汇集了众多顶尖人才，准备进行一场改变世界的行动。在高端市场，DEC的Alpha芯片性能是英特尔芯片的两倍，其对工作站市场的威胁严重到微软将Windows NT移植到了Alpha上。DEC启动了一个将x86动态翻译到Alpha的项目；在项目启动时，预计在Alpha上以模拟方式运行x86的性能将显著优于英特尔芯片上的原生x86。</p>
<p>1995年，英特尔发布了奔腾Pro。当时，它的工作站整数性能优于市面上其他任何产品，包括针对工作站的、价格昂贵得多的芯片，其浮点性能也与高端芯片相差不到两倍。这立即摧毁了主流Apple/IBM/Moto PPC芯片的生存空间，1998年IBM从萨默塞特合资企业中撤出<sup id="fnref:I"><a href="#fn:I" rel="noopener noreferrer">1</a></sup>，所有人都放弃了真正尝试生产桌面级PPC芯片的努力。苹果继续销售PPC芯片一段时间，但他们不得不编造虚假的基准测试来让芯片显得有些竞争力。等到DEC完成他们的动态翻译工作时，通过翻译运行的x86在浮点代码上仅比英特尔芯片上的原生x86稍快，而在整数代码上则慢得多。虽然这在技术上是非常令人印象深刻的成就，但还不足以说服人们从x86转向Alpha，这扼杀了DEC进入低端工作站和高端PC市场的尝试。</p>
<p>1999年，高端工作站大多还是RISC机器，超级计算机则混合使用了定制芯片、RISC芯片和x86芯片。如今，英特尔凭借x86主导了工作站市场，超级计算机市场也转向了x86。除了POWER，其他RISC指令集架构（ISA）大多被淘汰（如PA-RISC）或通过转向低利润的嵌入式市场（如MIPS）得以存活，这些市场利润太薄，不足以让英特尔认真竞争。你可以看到MIPS和ARM利用了这种指令集套利机会。Cavium和ARM会卖给你一块网络卡，将大量处理任务卸载到网卡上，这些网卡上分别配备了大量廉价的MIPS和ARM处理器。低端处理器在处理数据包方面本质上并不比英特尔CPU更好；它们只是价格足够低，以至于英特尔不会在价格上与之竞争，因为他们不想用低利润芯片的销售来蚕食高利润芯片的市场。MIPS和ARM没有这样的顾虑，因为MIPS已经在高端处理器市场失利，而ARM尚未进入该市场。如果说RISC芯片目前最好的情况是在英特尔不屑一顾的低利润领域存在，这算不上RISC胜利的有力证据。英特尔放弃低端市场，考虑到其起源，似乎有些讽刺，但他们一直积极向高端市场进军（从DRAM到SRAM再到闪存的转型过程中，他们也做了同样的事情，将几乎无利可图的DRAM市场留给了竞争对手）。</p>
<p>如果对x86构成任何威胁，那就是ARM，其威胁在于其商业模式，而非其ISA。至于其ISA，ARM在移动和个人计算领域取得最大进展是在ARMv7及更早的ISA上，这些ISA实际上并不比x86更“RISC化”<sup id="fnref:A"><a href="#fn:A" rel="noopener noreferrer">2</a></sup>。在他们主导的领域，他们“现代”的RISC风格ISA——ARMv8——目前毫无希望，并且在未来几年内将继续如此，他们将继续凭借非RISC ISA占据主导地位。</p>
<p>回想起来，RISC芯片在80年代看起来如此出色的原因在于，你可以在单个芯片上容纳完整的高性能RISC微处理器，而当时的x86芯片做不到这一点。但随着晶体管数量增加，这一点变得不那么重要了。</p>
<p>有人可能会吹毛求疵，说现代处理器内部将x86指令翻译成RISC微操作，所以不能说RISC是“否”。但如果你听过当时的讨论，人们认为外部RISC ISA的开销会低得多，因此RISC会胜出，这显然没有发生。此外，现代芯片还进行微操作融合，将操作融合成绝对非RISC的操作。一个干净的RISC ISA是美好的东西。我有时会重读迪克·赛茨（Dick Sites）对<a href="http://www.hpl.hp.com/hpjournal/dtj/vol4num4/vol4num4art1.pdf" rel="noopener noreferrer">Alpha设计的解释</a>，纯粹是为了欣赏它，但事实证明，美感对于ISA的商业成功并非关键。</p>

<h3 id="garbage-collection">垃圾回收</h3>
<p>这现在是一个巨大的“是”。1999年以后成功的每种语言都具有垃圾回收（GC）功能，并设计为让所有普通用户用它来管理所有内存。五年后，Rust或D语言可能会使最后一句话不再成立，但即便如此，GC仍会属于“是”类别。</p>

<h3 id="reuse">代码复用</h3>
<p>是的，我认为，尽管我不太确定兰普森这里指的是什么。兰普森说复用是“或许”，因为它有时有效（对于UNIX过滤器、操作系统、数据库、浏览器），但有时也不靠谱（对于OLE/COM）。现在已有广泛使用的OLE替代品；面向服务的架构似乎也符合他对复用的定义。</p>
<p>对于“否”类别，我们有：</p>

<h3 id="capabilities">权限能力</h3>
<p>是。在移动操作系统上被广泛使用。</p>

<h3 id="fancy-type-systems">复杂类型系统</h3>
<p>这取决于什么算作复杂类型系统，但如果“复杂”指的是至少像Scala或Haskell那样复杂，那么这是一个“否”。即使将标准放宽到ML式的类型系统，情况也是如此。天啊，我多希望能用ML（F#在我看来特别棒）做日常编程，但我们离这还很遥远。</p>
<p>1999年，C和C++是主流语言，也许还有Visual Basic和Pascal，而Java正在兴起。或许还有Perl，但当时大多数人认为它是脚本语言，不是用于“真正”开发的工具。PHP、Python、Ruby和JavaScript都已存在，但大多只在小众领域使用。那时，Tcl是使用最广泛的脚本语言之一，但其使用范围也并非十分广泛。现在，PHP、Python、Ruby和JavaScript不仅比Tcl更主流，而且比C和C++更主流。C#可能是唯一在流行度上能与这些语言相提并论的语言，而Go看起来是唯一增长足够快、有望在可预见的未来赶上的语言。自1999年以来，我们有许多动态语言，以及少数类型系统特意设计为不复杂的语言。</p>
<p>也许再过16年，我就能在非业余项目中使用F#了，但前景并不乐观。</p>

<h3 id="functional-programming">函数式编程</h3>
<p>在这个问题上我倾向于“或许”，尽管这可以说是一个“否”。函数式语言仍然相当小众，但函数式编程的理念现在已成为主流，至少对于HN/reddit/twitter群体而言。</p>
<p>你可能会说我在这里对函数式编程过于宽容，因为我对不可变性情有独钟。这很公平。1982年，<a href="http://research.microsoft.com/en-us/um/people/simonpj/Papers/other-authors/morris-real-programming.pdf" rel="noopener noreferrer">詹姆斯·莫里斯写道</a>：</p>
<blockquote>
<p>函数式语言使用起来不自然；但刀叉、外交礼仪、复式记账法以及现代文明发现有用的许多其他事物也是如此。任何规范都是不自然的，需要一段时间才能掌握，并且在极端情况下可能会失效。这并不是拒绝某种特定规范的理由。重要的问题是，函数式编程的不自然性，是像俳句那样的不自然，还是像空手道那样的不自然。</p>
<p>俳句是一种严格的形式诗歌，每首诗必须有精确的三行和十七个音节。就像诗歌一样，编写一个纯粹的函数式程序常常给人带来极大的审美愉悦。阅读或编写这样的程序通常非常有启发性。这些无疑是好处，但真正的程序员更注重结果，他们不愿意在已经可以工作的程序上耗费心血。</p>
<p>除非一种语言规范能够被用来一次性编写出解决问题的程序，否则他们不会接受——就像空手道偶尔会用来应对真实出现的问题一样。一个学习了空手道规范的人，即使在酒吧斗殴中也能直接应用它，而其他人可能对此一无所知。今天的计算环境中，函数式程序员也能这样说吗？不能。</p>
</blockquote>
<p>今天许多人会提出同样的论点。我不同意，但这是观点问题，而非事实问题。</p>

<h3 id="formal-methods">形式化方法</h3>
<p>或许？形式化方法在少数领域产生了巨大影响。模型检验在芯片设计中无处不在。微软的<a href="http://research.microsoft.com/pubs/70038/tr-2004-08.pdf" rel="noopener noreferrer">驱动验证工具</a>的影响可能比所有形式化芯片设计工具加起来还要大，clang现在内置了大量的静态分析，等等。但是，形式化方法仍然相当小众，绝大多数开发者并不应用形式化方法。</p>

<h3 id="software-engineering">软件工程</h3>
<p>否。1995年，大卫·帕纳斯（David Parnas）在ICSE（软件工程顶级会议）上发表演讲，指出即使是获得ICSE“最具影响力论文奖”的论文（包括帕纳斯自己的两篇论文）也<a href="//danluu.com/empirical-pl/" rel="noopener noreferrer">对工业界影响甚微</a>。</p>
<p>帕纳斯的批评在今天基本仍然成立。他的一些建议，例如为研究者和实践者举办不同的会议，已被采纳，但像ICSE和FSE这样的学术会议与StrangeLoop和PyCon等面向实践者的会议之间，并没有太多交叉交流。</p>

<h3 id="rpc">远程过程调用（RPC）</h3>
<p>是。事实上，RPC现在被广泛使用，以至于我见过多场“RPC有害”的演讲。</p>

<h3 id="distributed-systems">分布式系统</h3>
<p>是。它们无处不在，以至于没有分布式系统专业知识的初创公司也经常使用亚马逊或微软提供的分布式系统，而且这完全没问题。这些系统并不完美，也有一些臭名昭著的宕机事件，但如果你比较1999年的随机存储的比特错误率和像EBS或Azure Blob存储这样的东西，分布式系统看起来并不那么糟糕。</p>

<h3 id="security">安全性</h3>
<p>或许？与形式化方法类似，少数对现实世界产生巨大影响的项目从安全研究中获益良多。但安全性对大多数程序员来说仍不是首要考虑因素。</p>

<h3 id="conclusion">结论</h3>
<p>计算机系统研究中哪些技术行之有效？</p>

<table>
<thead>
<tr>
<th>主题</th>
<th>1999年</th>
<th>2015年</th>
</tr>
</thead>
<tbody>
<tr><td>虚拟内存</td><td>是</td><td>是</td></tr>
<tr><td>地址空间</td><td>是</td><td>是</td></tr>
<tr><td>分组网络</td><td>是</td><td>是</td></tr>
<tr><td>对象/子类型</td><td>是</td><td>是</td></tr>
<tr><td>关系数据库与SQL</td><td>是</td><td>是</td></tr>
<tr><td>事务处理</td><td>是</td><td>是</td></tr>
<tr><td>位图与图形界面</td><td>是</td><td>是</td></tr>
<tr><td>Web</td><td>是</td><td>是</td></tr>
<tr><td>算法</td><td>是</td><td>是</td></tr>
<tr><td>并行计算</td><td>或许</td><td>或许</td></tr>
<tr><td>RISC</td><td>或许</td><td>否</td></tr>
<tr><td>垃圾回收</td><td>或许</td><td>是</td></tr>
<tr><td>代码复用</td><td>或许</td><td>是</td></tr>
<tr><td>权限能力</td><td>否</td><td>是</td></tr>
<tr><td>复杂类型系统</td><td>否</td><td>否</td></tr>
<tr><td>函数式编程</td><td>否</td><td>或许</td></tr>
<tr><td>形式化方法</td><td>否</td><td>或许</td></tr>
<tr><td>软件工程</td><td>否</td><td>否</td></tr>
<tr><td>远程过程调用（RPC）</td><td>否</td><td>是</td></tr>
<tr><td>分布式计算</td><td>否</td><td>是</td></tr>
<tr><td>安全性</td><td>否</td><td>或许</td></tr>
</tbody>
</table>

<p><br>不仅1999年的所有“是”在今天仍然是“是”，而且七个“或许”和“否”被升级了，只有一个被降级。除此之外，还有许多主题（如神经网络）甚至不值得作为“否”被列入名单，但如今它们已是明确的“是”。</p>
<p>1999年，我还在参加SAT考试和申请大学。如今，我的职业生涯才刚开始不久，但领域已经发生了巨大变化；许多以前不切实际的学术课题现在在工业界被广泛应用。到我职业生涯结束，可能还有两倍的时间，<a href="//danluu.com/infinite-disk/" rel="noopener noreferrer">而且事物变化的速度比1999年更快</a>。回顾兰普森1999年的演讲，我对那些尚未产生太多实际影响的研究领域（如基于权限能力的计算和复杂类型系统）更加乐观了。预测未来三十年哪些领域会变得有价值，似乎基本上是不可能的。</p>

<h3 id="correction">更正</h3>
<p>本文最初将“权限能力”列为2015年的“否”。回想起来，我认为这是个错误，由于其在移动设备上的应用，它应该列为“是”。</p>

<p><small>感谢Seth Holloway, Leah Hanson, Ian Whitlock, Lindsey Kuper, Chris Ball, Steven McCarthy, Joe Wilder, David Wragg, Sophia Wisdom, 和 Alex Clemmer 的评论/讨论。</small></p>

<div>
<hr>
<ol>
<li id="fn:I">我认识不少从东海岸被IBM调往萨默塞特的人，因为他们后来在我工作过的一家公司工作。有趣的是，软件公司对员工没有同样的控制力，不能坚持要求员工搬到他们在任意地点创建的新设施。
 <a href="#fnref:I" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:A">我曾经在一家同时实现了x86和ARM解码器的公司工作过（我猜这是第一家为桌面级芯片这样做的公司），我们发现我们的ARM解码器在物理尺寸上比x86解码器更大、更复杂。与其他同样实现过ARM和x86前端的人交谈，这对于高性能实现来说似乎并不罕见。
 <a href="#fnref:A" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 9489 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/butler-lampson-1999/</link>
      <guid isPermaLink="false">https://danluu.com/butler-lampson-1999/</guid>
      <pubDate>Mon, 23 Nov 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>无限磁盘</title>
      <description>[AI 摘要] 该文探讨硬件延迟改进如何使存储分离成为可能，进而为应用程序提供无限磁盘空间，同时讨论系统设计需相应调整。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 该文探讨硬件延迟改进如何使存储分离成为可能，进而为应用程序提供无限磁盘空间，同时讨论系统设计需相应调整。</div><p>硬件性能“显然”影响软件性能及其优化方式。例如，缓存比内存快多个数量级的事实意味着<a href="http://suif.stanford.edu/papers/lam-asplos91.pdf" rel="noopener noreferrer">分块数组访问</a>比反复跨步遍历数组性能更好。</p>

<p>偶尔被忽视的是，硬件性能对系统设计和架构也有深远影响。<a href="https://gist.github.com/jboner/2841832" rel="noopener noreferrer">让我们看看这张自2012年起流传的延迟表</a>：</p>

<p></p>

<pre><code>操作                                延迟 (ns)     (ms)
L1 缓存引用                            0.5 ns
分支错误预测                            5   ns
L2 缓存引用                            7   ns
互斥锁/解锁                           25   ns
主存引用                              100   ns
用Zippy压缩1K字节                  3,000   ns
通过1 Gbps网络发送1K字节        10,000   ns    0.01 ms
从SSD随机读取4K               150,000   ns    0.15 ms
从内存顺序读取1 MB             250,000   ns    0.25 ms
同一数据中心内往返               500,000   ns    0.5  ms
从SSD顺序读取1 MB             1,000,000   ns    1    ms
磁盘寻道                        10,000,000   ns   10    ms
从磁盘顺序读取1 MB             20,000,000   ns   20    ms
发送数据包CA-&gt;荷兰-&gt;CA         150,000,000   ns  150    ms
</code></pre>

<p>考虑磁盘寻道（10毫秒）与同一数据中心内往返（0.5毫秒）的延迟。网络往返延迟远低于磁盘寻道时间，这意味着我们可以将存储分散并分布到数据中心的任何地方，而不会造成明显的性能下降，使应用程序拥有看似无限的磁盘空间且性能无明显变化。这一事实推动了过去二十年来数据中心内GFS等分布式文件系统的兴起，以及更早的各种网络附加存储方案。</p>

<p>然而，在2012年左右的商用网络和SSD上做同样的事情并不可行。读取SSD页面的时间是150微秒，而网络往返时间是500微秒。虽然这比旋转磁盘性能有显著提升，但仍比本地SSD慢4倍以上。</p>

<p>但到了2015年，情况发生了变化。磁盘速度大幅提升。企业级NVRAM驱动器可在约15微秒内完成4K随机读取，比2012年的SSD快一个数量级。网络改进更大。现在采用低延迟用户模式网络栈相对普遍，可将4K传输的往返延迟降至数十微秒。这足以分离SSD并为应用程序提供无限SSD访问。虽然还不足以分离高端NVRAM，但<a href="https://en.wikipedia.org/wiki/RDMA_over_Converged_Ethernet" rel="noopener noreferrer">RDMA</a>可以做到。</p>

<p><img src="https://danluu.com/images/infinite-disk/rdma_vs_tcp.png" width="651" height="147"></p>

<p>RDMA将延迟再降低一个数量级，使网络延迟低于NVRAM访问延迟足够多，从而实现NVRAM分离。注意这些数字是网络空载无拥塞时的——负载下会显著恶化，但说明了可能性。这并非新技术：高性能计算领域使用InfiniBand上的RDMA已有多年，但InfiniBand网络昂贵，在数据中心中未广泛采用。近几年的新进展是在以太网上运行RDMA的能力。这并非易事；微软<a href="http://conferences.sigcomm.org/sigcomm/2015/pdf/papers/p523.pdf" rel="noopener noreferrer">和</a><a href="http://conferences.sigcomm.org/sigcomm/2015/pdf/papers/p537.pdf" rel="noopener noreferrer">谷歌</a>都在今年的SIGCOMM上发表论文，阐述如何在扩展到多个节点时避免诸多问题。但这已成为可能，我们正接近非超大型企业也能大规模部署这项技术的时刻<sup id="fnref:R"><a href="#fn:R" rel="noopener noreferrer">1</a></sup>。</p>

<p>然而，虽然可以说应该使用分离磁盘，因为网络延迟与磁盘延迟的比率已改变，但这并不意味着随便找个旧系统放到快速网络上就能行。如果将2005年的分布式文件系统或数据库放到快速网络上，它无法真正利用网络优势。那个2005年的系统可能假设操作耗时500纳秒没问题，因为500纳秒能有多大影响？但当网络往返延迟仅比其高几倍时，这影响很大，而在高延迟时代编写的应用程序常常充满一次“不经意”操作就消耗数百纳秒的代码。更糟的是，为高延迟设计的最优方案在延迟降低时会产生开销。例如，延迟1毫秒时，本地缓存是巨大收益，2005年的高性能分布式应用常严重依赖本地缓存。但当延迟降至1微秒以下时，2005年作为巨大收益的缓存常变得不仅无用，反而成为适得其反的开销。</p>

<p>延迟不仅在数据中心内下降。如今，我访问YouTube的延迟约为2到3毫秒。YouTube、Netflix和许多其他服务将大量服务器部署在靠近消费者的位置，以提供高带宽低延迟连接。附带的结果是，拥有这些服务的任何公司都有能力为消费者提供仅比普通磁盘略慢的无限磁盘。虽然尚未实现的原因有很多，但这最终必然发生。看看主要云提供商支付的存储成本，他们提供安全复制存储的销货成本（<a href="https://en.wikipedia.org/wiki/Cost_of_goods_sold" rel="noopener noreferrer">COGS</a>）已经或即将低于我为家用机器上未备份未复制的本地磁盘支付的零售价。</p>

<p>云存储可能比本地存储便宜似乎奇怪，但大型云供应商有巨大议价能力。他们购买的中位数组件（非英特尔CPU或英伟达GPU）价格与零售价相比低得惊人。此外，大多数人并不频繁访问其大部分文件。看看如今大型机械硬盘的吞吐量，根本不可能这么做。一块<a href="http://hdd.userbenchmark.com/Toshiba-DT01ACA300-3TB/Rating/2735" rel="noopener noreferrer">典型消费者3TB硬盘</a>平均吞吐量为155MB/s，读取整个驱动器需3e12 / 155e6秒 = 1.9e4秒 = 5小时22分钟。而人们大多数时候根本不访问磁盘！即使访问，其访问模式产生的吞吐量也远低于线性读取整个磁盘时的吞吐量。这意味着绝大多数分离存储可存放在廉价的冷存储中。例如，<a href="https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-balakrishnan.pdf" rel="noopener noreferrer">Balakrishnan等人的Pelican OSDI 2014论文</a>证明，如果构建冷存储机架使得任何时候只能访问8%的磁盘，就能显著节省成本。一小部分存储必须位于边缘，原因与一小部分YouTube视频缓存在边缘相同。某种意义上，经济学比YouTube更不利，因为任何特定数据块很可能被共享，但鉴于边缘计算/存储的扩展速度，十年内这不太可能成为严重阻碍。</p>

<p>数据中心内外对分离磁盘最常见的反驳是带宽成本。但带宽成本指数级下降已持续数十年，并且仍在继续。自1995年以来，数据中心网卡速度从10Mb提升至40Gb，50Gb和100Gb即将到来。这种增长如此迅速，以至于除大型企业外，几乎没有人重新设计其应用程序以充分利用可用带宽。大多数应用无法占满10Gb网卡，更别说40Gb网卡。带宽实在多得让人不知如何使用。数据中心外的情况演变不那么快，但即便如此，我每月支付60美元获得100Mb带宽，如果过去二十年的趋势继续，未来十年每美元带宽应再增加50倍。尚不清楚当前的成本结构是否使云提供的分离磁盘对消费者今天可行，但带宽成本无情下降的当前趋势意味着五年内必然实现。</p>

<p>需注意的是，<a href="http://www.researchgate.net/profile/David_Rogers2/publication/265543548_Aggregation_and_disaggregation_techniques_and_methodology_in_optimization/links/54357d8e0cf2643ab986792e.pdf" rel="noopener noreferrer">仅仅因为我们可以分离某物，并不意味着我们应该这样做</a>。<a href="http://web.eecs.umich.edu/~twenisch/papers/hpca12-disagg.pdf" rel="noopener noreferrer">Lim等人在HPCA 2012关于分离RAM的论文</a>很有趣，他们通过背板连接RAM构建分离RAM。虽然我们有技术这样做，其双重优势是允许我们以更低的单位成本配置RAM并提高配置RAM的利用率，但到目前为止，这似乎并未在可接受的性能水平上提供性价比节省<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">2</a></sup>。</p>

<p>不同组件相对性能的变化导致应用程序应如何设计发生根本改变。仅仅分析应用程序并消除热点是不够的。要获得良好性能（或良好性价比），有时需要退后一步，重新审视假设，并重写系统。关于硬件改进放缓的讨论很多，这通常指CPU性能改进。确实如此，但许多其他领域正在经历快速变化，这要求关心性能或成本效率的应用程序做出改变。GPU、硬件加速器、存储和网络都在以比以往更快的速度发展。</p>

<h4 id="update">更新</h4>

<p>微软似乎在这一点上与我意见相左。OneDrive一直在朝相反方向发展。他们取消了无限磁盘，降低了非无限存储层的配额，并以更不自然的方式改变了同步模型。我花大约一小时写了这篇博文。他们可能有一个由哈佛MBA组成的团队，花了我百倍的时间讨论取消无限磁盘。我想知道我错过了什么。平均每用户使用量为5GB，这几乎免费。少数用户有大量数据，但如果有人上传100TB，你可以将大部分数据存放在磁带。磁带的访问时间很慢——机械臂取出磁带盒并放好需数秒，在磁带上找到正确位置需数十秒。但上传100TB的人基本上将其用作归档存储，你可以为最常见用例（上传电影或媒体库）屏蔽大部分延迟。如果文件开头部分不在磁带，用户开始播放存放在磁带上的电影，电影可以很容易地从稍快存储播放几分钟，同时排队访问磁带。你可能会说不值得花时间构建这样的系统（可能两名工程师工作六个月），但你已经需要一个能为大文件屏蔽磁带存储延迟的系统。在此之上再增加一层额外工作量不大。</p>

<h4 id="update-2">更新2</h4>

<p>它正在发生。2016年4月，Dropbox宣布推出“Dropbox Infinite”，无论本地磁盘容量如何，都可访问整个Dropbox。必然趋势发生了，尽管我有点惊讶率先行动的不是谷歌，因为他们有更好的边缘基础设施，存储成本几乎肯定更低。回想起来，也许这并不意外——谷歌、微软和亚马逊都将提供用户友好的存储视为次要任务，而Dropbox全力投入用户体验。</p>

<p><small>
感谢Leah Hanson、bbrazil、Kamal Marhubi、mjn、Steve Reinhardt、Joe Wilder和Jesse Luehrs的评论/修正/补充，促使本文进行编辑。
</small></p>
<div>

<hr>

<ol>
<li id="fn:R">如果你尝试复现谷歌结果时遇到不稳定问题，你不是唯一一个。论文省略了复现结果所需的特殊技术。
 <a href="#fnref:R" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:S"><p>如果你的目标是获得更好的利用率，今天的穷人方案是通过尽力而为的RDMA让应用程序访问未使用的RAM，这种方式模糊地类似于<a href="//danluu.com/intel-cat/" rel="noopener noreferrer">谷歌的Heracles工作</a>。你可能会说，等等：你可以对磁盘提出同样的论点，但事实上构建磁盘最便宜的方式是构建密集的磁盘刀片，而不是使用RDMA访问附加在标准服务器刀片上的普通磁盘；为什么RAM不该如此？要了解当磁盘、I/O和RAM相对于CPU配置不足时的样子，<a href="http://www.wired.com/2012/10/data-center-servers/" rel="noopener noreferrer">参见这篇文章，其中Mozilla员工声称CPU利用率为6%没问题，因为那些机器正忙于I/O</a>。当然没问题，如果你不介意为未使用的CPU付费，而不是构建具有正确磁盘存储比的刀片，但那些空闲CPU并非免费。</p>

<p>如果我们需要的RAM与CPU比率类似于我们所需的磁盘与CPU比率，那么分离RAM可能更便宜。但是，虽然<a href="http://web.eecs.umich.edu/~twenisch/papers/isca09-disaggregate.pdf" rel="noopener noreferrer">RAM需求增长快于计算需求</a>，我们尚未达到数据中心有大量核心因缺乏RAM而闲置的程度，就像如果我们使用标准服务器刀片作为存储，就会因缺乏磁盘而闲置核心一样。Xeon-EX每个插槽可处理1.5TB RAM。如今通常在1/2U刀片中放两个插槽，对于绝大多数工作负载，尝试将超过6TB RAM塞进可舒适放入1U的4个插槽中相当罕见。</p>

<p>话虽如此，分离RAM问题仍是个开放性问题，<a href="https://www.usenix.org/conference/srecon15europe/program/presentation/hoffman" rel="noopener noreferrer">一些人</a>比<a href="http://arxiv.org/abs/1503.01416" rel="noopener noreferrer">其他人</a>更确信其近期可行性。</p>
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 7476 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/infinite-disk/</link>
      <guid isPermaLink="false">https://danluu.com/infinite-disk/</guid>
      <pubDate>Sun, 1 Nov 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>为什么Intel添加了缓存分区</title>
      <description>[AI 摘要] 本文阐述Intel引入缓存分区的目的在于提升服务器资源利用率并实现任务隔离。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文阐述Intel引入缓存分区的目的在于提升服务器资源利用率并实现任务隔离。</div><p><a href="http://csl.stanford.edu/~christos/publications/2015.heracles.isca.pdf" rel="noopener noreferrer">典型服务器利用率在10%到50%之间。Google已经证明了在<em>不影响延迟SLA</em>的情况下达到90%的利用率</a>。 <a href="https://what-if.xkcd.com/63/" rel="noopener noreferrer">Xkcd估计Google拥有200万台机器</a>。 如果估计每台机器每年的摊销总成本为<a href="http://www.morganclaypool.com/doi/pdf/10.2200/S00516ED2V01Y201306CAC024" rel="noopener noreferrer">$4k</a>，那么每年就是80亿美元。 有了这样的数字，即使是小的改进也会产生很大的影响，而这并不是一个微不足道的改进。</p>

<p></p>

<p>如何在相同硬件上实现2倍到9倍的更好利用率？ 这些典型利用率数字的低端源于服务具有可变需求但机器分配固定。 假设有100台机器专用于Jenkins。 这些机器在开发人员活跃时可能非常繁忙，但在凌晨3点时利用率可能只有2%。 动态分配（在不需要时将机器切换到其他工作）可以将典型的延迟敏感服务提升到30%-70%的范围。 为了在具有严格SLA的各种延迟敏感工作负载上做得更好，我们需要一种方法在同一机器上调度低优先级工作，而不影响高优先级工作的延迟。</p>

<p>这并非显而易见。 如果高优先级和低优先级工作负载都需要独占一些共享资源，如最后一级缓存（LLC）、内存带宽、磁盘带宽或网络带宽，那我们就无能为力了。 除了某些专门的服务外，很少达到磁盘或网络的极限。 但缓存和内存呢？ 事实证明，<a href="http://www.industry-academia.org/download/ASPLOS12_Clearing_the_Clouds.pdf" rel="noopener noreferrer">Ferdman等人在2012年</a>研究了这一点，发现典型服务器工作负载从超过4MB-6MB的LLC中受益甚少，尽管现代服务器芯片拥有更大的缓存。</p>

<p><img src="https://danluu.com/images/intel-cat/server_llc.png" width="321" height="208"></p>

<p>对于此图，横向扩展工作负载包括分布式键值存储、类似MapReduce的计算、网络搜索、网络服务等。 SPECint(mcf)是传统工作站基准测试。 “服务器”是旧式的服务器基准测试，如SPECweb和TPC。 我们可以看到，将LLC从4MB增加到11MB对典型数据中心工作负载影响很小，但对传统工作站基准测试有显著影响。</p>

<p>数据中心工作负载处理如此大的数据集，以至于通常无法将数据集放入单台机器的RAM中，更不用说缓存，因此更大的LLC并不特别有用。 这一结果得到了<a href="http://www.eecs.harvard.edu/~skanev/papers/isca15wsc.pdf" rel="noopener noreferrer">Kanev等人在ISCA 2015论文中的证实，他们研究了Google的工作负载</a>。 他们还显示，内存带宽利用率平均相当低。</p>

<p><img src="https://danluu.com/images/intel-cat/low_bandwidth_utilization.png" width="342" height="175"></p>

<p>你可能认为低带宽利用率是因为工作负载是计算密集型的，内存访问不多。 然而，当作者查看核心在做什么时，他们发现大量时间都停滞不前，等待缓存/内存。</p>

<p><img src="https://danluu.com/images/intel-cat/cache_stalls.png" width="328" height="169"></p>

<p>每一行是一个Google工作负载。 运行这些典型工作负载时，核心花费46%到61%的时间阻塞在缓存/内存上。 有趣的是，我们有低缓存命中率、大量时间阻塞在缓存/内存上以及低带宽利用率。 这表明工作负载花费大量时间等待内存访问，而这些访问存在某种依赖性，阻止它们独立执行。</p>

<p>高端服务器芯片的LLC在12MB到30MB之间，尽管我们只需要4MB就能获得90%的性能，并且90%的带宽利用率为31%。 这似乎是资源浪费。 我们有很多资源闲置或未有效利用。 好消息是，由于我们芯片上的共享资源利用率如此低，我们应该能够在不降低性能的情况下在一台机器上调度多个任务。</p>

<p>很好！ 当我们在一台机器上调度多个任务时会发生什么？ <a href="http://csl.stanford.edu/~christos/publications/2015.heracles.isca.pdf" rel="noopener noreferrer">Lo等人在今年的ISCA上发表的Heracles论文对此进行了详细探讨</a>。 Heracles的目标是通过在同一机器上共存多个任务来提高机器的利用率。</p>

<p><img src="https://danluu.com/images/intel-cat/heracles_interference.png" width="665" height="343"></p>

<p>上图显示了三个具有严格SLA的延迟敏感（LC）工作负载。 websearch是Google搜索中的查询服务，ml_cluster是实时文本聚类，memkeyval是类似于memcached的键值存储。 值是延迟占SLA允许的最大延迟的百分比。 列表示服务的负载，行表示不同类型的干扰。 LLC、DRAM和网络正如其名；是专门设计仅竞争该资源的自定义任务。 HyperThread意味着干扰任务是在同一核心上的另一个超线程中运行的自旋循环（运行在同一个超线程中甚至不被考虑，因为操作系统上下文切换成本太高）。 CPU power是一个旨在使用大量功率并诱导热节流的任务。 Brain是深度学习。 所有干扰任务都在一个低优先级的容器中运行。</p>

<p>这张图有很多内容，但我们可以立即看到，仅使用容器优先级时，我们希望调度的尽力而为（BE）任务无法与任何LC任务共存——所有<code>brain</code>行都是红色的，即使在低利用率下（最左侧列），延迟也远高于SLA延迟的100%。 同样明显的是，不同的LC任务具有不同的特性，可以处理不同类型的干扰。 例如，websearch和ml_cluster既不网络密集也不计算密集，因此可以很好地处理网络和功率干扰。 然而，由于memkeyval既网络密集又计算密集，它无法处理网络或功率干扰。 论文更详细地探讨了从表格细节中可以推断出什么。 我发现这是论文中最有趣的部分之一；我将跳过它，但如果你对此类事情感兴趣，我建议阅读论文。</p>

<p>作者做出的一个简化假设是，这些类型的干扰基本上是独立的。 这意味着独立的隔离机制，将LC任务与“过多”的每种单独资源隔离开来，应该足以防止整体干扰。 也就是说，我们可以为每种资源使用设置上限，并保持在每个上限以下。 然而，这个假设并不完全正确——例如，作者展示了这张图，关联了LLC缓存大小与分配给LC任务的核心数量。</p>

<p><img src="https://danluu.com/images/intel-cat/llc_vs_cores.png" width="277" height="238"></p>

<p>垂直轴是LC任务在分配特定LLC和核心数量时，在违反SLA之前可以处理的最大负载。 我们可以看到，可以权衡缓存与核心，这意味着我们实际上可以在一个维度上超过资源上限，并通过使用更少的另一种资源来维护SLA。 在一般情况下，我们或许也能权衡其他资源。 然而，假设我们可以独立处理每种资源，将一个复杂的优化问题简化为相对直接的问题。</p>

<p>现在，让我们看看每种共享资源干扰类型以及Heracles如何分配资源以防止违反SLA的干扰。</p>

<h4 id="core">核心</h4>

<p>将LC和BE任务固定到不同的核心足以防止同一核心上下文切换干扰和超线程干扰。 为此，Heracles使用了<a href="https://www.kernel.org/doc/Documentation/cgroups/cpusets.txt" rel="noopener noreferrer">cpuset</a>。 Cpuset允许你将进程（及其子进程）限制仅在有限的CPU集上运行。</p>

<h4 id="network">网络</h4>

<p>在本地机器上，Heracles使用<a href="https://wiki.archlinux.org/index.php/Advanced_traffic_control" rel="noopener noreferrer">qdisc</a>来强制执行配额。 关于cpuset、qdisc和其他配额/分区机制的更多信息，<a href="https://lwn.net/Articles/604609/" rel="noopener noreferrer">Neil Brown关于cgroups的LWN系列文章是一个不错的起点</a>。 Cgroups现在被许多广泛使用的软件使用（Docker、Kubernetes、Mesos等）；即使你不关心这个特定应用，它们也值得了解。</p>

<h4 id="power">功耗</h4>

<p>Heracles使用<a href="https://01.org/blogs/tlcounts/2014/running-average-power-limit-%E2%80%93-rapl" rel="noopener noreferrer">Intel的运行平均功率限制</a>来估算功耗。 这是Sandy Bridge（2009年）及更新处理器上的一个功能，它使用一些片上监控硬件相当精确地估算功耗。 <a href="http://www.cs.binghamton.edu/~millerti/cs680r/papers/DVFS/SystemLevelAnalysis.pdf" rel="noopener noreferrer">每核心动态电压和频率缩放</a>用于限制特定核心的功耗，以防止超出预算。</p>

<h4 id="cache">缓存</h4>

<p>之前的隔离机制已经存在一段时间了，但这是Broadwell芯片（2015年发布）的新功能。 这里的问题是，如果BE任务需要1MB的LLC，而LC任务需要4MB的LLC，BE任务的一个大型分配会在共享的LLC上四处覆盖，抹去LC需要的4MB缓存数据。</p>

<p>Intel的“<a href="http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-vol-3b-part-2-manual.pdf" rel="noopener noreferrer">缓存分配技术</a>”（CAT）允许LLC限制哪些核心可以访问缓存的不同部分。 由于我们通常希望将性能敏感任务固定到核心上，这允许我们按任务划分缓存。</p>

<p><img src="https://danluu.com/images/intel-cat/CAT_LLC.png" width="431" height="157"></p>

<p>Intel的<a href="http://www.intel.com/content/dam/www/public/us/en/documents/white-papers/cache-allocation-technology-white-paper.pdf" rel="noopener noreferrer">2015年4月关于他们所谓的缓存分配技术（CAT）的白皮书</a>有一些简单的基准测试，比较了CAT与无CAT。
在这个例子中，他们测量在另一个应用程序有大量CPU到内存流量时，响应PCIe中断的延迟，开启和关闭CAT。</p>

<table>
<thead>
<tr>
<th>条件</th>
<th>最小值</th>
<th>最大值</th>
<th>平均值</th>
</tr>
</thead>

<tbody>
<tr>
<td>无CAT</td>
<td>1.66</td>
<td>30.91</td>
<td>4.53</td>
</tr>

<tr>
<td>有CAT</td>
<td>1.22</td>
<td>20.98</td>
<td>1.62</td>
</tr>
</tbody>
</table>

<p>有CAT时，平均延迟是无CAT时的36%。 尾部延迟改善不多，但也有显著改进。 这很有趣，但对我来说，更有趣的问题是在真实工作负载上这有多有效，当我们把所有这些机制组合在一起时就会看到。</p>

<p>CAT的另一个用途是防止时序攻击，例如<a href="https://eprint.iacr.org/2015/898.pdf" rel="noopener noreferrer">这种攻击，可以通过LLC干扰跨VM恢复RSA密钥</a>。 我不会讨论这一点。</p>

<h4 id="dram-bandwidth">DRAM带宽</h4>

<p>Broadwell和更新的Intel芯片具有内存带宽监控，但没有控制机制。 为了解决这个问题，如果BE任务使用了太多带宽而干扰了LC任务，Heracles会减少分配给BE任务的核心数量。 这种粗粒度的监控和控制在多个方面效率低下，如论文中详细所述，但尽管如此它仍然有效。 然而，具有每核心带宽限制会以更少的努力提供更好的结果。</p>

<h4 id="putting-it-all-together">整合所有内容</h4>

<p>这张图显示了LC websearch与其他BE任务调度的有效利用率，有足够的余量使得websearch的SLA不被违反。</p>

<p><img src="https://danluu.com/images/new-cpu-features/google_heracles.png" width="338" height="209"></p>

<p>从与其他公司人员的酒吧对话来看，这里的基线（红色）看起来已经相当不错：峰值期间80%的利用率，有7小时的低谷期利用率低于50%。 有了Heracles，<em>最坏情况</em>利用率为80%，平均为90%。 这太惊人了。</p>

<p>注意，有效利用率可以超过100%，因为它是根据LC任务在单台机器上100%负载时的吞吐量加上BE任务在单台机器上100%负载时的吞吐量来衡量的。 例如，如果一个任务需要100%的DRAM带宽和0%的网络带宽，而另一个任务需要相反的，那么这两个任务就可以在同一台机器上共存并实现200%的有效利用率。</p>

<p>在现实世界中，我们可能“只”能从像Heracles这样的系统中获得90%的平均利用率。 回想一下，对于一家大公司，运营成本估计为40亿美元，如果该公司已经达到了相当好的平均利用率75%，使用<a href="http://www.morganclaypool.com/doi/pdf/10.2200/S00516ED2V01Y201306CAC024" rel="noopener noreferrer">标准数据中心运营成本模型</a>，我们预期每美元的吞吐量增加15%，即6亿美元的免费计算资源。 从与那些正在成为大公司的较小公司（年计算支出在1000万到1亿美元之间的公司）交谈中，他们通常有20%左右的利用率。 使用相同的总成本模型，他们预期每美元的计算资源增加300%，即每年3000万到3亿美元的免费计算资源，取决于他们的规模<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">1</a></sup>。</p>

<h4 id="other-observations">其他观察</h4>

<p>我们看过的所有论文都有很多有趣的瑰宝。 我不会在这里深入探讨所有内容，但有几个引起了我的注意。</p>

<h5 id="arm-atom-servers">ARM / Atom服务器</h5>

<p>长期以来众所周知，数据中心机器大约有一半时间停滞，等待内存。 此外，服务器芯片在真实工作负载上能够执行的平均每时钟指令数相当低。</p>

<p><img src="https://danluu.com/images/intel-cat/server_ipc.png" width="335" height="207"></p>

<p>顶部行（带有水平条）是内部Google工作负载，底部行（带有绿点）是来自SPEC的标准基准测试套件的工作站基准测试。 我们可以看到Google工作负载幸运时平均每时钟0.5条指令。 我们之前也看到这些工作负载导致核心在内存上至少停滞一半时间。</p>

<p>尽管大部分时间都在等待内存并且平均每时钟周期大约半条指令，但在真实工作负载上，高端服务器芯片比Atom或ARM芯片表现更好（Reddi等人，ToCS 2011）。 这听起来有点矛盾——如果芯片只是在等待内存，为什么需要高性能芯片？ 一个小型ARM芯片可以同样有效地等待。 事实上，它可能更擅长等待，因为<a href="http://yosefk.com/blog/amdahls-law-in-reverse-the-wimpy-core-advantage.html" rel="noopener noreferrer">更多核心等待意味着它可以使用更多带宽</a>。 但事实证明，服务器也花费大量时间利用指令级并行性，同时执行多条指令。</p>

<p><img src="https://danluu.com/images/intel-cat/server_ilp.png" width="162" height="154"></p>

<p>这是一张同时有多少执行单元忙碌的图。 几乎三分之一的时间都有3个以上的执行单元忙碌。 在等待内存的长时间停滞之间，高端芯片能够完成更多计算并更早开始等待下一次停滞。
另一件好奇的事情是服务器工作负载的指令缓存未命中率比传统工作站工作负载高得多。</p>

<h5 id="code-and-data-prioritization-technology">代码和数据优先级技术</h5>

<p><img src="https://danluu.com/images/intel-cat/icache_miss.png" width="332" height="191"></p>

<p>再次强调，顶部行（带有水平条）是内部Google工作负载，底部行（带有绿点）是来自SPEC的标准基准测试套件的工作站基准测试。 作者将指令未命中的增加归因于两个因素。 首先，部署大型二进制文件（100MB）是正常的，这会使指令缓存不堪重负。 其次，指令必须与更大的数据流竞争缓存空间，导致许多指令被驱逐。</p>

<p>为了解决这个问题，Intel引入了他们所谓的“代码和数据优先级技术”（CDP）。 这是CAT的扩展，允许核心分别限制LLC中指令和数据可以占据的子集。 由于它针对最后一级缓存，因此并不直接解决上图所示的L2缓存未命中率。 然而，L2缓存未命中但命中LLC的成本约为<a href="http://users.atw.hu/instlatx64/GenuineIntel00306D4_Broadwell2_NewMemLat.txt" rel="noopener noreferrer">Broadwell上的26纳秒，而L2未命中也未命中LLC并必须访问主内存的成本为86纳秒</a>，这是一个显著的差异。</p>

<p>Kanev等人更进一步，提出了分裂的icache/dcache层次结构。 这并不完全是一个激进的想法——L1缓存已经是分裂的，那么为什么其他层不是呢？ 我的猜测是Intel和其他主要芯片供应商有模拟结果表明这不会提高每美元性能，但谁知道呢？ 也许我们很快会看到分裂的L2缓存。</p>

<h5 id="spec">SPEC</h5>

<p>一个更普遍的观察是，SPEC现在基本上已经作为基准测试无关紧要了。 作为工作站基准测试它有些过时，而作为服务器、办公机器、游戏机、哑终端、笔记本电脑和移动设备的基准测试则完全不合适<sup id="fnref:M"><a href="#fn:M" rel="noopener noreferrer">2</a></sup>。 SPEC设计的市场每年都在缩小，而且至少十年来SPEC甚至没有真正代表那个市场。 然而，在芯片人员中，它仍然是使用最广泛的基准测试。</p>

<p><img src="https://danluu.com/images/intel-cat/search_query.png" width="640" height="352"></p>

<p>这是Google搜索查询的样子。 一个查询进入，一组广泛的RPC被发送到一组机器（第一行）。 每台机器也执行一组RPC（第二行），那些执行更多RPC（第三行），还有第四行没有显示，因为图表内容太多以至于看起来像噪声。 这是数据中心一种相当正常的工作负载类型，而SPEC中没有任何类似的东西。</p>

<p>所有这些论文中还有更多有趣的片段，如果你觉得这篇文章中的任何内容有趣，我推荐阅读它们。 <strong>如果你喜欢这篇文章，你可能也会喜欢<a href="https://www.youtube.com/watch?v=QBu2Ae8-8LM" rel="noopener noreferrer">Dick Sites关于各种性能和分析相关主题的演讲</a>、<a href="//danluu.com/clwb-pcommit/" rel="noopener noreferrer">这篇关于Intel新CLWB和PCOMMIT指令的文章</a>，以及<a href="//danluu.com/new-cpu-features/" rel="noopener noreferrer">这篇关于其他“新”CPU特性的文章</a>。</strong></p>

<p><small>
感谢Leah Hanson、David Kanter、Joe Wilder、Nico Erfurth和Jason Davies的评论/更正。
</small></p>
<div>

<hr>

<ol>
<li id="fn:S">我经常听到人们问，为什么公司X这么大？ 你可以用十分之一的工程师做到这一点！ 这往往是<a href="http://scraps.benkuhn.net/2015/09/02/darkmatter.html" rel="noopener noreferrer">不正确的</a>。 但即使是对的，通常这样做也会留下很多钱。 随着公司规模扩大，越来越小的优化变得值得。 对于规模足够大的公司，一个小型初创公司不会花10分钟做的事情，即使需要一个五人团队一年时间，也能带来十倍的回报。
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:M">当我做最后一轮面试时，我问了许多移动芯片供应商他们如何衡量我在手机上关心的事情，比如响应性。 他们是否有完整的端到端测试，带有假手指和摄像头，让你能看到点击的实际响应时间？ 或者他们可能有某种跟踪框架可以模拟点击来查看响应时间？ 据我所知，除了苹果，没有人真正掌握这一点，这或许解释了为什么两代之前的iPhone在实际任务上击败了我的最先进Android手机，尽管Android手机在工作站基准测试如SPEC和早期80年代人们做的基准测试（如Dhrystone）上碾压对手（这些基准测试都被多个移动处理器供应商使用）。 我不知道我是否能说服那些不相信这一点的人，但选择好的基准测试极其重要。 我使用Android手机是因为我免费得到了它。 下次我买手机时，我会买一个能快速完成我实际任务的手机，而不是一个运行学术基准测试表现好的手机。
 <a href="#fnref:M" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 12702 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/intel-cat/</link>
      <guid isPermaLink="false">https://danluu.com/intel-cat/</guid>
      <pubDate>Sun, 4 Oct 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>慢速锁死</title>
      <description>[AI 摘要] 这篇文章探讨了分布式系统中“慢速硬件”故障的影响，即未完全损坏但性能下降的硬件如何导致整个集群性能严重退化甚至瘫痪。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 这篇文章探讨了分布式系统中“慢速硬件”故障的影响，即未完全损坏但性能下降的硬件如何导致整个集群性能严重退化甚至瘫痪。</div><p>偶尔，你会听到类似这样的故事：“某台机器上的千兆网卡突然只能以1 Kbps的速率传输数据，导致上游发生连锁反应，使得一个100节点集群的所有工作负载运行速度慢如蜗牛，实际上使整个系统在实际应用中变得不可用”。这类故事很有趣，事后分析读起来也很引人入胜，但目前尚不清楚系统对这类故障的脆弱程度，或者这类故障到底有多普遍。</p>

<p>这种情况让我想起了<a href="https://aphyr.com/tags/jepsen" rel="noopener noreferrer">Jepsen</a>出现之前的分布式系统故障。有许多耸人听闻的轶事，但对此常见的回应是“在我这儿没问题”，即使谈论的是现在已知存在严重问题的系统。少数几家非常重视正确性的公司有好的测试和指标，但他们大多不公开讨论，普通公众也很难了解他们运行的系统是否可靠。</p>

<p></p>

<p>Thanh Do 等人尝试系统性地研究这个问题——<a href="http://ucare.cs.uchicago.edu/pdf/socc13-limplock.pdf" rel="noopener noreferrer">受创但未彻底损坏的硬件会产生什么影响</a>，以及<a href="http://ucare.cs.uchicago.edu/pdf/socc14-cbs.pdf" rel="noopener noreferrer">这种情况在实际中发生的频率有多高</a>？研究发现，许多常用系统对“跛脚”硬件不够鲁棒，但这类故障的发生率较低（至少在你没有达到不合理的规模之前）。</p>

<p>单个慢速节点的影响可能<a href="http://pages.cs.wisc.edu/~thanhdo/pdf/talk-socc-limplock.pdf" rel="noopener noreferrer">相当剧烈</a>：</p>

<p><img src="https://danluu.com/images/limpware/fb_slow_nic.png" alt="单个慢速网卡对整个集群的影响" width="484" height="328"></p>

<p>作业完成率从每小时172个作业下降到每小时1个作业，实际上使整个集群瘫痪。Facebook有处理死机的机制，但当时显然没有处理慢速机器的方法。</p>

<p>当Do等人研究广泛使用的开源软件（HDFS、Hadoop、ZooKeeper、Cassandra和HBase）时，发现了类似的问题。</p>

<p><img src="https://danluu.com/images/limpware/limpware.png" width="617" height="362"></p>

<p>每个子图代表不同的故障条件。F是HDFS，H是Hadoop，Z是Zookeeper，C是Cassandra，B是HBase。最左边（白色）的条是基线无故障情况。向右依次是崩溃情况，再往右是单个降级但未崩溃硬件的测试结果（越往右表示越慢）。在大多数（但非全部）情况下，降级硬件对性能的影响比硬件故障大得多。注意，这些图都是对数刻度；向上增加一格表示性能差异10倍！</p>

<p>有趣的是，磁盘故障有时会导致某些操作加速。这是因为当副本故障时，某些操作的复制开销会减少。对我来说，这似乎有点奇怪，因为系统必须同时寻找替代副本并复制数据，但我知道什么呢？</p>

<p>总之，为什么慢速节点比死机节点糟糕得多？作者定义了三种故障模式并解释了各自的原因。有操作慢速锁（当操作的某个子部分变慢时操作变慢，例如因为磁盘降级导致磁盘读取变慢），节点慢速锁（即使看似不相关的操作也变慢，例如因为磁盘降级导致内存读取变慢），以及集群慢速锁（整个集群变慢，例如单个降级磁盘导致整个1000台机器的集群变慢）。</p>

<p>这些情况是如何发生的？</p>

<h4 id="operation-limplock">操作慢速锁</h4>

<p>这个最简单。如果你尝试从磁盘读取，而你的磁盘很慢，你的磁盘读取就会很慢。在现实世界中，当操作存在单点故障，并且监控被设计为处理完全故障而非降级性能时，我们会看到这种情况。例如，HBase对区域的访问会通过负责该区域的服务器。数据在HDFS上被复制，但如果拥有数据的节点运行缓慢，这对你没有帮助。说到HDFS，它的超时时间是60秒，读取以64K块进行，这意味着在HDFS切换到健康节点之前，你的读取速度可能慢到接近1K/s。</p>

<h4 id="node-limplock">节点慢速锁</h4>

<p>怎么会出现（例如）慢速磁盘导致内存读取变慢的情况？再看HDFS，它使用线程池。如果每个线程都在非常缓慢地完成磁盘读取，内存读取就会阻塞，直到线程释放。</p>

<p>这不仅是在使用有限线程池或其他有限抽象时的问题——现实情况是，机器资源有限，无界抽象如果不精心设计以避免这种可能性，就会遇到机器限制。例如，Zookeeper维护操作队列，一个慢速的跟随者可能会耗尽领导者的物理内存队列。</p>

<h4 id="cluster-limplock">集群慢速锁</h4>

<p>如果整个集群依赖于单个主节点并且主节点运行缓慢，整个集群很容易变得不健康。级联故障也可能导致这种情况——第一张图中，集群从每小时完成172个作业下降到每小时1个作业，实际上是Facebook在Hadoop上的工作负载。这里让我惊讶的是，Hadoop本应具有尾部容错能力——单个慢速任务不应该对整个作业的完成产生重大影响。那么发生了什么？不健康的节点会感染健康节点，最终使整个集群锁死。</p>

<p><img src="https://danluu.com/images/limpware/cascade.png" alt="一个不健康的节点感染了整个集群" width="211" height="147"></p>

<p>Hadoop的尾部容错能力来自于在结果缓慢返回时启动推测计算。特别是当落后者比其他结果异常缓慢时。当减少节点运行缓慢时（子图H2），这可以正常工作，但当<a href="http://static.googleusercontent.com/media/research.google.com/en//archive/mapreduce-osdi04.pdf" rel="noopener noreferrer">映射节点</a>运行缓慢时（子图H1），它可能会减慢同一作业中的所有减少器，这破坏了Hadoop的尾部容错机制。</p>

<p><img src="https://danluu.com/images/limpware/hadoop_deadlock.png" alt="单个坏映射节点实际上使hadoop死锁" width="176" height="62"></p>

<p>要理解原因，我们需要看看<a href="https://www.usenix.org/legacy/event/osdi08/tech/full_papers/zaharia/zaharia_html/index.html" rel="noopener noreferrer">Hadoop的推测算法</a>。每个作业有一个介于0和1之间的进度分数（包含0和1）。对于映射，分数是已读输入数据的比例。对于减少，三个阶段（从映射器复制数据、排序和减少）各占分数的1/3。如果一个任务运行了至少一分钟，并且其进度分数低于其类别平均值减去0.2，那么一个推测性作业就会被运行。</p>

<p>在H2情况下，网卡运行缓慢，因此映射阶段正常完成，因为结果最终写入本地磁盘。但当减少节点尝试从运行缓慢的映射节点获取数据时，它们都会停滞，拉低了该类别的平均分数，从而阻止了推测性作业的运行。从大局来看，每个Hadoop节点有有限数量的映射和减少任务。如果这些任务被缓慢任务填满，整个节点就会锁死。由于Hadoop不是为避免级联故障而设计的，这最终导致整个集群锁死。</p>

<p>我发现有趣的是，正是这种故障原因在2004年发表的<a href="http://research.google.com/archive/mapreduce.html" rel="noopener noreferrer">原始MapReduce论文</a>中已有描述。他们甚至明确指出了慢速磁盘和网络是落后者的原因，这启发了他们的推测执行算法。然而，他们没有提供算法的细节。MapReduce的开源克隆版Hadoop试图避免同样的问题。Hadoop最初于2008年发布。五年后，当我们阅读的这篇论文发表时，其内置的落后者检测机制不仅未能防止多种类型的落后者，也未能防止落后者使整个集群有效死锁。</p>

<h3 id="conclusion">结论</h3>

<p>我不会详细说明每个系统在测试中的表现。论文对此有非常详细的描述，我推荐阅读这篇论文。总结一下，Cassandra表现相当不错，而HDFS、Hadoop和HBase则不然。</p>

<p>Cassandra表现好有两个原因。首先，<a href="https://issues.apache.org/jira/browse/CASSANDRA-488" rel="noopener noreferrer">这个2009年的补丁</a>防止了队列溢出感染健康节点，从而避免了在其他系统中导致集群范围故障的主要故障模式。其次，所使用的架构（<a href="http://www.eecs.harvard.edu/~mdw/proj/seda/" rel="noopener noreferrer">SEDA</a>）解耦了不同类型的操作，这使得即使某些操作运行缓慢，好的操作也能继续执行。</p>

<p>读完这篇论文后，我的主要问题是：这类故障发生的频率有多高？如何发生？以及合理的指标/报告难道不应该已经捕获了这类问题吗？</p>

<p>对于第一个问题，许多相同作者还有一篇论文，研究了<a href="http://ucare.cs.uchicago.edu/pdf/socc14-cbs.pdf" rel="noopener noreferrer">Cassandra、Flume、HDFS和ZooKeeper中的3000个故障</a>，并确定了哪些故障与硬件相关以及是什么硬件故障。</p>

<p><img src="https://danluu.com/images/limpware/hardware_failure_causes.png" width="147" height="104"></p>

<p>在他们的样本中，性能降级有14例，其他硬件故障有410例。占故障的3%；虽不常见，但并非罕见到我们可以忽视这个问题。</p>

<p>如果我们不能忽视这类错误，我们如何在它们投入生产前捕获它们？这篇论文使用了<a href="http://www.emulab.net/" rel="noopener noreferrer">Emulab测试平台</a>，这非常酷。不幸的是，Emulab页面写着“Emulab是一个公共设施，免费向全球大多数研究人员开放。如果您不确定您是否符合使用资格，请查看我们的政策文档或向我们询问。如果您认为您符合资格，可以申请开始一个新项目”。这可以理解，但对大多数人来说可能不是一个好方案。</p>

<p>绝大多数慢速硬件是由于网络或磁盘速度慢造成的。为什么不能使用改进版的Jepsen或类似工具来模拟磁盘或网络速度慢？一个简单的实现达不到Emulab的精度，但既然我们谈论的是数量级的减速，即使有10%（或2倍）的方差也应该足以测试系统对降级硬件的鲁棒性。有多种方法可以想象其运作方式。例如，在Linux上模拟慢速网络，你可以尝试通过<a href="http://linux-ip.net/articles/Traffic-Control-HOWTO/" rel="noopener noreferrer">qdisc进行限速</a>、通过<a href="https://github.com/majek/fluxcapacitor" rel="noopener noreferrer">ptrace钩住系统调用</a>等。对于慢速CPU，你可以通过cgroups和cpu.shares进行速率限制，或者只是将进程映射到UC内存（或者可能是WT或WC内存，如果那有点太慢的话），磁盘和其他故障模式也可以类似处理。</p>

<p>这就引出了我的最后一个问题，即使系统没有特别关注这类故障，难道它们不应该已经检测到吗？正如我们上面看到的，具有严重慢速硬件的系统相当罕见，我们可以将其视为死机，而不会显著影响我们的总计算资源。而且，降级硬件的系统可以相当直接地检测到。此外，多租户系统无论如何都必须<a href="//danluu.com/new-cpu-features/#partitioning" rel="noopener noreferrer">持续监控自身性能以获得良好的利用率</a>。</p>

<p>那么，我们为什么要关心设计对慢速硬件鲁棒的系统呢？部分答案在于纵深防御。我们当然应该有监控，但我们也应该拥有在监控失败时（这不可避免）仍然鲁棒的系统。另一部分答案是，通过使系统更容忍慢速硬件，我们也会使它们更容忍多租户环境中其他工作负载的干扰。最后一点有点推测性——可能设计对竞争工作干扰不特别鲁棒的系统，同时使用<a href="//danluu.com/intel-cat/" rel="noopener noreferrer">更好的分区</a>来避免干扰，这样效率更高。</p>

<p><br>
<br>
<small>
感谢 Leah Hanson、Hari Angepat、Laura Lindzey、Julia Evans 和 James M. Lee 的评论/讨论。
</small></p><p><em>由 mimo-v2.5 模型翻译，花费 6347 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/limplock/</link>
      <guid isPermaLink="false">https://danluu.com/limplock/</guid>
      <pubDate>Wed, 30 Sep 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>史蒂夫·耶格的预测记录</title>
      <description>[AI 摘要] 本文回顾并评估了史蒂夫·耶格在2004年做出的技术预测的准确性。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文回顾并评估了史蒂夫·耶格在2004年做出的技术预测的准确性。</div><p></p>

<p>我尽量避免做预测<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">1</a></sup>。这是一件无论如何都赢不了的事：如果你说对了，<a href="https://en.wikipedia.org/wiki/Hindsight_bias" rel="noopener noreferrer">后见之明偏误</a>会让你看起来像是在指出显而易见的事物。而且大多数预测都是错的。偶尔，当有人回顾某些专家的预测时，它们几乎总是出错的概率与随机猜测差不多，而后见之明偏误又会让每个预测看起来错得离谱。</p>

<p>但是，偶尔你会遇到一个做出相当可靠的非显而易见预测的人。我重读了史蒂夫·耶格的一些旧文，发现他就是其中之一。</p>

<p>他最著名的预测可能是<a href="http://steve-yegge.blogspot.com/2007/02/next-big-language.html" rel="noopener noreferrer">JavaScript的崛起</a>。这在现在看来极其明显，以至于加里·伯恩哈特的<a href="https://www.destroyallsoftware.com/talks/the-birth-and-death-of-javascript" rel="noopener noreferrer">JavaScript的生与死</a>中描绘的未来看起来至少有点可信。但通过阅读他博客以及Hacker News、Reddit和其他常见网站上的评论，你可以看出史蒂夫当时的预测在当时是多么地不显而易见。</p>

<p>史蒂夫还非常勇敢地在2004年<a href="https://sites.google.com/site/steveyegge2/ten-predictions" rel="noopener noreferrer">发布了关于未来的十个预测</a>。他说“其中大部分可能都是错的。这项练习的重点在于练习本身，而不是结果。”但这些预测实际上相当合理。</p>

<h4 id="prediction-1-xml-databases-will-surpass-relational-databases-in-popularity-by-2011">预测一：到2011年，XML数据库在流行度上将超过关系数据库</h4>

<p>2011年可能稍早了一点，JSON也并非XML，但NoSQL数据库确实发展得非常好，原因基本如预测所述：“没人喜欢做对象关系映射；大家只想要一个解决方案。”当然，<a href="https://www.mongodb.com/presentations/theres-monster-my-closet-architecture-mongodb-powered-event-processing-system" rel="noopener noreferrer">MongoDB可能会丢失你的数据，但它易于设置和使用</a>。</p>

<h4 id="prediction-2-someone-will-make-a-lot-of-money-by-hosting-open-source-web-applications">预测二：有人将通过托管开源Web应用程序赚大钱</h4>

<p>这取决于你对“大钱”的定义，但这似乎基本上是正确的。</p>

<blockquote>
<p>我们正迅速进入托管网络服务的时代，大公司正在利用其可扩展的基础设施为缺乏此类专业知识的公司提供数据和计算托管服务。</p>
</blockquote>

<p>出于某些现在看来令人费解的原因，亚马逊很久以前就理解了这一点，并且比其所有主要竞争对手都早了很久，从而获得了巨大的领先优势。Azure直到2009年才起步，而谷歌直到更晚才认真对待公共云托管。</p>

<p>现在，既然大家都意识到了史蒂夫在2004年的预测，似乎每家公司都试图推出公共云产品，但市场竞争非常激烈，招聘变得极其困难。尽管开出了数量庞大且倍数高于市场水平的薪资offer，阿里巴巴仍然未能组建起一个能够打造有竞争力公共云的团队。而那些现在试图进入市场、却没有阿里巴巴那么多资金可烧的公司，日子更加艰难。</p>

<blockquote>
<p>对于错误数据库和源代码控制系统，将它们外包的障碍在于信任。我认为大多数公司会很乐意不必花钱找人来管理Bugzilla、Subversion、Twiki等等。天啊，他们可能还希望有人能托管他们的电子邮件。</p>
</blockquote>

<p>许多公司已将问题跟踪和源代码控制转移到GitHub或其竞争对手上，如果只计算源代码控制，转移的公司甚至更多。除了最偏执的（或最受法律合规问题困扰的），自己托管电子邮件也已成为过去式。</p>

<h4 id="prediction-3-multi-threaded-programming-will-fall-out-of-favor-by-2012">预测三：到2012年，多线程编程将不再流行</h4>

<p>很难说这是否正确。这取决于你问谁。对于不需要绝对最佳性能的应用程序来说，这似乎基本上是正确的。</p>

<blockquote>
<p>过去，自从发明线程以来的20年里，许多新的、更安全的模型已经出现。由于98%的程序员认为安全是“不够男子气概”的，替代模型（例如CSP、fork/join任务和轻量级线程、协程、Erlang风格的消息传递以及其他基于事件的编程模型）基本上被大众（包括我）忽视了。</p>
</blockquote>

<p>对于真正高性能的程序来说，共享内存并发仍然是主流，但Go推广了CSP模型；Actors和Futures在JVM上都很“流行”；等等。</p>

<h4 id="prediction-4-java-s-market-share-on-the-jvm-will-drop-below-50-by-2010">预测四：到2010年，Java在JVM上的“市场份额”将降至50%以下</h4>

<p>我不认为这在2010年是正确的，即使现在也不正确，尽管我们正朝着正确的方向发展。存在着大量的“暗物质”——即编写业务逻辑、不写博客也不演讲的程序员——这使得这个预测在短期内不太可能实现。</p>

<p>准确衡量市场份额是不可能的，但基本上你能找到的任何语言排行榜都会将<a href="http://redmonk.com/sogrady/2015/07/01/language-rankings-6-15/" rel="noopener noreferrer">Java排在前3名，而Scala和Clojure甚至进不了前10</a>。考虑到语言使用量测量的近似幂律分布，Java的市场份额肯定仍在90%以上（这很可能还是低估了）。</p>

<h4 id="prediction-5-lisp-will-be-in-the-top-10-most-popular-programming-languages-by-2010">预测五：到2010年，Lisp将跻身最受欢迎的编程语言前10名</h4>

<p>完全没做到。这取决于你如何衡量，Clojure可能在前20名之内（如果你相信Redmonk的排名的话），但很难看到它在这个十年内进入前10名。与前一个预测一样，这里存在着巨大的惯性。进入前10名意味着要加入Java、JavaScript、PHP、Python、Ruby、C、C++和C#的行列。Clojure就是不够“无聊”。C#能够悄悄挤进来是因为它假装很无聊，但Clojure没希望做到这一点，而且也没有另一个Dylan即将出现。</p>

<h4 id="prediction-6-a-new-internet-community-hangout-will-appear-one-that-you-and-i-will-frequent">预测六：一个全新的互联网社区聚集地将会出现。一个你和我都会经常光顾的地方</h4>

<p>这似乎基本上是正确的，至少对于大多数“你”的定义来说是如此。</p>

<blockquote>
<p>维基、新闻组、邮件列表、公告板、论坛、可评论的博客——它们都是废话。个人主页也是废话。人们希望社交、创作内容、在不同事情上轻松竞争、学习以及娱乐：所有这些都在同一个地方，都躺在他们的沙发上即可完成。
解决这个问题的人——即创建一个真正的人用的AOL，或者不管这东西最终会是什么——将会变得非常、非常富有。</p>
</blockquote>

<p>Facebook就是在那一年成立的。扎克伯格确实变得非常、非常富有。</p>

<h4 id="prediction-7-the-mobile-wireless-handheld-market-is-still-at-least-5-years-out">预测七：移动/无线/手持设备市场至少还要5年才会成熟</h4>

<p>从史蒂夫做出预测算起的五年后将是2009年。尽管iPhone在2007年发布，但销量真正起飞还需要一段时间。2009年，大多数手机仍然是功能手机，Android才刚刚起步。</p>

<p><img src="https://danluu.com/images/yegge-predictions/phone_sales.jpeg" alt="Symbian is in the lead until Q4 2010!" width="640" height="434"></p>

<p>请注意，这张图只运行到2013年；如果你将图表<a href="https://en.wikipedia.org/wiki/Mobile_operating_system" rel="noopener noreferrer">延伸到2015年</a>并使用线性比例，2009年的销量低到你基本上看不到发生了什么。</p>

<h4 id="prediction-8-someday-i-will-voluntarily-pay-google-for-one-of-their-services">预测八：总有一天，我会自愿为谷歌的某项服务付费</h4>

<p>很难说这是否正确（史蒂夫，随时可以<a href="https://twitter.com/danluu" rel="noopener noreferrer">让我知道</a>），但精神上似乎是正确的。谷歌越来越多的服务开始收费，他们甚至在尝试<a href="https://www.google.com/contributor/welcome/" rel="noopener noreferrer">让人们付费以避免看到广告</a>。</p>

<h4 id="prediction-9-apple-s-laptop-sales-will-exceed-those-of-hp-compaq-ibm-dell-and-gateway-combined-by-2010">预测九：到2010年，苹果笔记本电脑的销量将超过惠普/康柏、IBM、戴尔和Gateway的总和</h4>

<p>如果包括平板电脑，苹果在2010年已经成为市场第一，但我认为他们的销量没有超过所有老牌厂商的总和。同样，这似乎低估了“暗物质”的影响，在这种情况下，是指人们因为无聊的原因购买笔记本电脑，例如企业买家和想要低于苹果价格范围产品的普通消费者。</p>

<h4 id="prediction-10-in-five-years-time-most-programmers-will-still-be-average">预测十：五年后，大多数程序员的水平仍然是平均的</h4>

<p>这更像是一个随口的俏皮话而非预测，但确实如此。</p>

<p>对于2004年的预测来说，这是一组相当不错的预测。除了关于Lisp的部分，所有预测似乎在方向上都是正确的；未命中主要是由于低估了年轻/新的解决方案要接管市场所需的巨大惯性。</p>

<p>史蒂夫还有许多文章并非明确关于预测，但它们也对当下的情况做出了相当可靠的预测，写于很久以前的2004年。例如<a href="https://sites.google.com/site/steveyegge2/its-not-software" rel="noopener noreferrer">《这不是软件》</a>，它超前于时代很多年，讨论了人们如何编写“软件”，编写服务器应用与编写包装软件有何根本不同，以至于使许多以前可靠的建议（如乔尔反对重写的主张）过时，以及面向服务架构的面貌；<a href="https://sites.google.com/site/steveyegge2/google-at-delphi" rel="noopener noreferrer">《在德尔斐的谷歌》</a>（同样来自2004年）正确预测了机器学习和人工智能的重要性以及谷歌在ML上的巨额投资；以及一次<a href="https://web.archive.org/web/20060814161212/http://sztywny.titaniumhosting.com/2006/07/23/stiff-asks-great-programmers-answers/" rel="noopener noreferrer">旧采访</a>，他在其中预测“Web应用程序编程将逐渐成为最重要的客户端编程。我认为它将基本淘汰所有其他客户端工具包：GTK、Java Swing/SWT、Qt，当然还有所有平台特定的工具包，如Cocoa和Win32/MFC/”；等等。史蒂夫在谷歌内部的一些博客文章也做出了有趣的预测，但据我所知这些是保密的。当然，所有这些事情现在看来都是显而易见的，但这只是史蒂夫伪装成正常人类的计划的一部分。</p>

<p>在<a href="https://plus.google.com/110981030061712822816/posts/AaygmbzVeRq" rel="noopener noreferrer">一篇相对较新的文章</a>中，史蒂夫把杰夫·贝佐斯“甩下车”，揭露他是“对人类事务有着间接兴趣的超智能外星人”之一。虽然人群关注杰夫，史蒂夫能够从后门溜走。但我们已经看穿你了，史蒂夫。</p>

<p><small>
感谢Leah Hanson、Chris Ball、Mindy Preston和Paul Gross的评论/更正。
</small></p>
<div>

<hr>

<ol>
<li id="fn:1"><p>当被问及过去的一个预测时，彼得·蒂尔评论说写作是危险的，并提到一位教授曾告诉他写一本书比生一个孩子更危险——你可以不认孩子，但对书你无能为力。</p>

<p>我能记得的唯一一次公开做出的预测是，至少有五年时间我都在<a href="https://news.ycombinator.com/item?id=3164452" rel="noopener noreferrer">公开</a><a href="https://news.ycombinator.com/item?id=4954170" rel="noopener noreferrer">表示</a>，尽管炒作，ARM在不久的将来并不会完全击垮英特尔，但这似乎太明显了，甚至不值得称之为预测。话又说回来，直到最近，这还是一个少数派观点，所以也许它没那么明显。</p>

<p>我还正确预测了几家芯片初创公司的失败，但由于绝大多数初创公司都会失败，这是意料之中的。预测成功要有趣得多，而我在这方面的记录则好坏参半。纯粹基于参与者，我认为SiByte、Alchemy和PA Semi是不错的赌注。其中，SiByte取得了坚实的成功，Alchemy没有成功，而PA Semi则可能勉强持平。</p>
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 6933 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/yegge-predictions/</link>
      <guid isPermaLink="false">https://danluu.com/yegge-predictions/</guid>
      <pubDate>Mon, 31 Aug 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>事后分析的启示</title>
      <description>[AI 摘要] 该文总结了从技术系统事后分析中发现的常见故障模式，包括错误处理、配置、硬件、人为因素和监控告警等方面。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 该文总结了从技术系统事后分析中发现的常见故障模式，包括错误处理、配置、硬件、人为因素和监控告警等方面。</div><p>我喜欢阅读事后分析报告。它们具有教育意义，但与大多数教育文档不同，它们讲述了一个有趣的故事。我花了不少时间阅读谷歌和微软的事后分析报告。我还没有对最常见的严重故障原因进行任何正式分析（目前还没有），但我发现了一些反复出现的事后分析模式。</p>

<p></p>

<h3 id="error-handling">错误处理</h3>

<p>正确的错误处理代码很难写。错误处理代码中的缺陷是导致<em>严重</em>问题的主要原因。这意味着连续出现缺陷的概率——即一个错误导致有缺陷的错误处理代码被执行——并不仅仅是单个错误概率的简单相乘。级联故障导致严重停机的情况很常见。从某种意义上说这是显而易见的——错误处理通常被认为是困难的。如果我向人们提到这一点，他们会告诉我，严重的事后分析报告有很大比例源于糟糕的错误处理以及错误未被正确处理的级联故障，这是多么明显。但尽管这“显而易见”，却似乎没有投入足够的测试和静态分析工作来确保错误处理正常工作。</p>

<p>关于这一点，丁源等人的论文和演讲非常出色：<a href="https://www.usenix.org/conference/osdi14/technical-sessions/presentation/yuan" rel="noopener noreferrer">《简单的测试可以预防大多数关键故障：对分布式数据密集型系统生产故障的分析》</a>。这篇论文基本上就像标题所说。作者将关键故障定义为可能使整个集群宕机或导致数据损坏的情况，然后查看了Cassandra、HBase、HDFS、MapReduce和Redis中的数百个缺陷，找到了48个关键故障。他们接着分析这些故障的原因，发现大多数缺陷是由于错误处理不当造成的。其中92%的故障实际上是由于错误被错误处理导致的。</p>

<p><img src="https://danluu.com/images/postmortem-lessons/osdi_error.png" alt="上一段的图表" width="530" height="142"></p>

<p>进一步深入分析，25%的缺陷是由于简单地忽略错误，8%是由于捕获了错误的异常，2%是由于未完成的TODO项，另外23%是“易于检测的”，定义为“非致命错误的错误处理逻辑非常错误，以至于任何语句覆盖测试或开发人员更仔细的代码审查都会发现这些缺陷”。顺便说一句，这就是为什么我不介意Go风格的错误处理，尽管常见的抱怨是错误检查代码使主代码路径变得杂乱。如果你关心构建健壮的系统，错误检查代码就是主代码！</p>

<p><a href="https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-yuan.pdf" rel="noopener noreferrer">完整的论文</a>有很多精华，我这里大部分不会描述。例如，他们解释了<a href="https://aphyr.com/tags/jepsen" rel="noopener noreferrer">Jepsen</a>的非凡有效性（98%的关键故障可以在一个3节点集群中重现）。他们还深入研究了有多少比例的故障是非确定性的（样本中占26%），以及非确定性的原因，并创建了一个静态分析工具来捕捉许多常见的错误引发的故障。</p>

<h3 id="configuration">配置</h3>

<p>根据我的观察，配置缺陷（而非代码缺陷）是导致极其严重停机的最常见原因。当我查看公开可用的事后分析报告时，搜索“全局停机事后分析”返回的结果中，约50%的停机是由配置更改引起的。公开可用的事后分析报告并非所有停机的代表性样本，但从事后分析数据库的随机抽样也显示，配置更改导致的极端严重停机占比过高。与错误处理一样，我经常被告知配置更改很可怕是显而易见的，但同样显而易见的是，大多数公司测试和分阶段发布配置更改的方式并不像对待代码更改那样。</p>

<p>除了极端紧急情况，高风险的代码更改基本上不会同时部署到所有机器，因为这有导致整个公司服务宕机的风险。但似乎每家公司都必须通过痛苦的方式认识到，看似无害的配置更改也可能导致公司范围内的服务停机。例如，臭名昭著的2014年11月Azure停机就是由此引起的。我这里不是针对微软；他们的主要竞争对手也因类似原因发生过严重停机，并且他们都已建立流程以降低此类停机再次发生的风险。</p>

<p>我也不想针对大型云公司。如果说有什么不同的话，那里的情况比大多数初创公司，即使是资金雄厚的初创公司要好。我所知的大多数“独角兽”初创公司都没有适当的测试/预发布环境，让他们可以测试高风险的配置更改。我可以理解为什么——建立一个足够反映生产环境以测试配置更改的优质QA环境通常很难，就像不系安全带开车一样，绝大多数时候都不会发生什么坏事。如果我必须在开车前自己制作安全带，我可能也不会系安全带。话说回来，如果像不系安全带一样，做配置更改也那么可怕，我可能会三思而后行。</p>

<p><a href="http://research.microsoft.com/en-us/um/people/gray/papers/TandemTR85.7_WhyDoComputersStop.pdf" rel="noopener noreferrer">早在1985年，Jim Gray就观察到</a>“操作员操作、系统配置和系统维护是故障的主要来源——占42%”。此后，有多项研究发现了类似的结果。例如，<a href="http://asrabkin.bitbucket.org/papers/software12.pdf" rel="noopener noreferrer">Rabkin和Katz</a>发现了以下故障原因：</p>

<p><img src="https://danluu.com/images/postmortem-lessons/rabkin_katz.png" alt="原因按降序排列：配置错误、缺陷、操作、系统、用户、安装、硬件" width="303" height="234"></p>

<h3 id="hardware">硬件</h3>

<p>机器的每个部件基本上都可能出故障。许多组件也可能导致数据损坏，其速率通常远高于标称值。例如，<a href="http://research.google.com/pubs/pub35162.html" rel="noopener noreferrer">Schroeder、Pinherio和Weber</a>发现DRAM的错误率比标称值高出一个数量级以上。静默错误的数量是惊人的，实际上这在谷歌切换到ECC内存之前曾给他们带来过问题。即使有错误检测硬件，也可能出问题；依赖<a href="http://noahdavids.org/self_published/CRC_and_checksum.html" rel="noopener noreferrer">以太网校验和来防止错误是不安全的</a>，我个人就见过格式错误的数据包作为有效数据包通过。在大规模环境下，如果你期望硬件检查能捕获硬件数据损坏，你可能会遇到比预期更多的未检测到的错误。</p>

<p>从故障组件切换（failover）也可能失败。<a href="https://aws.amazon.com/message/67457/" rel="noopener noreferrer">这次AWS故障讲述了一个典型的故事</a>。尽管采取了看似合理的措施定期测试发电机故障切换流程，但当一场风暴导致断电，且一组备用发电机在负载下未能正确供电时，AWS East的大部分服务仍然宕机了。</p>

<h3 id="humans">人为因素</h3>

<p>这一节本应称为流程错误而非人为错误，因为我认为让人处于可能意外导致灾难性故障的位置是一个流程缺陷。人们普遍认为，如果你运行大规模系统，就必须拥有能应对硬件故障的健壮系统。如果你计算机器死亡的频率，显然不能应对硬件故障的系统无法可靠运行。但人比机器更容易出错。别误会，我喜欢人。我最好的朋友就是人类。但如果你反复将人置于可能引发灾难性故障的位置，最终灾难就会发生。然而，以下模式仍然相当常见：</p>

<blockquote>
<p>哦，我们马上要进行一项有风险的操作了！好吧，让负责执行风险操作的人要“非常小心”。哎呀！我们现在造成了全局停机。</p>
</blockquote>

<p>以“因为这是一项高风险操作，所以使用了foobar高风险协议”开头的事后分析报告随处可见，以至于我现在将为降低人为风险而额外增加的人工操作步骤视为一种运维问题。一些常见的协议包括安排多人监控或确认操作，或安排运维人员待命以防灾难。这些都是合理的做法，在一定程度上降低了风险，但在我阅读的许多事后分析报告中，自动化本可以大幅降低风险或完全消除风险。有很多情况下，停机是因为期望一个人能完美无误地执行一系列指令，而他没有做到。这正是程序擅长的事情！在其他情况下，期望人手动进行错误检查。这有时更难自动化，且优势不那么明显（因为人可能捕获程序遗漏的错误情况），但在我看到的大多数情况下，自动化此类事情仍然是净收益。</p>

<p><img src="https://danluu.com/images/postmortem-lessons/idc2013_downtime.png" alt="原因按降序排列：人为错误、系统故障、IP地址耗尽、自然灾害"></p>

<p>在一项IDC调查中，受访者投票将人为错误列为数据中心中最麻烦的问题原因。</p>

<p>我觉得有趣的一点是，人为错误在公开的事后分析报告中似乎代表性不足。据我所知，谷歌和微软的自动化程度都远高于大多数公司，所以我预计他们的事后分析数据库中人为错误导致的停机比例会低于我看到的公开报告，但事实恰恰相反。我的猜测是，这是因为当根本原因是允许人为错误的风险手动操作程序时，公司不太可能撰写公开的事后分析报告。一个表面上看似合理的替代原因是，技术的进步实际上增加了人为因素导致问题的比例，这在某些行业（如飞行）确实如此。我怀疑这里并非如此，因为许多公司存在大量的手动操作，但如果不访问多家公司的事后分析数据库，就无法确定。如果任何公司希望支持此类分析（以及其他分析）的进行（可能是匿名化的），请与我联系。</p>

<h3 id="monitoring-alerting">监控 / 告警</h3>

<p>缺乏适当的监控从来不是问题的唯一原因，但它通常是一个严重的促成因素。与人为错误的情况一样，这些在公开的事后分析报告中似乎代表性不足。当我与其他公司的人谈论他们最糟糕的“未遂事故”时，很大一部分原因是没有设置正确的告警。他们通常通过某种运维英雄主义避免了需要公开事后分析报告的严重灾难，但英雄主义不是可扩展的解决方案。</p>

<p>有时，这些未遂事故是由微妙的代码缺陷引起的，这可以理解。但更多时候，是由于明显的流程缺陷造成的，例如对于某一类故障没有清晰的升级路径，导致错误的团队花了半天时间调试问题；或者没有备份值班人员，导致系统在（不可避免地）值班人员未发现问题的情况下丢失或损坏数据数小时后才被发现。</p>

<p><a href="https://en.wikipedia.org/wiki/Northeast_blackout_of_2003" rel="noopener noreferrer">2003年美国东北部大停电</a>就是一个很好的例子。它本可以是一次轻微的停机，甚至只是一次轻微的服务降级，但（除其他原因外）一系列未收到的告警导致它成为史上最严重的停电事故之一。</p>

<h3 id="not-a-conclusion">并非结论</h3>

<p>这本该是写结论的地方，但我真的很想在写下某种结论或行动呼吁之前做一些严肃的数据分析。我应该寻找什么？还有哪些其他主要的常见错误类别应该考虑？这些不是修辞性问题，我真的有兴趣听到我应该考虑的其他类别。<a href="https://twitter.com/danluu" rel="noopener noreferrer">请随时在这里联系我</a>。我也正在<a href="https://github.com/danluu/post-mortems" rel="noopener noreferrer">这里收集公开的事后分析报告</a>。</p>

<p>总有一天，我会进行严肃的分析，但即使不对成千上万份事后分析报告进行分类，由于阅读了许多此类报告，我可能会在某些方面做得不同。在代码审查中，我会相对花更多时间在错误和错误处理代码上，而花更少时间在“快乐路径”上。我也会花更多时间检查并试图说服人们修复那些“显而易见”的流程缺陷。</p>

<p>这些故障模式让我感到好奇的一点是，当我与其他人讨论我的发现时，至少有一个人告诉我，我发现的每个流程问题都是显而易见的。但这些“显而易见”的事情仍然导致了很多故障。有一次，有人告诉我，我告诉他们的事情是显而易见的，而就在几乎同一时间，他们公司因我们正在讨论的精确原因导致了一个价值数十亿美元的服务的全局停机。仅仅因为某事显而易见，并不意味着它正在被践行。</p>

<h3 id="elsewhere">其他资料</h3>

<p>Richard Cook的<a href="http://web.mit.edu/2.75/resources/random/How%20Complex%20Systems%20Fail.pdf" rel="noopener noreferrer">《复杂系统如何失败》</a>采取了更通用的方法；他的工作启发了<a href="http://www.amazon.com/gp/product/0312430000/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0312430000&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=2VKSNUT3M4W7KUMR" rel="noopener noreferrer">《清单革命》</a>，这本书挽救了生命。</p>

<p>Allspaw和Robbin的<a href="http://www.amazon.com/gp/product/1449377440/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=1449377440&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=U7MG5A2J6OLQG627" rel="noopener noreferrer">《Web运维：保持数据准时》</a>在网络应用的背景下讨论了这类事情。Allspaw还有一篇关于<a href="http://www.kitchensoap.com/2011/04/07/resilience-engineering-part-i/" rel="noopener noreferrer">来自其他领域的一些相关文献</a>的不错的文章。</p>

<p>在我更熟悉的领域，对故障原因的研究有着悠久的历史。一些重点包括<a href="http://www.hpl.hp.com/techreports/tandem/TR-85.7.pdf" rel="noopener noreferrer">Jim Gray的《计算机为何停止？我们能做些什么？》</a>（1985年），<a href="https://www.usenix.org/legacy/event/usits03/tech/full_papers/oppenheimer/oppenheimer_html/" rel="noopener noreferrer">Oppenheimer等人的《互联网服务为何失败？我们能做些什么？》</a>（2003年），<a href="Understanding and Dealing with Operator Mistakes in Internet Services" rel="noopener noreferrer">Nagaraja等人的《理解与处理互联网服务中的操作员错误》</a>（2004年），<a href="http://www.morganclaypool.com/doi/abs/10.2200/S00516ED2V01Y201306CAC024" rel="noopener noreferrer">Barroso等人的《数据中心即计算机》</a>（2009年）的部分内容，以及<a href="http://asrabkin.bitbucket.org/papers/software12.pdf" rel="noopener noreferrer">Rabkin和Katz的《Hadoop集群如何崩溃》</a>（2013年），和<a href="http://cseweb.ucsd.edu/~tixu/papers/sosp13.pdf" rel="noopener noreferrer">Xu等人的《不要责怪用户的配置错误》</a>。</p>

<p>在理解飞机可靠性方面也有着悠久的历史，<a href="https://en.wikipedia.org/wiki/Reliability-centered_maintenance" rel="noopener noreferrer">几十年来流程如何改变的故事</a>非常迷人，尽管我不确定如何将这些经验教训推广。</p>

<p>顺便提一下，我发现为了提高一点正常运行时间和可靠性而付出的努力令人感兴趣。1974年，<a href="http://web.eecs.umich.edu/~prabal/teaching/eecs582-w13/readings/ritchie74unix.pdf" rel="noopener noreferrer">Ritchie和Thompson</a>描述了一个“成本仅4万美元”的系统，正常运行时间为98%。十年后，Jim Gray使用99.6%的正常运行时间作为一个相当好的基准。我们现在可以做得比这好得多，但实现它所需的复杂性水平令人咋舌。</p>

<h4 id="acknowledgments">致谢</h4>

<p><small>
感谢Leah Hanson、Anonymous、Marek Majkowski、Nat Welch、Joe Wilder和Julia Hansbrough对本文初稿提供了评论。Anonymous，如果你更愿意不匿名，请在Zulip上给我发消息。给关注的人记一下，这包括来自谷歌的三位，来自Cloudflare的一位，以及一位匿名评论者。我总是对评论/批评持开放态度，但我特别有兴趣听到在规模较小的公司工作的人的评论。我的印象是否具有普遍性？
</small></p>

<p><small>
感谢gwern和Dan Reif接受我的邀请，在本文中发现了一些错误。
</small></p><p><em>由 mimo-v2.5 模型翻译，花费 8308 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/postmortem-lessons/</link>
      <guid isPermaLink="false">https://danluu.com/postmortem-lessons/</guid>
      <pubDate>Thu, 20 Aug 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Slashdot和Sourceforge</title>
      <description>[AI 摘要] 文章揭示SourceForge强制捆绑广告软件破坏用户信任，以及其姊妹网站Slashdot压制相关报道进一步损害品牌声誉的事件。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章揭示SourceForge强制捆绑广告软件破坏用户信任，以及其姊妹网站Slashdot压制相关报道进一步损害品牌声誉的事件。</div><p></p>

<p>如果你在过去一周（2015年5月24日当周）关注任何科技新闻聚合平台，你很可能已经看到关于<a href="https://plus.google.com/+gimp/posts/cxhB1PScFpe" rel="noopener noreferrer">SourceForge</a>接管现有项目管理员账户并在GIMP等软件包安装程序中植入广告软件的报道。对于不了解此事的读者，SourceForge长期存在广告软件捆绑安装的历史，但过去是可选的。现在看来，这一流程对许多项目已成为强制性的。</p>

<p>自从SourceForge推出允许项目选择性捆绑广告软件的功能后，人们便对其保持警惕——至少还能说这是项目方自愿的选择。但如今SourceForge明显表现出恶意行为，彻底摧毁了十六年运营积累的全部用户信任。任何明理的人都不会再从SourceForge下载东西了。如果搜索引擎开始因SourceForge传播广告软件而对其进行惩罚，连那些不了解此事的用户流量都将消失，这基本上会抹去其所有价值。</p>

<p>每当我听到这类故事，都会惊讶于摧毁用户信任的速度之快，以及破坏品牌比建立品牌容易得多。有趣的是，同属一家公司的Slashdot也在试图毁掉自己的品牌。作为唯一未报道此事件的主流科技新闻聚合平台，这是因为他们<a href="http://www.reddit.com/r/programming/comments/37xbzt/goodbye_sourceforge/crqpnzo" rel="noopener noreferrer">封禁了所有相关投稿</a>。这促使人们开始在其他新闻评论区提交相关内容。</p>

<p><img src="https://danluu.com/images/slashdot-sourceforge/slashdot_sourceforge.png" alt="Slashdot上关于SourceForge的高赞评论" width="733" height="475"></p>

<p>我觉得这令人难以置信。怎么会有人认为在Slashdot上审查SourceForge广告软件捆绑事件对Slashdot Media（同时拥有Slashdot和Sourceforge的控股公司）有益？谷歌或谷歌新闻的快速搜索显示，该事件已登上多家主流科技媒体，这意味着压制报道的最佳情况是毫无价值。最坏的情况下，这种审查将制造另一个<a href="http://yro.slashdot.org/story/06/04/20/1538256/growing-censorship-concerns-at-digg" rel="noopener noreferrer">Digg时刻</a><sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">1</a></sup>——读者不再信任版主并转向审查更少的网站。这基本上没有任何好处，却存在巨大风险。</p>

<p>我能理解拥有Slashdot Media的DHI控股公司想采取行动。他们最近的财报显示Slashdot Media业绩不佳，而他们最不需要的就是负面宣传导致用户离开Slashdot：</p>

<blockquote>
<p>截至2015年3月31日的季度，“企业与其他”分部收入下降6%至450万美元，反映出Slashdot Media某些收入流的下滑。</p>
</blockquote>

<p>对比DHI收购Slashdot Media后的2012年第四季度收入（收购后首个季度）：</p>

<blockquote>
<p>总收入达5270万美元...其中470万美元来自Slashdot Media收购</p>
</blockquote>

<p>“企业与其他”似乎不仅涵盖Slashdot Media。尽管如此，连同他们从Sourceforge榨取的短期收入，整个“企业与其他”分部的表现仍不如2012年的Slashdot Media<sup id="fnref:2"><a href="#fn:2" rel="noopener noreferrer">2</a></sup>。他们最初对SourceForge和Slashdot的计划是<a href="http://www.theverge.com/2012/9/18/3351970/dice-holdings-geeknet-slashdot-careers-news" rel="noopener noreferrer">"保持原样，因为我们非常注重不破坏用户的使用方式..."</a>，但很快发现这行不通。以下是2013年财报的摘录：</p>

<blockquote>
<p>过去一年广告收入持续下降，未来Slashdot Media核心广告业务的财务表现也无改善预期。因此，与Slashdot Media相关的720万美元无形资产和630万美元商誉被核销为零。</p>
</blockquote>

<p>我相信此后不久SourceForge开始试验为可选项目捆绑广告软件/恶意软件，不知何故导致了今天的局面。</p>

<p>我能理解帮助Slashdot Media的迫切愿望，但很难看出永久损害Slashdot声誉能带来什么好处。据我所知，他们已陷入这种经典三段论："我们必须做点什么。这是一件事。我们必须做这件事。"</p>

<p><em>更新：</em>Sourceforge/GIMP事件在登上其他所有平台一周后、本文撰写次日终于出现在Slashdot上，编辑附注称自己刚度完周末回来，发现人们"对我们'封禁'此新闻感到恐慌"，试图淡化处理，暗示若非周末本会及时发布。但当该事件上周三爆出时，多位编辑（包括最终发布Sourceforge/GIMP报道的编辑）已提交数十篇投稿，因此这个"周末"借口并不令人信服。尤其是当Sourceforge/nmap事件在下个周三爆出、Slashdot因先前延迟受到严格审查时，他们竟能在同日几乎立即发布报道——尽管上次事件也发生在周三"周末"开始时。更甚者，Slashdot报道<a href="http://www.reddit.com/r/programming/comments/386c75/sourceforge_locked_in_projects_of_fleeing_users/crstuof" rel="noopener noreferrer">措辞谨慎</a>，使用"修改后的二进制文件""呈现第三方推荐"等词，而非"恶意软件"或"广告软件"。</p>

<p>当然这可能只是无辜的误解，我们可能永远无法确知真相。但Slashdot发布的借口确实不太令人信服。</p>
<div>

<hr>

<ol>
<li id="fn:1">讽刺的是，如果你点击链接会发现，Slashdot创始人CmdrTaco曾反对"因内容批评赞助商而删除"。并非说Slashdot当时没有偏见；Slashdot过去以亲Linux、亲开源、反微软、反商业的偏见著称。阅读该链接中的评论会发现，许多人在给与Slashdot固有偏见相悖的观点点赞后失去了投票权。但正是Slashdot的偏见使其遗漏此事件显得格外反常。这本是Slashdot读者和版主通常会大做文章的内容。但CmdrTaco已离开多年，旧版Slashdot也已不在。
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:2">若比较同比数据，Slashdot Media在2013年第一季度的收入为400万美元。
 <a href="#fnref:2" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 3756 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/slashdot-sourceforge/</link>
      <guid isPermaLink="false">https://danluu.com/slashdot-sourceforge/</guid>
      <pubDate>Sun, 31 May 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Googlebot垄断</title>
      <description>[AI 摘要] 本文探讨了许多网站通过robots.txt只允许Googlebot爬取，屏蔽其他爬虫和互联网档案馆的行为，损害了互联网多样性。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文探讨了许多网站通过robots.txt只允许Googlebot爬取，屏蔽其他爬虫和互联网档案馆的行为，损害了互联网多样性。</div><p>今天才知道，贝尔实验室和很多其他网站都屏蔽了archive.org，更不用说大多数搜索引擎了。原来我的一个GitHub仓库里有<a href="https://github.com/danluu/debugging-stories/issues/3" rel="noopener noreferrer">一个失效的网站链接</a>，这是因为一个旧网页被删除了。当我想从archive.org上获取原始内容时，发现它无法访问，因为贝尔实验室在他们的robots.txt中屏蔽了archive.org爬虫：</p>

<p></p>

<pre><code>User-agent: Googlebot
User-agent: msnbot
User-agent: LSgsa-crawler
Disallow: /RealAudio/
Disallow: /bl-traces/
Disallow: /fast-os/
Disallow: /hidden/
Disallow: /historic/
Disallow: /incoming/
Disallow: /inferno/
Disallow: /magic/
Disallow: /netlib.depend/
Disallow: /netlib/
Disallow: /p9trace/
Disallow: /plan9/sources/
Disallow: /sources/
Disallow: /tmp/
Disallow: /tripwire/
Visit-time: 0700-1200
Request-rate: 1/5
Crawl-delay: 5

User-agent: *
Disallow: /
</code></pre>

<p>实际上，贝尔实验室不仅屏蔽了互联网档案馆的爬虫，它还屏蔽了除Googlebot、msnbot和其公司自己的爬虫之外的所有爬虫。而msnbot在<a href="http://en.wikipedia.org/wiki/Msnbot" rel="noopener noreferrer">五年前</a>就已经被bingbot取代了！</p>

<p>用一个只有在贝尔实验室才能找到的术语<sup id="fnref:A"><a href="#fn:A" rel="noopener noreferrer">1</a></sup>（例如，“这是开始提供一些来自第十版Research Unix手册的材料。”）进行快速搜索，会发现bing已经索引了该页面；要么是bingbot遵循一些msnbot的规则，要么是msnbot仍然独立运行并索引像贝尔实验室这样的网站，这些网站屏蔽了bingbot但没有屏蔽msnbot。幸运的是，在这种情况下，很多搜索引擎（如Yahoo和DDG）都使用Bing的结果，所以贝尔实验室并没有从非Google的互联网上消失，但如果你是<a href="http://en.wikipedia.org/wiki/Yandex#cite_note-13" rel="noopener noreferrer">那55%使用yandex的俄罗斯人之一</a>，那你就没那么幸运了。</p>

<p>而所有这些都算是相对较好的情况，至少允许一个非Google的爬虫运行。看到robots.txt文件禁止除了Googlebot之外的一切并不少见。运营一个<a href="http://blog.nullspace.io/building-search-engines.html" rel="noopener noreferrer">竞争性的搜索引擎</a>并防止Google垄断已经够难了，如果网站再屏蔽非Google爬虫，那就更难了。我们不需要让事情变得更难，也不需要<a href="#fn:2" rel="noopener noreferrer">无意中<sup id="fnref:2"></sup></a><a href="#fn:2" rel="noopener noreferrer">2</a>屏蔽互联网档案馆爬虫。</p>

<p><small>
附言：当您检查您的robots.txt是否屏蔽了除Googlebot之外的所有人时，不妨也检查一下您的CPUID检查，确保您使用的是功能标志<a href="https://twitter.com/danluu/status/1203450367515615233" rel="noopener noreferrer">，而不是屏蔽除Intel和AMD之外的所有人</a>。

顺便说一句，我确实认为屏蔽爬虫，包括archive.org，可能有正当的理由，但我不认为许多网页开发者普遍存在的默认做法——屏蔽除googlebot之外的所有人——真的是打算屏蔽竞争对手的搜索引擎以及archive.org。
</small></p>

<p>2021年更新：自从本文首次发布以来，archive.org开始忽略robots.txt中的屏蔽指令，并归档那些在robots.txt中被屏蔽的帖子。我听说一些竞争对手的搜索引擎也这样做，所以这种对robots.txt的滥用——网站屏蔽除googlebot之外的所有人——正逐渐使robots.txt实际上变得无用，就像浏览器在用户代理字符串中将自己标识为每一种浏览器，以绕过那些错误地屏蔽他们认为不兼容的浏览器的网站一样。</p>

<p>一个相关的情况是，网站有时会因为一时不满而屏蔽竞争对手的搜索引擎，比如Bing，但他们不会对Google这样做，因为Google提供了太多的流量，他们无法承受那样做的后果，例如，<a href="https://twitter.com/danluu/status/981992814824378369" rel="noopener noreferrer">Discourse屏蔽了Bing，因为他们对Bing以0.46 QPS的速度爬取discourse感到不满</a>。
</p>
<div>

<hr>

<ol>
<li id="fn:A">至少在这个页面被索引之前是这样。Google在这个页面的更新上的周转时间是几分钟到几小时，我觉得这非常了不起。实际上，我觉得这比在CNN上看到的东西在几秒钟到几分钟内被反映出来更令人印象深刻。当然，搜索引擎会想实时更新CNN。但像我这样的博客？如果他们每小时都在爬取像我这样的小众网站，他们肯定也在每小时爬取数百万甚至数千万其他网站，并相应地更新他们的索引。要么就是他们通过RSS获取更新，但即使那样，对于我这样流量的网站，也需要每小时数百万甚至数千万次索引更新。
 <a href="#fnref:A" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:2">我原则上不反对一个防止archive.org归档网站的robots.txt——尽管程序员中的普遍看法是屏蔽archive.org是一种罪过，但我认为如果你不想让你网站的旧版本到处流传，这样做是可以的。但这应该是一个知情的决定，而不是一个意外。
 <a href="#fnref:2" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 2993 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/googlebot-monopoly/</link>
      <guid isPermaLink="false">https://danluu.com/googlebot-monopoly/</guid>
      <pubDate>Wed, 27 May 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>为“无趣”语言辩护</title>
      <description>[AI 摘要] 这篇文章为被认为“无趣”的编程语言辩护，指出它们在构建重要系统中广泛使用且被低估，其实际价值常被忽视。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 这篇文章为被认为“无趣”的编程语言辩护，指出它们在构建重要系统中广泛使用且被低估，其实际价值常被忽视。</div><p>“无趣”的语言被低估了。从市场份额来看，它们的评价实际上相当高，但即便如此，它们依然被低估了。尽管丹·麦金利（Dan McKinley）的<a href="https://mcfunley.com/choose-boring-technology" rel="noopener noreferrer">"选择无趣的技术"</a>一文广为流传，但“无趣”的语言仍备受诟病。使用它们的人也同样如此（例如，他们成为了保罗·格雷厄姆和乔尔·斯波斯基等人文章的<a href="https://news.ycombinator.com/item?id=2379259" rel="noopener noreferrer">批评对象</a>）。</p>

<p>人们推销“有趣”语言时常用的论点大致是：“当然，你可以用‘Blub’（一种假设的无趣语言）来应付那些无聊的工作，几乎所有程序员都这么干，但如果你做的是有趣的工作，那你就会想用有趣的语言。”我的感觉是，这完全搞反了。当我从事那些基本上瓶颈在于编写样板代码速度的无聊工作时，使用一种有趣的语言（比如F#）感觉要好得多，因为它能让我减少花在样板代码上的时间。但当我从事有趣的工作时，样板代码的占比微乎其微，我不介意使用像Java这样的“无趣”语言，即使这意味着我编写的大部分代码都是样板代码。</p>

<p>另一个常见的论点（与前者类似）是，学习有趣的语言会教你新的思维方式，从而让你成为一个更高效的程序员<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">1</a></sup>。我不能代表别人，但在我职业生涯早期学习ACL2（一种Lisp）、Forth、F#等语言时，我觉得这种推理很有说服力；其中足够多的东西留存下来，以至于我至今仍然热爱F#。但是，尽管我认真对待“学习大量支持不同编程范式的语言会改变你的思维方式”这一建议，我的经验是，我所学到的东西大多只是让我能更高效地处理样板代码。虽然当我在解决受样板代码限制的问题时这非常棒，但当我遇到难题时，我几乎不花时间在这类事情上，因此我从学习多种语言中获得的技能并不能真正帮到我；相反，能帮助我的是领域知识，它给了我一个解决难题的良好杠杆。这解释了我研究生毕业后进入现实世界时曾疑惑的一点：为什么那些构建出令我印象最深刻的系统的程序员，通常拥有深厚的领域知识，而不是有趣的语言知识？</p>

<p>对此的另一个视角是萨顿（Sutton）在被问及为何抢银行时的回答：“因为钱在那里。”我为什么使用“无趣”的语言？因为我想共事的人用的就是这些语言，我想构建的系统也是用这些语言编写的。我感兴趣的绝大多数系统都是用“无趣”的语言编写的。虽然这在技术上并不意味着我想共事的绝大多数人主要使用并精通这些“无趣”的语言，但实践中情况也确实如此。这意味着，对于全新项目，最佳选择很可能也是一种“无趣”的语言。我认为F#很棒，但为了与我想要共事的人一起解决我想解决的问题，我不会选择它。</p>

<p>如果我看看我个人印象深刻的事物清单（如Spanner、BigTable、Colossus等），它们基本上都是用C++编写的，其仿制品则几乎都用Java编写。稍加思考就会发现，用C、C++和Java编写的软件清单确实相当长。在我使用的东西的传递闭包，以及这些事物所依赖的库和基础设施中，这三种语言遥遥领先，PHP、Ruby和Python紧随其后，位列前六。如果算上前端的东西，JavaScript应该也在其中，但它如此普遍，以至于列出来似乎有点多此一举。</p>

<p>下面是一些用“无趣”语言编写的软件列表。这些列表很长，我将把它们分成一些任意的子列表。和往常一样，这些并非真正整齐的正交类别，更应该是标签，但我们就这么做了。在下面的列表中，应用程序根据其后端主要使用的语言被归类在“后端”下。其他类别相当直观，即使它们的定义有些独特且可能过于宽泛。</p>

<h2 id="c">C</h2>

<h3 id="operating-systems">操作系统</h3>

<p>Linux，包括KindleOS等变种<br>
BSD<br>
Darwin（含C++）<br>
<a href="http://en.wikipedia.org/wiki/Plan_9_from_Bell_Labs" rel="noopener noreferrer">Plan 9</a><br>
Windows（内核用C编写，其他部分含C++）</p>

<h3 id="platforms-infrastructure">平台/基础设施</h3>

<p><a href="http://en.wikipedia.org/wiki/Memcached" rel="noopener noreferrer">Memcached</a><br>
<a href="https://www.sqlite.org/index.html" rel="noopener noreferrer">SQLite</a><br>
<a href="http://en.wikipedia.org/wiki/Nginx" rel="noopener noreferrer">nginx</a><br>
<a href="http://en.wikipedia.org/wiki/Apache_HTTP_Server" rel="noopener noreferrer">Apache</a><br>
<a href="http://en.wikipedia.org/wiki/IBM_DB2" rel="noopener noreferrer">DB2</a><br>
<a href="http://en.wikipedia.org/wiki/PostgreSQL" rel="noopener noreferrer">PostgreSQL</a><br>
<a href="https://aphyr.com/posts/307-call-me-maybe-redis-redux" rel="noopener noreferrer">Redis</a><br>
<a href="http://en.wikipedia.org/wiki/Varnish_%28software%29" rel="noopener noreferrer">Varnish</a><br>
<a href="http://en.wikipedia.org/wiki/HAProxy" rel="noopener noreferrer">HAProxy</a><br>
AWS Lambda workers（大部分周边基础设施用Java编写），据@jayachdee所言</p>

<h3 id="desktop-apps">桌面应用</h3>

<p>git<br>
Gimp（含perl）<br>
VLC<br>
Qemu<br>
OpenGL<br>
<a href="http://en.wikipedia.org/wiki/FFmpeg" rel="noopener noreferrer">FFmpeg</a><br>
大多数GNU用户态工具<br>
大多数BSD用户态工具<br>
<a href="http://lcamtuf.coredump.cx/afl/" rel="noopener noreferrer">AFL</a><br>
Emacs<br>
Vim</p>

<h2 id="c-1">C++</h2>

<h3 id="operating-systems-1">操作系统</h3>

<p>BeOS/<a href="http://en.wikipedia.org/wiki/Haiku_%28operating_system%29" rel="noopener noreferrer">Haiku</a></p>

<h3 id="platforms-infrastructure-1">平台/基础设施</h3>

<p><a href="http://research.google.com/archive/gfs.html" rel="noopener noreferrer">GFS</a><br>
<a href="http://www.wired.com/2012/07/google-colossus/" rel="noopener noreferrer">Colossus</a><br>
<a href="http://en.wikipedia.org/wiki/Ceph_%28software%29" rel="noopener noreferrer">Ceph</a><br>
<a href="http://research.google.com/pubs/pub36632.html" rel="noopener noreferrer">Dremel</a><br>
<a href="http://research.google.com/archive/chubby.html" rel="noopener noreferrer">Chubby</a><br>
<a href="http://research.google.com/archive/bigtable.html" rel="noopener noreferrer">BigTable</a><br>
<a href="http://research.google.com/archive/spanner.html" rel="noopener noreferrer">Spanner</a><br>
<a href="http://en.wikipedia.org/wiki/MySQL" rel="noopener noreferrer">MySQL</a><br>
<a href="http://en.wikipedia.org/wiki/%C3%98MQ" rel="noopener noreferrer">ZeroMQ</a><br>
<a href="https://github.com/scylladb/scylla" rel="noopener noreferrer">ScyllaDB</a><br>
<a href="https://aphyr.com/posts/322-call-me-maybe-mongodb-stale-reads" rel="noopener noreferrer">MongoDB</a><br>
<a href="http://en.wikipedia.org/wiki/Apache_Mesos" rel="noopener noreferrer">Mesos</a><br>
<a href="http://en.wikipedia.org/wiki/Java_virtual_machine" rel="noopener noreferrer">JVM</a><br>
<a href="http://en.wikipedia.org/wiki/.NET_Framework" rel="noopener noreferrer">.NET</a></p>

<h3 id="backend-apps">后端应用</h3>

<p>Google Search<br>
PayPal<br>
Figma（<a href="https://www.figma.com/blog/building-a-professional-design-tool-on-the-web/" rel="noopener noreferrer">前端用C++编写并交叉编译为JS</a>）</p>

<h3 id="desktop-apps-1">桌面应用</h3>

<p>Chrome<br>
MS Office<br>
LibreOffice（含Java）<br>
Evernote（最初用C#，后转为C++）<br>
Firefox<br>
Opera<br>
Visual Studio（含C#）<br>
Photoshop, Illustrator, InDesign等<br>
gcc<br>
llvm/clang<br>
Winamp<br>
<a href="https://github.com/z3prover/z3/wiki" rel="noopener noreferrer">Z3</a><br>
大多数3A游戏<br>
大多数专业音频和视频制作应用</p>

<h3 id="elsewhere">其他领域</h3>

<p>另见<a href="http://www.stroustrup.com/applications.html" rel="noopener noreferrer">此列表</a>及<a href="https://isocpp.org/wiki/faq/big-picture#who-uses-cpp" rel="noopener noreferrer">此处部分链接</a>。</p>

<h2 id="java">Java</h2>

<h3 id="platforms-infrastructure-2">平台/基础设施</h3>

<p><a href="http://en.wikipedia.org/wiki/Apache_Hadoop" rel="noopener noreferrer">Hadoop</a><br>
<a href="http://www.aosabook.org/en/hdfs.html" rel="noopener noreferrer">HDFS</a><br>
<a href="http://en.wikipedia.org/wiki/Apache_ZooKeeper" rel="noopener noreferrer">Zookeeper</a><br>
<a href="http://techblog.netflix.com/2014/10/using-presto-in-our-big-data-platform.html" rel="noopener noreferrer">Presto</a><br>
<a href="https://aphyr.com/posts/294-call-me-maybe-cassandra/" rel="noopener noreferrer">Cassandra</a><br>
<a href="https://aphyr.com/posts/323-call-me-maybe-elasticsearch-1-5-0" rel="noopener noreferrer">Elasticsearch</a><br>
<a href="https://en.wikipedia.org/wiki/Lucene" rel="noopener noreferrer">Lucene</a><br>
<a href="http://en.wikipedia.org/wiki/Apache_Tomcat" rel="noopener noreferrer">Tomcat</a><br>
<a href="http://en.wikipedia.org/wiki/Jetty_(web_server)" rel="noopener noreferrer">Jetty</a></p>

<h3 id="backend-apps-1">后端应用</h3>

<p>Gmail<br>
LinkedIn<br>
<a href="http://www.theregister.co.uk/2007/11/12/ebay_glitches/" rel="noopener noreferrer">Ebay</a><br>
<a href="http://silvaetechnologies.eu/blg/50/the-majority-of-netflix-services-are-built-on-java" rel="noopener noreferrer">Netflix的大部分服务</a><br>
亚马逊服务中很大一部分</p>

<h3 id="desktop-apps-2">桌面应用</h3>

<p><a href="http://en.wikipedia.org/wiki/IBM_VisualAge" rel="noopener noreferrer">Eclipse</a><br>
JetBrains IDEs<br>
SmartGit<br>
<a href="http://en.wikipedia.org/wiki/Minecraft" rel="noopener noreferrer">Minecraft</a></p>

<h1 id="vhdl-verilog">VHDL/Verilog</h1>

<p>我甚至不会为此列表，因为基本上每个主要微处理器、网卡、交换机等都是用VHDL或Verilog制造的。对于现有项目，你可能会说这是因为你有一个熟悉某种“无趣”语言的大团队，但我曾参与过用于深度学习和网络虚拟化的全新硬件/软件协同设计项目，团队都是从头招聘的，我们仍然使用了Verilog，尽管其中一个团队拥有麻省理工学院阿文德（Arvind）小组之外最大的精通Bluespec的硬件工程师团队之一。</p>

<p><a href="https://twitter.com/danluu" rel="noopener noreferrer">请建议</a>你认为应属于此列表的其他软件；不一定非得是我个人使用的软件。另外，有人知道EC2、S3和Redshift是用什么写的吗？我怀疑是C++，但找不到确凿的引用来源。本文最后更新于2021年8月。</p>

<h2 id="appendix-meta">附录：元讨论</h2>

<p>我发现一个有趣的现象，在与人们的私下交谈中，我所认识的绝大多数经验丰富的开发者都认为，除了性能限制外，大多数主流语言基本上都没问题，这一点在构建了令我真正印象深刻系统的人群中尤其明显。而关于人们可能想学什么的在线讨论则大相径庭，学习有趣/花哨的语言通常在人们的清单上名列前茅。当我与新程序员交谈时，他们往往深受此影响（例如，在机器学习变得流行之前，在Recurse Center，学习花哨的语言是人们试图提升编程技能的最流行方式，我敢说现在它已降至第二，排在机器学习之后）。虽然我认为学习花哨的语言对某些人确实有效，但我认为这被高估了，因为有许多其他技术似乎对同样比例的尝试者有效，却远没有那么流行。</p>

<p>我有一个疑问，为什么在线讨论这个话题如此一边倒，而我在现实生活中进行的讨论却如此相反地一边倒。当然，无论是在网上活跃的人还是我个人认识的人，都不能代表程序员的样本，但我仍然觉得这很有趣。</p>

<p><small>
感谢Leah Hanson、James Porter、Waldemar Q、Nat Welch、Arjun Sreedharan、Rafa Escalante、@matt_dz、Bartlomiej Filipek、Josiah Irwin、@jayachdee、Larry Ogrondek、Miodrag Milic、Presto、Matt Godbolt、Leah Hanson、Noah Haasis、Lifan Zeng、@chozu@fedi.absturztau.be 和 Josiah Irwin 提供的评论/修正/讨论。
</small></p>
<div>

<hr>

<ol>
<li id="fn:S">此论证的一个变体超越了教授技术，认为你所知的语言通过萨丕尔-沃尔夫假说决定了你的思维。我个人并不觉得这有说服力，因为当我解决难题时，我不会用某种编程语言来思考。如果你用编程语言思考，情况可能因人而异，但我想到的是抽象解决方案，然后将其翻译成一种语言，所以工具箱里多一种语言最多只能帮我想到更好的翻译并节省翻译时间。
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 6888 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/boring-languages/</link>
      <guid isPermaLink="false">https://danluu.com/boring-languages/</guid>
      <pubDate>Mon, 25 May 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>单体仓库的优势</title>
      <description>[AI 摘要] 单体仓库通过简化组织结构、依赖管理和工具开发，显著提升了大规模软件开发的协同效率和便捷性。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 单体仓库通过简化组织结构、依赖管理和工具开发，显著提升了大规模软件开发的协同效率和便捷性。</div><p>以下是我反复经历的一段对话：</p>

<blockquote>
<p><strong>某人</strong>：你听说了吗？Facebook/Google 使用一个巨型单体仓库。这太疯狂了！<br>
<strong>我</strong>：是的！这样真的非常方便，你不觉得吗？<br>
<strong>某人</strong>：这是我听过最荒谬的事情。FB和Google难道不知道把所有代码放在一个仓库里是个多么糟糕的主意吗？<br>
<strong>我</strong>：我想FB和Google的工程师可能对使用小型仓库也很熟悉（<a href="http://en.wikipedia.org/wiki/Junio_Hamano" rel="noopener noreferrer">Junio Hamano</a>不是在Google工作吗？），但他们出于[某些原因]仍然更喜欢使用一个巨大的单仓库。<br>
<strong>某人</strong>：哦，这听起来确实不错。我仍然觉得这很奇怪，但我能理解为什么有人会想要这样做。</p>
</blockquote>

<p>“[某些原因]”非常冗长，所以我写下来，以避免一次又一次地重复同样的对话。</p>

<p></p>

<h3 id="simplified-organization">简化组织结构</h3>

<p>使用多个仓库时，你通常要么每个仓库一个项目，要么一个相关项目的集合对应一个仓库，但这迫使你为你特定的团队或公司定义什么是“项目”，并且有时纯粹因为管理开销而迫使你拆分和合并仓库。例如，仅仅因为项目太大或版本控制系统的历史记录过多而不得不拆分项目，并不是最优的选择。</p>

<p>使用单体仓库，项目可以按照你认为最符合逻辑一致性的任何方式进行组织和分组，而不仅仅是因为你的版本控制系统强迫你以某种特定的方式组织。使用单一仓库也减少了管理依赖关系的开销。</p>

<p>简化组织结构的一个附带好处是，项目更易于导航。我使用过的单体仓库允许你基本上像在一个网络文件系统上一样进行导航，复用了在项目内部导航的惯用方法。多仓库设置通常有两层导航结构——项目内部使用的文件系统导航，以及一个用于在项目之间导航的元级别。</p>

<p>这个附带好处的另一个附带好处是，在单体仓库中，通常很容易搭建开发环境来运行构建和测试。如果你期望能够通过等效于 <code>cd</code> 的方式在项目之间导航，那么你也会期望能够通过 <code>cd; make</code> 来运行构建。既然这样看起来不工作会很奇怪，那么它通常就能工作，而为了使其工作所需的任何工具开发工作都会完成<sup id="fnref:R"><a href="#fn:R" rel="noopener noreferrer">1</a></sup>。虽然从技术上讲，在多仓库中也可以实现这种便捷性，但这不那么自然，这意味着所需的工作不经常被完成。</p>

<h3 id="simplified-dependencies">简化依赖关系</h3>

<p>这一点可能不言而喻，但在多仓库情况下，你需要某种方式来指定和版本化仓库之间的依赖关系。这听起来应该很简单，但在实践中，大多数解决方案都很笨重并涉及大量开销。</p>

<p>使用单体仓库，很容易为所有项目设置一个统一的版本号。由于可以进行原子性的跨项目提交（虽然在大公司这往往出于实际原因会拆分为多个部分），仓库可以始终保持一致状态——在提交#X，所有项目构建都应该正常工作。依赖关系仍然需要在构建系统中指定，但无论是使用Makefiles还是bazel的BUILD文件，这些文件都可以像其他任何东西一样被提交到版本控制系统中。而且，由于只有一个版本号，Makefiles、BUILD文件或你选择的任何其他构建文件都不需要指定版本号。</p>

<h3 id="tooling">工具支持</h3>

<p>导航和依赖关系的简化使得编写工具变得容易得多。工具不需要理解仓库之间的关系，也不需要理解仓库内部文件的性质，它们基本上只需要能够读取文件（包括一些指定仓库内部单元之间依赖关系的文件格式）。</p>

<p>这听起来是件微不足道的事情，但是，<a href="https://github.com/chrisvana/repobuild/wiki/Motivation" rel="noopener noreferrer">以Christopher Van Arsdale的这个例子</a>来看看构建可以变得多么简单：</p>

<blockquote>
<p><a href="http://bazel.io/" rel="noopener noreferrer">Google内部的构建系统</a>使得使用大型模块化代码块构建软件变得极其容易。你想要一个爬虫？在这里添加几行代码。你需要一个RSS解析器？再添加几行。一个大型分布式、容错的数据存储？当然可以，再添加几行。这些是被许多项目共享的构建块和服务，并且易于集成。
...
这种乐高积木式的开发过程在开源世界中无法如此顺畅地实现。
...
由于这种状况（更多推测），开源领域存在一个复杂性壁垒，在过去几年中没有显著改变。这造成了在像Google这样的公司与开源项目之间可轻松获得成果之间的差距。</p>
</blockquote>

<p>Arsdale提到的这个系统非常方便，在它开源之前，来自<a href="https://facebook.github.io/buck/" rel="noopener noreferrer">Facebook</a>和<a href="https://pantsbuild.github.io/" rel="noopener noreferrer">Twitter</a>的前Google工程师就编写了自己版本的bazel，以获得同样的好处。</p>

<p>理论上可以在没有单体仓库的情况下，创建一个使构建任何东西、处理任何依赖关系都变得简单的构建系统，但这需要付出更多努力，这种努力大到我从未见过一个能无缝完成这件事的系统。Maven和sbt在某种程度上相当不错，但花费大量时间跟踪和解决版本依赖问题是很常见的。像rbenv和virtualenv这样的系统试图绕过这个问题，但它们导致了开发环境的激增。使用一个HEAD始终指向一致且有效版本的单体仓库，彻底消除了跟踪多个仓库版本的问题<sup id="fnref:V"><a href="#fn:V" rel="noopener noreferrer">2</a></sup>。</p>

<p>受益于在单体仓库上运行的不仅仅是构建系统。仅举一例，静态分析可以在没有额外工作的情况下跨项目边界运行。许多其他事情，如跨项目集成测试和<a href="https://github.com/google/codesearch" rel="noopener noreferrer">代码搜索</a>也得到了极大的简化。</p>

<h3 id="cross-project-changes">跨项目变更</h3>

<p>拥有大量仓库时，进行跨仓库变更很痛苦。它通常涉及在每个仓库中进行繁琐的手动协调或使用临时脚本。即使脚本有效，正确更新跨仓库版本依赖关系也有开销。重构一个在数十个活跃内部项目中使用的API，可能就需要花上一天的大部分时间。重构一个在数千个活跃内部项目中使用的API，则几乎是无望的。</p>

<p>使用单体仓库，你只需<a href="http://research.google.com/pubs/pub41342.html" rel="noopener noreferrer">在一次提交中重构API及其所有调用者</a>。这并不总是简单的，但比使用大量小型仓库要容易得多。我见过使用数千次、跨越数百个项目的API被重构，在单体仓库设置下这非常容易，以至于没人会多想。</p>

<p>大多数人现在认为使用像CVS、RCS或ClearCase这样的版本控制系统是荒谬的，因为无法对多个文件进行单次原子提交，迫使人们要么手动查看时间戳和提交消息，要么保留元信息来确定某些特定的跨文件变更是否“真正”是原子的。SVN、hg、git等解决了跨文件原子变更的问题；单体仓库则解决了跨项目的同一问题。</p>

<p>这不仅对大规模API重构有用。曾在Twitter从事将许多仓库迁移到单体仓库工作的David Turner给出了一个小型跨领域变更的例子以及必须为这些变更做发布带来的开销：</p>

<blockquote>
<p>我需要更新[项目A]，但为了做到这一点，我需要我的同事修复它的一个依赖项[项目B]。而我的同事，反过来，需要修复[项目C]。如果我必须等到C发布，然后是B，然后我才能修复并部署A，我可能还在等待。但既然所有东西都在一个仓库里，我的同事可以做出他的更改并提交，然后我就可以立即进行我的更改。</p>

<p>我想如果一切都通过git版本链接，我也可以这样做，但我的同事仍然需要做两次提交。而且总是存在选择一个版本然后“稳定化”（意思是，停滞）的诱惑。如果你只有一个项目，那没问题，但当你有一个相互依赖的项目网络时，这就不太好了。</p>

<p>[从另一个方向看，] 强制<em>依赖方</em>更新实际上是单体仓库的另一个优势。</p>
</blockquote>

<p>不仅仅是进行跨项目变更更容易，跟踪它们也更容易。要在多个仓库中实现等效于 <code>git bisect</code> 的功能，你必须严格使用另一个工具来跟踪元信息，而大多数项目根本不会这样做。即使它们这样做，现在你就有两个真正不同的工具，而一个本应足够的工具即可。</p>

<p>讽刺的是，在某种程度上，随着公司规模扩大，这种优势反而减弱。在规模不算小的Twitter，David Turner从能够发布跨项目变更中获得了很大价值。但在像Google这样规模的公司，大型提交可能大到需要将其拆分为许多较小的提交，这出于各种原因是有意义的，因此需要能够有效地将大型概念原子变更拆分为多个非原子提交的工具。</p>

<h3 id="mercurial-and-git-are-awesome-it-s-true">Mercurial和git确实很棒</h3>

<p>我从这些观点得到的最常见回应是，从CVS或SVN切换到git或hg是生产力的巨大提升。这是事实。但这其中很大一部分是因为git和hg在多个方面更优越（例如，更好的合并），而不是因为小型仓库本身更好。</p>

<p>事实上，Twitter一直在修补git，而<a href="https://code.facebook.com/posts/218678814984400/scaling-mercurial-at-facebook/" rel="noopener noreferrer">Facebook一直在修补Mercurial</a>，以支持巨型单体仓库。</p>

<h3 id="downsides">缺点</h3>

<p>当然，使用单体仓库也有缺点。我不打算讨论它们，因为缺点已经被广泛讨论过了。单体仓库并不严格优于多仓库。它们也并不严格更差。我的观点不是说你应该肯定切换到单体仓库；我只是想说使用单体仓库并非完全不合理，像Google、Facebook、Twitter、Digital Ocean和Etsy这些地方的人们可能更倾向于单体仓库而非数百、数千或数万个较小仓库，是有充分理由的。</p>

<h3 id="other-discussion">其他讨论</h3>

<p><a href="http://gregoryszorc.com/blog/2014/09/09/on-monolithic-repositories/" rel="noopener noreferrer">Gregory</a> <a href="http://gregoryszorc.com/blog/2015/02/17/lost-productivity-due-to-non-unified-repositories/" rel="noopener noreferrer">Szorc</a>。 <a href="https://developers.facebooklive.com/videos/561/big-code-developer-infrastructure-at-facebook-s-scale" rel="noopener noreferrer">Face</a><a href="https://code.facebook.com/posts/218678814984400/scaling-mercurial-at-facebook/" rel="noopener noreferrer">book</a>。 <a href="http://bitquabit.com/post/unorthodocs-abandon-your-dvcs-and-return-to-sanity/" rel="noopener noreferrer">Benjamin Pollack</a>（Kiln的联合创造者之一）。 <a href="https://qafoo.com/resources/presentations/froscon_2015/monorepos.html" rel="noopener noreferrer">Benjamin Eberlei</a>。 <a href="http://blog.rocketpoweredjetpants.com/2015/04/monorepo-one-source-code-repository-to.html" rel="noopener noreferrer">Simon Stewart</a>。 <a href="https://www.digitalocean.com/company/blog/taming-your-go-dependencies/" rel="noopener noreferrer">Digital Ocean</a>。 <a href="http://www.infoq.com/presentations/Development-at-Google" rel="noopener noreferrer">Goo</a><a href="https://www.youtube.com/watch?v=W71BTkUbdqE" rel="noopener noreferrer">gle</a>。 <a href="http://git-merge.com/videos/scaling-git-at-twitter-wilhelm-bierbaum.html" rel="noopener noreferrer">Twitter</a>。 <a href="http://www.reddit.com/r/programming/comments/1unehr/scaling_mercurial_at_facebook/cek9nkq" rel="noopener noreferrer">thedufer</a>。 <a href="http://paulhammant.com/categories.html#trunk_based_development" rel="noopener noreferrer">Paul Hammant</a>。</p>

<p><small>
感谢Kamal Marhubi、David Turner、Leah Hanson、Mindy Preston、Chris Ball、Daniel Espeset、Joe Wilder、Nicolas Grilly、Giovanni Gherdovich、Paul Hammant、Juho Snellman和Simon Thulbourn的评论/修正/讨论。
</small></p>
<div>

<hr>

<ol>
<li id="fn:R">这在我工作过的一家硬件公司也是如此，他们通过使用RCS在NFS上进行版本控制来创建单体仓库。当然你不能让人们在中央仓库中实时编辑文件，所以有人编写了一些脚本，基本上将其变成了类似Perforce的系统。我不推荐这种系统，但即使是一个极其临时搭建的单体仓库，你仍然能获得很多单体仓库的好处。
 <a href="#fnref:R" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:V">至少只要你有一些<a href="https://news.ycombinator.com/item?id=10604168" rel="noopener noreferrer">引入上游依赖</a>的机制。虽然这对Google效果很好，因为Google编写了它依赖的大部分代码，并且拥有足够的员工，因此将所有外部依赖都放入单体仓库的成本在所有员工中摊薄后很低，但我可以想象，对于较小的公司来说，利用这种优势可能成本过高。
 <a href="#fnref:V" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 6780 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/monorepo/</link>
      <guid isPermaLink="false">https://danluu.com/monorepo/</guid>
      <pubDate>Sun, 17 May 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>过去我们常在廉价电力附近建钢厂，如今那里却成了数据中心的选址</title>
      <description>[AI 摘要] 文章分析了数据中心选址靠近廉价电力的趋势，强调计算能耗对基础设施成本的重大影响。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章分析了数据中心选址靠近廉价电力的趋势，强调计算能耗对基础设施成本的重大影响。</div><p>为何如今人们如此关注硬件的功耗？对此的一些常见答案是：<a href="http://www.vox.com/2015/3/9/8178213/apple-macbook-all-batteries" rel="noopener noreferrer">电力对于手机、平板电脑和笔记本电脑至关重要</a>，并且<a href="ftp://ftp.cs.utexas.edu/pub/dburger/papers/ISCA11.pdf" rel="noopener noreferrer">现代芯片上能够集成的硅晶体数量已超过了我们能有效利用的极限</a>。2001年，<a href="http://ieeexplore.ieee.org/xpl/login.jsp?tp=&amp;arnumber=912412&amp;url=http%3A%2F%2Fieeexplore.ieee.org%2Fxpls%2Fabs_all.jsp%3Farnumber%3D912412" rel="noopener noreferrer">Patrick Gelsinger 曾观察到</a>，如果按当时的速度持续微缩，到2005年芯片的功率密度将达到核反应堆的水平，2010年相当于火箭喷嘴，而到2015年则会像太阳表面一样。这暗示着功率密度无法按当时的路径无限增长。尽管这一点在当时已经相当明显，但既然现在已经是2015年，我们可以更确定功率密度并未以无限制的速度持续增长。总之，便携设备的重要性和微缩极限都是合理且重要的原因，但由于它们已被广泛讨论，我将谈一个被低估的因素。</p>

<p>人们常常聚焦于便携市场，因为它在蚕食台式机市场，但这并非唯一的增长市场——服务器也正变得比台式机更为重要，而电力对于服务器至关重要。要理解电力为何对服务器重要，让我们来看一些<a href="http://www.amazon.com/gp/product/012383872X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=012383872X&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=Y6Z2OBCUCR72ALEB" rel="noopener noreferrer">Hennessy 和 Patterson</a> 关于数据中心运营成本的计算。</p>

<p><br></p>

<p>其中一个问题是，你需要为电力支付多次费用。部分电力在变电站就已损耗，尽管这部分可能无需直接付费。接着，在不间断电源中储存能量时会有更多损耗。下面的图表显示损耗为6%，但规模较小的数据中心很容易达到这个数字的两倍。之后，在将电压降至服务器可接受的水平时，会损失更多电力。对于一个相当高效的设备而言，这个损耗超过10%。</p>

<p>在电力进入服务器后，大部分电能会转化为废热。为防止服务器过热，我们需要为冷却它们支付电力。Barroso 和 Holzle 估计，数据中心30%到50%的电力用于冷却机，另有10%到20%用于<a href="http://www.dchuddle.com/2011/crac-v-crah/" rel="noopener noreferrer">计算机房空调系统（CRAC）</a>。这意味着，服务器每消耗一瓦电力，我们就需要额外支付1到2瓦的支持性电力。</p>

<p>要实际获得所有这些电力，我们必须支付将电力引入并分布到整个数据中心所需的基础设施费用。Hennessy 和 Patterson 估算，在一个数据中心的9000万美元总成本中（仅设施部分，不包括服务器），82%与电力和冷却相关<sup id="fnref:E"><a href="#fn:E" rel="noopener noreferrer">1</a></sup>。数据中心的服务器成本估计仅为7000万美元。直接比较这些数字并不公平，因为服务器的更换频率高于数据中心；一旦考虑数据中心整个生命周期的成本，分摊后的电力与冷却成本约占总成本的33%，而服务器的使用寿命为3年，基础设施的使用寿命为10-15年。</p>

<p>如果我们查看所有成本，细分如下：</p>

<p>
</p><table>
<tbody><tr>
<th>类别</th><th>占比 %</th></tr>
<tr>
<td>服务器设备</td><td>53</td></tr>
<tr>
<td>电力与冷却基础设施</td><td>20</td></tr>
<tr>
<td>电力使用</td><td>13</td></tr>
<tr>
<td>网络</td><td>8</td></tr>
<tr>
<td>其他基础设施</td><td>4</td></tr>
<tr>
<td>人力</td><td>2</td></tr>
</tbody></table><p></p>

<p>电力使用和人力是数据中心的运营成本（OPEX），而服务器设备、网络、电力与冷却基础设施以及其他基础设施是资本支出（CAPEX），会在数据中心的生命周期内进行摊销。</p>

<p>计算消耗大量电力。<a href="http://www.datacenterknowledge.com/archives/2010/05/19/microsoft-building-new-data-center-in-quincy/" rel="noopener noreferrer">过去我们常在廉价电力来源附近建造钢厂</a>，但<a href="http://www.datacenterknowledge.com/archives/2010/05/19/microsoft-building-new-data-center-in-quincy/" rel="noopener noreferrer">如今那里却成了数据中心的选址</a>。随着企业开始考虑应用程序的全部成本，我们看到越来越多注重能效的解决方案<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">2</a></sup>。遗憾的是，这非常困难。在软件方面，除了简单的微基准测试示例外，<a href="http://arcade.cs.columbia.edu/energy-oopsla14.pdf" rel="noopener noreferrer">编写高能效代码的最佳实践仍不明确</a>。在硬件方面，英特尔最近发布的新一代芯片显著提升了能效，但其绝对性能相比前代提升不大。在硬件加速器方面，一些大型公司正在为特定计算构建专用的节能硬件。但以现有工具来看，硬件加速器的成本很高，因此专用硬件仅对最大的公司有意义。目前并没有简单的解决方案。</p>

<p><small>
如果您喜欢这篇文章，您可能也会喜欢<a href="http://www.amazon.com/gp/product/012383872X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=012383872X&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=Y6Z2OBCUCR72ALEB" rel="noopener noreferrer">Hennessy 和 Patterson</a> 的第六章，该章不仅探讨了电力成本，还涉及了关于数据中心性能和成本的许多相关估算。</small></p><small>

<p>为这篇匆忙写下的文章致歉。我是在签署一份面试保密协议前匆匆记下这些想法的，当时我预期会了解到一些相关信息，并希望在可能受到保密协议约束的信息影响前记录下自己的想法。</p>

</small><p><small>感谢 Justin Blank 的评论/修正/讨论。
</small></p>
<div>

<hr>

<ol>
<li id="fn:E"><p>尽管这个数字被广泛引用，但我不确定其原始出处。这可能是整篇文章中最值得怀疑的数字。Hennessy 和 Patterson 引用了“Hamilton 2010”，这似乎指向<a href="http://mvdirona.com/jrh/TalksAndPapers/JamesHamilton_GenomicsCloud20100608.pdf" rel="noopener noreferrer">这份演示文稿</a>。该演示文稿并未明确数字来源，但<a href="http://perspectives.mvdirona.com/2008/11/cost-of-power-in-large-scale-data-centers/" rel="noopener noreferrer">Hamilton 的这篇博文</a>确实引用了该数字的出处，然而其引用指向<a href="http://blogs.msdn.com/b/the_power_of_software/archive/2008/09/19/intense-computing-or-in-tents-computing.aspx" rel="noopener noreferrer">这篇博文</a>，而这篇博文似乎是关于将数据中心建在帐篷里的，并未提及基础设施中用于电力和冷却的比例。</p>

<p>其他一些著作，如<a href="http://science.energy.gov/~/media/ascr/ascac/pdf/meetings/mar09/Reed.pdf" rel="noopener noreferrer">这篇</a>，引用了<a href="http://www.electronics-cooling.com/2007/02/in-the-data-center-power-and-cooling-costs-more-than-the-it-equipment-it-supports/" rel="noopener noreferrer">这篇文章</a>。然而，该文章并未直接说明82%这个数字，作者们进行了许多他们自己也承认非常粗略的估算，并使用了诸如“虽然，诚然，作者们承认这个方程式的误差范围很大，但它对于捕捉基础设施成本的量级非常有用。”这样的限定语。
 <a href="#fnref:E" rel="noopener noreferrer"><sup>[返回]</sup></a></p></li>
<li id="fn:1">话虽如此，电力并非一切——Reddi 等人研究了在真实工作负载（MS Bing）中用低功耗芯片替代传统芯片，发现虽然每查询功耗有所改善，但尾部延迟显著增加，尤其是在服务器负载较重时。由于 Bing 有一个机制，当达到延迟阈值时会提前终止与查询相关的计算，因此结果既是更高的延迟，也是更低的搜索质量。
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 5109 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/datacenter-power/</link>
      <guid isPermaLink="false">https://danluu.com/datacenter-power/</guid>
      <pubDate>Mon, 4 May 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>阅读文献引用比多数人想象的要容易</title>
      <description>[AI 摘要] 本文通过多个实例（如邓宁-克鲁格效应、收入与幸福感关系等）揭示常见科学误解的传播，并指出快速查阅原始论文即可有效识别错误。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文通过多个实例（如邓宁-克鲁格效应、收入与幸福感关系等）揭示常见科学误解的传播，并指出快速查阅原始论文即可有效识别错误。</div><p>人们常听说某种观点有“研究”或“科学”支持，但当我查阅原始论文时，数据往往与这些主张相悖。以下是最近遇到的几个例子。</p>

<h2 id="dunning-kruger">邓宁-克鲁格效应</h2>

<p>关于邓宁-克鲁格效应的流行科普版本最常见的是：一个人对某领域了解越少，自认为懂的就越多。另一个版本是：知识匮乏者会高估自己的专业水平，因为无知让他们产生虚幻的认知优势。邓宁和克拉默最初的主张比第一个流行版本弱得多，其证据也比第二个版本薄弱。原始论文篇幅不长，我们通过四幅图表就能把握其核心观点。下图中“感知能力”指主观自评分数，“实际能力”为测试结果。</p>

<p><img src="https://danluu.com/images/dunning-kruger/dunning_1.png" alt="邓宁-克鲁格效应图表" width="333" height="301">
<img src="https://danluu.com/images/dunning-kruger/dunning_2.png" alt="邓宁-克鲁格效应图表" width="334" height="302">
<img src="https://danluu.com/images/dunning-kruger/dunning_3.png" alt="邓宁-克鲁格效应图表" width="335" height="304">
<img src="https://danluu.com/images/dunning-kruger/dunning_4.png" alt="邓宁-克鲁格效应图表" width="331" height="299"></p>

<p>四组数据中有两组显示出感知能力与实际能力的明显正相关——这与流行解读截然相反。至于第二个版本，数据显示顶尖人群同样无法准确自评，因此仅用“能力不足者缺乏自知之明”解释该效应并不充分（尽管论文标题《无能且不自知：为何难以认识到自身缺陷会导致过度自信》容易让人产生这种印象）。图表顶端人群对自身水平的误判呈对称性偏差，这无法被上述解释覆盖。或许存在完全不同的机制，但除非有确凿证据（论文未提供），否则这种解释就显得过度复杂。</p>

<p>感知能力刻度被压缩（尤其在低分段）的合理解释是：极少有人愿将自己评为低于平均或绝对顶尖，这压缩了刻度范围但仍保持近似线性关系。刻度交叉点位于中位数上方，表明人们普遍高估自己——考虑到测试群体（康奈尔大学生可能确实在多数方面优于常人），这并不意外。维基百科关于邓宁-克鲁格效应的条目声称，东亚文化复现研究显示相反结果（感知能力低于实际能力，且能力越高偏差越大），认为该效应可能是美国文化特有的产物。但该引用实际链接至一篇提及东亚信心研究的元分析社论，这可能是另一个错误引用案例。无论如何，“懂得越多越觉得自己无知”的主张显然站不住脚。</p>

<h2 id="income-happiness">收入与幸福感</h2>

<p>“金钱买不到幸福”已成为常识。目前搜索“幸福感 收入”的知识卡片显示，年收入超过7.5万美元后幸福感不再提升。其他热门结果则声称幸福感上限分别出现在1万、3万、4万和7.5万美元区间。</p>

<p><img src="https://danluu.com/images/dunning-kruger/google_happiness.png" alt="谷歌知识卡片显示7.5万美元足以满足任何人" width="603" height="479"></p>

<p>这个结论不仅错误，而且错误性在所有研究国家中具有稳定性。</p>

<p><img src="https://danluu.com/images/dunning-kruger/happiness_within_country.png" alt="收入越高幸福感越强" width="629" height="506"></p>

<p>收入与幸福感的相关性并非来自单一研究。该结论在五轮世界价值观调查（1981-1984、1989-1993、1994-1999、2000-2004、2005-2009）、三轮皮尤全球态度调查（2002、2007、2010）、五轮国际社会调查项目（1991、1998、2001、2007、2008）以及大规模盖洛普调查中均成立。</p>

<p>上图采用对数坐标，若用线性坐标绘制单个国家数据，会呈现类似形态。</p>

<p><img src="https://danluu.com/images/dunning-kruger/happiness_income_linear.gif" alt="幸福感与收入的最佳拟合对数曲线" width="576" height="335"></p>

<p>正如所有对数函数图像，曲线看似即将趋平，导致诸如以下误解：</p>

<p><img src="https://danluu.com/images/dunning-kruger/bad_log_1.png" alt="失真的对数图表" width="291" height="393"></p>

<p>这是某篇宣称“收入不提升幸福感”文章中的真实图表。这类看似趋平的类对数图表非常普遍。想看更多例子，可尝试搜索“幸福感 收入”的图片结果。最夸张的是某张图表中高收入者直接“触顶”——仿佛存在某个货币价值能让人达到人类幸福的理论上限。</p>

<p>与邓宁-克鲁格效应类似，查看论文图表就能明白真相。此处误解之所以盛行，是因为线性坐标易导致误读，但快速浏览原始研究仍能厘清事实。</p>

<h2 id="hedonic-adaptation-happiness">享乐适应与幸福感</h2>

<p>“人们会从挫折（及积极事件）中恢复并回归固定幸福水平”的概念，因丹尼尔·吉尔伯特的畅销书而深入人心。</p>

<p>但即使不查阅适应逆境的相关文献，上一节关于财富的讨论也足以引发质疑：如果人们能从好坏事件中反弹，为何收入增加会提升幸福感？</p>

<p>事实证明，“人们适应负面事件并回归原幸福基准点”的说法是谬论。尽管具体影响因事件而异，但残疾、离婚、丧偶和失业均对幸福感产生长期负面影响。失业是相对容易逆转的事件，但其影响在再就业后依然持续。此处仅引用四项研究，但文献元分析显示该结论具有稳健性。</p>

<p>积极事件同理。尽管“中彩票不提升幸福感”是常识，但实际研究证伪了这一说法。</p>

<p>在这两类案例中，早期横截面研究曾暗示极端事件（如中奖或致残）可能无长期影响。但追踪个体的纵向研究表明，事件确实会影响幸福感。多数结论并非新发现（部分早期研究早于吉尔伯特著作），但基于不够严谨研究的旧结论传播速度仍快于纠正信息。</p>

<h2 id="chess-position-memorization">棋局记忆研究</h2>

<p>我常看到引述称：专家能更好记忆真实棋局，但面对随机排列的棋子时优势完全消失。具体引用通常是蔡斯和西蒙1973年的论文《象棋感知》（该经典文献被引用达7449次），文中指出：</p>

<blockquote>
<p>德格罗特在短期记忆实验中发现大师与弱手存在显著差异：大师仅观察5秒后就能近乎完美地复原棋局，而低于大师级的棋手这项能力急剧下降。这种优势不能归因于大师天生记忆更强——当棋子随机摆放时，大师的复原能力与弱手无异。这表明大师同样受短期记忆容量限制（米勒，1956），其优势在于能感知棋局结构并将棋子组合为组块。例如大师可能记住20个以上棋子位置，但短期记忆仅容纳5个组块，那么每个组块需包含4-5个棋子，并形成单一关系结构。</p>
</blockquote>

<p>论文通过实验“证明”大师级棋手在记忆随机中局时表现甚至不如新手（但真实中局中远优于新手；残局随机化后则与新手相当）。然而该实验样本量极小，每个水平仅测试一名棋手。</p>

<p>快速检索表明该结果在扩大样本后无法复现。例如戈贝特和西蒙在《快速呈现随机棋局的记忆能力与技能水平的关系》中指出：</p>

<blockquote>
<p>广为流传的观点认为，专家对专业领域有意义材料的记忆优势会在面对随机材料时消失。但近期象棋实验复审显示（呈现时间3-10秒），即使面对随机棋局，强手通常仍优于弱手，只是技能差异带来的优势比真实棋局中小很多。本文讨论该发现对棋类专长的启示，并探讨其他领域随机材料的记忆研究。</p>
</blockquote>

<p>他们发现这种优势与技能水平相关：记忆“真实”棋局时，2350+等级分棋手的记忆正确率是1600-2000等级分棋手的约2.2倍，而随机棋局中该比例约为1.6倍（该数据通过目测图表得出，可能存在误差）。1.6倍虽小于2.2倍，但绝非传言中的1:1等同。</p>

<p>我还见过此结论被推广至其他领域，但快速检索显示：其他领域研究要么呈现类似结果（随机化材料中差异缩小但仍存在），要么无法复现。例如麦克基森对程序员的研究发现：记忆“正常”程序时，专家首次尝试的记忆准确率是新手的2.5倍，第六次提升至3倍；记忆“打乱”程序时，专家首次达3倍但第六次降至1.5倍。尽管该结果与蔡斯和西蒙相悖，仍有人引用它来支持相同观点——显然是因为没有仔细阅读麦克基森的原始数据。</p>

<h2 id="type-systems">类型系统</h2>

<p>遗憾的是，虚假研究引用不仅存在于流行科学迷思；软硬件开发领域同样泛滥。例如某位Scala/函数式编程“思想领袖”的评论：</p>

<p><img src="https://danluu.com/images/empirical-pl/pl_godwin.png" alt="推文声称质疑类型系统有效性等同于反疫苗" width="524" height="214"></p>

<p>我每周至少看到类似言论。选择这个例子并非因其特别恶劣，而是因其典型性。关注几位知名函数式编程倡导者的推特，你会发现他们频繁宣称有“大量实证研究”支持类型系统的有效性。</p>

<p>然而，实证研究综述显示证据大多不完整，在非完整性之处也存在矛盾。在所有虚假迷思中，这个最令人费解。其他案例中我能找到合理的误解机制：“弱于预期”变成“相反于预期”；对数曲线看似渐近线；低质初步研究比严谨后续研究传播更快。但类型系统案例中，我找不到证据与信念之间的关联。</p>

<h2 id="is-this-preventable">这可以避免吗？</h2>

<p>我能理解虚假迷思为何能快速传播——即便它们与可靠来源直接矛盾。阅读论文听起来很费时，有时确实如此，但多数情况并非如此。阅读纯数学论文通常耗时，判断实证研究的方法论是否可靠也需投入精力（例如生物统计和计量经济学方法差异大，需熟悉特定领域方法才能把握其适用边界）。但仅为了查明论文主张而阅读实证论文通常很容易。</p>

<p>阅读摘要和结论后，快速浏览论文关键部分（图表、表格、方法缺陷等），在多数案例中就足以判断流行说法是否属实。理想情况下仅需阅读摘要即可，但论文摘要中的主张常比正文更绝对，因此至少需浏览全文。</p>

<p>或许我过于乐观，但我认为虚假迷思泛滥的主要原因是：查阅源头比实际更艰难可怕。典型案例是Quartz发表关于科技行业不存在性别薪酬差距的文章——其引用的多篇文献实际显示相反结论。推特上众人欢呼性别差距消失。当我发布仅引用原文献数据的博文后，许多人士承认原判断错误。他们愿意发推文更正值得赞赏，但据我所知无人真正查阅原始数据（尽管图表和表格已清晰表明Quartz作者并非在引用片面数据，而是引用了直接反驳其论点的文献）。</p>

<p>不幸的是，利己者有动机维持“阅读论文很困难”的假象。例如某流行付费文献综述网站创始人声称“普通人”不可能正确解读研究，需要专家为大众总结。但他不过是个发现阅读总结文献能牟利的连续创业者。更普遍的例子是人们常以“需要特定资质或权威背景”来树立专业壁垒。</p>

<p>某些领域确实需要背景知识才能正确解读研究，但学位涵盖的知识量其实很少，任何人都能通过自学掌握。例如排除实验操作（不包含解读结果的关键知识），我求学期间每学分耗时接近1小时（连续专注状态）。按此换算，我母校的工程学位约需100+小时，非工程学位多数低于40小时，且因共享基础课程（如微积分）存在大量重叠。因他人缺乏学位就否定其解读研究能力是荒谬的——毕竟一周时间就能掌握学位所授知识。</p>

<p><strong>若喜欢本文，你可能还会喜欢<a href="https://danluu.com/discontinuities/" rel="noopener noreferrer">关于异常不连续性的文章</a>、<a href="https://danluu.com/tech-discrimination/" rel="noopener noreferrer">探讨市场对歧视影响复杂性的文章</a>，以及<a href="https://danluu.com/percentile-latency/" rel="noopener noreferrer">剖析常见误解的另一篇文章</a>。</strong></p>

<h3 id="2021-update">2021年更新</h3>

<p>回顾时我认为“类型系统”案例的谜团很简单：这是与其他案例不同的虚假引用。前三例传播的是聪明但错误的反常识观点——人们喜爱聪明的反常识观点，不太在意其正确性，因此这类观点相对更容易病毒式传播。</p>

<p>类型系统案例中，人们只是在伪造证据后诉诸权威。撰写原文时，我困惑于为何有人看完证据后还能提出类型系统倡导者的那些主张。但在阅读数千条相关讨论后，我意识到自己太天真了：这些倡导者根本不会主动查找证据，只会假装证据存在。</p>

<p><small>
感谢Leah Hanson、Lindsey Kuper、Jay Weisskopf、Joe Wilder、Scott Feeney、Noah Ennis、Myk Pono、Heath Borders、Nate Clark和Mateusz Konieczny的评论/修正/讨论。</small></p><small>

</small><p><small>顺便说明，若想告知我明显有误，请确保我确实有误。我通常能从反馈中学到很多，但本文收到的反馈异常差劲。许多人声称我引用的研究已被其他我没读过的研究反驳，但到目前为止，那些“其他研究”我都读过。
</small></p>

<p>



</p>
<div>

<hr>

<ol>
<li id="fn:D">邓宁和克拉默认为高分者高估他人表现（我认为证据不足）。这或许成立，但“能力不足者”也可能低估他人表现。论文试图证明后者不成立，但我认为论证存在多处漏洞。例如其结尾声称“尽管看到同伴的优异表现，后四分之一参与者仍坚信自己表现良好”，但我们不知道参与者是否真认为自己“表现良好”——仅知其感知百分位。人完全可能在感知表现糟糕的同时位居前列（我经常在未认真练习的活动中持有此信念），而邓宁-克鲁格研究中得分较低的参与者很可能对自身水平有类似认知。
 <a href="#fnref:D" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:1"><p><a href="http://www.ncbi.nlm.nih.gov/pubmed/17469954" rel="noopener noreferrer">长期残疾与主观幸福感的持久变化：两项全国代表性纵向研究证据</a></p>

<blockquote>
<p>享乐适应指个体在生活境遇改变后回归幸福基准水平的过程。两项全国代表性面板研究（研究1：N=39,987；研究2：N=27,406）考察了长期残疾后的适应程度。研究1跟踪679名残疾参与者，残疾前平均7.18年，残疾后平均7.39年；研究2跟踪272名参与者，残疾前平均3.48年，残疾后平均5.31年。残疾导致幸福感中度至大幅下降（效应量0.40-1.27标准差），且随时间推移适应程度甚微。</p>
</blockquote>
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:2"><p><a href="http://www.ncbi.nlm.nih.gov/pubmed/16313658" rel="noopener noreferrer">时间并非良药</a></p>

<blockquote>
<p>横截面研究显示离婚者生活满意度低于已婚者，但此类研究无法确定满意度是否因离婚而实际改变。本研究利用3万德国人18年面板数据分析离婚反应与适应，发现满意度在离婚前开始下降，之后逐渐回升，但未恢复至原水平。前瞻性分析还显示，将离婚者在婚前就比保持婚姻者更不快乐。因此离婚与生活满意度的关联源于婚前差异与事件后的持久变化。</p>
</blockquote>
 <a href="#fnref:2" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:3"><p><a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.119.9139&amp;rep=rep1&amp;type=pdf" rel="noopener noreferrer">重审适应与幸福定点模型：对婚姻状态变化的反应</a></p>

<blockquote>
<p>根据适应理论，个体对事件会快速适应并回归主观幸福感基准。作者利用2.4万人15年纵向数据检验此观点：平均而言，个体对事件有反应后确实会向基准适应，但存在显著个体差异。强烈反应者多年后仍远未恢复基准，且许多人呈现与适应理论预测相反的轨迹。因此婚姻转变可能伴随满意度持久变化，但仅看平均趋势会忽略这点。</p>
</blockquote>
 <a href="#fnref:3" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:4"><p><a href="https://ideas.repec.org/p/bru/bruedp/02-16.html" rel="noopener noreferrer">失业改变生活满意度基准点</a></p>

<blockquote>
<p>根据主观幸福感定点理论，人们对事件会有反应但随后回归快乐与满足基准。作者通过2.4万德国人15年纵向数据检验此观点：个体对失业反应强烈后确实向基准回移，但平均而言即使再就业也未完全恢复原满意度。且与适应理论预期相反，有过失业经历者对新失业的负面反应不亚于从未失业者。这表明虽然生活满意度相对稳定，但生活事件能显著影响主观幸福感长期水平。</p>
</blockquote>
 <a href="#fnref:4" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:L"><p>有趣的是，可通过分享的链接观察谨慎者的真实观点。例如斯科特·亚历山大和泰勒·科温都分享过那篇虚假性别差距文章作为“有趣读物”，且倾向分享同观点内容。</p>

<p>若单纯阅读其文章，他们看似在客观呈现世界运行证据。但若观察到他们持续分享支持某种叙事的内容（无论质量），从不分享相反证据，就会发现他们通过链接选择暴露了强烈倾向。</p>
 <a href="#fnref:L" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 11660 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/dunning-kruger/</link>
      <guid isPermaLink="false">https://danluu.com/dunning-kruger/</guid>
      <pubDate>Sun, 29 Mar 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>鉴于我们在测试上投入的精力很少，应该如何测试软件？</title>
      <description>[AI 摘要] 本文讨论软件测试方法，比较硬件和软件测试，并提倡更多自动化和随机测试。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文讨论软件测试方法，比较硬件和软件测试，并提倡更多自动化和随机测试。</div><p>最近我读了很多关于软件测试的文章。来自硬件背景（CPU和硬件加速器），软件测试的不同之处很有趣。软件中的错误更容易修复，因此在测试上少投入精力是有道理的。由于测试投入的精力较少，方法论也不同；软件测试偏向于避免高固定成本的方法，而倾向于高可变成本的方法。但这并不能解释所有的差异，甚至大部分差异。大部分差异来自于文化的<a href="http://en.wikipedia.org/wiki/Path_dependence" rel="noopener noreferrer">路径依赖</a>，这显示了在硬件和软件中测试投入如何非最优地分配。</p>

<p></p>

<p>我对软件测试一无所知，但以下是我从谷歌、一些开源项目以及一些论文和演示中看到的笔记。由于我关注的是软件，我将避免讨论硬件测试如何不是最优的，但我也觉得很有趣。</p>

<h3 id="manual-test-generation">手动测试生成</h3>

<p>根据我所见，大多数软件项目的大部分测试工作来自手写测试。在我所知的硬件项目中，手写测试占用了测试工作的1%到25%，而实际发现的错误所占比例要小得多。手动测试被认为适用于健全性检查，有时也适用于非常棘手的边缘情况，但它不可扩展，效率太低，不能依赖。</p>

<p>确实，有些软件难以进行自动化测试，但我参与过的软件项目几乎完全依赖于手动测试，尽管它们处于最容易使用自动化测试的领域。据我所知，这不是因为有人权衡了利弊并决定手动测试是最佳方式，而是因为他们没有想到有手动测试的替代方案。</p>

<p>在我工作过的硬件公司，我们将程序员称为测试的东西称为“手动测试”或“手动作业”，因为它们是手写的（后者不是双关语，是因为我们将测试提交到作业系统中的“作业”）。软件世界中人们称为“模糊测试”、“基于属性的测试”、“随机测试”等，我们只称之为测试，因为那是默认方式。否则你会如何测试？当然，你可能会让1%的测试编写时间用于手动测试（这意味着实际测试中只有一小部分是手写的，肯定少于0.00000001%），但没人会真正花大量时间手写测试，对吧？</p>

<p>所以，你该怎么做？</p>

<h3 id="random-test-generation">随机测试生成</h3>

<p>好消息是随机测试易于实现。你可以花<a href="https://github.com/danluu/Fuzz.jl" rel="noopener noreferrer">一小时实现一个随机测试生成器并找到数十个错误</a>，或者花更多时间找到<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=jsfunfuzz" rel="noopener noreferrer">数千个错误</a>。</p>

<p>你可以从几乎完全随机且生成极其愚蠢测试的开始。随着你花更多时间，可以添加约束并生成更智能的随机测试，以发现更复杂的错误。这方面的一些好例子是<a href="http://www.squarefree.com/2007/08/02/introducing-jsfunfuzz/" rel="noopener noreferrer">jsfunfuzz</a>，它开始时相对简单，随着时间的推移变得智能；以及Jepsen，它最初检查一些相对简单的约束，现在可以检查线性化。</p>

<p>虽然你可以相当容易地生成随机测试，但编写一个强大的框架或函数集合仍然需要一些时间。幸运的是，这个领域已被现有框架很好地覆盖。</p>

<p>[2026年更新：我在写这篇帖子很久之后才写这个。自从写这篇帖子以来，我曾与几个人坐下来一起编写了一个模糊器。每次尝试都非常有效，人们在学习如何做之后就将这项技能带走并经常使用。关键在于，这非常容易做到，我实际上没有提供任何知识，除了它是可以做到的。如果你拿一个软件并使用任何你所知的知识开始向它发送随机输入，这往往会效果很好。</p>

<p>我还要补充一点，我现在对框架等的积极程度不如以前了。我尝试过几种框架，虽然我看到了它们的优点，但附加值远低于我写这篇帖子时没有太多软件经验时的预期。有各种原因，这些原因相当长，可能应该另写一篇帖子，但我认为最主要的两个原因是：几乎所有我尝试过的测试框架都比我自己编写的要慢得多，因此你失去了很多实际测试能力；另一部分是我没有发现框架在最耗时的测试方面真正节省时间，也没有发现一旦考虑到使用框架带来的执行速度减慢，它们在发现错误方面通常更好。</p>

<p>回顾这篇帖子，我认为这是一个相当大的失败，因为我与人们坐下来不到一小时，就有100%的成功率让他们变得相当擅长测试，而这篇博客文章在做同样的事情时成功率几乎为零。由于博客文章的读者规模，它肯定比我个人帮助了更多人学习如何良好地测试，但考虑到我亲自做这件事多么容易，我绝对没有传达这篇文章的关键见解。]</p>

<h3 id="random-test-generation-framework">随机测试生成，框架</h3>

<p>以下是使用Scott Feeney的<a href="https://github.com/graue/gentest" rel="noopener noreferrer">gentest</a>编写JavaScript测试的简单示例，取自gentest的自述文件。</p>

<p>你想测试类似</p>

<pre><code>function add(x, y) {
  return x + y;
}
</code></pre>

<p>的功能，以检查加法是否可交换，所以你会写</p>

<pre><code>var t = gentest.types;

forAll([t.int, t.int], 'addition is commutative', function(x, y) {
  return add(x, y) === add(y, x);
});
</code></pre>

<p>而不是手动检查值或编写生成值的代码，框架会处理这些，并在你指定约束后生成测试。QuickCheck类生成测试框架通常足够简单，学习使用它们并不比任何其他单元测试或模拟框架更难。</p>

<p>你有时会听到反对意见，认为随机测试只能发现浅层错误，因为随机测试太愚蠢，无法找到真正复杂的错误。首先，这假设你没有指定允许随机生成器生成复杂测试用例的约束。但即使如此，<a href="https://www.usenix.org/conference/osdi14/technical-sessions/presentation/yuan" rel="noopener noreferrer">这篇论文</a>分析了分布式系统中的生产故障，寻找“关键”错误，这些错误要么使整个集群崩溃，要么导致数据损坏，发现58%可以用非常简单的测试捕获。事实证明，生成“浅层”随机测试足以捕获大多数生产错误。而且这是在那些对测试和静态分析异常认真的项目中，这些项目的测试覆盖率远高于平均水平。</p>

<p>朴素随机测试有效性的一个具体例子是John Hughes在<a href="http://www.cse.chalmers.se/edu/year/2012/course/DIT848/files/13-GL-QuickCheck.pdf" rel="noopener noreferrer">这个演讲</a>中讲述的故事。它始于一些人带着问题来找他。</p>

<blockquote>
<p>我们知道dets代码中潜伏着一个错误。过去一年里，我们每隔一个月就会遇到“坏对象”和“过早的文件结束”错误。我们一直无法追踪这个错误，因为dets文件在下次打开时会自动修复。</p>
</blockquote>

<p><img src="https://danluu.com/images/testing/hughes_bug.png" alt="堆栈：应用程序在Mnesia之上，Mnesia在Dets之上，Dets在文件系统之上" width="293" height="487"></p>

<p>一个运行在分布式数据库Mnesia之上的应用程序，不知何故在数据库层下导致了错误。对原因有一些猜测。根据他们看到故障的时间，也许与文件大于1GB时重新哈希有关？但经过一个多月的努力，没人真正确定发生了什么。</p>

<p>不到一天时间，使用QuickCheck，他们发现了五个错误。修复这些错误后，他们再也没有看到这个问题。每个五个错误都可以在只有一条记录的数据库上，最多五次函数调用中重现。通常，具有复杂现实表现的错误，如果你知道在哪里查找，可以用非常简单的测试用例重现。</p>

<p>就开发人员时间而言，在许多情况下，使用某种生成随机测试的框架比手动编写测试是一个巨大的胜利，而且它如此微不足道，几乎没有理由不这样做。使用更高级技术的投资回报率可能值得也可能不值得额外的学习和实施。</p>

<p>虽然愚蠢的随机测试在许多情况下效果很好，但它有局限性。并非所有错误都是浅层的。我知道一家硬件公司非常擅长通过拥有多年或数十年领域知识的人员编写自定义测试生成器来发现深层错误，然后在数千台机器上运行。这效果很好，但需要大量测试工作，远超过几乎所有软件合理范围。</p>

<p>另一个选择是在执行测试生成的程序中构建更多智能。关于如何做的论文数量多得离谱，但很少有论文转化为实用、健壮的软件工具。AFL中使用的简单基于覆盖的测试生成论文不多，但它似乎很有效。</p>

<h3 id="random-test-generation-coverage-based">随机测试生成，基于覆盖</h3>

<p>如果你使用现有框架，基于覆盖的测试并不比使用任何其他类型的随机测试更难。至少在理论上是这样。通常有许多旋钮可以调整不同设置，以及其他复杂性。</p>

<p>如果你在编写框架，有很多决策要做。最重要的是使用什么覆盖度量以及如何使用该度量驱动测试生成。</p>

<p>对于第一个选择，覆盖度量，有些度量是可处理的，但过于简单，如函数覆盖或行覆盖（又名<a href="http://en.wikipedia.org/wiki/Basic_block" rel="noopener noreferrer">基本块</a>覆盖）。跟踪这些很容易，但也很容易在达到100%覆盖时错过非常严重的错误。然后有些度量很棒，但不可处理，如状态覆盖或路径覆盖。如果没有某种魔法来合并等效的路径或状态，对于非平凡程序是不可能跟踪的。</p>

<p>现在，假设我们不使用魔法，而是使用某种近似值。想出在实践中有效的良好近似值通常需要大量试验和错误。幸运的是，Michal Zalewski为<a href="http://lcamtuf.coredump.cx/afl/" rel="noopener noreferrer">AFL</a>试验了多种不同策略，AFL是一个使用覆盖度量来允许工具生成智能测试的测试工具。</p>

<p><a href="http://lcamtuf.coredump.cx/afl/technical_details.txt" rel="noopener noreferrer">AFL执行以下操作</a>。每个分支被注入类似以下内容，近似跟踪基本块之间的边，即哪些分支被采取以及多少次：</p>

<pre><code>cur_location = &lt;UNIQUE_COMPILE_TIME_RANDOM_CONSTANT&gt;;
shared_mem[prev_location ^ cur_location]++;
prev_location = cur_location &gt;&gt; 1;
</code></pre>

<p>shared_mem恰好是AFL中的一个64kB数组，但大小是任意的。</p>

<p>无损版本是将<code>shared_mem</code>设为<code>(prev_location, cur_location) -&gt; int</code>的映射，并递增它。这将跟踪每个边（prev_location, cur_location）在基本块图中被采取的次数。</p>

<p>使用固定大小的数组并对prev_location和cur_location进行异或提供有损压缩。为了避免从琐碎变化中获得太多噪声，例如运行循环1200次与1201次，AFL只考虑一个桶在跨越以下边界之一时发生变化：<code>1, 2, 3, 4, 8, 16, 32, or 128</code>。这是AFL用于确定覆盖的两个之一。</p>

<p>另一个是所有（prev_location, cur_location）元组的全局集合，这使得快速确定元组/转换是否是新的变得容易。</p>

<p>大致来说，AFL保持一个它发现的“有趣”测试用例队列，并生成队列中东西的变异来测试。如果某些东西改变了覆盖统计数据，它会被添加到队列中。还有一些逻辑来避免添加太慢的测试用例，以及移除相对不有趣的测试用例。</p>

<p>AFL大约有13k行代码，所以显然比那复杂得多，但从概念上讲，它相当简单。Zalewksi解释了他为什么保持AFL如此简单<a href="http://lcamtuf.coredump.cx/afl/related_work.txt" rel="noopener noreferrer">在这里</a>。他的评论很短，如果你感兴趣，值得完全阅读，但我还是会摘录一些。</p>

<blockquote>
<p>在过去六年左右的时间里，我也看到了相当多的关于智能模糊测试（主要关注符号执行）的学术论文，以及几篇讨论遗传算法概念验证应用的论文。我不太相信这些实验有多少是实用的
…
毫不费力地从AFL获得与符号执行在同等复杂软件中相当的结果似乎仍然不太可能，而且到目前为止尚未在实践中得到证明。</p>
</blockquote>

<h3 id="test-generation-other-smarts">测试生成，其他智能</h3>

<p>虽然Zalewski是对的，编写一个健壮且可推广的使用更多智能的工具很难，但特定领域的工具可以发挥很大作用。例如，<a href="http://db.cs.berkeley.edu/papers/dbtest12-bloom.pdf" rel="noopener noreferrer">BloomUnit</a>，一个分布式系统的测试框架，通过生成有效排序的子集来帮助你测试非确定性系统，这使用SAT求解器来避免生成等效的重新排序。作者没有以Zalewksi与AFL相同的方式提供基准结果，但即使没有基准，SAT求解器可以有效地应用于测试用例生成至少是合理的。如果没有其他原因，分布式系统测试通常很慢，你可以做很多工作而不会严重影响测试吞吐量。</p>

<p>Zalewski说“如果你的工具使发现错误的可能性增加10倍，但运行速度慢100倍，你的用户就吃亏了。”，这是一个很好的观点——测试智能性的提高必须与测试吞吐量的损失相平衡，但如果你使用像Jepsen这样的测试，其中你的被测程序实际上在需要相互通信的多台机器上运行，测试会慢到你可以在获得10倍或100倍减速之前进行大量计算来生成更智能的测试。</p>

<p>同样的效果使得将智能硬件测试框架移植到软件变得困难。硬件测试“短”几分钟，长测试几小时或几天并不少见。因此，花费大量计算来生成更高效的测试是值得的，但天真地移植智能硬件测试框架<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">1</a></sup>到软件是过度聪明的低效方法。</p>

<h3 id="why-not-coverage-based-unit-testing">为什么不是基于覆盖的单元测试？</h3>

<p>QuickCheck和数十或数百个QuickCheck克隆对于随机单元测试相当有效，AFL在基于覆盖的伪随机端到端测试生成以发现崩溃和安全漏洞方面确实令人惊叹。为什么没有一个工具做基于覆盖的单元测试？</p>

<p>我经常假设如果一个直接的想法没有实现，一定有原因，比如它可能比听起来更难，但<a href="http://www.somerandomidiot.com/" rel="noopener noreferrer">Mindy</a>说服我，有时没有理由某事以前没做过，所以我尝试制作最简单的玩具实现。</p>

<p>在我查看AFL内部之前，我创建了这个真正愚蠢的函数来测试。该函数接受一个任意长度的数组作为输入，并应该返回一个非零int。</p>

<pre><code>// Checks that a number has its bottom bits set
func some_filter(x int) bool {
	for i := 0; i &lt; 16; i = i + 1 {
		if !(x&amp;1 == 1) {
			return false
		}
		x &gt;&gt;= 1
	}
	return true
}

// Takes an array and returns a non-zero int
func dut(a []int) int {
	if len(a) != 4 {
		return 1
	}

	if some_filter(a[0]) {
		if some_filter(a[1]) {
			if some_filter(a[2]) {
				if some_filter(a[3]) {
					return 0 // A bug! We failed to return non-zero!
				}
				return 2
			}
			return 3
		}
		return 4
	}
	return 5
}
</code></pre>

<p>dut代表被测设备，是硬件世界中常用的术语。这段代码是故意设计的，以便基于覆盖的测试生成器容易取得进展。由于代码在每个分支和每个循环迭代中做尽可能少的工作，每次我们做一些额外工作时，覆盖度量就会改变<sup id="fnref:G"><a href="#fn:G" rel="noopener noreferrer">2</a></sup>。事实证明，<a href="http://lcamtuf.blogspot.com/2014/11/pulling-jpegs-out-of-thin-air.html" rel="noopener noreferrer">很多软件都这样，尽管不是故意构建的</a>。</p>

<p>随机测试将很难找到<code>dut</code>错误返回0的情况。即使你设置正确的数组长度，总共64位必须设置为特定值，因此任何特定随机输入命中的失败概率是2^64分之一。</p>

<p>但使用类似AFL模糊算法的测试生成器几乎立即命中这个情况。事实证明，使用合理的初始输入，它甚至在真正进行覆盖引导测试生成之前就找到了失败的测试用例，因为AFL用于生成随机测试的启发式方法生成了一个覆盖此情况的输入。</p>

<p>这就引出了为什么QuickCheck和大多数克隆不使用启发式方法生成随机数的问题。QuickCheck论文提到它使用随机测试，因为它几乎和<a href="http://ieeexplore.ieee.org/xpl/login.jsp?tp=&amp;arnumber=5010257&amp;url=http%3A%2F%2Fieeexplore.ieee.org%2Fxpls%2Fabs_all.jsp%3Farnumber%3D5010257" rel="noopener noreferrer">分区测试</a>一样好，并且更容易实现。这可能是真的，但这并不意味着生成一些使用简单启发式方法的值不能用相同的努力产生更好的结果。由于Zalewski已经<a href="http://lcamtuf.blogspot.com/2014/08/binary-fuzzing-strategies-what-works.html" rel="noopener noreferrer">通过经验确定了哪些启发式方法可能练习更多代码路径</a>，忽略它而只生成完全随机的值似乎是浪费。</p>

<p>是否值得使用覆盖引导生成有点不确定；玩具覆盖单元测试原型可以在一个适合覆盖测试的虚构函数中找到错误，并不能证明任何东西。但这不是重点。重点是看看是否有巨大的障碍应该阻止人们进行覆盖驱动的单元测试。据我所知，没有。</p>

<p>这有助于golang的实现注释良好，并具有操作go代码的良好功能，这使得真的很容易修改其覆盖工具以生成任何你想要的覆盖度量，但大多数语言都有某种覆盖工具，可以被修改以提供适当的覆盖度量，因此对于任何成熟语言来说，这应该不会太痛苦。一旦你有了覆盖数字，生成覆盖引导的测试并不比生成随机的QuickCheck类似测试更难。有些情况很难生成良好的覆盖引导测试，比如生成函数来测试使用高阶函数的函数，但即使在那些情况下，你也不会比使用QuickCheck克隆更糟<sup id="fnref:Q"><a href="#fn:Q" rel="noopener noreferrer">3</a></sup>。</p>

<h3 id="test-time">测试时间</h3>

<p>软件测试可以比硬件测试运行得快得多。这样做的一个副作用是，人们常常宣称所有测试都应在时间限制X内运行，如果没有，那就是错误的。我听过X从100毫秒到5分钟的各种值。无论这些陈述的有效性如何，这种态度的一个副作用是，人们常常认为运行测试生成器几个小时就是“大量测试”。我无意中听到一个评论说某个随机测试工具基本上找到了它能找到的所有错误，因为在大量错误修复后，它运行了几个小时没有发现额外错误。</p>

<p>然后你有硬件公司，它们会分配数千台机器来生成和运行测试。这对软件公司来说可能不合理，但考虑到单台机器的成本与开发人员的成本相比，几乎肯定值得至少分配一台机器来生成和运行测试。对于拥有自己机器或专用云实例的公司，在空闲机器上生成测试几乎是免费的。</p>

<h3 id="attitude">态度</h3>

<p>在“软件测试经验教训”中，作者提到QA不应该期望发现所有错误，QA不应该对发布有否决权，因为不可能捕获大多数重要错误，认为QA会这样做会导致草率。这在我见过的软件团队中是一种相当普遍的态度。但在硬件团队中，期望在最终发布前捕获所有“坏”错误，如果测试不足，QA会否决发布。尽管如此，开发人员相当认真地使东西可测试，避免不必要的复杂性。如果坏错误逃逸（例如，Pentium FDIV错误或Haswell STM错误），会有事后分析来弄清楚测试流程怎么会如此出错，以至于重大错误逃逸。</p>

<p>很难说硬件和软件之间的错误数量差异有多少是态度造成的，有多少是由于投入的测试努力差异造成的，但我认为态度是一个重要因素，除了资源差异。</p>

<p>它影响一切<sup id="fnref:A"><a href="#fn:A" rel="noopener noreferrer">4</a></sup>，直到人们编写什么级别的测试。软件中有很多关注单元测试。在硬件中，人们使用术语单元测试，但它通常指软件中称为集成测试的东西。彻底测试每个单元被认为太难；测试位于清晰API边界上的“单元”（可以是内部或外部）所需的总努力要少得多，因此测试努力集中在那里。</p>

<p>这也驱动了测试生成。如果你接受坏错误会经常发生，手动编写测试是可以的。但如果你的目标是从不发布有坏错误的芯片，手写测试是不可能做到的，所以你会依赖随机测试、手动测试棘手边缘情况和形式方法的某种组合。如果你然后决定你没有资源来避免所有时候的坏错误，并且你必须缩减规模，你将剩下最有效的错误发现方法，这不会给手写测试留下太多空间。</p>

<h3 id="conclusion">结论</h3>

<p>许多项目可以从更多自动化测试中受益。基本上每种语言都有一个类似QuickCheck的框架可用，但大多数适合QuickCheck的项目仍然依赖手动测试。对于除了最小的公司之外的所有公司，至少分配一台机器进行那种测试可能是值得的。</p>

<p>我认为QuickCheck类框架可以从使用覆盖驱动方法中受益。对于接受int数组的函数，这肯定很容易实现，但那也是使用类似AFL测试生成的最简单情况（除了可能的字节数组）。有可能这比我想象的要难得多，但如果是这样，我看不出为什么。</p>

<p>我的背景主要是硬件，所以我可能完全错了！如果你有软件测试背景，我真的很想<a href="https://twitter.com/danluu" rel="noopener noreferrer">听听你的想法</a>。另外，我没有谈到测试涵盖的大多数主题。例如，弄清楚应该测试什么真的很重要！弄清楚哪里可能隐藏着讨厌的错误，以及拥有良好的回归测试设置也很重要。但这些在硬件和软件之间相当相似，所以没有太多比较和对比。</p>

<h3 id="resources">资源</h3>

<p><a href="http://www.exampler.com/testing-com/writings/coverage.pdf" rel="noopener noreferrer">Brian Marick关于代码覆盖以及它如何被误用</a>。</p>

<blockquote>
<p>如果你测试套件的某个部分在覆盖可以检测的方式上薄弱，它可能也在覆盖无法检测的方式上薄弱。</p>
</blockquote>

<p>我习惯以同样的方式思考错误——如果测试生成器花一个月时间在某个区域捕获一个错误，该区域可能还有其他微妙错误，需要在生成器上做更多工作来发现它们。</p>

<p><a href="http://www.amazon.com/gp/product/0122007514/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0122007514&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=DNSHYGZA2USRTHCU" rel="noopener noreferrer">软件测试经验教训：上下文驱动方法，作者Kaner, Bach, &amp; Pettichord</a>。这本书太长无法摘录，但我发现它很有趣，因为它反映了很多传统智慧。</p>

<p><a href="http://lcamtuf.coredump.cx/afl/technical_details.txt" rel="noopener noreferrer">AFL白皮书</a>，<a href="http://lcamtuf.coredump.cx/afl/historical_notes.txt" rel="noopener noreferrer">AFL历史记录</a>，和<a href="http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz" rel="noopener noreferrer">AFL代码tarball</a>。所有这些都非常可读。我花这么多时间研究AFL的原因之一是因为它的文档如此好。另一个原因是，当然，它在各种项目中非常有效地发现错误。</p>

<p><em>更新：Dmitry Vyukov的<a href="https://speakerdeck.com/filosottile/automated-testing-with-go-fuzz" rel="noopener noreferrer">Go-fuzz</a>，看起来是在这篇帖子写完一个月后开始的，使用了这篇帖子概念验证中的方法，将AFL中看到的逻辑与QuickCheck类框架结合，并已证明非常有效。我相信David R. MacIver也计划在<a href="https://hypothesis.readthedocs.org/en/latest/" rel="noopener noreferrer">hypothesis</a>的下一个版本中使用这种方法。</em></p>

<p>这里是我的一些测试相关东西：<a href="//danluu.com/everything-is-broken/" rel="noopener noreferrer">一切都坏了</a>，<a href="//danluu.com/broken-builds/" rel="noopener noreferrer">构建坏了</a>，<a href="//danluu.com/julialang/" rel="noopener noreferrer">julia坏了</a>，和<a href="//danluu.com/new-cpu-features/" rel="noopener noreferrer">使用分析的自动化错误发现</a>。</p>

<h3 id="terminology">术语</h3>

<p>我经常使用术语随机测试，在我习惯于在硬件人员中使用的方式。我可能指的是比大多数软件人员所说的随机测试更广泛的东西。例如，<a href="http://www.sqlite.org/testing.html" rel="noopener noreferrer">sqlite如何描述他们的测试</a>。有一节关于模糊（随机）测试，但它比关于I/O错误测试或OOM测试的节要小得多。但作为硬件人员，我也会将I/O错误测试或OOM测试归为随机测试，因为我会期望使用随机生成的测试来测试它们。</p>

<h4 id="acknowledgments">致谢</h4>

<p><small>
我得到了很多软件人员的极好反馈！感谢Leah Hanson, Mindy Preston, Allison Kaptur, Lindsey Kuper, Jamie Brandon, John Regehr, David Wragg, 和 Scott Feeney提供评论/讨论/反馈。
</small></p>
<div>

<hr>

<ol>
<li id="fn:S"><p>这个脚注是关于一个特定硬件测试框架的完全离题！你可能想跳过这个！</p>

<p>SixthSense在生成智能测试方面做得非常好。它接受一些单元或单元集合（带有断言）、输出上的一些检查以及输入上的一些约束作为输入。如果你不给它任何约束，它假设任何输入都是合法的。</p>

<p>然后它运行一段时间。对于没有“太多”状态的单元，它会找到一个错误或告诉你它形式地证明没有错误。对于有“太多”状态的单元，它仍然擅长发现错误，使用随机模拟和穷举搜索的某种组合。</p>

<p><img src="https://danluu.com/images/testing/sixth_sense_search.png" alt="穷举搜索和随机执行的组合" width="559" height="151"></p>

<p>它可以为状态太多而无法暴力破解的单元发出形式证明。它如何减少状态空间并确定它覆盖了什么？</p>

<p>我基本上不知道。至少有<a href="http://researcher.watson.ibm.com/researcher/view_group_subpage.php?id=2989" rel="noopener noreferrer">三十七篇关于SixthSense的论文</a>。显然，它使用<a href="http://researcher.watson.ibm.com/researcher/view_group_subpage.php?id=2990" rel="noopener noreferrer">组合重写、顺序冗余移除、最小面积时序、顺序重写、输入重新参数化、定位、目标扩大、状态转换折叠、同构属性分解、展开、半正式搜索、符号模拟、使用BDD的SAT求解、归纳、插值等的组合</a>。</p>

<p>据我所知，SixthSense有一个多人团队开发了十多年。考虑到IBM在发现硬件错误方面投入的精力，投资数十或数百人年来创建像SixthSense这样的工具对他们来说是显而易见的胜利，但不清楚任何软件公司是否应该做同样的投资。</p>

<p>此外，SixthSense按软件测试标准来说非常慢。由于模拟硬件的巨大开销，SixthSense实际上比许多简单硬件测试通常运行得快，但在单个单元上运行SixthSense很容易比运行大多数软件项目的所有测试时间更长。</p>
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:G">除其他外，它使用嵌套<code>if</code>语句而不是<code>&amp;&amp;</code>，因为go的覆盖工具不为<code>&amp;&amp;</code>和<code>||</code>创建单独的覆盖点。
 <a href="#fnref:G" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:Q">好吧，你稍微处于劣势，因为生成和查看覆盖统计数据的开销，但对于大多数非平凡程序来说，这相当小。
 <a href="#fnref:Q" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:A"><p>这是另一个长的、可跳过的脚注。这种态度的差异也改变了人们如何尝试编写正确的软件。我曾被软件人员引用“<a href="//danluu.com/tests-v-reason/" rel="noopener noreferrer">测试是绝望地不充分的……它可以非常有效地显示错误的存在，但永远不能显示错误的不存在。</a>”数十次，并伴随一个论点，即我们必须通过推理来避免错误。但大多数硬件人员的态度是，虽然该陈述的后半部分是正确的，但测试（以及在某种程度上形式验证）是确保某物可能没有坏错误的最不坏方式。</p>

<p>这不仅在宏观层面上如此，在微观层面上也是如此。当我在2003年在Micron实习时，我从事闪存工作。我读了“绿皮书”以及一些足够新以至于不在绿皮书中的论文。读完所有这些后，很明显我们（人类）并不理解闪存操作和故障模式背后的所有机制。有关于确切机制细节的合理理论，但证明所有这些仍然是一个开放问题。即使单个闪存比特也超出了人类的理解范围。然而，我们仍然成功地构建了可靠的闪存设备，尽管是用理解不完全的比特构建的，每个比特最终会因某种随机（量子意义上）机制而失效。</p>

<p>工程进展快于人类对底层物理的理解是很常见的。当你处理未被理解的设备并将它们组装成任何人类都无法理解或任何已知技术都无法形式验证的产品时，除了依赖测试别无选择。对于软件，人们常常认为有可能避免依赖测试，因为可能理解整个东西。</p>
 <a href="#fnref:A" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 24088 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/testing/</link>
      <guid isPermaLink="false">https://danluu.com/testing/</guid>
      <pubDate>Tue, 10 Mar 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>当你输入一个网址后会发生什么？</title>
      <description>[AI 摘要] 这篇文章详细探讨了从输入网址到浏览器渲染页面的整个复杂技术链条，涉及硬件、操作系统、网络、协议和软件渲染等多个层面。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 这篇文章详细探讨了从输入网址到浏览器渲染页面的整个复杂技术链条，涉及硬件、操作系统、网络、协议和软件渲染等多个层面。</div><p>最近我经常听到这个问题，每次听到都让我意识到自己有多少不知道的事情。以下是这个问题引发的一些思考。</p>

<p></p>

<ol>
<li>键盘是如何工作的？为什么不能同时按任意三个键，除非是高档的游戏键盘？这暗示了按键检测/编码的某些机制。</li>
<li>按键是如何去抖动的？是某些模拟逻辑，还是键盘里有个微控制器在做这个？薄膜开关是如何工作的？</li>
<li>操作系统如何得知按键事件？对于286处理器我或许能回答，但现在是通过x2APIC实现的，对吧？这又是如何工作的？</li>
<li>另外，USB、PS/2和AT键盘有什么不同？USB是如何工作的？笔记本电脑键盘呢？那是不是只是一个USB连接？</li>
<li>USB连接器是如何工作的？它能处理10Gb/s的数据传输。如果物理连接件之间有任何缝隙，这肯定无法工作。人们如何设计能够承受数万次插拔并仍然保持公差的连接器？</li>
<li>操作系统如何告知程序有事件发生？它怎么知道该与哪个程序通信？</li>
<li>浏览器如何知道要去加载网页？我猜它看到了"http://"，或者假设任何没有前缀的东西都是网址？</li>
<li>假设我们没有缓存网页，因此需要进行DNS查询等操作。</li>
<li>DNS是如何工作的？DNS缓存又是如何工作的？假设在附近任何地方都没有缓存，我们必须去寻找一个遥远的DNS服务器。</li>
<li>TCP？我们建立一个连接？DNS也这样还是必须使用UDP？</li>
<li>操作系统如何决定是否允许一个出站连接？如果有软件防火墙呢？防火墙是如何工作的？</li>
<li>对于TCP，在没有TLS/SSL的情况下，我们可以先慢启动然后遵循一些标准的拥塞协议，对吧？这里面有更深层次的复杂性吗？</li>
<li>再往下一层，网卡是如何工作的？</li>
<li>那么，网卡怎么知道该做什么？是不是有一个我们可以写入的内存区域，网卡可以看到，还是它直接监控总线事务？</li>
<li>好的，假设有一个内存区域。那它是如何工作的？我们如何写入内存？</li>
<li>有些事情发生在CPU/SoC内部！这是我少数了解一些的领域之一，所以我会跳过。最终信号会从一些引脚上输出。那个信号是什么？现在人们使用DDR3，但我们并不总是使用这个协议。大概DDR3让我们比DDR2更快，DDR2又比DDR更快，等等，但为什么？</li>
<li>然后信号最终会进入DRAM模块。与CPU一样，我将主要忽略内部发生了什么，但我很好奇DRAM模块是仍然使用沟槽电容器还是堆叠电容器，或者这项技术已经发展了？</li>
<li>回到我们的网卡，当信号通过网线传出时会发生什么？为什么对于100Mb以太网你需要cat5网线而不是cat3？这纯粹是信号完整性的问题，还是电缆实际上有不同的布线？</li>
<li>在更底层，这些电线肯定足够长，以至于它们可以像传输线/波导一样工作。终端匹配是如何处理的？双绞线足以防止电感耦合，还是有更复杂的处理？</li>
<li>假设我们有一个连接到有线调制解调器的本地以太网连接。有线调制解调器是如何工作的？有线网络在不同用户之间不是某种复用的吗？如何通过单根同轴电缆获得如此大的带宽？</li>
<li>回到上一层，有线连接最终会到达ISP。ISP如何知道将数据路由到哪里？互联网路由是如何工作的？报头中的某些位决定路由？路由表是如何调整的？</li>
<li>另外，8.8.8.8 DNS服务是任播（anycast）的，对吧？这与路由"正常"流量有什么不同？对于通过Cloudflare CDN提供的任何服务也是如此。他们需要做什么来防止路由抖动和其他问题？</li>
<li>是什么让任播（anycast）如此难以实现，以至于很少有公司使用它？</li>
<li>我记得，斯坦福/Coursera的算法课程提到这基本上是一个分布式的Bellman-Ford计算。但如何防止有人发布虚假路由？</li>
<li>如果我们能弄清楚数据包要经过的路径，它们会从我们的ISP出发，经过一些边缘路由器、一些核心路由器、另一个边缘路由器，然后进入他们的网络以到达数据中心的“核心”。</li>
<li>核心路由器和边缘路由器有什么区别？</li>
<li>在某个时刻，我们的连接最终会进入光纤。这是如何发生的？</li>
<li>肯定需要某种激光。什么样的？信号如何调制？是WDM还是TDM？是单模还是多模光纤？</li>
<li>如果是WDM，它如何复用/解复用？在自由空间中使用棱镜会很奇怪，对吧？这就是阵列波导光栅（AWG）可以做的事情。这是实际使用的技术吗？</li>
<li>链路之间必须有中继器。中继器是如何工作的？它们只是增强信号，还是先解码信号以避免传播噪声？如果是后者，中继器之间必须有色散补偿光纤（DCF）。</li>
<li>最简单的情况是只增强信号的设备。掺铒光纤放大器（EDFA）是如何工作的？基本上就是将电流通过掺杂光纤，还是有更深层次的东西？</li>
<li>在更底层，涉及到标准单模光纤和DCF是如何工作的问题。</li>
<li>我们到底为什么需要DCF？我猜组合使用标准光纤和DCF比使用具有非常低色散的光纤更便宜。为什么是这样？</li>
<li>光纤究竟是如何工作的？我的意思是，好吧，它可能是一个使用不同介电材料将光限制在内的波导，但好光纤和坏光纤的区别是什么？</li>
<li>例如，过去几十年间光纤是否发生了变化，严重降低了偏振模色散（PMD）？这是如何实现的？这仅仅是更精确的制造，还是有其他因素参与？</li>
<li>在偏振模色散成为问题并得到解决之前，有几十年的工作致力于提高光纤带宽，大致类似于提高处理器性能的几十年工作，但又完全不同。那项工作是什么？那项工作正在清除哪些障碍？要回答这个问题，你必须真正了解大量的光纤工程知识，而我并不了解。</li>
<li>回到上几层，我们进入一个数据中心。那里有什么？我们的数据包通过交换网络到达机架顶部交换机（TOR）再到机器？可能的交换拓扑是什么样的？<a href="https://code.facebook.com/posts/360346274145943/introducing-data-center-fabric-the-next-generation-facebook-data-center-network/" rel="noopener noreferrer">Facebook的</a>拓扑并非完全出自<a href="http://www.amazon.com/gp/product/0122007514/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0122007514&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=DNSHYGZA2USRTHCU" rel="noopener noreferrer">Dally and Towles</a>的描述，但它正是你可以想象用那种知识构建的某种东西。自Facebook公布其拓扑结构以来，时间还不够长，人们还没来得及复制，但这个想法是否足够明显，以至于你会预期它会被独立地“复制”？</li>
<li>等等，这真的是对的吗？我们应该预期DNS服务器会位于某个数据中心吗？</li>
<li>无论如何，经过这一切，我们的DNS查询解析到一个IP地址。我们建立一个连接，然后呢？</li>
<li>HTTP GET？HTTP 1.0和1.1有什么不同？2.0呢？</li>
<li>然后我们收到一些文件，浏览器必须以某种方式渲染它们。有HTML的请求，也有CSS和js的请求，还有图像的单独请求？这一定很复杂，因为浏览器很复杂。我对此的复杂性一无所知，所以肯定有很多我不知道的东西。</li>
<li>浏览器渲染出内容后，它是如何传送到GPU的，GPU又做了什么？</li>
<li>对于2D图形，我们可能只需要通知操作系统……某些事情。这是如何工作的？</li>
<li>操作系统如何与GPU通信？是否有某种内存映射区域可以让你直接绘制像素，还是更复杂？</li>
<li>LCD显示器是如何工作的？显示器和GPU之间的连接是如何工作的？</li>
<li>VGA可能是最简单的可能性。它是如何工作的？</li>
<li>如果是一个静态网站，我猜我们完成了？</li>
<li>但如果网站有广告，那些东西不是很复杂吗？定向广告和广告拍卖是如何工作的？大量事情如何在200毫秒内发生？</li>
</ol>

<p>在哪里可以找到这些问题的答案<sup id="fnref:E"><a href="#fn:E" rel="noopener noreferrer">1</a></sup>？这不是一个反问！<a href="https://twitter.com/danluu" rel="noopener noreferrer">我真的很有兴趣听听其他资源</a>！</p>

<p><a href="https://github.com/alex/what-happens-when" rel="noopener noreferrer">Alex Gaynor设置了一个GitHub仓库，试图回答这整个问题</a>。它回答了一些问题，并且回答了一些我甚至没想到要问的问题，但它缺少绝大多数这些问题的答案。</p>

<p>对于高层次的答案，这里有<a href="http://www.html5rocks.com/en/tutorials/internals/howbrowserswork/" rel="noopener noreferrer">Tali Garsiel和Paul Irish关于浏览器如何工作的介绍</a>，以及<a href="http://pyvideo.org/video/1677/how-the-internet-works" rel="noopener noreferrer">Jessica McKellar关于互联网如何工作的介绍</a>。关于简单操作系统如何工作，<a href="http://pdos.csail.mit.edu/6.828/2014/xv6.html" rel="noopener noreferrer">Xv6</a>有很好的解释。关于Linux如何工作，<a href="http://duartes.org/gustavo/blog/" rel="noopener noreferrer">Gustavo Duarte在这里有一系列解释</a>。<a href="http://www.linusakesson.net/programming/tty/index.php" rel="noopener noreferrer">关于TTY，Linus Akesson的这篇文章是Duarte博客的一个不错的补充</a>。</p>

<p>再往下一层，<a href="http://www.jmarshall.com/easy/http/" rel="noopener noreferrer">James Marshall对HTTP 1.0和1.1有简洁的解释</a>，<a href="http://www.sans.org/reading-room/whitepapers/protocols/ssl-tls-beginners-guide-1029" rel="noopener noreferrer">SANS有一份关于SSL和TLS的旧但可读的指南</a>。<a href="https://url.spec.whatwg.org/" rel="noopener noreferrer">这个URL规范虽然不是流畅的散文，但它详细解释了URL是什么</a>。</p>

<p>再往下一层，<a href="https://technet.microsoft.com/en-us/library/cc786128.aspx" rel="noopener noreferrer">微软TechNet有关于TCP的解释</a>，其中也包括对UDP的简短解释。</p>

<p>再下一层，<a href="http://www.informit.com/articles/printerfriendly/21320" rel="noopener noreferrer">Kyle Cassidy有以太网快速入门</a>，<a href="http://arstechnica.com/gadgets/2011/07/ethernet-how-does-it-work/" rel="noopener noreferrer">Iljitsch van Beijnum有更详细、更多历史的解释</a>，<a href="http://www.ciscopress.com/articles/printerfriendly/357103" rel="noopener noreferrer">Matthew J Castelli解释了局域网交换机</a>。然后是<a href="http://support.usr.com/support/6000/6000-ug/two.html" rel="noopener noreferrer">DOCSIS和有线调制解调器</a>。<a href="http://www.olson-technology.com/AppNotes/long-haul-communications-systems.pdf" rel="noopener noreferrer">这简要勾勒了长途光纤的设置方式</a>，但肯定有更详细的解释。还有<a href="//danluu.com/new-cpu-features/" rel="noopener noreferrer">现代CPU的简要概述</a>。<a href="http://www.waitingforfriday.com/index.php/C64_VICE_Front-End" rel="noopener noreferrer">针对键盘的具体问题，Simon Inns解释了按键解码以及为什么不能在键盘上按下任意组合的键</a>。</p>

<p>再往下一层，<a href="http://electriciantraining.tpub.com/14182/" rel="noopener noreferrer">这解释了电线是如何工作的</a>，<a href="https://www.nanog.org/meetings/nanog57/presentations/Monday/mon.tutorial.Steenbergen.Optical.39.pdf" rel="noopener noreferrer">Richard A. Steenbergen解释了光纤</a>，<a href="http://www.amazon.com/gp/product/0201543931/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0201543931&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=MXV5K7IJXWXJD446" rel="noopener noreferrer">Pierret</a><a href="http://www.amazon.com/gp/product/013061792X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=013061792X&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=N7YLCANVDPI3M35R" rel="noopener noreferrer">解释了</a>晶体管。</p>

<p>附注：作为一个面试问题，这几乎是<a href="http://sockpuppet.org/blog/2015/03/06/the-hiring-post/" rel="noopener noreferrer">tptacek策略</a>的反面。根据我的观察，我猜tptacek风格的面试作为过滤器比像这样的开放性问题要好得多。</p>

<p><small></small></p><small>

<p>感谢Marek Majkowski, Allison Kaptur, Mindy Preston, Julia Evans, Marie Clemessy 和 Gordon P. Hemsley 提供答案和资源链接！另外，感谢Julia Evans和Sumana Harihareswara说服我把这些问题整理成一篇博客文章。</p>

</small><p><small></small></p>
<div>

<hr>

<ol>
<li id="fn:E">我主要没有列出关于PC内部发生的事情的问题，但我很好奇现代高速总线是如何工作的，以及高速芯片在必须处理将信号传入和传出芯片的巨大电感时是如何工作的。
 <a href="#fnref:E" rel="noopener noreferrer"><sup>[return]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 7288 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/navigate-url/</link>
      <guid isPermaLink="false">https://danluu.com/navigate-url/</guid>
      <pubDate>Sat, 7 Mar 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>古德哈特现象：智商、胆固醇与尾部延迟</title>
      <description>[AI 摘要] 文章通过智商、胆固醇和延迟优化的案例，阐述了将中间目标作为优化重点时，可能导致与最终目标脱节的问题。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章通过智商、胆固醇和延迟优化的案例，阐述了将中间目标作为优化重点时，可能导致与最终目标脱节的问题。</div><p>大多数现实问题都过于复杂，无法直接奔向最终目标，而必须将其分解为更小的部分并设定中间目标。许多游戏也是如此。在象棋中，“赢”这个目标过于庞大，因此你可能需要设置子目标，比如<a href="http://www.chesstactics.org/index.php?Type=page&amp;Action=none&amp;From=2,1,1,1" rel="noopener noreferrer">避免被双攻</a>。虽然设定子目标使棘手的问题变得可解，但它也带来了确定不同子目标相对优先级以及子目标是否与最终目标相关的问题。在象棋中，仅此一项就已有大量书籍进行阐述。</p>

<p>而与许多现实问题相比，象棋其实非常简单。64个方格，32个棋子。你能想到的几乎所有模拟问题都比象棋包含更多状态，许多离散问题也是如此。象棋也相对简单，因为你可以直接衡量是否成功（获胜）。许多现实问题则面临无法直接衡量目标的额外问题。</p>

<h2 id="iq-early-childhood-education">智商与幼儿教育</h2>

<p>1962年，如今被称为佩里学前教育研究的项目在底特律附近的蓝领城镇伊普西兰蒂启动。这是一项随机试验，学生们要么没有接受学前教育，要么接受两年的免费学前教育。两年后，学前班学生的智商分数提高了15分；其他早期教育研究也显示了类似结果。</p>

<p>在20世纪60年代，这些充满希望的早期结果促使了“启蒙计划”的创建，这是一个旨在帮助经济弱势儿童的大规模学前教育计划。启蒙计划的初步结果也很有希望；参与项目的儿童智商提升了10分。</p>

<p>接下来的结果令人失望。到10岁时，试验组和对照组在测试分数和智商上的差异不再具有统计学意义。规模更大的启蒙计划研究也显示了类似结果；<a href="http://eric.ed.gov/?id=ED036321" rel="noopener noreferrer">启蒙计划的首次主要分析</a>的作者总结道：</p>

<blockquote>
<p>（1）暑期项目在情感和认知发展的持久收益方面无效，（2）全年项目在帮助<a href="http://link.springer.com/referenceworkentry/10.1007%2F978-1-4419-1698-3_1718" rel="noopener noreferrer">情感发展</a>方面无效，且仅在产生持久认知收益方面效果甚微，（3）所有启蒙计划儿童在语言发展和学业成就测试中仍远低于全国标准，而一年级入学准备度接近全国标准，（4）启蒙计划儿童的家长对该计划表示强烈认可。因此，虽然全年制启蒙计划略优于暑期计划，但两者都不能说是令人满意的。</p>
</blockquote>

<p>美国的教育并不便宜，这些早期的负面结果导致了削减资金甚至取消该计划的呼声。事实证明，要削减一个旨在帮助弱势儿童的计划的资金是相当困难的，该计划尽管屡遭挫折或被终止的呼声，但仍得以延续。</p>

<p>在最初呼吁关闭启蒙计划很久之后，佩里学前教育研究的长期结果开始陆续公布。成年后，实验组（接受学前教育）的人被捕的可能性更小，入狱时间更少，高中毕业的可能性更高。不幸的是，由于研究设计中的方法论问题，目前尚不完全清楚这些效应来自何处。虽然目标是进行随机试验，但实验设计要求对实验组进行家访。结果，实验组中母亲有工作的儿童与对照组中母亲无业的儿童交换了组别。对学前班的积极影响可能是由母亲在家这一因素造成的。
由于启蒙计划研究不是随机的，并且使用<a href="https://www.aeaweb.org/articles.php?doi=10.1257/jep.15.4.69" rel="noopener noreferrer">工具变量</a>（IV）从“自然实验”中梳理因果关系直到相对较晚才流行起来，因此花了很长时间才从启蒙计划中获得看似可信的因果结果。</p>

<p>工具变量分析的目标是提取因果关系，就像在随机试验中能够做到的那样。一个经典例子是确定让孩子提前或晚一年上学的影响。有些孩子自然会提前或晚一年上学，但有各种各样的因素可能导致这种情况发生，这意味着晚一年上学的孩子在大学参加体育运动的可能性增加，其相关性同样可能来自导致孩子晚一年上学的其他因素，而不是实际上晚一年上学。</p>

<p>然而，出生日期可以用作一个与其他因素不相关的工具变量。对于每个学区，都有一个任意的截止日期，导致一侧的孩子比另一侧的孩子晚一年上学。在假设晚出生一天不会让孩子在大学成为更好的运动员的前提下，你可以观察晚一年上学是否对大学参加体育运动的可能性有因果影响。</p>

<p>现在，回到启蒙计划。一项IV分析利用县际的资金不连续性来产生一个准实验。其思路是，由于一个连续变量的变化，导致了地区间启蒙计划资金水平的离散跳跃，这就给你提供了类似随机试验的东西。跨过县界移动20英尺，对孩子或家庭改变不大，但让孩子进入了一个启蒙计划资金发生显著变化的区域。</p>

<p>其他对启蒙计划的IV分析结果类似。测试分数的改善随时间逐渐消失，但在毕业率（高中和大学）、犯罪率、健康结果以及其他比测试分数更重要的变量上，存在显著的长期影响。</p>

<p>没有单个极具说服力的证据。随机试验存在方法论问题，IV分析几乎总会留下一些悬而未决的问题，但证据的权重表明，尽管标准化测试（包括智商测试）的分数没有被早期教育项目提高，但人们的生活却因早期教育项目而得到了显著改善。然而，如果你看看<a href="http://query.nytimes.com/gst/abstract.html?res=9F07E2DE1F39E63ABC4C52DFB2668382679EDE" rel="noopener noreferrer">早期对启蒙计划等项目的评论</a>，你会发现没有任何承认中间目标（如智商分数）可能无法与生活结果完美相关的迹象。相反，你会看到诸如<a href="http://archives.chicagotribune.com/1969/04/14/page/62/article/head-starts-effect-is-nil-study-shows" rel="noopener noreferrer">“贫困儿童在婴儿期因低社会阶层环境受到严重损害，以至于启蒙计划无法产生多大影响”</a>之类的断言。</p>

<p>这一切的有趣之处在于，众所周知，智商并不能完美地与结果相关。在美国典型家庭所见的环境范围内，智商与你可能关心的结果的相关系数（r值）在0.3到0.4之间。对于社会科学领域的事物来说，这已经是非常强的相关性了，但即使是如此强的相关性，也表明智商并非“大部分”影响实际结果的因素，即使忽略可能的混杂因素。</p>

<h2 id="cholesterol-myocardial-inarction">胆固醇与心肌梗死</h2>

<p>长期以来，人群研究表明胆固醇水平与心脏病发作风险增加之间存在相关性。许多早期研究发现，改善胆固醇水平的生活方式干预也降低了心脏病发作风险。然后他汀类药物被发明出来。与旧药相比，他汀类药物能显著改善胆固醇水平，并对心脏病发作风险产生巨大影响。</p>

<p>在他汀类药物发明之前，标准干预措施是饮食和他汀前药物的组合。这方面有很多文献；<a href="http://www.nejm.org/doi/full/10.1056/NEJM199010183231606" rel="noopener noreferrer">这里有一篇典型的综述</a>，发现在随机试验中，饮食改变和药物的组合对胆固醇水平和心脏病发作风险都有适度影响。</p>

<p>鉴于此，尝试开发改善胆固醇水平的新药当然是合理的，但当辉瑞公司耗资8亿美元这样做，开发torcetrapib时，他们发现这种药<a href="http://en.wikipedia.org/wiki/Torcetrapib" rel="noopener noreferrer">在改善胆固醇水平的同时，大幅增加了心脏病发作风险</a>。霍夫曼-罗氏公司的尝试稍好一些，因为它<a href="http://en.wikipedia.org/wiki/Dalcetrapib" rel="noopener noreferrer">改善了胆固醇但没有导致死亡，但仍然未能降低心脏病发作风险</a>。<a href="http://en.wikipedia.org/?title=Ezetimibe/simvastatin" rel="noopener noreferrer">默克</a>和<a href="http://pipeline.corante.com/archives/2010/03/15/tricors_troubles.php" rel="noopener noreferrer">Tricor</a>也遇到了同样的问题。</p>

<p>发生了什么？一些影响胆固醇水平的干预措施也影响了真实的健康结果，促使人们开发影响胆固醇的药物。但事实证明，改善胆固醇本身并非好事，与许多中间目标一样，可能在<a href="http://pipeline.corante.com/archives/2010/03/15/tricors_troubles.php" rel="noopener noreferrer">不影响最终目标</a>的情况下得到改善。</p>

<h2 id="99-ile-latency-latency">99百分位延迟与延迟</h2>

<p>如今，延迟测量和基准测试相当常见。人们很清楚应用程序延迟性能差会让你损失金钱，因为它会导致人们停止使用该应用程序。同样，人们也明白平均延迟（均值、中位数或众数）本身并不是一个很好的指标。通常使用第99百分位、99.9百分位、99.99百分位等，以捕获有关分布的一些信息，并确保最坏的情况不会太糟。</p>

<p>当你将99百分位延迟作为中间目标时会发生什么？如果要求99百分位延迟低于0.5毫秒，99.99百分位延迟低于5毫秒，你可能会得到一个类似这样的延迟分布。</p>

<p><img src="https://danluu.com/images/percentile-latency/tene_latency.png" alt="在第99百分位、99.9百分位和99.99百分位处有拐点的延迟图表" width="399" height="294"></p>

<p>这是Gil Tene在其<a href="https://www.youtube.com/watch?v=9MKY4KypBzg" rel="noopener noreferrer">关于延迟的演讲</a>中展示的一个实际应用程序的图表。如果你用99百分位、99.9百分位和99.99百分位来设定目标，你会优化系统以勉强达到这些目标。这些优化通常会推动其他延迟发生变化，导致分布看起来很奇怪，在这些点上出现拐点，而其他地方的延迟通常几乎和可能的一样糟糕。</p>

<p>这有点奇怪，但并非有什么恶意。如果你在只看三个数字的情况下尝试一系列优化，你会选择能改善这三个数字的优化，即使它们会使分布的其余部分变得更糟。在这种情况下，延迟在超过99.99百分位后迅速恶化，因为进行优化的人在做更改时，确实不知道他们将99.991百分位变得有多糟。这就像解决电子游戏问题的AI在角色即将被杀死时按下暂停键，因为<a href="http://www.cs.cmu.edu/~tom7/mario/" rel="noopener noreferrer">暂停游戏会阻止其生命值减少</a>。如果你有非常狭窄的优化目标，并且你的测量没有让你看到其他任何情况，那么除了你的优化目标之外，所有其他方面都会被抛在脑后。</p>

<p>由于最终目标通常是改善用户体验，而不仅仅是优化分布上的三个特定点，因此针对几个点而不是某种加权积分，很容易导致反优化，降低实际用户体验，同时产生出色的演示文稿。</p>

<p>除了仅优化99百分位而损害其他方面的问题外，还有一个如何测量99百分位的问题。测量延迟的一种方法（被多个常用的基准测试框架使用）相当于执行以下操作：</p>

<pre><code>for (int i = 0; i &lt; NUM; ++i) {
  auto a = get_time();
  do_operation();
  auto b = get_time();
  measurements[i] = b - a;
}
</code></pre>

<p>如果你优化了该测量的99百分位，你优化的就是所有用户聚在一起决定顺序使用你的应用程序，并协调确保在前一个用户完成之前没有人发起任何请求的情况下的99百分位。</p>

<p>考虑一个人为设定的情况，你测量20秒。在前10秒内，每个响应耗时1毫秒。在接下来的10秒内，系统停顿，因此最后一个请求耗时10秒，导致10,000个测量值为1毫秒，1个测量值为10秒。使用这些测量值，99百分位是1毫秒，99.9百分位也是如此。一切看起来都很棒！</p>

<p>但如果你考虑一个“真实”系统，用户只是随机均匀地提交请求，那么75百分位延迟应 &gt;= 5秒，因为如果在第二半段期间有任何查询到达，它就会被阻塞，平均耗时5秒，最多10秒，此外还会因为请求被卡在其他请求后面而发生排队。</p>

<p>如果这个例子听起来是人为设定的，确实如此；如果你更喜欢一个现实世界的例子，<a href="http://psy-lob-saw.blogspot.com/2015/03/fixing-ycsb-coordinated-omission.html" rel="noopener noreferrer">请参阅Nitsan Wakart的这篇文章</a>，它展示了Yahoo云服务基准测试（YCSB）如何存在这个问题，以及修复前后的分布看起来有多大不同。</p>

<p><img src="https://danluu.com/images/percentile-latency/ycsb_coordinated.png" alt="YCSB测量与真实值之间数量级延迟差异" width="640" height="247"></p>

<p>红线是YCSB声称的延迟。蓝线是Wakart修复协调问题后的延迟情况。原始YCSB测量值和Wakart的修正版本之间存在超过一个数量级的差异。</p>

<p>不仅要考虑整个分布，还要确保你测量的是相关的分布，这一点很重要。真实的用户，无论是点击Web应用程序上某物的人类，还是等待RPC的应用程序，都不会协调以确保他们不提交重叠的请求；他们甚至不会服从均匀随机分布。</p>

<h2 id="conclusion">结论</h2>

<p>这是博客文章中你应该提供解决问题的奇特技巧的点。但唯一的技巧是根本没有技巧，你必须不断检查你的地图（目标）是否以某种方式与领土（现实）相关联<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">1</a></sup>。</p>

<h3 id="resources">资源</h3>

<p><a href="http://aspe.hhs.gov/daltcp/reports/headstar.htm" rel="noopener noreferrer">1990年美国卫生与公众服务部关于启蒙计划的报告</a>。
<a href="http://aspe.hhs.gov/daltcp/reports/headstar.htm" rel="noopener noreferrer">2012年关于启蒙计划证据的综述</a>。</p>

<p><a href="https://www.aeaweb.org/articles.php?doi=10.1257/jep.15.4.69" rel="noopener noreferrer">一篇关于工具变量的简短文章</a>。 <a href="http://www.amazon.com/gp/product/0691120358/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0691120358&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=6WFT6E7PJBSVW47N" rel="noopener noreferrer">一本关于计量经济学和工具变量的书</a>。</p>

<p><a href="https://www.youtube.com/watch?v=XmImGiVuJno" rel="noopener noreferrer">Aysylu Greenberg关于基准测试陷阱的视频</a>；虽非专门针对延迟，但涵盖了多种常见错误。
<a href="https://www.youtube.com/watch?v=9MKY4KypBzg" rel="noopener noreferrer">Gil Tene关于延迟的视频</a>；涵盖的内容比这篇文章多得多。 <a href="http://psy-lob-saw.blogspot.com/2015/02/hdrhistogram-better-latency-capture.html" rel="noopener noreferrer">Nitsan Wakart关于测量延迟的文章</a>；包含代码示例和库链接。</p>

<p><small></small></p><small>

<h3 id="acknowledgments">致谢</h3>

<p>感谢Leah Hanson对此文的详细评论，以及Scott Feeney和Kyle Littler的评论促成了小的修改。</p>

</small><p><small></small></p>
<div>

<hr>

<ol>
<li id="fn:S">除非你还在上学，并且你的教授喜欢给出答案是漂亮、简单数字的问题，也许这个奇特技巧是，如果你得到一个中间答案前面有<code>170/23</code>，你就知道你偏离轨道了。
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 8423 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/percentile-latency/</link>
      <guid isPermaLink="false">https://danluu.com/percentile-latency/</guid>
      <pubDate>Thu, 5 Mar 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>AI无需非常优秀即可取代人类</title>
      <description>[AI 摘要] 本文论证了由于人工服务本就低效且成本高，AI即使性能一般也能在多个领域取代人类岗位。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文论证了由于人工服务本就低效且成本高，AI即使性能一般也能在多个领域取代人类岗位。</div><p>关于“AI”是否最终会好到足以取代人类，以及如果会，何时发生的争论仍在继续。在这场辩论中，乐观主义者倾向于关注AI的进步速度，而悲观主义者则指出AI在所有方面都不如一个理想的人类。我认为这忽略了两个非常重要的因素。</p>

<p>第一，那些处于潜在淘汰边缘的工作，例如一线客户服务、利润率低或不关心客户的行业客服等，往往由冷漠的人类在设计糟糕的系统中担任，而且<a href="https://danluu.com/p95-skill/" rel="noopener noreferrer">人类甚至不擅长他们</a><a href="https://danluu.com/bad-decisions/" rel="noopener noreferrer">非常关心</a>的简单任务。当我们漠不关心时，我们表现得极其糟糕；要达到至少勉强可比的水平，并不需要一个近乎全知的科幻级AI。</p>

<p>第二，即使AI明显更差，如果AI便宜得多，公司也会在许多岗位上用AI取代人类。一个已经发生的例子（尽管该软件可能过于基础，算不上AI）是电话语音导航系统。电话语音导航系统相比被取代的人类客服绝对糟糕得多，但也便宜了几个数量级。尽管一些高利润、高接触的公司不会让你进入语音导航，但在大多数公司，对于寻求客户服务的客户来说，大量的工作时间已被语音导航取代，然后又被语音识别质量很差、甚至比老式按键电话导航更糟的AI语音导航取代。体验并不好，而且当AI进一步自动化流程时，情况可能会变得更糟。</p>

<p>但另一方面，以下是我上周经历的一次不太典型的、由人类处理的客服互动，其表现明显不如一个普通的AI。我预约了一次核磁共振检查。这次检查是针对我的颌骨问题，该问题使得说话很痛苦。我原本希望预约过程能很轻松，这样我就不用花很多时间在电话上交谈。但是，像处理官僚机构时通常发生的那样，过程并不轻松。</p>

<p>以下是经过的步骤。</p>

<p></p>

<ol>
<li>感到颌骨疼痛。</li>
<li>去看牙医。当牙医判断很可能是关节问题时，为我转诊去做核磁共振。</li>
<li>牙医从UW Health（威斯康星大学健康中心）获取转诊表格，按照表格上的指示将其传真过去，并通过电子邮件给我发送了转诊副本。</li>
<li>致电UW Health。</li>
<li>UW Health试图为我预约脑垂体的核磁共振。</li>
<li>请他们确认是否出错。</li>
<li>UW Health再次查看后意识到那是另一个事项的转诊。他们找不到任何关于我的记录。</li>
<li>请UW Health致电牙医解决此事。UW Health声称他们无法拨打电话。</li>
<li>再次联系牙医。请牙医重新传真表格。</li>
<li>再次致电UW Health。请他们再次检查。</li>
<li>UW Health说表格填写不符合规定。</li>
<li>再次请他们致电牙医解决。</li>
<li>UW Health说这不可能。</li>
<li>问为什么。</li>
<li>UW Health说，“出于法律原因”。</li>
<li>意识到这可能是对HIPAA（健康保险隐私与责任法案）法规模糊且毫无根据的担忧。尝试用不同的措辞礼貌地请求他们致电我的牙医。</li>
<li>UW Health同意致电牙医。然后挂断了电话。</li>
<li>查看转诊表格，意识到根据表格上的指示，UW Health外部的人（比如我的牙医）确实无法合法填写该表格。</li>
<li>再次联系牙医。</li>
<li>牙医同意表格无法填写，并致电UW Health协商解决。</li>
<li>致电UW Health查看他们是否收到了表格。</li>
<li>UW Health确认收到了有效的转诊。</li>
<li>请求预约尽可能早的时间。</li>
<li>UW Health不确定他们是否可以接受牙医的转诊。需要去核实。</li>
<li>UW Health确定可以接受牙医的转诊。</li>
<li>UW Health建议在2月17日的一个时间。</li>
<li>我指出我可能无法赴约，因为与UW Health的另一项预约冲突，我知道这个冲突是因为我在UW Health在线系统登录后可以在我的档案中看到它。</li>
<li>UW Health建议在2月18日的一个时间。</li>
<li>我指出了另一个存在于UW Health系统中的冲突。</li>
<li>UW Health开始查看更晚日期的时间。</li>
<li>我询问2月17日是否还有其他可用时间。</li>
<li>UW Health注意到2月17日还有其他时间，并为我预约了当天晚些的时候。</li>
</ol>

<p>我列举这个例子不是因为它是个糟糕的案例，而是因为它具有代表性<sup id="fnref:R"><a href="#fn:R" rel="noopener noreferrer">1</a></sup>。在这个案例中，我的牙医诊所很乐意采取一切必要措施来解决问题，但UW Health拒绝与他们沟通，即使我多次暗示与我的牙医沟通是解决问题的最简单方法。即便如此，我也不确定这有多大帮助。这甚至还不算太糟，因为我设法说服了固执的一方进行合作。更糟糕的情况是双方都拒绝与对方沟通，并都声称只有当对方联系他们时才能解决问题，从而导致僵局。最好的情况是双方都愿意互相沟通并解决任何必要的问题。拥有一个非AI的电话导航或暴露简单调度功能的网页应用，将远远优于这里的人类客服体验。一个基于网页应用将使用的API构建的轻量级AI聊天机器人，会比使用普通网站更差，但仍会优于人类客服。而一个超越轻量级包装的AI聊天机器人，将彻底超越UW Health的这些人类客服员工。</p>

<p>反对用计算机替代人类的论点是，计算机不善于处理错误情况，无法适应不寻常的情况，并且按照机械规则运行，这常常导致荒谬的结果，但这恰恰是我们目前与人类打交道时的处境。感觉就像在和一个计算机程序打交道。不是现代的计算机程序，而是一个80年代的编译器，它只告诉你至少有一个错误，没有其他诊断信息。</p>

<p>UW Health向我的牙医发送了一份指示不可能完成的表格。这不好，但可以理解；错误难免发生。然而，当他们收到表格并发现没有正确填写时，他们没有联系我的牙医，而是直接扔掉了。就像一个80年代的编译器。错误！第二次，他们告诉我表格填写不正确。错误！电话那头本可以有人注意到这份表格根本无法按说明填写。但就像80年代的编译器一样，他们停在第一个错误就不再深思。这最终得到了解决，但我在此过程中得到的错误信息比我对现代程序的预期要差得多。Clang（甚至gcc）给我的错误信息都比这里得到的好得多。</p>

<p>当然，正如我们在healthcare.gov上看到的那样，将交互外包给计算机并不能保证好的结果。<a href="https://danluu.com/tech-discrimination/" rel="noopener noreferrer">有说法称市场解决方案会自动修复任何问题</a>，但这些<a href="https://news.ycombinator.com/item?id=9051049" rel="noopener noreferrer">说法</a><a href="https://news.ycombinator.com/item?id=5523992" rel="noopener noreferrer">并不</a><a href="https://news.ycombinator.com/item?id=5208988" rel="noopener noreferrer">总是</a><a href="https://news.ycombinator.com/item?id=453507" rel="noopener noreferrer">奏效</a><a href="https://news.ycombinator.com/item?id=4216898" rel="noopener noreferrer"></a>。</p>

<p><img src="https://danluu.com/images/customer-service/seeking_googler.png" alt="Seeking AdSense Googler. Need AdSense help. My emails remain unanswered. Are you the special Googler who will help?" width="273" height="148"></p>

<p>那是某人在Facebook上运行了几个月的广告，试图找到一个能帮到他们的Google真人，因为他们所掌握的所有常规技巧都失效了。Google可能拥有世界上最先进的机器学习，他们像任何其他上市公司一样以市场为导向，并且他们大多试图将一级支持等服务岗位自动化，因为支持业务无法规模化。因此，在Google获得支持的最可靠方法是：</p>

<ol>
<li>足够出名，以至于一篇博客文章或推文能引起足够的关注以获得回应。</li>
<li>在Google工作或认识在Google工作的人，并且此人不仅愿意提交内部bug，还能推动确保它得到处理。</li>
</ol>

<p>如果你无法直接使用这两种方法之一，投放广告实际上是一个相当合理的解决方案。（1）和（2）并不总是有效，但它们比不出名却希望博客文章能上HN（Hacker News）有效，也比<a href="https://news.ycombinator.com/item?id=526688" rel="noopener noreferrer">付费客户</a>更有效。这里的目的不是要抨击Google，而是要说明，即使你拥有能击败最强围棋棋手的AI，并且有好几栋楼的人在将同样的技术应用于实际问题，自动化的客服解决方案也并非万无一失。</p>

<p>虽然用计算机替代人类并不总是能创造出极佳的体验，但对于调度和转诊这类事情，基于计算机的优秀系统已经能够比官僚机构中的普通人类表现得好得多<sup id="fnref:L"><a href="#fn:L" rel="noopener noreferrer">2</a></sup>。在正确的设置下，基于计算机的系统在将棘手问题升级给有能力解决的人方面，可能比基于人类的系统做得更好。而且计算机在这方面只会变得更好。会有错误。也会有糟糕的系统。但人类系统中早已存在错误。也早已存在糟糕的人类系统。</p>

<p>我不确定在我的有生之年，技术是否能发展到计算机可以与一个设计精良的系统中有帮助的人类相媲美的程度。但我们已经达到了计算机可以像设计糟糕的系统中冷漠的人类那样有帮助的水平，而这描述了相当一部分服务岗位。</p>

<h3 id="2023-update">2023年更新</h3>

<p>当ChatGPT在2022年发布时，上述2015年描述的辩论再次发生，双方提出了相同的论点。人们再次声称AI（这次是ChatGPT和大型语言模型）无法取代人类，因为优秀的人类优于ChatGPT。他们经常挑选ChatGPT说了一些极其愚蠢的话、“产生幻觉”的例子，但如果你<a href="https://mastodon.social/@danluu/109458271351706514." rel="noopener noreferrer">让人类解释一件事，即使是世界级专家，他们也经常会编造完全虚假的解释</a>。</p>

<p>许多悲观主义者认为大型语言模型需要几十年才能取代人类，理由正是我们在2015年指出的那些是错误的。每个人都在<a href="https://mastodon.social/@danluu/109579276227658596" rel="noopener noreferrer">多个行业由于大型语言模型之前的“AI”自动化而大幅减少所需人类雇员数量之后</a>提出了这个论点，而且这些人中的许多甚至是在公司已经裁员并用大型语言模型替代了人员之后提出这个论点的。<a href="https://mastodon.social/@danluu/109458264830891897" rel="noopener noreferrer">我当时对此进行了评论，使用了与这篇2015年文章相同的推理</a>，后来才意识到<a href="https://mastodon.social/@danluu/109458901726543386" rel="noopener noreferrer">我在2015年已经写下过这条推理思路</a>。但是，请对我宽容一点；我只是一个人，不是计算机，所以我记忆有误。</p>

<p>距离ChatGPT发布已经一年了，那些认为大型语言模型需要很长时间才能取代人类工作的AI悲观主义者，已经被一次又一次的裁员证明是大错特错，客服部门被削减到只剩骨头，大部分被AI取代，而AI客服看起来相当差劲，就像人类客服一样。但人类客服并没有改善，而AI客服在进步。例如，以下是我最近因将汽车送去换机油、调轮胎和做第三件事（说来话长）而经历的一些客服互动：</p>

<ol>
<li>我致电我当地的轮胎店和换油处<sup id="fnref:O"><a href="#fn:O" rel="noopener noreferrer">3</a></sup>，询问他们能否完成我想对我的汽车做的这三件事。</li>
<li>他们说可以。</li>
<li>我问是需要预约还是可以直接过去。</li>
<li>他们说可以，我可以直接过去完成这项工作。</li>
<li>我问能否直接和服务经理谈谈，获取更多信息。</li>
<li>转接给服务经理后，我再次描述了我的需求，并询问我何时可以过去。</li>
<li>他们说这会花很多时间，我需要预约。他们可以安排我在下周去。如果我听信了第一个人的说法，我就会进行一次完全无意义的一小时往返驾驶，因为他们实际上并不能作为临时业务完成我想要的工作。</li>
<li>一周后，我把车开过去，与前台的一个人交谈，他问我需要完成什么。</li>
<li>我描述了所需的服务，并注意到他只记录了我说的大约1/3内容，于是我跟进并</li>
<li>询问他们将使用什么机油。</li>
<li>那人说“我们会用合适的机油”。</li>
<li>我告诉他，我想要0W-20合成机油，因为我的车有一份服务公告指出这是推荐的，这与车标签上的不同，所以能否请他注明这一点。</li>
<li>那人重复“我们会用合适的机油”。</li>
<li>（12）再次，用略有不同的措辞。</li>
<li>（13）再次，用略有不同的措辞。</li>
<li>（12）再次，用略有不同的措辞。</li>
<li>那人说，“都在电脑里，电脑里有正确的机油”。</li>
<li>我问电脑里说要使用什么机油。</li>
<li>那人显得很恼火，走过去操作电脑，调出我的车，告诉我我的车应该使用5W-30机油。</li>
<li>我告诉他，由于服务公告的原因，这不适合我的车，我想要0W-20合成机油。</li>
<li>那人看起来很震惊，说“哦”，然后看着电脑说“哦，它说我们也可以使用0W-20”。</li>
<li>那人在我的车单子上写下了0W-20。</li>
<li>我离开时，预期我要求的第三件事不会被完成或不会彻底完成，因为它并未真正被记录下来。</li>
<li>第二天，我来取车，他们完全没有做第三件事。</li>
</ol>

<p>总体而言，与一个大型语言模型相比如何？它可能比这个表现得像典型的、不想待在那里、不想做任何事的“stoner”（嬉皮士/懒散者）家伙要好得多，而且大型语言模型也会更便宜。然而，大型语言模型会比一个让我能精确预订所需工作并向技师添加备注的网页界面更差。无论好坏，我认为我当地的轮胎/换油店不会很快给我一个不错的网页界面让我精确预订所需工作，所以这个家伙将被大型语言模型取代，而不是一个简单的网页应用。</p>

<h3 id="elsewhere">其他地方</h3>

<ul>
<li><a href="https://srconstantin.github.io/2019/02/25/humans-who-are-not-concentrating.html" rel="noopener noreferrer">Sarah Constantin, 2019年：不专注的人类不是通用智能体</a></li>
<li><a href="https://bsky.app/profile/vickiboykis.com/post/3kewppylblx2g" rel="noopener noreferrer">Vicki Boykis, 2023年：</a>：可能40-50%的大型语言模型用例，如果互联网上排名前10-20%的网站内部搜索引擎好一点的话，就会变得多余。

<ul>
<li><a href="https://bsky.app/profile/hillelwayne.com/post/3kex4cjtjeu2v" rel="noopener noreferrer">Hillel Wayne回应</a>：如果它们有批量搜索功能，这个比例是45-55%。</li>
</ul></li>
</ul>

<p><small>
感谢Leah Hanson和Josiah Irwin的评论/纠正/讨论。
</small></p>
<div>

<hr>

<ol>
<li id="fn:R">至少是我在麦迪逊经历的代表性案例。我在奥斯汀遇到的绝对最坏的情况甚至不如我在麦迪逊看到的中位数情况。情况可能因地区而异（YMMV）。
 <a href="#fnref:R" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:R"><p>我想知道是否有一个扭曲版本的<a href="http://en.wikipedia.org/wiki/Law_of_one_price" rel="noopener noreferrer">一价定律</a>适用，即“客户服务水平一致定律”。一个组织在客户服务方面做得有多好或多差，他们就会创造或购买同等好或差的自动化解决方案。</p>

<p>在Costco，收银员动作快且乐于助人，所以你没有太多理由使用自助结账。但自助结账机往往设计精良；其物理布局减少了处理大量商品所需的时间，并且它们很少出错或死锁，因此没有太多理由不使用它们。在许多其他连锁超市，收银员漠不关心且动作缓慢，除非你提醒他们，否则会出错。在这些地方使用自助结账是有道理的，但自助结账机设计得不是特别好，通常配置得很容易出错，需要不堪重负的收银员进行干预。</p>

<p>同样的事情似乎也发生在自动电话导航系统以及上述两个例子中。Local Health有一个在线系统来自动化客户服务，但他们选择了Epic作为提供商，结果其体验甚至比处理他们的电话支持更糟。而且，如果你是某些Google产品的客户，你有可能找到真人客服，但那个真人<a href="http://successfulsoftware.net/2015/03/04/google-bans-hyperlinks/" rel="noopener noreferrer">通常并不比你本来要处理的</a><a href="https://news.ycombinator.com/item?id=3803568" rel="noopener noreferrer">自动化系统</a><a href="http://zoekeating.tumblr.com/post/108898194009/what-should-i-do-about-youtube" rel="noopener noreferrer">更有帮助</a>。</p>
 <a href="#fnref:L" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:O">顺便说一句，这并不是对我当地轮胎店的贬低。我使用当地轮胎店是因为他们实际上高于平均水平！我也尝试过当地的经销商，还不错但非常昂贵，以及一家广受推荐的独立沃尔沃专业店，情况糟糕得多——他们工作马虎，忽略了重要问题，其他方面也很马虎；他们真的忘记了订购他们打算做的工作的零件（AI可能不会犯这个错误），所以我不得不改天再来完成我汽车的工作！
 <a href="#fnref:O" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 9718 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/customer-service/</link>
      <guid isPermaLink="false">https://danluu.com/customer-service/</guid>
      <pubDate>Sun, 15 Feb 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>CPU后门</title>
      <description>[AI 摘要] 本文探讨了CPU植入后门的技术可行性，包括触发机制、实施途径和潜在风险，指出尽管缺乏实证，但技术上完全可能。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文探讨了CPU植入后门的技术可行性，包括触发机制、实施途径和潜在风险，指出尽管缺乏实证，但技术上完全可能。</div><p>人们普遍认为，任何软件都可能被植入后门。典型案例包括<a href="http://en.wikipedia.org/wiki/Sony_BMG_copy_protection_rootkit_scandal" rel="noopener noreferrer">索尼/BMG安装程序</a>（内建后门允许索尼阻止用户复制CD，但同时也让恶意第三方能控制所有安装了该软件的设备）；<a href="http://redmine.replicant.us/projects/replicant/wiki/SamsungGalaxyBackdoor" rel="noopener noreferrer">三星Galaxy手机</a>（其后门允许调制解调器访问设备文件系统，进而让<a href="https://www.youtube.com/watch?v=RXqQioV_bpo" rel="noopener noreferrer">运行伪基站的人访问设备文件</a>）；<a href="http://www.cypherspace.org/adam/hacks/lotus-nsa-key.html" rel="noopener noreferrer">Lotus Notes</a>（其后门可绕过加密保护）；以及<a href="https://forums.lenovo.com/t5/Lenovo-P-Y-and-Z-series/Lenovo-Pre-instaling-adware-spam-Superfish-powerd-by/td-p/1726839" rel="noopener noreferrer">联想笔记本电脑</a>（通过代理转发所有网络流量[包括使用可信根证书的HTTPS]来推送广告，导致任何拥有相应密钥[该密钥在每台笔记本上分发]的人都能拦截HTTPS流量）。</p>

<p>尽管在<a href="http://www.cl.cam.ac.uk/~sps32/sec_news.html#Assurance" rel="noopener noreferrer">FPGA</a>和<a href="https://github.com/elvanderb/TCP-32764" rel="noopener noreferrer">网络设备</a>中已发现过后门，但每当有人提出CPU后门的可能性时，仍有许多人声称这是<a href="https://news.ycombinator.com/item?id=6147767" rel="noopener noreferrer">不可能的</a>。我并不打算声称CPU后门确实存在，但我要强调：如果拥有适当的权限，植入后门在技术上是容易实现的。</p>

<p>假设你想制造一个后门，该如何实施？这涉及三个部分：被植入后门的CPU能做什么？后门如何被触发？安装后门需要何种程度的妥协？</p>

<p>首先讨论后门的功能。可能性很多，最简单的是实现权限提升：<a href="http://en.wikipedia.org/wiki/Protection_ring" rel="noopener noreferrer">使CPU从用户模式（ring3）跳转到内核模式（ring0）或系统管理模式（SMM）</a>，赋予运行进程内核级权限。由于这是由CPU直接执行，该操作能穿透硬件和软件虚拟化层。虽然可以实现更隐蔽或更具侵入性的操作，但权限提升既足够简单又足够强大，因此我将专注于此。</p>

<p>明确功能后，需要确定触发方式。理想情况下，触发条件应难以被偶然发现或通过暴力破解找到。即便有此限制，可能的触发状态空间仍然极其庞大。</p>

<p>以特定指令<code>fyl2x</code><sup id="fnref:I"><a href="#fn:I" rel="noopener noreferrer">1</a></sup>为例。正常操作中，它接收两个浮点寄存器作为输入，提供<code>2×80=160</code>位来隐藏触发条件。若用特定值对触发后门，很可能能避免随机发现。若担心后门被偶然触发或通过暴力破解疑似后门，可以检查超出常规输入寄存器的范围（毕竟你控制着CPU）。</p>

<p>这种触发机制简单有效，但缺点在于可能需要执行原生代码才能触发（因为Chrome或Firefox不太可能生成<code>fyl2x</code>指令）。可以尝试改用JavaScript引擎容易生成的指令（如<code>fadd</code>）作为触发器。但问题是，若在加法指令中添加检查逻辑，会导致性能明显下降（不过若能修改硬件，则可能实现无开销检测）。或许可以通过修改<a href="http://www.csc.depauw.edu/~bhoward/asmtut/asmtut7.html" rel="noopener noreferrer">重复串操作指令</a>，配合设置特定“密钥”和块复制操作，或使用<code>idiv</code>指令，来创建可通过JavaScript触发且难以检测的后门。另一种可能是利用<a href="http://en.wikipedia.org/wiki/Trusted_Platform_Module" rel="noopener noreferrer">TPM</a>等硬件机制进行隐藏。</p>

<p>现在考虑如何植入后门。在软件领域，可通过修改源代码或<a href="//danluu.com/edit-binary/" rel="noopener noreferrer">二进制文件</a>实现。在硬件领域，如果有权访问源代码，修改起来同样简单。硬件层面的重新编译（即制造物理芯片）具有极高的固定成本；若要将修改植入源代码，需在设计阶段<a href="http://www.cl.cam.ac.uk/~sps32/sec_news.html#Assurance" rel="noopener noreferrer">妥协设计流程</a>，或在制造阶段<a href="http://es.slideshare.net/ortegaalfredo/deep-submicronbackdoorsortegasyscan2014slides" rel="noopener noreferrer">最后时刻混入修改</a>。</p>

<p>若觉得上述方法过于困难，可尝试攻击补丁机制。现代CPU大多内置补丁功能以便后期修复错误。你使用的CPU很可能从出厂起就经过多次补丁更新。补丁机制的细节属于高度机密，通常CPU内置公钥，只接受特定私钥签名的补丁。</p>

<p>这种情况是否真的发生？我不得而知。但完全有可能发生。概率有多大？主要挑战在于技术之外的因素，因此不适合由我来判断。若非要猜测，我认为可能性较低，部分原因是<a href="https://github.com/elvanderb/TCP-32764" rel="noopener noreferrer">攻击其他设备更为容易</a>。</p>

<p><small> 我尚未讨论如何制造即使攻击者获得你使用的软件也难以检测的后门。这更为困难，但随着芯片内置<a href="http://en.wikipedia.org/wiki/Trusted_Platform_Module" rel="noopener noreferrer">TPM</a>，技术上应该可行。</small></p><small>

<p><strong>若喜欢本文，你或许也对<a href="//danluu.com/cpu-bugs/" rel="noopener noreferrer">关于CPU错误的讨论</a>感兴趣，或想了解<a href="//danluu.com/new-cpu-features/" rel="noopener noreferrer">过去35年CPU新特性的发展</a>。</strong></p>

<h4 id="updates">更新</h4>

<p>更多讨论请参见<a href="https://twitter.com/danluu/status/562962211782815746" rel="noopener noreferrer">此推特话题</a>，部分内容总结如下。</p>

<p>因评论过多，不再单独致谢，以下内容综合自@hackerfantastic、Arrigo Triulzi、David Kanter、@solardiz、@4Dgifts、Alfredo Ortega、Marsh Ray和Russ Cox的见解（错误由我本人负责）。</p>

<p><a href="http://www.realworldtech.com/forum/?threadid=35566&amp;curpostid=35566" rel="noopener noreferrer">AMD的K7和K8处理器曾出现微码补丁机制被攻破的情况</a>，允许本文所述的攻击。AMD未对更新进行加密或校验，导致可轻松修改更新文件以实现特定目的。</p>

<p><a href="http://es.slideshare.net/ortegaalfredo/deep-submicronbackdoorsortegasyscan2014slides" rel="noopener noreferrer">以下是Alfredo Ortega为演示目的创建的后门实例</a>。</p>

<p>对于非硬件背景的读者，<a href="http://media.ccc.de/browse/congress/2013/30C3_-_5443_-_en_-_saal_g_-_201312281830_-_introduction_to_processor_design_-_byterazor.html#video" rel="noopener noreferrer">这场关于如何用VHDL实现CPU的演讲很精彩，其中包含后门实现章节</a>。</p>

<p>通过RDRAND提供错误随机数是否可能？答案是肯定的。我在初稿中提到过这点，但删去了，因为印象中人们不信任RDRAND，会将其结果与其他熵源混合。这虽不使后门完全失效，但大幅降低了其价值。</p>

<p>是否可能存储并导出AES-NI密钥？虽难以在不被察觉的情况下向芯片闪存模块，但现代芯片具有逻辑分析器设施，可存储和导出数据。然而访问这些设施需通过特定秘密机制，且难以获得可逆向工程其操作的二进制文件。这与K8的逆向工程形成鲜明对比——后者之所以可行，是因为微码补丁包含在固件更新中。</p>

<p>可通过检查指令前缀实现触发。x86允许在指令上添加冗余（甚至矛盾的）前缀。前缀使用规则明确，可在不超过长度限制下自由添加而不引发问题。但此方法可能导致：若用微码补丁实现可能牺牲性能；前缀数量和长度限制导致有效密钥空间较小（若不跟踪多指令状态）；以及只能用原生代码触发。</p>

<p>据目前所知，这一切仍是推测，尚未有实际CPU后门在野使用的报告。</p>

<h4 id="acknowledgments">致谢</h4>

<p>感谢Leah Hanson提供的详细评论，Aleksey Shipilev和Joe Wilder的建议/修正，以及上述推特讨论中的众多参与者。同时感谢Markus Siemens发现某些RSS阅读器的错误并提供解决方法——虽与本文无直接关联，但恰在此处提及相关。</p>
<div>

<hr>

<ol>
<li id="fn:I"><p>此指令的选择虽非完全任意，但有一定考量。通常会选择既慢又采用微码执行的指令，以便通过微码补丁修改时不会造成显著性能下降。此脚注剩余部分将解释何为微码指令（篇幅较长且非核心内容，建议略过）。</p>

<p>微码指令与硬件实现指令的区别本身具有一定随意性。CPU实现的指令集可视为公共API，其内部可能执行不同的指令集（私有API）。</p>

<p>在现代Intel芯片上，转换为四条或更少微操作（私有API调用）的指令由解码器直接转换；产生更多微操作（五条至数百甚至数千条）的指令则通过微码引擎解码，该引擎从CPU内置的微ROM或RAM中读取微操作。为何以四条为界？这是权衡结果，非根本原则。术语虽未标准化，但业内通常将微码引擎处理解码的指令称为“微码指令”，标准解码器处理的称为“硬件实现指令”。微码引擎本身类似独立CPU，需处理临时寄存器读写（架构不可见）、内部RAM访问（用于需多个寄存器空间的指令）、条件微码分支（改变引擎获取和解码的微码）等操作。</p>

<p>具体实现细节各异且保密。但无论如何实现，可将微码引擎视为启动时加载微码至RAM的部件，随后从中获取并解码微码指令。通过启动时修改加载的微码补丁，可轻松更改执行的微码。</p>

<p>为加快调试周期，Intel很可能也具备强制非微码指令从微码RAM执行的机制，以便通过微码补丁修改。即便没有，攻破微码补丁机制并修改单条微码指令也足以植入后门。</p>
 <a href="#fnref:I" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:D">这些调试触发器大多未公开文档，但可从<a href="http://click.intel.com/the-tick-tock-beat-of-microprocessor-development-at-intel.html" rel="noopener noreferrer">Intel技术期刊第4卷第3期第128页</a>起，了解几代前Intel内置调试触发器的概览。<p></p>
 <a href="#fnref:D" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:A">过去数年，关于大型企业是否被渗透及其可能性的争论持续存在。冷战期间，各方政府机构曾长期在不同层面被渗透，尽管其拥有当今企业无法企及的反制手段（如不雇佣外国公民、“强化审讯技术”等）。我们或许永远无法确知企业是否被渗透，但渗透当今企业比冷战时期渗透政府机构容易得多——而后者曾切实发生。相比冷战时期的渗透行动，获取微码补丁密钥所需的渗透程度轻而易举。<p></p>
 <a href="#fnref:A" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:E"><p>这是另一个关于技术细节的长篇脚注，主要讨论制造过程，建议略过！若决定阅读，请勿抱怨未获提醒。</p>

<p>事实上，在制造完全结束前修改芯片相对容易（设计使然）。为解释原因，需了解芯片制造流程。</p>

<p><img src="https://danluu.com/images/cpu-backdoors/intel_22nm.png" alt="英特尔芯片截面图（22纳米工艺）" width="420" height="440"></p>

<p><a href="http://www.intel.com/content/dam/www/public/us/en/documents/presentation/silicon-technology-leadership-presentation.pdf" rel="noopener noreferrer">芯片截面</a>显示，硅栅位于底层形成逻辑门（如<a href="http://www.nand2tetris.org/" rel="noopener noreferrer">与非门</a>），上方为多层金属层（标记为M1至M8），形成连接不同栅极的导线。制造流程的简化模型是逐层自下而上构建芯片，每层通过沉积材料后光刻蚀刻形成（类似<a href="http://en.wikipedia.org/wiki/Lithography" rel="noopener noreferrer">光刻印刷</a>）。实际流程极其复杂——<a href="https://www.youtube.com/watch?v=NGFhc8R_uO4" rel="noopener noreferrer">Todd Fernendez估算仅创建“M1”以下层就需要约500道工序</a>。此外，所需精度高到刻蚀所用光源会造成设备严重磨损（通常人们不会想到透镜因透光而磨损，但在制造每个晶体管所需的数百道工序中，精度要求使此问题不容忽视）。若觉惊讶，你并不孤单。90年代的<a href="http://www.itrs.net/" rel="noopener noreferrer">ITRS路线图</a>曾预测2016年将实现约30GHz主频（越高越好）、9纳米制程（越小越好）及约300瓦功耗。而现状是5GHz已属高速，非英特尔厂商能在2016年初实现14纳米制程高良率量产已属幸运。芯片制造比任何人预想的都困难。</p>

<p>现代芯片层数众多，从开始到完成约需三个月。这使得缺陷修复代价高昂——若需修改底层则需三个月制造周期。为缩短修复周期，通常会在硅片上分布未使用的逻辑门，以便通过修改接近顶层的少数层实现小幅修复。由于芯片采用流水线制造，任何时刻都有部分完成的批次。若仅需编辑顶层金属层，可对半成品应用修改，将周期从数月缩短至数周。</p>

<p>鉴于芯片设计便于修改，有权在制造前访问设计的人员（如制造商）能以相对较小的修改实现重大变更。我猜想若向主要CPU公司的人员提出此观点，他们会声称其不可能实现，因为会在特性测试或时序路径优化等环节被发现。人们期望如此，但<a href="http://www.cl.cam.ac.uk/~sps32/sec_news.html#Assurance" rel="noopener noreferrer">实际硬件设备曾以后门形态出货</a>，而要么无人察觉，要么存在共谋。</p>
 <a href="#fnref:E" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div></small><p><em>由 mimo-v2.5 模型翻译，花费 8199 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/cpu-backdoors/</link>
      <guid isPermaLink="false">https://danluu.com/cpu-backdoors/</guid>
      <pubDate>Tue, 3 Feb 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>博客盈利</title>
      <description>[AI 摘要] 作者权衡了在个人博客上投放广告的利弊，认为收入有限且可能损害流量和用户体验，但最终仍尝试投放以获取数据，后因支付问题移除，并探讨了其他变现方式。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 作者权衡了在个人博客上投放广告的利弊，认为收入有限且可能损害流量和用户体验，但最终仍尝试投放以获取数据，后因支付问题移除，并探讨了其他变现方式。</div><p>在我的博客上投放广告有意义吗？我最近一直在思考这个问题，因为Carbon Ads联系我，想在上面放广告。利弊有哪些？这不是反问句。我真的<a href="https://twitter.com/danluu" rel="noopener noreferrer">很想知道你的看法</a>。</p>

<p></p>

<h3 id="pros">优点</h3>

<h4 id="money">金钱</h4>

<p>嘿，谁能不需要更多的钱呢？而且这基本上是白得的钱。嗯，除了所有那些缺点。</p>

<h4 id="data">数据</h4>

<p>有很多关于广告对网站使用和行为影响的研究。但与任何基准测试一样，如果你对这个领域没有深入的理解，就不太清楚这些结论如何或者是否适用于其他网站，而我对此几乎一无所知。如果我投放一些广告并做一些A/B测试，我就能看看对我的网站有什么影响，这会很有趣。</p>

<h3 id="cons">缺点</h3>

<h4 id="money-1">金钱</h4>

<p>这点钱不足以维持生计，而且永远不够。当Carbon联系我时，他们问我过去30天的流量。当时，Google Analytics显示有118k会话，94k用户，143k页面浏览量。Cloudflare显示的流量通常高出20%，因为有20%的人屏蔽了Google Analytics，但这20%加上更多的人可能会屏蔽广告，所以“真实”的数字在这里没用。我告诉了他们这些，但也说明那些数字相当反常，我预计平均流量会少得多。</p>

<p>这值多少钱？我不知道他们给我的CPM（每千次展示成本）数字是否保密，所以我就用目前1美元CPM的标准数字。如果我的流量持续保持在那个水平，那就是每月143美元，或每年1700美元。好吧，这还不错。</p>

<p><img src="https://danluu.com/images/blog-ads/ga_traffic.png" alt="此博客的流量分布。总计约50万次点击。" width="640" height="142"></p>

<p>但我们看看我开始写这个博客以来的流量。我在一篇帖子被Hacker News和Reddit广泛传播后才添加分析，所以这不是我全部的流量，但也差不多。</p>

<p>首先，30天内143k的点击量似乎是个例外。我从未有过日历月份的流量达到那么多。我只是恰好在特定的30天内遇到了一堆流量。</p>

<p>此外，如果我停止写博客，就像我4月到10月那样，我的流量水平几乎降到零。即使我继续写博客，我的“自然”流量水平是多少也不太清楚。是暂停前的水平正常，还是暂停后的水平正常？无论哪种情况，每月143美元似乎是合理估计的上限。我可能会超过这个数，但我对此表示怀疑。</p>

<p>为了设定一个绝对的上限，我们来看看阅读量最广的编程博客之一——Coding Horror。Jeff Atwood很贴心地公开了他的流量统计。谢谢你，Jeff！</p>

<p><img src="https://danluu.com/images/blog-ads/ch_traffic.png" alt="Coding Horror的流量分布。单月峰值170万次点击。" width="592" height="252"></p>

<p>他在最好的月份获得了170万次点击，即使在他经常写博客的时期，125万次对他来说也不算差。按现在的CPM费率，他在高峰期每月能赚1700美元，普通月份也能赚1250美元。</p>

<p>但Jeff Atwood写的是大众感兴趣的编程话题，比如<a href="http://blog.codinghorror.com/the-future-of-markdown/" rel="noopener noreferrer">Markdown</a>和<a href="http://blog.codinghorror.com/why-ruby/" rel="noopener noreferrer">Ruby</a>，而我写的是冷门的东西，比如<a href="//danluu.com/clwb-pcommit/" rel="noopener noreferrer">为什么英特尔可能想添加新指令来加速非易失性存储</a>，偶尔来个<a href="//danluu.com/empirical-pl/" rel="noopener noreferrer">文献综述</a>换换口味。我绝不可能获得像写更大众话题的人那样的流量；即使我能达到他们一半的流量我都会感到惊讶，所以每月600美元似乎是可持续收入绝对难以达到的上限。</p>

<p>这并不算差。交税后，这大约能覆盖我在奥斯汀的房租，如果有室友的话，还能覆盖房租+水电费和其他开支。但这是极其乐观的成功率，结果只是在你勉强支付房租的时候，而编程就业市场正热，大公司中级职位的总薪酬是美国中位数收入的8-9倍。这并不好。</p>

<p>更糟的是，情况还在恶化。自90年代以来，CPM已经下降了大约5倍，并且还在持续下降。与此同时，使用广告拦截器的人比例也在不断增加。</p>

<p>优质广告可以获得超过一个数量级更高的CPM，<a href="http://daringfireball.net/feeds/sponsors/" rel="noopener noreferrer">赞助可以带来更好的回报</a>，所以情况可能不像我描述的那么悲观。但要获得优质广告，你需要吸引特定的广告商。哪些广告商对主要由对底层技术感兴趣的程序员组成的受众感兴趣？我不知道，而且我怀疑除非我能达到Jeff Atwood级别的流量，否则不值得费心去了解，而我觉得这不太可能。</p>

<h5 id="a-tangent-on-alexa-rankings">题外话：关于Alexa排名</h5>

<p>Alexa怎么回事？为什么这么多人把它当作黄金标准？理论上，它应该显示一个网站在过去三个月的受欢迎程度。根据Alexa，Coding Horror排名22k，我排名162k。据我所知，流量与排名的关系是超线性的，所以你预计Coding Horror的流量会远超过我的7倍。但如果你比较Jeff和我过去三个月（10月21日至1月21日）的统计数据，statcounter声称他有78k次点击，而我有298k次。即使你假设流量与Alexa排名仅仅是线性关系，直接测量结果与Alexa估算结果之间也存在28倍的相对流量差异。</p>

<p>我并非声称我的博客在任何有意义的层面上更受欢迎——如果Jeff在过去三个月里像我一样频繁发帖，我敢肯定他的流量至少是我的10倍。但考虑到Jeff现在大部分时间花在非博客活动上，而且他的博客在很少更新的情况下流量也达到现有水平，我们网站的Alexa排名似乎偏差很大。</p>

<p>此外，Alexa的子指标不一致且毫无意义。看看这张关于用户从家、学校或工作场所访问此网站比例的图表。</p>

<p><img src="https://danluu.com/images/blog-ads/alexa_below_average.png" alt="在所有方面都低于平均水平！" width="249" height="138"></p>

<p>它在所有类别中都低于平均水平，对于这种相对排名来说应该是不可能的。但即使是数学上的不可能也阻止不了Alexa！</p>

<h4 id="traffic">流量</h4>

<p>广告会减少流量。减少多少取决于网站和广告本身。我也许以后会做个文献综述，但现在我只链接一个由<a href="http://www.decisionsciencenews.com/2015/01/02/annoying-animated-ads-may-cost-worth-websites/" rel="noopener noreferrer">Daniel G. Goldstein, Siddharth Suri, R. Preston McAfee, Matthew Ekstrand-Abueg, 和 Fernando Diaz</a>提供的单个研究结果，试图量化其代价。</p>

<p>我的观点并不是说某个特定研究适用于在我的网站上添加一个广告，而是众所周知，添加广告会减少流量，并对长期用户行为产生一定影响，这是有一定代价的。</p>

<p>如果像上面那项研究一样，比较“烦人”广告和“好”广告来量化“烦人”广告的代价，相对容易。但对于一个个人博客来说，其基线收益是非金钱的，量化起来就更难了。</p>

<p>无论如何，我从这个博客中得到了什么？我能看到的主要好处是，我遇到并定期与一些很棒的人交流，如果没有博客我可能不会认识他们；我经常能获得关于我想法的良好反馈；偶尔有人因为看到我相关的帖子而联系我，提供听起来有趣的工作机会。</p>

<p>我怀疑我能否有效估计我会失去多少流量，即使我能，我也怀疑我能否弄清楚那与我从写博客中获得的价值之间的关系。我的直觉是这个价值“很大”，而金钱回报可能“不多”，但在边际情况下这意味着什么并不清楚。</p>

<h4 id="incentives">激励</h4>

<p>人们会受到金钱的影响，即使他们自己没有注意到。我也是人。我可能会为了增加一点收入而做点什么，即使金额很小，我也不会有意识地花很多精力去优化来每月多赚5美元。</p>

<p>这在这里意味着什么？也许我会写更多博客文章？当我尝试更频繁地、不加过多编辑地发布博客文章时，我得到了一致的积极反馈，所以也许被激励写更多文章并不是坏事。但我总是担心无意识的偏见，我想知道投放广告还会对我产生什么其他影响。</p>

<h4 id="privacy">隐私</h4>

<p>广告网络可以通过广告跟踪人们。我的印象是，人们主要担心的是那些拥有足够信息、如果愿意就可以反匿名化用户的大公司，比如谷歌和Facebook，但有些人可能也担心像Carbon这样的小广告网络。顺便说一下，我很好奇像Tapad和MediaMath这样试图大量跟踪用户的公司，是否真的比雅虎和eBay这样更知名的公司拥有更多关于人们的数据。不过，我怀疑这类数据不会公开。</p>

<h4 id="paypal">Paypal</h4>

<p>这具体到Carbon，他们通过PayPal付款，PayPal以冻结资金六个月而臭名昭著，如果你赚到足够多、你真正想要这笔钱的时候，或者由于<a href="http://www.jacquesmattheij.com/PayPal+robbed+my+bank+account" rel="noopener noreferrer">文书错误随机抽干你的银行账户</a>。到目前为止，我一直设法避免将我的PayPal账户连接到我的银行账户，但如果我最终赚到足够多、想要提现的钱，我将不得不这样做，或者通过中间人取钱。</p>

<h3 id="conclusion">结论</h3>

<p>投放广告值得吗？我不知道。如果我必须猜，我会说不。但我还是会尝试，因为我好奇数据是什么样子，如果我不尝试任何事情就永远看不到数据，但即使有了这些数据，也不会告诉我这是否值得。</p>

<p>充其量，我能看到有广告和无广告时博客点击率的差异。这个博客主要靠口碑传播，所以我真正想看到的是博客被他人分享的速率差异，但我看不到好的方法来衡量这一点。我可以尝试按月全局启用或禁用广告，但月份之间的差异太大了，即使我做几年，我也不知道是否能得到好的数据。</p>

<p><i>感谢Anja Boskovic的评论/修正/讨论。</i></p>

<h4 id="update">更新</h4>

<p>投放广告一段时间后，看起来大约40%的流量使用了广告拦截器（而大约17%的流量屏蔽了Google Analytics）。我不知道这个数字是太高还是太低，应该感到惊讶。一方面，40%很多！另一方面，尽管有人抱怨广告拦截器会拖慢浏览器速度，但我的经验是，使用正确的广告拦截器屏蔽广告时，网页加载速度快得多，我没有理由不使用广告拦截器。我预计我的大部分流量来自程序员，他们都知道可以拦截广告。</p>

<p>有观点认为拦截广告是盗版和/或偷窃，但我从未听到过令人信服的论证。如果有的话，我认为提出这种观点的一些人越界了，比如Ars Technica屏蔽了使用广告拦截器的人，然后又退缩了，只是敦促人们为其网站禁用广告拦截。我认为大多数人都会同意，直接敦促人们点击广告并进行点击欺诈是不道德的；要求人们禁用广告拦截只是程度上的不同，本质上没有区别。使用广告拦截器的人点击广告的可能性要低得多，让他们禁用广告拦截器以产生不太可能转化的展示，在我看来，与让对产品不感兴趣的人产生点击非常相似。</p>

<p>总之，在他们第一次付款后未能发送付款后，我最终移除了这则广告。据传AdSense会等到付款前一刻才切断用户，以获取尽可能多的免费展示，但AdSense至少会通知你。Carbon却在继续运行广告的同时，不声不响地停止了付款。我可能可以问问Carbon或BuySellAds的某个人，但考虑到这则广告价值很小，真的不值得费那个劲去问。</p>

<h4 id="update-2">更新 2</h4>

<p>距离我说博客流量永远不足以支付生活费用已经快两年了。事实证明那不对！我的推理是我主要倾向于写关于底层技术的话题，这些不可能产生足够的流量来获得“真正的”广告收入。这个理由至今仍然成立，但我的博客现在大约一半是底层技术内容，一半是针对程序员的通用兴趣话题。</p>

<p><img src="https://danluu.com/images/blog-ads/2016-month.png" alt="此博客2016年一个月的流量。大约310万次点击。" width="1856" height="878"></p>

<p>这是我过去30天（截至2016年10月25日）的流量图表。由于这是Cloudflare的请求图，对大多数网站来说会严重高估流量，因为每个图片和CSS文件都算一个请求。然而，由于我的绝大多数流量都流向没有外部CSS和图片的页面，这相当接近我实际的流量水平。15%的请求是图片，10%是RSS（我不计算这个，因为RSS点击率很难与实际阅读人数关联）。但这意味着75%的流量似乎是“真实”的，这使得该网站的流量约为每月230万次点击。按典型的1美元广告CPM计算，那就是每月2300美元，可以支付我的那份家庭开支。</p>

<p>另外，当我看看那些真正尝试将其流量变现的博客时，他们的变现率往往高得多。例如，<a href="https://web.archive.org/web/20190220011804/https://slatestarcodex.com/advertise/" rel="noopener noreferrer">Slate Star Codex 收费1250美元投放6个月广告，似乎同时运行8则广告</a>，总计每年2万美元。作者声称每天获得“10,000到20,000次展示”，或大约每月45万次点击。我得到的流量大约是它的5倍。如果线性放大，那可能是每年10万美元，而不是2万美元。我觉得有趣的一点是，Slate Star Codex上的广告没有被我的广告拦截器屏蔽。这似乎是因为作者不属于某个巨大的广告程序，而广告拦截器不会刻意去屏蔽每一个单独网站的自定义广告。我用Slate Star Codex作为例子，因为我认为它的广告优化程度不是特别高，因为我怀疑如果我投放广告，也不会对广告进行太多优化。</p>

<p>这似乎到了有点不合常理的地步，以至于我不应该投放广告（我怀疑我从这个博客获得的非直接价值无法持续超过每年10万美元）。我大概“应该”投放广告，但我不认为从AdSense或Carbon这样的地方获得的收入真的值得，而且像Slate Star Codex那样运行自己的广告程序似乎很麻烦。因为“看起来很麻烦”而放弃每年9万美元，这完全是非理性的，但我们就是这么做的。我回去给所有我的亚马逊链接添加了联盟代码，但如果我对亚马逊的报酬估算正确的话，那每月会少于100美元。</p>

<p>我认为这不一定比我看到的其他人行为更不理性——我经常和那些为了在创业公司工作而不是在大公司工作而放弃每年20万美元或更多收入的人交谈，对我来说这似乎是一个合理的选择。他们赚到了“足够”的钱，喜欢现状。有什么错呢？那么为什么不能“不投放广告”成为一种合理的选择呢？尽管如此，这仍然让我觉得相当不合理！有几个人建议众筹，但顶级收入的程序员至少比我高出一个数量级的曝光度，而他们从广告中能赚到的比我少一个数量级（像Casey Muratori、ESR和eevee这样的人每月大约赚1000美元）。</p>

<h4 id="update-3">更新 3</h4>

<p>我正在尝试<a href="https://www.patreon.com/danluu" rel="noopener noreferrer">通过Patreon接受捐赠</a>。我怀疑这不会成功，但我很乐意被证明是错的！</p><p><em>由 mimo-v2.5 模型翻译，花费 8417 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/blog-ads/</link>
      <guid isPermaLink="false">https://danluu.com/blog-ads/</guid>
      <pubDate>Sat, 24 Jan 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>80年代以后CPU有哪些新进展？</title>
      <description>[AI 摘要] 本文概述了从80年代至今CPU的新进展，包括缓存、SIMD、虚拟化等技术变化及其对编程的影响。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文概述了从80年代至今CPU的新进展，包括缓存、SIMD、虚拟化等技术变化及其对编程的影响。</div><p>以下是对David Albert所提问题的回答：</p>

<blockquote>
<p>我对CPU的认知还停留在1980年代：基本上就是做算术、逻辑、位操作和移位，以及在内存中加载和存储数据的盒子。我模糊地知道一些较新的发展，比如向量指令（SIMD），以及较新的CPU支持虚拟化的想法（虽然我不知道实际中这意味着什么）。</p>

<p>我错过了哪些很酷的新进展？今天的CPU能做什么去年的CPU做不到的事情呢？两年前、五年前或十年前的CPU呢？我最感兴趣的是程序员必须手动利用（或编程环境必须重新设计以利用）才能使用，因此可能尚未使用的东西。我认为这排除了像超线程/SMT这样的东西，但老实说我不太确定。我也对CPU目前还做不到但未来不久就能做到的事情感兴趣。</p>
</blockquote>

<p></p>

<p>以下所有内容均指x86和Linux，除非另有说明。历史倾向于重复，许多对x86来说是新的东西，对超级计算、大型机和工作站领域的人来说早已司空见惯。</p>

<h3 id="the-present">现状</h3>

<h4 id="miscellania">杂项</h4>

<p>首先，芯片的寄存器更宽，可以寻址更多内存。在80年代，你可能使用8位CPU，但现在你的机器几乎肯定有<a href="http://courses.cs.washington.edu/courses/csep590/06au/projects/history-64-bit.pdf" rel="noopener noreferrer">64位CPU</a>。我不打算多谈这个，因为我假设你熟悉64位机器的编程。除了提供更多地址空间外，64位模式还提供更多寄存器和更一致的浮点结果（通过避免x87浮点运算中32位和64位操作可能随机获得80位精度的情况）。其他你很可能正在使用、自80年代初引入x86的特性包括分页/虚拟内存、流水线和浮点运算。</p>

<h4 id="esoterica">深奥话题</h4>

<p>我也将避免讨论现在已经无关紧要的东西（如A20M），以及只有在编写驱动程序、BIOS代码、进行安全审计或其他非常底层的工作时才会影响你的东西（如APIC/x2APIC、SMM、NX或<a href="https://eprint.iacr.org/2016/086" rel="noopener noreferrer">SGX</a>）。</p>

<h4 id="memory-caches">内存/缓存</h4>

<p>在剩余的主题中，最有可能对日常编程产生实际影响的是内存的工作方式。我的第一台电脑是286。在那台机器上，一次内存访问可能需要几个时钟周期。几年前，我使用了一台奔腾4系统，一次内存访问需要超过400个周期。处理器速度的增长远快于内存。解决内存相对较慢问题的方案是添加缓存，为频繁使用的数据提供快速访问，以及预取，如果访问模式可预测，则将数据预加载到缓存中。</p>

<p>几个周期与400多个周期听起来真的很糟糕；慢了100多倍。但如果我写一个简单的循环来读取和处理一大块64位（8字节）值，CPU足够智能，可以在你需要之前预取正确的数据，这让我在我的3GHz处理器上可以达到约<a href="//danluu.com/assembly-intrinsics/" rel="noopener noreferrer">22 GB/s</a>的处理速度。一个在3GHz下每个周期消耗8字节的计算速度是24GB/s，所以达到22GB/s不算太差。我们因为必须访问主存而损失了大约8%的性能，而不是100倍。</p>

<p>作为一阶近似，使用可预测的内存访问模式和处理小于CPU缓存的数据块，你可以获得现代缓存的大部分好处。如果你想尽可能挤出性能，<a href="http://people.freebsd.org/~lstewart/articles/cpumemory.pdf" rel="noopener noreferrer">这份文档</a>是一个很好的起点。消化完这份100页的PDF后，你将需要熟悉你优化系统的微架构和内存子系统，并学习如何使用像<a href="https://code.google.com/p/likwid/" rel="noopener noreferrer">likwid</a>这样的工具来分析应用程序的性能。</p>

<h4 id="tlbs">TLB</h4>

<p>芯片上有各种东西的小缓存，不仅仅是主存。除非你真的要进行微优化，否则你不需要知道解码指令缓存和其他奇怪的小缓存。最大的例外是TLB，它们是虚拟内存查找的缓存（在x86上通过4级页表结构完成）。即使页表在L1数据缓存中，每次查找也需要4个周期，或者每次完成整个虚拟地址查找需要16个周期。这对于所有用户模式内存访问所必需的东西来说是完全不可接受的，因此有小型、快速的虚拟地址查找缓存。</p>

<p>因为第一级TLB缓存必须很快，它的大小受到严格限制（在现代芯片上可能是64个条目）。如果你使用4KB页面，这限制了你可以寻址的内存量，而不会产生TLB未命中。x86还支持2MB和1GB页面；一些应用程序使用更大的页面大小会受益很多。如果你有一个使用大量内存的长时间运行的应用程序，这值得研究一下。</p>

<p>另外，一级缓存通常受页面大小乘以<a href="//danluu.com/3c-conflict/" rel="noopener noreferrer">缓存关联度</a>的限制。如果缓存小于这个值，用于索引缓存的位在查看虚拟地址还是物理地址时都是相同的，因此你不必在索引缓存之前进行虚拟到物理的转换。如果缓存大于这个值，你必须先进行TLB查找才能索引缓存（这至少会额外消耗一个周期），或者构建一个虚拟索引的缓存（这是可能的，但会增加复杂性和与软件的耦合）。你可以在现代芯片上看到这个限制。Haswell有一个8路关联缓存和4KB页面。它的L1数据缓存是<code>8 * 4kB = 32kB</code>。</p>

<h4 id="out-of-order-execution-serialization">乱序执行/串行化</h4>

<p>几十年来，x86芯片能够推测性地执行和重新排序执行（以避免阻塞在单个停滞资源上）。这有时会导致<a href="https://docs.google.com/document/d/18gs0bkEwQ5cO8pMXT_MsOa8Xey4NEavXq-OvtdUXKck/pub" rel="noopener noreferrer">奇怪的性能卡顿</a>。但x86非常严格地要求，对于单个CPU，外部可见的状态（如寄存器和内存）必须按照所有指令顺序执行来更新。实现这一点涉及确保对于任何一对有依赖关系的指令，这些指令相对于彼此以正确的顺序执行。</p>

<p>看起来它们顺序执行的限制意味着，在大多数情况下，你可以忽略乱序执行的存在，除非你试图挤出最佳性能。主要例外是你需要确保某些事情不仅在外部看起来顺序执行，而且在内部确实顺序执行。</p>

<p>你可能关心的一个例子是，如果你正试图使用<code>rdtsc</code>测量一系列指令的执行时间。<code>rdtsc</code>读取一个隐藏的内部计数器，并将结果放入<code>edx</code>和<code>eax</code>中，这两个是外部可见的寄存器。</p>

<p>假设我们做这样的事情：</p>

<pre><code>foo
rdtsc
bar
mov %eax, [%ebx]
baz
</code></pre>

<p>其中foo、bar和baz不接触<code>eax</code>、<code>edx</code>或<code>[%ebx]</code>。紧随rdtsc之后的mov将<code>eax</code>的值写入内存中的某个位置，因为<code>eax</code>是一个外部可见的寄存器，CPU将保证<code>mov</code>在<code>rdtsc</code>执行之前不会执行，以便一切看起来顺序发生。</p>

<p>然而，由于<code>rdtsc</code>与<code>foo</code>或<code>bar</code>之间没有显式依赖关系，<code>rdtsc</code>可能在<code>foo</code>之前、<code>foo</code>和<code>bar</code>之间或<code>bar</code>之后执行。甚至<code>baz</code>可能在<code>rdtsc</code>之前执行，只要<code>baz</code>不影响mov指令。在某些情况下这是可以的，但如果<code>rdtsc</code>是为了测量<code>foo</code>的执行时间，那就不行了。</p>

<p>为了精确地将<code>rdtsc</code>与其他指令排序，我们需要一个串行化执行的指令。关于如何执行此操作的<a href="http://www.intel.com/content/dam/www/public/us/en/documents/white-papers/ia-32-ia-64-benchmark-code-execution-paper.pdf" rel="noopener noreferrer">详细信息由Intel在此文档中提供</a>。</p>

<h4 id="memory-concurrency">内存/并发</h4>

<p>除了上述排序限制（这意味着对同一位置的加载和存储不能相对于彼此重新排序）外，x86的加载和存储还有一些其他限制。特别是，对于单个CPU，存储从不与其他存储重新排序，存储也从不与较早的加载重新排序，无论它们是否指向同一位置。</p>

<p>然而，加载可以与较早的存储重新排序。例如，如果你写：</p>

<pre><code>mov 1, [%esp]
mov [%ebx], %eax
</code></pre>

<p>它可以执行为：</p>

<pre><code>mov [%ebx], %eax
mov 1, [%esp]
</code></pre>

<p>但反过来不成立——如果你写后者，它永远不会执行为前者。</p>

<p>你可以通过插入串行化指令来强制第一个例子按书面顺序执行。但这需要CPU串行化所有指令。这很慢，因为它实际上迫使CPU等待串行化指令之前的所有指令完成后才执行之后的任何指令。如果你只关心加载/存储排序，还有一个只串行化加载和存储的<code>mfence</code>指令。</p>

<p>我不打算讨论其他内存屏障lfence和sfence，<a href="https://stackoverflow.com/q/20316124/334816" rel="noopener noreferrer">但你可以在这里阅读更多</a>。</p>

<p>我们已经讨论了单核排序，其中加载和存储大部分是有序的，但还有多核排序。上述限制都适用；如果核心0在观察核心1，它将看到核心1的加载和存储都遵守单核规则。然而，如果核心0和核心1交互，则不能保证它们的交互是有序的。</p>

<p>例如，假设核心0和核心1的<code>eax</code>和<code>edx</code>都设置为0，并且核心0执行：</p>

<pre><code>mov 1, [_foo]
mov [_foo], %eax
mov [_bar], %edx
</code></pre>

<p>而核心1执行：</p>

<pre><code>mov 1, [_bar]
mov [_bar], %eax
mov [_foo], %edx
</code></pre>

<p>对于两个核心，<code>eax</code>必须为<code>1</code>，因为第一条和第二条指令之间存在核心内依赖关系。然而，<code>edx</code>在两个核心中都可能为<code>0</code>，因为核心0的第三行可以在核心0看到核心1的任何东西之前执行，反之亦然。</p>

<p>这涵盖了内存屏障，它在核心内串行化内存访问。因为存储必须在跨核心时以一致的顺序被看到，所以它们也会影响跨核心并发，但正确推理这类事情非常困难。<a href="http://yarchive.net/comp/linux/locking.html" rel="noopener noreferrer">Linus对使用内存屏障代替锁有如下评论</a>：</p>

<blockquote>
<p>不加锁的<em>真正</em>成本通常最终是不可避免的bug。用内存屏障做聪明的事情几乎总是在为bug的发生做准备。要理解在十种不同架构、不同内存排序以及单个缺失屏障下可能发生的所有事情真的<em>非常</em>困难。
……事实是，任何时候有人发明新的锁定机制，他们总是弄错。不要这样做。</p>
</blockquote>

<p>事实证明，在现代x86 CPU上，使用锁来实现并发原语通常比使用内存屏障更便宜，所以我们来看看锁。</p>

<p>如果我们设置<code>_foo</code>为0，并且有两个线程都执行<code>incl (_foo)</code> 10000次，用单条指令递增同一位置20000次，保证不会超过20000次，但（理论上）可能低至2次。如果理论最小值是2而不是10000不明显，想清楚这是一个很好的练习。如果明显，给你的附加练习是：任何合理的CPU实现能得到那个结果吗，还是规范允许但永远不会发生的愚蠢事情？这篇文章中没有足够的信息来回答附加问题，但我相信我提供了足够的信息。</p>

<p>我们可以用一个简单的代码片段来尝试一下：</p>

<pre><code>#include &lt;stdlib.h&gt;
#include &lt;thread&gt;

#define NUM_ITERS 10000
#define NUM_THREADS 2

int counter = 0;
int *p_counter = &amp;counter;

void asm_inc() {
  int *p_counter = &amp;counter;
  for (int i = 0; i &lt; NUM_ITERS; ++i) {
    __asm__("incl (%0) \n\t" : : "r" (p_counter));
  }
}

int main () {
  std::thread t[NUM_THREADS];
  for (int i = 0; i &lt; NUM_THREADS; ++i) {
    t[i] = std::thread(asm_inc);
  }
  for (int i = 0; i &lt; NUM_THREADS; ++i) {
    t[i].join();
  }
  printf("Counter value: %i\n", counter);
  return 0;
}
</code></pre>

<p>用<code>clang++ -std=c++11 -pthread</code>编译上述代码，我在两台机器上得到以下结果分布：</p>

<p><img src="https://danluu.com/images/new-cpu-features/write-contention.png" alt="Haswell和Sandy Bridge上非确定性的不同分布" width="640" height="480"></p>

<p>不仅结果在运行之间不同，而且在不同机器上的结果分布也不同。我们从未达到理论最小值2，或者更确切地说，从未低于10000，但最终结果在10000到20000之间都有可能。</p>

<p>即使<code>incl</code>是单条指令，也不保证是原子的。在内部，<code>incl</code>实现为一个加载，然后一个加法，然后一个存储。有可能在cpu0上的递增在cpu1的加载和存储之间执行，反之亦然。</p>

<p>Intel的解决方案是<code>lock</code>前缀，可以将其添加到少数几条指令以使其原子化。如果我们将上面的代码中的<code>incl</code>改为<code>lock incl</code>，结果总是20000。</p>

<p>所以，这就是我们如何使单条指令原子化。要使一个序列原子化，我们可以使用<code>xchg</code>或<code>cmpxchg</code>，它们总是锁定的比较并交换原语。我不会详细讨论其工作原理，但如果你好奇可以看看<a href="http://davidad.github.io/blog/2014/03/23/concurrency-primitives-in-intel-64-assembly/" rel="noopener noreferrer">David Dalrymple的这篇文章</a>。</p>

<p>除了使内存事务原子化之外，锁之间是全局有序的，并且加载和存储不会相对于锁重新排序。</p>

<p>关于内存排序的严格模型，<a href="http://www.cl.cam.ac.uk/~pes20/weakmemory/x86tso-paper.pdf" rel="noopener noreferrer">参见x86 TSO文档</a>。</p>

<p>所有这些讨论都是关于硬件中并发如何工作。尽管x86对重新排序有限制，但编译器不一定有相同的限制。在C或C++中，你需要插入适当的原语来确保编译器不会重新排序任何东西。正如<a href="http://yarchive.net/comp/linux/memory_barriers.html" rel="noopener noreferrer">Linus在这里指出的</a>，如果你有这样的代码：</p>

<pre><code>local_cpu_lock = 1;
// .. do something critical ..
local_cpu_lock = 0;
</code></pre>

<p>编译器不知道<code>local_cpu_lock = 0</code>不能被推送到关键部分的中间。编译器屏障与CPU内存屏障不同。由于x86内存模型相对严格，一些编译器屏障在硬件级别是空操作，告诉编译器不要重新排序。如果你使用的语言比微码、汇编、C或C++更高级，你的编译器可能会在没有任何注释的情况下为你处理这些。</p>

<h4 id="memory-porting">内存/移植</h4>

<p>如果你将代码移植到其他架构，需要注意的是x86具有你现在可能遇到的任何架构中最强的内存模型之一。如果你编写不假思索就能工作的代码并将其移植到具有较弱保证的架构（PPC、ARM或Alpha）上，你几乎肯定会有bug。</p>

<p>考虑这个例子：</p>

<pre><code>Initial
-----
x = 1;
y = 0;
p = &amp;x;

CPU1         CPU2
----         ----
i = *p;      y = 1;
             MB;
             p = &amp;y;
</code></pre>

<p><code>MB</code>是一个内存屏障。在Alpha 21264系统上，这可能导致<code>i = 0</code>。</p>

<p>Kourosh Gharachorloo解释了如何发生：</p>

<blockquote>
<p>CPU2执行y=1，导致一个“invalidate y”被发送到CPU1。这个无效化进入CPU1的传入“探测队列”；正如你将看到的，问题在于这个无效化理论上可以停留在探测队列中而不在CPU1上执行MB。无效化此时立即被确认（即，你不必等到它实际无效化CPU1缓存中的副本后才发送确认）。因此，CPU2可以执行它的MB。然后它继续写入p。现在CPU1继续读取p。读取p的回复允许绕过CPU1在其传入路径上的探测队列（这允许回复/数据快速返回到21264，而无需等待先前的传入探测被服务）。现在，CPU1可以取消引用p以读取其缓存中的旧y值（CPU1探测队列中的invalidate y仍然在那里）。</p>

<p>CPU1上的MB如何修复这个问题？21264在每个MB时刷新其传入探测队列（即，服务其中任何待处理的消息）。因此，在读取p之后，你执行一个MB，它肯定会拉入对y的无效化。你不再能看到y的旧缓存值。</p>

<p>即使上述场景理论上是可能的，但观察到由其引起的问题的机会极其微小。原因是即使你正确设置了缓存，CPU1在它接收到“read p”的数据回复之前，很可能会有足够的机会来服务其探测队列中的消息（即，无效化）。尽管如此，如果你遇到这样一种情况，即你已经在CPU1的探测队列中放置了许多东西，领先于对y的无效化，那么p的回复可能返回并绕过这个无效化。虽然设置场景并实际观察异常会很困难。</p>
</blockquote>

<p>这已经很长了，我没有讨论其他架构，所以我不会详细说明，但如果你想知道为什么有人会创建一个允许这种优化的规范，请考虑在制造成本飙升摧毁DEC之前，他们的芯片非常快，以至于可以在模拟中运行真实工作负载的行业标准x86基准测试，并且速度比x86芯片本地运行相同基准测试更快。有关当时最具RISC风格的架构为何做出这些决定的更多解释，<a href="https://danluu.com/dick-sites-alpha-axp-architecture.pdf" rel="noopener noreferrer">参见这篇关于Alpha架构动机的论文</a>。</p>

<p>顺便说一句，这也是我对Mill架构持怀疑态度的主要原因之一。撇开他们是否会兑现性能承诺的争论不谈，技术上优秀本身并不是一种商业模式。</p>

<h4 id="memory-non-temporal-stores-write-combine-memory">内存/非时间存储/写合并内存</h4>

<p>上一节中概述的限制适用于可缓存（即“回写”或WB）内存。这本身在当时是新的。在那之前，只有不可缓存（UC）内存。</p>

<p>UC内存的一个有趣之处是所有加载和存储都预期会发出到总线。在没有缓存且几乎没有板载缓冲的处理器中，这完全合理。结果是，有权访问内存的设备可以依赖对UC内存区域的所有访问创建单独的总线事务，按顺序（因为一些设备会使用内存读取或写入作为触发来做某事）。这在1982年运作得很好，但如果你有一块只想获取最新更新的显卡，那就不太好了。如果对同一UC位置（或同一字的不同字节）发生多次写入，CPU必须为每次写入发出单独的总线事务，即使显卡并不真正关心看到每个中间结果。</p>

<p>解决方案是创建一种名为写合并（WC）的内存类型。WC是一种最终一致的UC。写入最终必须到达内存，但它们可以在内部缓冲。WC内存的排序保证也比UC弱。</p>

<p>在大多数情况下，除非你直接与设备通信，否则你不需要处理这个。唯一的例外是“非时间”加载和存储操作。这些使特定的加载和存储表现得像它们是对WC内存的，即使地址位于标记为WB的内存区域中。</p>

<p>如果你不想用某物污染你的缓存，这很有用。这通常在你进行<a href="http://blogs.fau.de/hager/archives/2103" rel="noopener noreferrer">某种流式计算</a>时很有用，你知道你不会再次使用特定数据。</p>

<h4 id="memory-numa">内存/NUMA</h4>

<p>非统一内存访问，其中不同处理器的内存延迟和带宽不同，是如此普遍，以至于我们不再经常谈论NUMA或ccNUMA，因为它们太普遍了，被认为是默认的。</p>

<p>这里的要点是，共享内存的线程应该在同一个插座上，而内存映射I/O密集型线程应确保它位于与其通信的I/O设备最近的插座上。</p>

<p>我大多避免解释事物背后的<em>原因</em>，因为这将使这篇帖子至少比它将要的长一个数量级。但我将给出一个极度简化的原因，说明为什么我们有NUMA系统，部分因为它是一个独立的、相对容易解释的东西，部分为了展示原因与内容相比有多长。</p>

<p>曾经，只有内存。然后CPU相对于内存变得足够快，人们想要添加一个缓存。如果缓存与后备存储（内存）不一致，那就是坏消息，所以缓存必须保留一些关于它持有内容的信息，以便知道是否/何时需要将事物写入后备存储。</p>

<p>这还不算太坏，但是一旦你有了两个具有自己缓存的内核，情况就变得有点复杂了。为了保持与没有缓存情况相同的编程模型，缓存必须彼此一致，并与后备存储一致。因为现有的加载/存储指令在其API中没有任何内容允许它们说<code>对不起！这个加载失败了，因为某些其他CPU正在持有你想要的地址</code>，最简单的方法是让每个CPU在每次想要加载或存储东西时向总线发送消息。我们已经有了这个两个CPU都连接到的内存总线，所以我们只需要要求其他CPU响应数据（并使适当的缓存行无效），如果它们在其缓存中拥有数据的修改版本的话。</p>

<p>这运作得还行。大多数时候，每个CPU只接触另一个CPU不关心的数据，所以有一些浪费的总线流量。但这并不太糟糕，因为一旦CPU发出一条消息说<code>嗨！我将获取这个地址并修改数据</code>，它就可以假设它完全拥有这个地址，直到其他CPU要求它，而这可能不会发生。并且不是对单个内存地址执行操作，我们可以对例如64字节的缓存行进行操作。所以，总体开销相当低。</p>

<p>对于4个CPU来说它仍然运作得还行，尽管开销更糟一点。但每个CPU都必须响应每个其他CPU的这种方式无法扩展到超过4个CPU，因为总线会饱和，而且缓存也会饱和（缓存的物理大小/成本是支持的并发读取和写入数量的<code>O(n^2)</code>，并且速度与大小成反比）。</p>

<p>这个问题的一个“简单”解决方案是拥有一个集中的目录来跟踪所有信息，而不是进行N方点对点广播。既然我们现在在单个芯片上封装2-16个内核，那么每个芯片（插座）拥有一个目录来跟踪芯片上每个内核的缓存状态是非常自然的。</p>

<p>这仅解决了每个芯片的问题，我们需要某种方式让芯片彼此通信。不幸的是，当我们将这些系统扩大规模时，总线速度变得足够快，以至于真的很难将信号驱动得足够远以在一条总线上连接一堆芯片和内存，即使是小型系统也是如此。最简单的解决方案是让每个插座拥有一个内存区域，这样每个插座就不需要连接到内存的每个部分。这还避免了需要目录的目录的复杂性，因为清楚哪个目录拥有任何特定内存。</p>

<p>缺点是，如果你在一个插座中并且想要另一个插座拥有的内存，你将承受显著的性能损失。为简单起见，大多数“小型”（&lt;128核）系统使用环状总线，所以性能损失不仅仅是你为了到达内存而穿越额外跳数所付出的直接延迟/带宽损失，它还消耗有限的资源（环状总线）并减慢其他跨插座访问。</p>

<p>理论上，操作系统透明地处理这个问题，但这<a href="https://www.usenix.org/conference/atc14/technical-sessions/presentation/gaud" rel="noopener noreferrer">通常效率低下</a>。</p>

<h4 id="context-switches-syscalls">上下文切换/系统调用</h4>

<p>在这里，syscall指的是Linux系统调用，而不是<code>SYSCALL</code>或<code>SYSENTER</code> x86指令。</p>

<p>现代内核所有缓存的一个副作用是<a href="http://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html" rel="noopener noreferrer">上下文切换代价高昂</a>，这导致系统调用代价高昂。Livio Soares和Michael Stumm在他们的<a href="http://www.cs.cmu.edu/~chensm/Big_Data_reading_group/papers/flexsc-osdi10.pdf" rel="noopener noreferrer">论文</a>中详细讨论了成本。我将在下面使用他们的几个数字。这是Core i7在Xalan（SPEC CPU的一个子基准测试）上实现的每时钟指令数（IPC）图。</p>

<p><img src="https://danluu.com/images/new-cpu-features/flexsc_ipc_recovery.png" alt="系统调用后开销的长尾。14,000个周期。" width="268" height="147"></p>

<p>系统调用后14,000个周期，代码仍然没有完全以全速运行。</p>

<p>这是一些不同系统调用开销的表格，包括直接成本（以指令和周期计）和间接成本（来自缓存和TLB驱逐的数量）。</p>

<p><img src="https://danluu.com/images/new-cpu-features/flexsc_cost.png" alt="stat、pread、pwrite、open+close、mmap+munmap和open+write+close的成本" width="502" height="108"></p>

<p>其中一些系统调用导致40次以上的TLB驱逐！对于一个具有64个条目d-TLB的芯片，这几乎清除了TLB。缓存驱逐也不是免费的。</p>

<p>系统调用的高成本是人们转向使用批处理版本系统调用（例如<code>epoll</code>或<code>recvmmsg</code>）用于高性能代码的原因，也是需要<a href="//danluu.com/clwb-pcommit/" rel="noopener noreferrer">非常高性能I/O</a>的人经常使用用户空间I/O堆栈的原因。更一般地说，上下文切换的成本是为什么高性能代码通常是每核一个线程（或甚至在固定线程上单线程），而不是每个逻辑任务一个线程的原因。</p>

<p>这种高成本也是<a href="http://www.linuxjournal.com/content/creating-vdso-colonels-other-chicken" rel="noopener noreferrer">vDSO背后的驱动因素，它将一些不需要任何权限提升的简单系统调用转换为简单的用户空间库调用</a>。</p>

<h4 id="simd">SIMD</h4>

<p>基本上所有现代x86 CPU都支持SSE，128位宽的向量寄存器和指令。由于经常需要对相同操作执行多次，Intel添加了可以让你操作128位数据块为2个64位块、4个32位块、8个16位块等的指令。ARM以不同的名称（NEON）支持相同的功能，并且支持的指令非常相似。</p>

<p>使用SIMD指令通常可以获得2倍-4倍的加速；如果你有计算密集型工作负载，绝对值得研究。</p>

<p>编译器足够好地识别可以向量化的常见模式，这样像下面这样的简单代码将自动使用向量指令与现代编译器：</p>

<pre><code>for (int i = 0; i &lt; n; ++i) {
  sum += a[i];
}
</code></pre>

<p>但如果你不是手写汇编，编译器<a href="//danluu.com/assembly-intrinsics/" rel="noopener noreferrer">通常会产生非最优代码</a>，尤其是对于SIMD代码，所以如果你想获得最佳性能，你将想要查看反汇编并检查编译器优化错误。</p>

<h4 id="power-management">电源管理</h4>

<p>现代CPU上有许多花哨的电源管理功能，在不同场景下<a href="//danluu.com/datacenter-power/" rel="noopener noreferrer">优化功耗</a>。这些的结果是“竞速到空闲”，尽快完成工作然后让CPU回到睡眠状态是最节能的工作方式。</p>

<p>已经有很多工作表明特定的微优化可以有益于功耗，但<a href="http://arcade.cs.columbia.edu/energy-oopsla14.pdf" rel="noopener noreferrer">在真实工作负载上应用这些微优化通常产生比预期小的好处</a>。</p>

<h4 id="gpu-gpgpu">GPU / GPGPU</h4>

<p>我比谈论其他内容更没有资格谈论这个。幸运的是，Cliff Burdick自愿写了一节关于GPU的内容，所以以下是他的内容。</p>

<p>在2000年代中期之前，图形处理单元（GPU）被限制在只允许对硬件进行非常有限控制的API中。随着库变得更加灵活，程序员开始将处理器用于更通用的任务，例如线性代数程序。GPU的并行架构可以通过启动数百个并行线程来处理矩阵的大块。然而，代码必须使用传统的图形API，并且在控制多少硬件方面仍然受到限制。Nvidia和ATI注意到了这一点，并发布了允许用户使用图形行业外人员熟悉的API访问更多硬件的框架。这些库获得了普及，今天GPU与CPU一起广泛用于高性能计算（HPC）。</p>

<p>与CPU相比，GPU上的硬件有几个主要区别，如下所述：</p>

<h5 id="processors">处理器</h5>

<p>在顶层，GPU处理器包含一个或多个流式多处理器（SM）。现代GPU上的每个流式多处理器通常包含超过100个浮点单元，或者通常被称为GPU世界中的核心。每个核心通常在800MHz左右，尽管，像CPU一样，也有时钟频率更高但核心更少的处理器可用。GPU处理器缺少其CPU对应物的许多功能，包括大缓存和分支预测。在内核、SM和整个处理器之间的层级中，通信变得越来越慢。因此，在GPU上表现良好的问题通常是高度并行的，但在少量线程之间可以共享一定数量的数据。我们将在下面的内存部分讨论原因。</p>

<h5 id="memory">内存</h5>

<p>现代GPU上的内存分为三类：全局内存、共享内存和寄存器。全局内存是GPU包装盒上宣传的GDDR内存，通常约为2-12GB，吞吐量为300-400GB/s。全局内存可以被处理器上所有SM上的所有线程访问，也是卡上最慢的内存类型。共享内存，顾名思义，是在同一SM内所有线程之间共享的内存。它通常至少是全局内存的两倍快，但不可在不同SM上的线程之间访问。寄存器与CPU上的寄存器非常相似，因为它们是GPU上访问数据最快的方式，但它们是每个线程本地的，数据对任何其他运行线程不可见。
共享内存和全局内存对其访问方式都有非常严格的规定，不遵守会有严重的性能损失。要达到上述吞吐量，内存访问必须在线程组内线程之间完全合并。类似于CPU读入单个缓存行，GPU具有大小合适的缓存行，如果正确对齐，单个访问可以为组中的所有线程提供服务。然而，在最坏的情况下，组中的所有线程访问不同的缓存行中的内存，则每个线程需要单独的内存读取。这通常意味着缓存行中的大部分数据不被线程使用，因此内存的可用吞吐量下降。类似的规则也适用于共享内存，有几个例外我们在此不介绍。</p>

<h5 id="threading-model">线程模型</h5>

<p>GPU线程以SIMT（单指令多线程）方式运行，每个线程与硬件中预定义大小（通常为32）的组一起运行。最后一部分有许多含义；该组中的每个线程必须同时处理相同的指令。如果组中的任何线程需要从其他线程采取不同的路径（例如if语句），所有不属于分支的线程将暂停执行，直到分支完成。作为简单的例子：</p>

<pre><code>if (threadId &lt; 5) {
   // Do something
}
// Do More
</code></pre>

<p>在上面的代码中，这个分支将导致我们32个线程组中的27个线程暂停执行，直到分支完成。你可以想象如果许多线程组都运行这段代码，当大多数核心空闲时，整体性能将受到很大影响。只有当一个完整的线程组被阻塞时，硬件才允许将另一组交换到这些核心上运行。</p>

<h5 id="interfaces">接口</h5>

<p>现代GPU必须有一个CPU来将数据复制到CPU和GPU内存之间，并在GPU上启动和运行代码。在最高吞吐量下，具有16个通道的PCIe 3.0总线可以达到约13-14GB/s的速率。这听起来可能很高，但与GPU本身的内存速度相比，它们慢了一个数量级。事实上，随着GPU变得越来越强大，PCIe总线正日益成为瓶颈。要看到GPU相对于CPU的任何性能优势，GPU必须加载大量工作，以便GPU运行作业的时间明显高于复制数据所需的时间。</p>

<p>较新的GPU具有在GPU代码中动态启动工作的功能，而无需返回CPU，但目前在其使用中相当有限。</p>

<h5 id="gpu-conclusion">GPU结论</h5>

<p>由于CPU和GPU之间的主要架构差异，很难想象其中任何一个完全取代另一个。事实上，GPU与CPU在并行工作方面互补，并允许CPU在GPU运行时独立处理其他任务。AMD试图通过其“异构系统架构”（HSA）合并这两种技术，但将现有CPU代码拆分为CPU和GPU部分将不仅对处理器，而且对编译器来说都是一个巨大挑战。</p>

<h4 id="virtualization">虚拟化</h4>

<p>既然你提到了虚拟化，我将稍微谈谈它，但Intel的虚拟化指令实现通常不是你需要考虑的，除非你正在编写直接处理虚拟化的非常底层的代码。</p>

<p>处理那些东西相当混乱，正如你从<a href="https://github.com/vishmohan/vmlaunch" rel="noopener noreferrer">这段代码</a>中看到的。设置东西以使用Intel的VT指令启动VM客户机大约需要1000行底层代码，即使对于那里展示的非常简单的情况也是如此。</p>

<h4 id="virtual-memory">虚拟内存</h4>

<p>如果你看Vish的VT代码，你会发现有相当多的代码专门用于页表/虚拟内存。这是另一个“新”特性，除非你正在编写操作系统或其他底层系统代码，否则你不必担心。使用虚拟内存比使用分段内存简单得多，但如今这已经无关紧要，所以我就不多说了。</p>

<h4 id="smt-hyper-threading">SMT/超线程</h4>

<p>既然你提到了，我也会提到SMT。正如你所说，这对程序员来说大多是透明的。在单个内核上启用SMT的典型加速约为25%。这对整体吞吐量有好处，但意味着每个线程可能只能获得其原始性能的60%。对于你非常关心单线程性能的应用程序，你最好禁用SMT。这在很大程度上取决于工作负载，与任何其他更改一样，你应该在确切的工作负载上运行一些基准测试，看看什么最有效。</p>

<p>添加到芯片（和软件）中的所有这些复杂性的一个副作用是性能比过去更难预测；在将要运行的具体硬件上对确切的工作负载进行基准测试的相对重要性增加了。</p>

<p>举个例子，人们经常引用<a href="http://benchmarksgame.alioth.debian.org/" rel="noopener noreferrer">计算机语言基准测试游戏</a>的基准测试结果作为一种语言比另一种语言更快的证据。我曾尝试自己重现结果，在我的移动Haswell（而不是结果中使用的服务器Kentsfield）上，我得到的结果相对速度相差多达2倍。在同一台机器上运行相同的基准测试，Nathan Kurz最近向我指出一个例子，其中<code>gcc -O3</code>比<code>gcc -O2</code>慢25%。<a href="http://www-plan.cs.colorado.edu/klipto/mytkowicz-asplos09.pdf" rel="noopener noreferrer">更改C++程序的链接顺序可能导致15%的性能变化</a>。基准测试是一个难题。</p>

<h4 id="branches">分支</h4>

<p>老派传统智慧是分支代价高昂，应该不惜一切（或大多数）代价避免。在Haswell上，分支误预测惩罚是14个周期。分支误预测率取决于工作负载。使用<code>perf stat</code>对几种不同的东西（bzip2、top、mysqld、重新生成我的博客）进行测试，我得到的分支误预测率在0.5%到4%之间。如果我们说正确预测的分支成本为1个周期，平均成本在<code>.995 * 1 + .005 * 14 = 1.065周期</code>到<code>.96 * 1 + .04 * 14 = 1.52周期</code>之间。这还不算太糟。</p>

<p>实际上这高估了惩罚，因为大约从1995年开始，Intel添加了条件移动指令，允许你在没有分支的情况下有条件地移动数据。这个指令被Linus<a href="http://yarchive.net/comp/linux/cmov.html" rel="noopener noreferrer">著名地批评过</a>，这给了它一个坏名声，但<a href="https://github.com/logicchains/LPATHBench/issues/53#issuecomment-68160081" rel="noopener noreferrer">使用cmov与分支相比通常能获得显著的加速</a>。</p>

<p>额外分支成本的一个现实例子是启用整数溢出检查。当使用<a href="//danluu.com/integer-overflow/" rel="noopener noreferrer">bzip2压缩特定文件时，这会使指令数量增加约30%（所有增加都来自额外的分支指令），导致1%的性能损失</a>。</p>

<p>不可预测的分支很糟糕，但大多数分支是可预测的。在你的分析器告诉你有一个热点之前忽略分支的成本现在是相当合理的。在过去的十年中，CPU在执行优化不佳的代码方面变得好得多，编译器在优化代码方面也变得更好，这使得优化分支除非你试图从某些代码中挤出绝对最佳性能，否则是时间利用不佳。</p>

<p>如果事实证明你需要这样做，你可能最好使用<a href="http://en.wikipedia.org/wiki/Profile-guided_optimization" rel="noopener noreferrer">配置文件引导优化</a>，而不是试图手工处理这些东西。</p>

<p>如果你真的必须手动完成，你可以使用编译器指令来指示特定分支是否可能被采用。现代CPU忽略分支提示指令，但它们可以帮助编译器更好地布局代码。</p>

<h4 id="alignment">对齐</h4>

<p>老派传统智慧是，你应该填充结构体并确保事物是对齐的。但在Haswell芯片上，对于几乎任何你可以想到的不跨页边界的单线程事物，不对齐是零。有些情况下它可能有影响，但总的来说，这是另一种由于CPU在执行不良代码方面变得好得多而大部分无关紧要的优化。它在增加内存占用而没有任何好处的情况下还有轻微的害处。</p>

<p>另外，<a href="//danluu.com/3c-conflict/" rel="noopener noreferrer">不要使事物页对齐或以其他方式对齐到大边界，否则你会破坏缓存的性能</a>。</p>

<h4 id="self-modifying-code">自修改代码</h4>

<p>这是另一个现在没有太大意义的优化。使用自修改代码来减少代码大小或提高性能曾经有意义，但因为现代缓存倾向于拆分其L1指令和数据缓存，修改运行中的代码需要芯片的L1缓存之间昂贵的通信。</p>

<h3 id="the-future">未来</h3>

<p>以下是一些可能的更改，从最不推测到最推测。</p>

<h4 id="partitioning">分区</h4>

<p>现在很明显，越来越多的计算正在转移到大型数据中心。有时这涉及在VM上运行，有时在某种容器中运行，有时在裸机上运行，但无论哪种情况，单个机器通常被复用以运行各种工作负载。理想情况下，你希望能够调度尽力而为的工作负载来吸收搁置资源，而不会影响具有SLA的延迟敏感工作负载。事实证明，你可以用一些<a href="//danluu.com/intel-cat/" rel="noopener noreferrer">相对简单的硬件更改</a>来做到这一点。</p>

<p><img src="https://danluu.com/images/new-cpu-features/google_heracles.png" alt="90%的机器整体利用率" width="338" height="209"></p>

<p><a href="http://csl.stanford.edu/~christos/publications/2015.heracles.isca.pdf" rel="noopener noreferrer">David Lo等人表明，如果可以对缓存进行分区，使得尽力而为的工作负载不会影响延迟敏感的工作负载，那么你可以获得大约90%的机器利用率，而不会影响延迟SLA</a>。实心红线是正常Google Web搜索集群上的负载，虚线绿色是你通过适当优化得到的结果。从酒吧谈话中，我的印象是实心红线实际上已经比大多数Google的竞争对手能够做到的更高了。如果你将90%优化利用率与典型的服务器利用率10%到90%进行比较，那么与运行原始、未优化设置相比，每单位工作量的成本会有巨大差异。通过大量的硬件工作，Google能够避免干扰，但额外的隔离功能可能允许以更少的努力更高效地完成此操作。</p>

<h4 id="transactional-memory-and-hardware-lock-elision">事务性内存和硬件锁消除</h4>

<p>IBM已经在他们的POWER芯片中具有这些功能。Intel试图在Haswell中添加这些，但因为一个bug而被禁用。总的来说，现代CPU非常复杂，<a href="//danluu.com/cpu-bugs/" rel="noopener noreferrer">我们应该期望看到比过去更多的bug</a>。</p>

<p>事务性内存支持就是它的样子：事务的硬件支持。这通过三个新指令实现：<code>xbegin</code>、<code>xend</code>和<code>xabort</code>。</p>

<p><code>xbegin</code>开始一个新事务。冲突（或<code>xabort</code>）导致处理器的架构状态（包括内存）回滚到<code>xbegin</code>之前的状态。如果你通过库或语言支持使用事务性内存，这应该是透明的。如果你正在实现库支持，你将必须弄清楚如何将这个硬件支持（具有有限的硬件缓冲区大小）转换为可以处理任意事务的东西。</p>

<p>我不打算讨论硬件锁消除，只是说，在底层，它是通过与用于实现事务性内存的机制非常相似的机制实现的，并且它旨在加速基于锁的代码。如果你想利用HLE，<a href="http://mcg.cs.tau.ac.il/papers/amir-levy-msc.pdf" rel="noopener noreferrer">参见此文档</a>。</p>

<h4 id="fast-i-o">快速I/O</h4>

<p>I/O带宽正在上升，I/O延迟正在下降，对于存储和网络都是如此。问题是I/O通常通过系统调用完成。正如我们所看到的，系统调用的相对开销一直在上升。
对于存储和网络，答案是转向<a href="http://research.microsoft.com/pubs/198366/Asplos2012_MonetaD.pdf" rel="noopener noreferrer">用户模式I/O堆栈</a>（将所有内容放在内核模式中也可以，但那更难卖）。在存储方面，这仍然主要是一个奇怪的研究事物，但HPC和HFT人员已经在网络中这样做了一段时间。而一段时间，我不是指几个月。这里有一篇<a href="http://web.stanford.edu/group/comparch/papers/huggahalli05.pdf" rel="noopener noreferrer">2005年的论文，讨论了我将讨论的网络内容以及一些我不讨论的内容（DCA）</a>。</p>

<p>这终于逐渐渗入非超级计算世界。MS已经用带有虚拟化RDMA的infiniband网络宣传Azure一年多，Cloudflare<a href="http://blog.cloudflare.com/a-tour-inside-cloudflares-latest-generation-servers/" rel="noopener noreferrer">谈论使用Solarflare NIC来获得相同能力</a>，等等。最终，我们将看到带有快速以太网的SoC，除非仅限于Xeon类型的设备，否则它将渗透到所有设备。ARM设备之间的竞争可能会导致至少一个ARM设备制造商在其商用SoC上放置快速以太网，这可能迫使Intel采取行动。</p>

<p>RDMA部分意义重大；它让你完全绕过CPU，并让NIC响应远程请求。几个月前，我完成了斯坦福/Coursera挖掘海量数据集课程。在第一节讲座中，他们提供了一个具有1Gb机架顶交换机的“典型”数据中心设置示例。如果你在通过非RDMA NIC运行内核TCP时处理“海量”数据，这并非不合理，因为你可能用尽整个核心来推动1GB/s通过Linux的TCP堆栈。但通过Azure，MS谈到从单个机器获得40Gb；那是一台机器获得你可能期望整个机架的40倍带宽。他们还提到低于2us的延迟，这比内核TCP能获得的延迟低几个数量级。这并不是一个新想法。这篇来自2011年的<a href="http://www.scs.stanford.edu/~rumble/papers/latency_hotos11.pdf" rel="noopener noreferrer">论文</a>预测了目前在网络方面发生的一切，以及一些仍然有些遥远的事情。</p>

<p><a href="https://www.youtube.com/watch?v=8Kyoj3bKepY&amp;feature=youtu.be&amp;t=20m8s" rel="noopener noreferrer">这次MS演讲讨论了你如何利用这种带宽和延迟</a>进行网络存储。一个不需要点击链接的具体例子是Amazon的EBS。它让你在任何AWS节点上使用任意大小的“弹性”磁盘。由于旋转金属磁盘寻道的延迟高于通过内核TCP的RPC，你可以几乎透明地获得无限存储。例如，假设你可以从网络中获得<a href="http://www.scs.stanford.edu/~rumble/papers/latency_hotos11.pdf" rel="noopener noreferrer">100us（.1ms）延迟</a>，你的磁盘寻道时间是8ms。这使得远程磁盘访问为8.1ms而不是8ms，开销不是很大。但这对SSD效果不佳，因为你可以从SSD获得20 us（.02ms）<a href="http://www.anandtech.com/show/8104/intel-ssd-dc-p3700-review-the-pcie-ssd-transition-begins-with-nvme/3" rel="noopener noreferrer">延迟</a>。但RDMA延迟足够低，使得透明的类似EBS的层对于SSD成为可能。</p>

<p>所以那是网络I/O。如果/当比Flash更快的下一代存储技术开始部署时，在磁盘方面的性能收益可能更大。性能差异如此之大，以至于<a href="//danluu.com/clwb-pcommit/" rel="noopener noreferrer">Intel正在添加新指令以跟上下一代低延迟存储技术</a>。根据你询问的对象，这些东西已经距离几年到几十年；这比网络内容更不确定。但即使使用Flash，人们也展示了可以达到微秒范围的设备，这是实质性的改进。</p>

<h4 id="hardware-acceleration">硬件加速</h4>

<p>像快速网络I/O一样，这已经在一些利基市场存在了。<a href="http://www.deshawresearch.com/publications.html" rel="noopener noreferrer">DESRES多年来一直在使用ASIC在计算化学中获得100倍-1000倍的加速</a>。<a href="http://research.microsoft.com/apps/pubs/default.aspx?id=212001" rel="noopener noreferrer">Microsoft谈论使用FPGA加速搜索</a>。人们已经<a href="http://web.eecs.umich.edu/~twenisch/papers/isca13.pdf" rel="noopener noreferrer">研究加速memcached和类似系统有一段时间</a>，<a href="http://csl.stanford.edu/~christos/publications/2014.hwkvs.nvmw.slides.pdf" rel="noopener noreferrer">东芝和斯坦福的研究人员之前展示了实际实现</a>，我最近看到伯克利的另一篇预印本。有多家公司生产比特币挖矿ASIC。对于<a href="http://www.neuflow.org/" rel="noopener noreferrer">其他</a> <a href="http://www.artificiallearning.com/products/" rel="noopener noreferrer">应用</a> <a href="https://github.com/cambridgehackers/" rel="noopener noreferrer">领域</a>也是如此。</p>

<p>随着从CPU中获得功耗/性能提升变得越来越困难，我们似乎应该看到更多这种情况。如果你认为编程是一种以软件为导向的事业，你可能认为这是对你问题的回避，但另一种看法是编程某物的含义将改变。未来，它可能意味着设计像FPGA或ASIC这样的硬件，结合编写软件。</p>

<h5 id="update">更新</h5>

<p>现在是2016年，距离这篇文章最初发布已经一年，我们可以看到公司正在投资硬件加速器。除了之前在FPGA加速搜索方面的工作外，Microsoft<a href="https://www.youtube.com/watch?v=RffHFIhg5Sc&amp;feature=youtu.be&amp;t=23m30s" rel="noopener noreferrer">宣布它正在使用FPGA来加速网络</a>。Google一直对基础设施保持沉默，这是他们的典型做法，但如果你查看Tensorflow的初始版本，你可以看到明确引用FPGA的代码片段，例如：</p>

<pre><code>enum class PlatformKind {
  kInvalid,
  kCuda,
  kOpenCL,
  kOpenCLAltera,  // Altera FPGA OpenCL platform.
                  // See documentation: go/fpgaopencl
                  // (StreamExecutor integration)
  kHost,
  kMock,
  kSize,
};
</code></pre>

<p>和：</p>

<pre><code>string PlatformKindString(PlatformKind kind) {
  switch (kind) {
    case PlatformKind::kCuda:
      return "CUDA";
    case PlatformKind::kOpenCL:
      return "OpenCL";
    case PlatformKind::kOpenCLAltera:
      return "OpenCL+Altera";
    case PlatformKind::kHost:
      return "Host";
    case PlatformKind::kMock:
      return "Mock";
    default:
      return port::StrCat("InvalidPlatformKind(", static_cast&lt;int&gt;(kind), ")");
  }
}
</code></pre>

<p>截至撰写本文时，Google没有为<code>+google +kOpenClAltera</code>返回任何结果，因此这似乎并未被广泛注意到。如果你不熟悉Altera OpenCL并且在Google工作，你可以尝试文档中建议的内部go链接，<code>go/fpgaopencl</code>。如果像我一样，你不在Google工作，那么<a href="https://www.altera.com/products/design-software/embedded-software-developers/opencl/overview.html" rel="noopener noreferrer">这里有Altera的文档</a>。基本思想是你可以采用OpenCL代码，与你在GPU上运行的相同类型的东西，并在FPGA上运行它，并且从注释来看，Google似乎有一些设置让你可以在带FPGA的节点之间流入和流出数据。</p>

<p>那个FPGA特定的代码在ddd4aaf5286de24ba70402ee0ec8b836d3aed8c7中被删除了，提交消息以“TensorFlow: upstream changes to git.”开头，然后列出了正在上游化的内部Google提交，以及每个内部提交的描述。奇怪的是，即使移除FPGA支持似乎是一件足够大的事情，你应该期望它被描述，除非它被故意删除。Amazon也对他们的基础设施计划非常保密，但你可以通过查看他们一直吸纳的硬件人员来做出合理的猜测。其他几家公司也押注硬件加速器，但由于我是通过私人对话而不是意外发布的公共源代码或其他公共信息了解到这一点，我将让你猜测是哪些公司。</p>

<h4 id="dark-silicon-socs">暗硅/SoC</h4>

<p>晶体管扩展方式产生的一个有趣副作用是，我们可以在芯片上打包大量的晶体管，但它们产生如此多的热量，以至于如果你不想芯片熔化，平均晶体管大部分时间不能切换。</p>

<p>结果是，包含专用硬件更有意义，这些硬件大部分时间不被使用。一方面，这意味着我们获得各种专用指令，如<a href="http://www.felixcloutier.com/x86/PCMPESTRI.html" rel="noopener noreferrer">PCMP</a>和<a href="http://www.intel.com/content/dam/www/public/us/en/documents/white-papers/ia-large-integer-arithmetic-paper.pdf" rel="noopener noreferrer">ADX</a>指令。但这也意味着我们正在获得芯片上集成的整个设备，这些设备以前会存在于芯片外。这包括像GPU和（对于移动设备）无线电之类的东西。</p>

<p>与硬件加速趋势相结合，这也意味着公司设计自己的芯片，或至少他们自己芯片的一部分更有意义。Apple通过收购PA Semi获得了很大收益。首先，在标准ARM架构上添加小的自定义加速器，然后在他们自己的自定义架构上添加自定义加速器。由于正确的自定义硬件加上经过深思熟虑的基准测试和系统设计的组合，iPhone 4比我的旗舰Android手机稍微响应更快，后者更新了几年，并且具有快得多的处理器和更多的RAM。</p>

<p>Amazon已经吸收了Calxeda团队的相当一部分，并且招聘足够建立一个良好的硬件设计团队。Facebook吸收了少数几个ARM SoC人员，并与Qualcomm合作做某种事情。<a href="http://www.realworldtech.com/forum/?threadid=146066&amp;curpostid=146227" rel="noopener noreferrer">Linus有记录说我们将看到更多专用硬件随处可见</a>。等等，等等。</p>

<h3 id="conclusion">结论</h3>

<p>x86芯片已经获得了很多新功能和花哨的小工具。在大多数情况下，你不必知道它们是什么就能利用它们。作为一阶近似，使你的代码可预测并注意内存局部性效果很好。真正底层的东西通常被库或驱动程序隐藏，编译器会尝试处理其余部分。例外是你正在编写非常底层的代码，在这种情况下<a href="https://github.com/vishmohan/vmlaunch/blob/master/vmlaunch_simple.c" rel="noopener noreferrer">世界已经变得更混乱</a>，或者你正在试图从代码中挤出绝对最佳性能，在这种情况下<a href="https://docs.google.com/document/d/18gs0bkEwQ5cO8pMXT_MsOa8Xey4NEavXq-OvtdUXKck/pub" rel="noopener noreferrer">世界已经变得更奇怪</a>。</p>

<p>另外，未来会发生事情。但大多数预测都是错误的，所以谁知道呢？</p>

<h3 id="resources">资源</h3>

<p><a href="https://www.youtube.com/watch?v=hgcNM-6wr34" rel="noopener noreferrer">这是Matt Godbolt的演讲</a>，涵盖了很多我没有深入的实现细节。要深入到更详细的一级，<a href="http://www.amazon.com/gp/product/1478607831/ref=as_li_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=1478607831&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=HXBNFFJ3CIXMWUZP" rel="noopener noreferrer">参见Shen和Lipasti的《现代处理器设计》</a>。尽管Amazon上列出的日期是（2013年），这本书相当古老，但它仍然是我发现的关于处理器内部的最佳书籍。它详细描述了使P6时代高性能CPU成为你需要实现的东西。它还根据不同的假设推导出理论性能限制，并讨论了许多不同的工程权衡，其中许多都有解释原因。</p>

<p>对于更深层次的“为什么”，你可能需要查看VLSI文本，它将解释设备和互连如何扩展以及这如何影响电路设计，进而影响架构。我真的很喜欢<a href="http://www.amazon.com/gp/product/B008VIXPI2/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=B008VIXPI2&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=YOJVAWVH5XTIF6LN" rel="noopener noreferrer">Weste &amp; Harris</a>，因为他们有清晰的解释和可以在线找到解决方案的好的练习，但如果你不打算做练习，任何VLSI文本都可以。对于更深层次的“为什么”，你将需要固体器件教材和解释传输线和互连如何工作的材料。对于器件，我真的很喜欢<a href="http://www.amazon.com/gp/product/0201543931/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0201543931&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=MXV5K7IJXWXJD446" rel="noopener noreferrer">Pierret的</a> <a href="http://www.amazon.com/gp/product/013061792X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=013061792X&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=N7YLCANVDPI3M35R" rel="noopener noreferrer">书籍</a>。我是通过<a href="http://www.amazon.com/gp/product/8126515252/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=8126515252&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=XZUQXDRDJVABGITB" rel="noopener noreferrer">Ramo, Whinnery &amp; Van Duzer</a>接触电磁场材料的，但<a href="http://www.amazon.com/gp/product/3319078054/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=3319078054&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=GTQSYCVTMIFZKSPZ" rel="noopener noreferrer">Ida</a>是更好的入门文本。</p>

<p>关于当前一代CPU的具体信息和特定优化技术，<a href="http://www.agner.org/optimize/" rel="noopener noreferrer">参见Agner Fog的网站</a>。关于来自未来的<a href="//danluu.com/perf-tracing/" rel="noopener noreferrer">优化工具，参见这篇文章</a>。<a href="http://www.akkadia.org/drepper/cpumemory.pdf" rel="noopener noreferrer">每个程序员应该知道的内存知识</a>也是很好的背景知识。这些文档涵盖了许多重要材料，但如果你用更高级的语言编写，<a href="http://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html" rel="noopener noreferrer">有很多其他需要记住的事情</a>。有关Intel CPU历史的更多信息，Xao-Feng Li <a href="https://people.apache.org/~xli/presentations/history_Intel_CPU.pdf" rel="noopener noreferrer">有一个很好的概述</a>。</p>

<p>对于一些有点偏离常规的内容，<a href="//danluu.com/cpu-backdoors/" rel="noopener noreferrer">参见这篇关于<b>CPU后门</b>可能性的文章</a>。对于不那么偏离常规的内容，<a href="//danluu.com/cpu-bugs/" rel="noopener noreferrer">参见这篇关于现代CPU中的复杂性如何导致各种<b>令人兴奋的bug</b>的文章</a>。</p>

<p>有关锁的更多基准测试，<a href="http://shipilev.net/blog/2014/on-the-fence-with-dependencies/" rel="noopener noreferrer">参见Aleksey Shipilev的这篇文章</a>，<a href="http://www.pvk.ca/Blog/2015/01/13/lock-free-mutual-exclusion/" rel="noopener noreferrer">参见Paul Khuong的这篇文章</a>，以及他们的档案。</p>

<p>对于一般基准测试，去年的<a href="https://www.youtube.com/watch?v=XmImGiVuJno" rel="noopener noreferrer">Strange Loop基准测试演讲Aysylu Greenberg</a>是对常见陷阱的很好介绍。对于更高级但更具体的内容，<a href="https://www.youtube.com/watch?v=9MKY4KypBzg" rel="noopener noreferrer">Gil Tene关于延迟的演讲</a>很棒。</p>

<p>对于早在我提到的所有内容之前的计算历史，<a href="http://www.amazon.com/gp/product/0262523930/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0262523930&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=NB4TSSXVLME77X4B" rel="noopener noreferrer">IBM早期计算机</a>和<a href="http://ygdes.com/CDC/DesignOfAComputer_CDC6600.pdf" rel="noopener noreferrer">计算机设计</a>（描述CDC 6600的设计）是很好的参考。<a href="http://www.amazon.com/gp/product/1558605398/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=1558605398&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=MCCNUSAAP4B6NZAP" rel="noopener noreferrer">计算机体系结构读本</a>也很好地展示了这些想法的起源。</p>

<p>抱歉，这个列表相当不完整。欢迎提供建议！</p>

<p><small></small></p><small>

<h4 id="tiny-disclaimer">小声明</h4>

<p>毫无疑问，我遗漏了一些东西。<a href="https://twitter.com/danluu/" rel="noopener noreferrer">让我知道</a>如果你认为我遗漏了任何重要的东西，我会更新它。我试图保持尽可能简单，同时仍然捕捉到正在发生的事情的精髓，但我肯定有些情况下我过于简单化了，有些事情我只是完全忘记了。当然，如果你真的非常精确，我基本上做的每个概括都是错误的。甚至只挑出我开头的几句话，A20M并非总是和处处都无关（我可能花了大约0.2%的职业生涯来处理它），x86-64并非严格优于x86（在一个我必须处理的工作负载中，额外寄存器带来的性能收益被更长的指令成本所抵消；指令流和icache未命中是工作负载的长杆的情况很少见，但确实存在），等等。最大的问题可能在我的NUMA解释中，因为P6总线实际上可以响应延迟或重试请求。避免使用类似机制来强制一致性是合理的，但我无法想到不涉及多层解释的合理解释。我真的不是在开玩笑，几乎每个概括如果你深入挖掘都会崩溃。我谈论的每个抽象都是泄露的。我试图包含至少深入一级的文档链接，但我可能遗漏了一些领域。</p>

<h4 id="acknowledgments">致谢</h4>

</small><p><small>感谢Leah Hanson和Nathan Kurz的评论导致主要编辑，感谢Nicholas Radcliffe、Stefan Kanthak、Garret Reid、Matt Godbolt、Nikos Patsiouras、Aleksey Shipilev和Oscar R Moll的评论导致次要编辑，感谢David Albert允许我引用他，也感谢我们在谈论这个话题时的一些有趣的后续问题。另外，感谢Cliff Burdick撰写关于GPU的部分，感谢Hari Angepat在TensorFlow中发现了Google kOpenCLAltera代码。
</small></p>

<hr><p><em>由 mimo-v2.5 模型翻译，花费 31893 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/new-cpu-features/</link>
      <guid isPermaLink="false">https://danluu.com/new-cpu-features/</guid>
      <pubDate>Sun, 11 Jan 2015 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>对Julia语言的评论</title>
      <description>[AI 摘要] 这篇文章批评了Julia语言在测试、文档、正确性、错误处理、性能基准和社区响应等方面存在的诸多问题和不足。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 这篇文章批评了Julia语言在测试、文档、正确性、错误处理、性能基准和社区响应等方面存在的诸多问题和不足。</div><p>这是一种性能接近C、使用感受类似Python或Ruby的语言，支持可选的类型注解（可以提供给两个静态分析工具之一），对宏有良好支持，对函数式编程也有不错的支持，此外还有更多特性。有什么理由不喜欢呢？不过，我主要不会谈论Julia有多出色，因为你在互联网上能找到大量赞美它的博客文章。</p>

<p>我上次使用Julia（大约在2014年10月）时，遇到了两个新的（对我来说）涉及处理Unicode字符串时抛出错误异常的bug。为了绕过它们，我使用了try/catch，但这当然又遇到了我发现的try/catch相关的一个非确定性bug。我还遇到了一个bug，当传递给函数错误类型的参数时，它会返回完全错误的结果，而不是抛出“没有方法”错误。我花了半小时写一个临时脚本，结果在核心语言中遇到了四个bug。</p>

<p>倒数第二次使用Julia时，我遇到的bug多到数不清；其中最严重的是导致生成图表每个需要30秒，这迫使我改用R/ggplot2进行绘图。首先是这个<a href="https://github.com/dcjones/Gadfly.jl/issues/462" rel="noopener noreferrer">绘制日期无法工作</a>的bug。当我绕过它时，又遇到了一个回归错误，导致绘图<a href="https://github.com/JuliaLang/julia/issues/8631" rel="noopener noreferrer">破坏了核心语言的大部分功能</a>，因此数据处理必须在绘图之前完成。如果我知道确切想要什么，那也没问题，但对于探索性数据分析，我想先绘制一些数据，对数据进行一些处理，然后再绘制它。这样做需要为每张新图表重启REPL。这本来也可以接受，但我的1.7GHz Haswell处理器加载Gadfly需要22秒（通过<code>time</code>计时一个加载Gadfly但不做任何工作的文件），加上加载我使用的其他包还需要大约10秒，这使得我的绘图工作流程变成了：重启REPL，等待30秒，进行更改，绘制图表，查看图表，然后重复。</p>

<p>使用一门年轻的语言遇到bug并不罕见，但考虑到Julia的成熟度水平，它遇到的bug数量超过了应有的份额。如果你观察其测试过程，这基本上是不可避免的。</p>

<p>据我所知，FactCheck是最常用的、类似于现代测试框架的东西，但它几乎未被使用。直到最近，它一直处于无人维护且损坏的状态，但即使现在，绝大多数测试也只是使用<code>@test</code>编写，这基本上就是一个assert。理论上，通过一个充满测试代码和assert的文件来编写好的测试是可能的。但实际上，任何这样做的人对测试都不认真，也不会写出好的测试。</p>

<p>不仅现有测试质量不高，大多数东西根本就没测试过。你可能会指出许多包的代码覆盖率数据并不差，但我上次查看时，覆盖工具有一个bug，导致它只聚合具有非零覆盖率函数的覆盖率统计数据。也就是说，未测试函数中的代码不计入覆盖率统计！这加上使用的薄弱测试覆盖率概念（行覆盖率<sup id="fnref:L"><a href="#fn:L" rel="noopener noreferrer">1</a></sup>），使得覆盖率数据对于判断包是否经过良好测试毫无帮助。</p>

<p>缺乏测试不仅意味着你会遇到回归bug。功能也会随机消失。当REPL被重写时，许多现有的快捷键和其他功能停止了工作。据我所知，这不是因为有人希望它有所不同。而是因为没有测试，重写任何东西都无法避免功能丢失。</p>

<p>与大多数Julia包（以及语言本身）的测试水平相辅相成的，是缺乏良好的错误处理方案。虽然在Julia中你可以轻松使用<code>Nullable</code>（Julia中<code>Some/None</code>的等价物）或错误代码，但最常见的惯用方法是使用异常。而且如果你使用<code>Base</code>中的东西，比如数组或<code>/</code>，你就只能使用异常了。我<a href="https://web.archive.org/web/20140213012902/http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/352949.aspx" rel="noopener noreferrer">不热衷于此</a>，但这没关系——许多可靠的软件都使用异常进行错误处理。</p>

<p>问题在于，因为Julia占据的细分领域并不关心<sup id="fnref:R"><a href="#fn:R" rel="noopener noreferrer">2</a></sup>错误处理，所以编写一个健壮的Julia程序极其困难。当你编写较小的脚本时，你通常希望“快速失败”以便于调试，但对于某些程序，你希望程序做一些合理的事情、继续运行，并可能记录错误。即使按照这个薄弱的健壮性定义，编写一个健壮的程序也很困难。问题存在于多个层面。为了节省篇幅，我只列出两个。</p>

<p>如果我编写一些我希望健壮的东西，我非常希望函数文档包含该函数可能抛出的所有异常。Julia的文档不仅没有做到这一点，而且通常调用某个函数会抛出一个与实现细节相关的随机异常，与API接口毫无关系。我写过的所有必须可靠的东西都是无异常的，所以也许当人们使用异常时这是正常的？不过，在我看来这很奇怪。</p>

<p>另一个问题是捕获异常有时（随机地）不工作。我遇到过一个bug，使用异常导致代码被错误地优化掉了。你可能会说这不公平，因为它是通过模糊测试<sup id="fnref:F"><a href="#fn:F" rel="noopener noreferrer">5</a></sup>发现的，而模糊测试就是要找bug，但这个模糊器并不是在测试异常或表达式。它的实现只是恰好涉及在循环中评估函数调用，并使用<code>try/catch</code>来处理异常。结果发现，如果你这样做，函数可能根本不会被调用。这不是使用模糊测试生成数十亿个测试，其中一个失败的情况。而是尝试一件事，其中一个失败的情况。那个bug现在已经修复了，但还有一个顽固的bug，导致异常有时无法被<code>catch</code>捕获，这对于把东西放在<code>try/catch</code>块中以防止异常传播到顶层并杀死你的程序来说，是非常糟糕的消息。</p>

<p>当我在<code>Base</code>中搜索实际捕获异常并根据特定异常执行操作的情况时，我只找到了一个实例。现在，是我用less在看grep输出，所以我可能漏掉了一些，但这并不常见，而且搜索常见包也发现了类似的错误处理代码与其他代码的比例。Julia的开发者不关心错误处理，所以它充满bug且不完整。我曾问过这个问题，得到的回复是异常不能用并不重要，因为你根本不应该使用异常——你应该使用Erlang风格的错误处理，在错误时杀死整个进程，并构建事务性健壮系统，能够承受随机进程被杀死的情况。且不说在缺乏Erlang支持的那种语言中实现这一点有多困难，在Erlang中你可以轻松启动数百万个进程。而在Julia中，如果你只加载一两个常用包，启动一个全新的Julia实例可能需要半分钟或一分钟。要启动一百万个独立实例，按每个30秒计算，大约需要两年时间。</p>

<p>既然我们大体上在讨论API，错误条件并不是<code>Base</code> API唯一不尽如人意的地方。从函数命名到参数顺序，约定在很多方面都不一致。有些集合上的方法将集合作为第一个参数，有些则不是（例如，replace首先接受字符串，然后是正则表达式，而match首先接受正则表达式，然后是字符串）。</p>

<p>更普遍地说，<code>Base</code> API在Julia目标细分领域之外往往没有意义。例子太多无法一一列举，但考虑这个：UDP接口在接收到部分数据包时抛出异常。这真的很奇怪，也没有帮助。<a href="https://github.com/JuliaLang/julia/pull/5697" rel="noopener noreferrer">多个人在这个issue上表示了这一点</a>，但开发者还是决定抛出异常。Julia实现者在他们熟悉的线性代数等领域有很好的直觉。但他们终究是人，他们在不熟悉的领域直觉就没那么好了。问题是，即使面对评论指出那可能不是最好的主意，他们还是坚持自己的直觉。</p>

<p>另一件对我来说是个问题的事是，我不是包管理器设计的目标受众。它以一种巧妙的方式基于git，允许人们做各种我永远不会做的事情。所有这一切的结果是，当我运行<code>Pkg.status()</code>时，它需要在每个包上执行<code>git status</code>，这使其极其缓慢；我关心的大多数其他<code>Pkg</code>操作也因为类似原因而很慢。</p>

<p>如果它具备我最想要的功能，即指定包的精确版本并安装多个相互冲突的版本<sup id="fnref:T"><a href="#fn:T" rel="noopener noreferrer">3</a></sup>，那或许还可以。由于核心语言库和包中存在大量回归，我经常需要使用某些包的旧版本才能让某些功能正常工作，这可能需要其依赖项的旧版本。目前没有非变通的方法来做到这一点。</p>

<p>既然我在谈论我比核心开发者更在意的问题，那还包括基准测试。该网站展示了一些听起来令人印象深刻的、相对于其他语言的加速数字。但它们都是与实际工作负载相去甚远的基准测试。即使你具有强大的工作负载特征分析和系统架构（计算机架构，而非软件架构）背景，也很难从微基准测试数字推断任何接近实际工作负载的性能结果。据我所知，Julia的性能优化是基于一组更大的类似基准测试进行的，这出于同样的所有原因存在问题。Julia实际上相当快，但这种临时的基准测试基本上保证了性能未得到充分发挥。此外，基准测试的编写方式对其他语言不公平。来自其他语言社区的人提交以惯用方式重写基准测试的PR时经常被拒绝。Julia网站声称“所有基准测试都是为了测试特定算法的性能，以合理的惯用法表达”，并表示为特定语言进行惯用的调整将是不公平的。然而，如果你查看Julia代码，你会注意到它们是以避免执行许多会严重降低性能的操作的方式编写的。如果你关注邮件列表，你会看到许多编写Julia代码的直观方式会导致性能非常差。Julia基准测试避免了这些陷阱，但其他语言的代码远没有那样仔细地编写；事实上，恰恰相反。</p>

<p>我刚刚列举了Julia的一堆问题。我相信对于开源项目抱怨的标准回应是，你自己为什么不去修复这些bug，你这个自以为是的家伙？嗯，我试过。一方面，<a href="//danluu.com/everything-is-broken/#julia" rel="noopener noreferrer">bug太多了，我常常甚至不去提交bug，更不用说修复了，因为这太打扰人了</a>。但更大的问题是对新贡献者的障碍。我花了几天时间修复bug（主要是调试，不是写代码），这几乎足以让我进入GitHub贡献者排行榜的前40名。我的意思不是我贡献了很多。而是我贡献不多，却仍然排在40名左右紧下方。</p>

<p>有很多摩擦阻碍人们为Julia做贡献。构建经常失败或测试不通过。当我为GitHub上的语言<a href="//danluu.com/broken-builds/" rel="noopener noreferrer">统计Travis CI的运行时间</a>时，Julia基本上并列倒数第一。这不仅仅是一个统计上的奇特现象：我第一次尝试修复问题时，由于有人未经审查直接将错误代码提交到master，构建在将近一周的时间里非确定性地失败。我花了大约一周修复了一些东西，然后休息了一下。下次我回来修复问题时，测试因为又一次错误的提交而失败了一天，我放弃了修复bug的想法。测试如此频繁地失败，在考虑到覆盖率很差的情况下，甚至比听起来更糟。即使构建“正常运行”，它也使用<a href="http://aegis.sourceforge.net/auug97.pdf" rel="noopener noreferrer">递归Makefile</a>，并且经常失败，提示你需要运行<code>make clean</code>然后重新构建，这需要半小时。当你这样做时，它经常失败，提示你需要运行<code>make clean all</code>然后重新构建，这需要一小时。然后还有些概率会再次失败，你需要手动清理<code>deps</code>目录然后重新构建，这需要更长时间。而这还是好的情况！糟糕的情况是构建非确定性地失败。这些是在使用递归Make时众所周知的问题，在<a href="http://aegis.sourceforge.net/auug97.pdf" rel="noopener noreferrer">《递归Make被认为有害》</a>中大约1997年就有描述。</p>

<p>而这甚至不是对Julia核心做贡献的最大障碍。最大的障碍是绝大多数核心代码编写时没有任何意图标记（注释、有意义的变量名、断言、有意义的函数名、短变量或函数名的解释、设计文档等）。所有这些都带来了在Julia核心深处调试和修复bug的代价。我碰巧认识一位Julia核心贡献者（目前在GitHub排名中列第二），当我向他请教我正在研究的一些比较晦涩的函数时，他也没能弄明白。他的建议是询问邮件列表，但对于核心代码库中真正晦涩难懂的代码，也许只有一到三个人真正理解代码，如果他们太忙没空回复，你就没辙了。</p>

<p>我不介意在业余时间为修复别人的bug而免费工作。事实上，我经常这样做，而且我发现自己很享受其中。但我年纪大了，脾气也不太好，没那个闲心去解读那些连核心开发者都搞不懂的晦涩代码。</p>

<p>所有这些并不是说Julia不好，而是核心团队的关注点与我的关注点大不相同。这是在抱怨博客文章中应该建议替代方案或发出行动呼吁的地方，但我不知道这里两者是否都合理。纯粹的技术问题，如加载时间慢或包管理器，正在或将会被修复，所以对此没什么可说的。至于流程问题，比如不写测试、不写内部文档、以及将未经审查且有时具有破坏性的更改直接提交到master，嗯，这“容易”<sup id="fnref:E"><a href="#fn:E" rel="noopener noreferrer">4</a></sup>通过添加强制人们为代码编写测试和文档的代码审查流程来解决，但这并非没有代价。</p>

<p>一小群<a href="http://lwn.net/2000/0824/a/esr-sharing.php3" rel="noopener noreferrer">极具天赋</a>的开发者，基本上能在集体头脑中容纳所有代码，可以取得巨大进展，同时摒弃任何不直接编码的东西，代价是让其他人更难贡献。这值得吗？很难说。如果你必须让<a href="https://github.com/JeffBezanson" rel="noopener noreferrer">Jeff</a>、<a href="https://github.com/Keno" rel="noopener noreferrer">Keno</a>和其他高产核心贡献者慢下来，而你得到的只是像我这样几个废物，那可能不值得。如果你得到一千个像我这样的人，那可能是值得的。现实情况介于两者之间的模糊地带，可能值得也可能不值得。这个计算因为大部分收益来自长期，而代价不成比例地落在短期而变得复杂。我曾有一个<a href="http://morrow.ece.wisc.edu/" rel="noopener noreferrer">工程学教授</a>声称每个工程问题的答案都是“看情况”。Julia应该怎么做？看情况。</p>

<h3 id="2022-update">2022年更新</h3>

<p>这篇文章最初提到了Julia社区有多友好，但我删除了这个说法，因为在回应看来这并不准确。许多人给予了高度支持，比如这位Julia核心开发者：</p>

<p><img src="https://danluu.com/images/julialang/julia_community.png"></p>

<p>然而，也有一些人的回应相当刻薄，我认为不能说一个社区是友好的，如果回应主要是积极的，但有相当一部分是刻薄的，因为一点点的刻薄就足以让一个群体显得不友好。此外，由于<a href="https://yosefk.com/blog/people-can-read-their-manager-mind.html" rel="noopener noreferrer">社区倾向于从顶层获取方向</a>，而且几位Julia的共同创建者一直对这篇文章持负面态度，因此对这篇文章的看法随着时间的推移变得更加负面。</p>

<p>现在，谈谈这些问题在多大程度上得到了解决。联合创始人的最初回应是这些问题并不真实，文章是严重错误的。随着时间的推移，随着一些问题得到处理，回应变成了文章已过时，所有问题都已修复，例如<a href="https://news.ycombinator.com/item?id=11073642" rel="noopener noreferrer">2016年Julia联合创始人之一的回应</a>：</p>

<blockquote>
<p>Dan文章中主要有效的抱怨是：</p>

<ol>
<li><p>测试和覆盖率不足。Julia基础代码的覆盖率现在达到84%，而他写这篇文章时大约是50%。虽然你永远可以拥有更多测试（并且正在发生），我肯定不认为这是目前的一个主要抱怨。</p></li>
<li><p>包问题。Julia现在有包预编译，因此包加载相当快。包管理器本身被重写以使用libgit2，这使其快了很多，尤其是在Windows上，因为通过shell执行非常慢。</p></li>
<li><p>Travis运行时间。现在好多了。Dan写那篇文章时有一个特定的神秘问题。那个问题已经解决了。我们如今也在AppVeyor上做Windows CI。</p></li>
<li><p>Julia内部文档。鉴于现在相当全面的开发者文档，很难认为这一点未得到解决：<a href="http://julia.readthedocs.org/en/latest/devdocs/julia/" rel="noopener noreferrer">http://julia.readthedocs.org/en/latest/devdocs/julia/</a></p></li>
</ol>

<p>所以那篇博客文章中提出的合理问题已经解决了。</p>
</blockquote>

<p>最热门的回应是：</p>

<blockquote>
<blockquote>
<p>主要<i>有效</i>的抱怨 [...] <i>合理</i>的问题 [...] 
</p></blockquote>
</blockquote>

<p>这是一种非常消极对抗、狡猾的措辞。我建议重新考虑在公开讨论回应中使用这种语气。</p>

<p>你可以根本不提其他抱怨，或者至少用更友好的语言将其搁置。例如“...主要的<i>可操作</i>抱怨...”或“主要的<i>技术</i>抱怨...”</p>

<p>撇开语气问题不谈，我会说文章中的主要问题，即核心团队对正确性的态度，既是一个合理的问题，也是一个未解决的问题，正如我们将在看到具体声称已修复的问题也同样未解决时所看到的那样。</p>

<p>关于正确性，如果正确性问题已经解决，我们就不会继续看到阻碍使用的严重bug，但我有几个朋友连续使用Julia好几年，直到他们受够了正确性问题，并发给我不少他们在2016年评论之后遇到的严重bug，例如<a href="https://github.com/JuliaStats/Distributions.jl/issues/1241" rel="noopener noreferrer">从分布中抽样得到错误结果</a>、<a href="https://github.com/JuliaStats/StatsBase.jl/issues/642" rel="noopener noreferrer">从数组中抽样产生错误结果</a>、<a href="https://github.com/JuliaLang/julia/issues/39183" rel="noopener noreferrer">乘积函数，即乘法，产生错误结果</a>、<a href="https://github.com/JuliaStats/Statistics.jl/issues/119" rel="noopener noreferrer">分位数产生错误结果</a>、<a href="https://github.com/JuliaStats/Statistics.jl/issues/22" rel="noopener noreferrer">均值产生错误结果</a>、<a href="https://github.com/JuliaLang/julia/issues/39379" rel="noopener noreferrer">数组索引错误</a>、<a href="https://github.com/JuliaLang/julia/issues/49422" rel="noopener noreferrer">除法产生错误结果</a>、<a href="https://github.com/JuliaLang/julia/issues/49422" rel="noopener noreferrer">浮点数转整数产生错误结果</a>、<a href="https://github.com/JuliaStats/Statistics.jl/issues/144" rel="noopener noreferrer">分位数再次产生错误结果</a>、<a href="https://github.com/JuliaStats/Statistics.jl/issues/140" rel="noopener noreferrer">均值再次产生错误结果</a>等。</p>

<p>持续有大量来自Julia的严重bug，以及许多其他人指出他们遇到严重bug，例如<a href="https://kidger.site/thoughts/jax-vs-julia/" rel="noopener noreferrer">这里</a>：</p>

<blockquote>
<p>我非常不情愿地记得有一次我的一个Julia模型训练失败。我断断续续花了几个月时间试图让它工作，尝试了我能想到的每一个技巧。</p>

<p>最终——最终！——我找到了错误：<b>Julia/Flux/Zygote返回了不正确的梯度</b>。在花了这么多精力与上述第1点和第2点搏斗之后，这一刻我干脆放弃了。两个小时的开发工作后，我成功在PyTorch中训练了模型。</p>
</blockquote>

<p>以及<a href="https://discourse.julialang.org/t/state-of-machine-learning-in-julia/74385/13" rel="noopener noreferrer">这里</a></p>

<blockquote>
<p>我曾被Zygote/ReverseDiff.jl中不正确的梯度bug坑过。这让我损失了几周的生命，彻底动摇了我对整个Julia自动微分生态的信心。[...]在我使用PyTorch/TF/JAX的这些年里，我从未遇到过不正确的梯度bug。</p>
</blockquote>

<p>以及<a href="https://discourse.julialang.org/t/state-of-machine-learning-in-julia/74385/21" rel="noopener noreferrer">这里</a></p>

<blockquote>
<p>自从我开始使用Julia，我遇到了两个Zygote的bug，让我的工作延迟了几个月。积极的一面是，这迫使我深入代码，学到了很多关于我所使用库的知识。但我发现自己处于这样一种情况：这变得太多了，我需要花很多时间调试代码，而不是做气候研究。</p>
</blockquote>

<p>尽管持续出现bug，Julia联合创始人以及许多核心社区成员的公开回应通常声称，就像对这篇文章一样，这些问题很快就会得到解决（例如，<a href="https://news.ycombinator.com/item?id=31263516" rel="noopener noreferrer">这里一些核心开发者对最近一篇文章的评论，称所有问题都在处理中，很快就会解决</a>，或者这个<a href="https://news.ycombinator.com/item?id=22138071" rel="noopener noreferrer">2020年的评论，称2016年有严重正确性问题，但“现在”已经好了</a>，等等）。</p>

<p>开发者没有认真对待正确性问题或其他问题，反而发表诸如<a href="https://danluu.com/images/julialang/julia-testing-seriously.jpg" rel="noopener noreferrer">一位Julia联合创始人的以下评论（由我的一位朋友在我又遇到一个阻碍使用的bug时转给我）</a>的言论：</p>

<blockquote>
<p>关于Julia不认真对待测试的说法...我不明白。我们花了大量时间和精力对所有东西进行彻底测试。我真的不知道还有哪个开源项目像这样进行彻底的端到端测试。</p>
</blockquote>

<p>普遍的声明是，Julia不仅已经修复了它的正确性问题，而且在正确性方面已经做到了最好。</p>

<p>关于包问题，声称是到2016年包加载时间问题已得到解决。但这仍然是我认识的Julia使用者的主要抱怨，例如，Jamie Brandon在2022年放弃了使用Julia，因为他的CSV解析管道需要两分钟才能运行，其中大部分时间是包加载。另一个例子是，在2020年，<a href="https://news.ycombinator.com/item?id=24746057" rel="noopener noreferrer">在一个Julia开发者吹嘘Julia在重复加载相同CSV（在一个循环中，而不是多次运行脚本）这个奇特工作负载上比R快得多的基准测试中</a>，一些人指出这不切实际，因为Julia的包加载时间非常长，称打开CSV包需要2秒，加载一个绘图库需要104秒。在2022年，针对包加载速度慢的评论，一位Julia开发者对每个问题回应说每个都会解决；关于包加载，他们说</p>

<blockquote>
<p>我们接近原生代码缓存了，还有更多：<a href="https://discourse.julialang.org/t/precompile-why/78770/8" rel="noopener noreferrer">https://discourse.julialang.org/t/precompile-why/78770/8</a>。正如你也会读到的，困难在于Julia在可组合性和激进特化方面做出的重要权衡...但这不是根本问题，可以克服。是的，有一些痛苦，但最终希望我们能得到接近两全其美的东西。</p>
</blockquote>

<p>奇怪的是，这些问题在2020年和2022年仍然存在，而Julia联合创始人曾在2016年声称包加载时间问题已经修复。但这是我们看到的Julia公关的一般模式。对于任何特定的批评，回应要么是：不合理，要么是即将修复，或者当批评超过一年历史时，是已经修复。但我们可以从随时间变化的回应中看到，那些“已经修复”或“很快修复”的问题，在声称修复多年后实际上并未修复。确实有进展，但如果说包加载时间问题已修复且“包加载相当快”，而当在Julia中加载CSV并使用标准绘图库（相当于ggplot2）生成图表需要近两分钟时，这是不公平的。同样，对于核心库、Julia本身以及那些被命名为Julia神奇之处的库（例如，<a href="https://news.ycombinator.com/item?id=29684214" rel="noopener noreferrer">自动微分经常被提及为Julia在功能上的巨大优势</a>，但<a href="https://news.ycombinator.com/item?id=31268796" rel="noopener noreferrer">当谈到bug时，这些bug不算数，因为它们不在Julia本身</a>（最后那条评论当然有Julia开发者的回复，称所有问题很快都会解决））中仍有持续出现的正确性问题，这是不公平的。</p>

<p>这里有一种障眼法，即许多Julia联合创始人以及核心开发者的条件反射式回应是，对于任何特定问题，都用一个在Hacker News或Twitter上听起来貌似有理的评论来搪塞，尤其是<a href="https://twitter.com/danluu/status/1524949021252411392" rel="noopener noreferrer">对于不了解认真使用过Julia的人的人来说</a>。这对于一个像Julia这样的新兴语言来说是好的公关，因为大多数潜在用户不会认识认真使用过它的人，而且这些条件反射式的评论如果你不深入了解，听起来很合理。</p>

<p>我在这里使用“条件反射式”这个词，因为似乎一些Julia联合创始人会对任何批评都进行反驳，例如<a href="https://twitter.com/danluu/status/1526256307736412160" rel="noopener noreferrer">这里</a>，一位核心开发者对一篇关于阻碍使用的bug的文章回应说有bug实际上很好，以及<a href="https://twitter.com/Viral_B_Shah/status/1315425480548462592" rel="noopener noreferrer">这里</a>，在我指出有些人评论说厌倦了Julia开发者误导性的基准测试做法时，一位Julia联合创始人出来说“我想在此声明，我不同意你在这个帖子中对Julia的陈述。”但我在帖子中的陈述只是存在类似<a href="https://news.ycombinator.com/item?id=24748582" rel="noopener noreferrer">https://news.ycombinator.com/item?id=24748582</a>的评论。声明不同意存在那种评论是相当荒谬的，因为它们显然存在。</p>

<p>另一个条件反射式回应的例子是<a href="https://news.ycombinator.com/item?id=31401814" rel="noopener noreferrer">这个2022年的帖子</a>，其中一位尝试过Julia但因遇到太多需要数周调试的bug而停止将其用于严肃工作的人建议Julia生态系统需要重写，因为社区的态度和文化导致了大量正确性问题。一位核心Julia开发者“反驳”该评论说，事情一直在被重写，并举了为性能原因重写的例子。众所周知，性能重写是引入bug的好方法，使得“反驳”实际上成为一种反反驳。但是，就像许多核心Julia开发者一样，这个人看到一个问题（重写不够），就条件反射式地否认了，说重写已经足够了。</p>

<p>如果你花点时间阅读它们并查看历史背景，这些条件反射式的回应显然是站不住脚的，但这种“否认、否认、否认”的回应通常是有效的公关，并且对Julia一直有效，所以这样做是可以理解的。例如，<a href="https://news.ycombinator.com/item?id=22138071" rel="noopener noreferrer">在这个2020年的评论中，它表明2016年关于正确性已修复的评论，声称2016年有严重问题但“现在”2020年已经好了</a>，有人回应说“谢谢，这非常令人振奋。”因为这让他们不再担心仍然存在问题。当然，你可以在2022年的讨论中看到基本上相同的讨论，但2022年阅读讨论的人通常不会回溯去看相同的讨论发生在2020年、2016年、2013年等。</p>

<p>关于构建运行时间，声称导致运行时间问题的具体问题已修复，但我那里的评论是关于用“在我机器上能运行”来搪塞问题一段时间的态度。正如我们从上面的例子中看到的，这种搪塞问题的元问题仍在继续。</p>

<p>关于最后一个声称是合理且已修复的问题，即文档，这仍然是社区的常见抱怨，例如<a href="https://news.ycombinator.com/item?id=17730094" rel="noopener noreferrer">2018年这里</a>（在声称2016年文档已修复的两年后），<a href="https://news.ycombinator.com/item?id=20589167" rel="noopener noreferrer">2019年这里</a>，<a href="https://news.ycombinator.com/item?id=31398705" rel="noopener noreferrer">2022年这里</a>，等等。在一个更冗长的抱怨中，一个人指出</p>

<blockquote>
<p>最大的问题，而且他们似乎不愿意真正解决的问题是，实际上使用类型系统做任何很酷的事情都需要你完全依赖可能不存在（或已过时）的文档。</p>
</blockquote>

<p>另一个人呼应了这种情绪：</p>

<blockquote>
<p>这确实是一个重要的问题。</p>
</blockquote>

<p>当然，<a href="https://news.ycombinator.com/item?id=17741797" rel="noopener noreferrer">有回应说这很快会修复</a>，这通常如此。然而，你仍然可以找到人们抱怨文档。</p>

<p>如果你回顾关于Julia正确性问题的讨论，另外三个常见的辩护是：万物皆有bug、bug很快修复、测试实际上很棒因为X经过了很好的测试。你可以在<a href="https://groups.google.com/g/julia-users/c/GyH8nhExY9I/m/0BznqtD5VJgJ" rel="noopener noreferrer">2014年这里</a>以及<a href="https://news.ycombinator.com/item?id=31397499" rel="noopener noreferrer">2022年这里</a>（当然中间也有）看到“万物皆有bug”的例子，仿佛所有非零错误率都是相同的，尽管许多开发者表示他们停止使用Julia用于工作并切换到其他生态系统，因为虽然万物皆有bug，但所有非零数字当然并不相同。bug很快修复有时并不属实（例如，本文中链接的许多bug已经开放相当长一段时间且仍然开放），并且这也是<a href="https://twitter.com/danluu/status/1270987200545435648" rel="noopener noreferrer">一种用于转移对直接导致产生异常大量新bug的做法的关注的经典辩护</a>。如上文许多链接所述，调试正确性问题可能需要数周或数月，因为许多正确性问题是“静默返回错误结果”的形式，并且如上所述，我遇到了一个bug，异常有时非确定性地未被捕获。诚然，在某些情况下，这类bug在发现后可能很快修复，但这些问题仍然给用户带来大量调试时间。我们在上面看到了“测试实际上很棒因为X经过了很好测试”的例子。如果你想要更近的例子，<a href="https://news.ycombinator.com/item?id=31397499" rel="noopener noreferrer">这里是2022年的一个例子</a>，其中一个人说在Julia中遇到的正确性bug比他们在几十年编程中使用的任何其他生态系统都多，一位核心Julia开发者回应说，Julia中许多东西都经过了很好的测试，比如libuv，好像对某些组件进行良好测试就能成为抵御其他组件bug的护身符。这显然是荒谬的，就像说一扇开着的门不能不安全，因为它也有非常坚固的墙，但这是核心Julia开发者常用的辩护。当然，还有对撰写关于Julia的文章的赤裸裸的FUD。例如，在2022年，关于Yuri Vishnevsky关于Julia bug的文章，一位Julia联合创始人说<a href="https://news.ycombinator.com/item?id=31881164" rel="noopener noreferrer">“Yuri的批评并不是Julia作为语言存在正确性bug，而是某些库与常见操作组合时存在bug（其中许多现已解决）。”</a>。这当然是完全不正确的。在与Yuri的交谈中，他向我指出，他特别包含了核心语言和核心库bug的例子，因为这些bug发生得太频繁了，并且核心Julia人士假装那些不存在，而且他们的FUD似乎奏效了，因为人们通常回应时仿佛他们的评论不是不真实的。如上所述，这种对简单事实的断然否认是非常有效的，所以人们采用它是可以理解的，但就个人而言，这不是我的口味。</p>

<p>需要明确的是，我并不天生反对软件有bug。正如我所提到的，<a href="https://twitter.com/danluu/status/1487232684363370496" rel="noopener noreferrer">我认为“快速行动，打破常规”可能是一个好价值观</a>，因为它明确表示速度比正确性更受重视。Julia创造者以及核心开发者的评论表明Julia不仅高度可靠和正确，而且实际上是世界级的（“我们花了大量时间和精力对所有东西进行彻底测试。我真的不知道还有哪个开源项目像这样进行彻底的端到端测试。”等等）。但是，通过显示的偏好，我们可以看到Julia的价值观是“快速行动，打破常规”。</p>

<h3 id="appendix-blog-posts-on-julia">附录：关于Julia的博客文章</h3>

<ul>
<li>2014年：<a href="https://danluu.com/julialang/" rel="noopener noreferrer">这篇文章</a></li>
<li>2016年：<a href="https://www.zverovich.net/2016/05/13/giving-up-on-julia.html" rel="noopener noreferrer">Victor Zverovich</a>
<ul>
<li>Julia在不具代表性的微基准测试上吹嘘高性能，但在实践中经常性能不佳</li>
<li>复杂的代码库导致许多bug</li>
</ul></li>
<li>2022年：<a href="https://weissmann.pm/julialang/" rel="noopener noreferrer">Volker Weissman</a>
<ul>
<li>文档质量差</li>
<li>令人困惑/不清晰的错误消息</li>
<li>基准测试声称性能良好，但基准测试工作负载不切实际，实践中性能经常不佳</li>
</ul></li>
<li>2022年：<a href="https://kidger.site/thoughts/jax-vs-julia/" rel="noopener noreferrer">Patrick Kidger</a> 将Julia与JAX和PyTorch进行比较
<ul>
<li>文档质量差</li>
<li>被广泛依赖的重要库中存在正确性问题</li>
<li>难以理解的错误消息</li>
<li>代码质量差，导致bug和其他问题</li>
</ul></li>
<li>2022年：<a href="https://yuri.is/not-julia/" rel="noopener noreferrer">Yuri Vishnevsky</a>
<ul>
<li>在语言运行时和被大量依赖的核心库中存在许多非常严重的正确性bug</li>
<li>文化/态度持续导致大量bug，“Julia及其包拥有我用过的任何编程系统中最高的严重正确性bug率，我是2000年代中期从Visual Basic 6开始编程的”</li>
<ul>
<li>持续出现的严重bug与核心Julia开发者和联合创始人声称Julia非常稳固且具有出色正确性特性的评论形成鲜明对比</li>
</ul>
</ul></li>
</ul>

<p><small>
感谢（或反感谢）Leah Hanson过去几个月催促我写这篇文章。这不是我通常会写的东西，但这里的问题在私下提出时被反复忽视。例如，当我提出测试问题时，我被告知Julia比大多数项目测试得更好。虽然这在某些技术意义上是真的（GitHub上中位数的项目可能没有零测试，所以任何非零测试都高于平均水平），但我没觉得这是一个有意义的反驳（而不是一个回复说Julia仍然预期大部分未经测试，因为它处于alpha状态）。在许多类似话题上得到类似回应后，我停止使用Julia。通常那就结束了，但Leah真的希望这些问题不再被忽视，所以我写了这篇文章。

另外，感谢Leah Hanson、Julia Evans、Joe Wilder、Eddie V、David Andrzejewski、@sasuke___420@mastodon.social和Yuri Vishnevsky的评论/修正/讨论。
</small></p>
<div>

<hr>

<ol>
<li id="fn:L">我这里的意思是，即使你有100%的行覆盖率，仍然可能出现大量bug。并不是说行覆盖率不好，而是它不够，远远不够。而且因为它不够，当你不仅没有100%行覆盖率，甚至没有100%函数覆盖率时，这是一个相当糟糕的迹象。
 <a href="#fnref:L" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fnR">我将使用几次“关心”这个词，当我使用时，我指的是特定的含义。当我说关心时，我指的是口语化的<a href="http://en.wikipedia.org/wiki/Revealed_preference" rel="noopener noreferrer">显示偏好</a>意义上的关心。还有另一个意义上的关心，即每个人都关心测试和错误处理，就像每个政客都关心家庭价值观一样。但那种关心与我关心的不同，我关心的是具体行动。
 <a href="#fnref:R" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:T">技术上可以安装多个版本，但这个过程完全是变通方法。
 <a href="#fnref:T" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:E">所谓“容易”，我是指极其困难。技术修复可能容易，但流程和文化的修复几乎总是困难的。
 <a href="#fnref:E" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:F">模糊测试（Fuzzing）是一种自动化软件测试技术，它涉及向程序提供无效、意外或随机数据作为输入。
 <a href="#fnref:F" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 19215 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/julialang/</link>
      <guid isPermaLink="false">https://danluu.com/julialang/</guid>
      <pubDate>Sun, 28 Dec 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>整数溢出检查的性能开销</title>
      <description>[AI 摘要] 该文分析了启用整数溢出检查的性能开销，通过基准测试表明在不输出详细诊断信息时开销很小（约3%），但输出诊断信息会因编译器优化问题导致开销显著增加（压缩任务可达28%）。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 该文分析了启用整数溢出检查的性能开销，通过基准测试表明在不输出详细诊断信息时开销很小（约3%），但输出诊断信息会因编译器优化问题导致开销显著增加（压缩任务可达28%）。</div><p>启用整数溢出检查预计会带来多少开销？通过编译器标志或内置函数，我们应该能够使用一个基于<code>add</code>和<code>sub</code>设置的溢出标志进行条件分支来执行检查。类似这样的代码：</p>

<pre><code>add     %esi, %edi
</code></pre>

<p>应该会变成类似这样的形式：</p>

<pre><code>add     %esi, %edi
jo      &lt;handle_overflow&gt;
</code></pre>

<p>假设该分支总是被正确预测（对于大多数代码应该是这样），分支的开销包括执行正确预测的未执行分支的成本、分支在分支历史表中造成的污染以及解码分支的成本（在x86上，<code>jo</code>和<code>jno</code>不会与<code>add</code>或<code>sub</code>融合，这意味着在快速路径上，该分支将占用每个周期从解码指令缓存中取出的4条指令中的1条）。在最坏的情况下（这可能发生在紧密优化的循环中，但通常应该很少见），这可能造成每次<code>add</code>或<code>sub</code>操作小于2倍的惩罚，再加上一些模糊的分支历史污染惩罚，这在微基准测试中确实很难测量。总的来说，我们可以将2倍作为总惩罚的悲观估计。</p>

<p>2倍听起来很多，但应用程序花费多少时间在加法和减法上？如果我们看一下最常用的“工作站”整数工作负载基准测试<a href="http://www.spec.org/cpu2006/" rel="noopener noreferrer">SPECint</a>，其构成可能是40%加载/存储操作、10%分支和50%其他操作。在这50%“其他”操作中，可能有30%是整数加法/减法操作。如果我们粗略估计加载/存储操作比加法/减法操作昂贵10倍，其他操作与加法/减法操作成本相同，那么对加法/减法操作施加2倍惩罚应该会导致<code>(40*10+10+50 + 12) / (40*10+10+50) = 3%</code>的惩罚。分支惩罚是2倍、加法/减法操作仅比加载/存储操作快10倍、加法/减法操作不比其他“其他”操作更快，这些都是悲观假设，因此这个估计对于大多数工作负载应该处于高端。</p>

<p></p>

<p>John Regehr，他对整数溢出检查进行了严肃的分析，估计惩罚应该在<a href="http://blog.regehr.org/archives/1154" rel="noopener noreferrer">大约5%</a>左右，这与我们粗略估算的结果处于同一数量级。</p>

<p>SPEC许可证费用为800美元，因此我们转而对bzip2（它是SPECint的一个组成部分）进行基准测试，而不是支付800美元购买SPECint。使用<code>clang -O3</code>编译bzip2，与使用<code>clang -O3 -fsanitize=signed-integer-overflow,unsigned-integer-overflow</code>（在溢出时打印警告）编译相比，以及与使用<code>-fsanitize-undefined-trap-on-error</code>编译并启用未定义溢出检查（在未定义溢出时导致崩溃）相比，我们在我机器上恰好存放的1GB代码和二进制文件的压缩和解压缩上得到了以下结果：</p>

<table>
<thead>
<tr>
<th>选项</th>
<th>压缩（秒）</th>
<th>解压缩（秒）</th>
<th>压缩（时间比率）</th>
<th>解压缩（时间比率）</th>
</tr>
</thead>

<tbody>
<tr>
<td>普通编译</td>
<td>93</td>
<td>45</td>
<td>1.0</td>
<td>1.0</td>
</tr>

<tr>
<td>启用溢出检查（打印信息）</td>
<td>119</td>
<td>49</td>
<td>1.28</td>
<td>1.09</td>
</tr>

<tr>
<td>启用溢出检查（触发陷阱）</td>
<td>94</td>
<td>45</td>
<td>1.01</td>
<td>1.00</td>
</tr>
</tbody>
</table>

<p>在表格中，比率是运行时间的相对比率，而不是压缩比率。<code>-fsanitize-undefined-trap-on-error, 解压</code>与<code>普通编译, 解压</code>之间的差异实际上不是0，但如果我们以整秒为单位测量，四舍五入后是0。如果我们启用良好的错误消息，解压缩并不会慢太多（45秒对49秒），但压缩会慢很多（93秒对119秒）。如果我们输出详细的诊断信息，整数溢出检查在压缩时的惩罚是28%，解压缩时是9%，但如果我们不输出诊断信息，则几乎没有惩罚。这怎么可能？Bzip2通常有几个无符号整数溢出。如果我修补代码以移除这些溢出，使诊断打印代码路径永远不会被执行，它仍然会导致巨大的性能损失。</p>

<p>让我们看看当我们只执行一些类似于这样的加法时的惩罚：</p>

<pre><code>for (int i = 0; i &lt; n; ++i) {
  sum += a[i];
}
</code></pre>

<p>在我的机器（3.4 GHz Sandy Bridge）上，使用<code>-fsanitize=signed-integer-overflow,unsigned-integer-overflow</code>编译后，这竟然慢了大约6倍。查看反汇编，普通版本使用SSE加法，而启用sanitizer的版本使用普通加法。好吧，6倍对于未检查的SSE加法与检查过的加法来说似乎合理。</p>



<p>这是一个添加两个整数的函数的反汇编，首先用<code>-O3</code>编译，然后用<code>-O3 -fsanitize=signed-integer-overflow,unsigned-integer-overflow</code>编译。</p>

<pre><code>0000000000400530 &lt;single_add&gt;:
  400530:       01 f7                   add    %esi,%edi
  400532:       89 f8                   mov    %edi,%eax
  400534:       c3                      retq
</code></pre>

<p>编译器对<code>-O3</code>版本的处理相当合理。根据标准的AMD64调用约定，参数通过<code>esi</code>和<code>edi</code>寄存器传入，并通过<code>eax</code>寄存器传出。与内联的<code>add</code>指令相比有一些开销，因为我们需要将结果移动到<code>eax</code>，然后从函数调用返回，但考虑到这是一个函数调用，它是一个完全合理的实现。</p>

<pre><code>000000000041df90 &lt;single_add&gt;:
  41df90:       53                      push   %rbx
  41df91:       89 fb                   mov    %edi,%ebx
  41df93:       01 f3                   add    %esi,%ebx
  41df95:       70 04                   jo     41df9b &lt;single_add+0xb&gt;
  41df97:       89 d8                   mov    %ebx,%eax
  41df99:       5b                      pop    %rbx
  41df9a:       c3                      retq
  41df9b:       89 f8                   mov    %edi,%eax
  41df9d:       89 f1                   mov    %esi,%ecx
  41df9f:       bf a0 89 62 00          mov    $0x6289a0,%edi
  41dfa4:       48 89 c6                mov    %rax,%rsi
  41dfa7:       48 89 ca                mov    %rcx,%rdx
  41dfaa:       e8 91 13 00 00          callq  41f340
&lt;__ubsan_handle_add_overflow&gt;
  41dfaf:       eb e6                   jmp    41df97 &lt;single_add+0x7&gt;
</code></pre>

<p>编译器对<code>-O3 -fsanitize=signed-integer-overflow,unsigned-integer-overflow</code>版本的处理并不合理。优化专家Nathan Kurz对clang的输出有这样的评论：</p>

<blockquote>
<p>这很糟糕（尽管并不罕见）是编译器生成的代码。不知为何，编译器决定使用%ebx作为加法的结果寄存器。一旦它这样做，它就必须完成其余操作。问题在于为什么它不用一个临时寄存器，为什么它觉得有必要执行这个移动，以及将来可以做些什么来防止它这样做。如您所知，%ebx是一个“被调用者保存”寄存器，这意味着当函数返回时它必须具有相同的值——因此有push和pop。如果编译器只是在不额外移动的情况下执行加法，将输入留在%edi/%esi中（就像在非检查版本中所做的那样），这就不是必需的。我猜这是某些早期优化过程的残留，但不知何故%ebx的幽灵仍然存在。</p>
</blockquote>

<p>然而，添加<code>-fsanitize-undefined-trap-on-error</code>会将这变成：</p>

<pre><code>0000000000400530 &lt;single_add&gt;:
  400530:       01 f7                   add    %esi,%edi
  400532:       70 03                   jo     400537 &lt;single_add+0x7&gt;
  400534:       89 f8                   mov    %edi,%eax
  400536:       c3                      retq
  400537:       0f 0b                   ud2
</code></pre>

<p>虽然这是一个微小的、人为的例子，但我们在其他使用允许fsanitize输出诊断选项的代码中看到了各种错误的优化。</p>

<p>虽然理论上一个更好的C编译器可以做得更好，但gcc 4.8.2在这里并不比clang 3.4做得更好。一方面，gcc的<code>-ftrapv</code>只检查有符号溢出。更糟的是，它不工作，<a href="https://gcc.gnu.org/bugzilla/show_bug.cgi?id=35412" rel="noopener noreferrer">这个关于ftrapv的错误自2008年以来就一直存在</a>。尽管检查更少且不正确，但gcc的<code>-ftrapv</code>在bzip2上使速度变慢的程度与clang的<code>-fsanitize=signed-integer-overflow,unsigned-integer-overflow</code>大致相同，并且比<code>-fsanitize=signed-integer-overflow</code>慢得多。</p>

<p>总而言之，整数溢出检查在典型的整数密集型工作负载上应该只花费几个百分点的成本，而它们确实如此，只要你不需要很好的错误信息。当前的机制在许多情况下以某种方式导致优化出错<sup id="fnref:H"><a href="#fn:H" rel="noopener noreferrer">1</a></sup>。</p>

<h3 id="update">更新</h3>

<p>在clang 3.8.0及更高版本，以及gcc 5及更高版本中，寄存器分配似乎按预期工作（尽管你可能需要传递<code>-fno-sanitize-recover</code>。我还没有回头在不同版本的clang和gcc上重新运行我的基准测试，但我想在有时间的时候这样做。</p>

<h3 id="cpu-internals-series">CPU内部系列</h3>

<ul>
<li><a href="//danluu.com/branch-prediction/" rel="noopener noreferrer">分支预测简史</a></li>
<li><a href="//danluu.com/new-cpu-features/" rel="noopener noreferrer">80年代以来的新的CPU特性</a></li>
<li><a href="//danluu.com/integer-overflow/" rel="noopener noreferrer">真实代码中分支和整数溢出检查的成本</a></li>
<li><a href="https://danluu.com/cpu-bugs/" rel="noopener noreferrer">CPU错误</a></li>
<li><a href="//danluu.com/hardware-unforgiving/" rel="noopener noreferrer">为什么CPU开发很难</a></li>
<li><a href="//danluu.com/why-hardware-development-is-hard/" rel="noopener noreferrer">Verilog很糟糕，第一部分</a></li>
<li><a href="//danluu.com/pl-troll/" rel="noopener noreferrer">Verilog很糟糕，第二部分</a></li>
</ul>

<p><small>
感谢Nathan Kurz对本主题的评论，包括但不限于归功于他的引述，以及Stan Schwertly、Nick Bergson-Shilcock、Scott Feeney、Marek Majkowski、Adrian和Juan Carlos Borras的错别字修正和澄清建议。同时，非常感谢Richard Smith向我指出了<code>-fsanitize-undefined-trap-on-error</code>选项。本文在Richard的评论后用该选项的结果进行了更新。另外，感谢Filipe Cabecinhas注意到clang在clang 3.8（大约在本文发布后1.5年发布）中修复了此行为。</small></p><small>

<p>John Regehr在这里有一些<a href="https://plus.google.com/105487075784331805819/posts/dyKKLrW8jXb" rel="noopener noreferrer">更多评论</a>，关于为什么clang的整数溢出检查实现还不快。</p>

</small><p><small></small></p>
<div>

<hr>

<ol>
<li id="fn:H"><p>人们经常<a href="http://yosefk.com/blog/the-high-level-cpu-challenge.html" rel="noopener noreferrer">呼吁硬件支持</a>，以超越现有溢出标志的整数溢出检查。这将增加每个制造芯片的费用和复杂性，以在最佳情况下、优化代码中最多获得几个百分点的额外性能。这可能值得——英特尔添加了许多特性，这些特性只将少数应用程序的速度提高几个百分点。</p>

<p>这经常被描述为一个鸡生蛋的问题；如果检查不是那么慢，人们会使用溢出检查，而硬件支持对于使检查快速是必要的。但已经存在硬件支持，足以让绝大多数应用程序获得足够好的性能。它只是没有被利用，因为人们实际上并不关心这个问题。</p>
 <a href="#fnref:H" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 7441 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/integer-overflow/</link>
      <guid isPermaLink="false">https://danluu.com/integer-overflow/</guid>
      <pubDate>Wed, 17 Dec 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Malloc 教程</title>
      <description>[AI 摘要] 本文通过一个简单的C语言示例教程，逐步讲解了如何实现一个基本的动态内存分配器（包括malloc和free函数），并探讨了如何调试以及在现有程序中使用它。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文通过一个简单的C语言示例教程，逐步讲解了如何实现一个基本的动态内存分配器（包括malloc和free函数），并探讨了如何调试以及在现有程序中使用它。</div><p>让我们来编写一个 <a href="http://man7.org/linux/man-pages/man3/malloc.3.html" rel="noopener noreferrer">malloc</a>，看看它如何与现有程序协同工作！</p>

<p>这基本上是对我在阅读了 Marwan Burelle 的<a href="http://www.inf.udec.cl/~leo/Malloc_tutorial.pdf" rel="noopener noreferrer">这篇教程</a>之后所做工作的扩展解释，然后我坐下来尝试编写自己的实现。因此，步骤会非常相似。主要实现差异在于我的版本更简单，更容易受到内存碎片的影响。在阐述方面，我的风格更加随意。</p>

<p>本教程假设你了解指针是什么，并且具备足够的 C 语言知识，知道 <code>*ptr</code> 是对指针的解引用，<code>ptr-&gt;foo</code> 等同于 <code>(*ptr).foo</code>，malloc 用于<a href="http://duartes.org/gustavo/blog/post/how-the-kernel-manages-your-memory/" rel="noopener noreferrer">动态分配空间</a>，并且熟悉链表的概念。关于 C 语言的基础介绍，<a href="http://www.amazon.com/gp/product/0673999866/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0673999866&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=5C3DNUKAQELP2KUL" rel="noopener noreferrer">Pointers on C</a> 是我最喜欢的书籍之一。如果你想一次查看所有代码，可以在这里找到：<a href="https://github.com/danluu/malloc-tutorial/blob/master/malloc.c" rel="noopener noreferrer">github链接</a>。</p>

<p>前言到此为止，malloc 的函数原型是：</p>

<pre><code>void *malloc(size_t size);
</code></pre>

<p>它接收一个字节数作为输入，并返回一个指向该大小内存块的指针。</p>

<p>我们可以用多种方式实现这个函数。我们任意选择使用 <a href="http://man7.org/linux/man-pages/man2/sbrk.2.html" rel="noopener noreferrer">sbrk</a>。操作系统为进程保留栈空间和堆空间，sbrk 让我们可以操作堆。<code>sbrk(0)</code> 返回指向当前堆顶的指针。<code>sbrk(foo)</code> 将堆大小增加 <code>foo</code>，并返回指向先前堆顶的指针。</p>

<p></p>

<p><img src="https://danluu.com/images/malloc-tutorial/heap.png" alt="Linux 内存布局示意图，由 Gustavo Duarte 提供。" width="194" height="301"></p>

<p>如果我们想实现一个非常简单的 malloc，可以这样做：</p>

<pre><code>#include &lt;assert.h&gt;
#include &lt;string.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;unistd.h&gt;


void *malloc(size_t size) {
  void *p = sbrk(0);
  void *request = sbrk(size);
  if (request == (void*) -1) {
    return NULL; // sbrk failed.
  } else {
    assert(p == request); // Not thread safe.
    return p;
  }
}
</code></pre>

<p>当程序向 malloc 请求空间时，malloc 会要求 sbrk 增加堆大小，并返回指向堆上新区域起始位置的指针。这里缺少一个技术细节，即 <code>malloc(0)</code> 应该返回 <code>NULL</code> 或另一个可以传递给 free 而不会引发混乱的指针，但基本功能已经实现。</p>

<p>但是说到 free，free 是如何工作的呢？free 的原型是：</p>

<pre><code>void free(void *ptr);
</code></pre>

<p>当 free 接收到一个先前由 malloc 返回的指针时，它应该释放该空间。但给定一个由我们的 malloc 分配的指针，我们不知道与之关联的内存块的大小是多少。这个信息存储在哪里？如果我们有一个可用的 malloc，我们可以 malloc 一些空间并存储它，但如果我们每次调用 malloc 来预留空间时都需要再次调用 malloc，就会遇到问题。</p>

<p>一个常用的技巧是将内存区域的元信息存储在我们藏匿在返回给调用者的指针正下方的某个空间中。假设堆顶目前位于 <code>0x1000</code>，我们请求 <code>0x400</code> 字节。我们当前的 malloc 会向 <code>sbrk</code> 请求 <code>0x400</code> 字节，并返回一个指向 <code>0x1000</code> 的指针。如果我们改为保留，比如说，<code>0x10</code> 字节来存储块的信息，我们的 malloc 就会向 <code>sbrk</code> 请求 <code>0x410</code> 字节，并返回一个指向 <code>0x1010</code> 的指针，从而将 <code>0x10</code> 字节的元信息块对调用 malloc 的代码隐藏起来。</p>

<p>这让我们可以释放一个块，但然后呢？我们从操作系统获得的堆区域必须是连续的，所以我们无法将中间的内存块归还给操作系统。即使我们愿意将新释放区域上方的所有内容向下复制以填补空洞，从而在末尾归还空间，也没有办法通知所有持有指向堆的指针的代码去调整那些指针。</p>

<p>相反，我们可以标记该块已被释放而不将其返回给操作系统，以便未来的 malloc 调用可以重新使用该块。但要做到这一点，我们需要能够访问每个块的元信息。对此有许多可能的解决方案。为简单起见，我们任意选择使用单链表。</p>

<p>因此，对于每个块，我们希望有类似这样的结构：</p>

<pre><code>struct block_meta {
  size_t size;
  struct block_meta *next;
  int free;
  int magic; // For debugging only. TODO: remove this in non-debug mode.
};

#define META_SIZE sizeof(struct block_meta)
</code></pre>

<p>我们需要知道块的大小、是否空闲以及下一个块是什么。这里有一个用于调试目的的魔术数字，但这并非真正必要；我们将其设置为任意值，这将使我们能够轻松看到哪个代码最后一次修改了该结构体。</p>

<p>我们还需要链表的头节点：</p>

<pre><code>void *global_base = NULL;
</code></pre>

<p>对于我们的 malloc，我们希望在可能的情况下重用空闲空间，当无法重用现有空间时再分配新空间。鉴于我们有这种链表结构，检查是否有空闲块并返回它很简单。当我们收到某个大小的请求时，我们会遍历链表，查看是否有足够大的空闲块。</p>

<pre><code>struct block_meta *find_free_block(struct block_meta **last, size_t size) {
  struct block_meta *current = global_base;
  while (current &amp;&amp; !(current-&gt;free &amp;&amp; current-&gt;size &gt;= size)) {
    *last = current;
    current = current-&gt;next;
  }
  return current;
}
</code></pre>

<p>如果我们没有找到空闲块，我们将不得不使用 sbrk 向操作系统请求空间，并将新块添加到链表的末尾。</p>

<pre><code>struct block_meta *request_space(struct block_meta* last, size_t size) {
  struct block_meta *block;
  block = sbrk(0);
  void *request = sbrk(size + META_SIZE);
  assert((void*)block == request); // Not thread safe.
  if (request == (void*) -1) {
    return NULL; // sbrk failed.
  }

  if (last) { // NULL on first request.
    last-&gt;next = block;
  }
  block-&gt;size = size;
  block-&gt;next = NULL;
  block-&gt;free = 0;
  block-&gt;magic = 0x12345678;
  return block;
}
</code></pre>

<p>与我们最初的实现一样，我们使用 <code>sbrk</code> 请求空间。但我们添加了一些额外的空间来存储我们的结构体，然后适当地设置结构体的字段。</p>

<p>现在我们有了检查是否有现有空闲空间以及请求空间的辅助函数，我们的 malloc 就很简单了。如果我们的全局基址指针是 <code>NULL</code>，我们需要请求空间并将基址指针设置为我们的新块。如果它不是 <code>NULL</code>，我们检查是否可以重用任何现有空间。如果可以，我们就重用；如果不行，我们就请求空间并使用新空间。</p>

<pre><code>void *malloc(size_t size) {
  struct block_meta *block;
  // TODO: align size?

  if (size &lt;= 0) {
    return NULL;
  }

  if (!global_base) { // First call.
    block = request_space(NULL, size);
    if (!block) {
      return NULL;
    }
    global_base = block;
  } else {
    struct block_meta *last = global_base;
    block = find_free_block(&amp;last, size);
    if (!block) { // Failed to find free block.
      block = request_space(last, size);
      if (!block) {
        return NULL;
      }
    } else {      // Found free block
      // TODO: consider splitting block here.
      block-&gt;free = 0;
      block-&gt;magic = 0x77777777;
    }
  }

  return(block+1);
}
</code></pre>

<p>对于不熟悉 C 的人来说，我们返回 block+1 是因为我们想返回指向 block_meta 之后区域的指针。由于 block 是 <code>struct block_meta</code> 类型的指针，<code>+1</code> 会将地址增加一个 <code>sizeof(struct block_meta)</code>。</p>

<p>如果我们只需要一个没有 free 的 malloc，我们可以使用最初那个简单得多的 malloc。那么让我们来写 free！free 需要做的主要事情是设置 <code>-&gt;free</code>。</p>

<p>因为我们需要在代码的多个地方获取结构体的地址，所以让我们定义这个函数。</p>

<pre><code>struct block_meta *get_block_ptr(void *ptr) {
  return (struct block_meta*)ptr - 1;
}
</code></pre>

<p>现在我们有了它，这是 free 的实现：</p>

<pre><code>void free(void *ptr) {
  if (!ptr) {
    return;
  }

  // TODO: consider merging blocks once splitting blocks is implemented.
  struct block_meta* block_ptr = get_block_ptr(ptr);
  assert(block_ptr-&gt;free == 0);
  assert(block_ptr-&gt;magic == 0x77777777 || block_ptr-&gt;magic == 0x12345678);
  block_ptr-&gt;free = 1;
  block_ptr-&gt;magic = 0x55555555;
}
</code></pre>

<p>除了设置 <code>-&gt;free</code>，向 free 传递一个 NULL 指针是有效的，所以我们需要检查 NULL。由于 free 不应该在任意地址或已经释放的块上被调用，我们可以断言这些事情永远不会发生。</p>

<p>你实际上不需要断言任何东西，但这通常会使调试容易得多。事实上，当我编写这段代码时，我遇到了一个如果没有这些断言就会导致静默数据损坏的 bug。相反，代码在断言处失败，这使得调试变得轻而易举。</p>

<p>现在我们已经有了 malloc 和 free，我们可以使用自定义内存分配器编写程序了！但在我们可以将分配器放入现有代码之前，我们需要实现另外几个常用函数：realloc 和 calloc。Calloc 只是一个将内存初始化为 0 的 malloc，所以我们先来看 realloc。Realloc 用于调整从 malloc、calloc 或 realloc 获得的内存块的大小。</p>

<p>Realloc 的函数原型是：</p>

<pre><code>void *realloc(void *ptr, size_t size)
</code></pre>

<p>如果我们向 realloc 传递一个 NULL 指针，它的行为应该像 malloc。如果我们传递一个先前 malloc 的指针，如果新大小小于之前的大小，它应该释放空间；如果新大小大于之前的大小，它应该分配更多空间并将现有数据复制过去。</p>

<p>如果我们不减小大小时不调整，也不释放任何东西，一切仍然会正常工作，但如果大小增加了，我们绝对需要分配更多空间，所以让我们从这一点开始。</p>

<pre><code>void *realloc(void *ptr, size_t size) {
  if (!ptr) {
    // NULL ptr. realloc should act like malloc.
    return malloc(size);
  }

  struct block_meta* block_ptr = get_block_ptr(ptr);
  if (block_ptr-&gt;size &gt;= size) {
    // We have enough space. Could free some once we implement split.
    return ptr;
  }

  // Need to really realloc. Malloc new space and free old space.
  // Then copy old data to new space.
  void *new_ptr;
  new_ptr = malloc(size);
  if (!new_ptr) {
    return NULL; // TODO: set errno on failure.
  }
  memcpy(new_ptr, ptr, block_ptr-&gt;size);
  free(ptr);
  return new_ptr;
}
</code></pre>

<p>现在来看 calloc，它只是在返回指针之前清零内存。</p>

<pre><code>void *calloc(size_t nelem, size_t elsize) {
  size_t size = nelem * elsize; // TODO: check for overflow.
  void *ptr = malloc(size);
  memset(ptr, 0, size);
  return ptr;
}
</code></pre>

<p>注意，这里没有检查 <code>nelem * elsize</code> 是否溢出，而这是规范实际上要求的。这里的所有代码只是为了得到一个勉强可用的东西。</p>

<p>现在我们有了一个勉强可用的东西，我们可以将其用于现有程序（甚至不需要重新编译程序）！</p>

<p>首先，我们需要编译我们的代码。在 Linux 上，类似于：</p>

<pre><code>clang -O0 -g -W -Wall -Wextra -shared -fPIC malloc.c -o malloc.so
</code></pre>

<p>应该可以工作。</p>

<p><code>-g</code> 添加调试符号，这样我们可以用 <code>gdb</code> 或 <code>lldb</code> 查看代码。<code>-O0</code> 将通过防止单个变量被优化掉来帮助调试。<code>-W -Wall -Wextra</code> 添加额外的警告。<code>-shared -fPIC</code> 将允许我们动态链接代码，这就是我们<a href="http://jvns.ca/blog/2014/11/27/ld-preload-is-super-fun-and-easy/" rel="noopener noreferrer">在现有二进制文件中使用代码</a>的方式！</p>

<p>在 Mac 上，我们会希望类似这样：</p>

<pre><code>clang -O0 -g -W -Wall -Wextra -dynamiclib malloc.c -o malloc.dylib
</code></pre>

<p>注意，<code>sbrk</code> 在 OS X 的最新版本中已弃用。Apple 对"弃用"使用了非正统的定义——一些被弃用的系统调用已经严重损坏。我并没有真正在 Mac 上测试过，所以有可能这会导致奇怪的故障，或者只是在 Mac 上根本无法工作。</p>

<p>现在，要让一个二进制文件在 Linux 上使用我们的 malloc，我们需要设置 <code>LD_PRELOAD</code> 环境变量。如果你使用 bash，你可以这样做：</p>

<pre><code>export LD_PRELOAD=/absolute/path/here/malloc.so
</code></pre>

<p>如果你用的是 Mac，你会希望：</p>

<pre><code>export DYLD_INSERT_LIBRARIES=/absolute/path/here/malloc.so
</code></pre>

<p>如果一切正常，你可以运行任意二进制文件，它会正常运行（只是速度会慢一点）。</p>

<pre><code>$ ls
Makefile  malloc.c  malloc.so  README.md  test  test-0  test-1  test-2  test-3  test-4
</code></pre>

<p>如果有 bug，你可能会得到类似这样的结果：</p>

<pre><code>$ ls
Segmentation fault (core dumped)
</code></pre>

<h4 id="debugging">调试</h4>

<p>让我们谈谈调试！如果你熟悉使用调试器设置断点、检查内存和单步执行代码，你可以跳过本节，直接转到<a href="#exercises" rel="noopener noreferrer">练习</a>。</p>

<p>本节假设你能弄清楚如何在系统上安装 gdb。如果你在 Mac 上，你可能想直接使用 lldb 并相应地翻译命令。因为我不知道你可能会遇到什么 bug，所以我将引入几个 bug 并展示我如何追踪它们。</p>

<p>首先，我们需要弄清楚如何运行 gdb 而不会导致它段错误。如果 ls 段错误，我们尝试运行 <code>gdb ls</code>，gdb 几乎肯定也会段错误。我们可以写一个包装器来做这个，但 gdb 也支持这个。如果我们启动 gdb，然后在运行程序之前运行 <code>set environment LD_PRELOAD=./malloc.so</code>，<code>LD_PRELOAD</code> 将正常工作。</p>

<pre><code>$ gdb /bin/ls
(gdb) set environment LD_PRELOAD=./malloc.so
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7bd7dbd in free (ptr=0x0) at malloc.c:113
113       assert(block_ptr-&gt;free == 0);
</code></pre>

<p>正如预期的那样，我们得到了一个段错误。我们可以用 <code>list</code> 来查看段错误附近的代码。</p>

<pre><code>(gdb) list
108     }
109
110     void free(void *ptr) {
111       // TODO: consider merging blocks once splitting blocks is implemented.
112       struct block_meta* block_ptr = get_block_ptr(ptr);
113       assert(block_ptr-&gt;free == 0);
114       assert(block_ptr-&gt;magic == 0x77777777 || block_ptr-&gt;magic == 0x12345678);
115       block_ptr-&gt;free = 1;
116       block_ptr-&gt;magic = 0x55555555;
117     }
</code></pre>

<p>然后我们可以使用 <code>p</code>（代表打印）来查看这里的变量情况：</p>

<pre><code>(gdb) p ptr
$6 = (void *) 0x0
(gdb) p block_ptr
$7 = (struct block_meta *) 0xffffffffffffffe8
</code></pre>

<p><code>ptr</code> 是 <code>0</code>，即 <code>NULL</code>，这就是问题所在：我们忘记检查 NULL 了。</p>

<p>既然我们已经找到了原因，让我们尝试一个稍微困难一点的 bug。假设我们决定用以下结构体替换我们的结构体：</p>

<pre><code>struct block_meta {
  size_t size;
  struct block_meta *next;
  int free;
  int magic;    // For debugging only. TODO: remove this in non-debug mode.
  char data[1];
};
</code></pre>

<p>然后从 malloc 返回 <code>block-&gt;data</code> 而不是 <code>block+1</code>，不进行其他更改。这看起来与我们已经做的非常相似——我们只定义一个指向结构体末尾的成员，并返回指向该成员的指针。</p>

<p>但如果我们尝试使用新的 malloc，会发生这种情况：</p>

<pre><code>$ /bin/ls
Segmentation fault (core dumped)
gdb /bin/ls
(gdb) set environment LD_PRELOAD=./malloc.so
(gdb) run

Program received signal SIGSEGV, Segmentation fault.
_IO_vfprintf_internal (s=s@entry=0x7fffff7ff5f0, format=format@entry=0x7ffff7567370 "%s%s%s:%u: %s%sAssertion `%s' failed.\n%n", ap=ap@entry=0x7fffff7ff718) at vfprintf.c:1332
1332    vfprintf.c: No such file or directory.
1327    in vfprintf.c
</code></pre>

<p>这不像我们上次的错误那么友好——我们可以看到我们的一个断言失败了，但 gdb 将我们带到了某个在断言失败时被调用的打印函数中。但那个打印函数使用了我们有 bug 的 malloc，并崩溃了！</p>

<p>我们在这里可以做的一件事是检查 <code>ap</code>，看看 <code>assert</code> 试图打印什么：</p>

<pre><code>(gdb) p *ap
$4 = {gp_offset = 16, fp_offset = 48, overflow_arg_area = 0x7fffff7ff7f0, reg_save_area = 0x7fffff7ff730}
</code></pre>

<p>这会工作得很好；我们可以四处查看，直到弄清楚应该打印什么，从而找出错误。其他解决方案包括编写我们自己的自定义断言，或使用正确的钩子来防止 <code>assert</code> 使用我们的 malloc。</p>

<p>但在这种情况下，我们知道代码中只有几个断言。一个是 malloc 中的断言，检查我们不会在多线程程序中使用它；另一个在 free 中，有两个检查我们没有释放不应该释放的东西。让我们先看 free，通过设置断点。</p>

<pre><code>$ gdb /bin/ls
(gdb) set environment LD_PRELOAD=./malloc.so
(gdb) break free
Breakpoint 1 at 0x400530
(gdb) run /bin/ls

Breakpoint 1, free (ptr=0x61c270) at malloc.c:112
112       if (!ptr) {
</code></pre>

<p><code>block_ptr</code> 还没有设置，但如果我们用 <code>s</code> 几次单步执行到它设置之后，我们可以看到它的值是什么：</p>

<pre><code>(gdb) s
(gdb) s
(gdb) s
free (ptr=0x61c270) at malloc.c:118
118       assert(block_ptr-&gt;free == 0);
(gdb) p/x *block_ptr
$11 = {size = 0, next = 0x78, free = 0, magic = 0, data = ""}
</code></pre>

<p>我使用 <code>p/x</code> 而不是 <code>p</code>，这样我们可以以十六进制查看。<code>magic</code> 字段是 0，对于我们要释放的有效结构体来说，这应该是不可能的。也许 <code>get_block_ptr</code> 返回了一个错误的偏移量？我们有 <code>ptr</code> 可用，所以我们可以直接检查不同的偏移量。由于它是一个 <code>void *</code>，我们必须进行类型转换，这样 gdb 才能知道如何解释结果。</p>

<pre><code>(gdb) p sizeof(struct block_meta)
$12 = 32
(gdb) p/x *(struct block_meta*)(ptr-32)
$13 = {size = 0x0, next = 0x78, free = 0x0, magic = 0x0, data = {0x0}}
(gdb) p/x *(struct block_meta*)(ptr-28)
$14 = {size = 0x7800000000, next = 0x0, free = 0x0, magic = 0x0, data = {0x78}}
(gdb) p/x *(struct block_meta*)(ptr-24)
$15 = {size = 0x78, next = 0x0, free = 0x0, magic = 0x12345678, data = {0x6e}}
</code></pre>

<p>如果我们从使用的地址回溯一点，可以看到正确的偏移量是 24 而不是 32。这里发生的情况是结构体被填充了，所以 <code>sizeof(struct block_meta)</code> 是 32，即使最后一个有效成员在 <code>24</code> 处。如果我们想去掉那个额外的空间，需要修复 <code>get_block_ptr</code>。</p>

<p>调试到此结束！</p>

<h4 id="exercises">练习</h4>

<p>就我个人而言，除非我做一些练习，否则这类东西从不牢记，所以我在这里留下几个练习给感兴趣的人。</p>

<ol>
<li><p>malloc 应该返回一个“适用于任何内置类型的合适对齐”的指针。我们的 malloc 做到了吗？如果做到了，为什么？如果没有，修复对齐。注意，“任何内置类型”对于 C 来说基本上是最大 8 字节，因为 SSE/AVX 类型不是内置类型。</p></li>

<li><p>如果我们尝试重用现有块而不需要所有空间，我们的 malloc 会非常浪费。实现一个函数来分割块，使它们只使用所需空间的最小值。</p></li>

<li><p>在完成 <code>2</code> 之后，如果我们多次用随机大小调用 malloc 和 free，最终会产生许多小块，这些块只能在我们请求少量空间时重用。实现一个机制来合并相邻的空闲块，这样任何连续的空闲块都会被合并成一个单独的块。</p></li>

<li><p>在现有代码中寻找 bug！我没有测试太多，所以即使它勉强能工作，也肯定有 bug。</p></li>
</ol>

<h4 id="resources">资源</h4>

<p>如上所述，有 Marwan Burelle 的<a href="http://www.inf.udec.cl/~leo/Malloc_tutorial.pdf" rel="noopener noreferrer">教程</a>。</p>

<p>关于 Linux 如何处理内存管理的更多信息，请参见 Gustavo Duarte 的<a href="http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory/" rel="noopener noreferrer">这篇文章</a>。</p>

<p>关于现实世界中 malloc 实现如何工作的更多信息，<a href="http://g.oswego.edu/dl/html/malloc.html" rel="noopener noreferrer">dlmalloc</a> 和 <a href="http://goog-perftools.sourceforge.net/doc/tcmalloc.html" rel="noopener noreferrer">tcmalloc</a> 都是非常好的读物。我没有阅读过 <a href="http://www.canonware.com/jemalloc/" rel="noopener noreferrer">jemalloc</a> 的代码，我听说它更难理解，但它也是目前使用最广泛的高性能 malloc 实现。</p>

<p>关于调试帮助，<a href="https://code.google.com/p/address-sanitizer/wiki/AddressSanitizer" rel="noopener noreferrer">Address Sanitizer</a> 非常出色。如果你想编写一个线程安全的版本，<a href="https://code.google.com/p/data-race-test/wiki/ThreadSanitizer" rel="noopener noreferrer">Thread Sanitizer</a> 也是一个很棒的工具。</p>

<p>这篇文章有<a href="http://mgarciaisaia.github.io/tutorial-c/blog/2014/12/26/un-tutorial-rapido-para-implementar-y-debuggear-malloc/" rel="noopener noreferrer">西班牙语翻译版本在这里</a>，由 Matias Garcia Isaia 提供。</p>

<h4 id="acknowledgements">致谢</h4>

<p>感谢 Gustavo Duarte 允许我使用他的一张图片来说明 sbrk，也感谢 Ian Whitlock、Danielle Sucher、Nathan Kurz、"tedu"、@chozu@fedi.absturztau.be 和 David Farrel 的评论/纠正/讨论。如果你在这篇文章中发现其他错误（无论是文字还是代码），请<a href="https://twitter.com/danluu" rel="noopener noreferrer">告诉我</a>。</p><p><em>由 mimo-v2.5 模型翻译，花费 13288 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/malloc-tutorial/</link>
      <guid isPermaLink="false">https://danluu.com/malloc-tutorial/</guid>
      <pubDate>Thu, 4 Dec 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>市场、歧视与“降低标准”</title>
      <description>[AI 摘要] 文章认为市场力量无法消除歧视，且提升多样性能提高行业水平。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章认为市场力量无法消除歧视，且提升多样性能提高行业水平。</div><p>关于科技行业歧视的公开讨论常常引发这样的论点：由于市场力量的存在，歧视是不可能发生的。以下是Marc Andreessen的一段话，代表了一种常见观点<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">1</a></sup>。</p>

<blockquote>
<p>我们直接开始吧。我认为，关于硅谷公司有意、系统性地进行歧视的批评是错误的，理由有二：
……
第二，我们的公司极度渴求人才。极度渴求。我们的公司求贤若渴。他们就像瘫在沙滩上喘不过气，因为无法招到足够多的优秀人才来做这些工作。去任何地方寻找人才的动机都高得令人难以置信。</p>
</blockquote>

<p>Marc Andreessen的观点是，市场竞争如此激烈，歧视不可能存在。但风险资本支持的初创公司并非世界上第一批面临激烈招聘市场的公司。可以看看1958年至1987年经济学博士的市场。Alan Greenspan曾这样描述他的公司Townsend-Greenspan所面对的市场情况：</p>

<blockquote>
<p>Townsend-Greenspan与其他经济学公司的不同之处在于，男性为女性工作（我们总共有大约二十五名员工）。我雇佣女性经济学家并非出于妇女解放运动。这纯粹是商业上的明智之举。我平等地看待男性和女性，并发现由于其他雇主不这么做，优秀的女性经济学家比男性更便宜。雇佣女性……让Townsend-Greenspan以同样的成本获得了更高质量的工作……</p>
</blockquote>

<p>竞争不仅没有终结歧视，而且存在足够的歧视，以至于不歧视的行为为Townsend-Greenspan带来了显著的竞争优势。这还是在金融业，一个以残酷著称的行业。而且不只是金融业的任何部分，而是由经济学博士雇佣其他经济学博士的领域。在这个行业，招聘者最有可能熟悉理论模型以及表明歧视通过压低某些群体工资来创造市场机会的实证研究。但即便如此，当Greenspan在1958年接管Townsend-Greenspan时，这仍然不足以实现男女同工同酬，而当他1987年离开去担任美联储主席时，情况依然如此。这就是歧视的问题所在。当歧视根植于根深蒂固的信念时，人们很难意识到自己在歧视。</p>

<p>然而，在讨论科技招聘时，人们常常声称，由于市场和招聘是完全竞争或高效的，公司必然已经在雇用呈现给他们的最优秀的人。由此得出的一个推论是，反歧视或多元化的政策必然意味着“降低标准”，因为这偏离了现有的最佳招聘实践。反之，即使在讨论中没有涉及“市场力量”，声称增加招聘多样性必然意味着“降低标准”也是基于对招聘某种最优性的假设。我认为，对科技招聘实践的审视非常清楚地表明，<a href="https://danluu.com/programmer-moneyball/" rel="noopener noreferrer">实践远非</a> <a href="https://danluu.com/algorithms-interviews/" rel="noopener noreferrer">最优</a>，但我不想基于实践来反驳这一主张（相关文章已论述过），而是想探讨“市场力量使歧视不可能”这一元主张。人们对市场效率和经济学做出模糊的断言，比如<a href="https://twitter.com/rogerdickey/status/1268327765905772546" rel="noopener noreferrer">这位有影响力连续创业者在结束关于招聘的评论时说：“资本主义是真实的，市场是有效的。”</a><sup id="fnref:A"><a href="#fn:A" rel="noopener noreferrer">2</a></sup>。人们似乎喜欢对“市场”或“经济学家”进行泛泛而谈的引用。</p>

<p>但如果我们真正阅读经济学家关于招聘市场如何运作的观点，他们通常并不声称市场是完全有效的，或者在通常被称为高度竞争的市场中歧视不会发生。既然我们在讨论歧视，一个很好的起点可能是<a href="http://web.archive.org/web/20150401024833/http://economics.mit.edu/files/553" rel="noopener noreferrer">Becker关于歧视的开创性著作</a>。Becker说的是，市场对歧视施加成本，并且在某些市场条件下，Becker所称的“偏好型”<sup id="fnref:T"><a href="#fn:T" rel="noopener noreferrer">3</a></sup>歧视平均而言的存在，并不意味着边际上不存在歧视。这是一个非常具体的陈述，如果你阅读歧视文献中的其他论文，它们也会做出类似的精确陈述。你不会看到像科技讨论中那样的模糊说法，即“市场力量”或“竞争”与歧视或非最优招聘不相容。坦率地说，我从未与那些说“资本主义是真实的，市场是有效的”之类话的人进行过看起来他们对Becker在歧视经济学领域的开创性著作有哪怕初步了解的讨论，更不用说该主题的其他主要著作了。</p>

<p>在更广泛的科技社区讨论中，我从未见过有人论证科技行业（或任何行业）满足Becker所描述的条件，即平均而言的偏好型歧视不意味着边际上的偏好型歧视。我也从未看到人们论证我们只存在偏好型歧视，或者我们也满足不存在其他形式歧视的条件。当人们在招聘或科技的其他部分引用“有效市场”时，通常都是模糊的泛泛而谈，听起来像是诉诸权威，但这个权威可能是所谓的青少年自由主义者对市场行为的想象。</p>

<p>由于人们通常觉得像Becker著作中那种抽象推理并不令人信服，让我们来看几个具体的例子。你可以在很多领域看到歧视。一个问题是，很难将歧视的影响与混杂变量分开，因为很难获得关于员工绩效随时间变化与薪酬关系的良好数据。幸运的是，有一个领域的数据是可得的：体育。在讨论具体例子之前，值得注意的是，我们预计体育领域的歧视应该比科技领域少得多。不仅因为这里有更好的员工绩效数据，而且从过去的绩效预测未来绩效更容易，员工绩效对“公司”绩效的影响更大且更容易量化，市场也更具竞争性。相对于科技，这些因素既增加了歧视的成本，又使成本更可见。</p>

<p>在棒球领域，<a href="https://ideas.repec.org/a/ucp/jpolec/v82y1974i4p873-81.html" rel="noopener noreferrer">Gwartney和Haworth（1974）</a>发现，在种族隔离结束后的十年里，歧视非白人球员较少的球队表现更好。使用“经典”生产力指标对后几十年的研究大多发现薪资趋于平等。然而，<a href="http://www.hardballtimes.com/searching-for-racial-earnings-differentials-in-major-league-baseball/" rel="noopener noreferrer">Swartz（2014）</a>使用更新更准确的生产力指标发现，拉丁裔球员的生产力水平所对应的薪酬显著偏低。薪酬并非唯一的歧视方式——<a href="http://sf.oxfordjournals.org/content/67/2/524.short" rel="noopener noreferrer">Jibou（1988）</a>发现，在控制年龄和表现后，黑人球员从棒球界退出的比例更高。这对任何想知道<a href="http://leanangry.tumblr.com/post/125716699460/your-pipeline-argument-is-bullshit" rel="noopener noreferrer">科技领域退出率</a>的人来说应该很熟悉。</p>

<p>市场这种缓慢的效应不仅限于棒球；在其他运动中似乎更糟。Kahn（1991）的一篇综述文章指出，在篮球领域，最近的研究（截至综述时）发现黑人球员存在11%-25%的薪资惩罚以及更高的退出率。Kahn还提到了多项针对法裔加拿大人在冰球运动中遭受歧视的研究，这被认为源于法裔加拿大男性不如其他男性阳刚的刻板印象<sup id="fnref:E"><a href="#fn:E" rel="noopener noreferrer">4</a></sup>。</p>

<p>在科技行业，一些人担心增加多样性会“降低标准”，但在体育领域，那里有比科技更具竞争性的招聘市场，我们看到了相反的情况：增加多样性提升了水平而不是降低水平，因为它意味着根据资质而不是外表来雇用人员。我并不同意那些认为科技公司因为不雇用合格的少数族裔而放弃利润是荒谬说法的人的观点。但这正是我们在所研究的体育运动中看到的情况，在那些运动中，由于绩效量化相对容易，这种情况甚至更荒谬。然而，几十年来，球队通过青睐白人球员（在冰球中，还包括非法裔加拿大球员）而放弃了大量利润，而这些人简单来说，资质不如他们的同行。<a href="https://twitter.com/danluu/status/1270181411534761984" rel="noopener noreferrer">这个世界就是如此荒谬</a>。</p>

<p>在拥有足够数据来观察是否存在歧视的领域，我们经常发现歧视的存在。即使在那些本身竞争最为激烈的领域，如职业体育大联盟。关于歧视的研究不仅限于实证研究和数据挖掘。已有实验证明在从简历初筛、电话面试、录用到薪资谈判、职场晋升的各个层面都存在歧视。而且这些研究大多是在性别比例大致相当的领域进行的。在那些歧视较弱以至于入口比例上存在性别或种族平衡的领域，我们可以看到过去两代人歧视水平的稳步下降。歧视尚未被消除，但已大大减少。</p>

<p><img src="https://danluu.com/images/tech-discrimination/cs_enrollment.png" alt="显示医学院、法学院、科学领域和计算机科学按性别划分的入学率图表。图表由NPR提供。" width="568" height="418"></p>

<p>然后我们有计算机科学。入口比例的差异大约相当于医学、法学和物理科学在70年代时的情况。恰巧，为这种性别差异所做的辩解，与70年代为解释为何女性不想进入或无法应付像医学、经济学、金融和生物学这样的技术领域时所用的借口一模一样。</p>

<p>一个常被提出的论点是，女性天生对“更难”的科学兴趣较小，因此你会预期更多女性进入生物学或医学领域，而不是编程。有两个主要原因我认为这种推理没有说服力。首先，按比例来说，进入数学和化学工程等领域的女性比进入编程的女性多。我认为按“硬科学”程度对数学和科学进行排名毫无意义，但如果问人们这些领域，大多数人会把数学排在编程之上，如果他们了解化学工程学位的内容，我认为他们也会把化学工程排在编程之上，然而这些领域的女性比例却高于编程。其次，如果你看看其他国家，学习计算机科学的人口比例千差万别，原因似乎主要是文化因素。考虑到我们确实看到了所有这些差异，我看不出有任何理由认为美国反映了女性学习编程的“真实”比率，而那些（按比例）有更多女性想学习编程的国家，其比率是因文化偏见而偏离“真实”比率的结果。</p>

<p>撇开理论争论不谈，我很想知道为什么我的亲身经历与Andreessen如此不同。他的推理在他脑中听起来一定很合理，而女性和少数族裔讲述的歧视故事在他听来一定不真实。但对我来说，情况恰恰相反。</p>

<p>就在前几天，我和我的朋友John（以及文中所有其他名字都是随机选择以保持匿名）聊天，他是一位优秀的程序员。他花了两年时间才找到工作，在当今的就业市场，这对我这个年纪的人来说很震惊，但对像他这样年纪是我两倍的人来说却可悲地正常。</p>

<p>你可能会想是不是John除了年龄之外还有其他问题，但当我在谷歌的同事和我对他进行模拟面试时，他表现得很好。我接受过谷歌的标准面试培训，也为谷歌工作面试过，当我将他与那个标准相比时，我认为他被谷歌雇用基本上就是抛硬币。状态好时是肯定；状态不好时是否定。当他面试谷歌时，他没有得到录用通知，但通过了电话筛选，并且在面试后他们强烈建议他一年后再申请，这是个好迹象。但大多数地方甚至都不愿和John谈谈。</p>

<p>即使在谷歌这样大肆宣扬消除流程偏见的公司，流程也常常无法做到。当我推荐Mary去谷歌时，她在招聘员电话面试中因被认为技术性不够而被拒绝；而我看到William因健康问题遭到一位经理日益增加的愤怒对待，最终导致他辞职。</p>

<p>当然，在在线讨论中，人们会质疑像Mary这样的人的技术能力。嗯，Mary是我见过的最令人印象深刻的工程师之一，无论在哪个领域。人们说这话时意思各不相同，所以让我提供一个参考框架：其他属于我这个类别的人包括一位<a href="http://en.wikipedia.org/wiki/IBM_Fellow" rel="noopener noreferrer">IBM院士</a>、那位IBM院士称为IBM最优秀工程师的人、一位现在是CMU教授的数学奥赛奖牌得主、Sun的一位杰出工程师，以及其他几位类似的人物。</p>

<p>总之，Mary接通了谷歌招聘员的电话。招聘员对Mary拥有数学学位而非计算机科学学位发表了一些评论，可能技术性不够，并质疑Mary的编程经验：是“算法”还是“只是编码”？情况从此急转直下。

</p><p>谷歌有大量没有计算机科学学位的工程师，有历史、音乐和艺术学位的人，还有很多没有任何学位的工程师，甚至没有高中文凭。但不知何故，一个数学学位加上我的内部推荐提到这是我见过的最优秀的工程师之一，却导致了Mary技术性不够的决定。</p>

<p>你可能会说，就像John的例子一样，这是某种偶然。也许吧。但从我的经验来看，如果Mary是男性而不是女性，偶然的可能性会更低。</p>

<p>这种动态不仅限于招聘。每次我读Anna博客的评论时都会注意到这一点。经常有人会质疑Anna的技术能力。甚至不是他们发现了当前帖子中的“嗯，实际上”（尽管有时也会发生）；通常是他们翻出六个月前的帖子，根据他们的说法，技术性不够。</p>

<p>我在技术上并不比Anna更强，但我从未遇到过这种情况。我见过发生在男性身上，但仅限于那些极其高调的人（比如在科技博主中最知名的N人，如Steve Yegge或Jeff Atwood），或者推动常常被轻视的议程的人（如动态语言）。但这经常发生在像Anna这样中等知名的女性博主身上。</p>

<p>对女性和少数族裔的差别对待不仅限于招聘和写博客。我已经记不清有多少次女性不经意地向我提到，某个男人以为她是招聘人员、前端开发者、妻子、女朋友或用户体验顾问。这在任何地方都发生。在会议上。在开发者的聚会上。在工作中。无处不在。不仅这从未发生在我身上，而且相反的情况经常发生在我身上——如果我和物理或数学研究生在一起，人们会认为我是他们的研究生同伴。</p>

<p>当人们在这种讨论中提起市场时，他们说得好像市场是一种自然力量。它不是。它只是描述了在特定情况下人类集体行动的一个词。Mary的情况并没有因为自由市场而自动得到解决。Mary被招聘员拒绝的事情是在我向我的工程总监抱怨后才得以解决的，他把我介绍给了一位HR总监，她耐心地听完整个故事并推翻了决定<sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">5</a></sup>。市场就是人类。一路到底都是人类。</p>

<p>如果我们停止假设市场会为我们解决这个问题，我们就能解决它。</p>

<h3 id="appendix-a-few-related-items">附录：一些相关条目</h3>

<ul>
<li><a href="//danluu.com/gender-gap/" rel="noopener noreferrer">人们确实会从他们看到的研究中解读出相反的结果</a></li>
<li>今年在<a href="http://fse22.gatech.edu/" rel="noopener noreferrer">FSE</a>大会上，一位演讲者指出，如果你给一群程序员展示随机数据，他们会将这些数据解读为支持他们关于最佳实践的先验信念</li>
<li><a href="https://ed.stanford.edu/sites/default/files/uhlmann_et_2005.pdf" rel="noopener noreferrer">人们越有偏见，就越认为自己客观</a></li>
</ul>

<p>另外，请注意，尽管本文最初发表于2014年，但在2020年进行了更新，添加了一些近期评论的链接并稍作重组。</p>

<p><small>
感谢Leah Hanson、Kelley Eskridge、Lindsey Kuper、Nathan Kurz、Scott Feeney、Katerina Barone-Adesi、Yuri Vishnevsky、@teles_dev、"Negative12DollarBill"和Patrick Roberts对本文的反馈，感谢Julia Evans在我犹豫是否要公开发表时鼓励我发表。
</small></p><p><small>请注意，本文中的所有名字都是化名，取自1880年美国常见名字列表。
</small></p>
<div>

<hr>

<ol>
<li id="fn:1">如果你好奇他的“第一点”是什么，那是说不可能存在歧视，因为我们看看我们已有的多样性就知道了。中国人。印度人。越南人。等等。论点是，我们不可能歧视某些群体，因为我们没有歧视其他群体。特别是，我们不可能歧视不符合典型工程师形象的群体，因为我们没有歧视符合典型工程师形象的群体。
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:A">另见，Benedict Evans的这条评论，“反驳”了关于SV公司可能存在次优雇佣实践的评论，他说“<a href="https://twitter.com/benedictevans/status/1274124694913011715" rel="noopener noreferrer">我不必告诉你，硅谷对人才的争夺异常激烈。</a>”。这条评论本身不是关于多元化或歧视的，但认为硅谷就业市场在某种程度上强制实现了一种最优性的普遍观念，在硅谷思想领袖中非常普遍。
 <a href="#fnref:A" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:T"><p>“偏好型”歧视是基于与任何实际存在的群体间生产力差异无关的偏好的歧视。当然，人们常常声称<a href="https://lwn.net/Articles/824148/" rel="noopener noreferrer">他们在某些环境中从未见过种族主义或性别歧视</a>，通常暗示，有时也明确声称我们看到的任何差异都是由于群体层面的差异造成的。如果是这样，我们想看看关于“统计性”歧视的文献。然而，统计性歧视似乎与这场讨论无关。一个人为的统计性歧视相关的例子是，如果我们必须仅根据身高和体重来雇用篮球运动员，而没有任何能力观察他们的打法，无论是直接观察还是通过统计观察。</p>

<p>在这种情况下，球队会希望专门雇用高个子篮球运动员，因为如果你只有身高这一依据，那么身高比什么都没有是更好的篮球生产力代理指标。然而，如果我们考虑非人为的、实际篮球生产力的例子，并将NBA球员的实际生产力与他们的身高进行比较，那么（对于身高在篮球运动员中异常矮的异常值除外）身高与表现之间没有相关性。原因是，如果我们能直接测量表现，我们就可以直接根据表现雇用，这就把身高从表现方程式中排除了。对于非常矮的球员来说，这是一个例外，他们必须克服偏见（偏好型歧视），这种偏见导致人们忽视他们。</p>

<p>虽然对编程生产力的衡量相当差，但就我们所知，整个群体中种族、性别和生产力之间的实际统计相关性为零，这使得统计性歧视与之无关。
 <a href="#fnref:T" rel="noopener noreferrer"><sup>[返回]</sup></a></p>
</li><li id="fn:E">这里的证据并非完全明确。在综述中，Kahn指出，对于某些地区，早期的研究没有发现薪资差距，但这些研究只涉及小样本球员。另外，Kahn指出（当时），足球领域的证据还不足以说明问题。
 <a href="#fnref:E" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:B">为了完全披露，这<a href="http://fromthearchives.blogspot.com/2006/06/always-bid.html" rel="noopener noreferrer">并没有改变最终结果</a>，因为Mary在第一次面试后不想再和谷歌有任何瓜葛。考虑到第一次面试的情况，让这成为Mary的决定而不是谷歌的，可能是最好的可能结果，而且从我听到的HR总监的评论来看，未来发生同样事情的概率可能会降低。
 <a href="#fnref:B" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 10096 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/tech-discrimination/</link>
      <guid isPermaLink="false">https://danluu.com/tech-discrimination/</guid>
      <pubDate>Mon, 1 Dec 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>TF-IDF 分析 Linux 提交信息</title>
      <description>[AI 摘要] 本文使用 TF-IDF 方法分析 Linux 内核提交信息，以识别开发者的主要工作方向。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文使用 TF-IDF 方法分析 Linux 内核提交信息，以识别开发者的主要工作方向。</div><p>我好奇不同的人在 Linux 上做了什么，所以我尝试从当前 git 仓库中获取数据，看看是否能从提交信息中提取出这些信息。这不包括他们切换到 git 之前的历史，所以只追溯到 2005 年，但这仍然是相当一部分历史。</p>

<p>以下是提交次数最多的前四位提交者（按提交次数排序）在提交信息中使用最频繁的单词列表。</p>

<p></p>

<table>
<thead>
<tr>
<th>用户</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
</tr>
</thead>

<tbody>
<tr>
<td>viro</td>
<td>to</td>
<td>in</td>
<td>of</td>
<td>and</td>
<td>the</td>
</tr>

<tr>
<td>tiwai</td>
<td>alsa</td>
<td>the</td>
<td>-</td>
<td>for</td>
<td>to</td>
</tr>

<tr>
<td>broonie</td>
<td>the</td>
<td>to</td>
<td>asoc</td>
<td>for</td>
<td>a</td>
</tr>

<tr>
<td>davem</td>
<td>the</td>
<td>to</td>
<td>in</td>
<td>and</td>
<td>sparc64</td>
</tr>
</tbody>
</table>

<p><br>
好吧，他们最常用的单词是 <code>to</code>、<code>alsa</code>、<code>the</code> 和 <code>the</code>。结果发现，高桥贵志（tiwai）经常处理音频（alsa），通过列表向下看，我们可以看到大卫·米勒（davem）的第五个最常用术语是 <code>sparc64</code>，这很好地表明他做了很多 sparc 工作。但表格大部分是噪音。当然，人们经常使用 <code>to</code>、<code>in</code> 和其他常见词！将其放入表格中提供了零信息。</p>

<p>有一些标准技术可以处理这个问题。一种是显式过滤“停用词”，即我们不关心的常见词。不幸的是，如果没有手动干预，这在这个数据集上效果不佳。标准的停用词列表会错过像 <code>Signed-off-by</code> 和 <code>cc</code> 这样的词，这些词相当无趣。我们可以使用一些阈值来生成自定义停用词列表，但任何足够高以捕捉所有噪音的阈值也会捕捉常见但有趣的术语，如 <code>null</code> 和 <code>driver</code>。</p>

<p>幸运的是，手动操作大约只需要一分钟。完成后，结果是许多提交者的顶部单词相同。我不会重现按提交者排列的顶部单词表格，因为它只是许多相同单词的重复。相反，这里是顶部单词的表格（按使用该单词的提交信息数量排名，而不是原始计数），移除了停用词，具有相同的数据，但没有被按提交者细分的额外噪音。</p>

<table>
<thead>
<tr>
<th>单词</th>
<th>计数</th>
</tr>
</thead>

<tbody>
<tr>
<td>driver</td>
<td>49442</td>
</tr>

<tr>
<td>support</td>
<td>43540</td>
</tr>

<tr>
<td>function</td>
<td>43116</td>
</tr>

<tr>
<td>device</td>
<td>32915</td>
</tr>

<tr>
<td>arm</td>
<td>28548</td>
</tr>

<tr>
<td>error</td>
<td>28297</td>
</tr>

<tr>
<td>kernel</td>
<td>23132</td>
</tr>

<tr>
<td>struct</td>
<td>18667</td>
</tr>

<tr>
<td>warning</td>
<td>17053</td>
</tr>

<tr>
<td>memory</td>
<td>16753</td>
</tr>

<tr>
<td>update</td>
<td>16088</td>
</tr>

<tr>
<td>bit</td>
<td>15793</td>
</tr>

<tr>
<td>usb</td>
<td>14906</td>
</tr>

<tr>
<td>bug</td>
<td>14873</td>
</tr>

<tr>
<td>register</td>
<td>14547</td>
</tr>

<tr>
<td>avoid</td>
<td>14302</td>
</tr>

<tr>
<td>pointer</td>
<td>13440</td>
</tr>

<tr>
<td>problem</td>
<td>13201</td>
</tr>

<tr>
<td>x86</td>
<td>12717</td>
</tr>

<tr>
<td>address</td>
<td>12095</td>
</tr>

<tr>
<td>null</td>
<td>11555</td>
</tr>

<tr>
<td>cpu</td>
<td>11545</td>
</tr>

<tr>
<td>core</td>
<td>11038</td>
</tr>

<tr>
<td>user</td>
<td>11038</td>
</tr>

<tr>
<td>media</td>
<td>10857</td>
</tr>

<tr>
<td>build</td>
<td>10830</td>
</tr>

<tr>
<td>missing</td>
<td>10508</td>
</tr>

<tr>
<td>path</td>
<td>10334</td>
</tr>

<tr>
<td>hardware</td>
<td>10316</td>
</tr>
</tbody>
</table>

<p><br>
好的，所以有大量关于 <code>arm</code> 的工作，许多与 <code>memory</code>、<code>null</code>、<code>pointer</code> 等相关。但如果我们想看看个人在做什么，我们需要其他方法。</p>

<p>其他方法可能是惩罚更常见的词而不完全消除它们。一个标准的归一化指标是逆文档频率（IDF），<code>log(总信息数 / 包含该单词的信息数)</code>。因此，不是按术语计数或术语频率排序，而是尝试按 <code>(术语频率) * log(总信息数 / 包含该单词的信息数)</code> 排序，这通常称为 TF-IDF<sup id="fnref:H"><a href="#fn:H" rel="noopener noreferrer">1</a></sup>。这给我们提供了一个人使用的、其他人不常用单词。</p>

<p>以下是前 40 位 Linux 提交者及其根据 TF-IDF 最常用单词的列表。</p>

<table>
<thead>
<tr>
<th>用户</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
</tr>
</thead>

<tbody>
<tr>
<td>viro</td>
<td>switch</td>
<td>annotations</td>
<td>patch</td>
<td>of</td>
<td>endianness</td>
</tr>

<tr>
<td>tiwai</td>
<td>alsa</td>
<td>hda</td>
<td>codec</td>
<td>codecs</td>
<td>hda-codec</td>
</tr>

<tr>
<td>broonie</td>
<td>asoc</td>
<td>regmap</td>
<td>mfd</td>
<td>regulator</td>
<td>wm8994</td>
</tr>

<tr>
<td>davem</td>
<td>sparc64</td>
<td>sparc</td>
<td>we</td>
<td>kill</td>
<td>fix</td>
</tr>

<tr>
<td>gregkh</td>
<td>cc</td>
<td>staging</td>
<td>usb</td>
<td>remove</td>
<td>hank</td>
</tr>

<tr>
<td>mchehab</td>
<td>v4l/dvb</td>
<td>media</td>
<td>at</td>
<td>were</td>
<td>em28xx</td>
</tr>

<tr>
<td>tglx</td>
<td>x86</td>
<td>genirq</td>
<td>irq</td>
<td>prepare</td>
<td>shared</td>
</tr>

<tr>
<td>hsweeten</td>
<td>comedi</td>
<td>staging</td>
<td>tidy</td>
<td>remove</td>
<td>subdevice</td>
</tr>

<tr>
<td>mingo</td>
<td>x86</td>
<td>sched</td>
<td>zijlstra</td>
<td>melo</td>
<td>peter</td>
</tr>

<tr>
<td>joe</td>
<td>unnecessary</td>
<td>checkpatch</td>
<td>convert</td>
<td>pr_</td>
<td>use</td>
</tr>

<tr>
<td>tj</td>
<td>cgroup</td>
<td>doesnt</td>
<td>which</td>
<td>it</td>
<td>workqueue</td>
</tr>

<tr>
<td>lethal</td>
<td>sh</td>
<td>up</td>
<td>off</td>
<td>sh64</td>
<td>kill</td>
</tr>

<tr>
<td>axel.lin</td>
<td>regulator</td>
<td>asoc</td>
<td>convert</td>
<td>thus</td>
<td>use</td>
</tr>

<tr>
<td>hch</td>
<td>xfs</td>
<td>sgi-pv</td>
<td>sgi-modid</td>
<td>remove</td>
<td>we</td>
</tr>

<tr>
<td>sachin.kamat</td>
<td>redundant</td>
<td>remove</td>
<td>simpler</td>
<td>null</td>
<td>of_match_ptr</td>
</tr>

<tr>
<td>bzolnier</td>
<td>ide</td>
<td>shtylyov</td>
<td>sergei</td>
<td>acked-by</td>
<td>caused</td>
</tr>

<tr>
<td>alan</td>
<td>tty</td>
<td>gma500</td>
<td>we</td>
<td>up</td>
<td>et131x</td>
</tr>

<tr>
<td>ralf</td>
<td>mips</td>
<td>fix</td>
<td>build</td>
<td>ip27</td>
<td>of</td>
</tr>

<tr>
<td>johannes.berg</td>
<td>mac80211</td>
<td>iwlwifi</td>
<td>it</td>
<td>cfg80211</td>
<td>iwlagn</td>
</tr>

<tr>
<td>trond.myklebust</td>
<td>nfs</td>
<td>nfsv4</td>
<td>sunrpc</td>
<td>nfsv41</td>
<td>ensure</td>
</tr>

<tr>
<td>shemminger</td>
<td>sky2</td>
<td>net_device_ops</td>
<td>skge</td>
<td>convert</td>
<td>bridge</td>
</tr>

<tr>
<td>bunk</td>
<td>static</td>
<td>needlessly</td>
<td>global</td>
<td>patch</td>
<td>make</td>
</tr>

<tr>
<td>hartleys</td>
<td>comedi</td>
<td>staging</td>
<td>remove</td>
<td>subdevice</td>
<td>driver</td>
</tr>

<tr>
<td>jg1.han</td>
<td>simpler</td>
<td>device_release</td>
<td>unnecessary</td>
<td>clears</td>
<td>thus</td>
</tr>

<tr>
<td>akpm</td>
<td>cc</td>
<td>warning</td>
<td>fix</td>
<td>function</td>
<td>patch</td>
</tr>

<tr>
<td>rmk+kernel</td>
<td>arm</td>
<td>acked-by</td>
<td>rather</td>
<td>tested-by</td>
<td>we</td>
</tr>

<tr>
<td>daniel.vetter</td>
<td>drm/i915</td>
<td>reviewed-by</td>
<td>v2</td>
<td>wilson</td>
<td>vetter</td>
</tr>

<tr>
<td>bskeggs</td>
<td>drm/nouveau</td>
<td>drm/nv50</td>
<td>drm/nvd0/disp</td>
<td>on</td>
<td>chipsets</td>
</tr>

<tr>
<td>acme</td>
<td>galbraith</td>
<td>perf</td>
<td>weisbecker</td>
<td>eranian</td>
<td>stephane</td>
</tr>

<tr>
<td>khali</td>
<td>hwmon</td>
<td>i2c</td>
<td>driver</td>
<td>drivers</td>
<td>so</td>
</tr>

<tr>
<td>torvalds</td>
<td>linux</td>
<td>commit</td>
<td>just</td>
<td>revert</td>
<td>cc</td>
</tr>

<tr>
<td>chris</td>
<td>drm/i915</td>
<td>we</td>
<td>gpu</td>
<td>bugzilla</td>
<td>whilst</td>
</tr>

<tr>
<td>neilb</td>
<td>md</td>
<td>array</td>
<td>so</td>
<td>that</td>
<td>we</td>
</tr>

<tr>
<td>lars</td>
<td>asoc</td>
<td>driver</td>
<td>iio</td>
<td>dapm</td>
<td>of</td>
</tr>

<tr>
<td>kaber</td>
<td>netfilter</td>
<td>conntrack</td>
<td>net_sched</td>
<td>nf_conntrack</td>
<td>fix</td>
</tr>

<tr>
<td>dhowells</td>
<td>keys</td>
<td>rather</td>
<td>key</td>
<td>that</td>
<td>uapi</td>
</tr>

<tr>
<td>heiko.carstens</td>
<td>s390</td>
<td>since</td>
<td>call</td>
<td>of</td>
<td>fix</td>
</tr>

<tr>
<td>ebiederm</td>
<td>namespace</td>
<td>userns</td>
<td>hallyn</td>
<td>serge</td>
<td>sysctl</td>
</tr>

<tr>
<td>hverkuil</td>
<td>v4l/dvb</td>
<td>ivtv</td>
<td>media</td>
<td>v4l2</td>
<td>convert</td>
</tr>
</tbody>
</table>

<p><br>
这就对了。一些常见词仍然出现——如果手动添加停用词来移除像 <code>cc</code> 和 <code>of</code> 这样的词，这将会改进。但总的来说，我们可以看到谁在做什么。高桥贵志（tiwai）花了很多时间在 hda 领域并处理编解码器，大卫·S·米勒（davem）花了很多时间在 sparc64 上，拉尔夫·贝克尔（ralf）在 mips 上做了大量工作，等等。然后，也许有趣的是，一些人（但不是全部）<code>cc</code> 其他人如此之多，以至于即使在 IDF 惩罚后，它仍然出现在他们的前五列表中。</p>

<p>我们也可以使用这个来查看人们在提交信息中谈论的内容分布与他们提交频率的对比。</p>

<p><img src="https://danluu.com/images/linux-devs-say/null-percentile.png" alt="谁关心 null？这些人！"></p>

<p>这个图表的 x 轴是人，y 轴是相对词使用频率（按 TF-IDF 排名）。在 x 轴上，最频繁的提交者在左边，最少的在右边。在 y 轴上，如果该提交者更频繁地使用单词 <code>null</code>，点就更高，如果使用较少，则更低。</p>

<p><img src="https://danluu.com/images/linux-devs-say/posix-percentile.png" alt="谁关心 POSIX？几乎没有人！"></p>

<p>相对而言，几乎没有人做 <code>POSIX</code> 合规性工作。你实际上可以数出在提交信息中提到 <code>POSIX</code> 的个人。</p>

<p>这是博客文章中你可能期望某种总结或至少一个模糊观点的地方。抱歉。没有这样的运气。我只是因为 TF-IDF 是当前运行的<a href="https://www.coursera.org/course/mmds" rel="noopener noreferrer">挖掘海量数据集</a>课程中提出的无数概念之一而做了这个，我知道如果不写一些代码，它不会真正固定下来。</p>

<p>如果你真的必须有一个结论，TF-IDF 有时很有用且极其容易应用。你应该在应该使用时使用它（当你想看看什么词区分不同的文档/人时），在不应该使用时不要使用它（当你想看看文档/人之间的共同点时）。结束。</p>

<p><small>
我正在通过每篇帖子花费更少时间并只在 30-90 分钟内倾倒东西来实验更多地博客。如果不清楚或完全错误，请<a href="https://twitter.com/danluu" rel="noopener noreferrer">让我知道</a>。认真的。我在这篇上超时了，但主要是因为 argh 数据和表格以及 Julia 中的错误，而不是因为校对。我肯定有错误！</small></p><small>

</small><p><small>感谢 Leah Hanson 发现这篇文章中的一些写作错误，以及 Zack Maril 关于未来如何可能展示随时间变化的对话。
</small></p>
<div>

<hr>

<ol>
<li id="fn:H">我实际上不明白为什么在这里取对数是标准的。有时你想取对数以便处理较小的数字，或者将一堆乘法转换为一堆加法，但这两者在这里都不成立。如果这对你来说显而易见，请<a href="https://twitter.com/danluu" rel="noopener noreferrer">让我知道</a>。
 <a href="#fnref:H" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 10051 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/linux-devs-say/</link>
      <guid isPermaLink="false">https://danluu.com/linux-devs-say/</guid>
      <pubDate>Mon, 24 Nov 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>一周里的错误</title>
      <description>[AI 摘要] 作者记录了一周内遇到的各类软件错误，并指出当前测试不足的问题，倡导使用模糊测试等更高效的方法来发现错误。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 作者记录了一周内遇到的各类软件错误，并指出当前测试不足的问题，倡导使用模糊测试等更高效的方法来发现错误。</div><p>如果要我猜的话，我可能会说在一个普通周里，我大约要处理上百个错误，而在糟糕的一周里则上千个。在一个星期内遇到一百个新错误对我来说并不少见。但当我提到我每天都会遇到多个对我而言的新错误时，通常会引来质疑，而且他们认为如果我们不改变测试方式，这种情况就无法避免。好吧，这里记录了一周内的错误日志，仅限于那周对我来说是新的错误。在简要描述这些错误之后，我将谈谈我们可以做些什么来改善这种情况。显而易见的答案是投入更多精力进行测试，但每个人都已经知道我们应该这么做，却没人去做。不过，这并不意味着无望。</p>

<h3 id="one-week-of-bugs">一周的错误</h3>

<h4 id="ubuntu">Ubuntu</h4>

<p>当我登录我的机器时，看到一个屏幕显示我输入了错误的密码。在五秒的延迟后，它还是让我登录了。这可能至少有两个错误，或许更多。</p>

<h4 id="github">GitHub</h4>

<p>GitHub 从 Pygments 切换到了他们为 Atom 使用的东西，<a href="http://www.greghendershott.com/2014/11/github-dropped-pygments.html" rel="noopener noreferrer">破坏了大多数语言的语法高亮</a>。HN 上的评论表明，这不仅仅影响冷门语言；Java、PHP、C 和 C++ 都有明显的故障。</p>

<p><a href="https://github.com/github/linguist/issues/1717" rel="noopener noreferrer">在一个 GitHub issue 中</a>，一位 GitHub 开发者说</p>

<p><small></small></p><small>

<blockquote>
<p>你当然可以自由地 fork Racket 包并根据需要进行改进。恐怕 GitHub 没有人使用 Racket，所以我们无法判断什么是正确的高亮显示。但当然我们会通过 O P E N S O U R C E 的神奇力量拉取你的更改。</p>
</blockquote>

</small><p><small></small></p>

<p>考虑到最近另一位 GitHub 员工的演讲主题是<a href="http://zachholman.com/talk/move-fast-break-nothing/" rel="noopener noreferrer">“快速行动，不破坏任何东西”</a>，这有点讽刺。更不用说这不太可能奏效了。上次我向 linguist 提交 PR 时，它只有在 <a href="//danluu.com/discourage-oss/" rel="noopener noreferrer">我写了一篇博客文章指出他们有数百个未合并的 PR，其中一些已经拖了一年</a>之后才被合并，该文章在 Reddit 上火了之后他们才合并了一批 PR。据我所知，“O P E N S O U R C E 的神奇力量”是登上了 Reddit/HN 头版或拥有大量 Twitter 粉丝的另一种说法。</p>

<p>另外，图标坏了一段时间。是过去一周的事吗？</p>

<h4 id="linkedin">LinkedIn</h4>

<p>在回复了某人的“InMail”之后，我几天后查看，发现他们的原始消息仍被列为未读（没有回复）。我的回复真的发出去了吗？在对方回复之前，我完全不知道。</p>

<h4 id="inbox">Inbox</h4>

<p>Inbox 应用（不要与 Inbox App 混淆）会在实际下载邮件之前通知您有新消息。在应用本身收到邮件之前需要任意的时间，而且在应用中刷新并不会触发邮件下载。</p>

<p>通知的另一个问题是，有时你收到消息时通知并不会显示。大约一半时间我收到 Gmail 应用的通知时，也会收到 Inbox 应用的通知。另一半时间，通知就丢失了。</p>

<p>总的来说，我收到一封可读消息的通知，大概只有 1/3 的时间。</p>

<h4 id="google-analytics">Google Analytics</h4>

<p>美国附近的一些地方（如墨西哥城和多伦多）被认为不配拥有自己的国家。位置地图显示这些城市位于美国之外的蓝色海洋中。</p>

<h4 id="octopress">Octopress</h4>

<p>如果你允许文章显示在主页（而不是索引页）并使用语法将某些内容放在折叠下方，那么脚注在主页上无法正常工作。你得到的不是指向脚注的链接，而是一个无处可去的锚文本引用。这还不算我已经知道的另一个脚注错误。</p>

<p>标签只在某些情况下转换为小写，但在其他情况下不转换，这意味着任何带大写字母的标签（有时）都无法正常工作。我甚至不用标签，但我从别人的博客上注意到了这一点。</p>

<p>我的 Atom 订阅源<a href="https://twitter.com/chmaynard/status/534540677078855680" rel="noopener noreferrer">无法正常工作</a>。</p>

<p>如果你认为性能错误是问题的话，我在过去一周注意到很多这类问题，以至于它们<a href="//danluu.com/octopress-speedup/" rel="noopener noreferrer">有自己的博客文章</a>。</p>

<h4 id="running-with-rifles-game">Running with Rifles（游戏）</h4>

<p>本应使敌人晕眩的武器却会伤害你。直到有人提到这将在下一个版本中修复，我才意识到这是个错误。</p>

<p>可以隔着墙刺人。</p>

<p>如果你在切换关卡时按住某个键，你的角色在下一关中会继续执行该动作，即使你已经松开了键。</p>

<p>你角色的位置会随机与服务器不同步。发生这种情况时，我发现唯一可靠的修复方法是随机射击一会儿。显然射击会导致客户端做类似向服务器发送位置快照的事情？不确定为什么那不会规律地发生。</p>

<p>载具会随机刷新在你头顶，导致你死亡。</p>

<p>你会随机刷新在载具下方，导致你死亡。</p>

<p>AI 队友在扔手榴弹时不考虑墙壁或建筑物，这常常导致他们自杀。</p>

<p>手榴弹有时会伤害你最后乘坐的载具，即使你离载具很远。</p>

<p>AI 载具可能会在几乎任何障碍物上永久卡住。</p>

<p>这是我大约 15 年来玩的第一款电子游戏。我倾向于认为游戏相当可靠，但这可能是因为 15 年前的游戏要简单得多。MS Paint 也没有太多错误。</p>

<p>更新：上面的同步问题是由内存泄漏引起的。我最初以为是游戏的联机代码非常差，但事实证明在服务器重启后的前 6 个左右小时里它表现还可以。有一些脚本可以定期重启服务器，但这些脚本有时会出现故障导致停止运行。当这种情况发生在官方服务器上时，游戏基本上就无法在线玩了。</p>

<h4 id="julia">Julia</h4>

<p>Unicode 序列导致 match/ismatch 因边界错误而崩溃。</p>

<p>Unicode 序列导致将字符串用作哈希索引时因边界错误而崩溃。</p>

<p>异常被随机地未被 catch 捕获。这很糟糕，因为将东西放在 try/catch 中是上述两个错误的解决方法。我以前见过其他变体；这可能算不上新错误，因为它可能是我之前见过的某个错误的同一根本原因。</p>

<p>某个函数（我忘了是哪个）在给定错误长度参数时会返回完全错误的结果。你甚至可以给它错误类型的长度参数，它仍然会“工作”，而不是抛出异常或返回错误。</p>

<p>如果 API 设计错误也算的话，有时操作可迭代对象的方法将对象作为第一个参数，有时则不。这类例子太多了，无法一一列举。举个例子，<code>match</code> 先接收正则表达式再接收字符串，而 <code>search</code> 先接收字符串再接收正则表达式。这周，我在一个数值函数上遇到了类似的问题。</p>

<p>当然，我仍然遇到那个<a href="https://github.com/JuliaLang/julia/issues/8631" rel="noopener noreferrer">存在一个多月的破坏 convert 的错误</a>，它非常普遍，任何导致它发生的事情都会使 Julia 无法使用。</p>

<p>这里有一个可能是 OS X 的错误？我有一些坏代码导致 Julia 代码中的无限循环。在 <code>while</code> 循环中实际上什么都没发生，所以它会永远运行下去。糟糕。这个错误是，它不知何故导致我的系统内存耗尽并变得无响应。活动监视器显示内核占用了越来越多的内存，当我杀掉 Julia 进程后内存就恢复了。</p>

<p>我不会列出包中的错误，因为太多了。即使在 Julia 核心中，我也遇到了很多 Julia 错误，以至于我不再提交 bug 了。这太打断工作了。等我有时间时，我应该花一天时间提交所有我记得的错误，但我认为为每个错误写一份好的、可重现的错误报告可能真的需要一整天。</p>

<p>更多关于我为什么遇到这么多 Julia 错误的信息，请参见<a href="//danluu.com/julialang/" rel="noopener noreferrer">这篇文章</a>。</p>

<h4 id="google-hangouts">Google Hangouts</h4>

<p>开始聊天时：“此视频通话目前不可用。请稍后再试。”</p>

<p>同一个人出现在联系人列表中两次。两个条目列出的电子邮件相同，双击任何一个都会进入相同的聊天窗口。</p>

<h4 id="uw-health">UW Health</h4>

<p>大约 10% 的储物柜的锁扣机构与门不完全齐平，所以除非你在将锁扣移到关闭位置时用力推门，否则你的储物柜实际上不会被锁上。</p>

<p>没有视觉（或其他）指示表明锁扣未能锁上。据我所知，唯一的方法是在尝试锁上后拉动手柄，看门是否打开。</p>

<h4 id="coursera-mining-massive-data-sets">Coursera，大规模数据集挖掘</h4>

<p>选择正确的测验答案得 0 分。解决方法（论坛上多人独立发现）是不断提交，直到正确答案给你 1 分。这是在测验出现错误答案选项导致没有正确答案的一周后发生的。</p>

<h4 id="facebook">Facebook</h4>

<p>如果在滚动某人的主页时用鼠标做了“错误”的事情，顶部的蓝色栏不知怎地会变成一个与封面照片一样大的巨大方块，并且在你向下滚动时不会消失。</p>

<p>点击右侧的活动侧边栏会弹出一个被其他 UI 元素覆盖的内容，使其无法阅读或交互。</p>

<h4 id="pandora">Pandora</h4>

<p>某个电台一直播放电子音乐，即使我每次听到电子歌曲都点了踩。种子歌曲是迪士尼音乐剧里的一首歌。</p>

<h4 id="dropbox-zulip">Dropbox/Zulip</h4>

<p>一个老问题是，你无法禁用来自 <code>@all</code> 提及的通知。由于据我所知，没有一个与我相关，而 <code>@all</code> 通知的数量超过了其他通知，这意味着我收到的大多数通知都是垃圾信息。</p>

<p>新问题是，我试图屏蔽那些经常发送垃圾信息的频道，但通知会绕过屏蔽。我的解决方法是禁用了所有通知，但现在如果有人私信我或使用 <code>@danluu</code>，我也不会收到通知。</p>

<h4 id="chrome">Chrome</h4>

<p>我的 Chrome 版本（无插件）无法阅读 Rust 指南。</p>

<p><img src="https://danluu.com/images/everything-is-broken/rust_guide.png" alt="无法阅读的引用块" width="773" height="356"></p>

<h4 id="google-docs">Google Docs</h4>

<p>我尝试与 <a href="http://rose.github.io/" rel="noopener noreferrer">Rose Ames</a> 协作写一份文档。我这边一切正常，但她看到的所有内容都是乱码，所以我们改用 hackpad。</p>

<p>直到我尝试 hackpad 之后我才注意到，Docs 真的很慢。Hackpad 的响应速度快得惊人，但这其实只是因为 Docs 很卡。这和我试用 fastmail 后的感觉一样。在使用不卡的东西之前，Gmail 似乎并不慢。</p>

<h4 id="hackpad">Hackpad</h4>

<p>文档创建几小时后，它说“ROSE AMES 于 1 分钟前创建了此文档。”</p>

<p>右侧列表显示谁在房间里，即使只有 2 个人，它也显示一堆人（N 个人）。</p>

<h4 id="rust">Rust</h4>

<p>在那之后，Rose 和我一起学习了 Rust 指南。我不会在这里列出问题，因为它们太长了，我们那个充满错误的 hackpad 文档至少比这篇博客文章长两倍。而且这不是针对 Rust 文档的批评，这些文档实际上比几乎所有其他语言的文档都要好得多。</p>

<h3 id="wat">WAT</h3>

<p></p><blockquote lang="en"><p>我心情超好。一切都还是坏的，但现在它变得好笑了，而不是让我生气了。</p>— Gary Bernhardt (@garybernhardt) <a href="https://twitter.com/garybernhardt/status/296033898822004738" rel="noopener noreferrer">2013 年 1 月 28 日</a></blockquote>
<p></p>

<p>这是怎么回事？如果我包括那些因为软件太烂而没有列出的错误（列出所有错误会使这篇文章长度增加两倍），那么一周内大约有 80 个错误。而且这还只是我之前没见过的错误。为什么所有东西都有这么多错误？</p>

<p>对这种评论的常见回应是：这是开源的，你这忘恩负义的家伙，为什么不自己修复错误？我确实修复了一些错误，但一周内真的没有足够的时间来调试和修复我遇到的每一个错误。这里存在公地悲剧效应。如果只有少数错误，开发者很可能会修复他们遇到的错误。但如果错误多到让你觉得改进无望，很多人就不会费心了。</p>

<p>我打算看看 Julia，因为我已经对它很熟悉了，但我预计它的测试水平并不比大多数其他项目好或差（除了 Chrome，它相对测试得比较好）。作为衡量投入多少测试工作的粗略指标，它有 18k 行测试代码。但相比 <code>src</code> 和 <code>Base</code> 中大约 108k 行的代码。</p>

<p>在我工作过的每个地方，一个只是为了获得初步性能数字或可能试验 API 而存在的 2k 行原型，都期望有至少同等数量的测试，否则你怎么知道它不是坏到让你的性能估计偏差了一个数量级？因为复杂性并不随代码行数线性增长，所以随着原型变大，人们期望有更多的测试代码。</p>

<p>18k 行测试代码对应 108k 行源代码，用户将会发现错误。很多错误。</p>

<p>这里我应该呼吁更认真地对待测试，并<a href="//danluu.com/empirical-pl/#fn2" rel="noopener noreferrer">投入真正的努力</a>。但我们都知道那不会奏效。要使 Julia 达到测试不足的原型的测试水平（错误地假设复杂性随规模线性增长），需要 90k 行测试代码。那是两个人年的工作，还不包括调试和修复错误的时间（可能接近四到五年）。谁来做？没人。写测试就像写文档。每个人都知道应该做。告诉人们应该做，增加的信息量为零<sup id="fnref:D"><a href="#fn:D" rel="noopener noreferrer">1</a></sup>。</p>

<p>鉴于人们不会在测试上投入任何努力，什么是最好的方法？</p>

<p>基于属性的测试。生成式测试。随机测试。具体化测试（这个术语出现之前就已存在）。静态分析。<a href="//danluu.com/everything-is-broken/" rel="noopener noreferrer">模糊测试</a>。<a href="//danluu.com/bugalytics/" rel="noopener noreferrer">统计性错误发现</a>。有很多选择。有些实际上是同一回事，因为我们使用的术语不一致且有缺陷。我将任意选择一个来讨论，但它们都值得研究。</p>

<p>不过人们常常对这些感到畏惧。我看过很多关于这些的演讲，它们常常把这东西说得很难。<a href="http://embed.cs.utah.edu/csmith/" rel="noopener noreferrer">Csmith</a> 有 4 万行代码。<a href="https://code.google.com/p/american-fuzzy-lop/" rel="noopener noreferrer">American Fuzzy Lop</a> 的编译时插桩很聪明，可以<a href="http://lcamtuf.blogspot.com/2014/11/pulling-jpegs-out-of-thin-air.html" rel="noopener noreferrer">生成有效的 JPEG</a>。<a href="http://researcher.watson.ibm.com/researcher/view_group_subpage.php?id=2989" rel="noopener noreferrer">Sixth Sense</a> 在探索方面具有与 American Fuzzy Lop 相同的智能，并且此外，使用符号执行来详尽地探索大部分状态空间；如果它能将状态空间压缩到足以详尽搜索的程度，它将形式化地验证你的断言是否成立；否则它仅通过覆盖不同的路径和状态来尝试获得最佳的测试覆盖率。此外，它将使用符号等价检查来检查你代码的不同版本。</p>

<p>所有这些都非常令人印象深刻，但你不需要形式化方法的博士学位来做这些。你可以写一个模糊测试器，它在一小时内就能找出大量错误<sup id="fnref:T"><a href="#fn:T" rel="noopener noreferrer">2</a></sup>。说真的。我有点不好意思链接这个，但<a href="https://github.com/danluu/Fuzz.jl" rel="noopener noreferrer">这个模糊测试器</a>大约一个小时就写好了，并且找到了 20-30 个错误<sup id="fnref:R"><a href="#fn:R" rel="noopener noreferrer">3</a></sup>，包括不正确的代码生成，以及在乘法和指数等基本操作上的崩溃。我猜再花 2-3 小时可以找出另外 20-30 个错误（支持更多类型），也许再花一天时间又能找出 20-30 个（支持非常基本的随机表达式）。我提到这个不是因为它好。它一点也不好。它非常糟糕。但这就是重点。你可以用一个小时拼凑出一个荒谬的黑客工具，结果它会相当有用。</p>

<p>与手动编写单元测试相比：即使我事先知道错误是什么，我也很难在一小时内编写足够快的代码来生成 30 个错误。一天 30 个错误？当然可以，但如果我不是事先知道错误是什么的话就不行了。这并不是说单元测试没有价值，但如果你要花几个小时写测试，花几个小时写一个模糊测试器会比花几个小时写单元测试更有成效。你打字可能达到每分钟 100 个词，但你的 CPU 可以轻松执行每分钟 2000 亿条指令。这没得比。</p>

<p>写一个模糊测试器真正需要什么？嗯，你需要为程序生成随机输入。在<a href="https://github.com/danluu/Fuzz.jl" rel="noopener noreferrer">这个例子</a>中，我们是在某个命名空间内生成随机函数调用。很简单。它花了一个小时的唯一原因是因为我没有真正理解 Julia 的反射能力，无法轻松生成随机类型，导致我不得不手动编写类型生成的部分。</p>

<p>这适用于很多不同类型的程序。有 GUI 吗？随机点击 UI 元素相当容易。读取文件或网络上的东西？生成（或变异）随机数据很简单。这是任何人都可以做到的事情。</p>

<p>但这不是万能药。懒散的测试意味着<a href="//danluu.com/cpu-bugs/" rel="noopener noreferrer">你的用户会发现错误</a>。然而，即使开发者不会在测试上投入足够的时间，我们仍然可以比现在做得好得多。</p>

<p><small></small></p><small>

<h4 id="resources">资源</h4>

<p>有很多很好的资源，但如果你刚开始，我发现<a href="http://blog.regehr.org/archives/1039" rel="noopener noreferrer">这篇对模糊测试器类型的描述</a>是我读过的最有帮助（也最简单）的东西之一。</p>

<p>John Regehr 有一个关于软件测试的 <a href="https://www.udacity.com/course/cs258" rel="noopener noreferrer">Udacity 课程</a>。我还没上完（Pablo Torres 刚刚提到它），但鉴于 Regehr 博士写作的质量，我预计课程会很好。</p>

<p>更多关于我测试观点的内容，请见<a href="//danluu.com/testing/" rel="noopener noreferrer">这里</a>。</p>

<h4 id="acknowledgments">致谢</h4>

<p>感谢 Leah Hanson 和 Mindy Preston 捕获写作错误，Steve Klabnik 解释 Chrome 错误的原因/修复（损坏的网页字体），以及 Phillip Joseph 发现一个 markdown 错误。</p>

<p>我正在尝试通过减少每篇文章的时间，只是在 30-90 分钟内快速写出来，以进行更多博客写作。如果某些内容不清楚或完全错误，请<a href="https://twitter.com/danluu" rel="noopener noreferrer">告诉我</a>。说真的。</p>

</small><p><small></small></p>
<div>

<hr>

<ol>
<li id="fn:D"><p>如果我真想说服你，我会专门写一篇文章讨论商业案例，深入研究数据并试图计算错误的成本。那篇未写的文章的简短版本是：响应时间已被充分研究，并且已知 100 毫秒的额外延迟会给你带来显著的收入损失。1 秒的延迟就是灾难。你觉得这和产品完全无法工作相比如何？</p>

<p>与 100 毫秒的延迟相比，当页面加载后出错导致完全无法使用时，有多糟糕？如果它破坏了用户状态，迫使用户重新输入他们想放入购物车的所有内容呢？移除一次额外的点击就值大量的收入，而现在我们讨论的是为随机子集用户增加 10 次额外点击或无限延迟。而且不是一小部分。想停止烧钱吗？写测试。如果那太费劲，至少<a href="//danluu.com/bugalytics/" rel="noopener noreferrer">利用你已有的数据来发现错误</a>。</p>

<p>当然，有时烧钱是值得的。也许你的火箭是用燃烧的钞票驱动的。如果你是一个快速增长的初创公司，收入增加 20% 可能不值那么多。最好专注于添加能驱动增长的功能。重点不是你一定要写更多测试，而是你一定要算笔账，看看是否应该写更多测试。</p>
 <a href="#fnref:D" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn-T">加上调试时间。
 <a href="#fnref:T" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:R">我真的需要更新 readme，加上更多错误。
 <a href="#fnref:R" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 11248 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/everything-is-broken/</link>
      <guid isPermaLink="false">https://danluu.com/everything-is-broken/</guid>
      <pubDate>Tue, 18 Nov 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>将此网站速度提升50倍</title>
      <description>[AI 摘要] 作者通过最小化脚本、CSS、字体并使用CDN，将他们的Octopress博客加载速度提升了50倍。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 作者通过最小化脚本、CSS、字体并使用CDN，将他们的Octopress博客加载速度提升了50倍。</div><p>我看过所有这些研究表明，页面加载时间减少100毫秒对页面浏览量、转化率等有显著影响，但我从未真正尝试过优化我的网站。这个博客是一个静态的Octopress网站，托管在GitHub Pages上。静态网站应该很快，而GitHub Pages使用Fastly，这也应该很快，所以一切应该都很快，对吧？</p>

<p>以前没做过这个，我不知道该怎么做。但在一个关于互联网如何工作的精彩演讲中，<a href="https://twitter.com/danielespeset" rel="noopener noreferrer">Dan Espeset</a>建议尝试<a href="http://www.webpagetest.org" rel="noopener noreferrer">webpagetest</a>；我们来试试吧。</p>

<p></p>

<p>以下是我几乎原始的Octopress设置所显示的结果<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">1</a></sup>。我唯一做的更改是启用了Google Analytics、文章底部的社交媒体按钮，以及为表格添加了CSS样式（默认情况下，表格没有样式且难以阅读）。</p>

<p><img src="https://danluu.com/images/octopress-speedup/octo_initial.png" alt="开始渲染时间：9.7秒">
<img src="https://danluu.com/images/octopress-speedup/octo_initial_detail.png" alt="视觉完成时间：10.9秒"></p>

<p>12秒才看到第一页！发生了什么？我以为静态网站应该很快。第一个字节在不到半秒内到达，但页面直到9秒后才开始渲染。</p>

<p><img src="https://danluu.com/images/octopress-speedup/octo_initial_waterfall.png" alt="大量js、CSS和字体"></p>

<p>看起来首先发生的是我们加载了大量的<code>js</code>和<code>CSS</code>。查看源代码，我们在<code>source/_includes/head.html</code>中有了所有这些<code>js</code>。</p>

<pre><code>&lt;script src="{{ root_url }}/javascripts/modernizr-2.0.js"&gt;&lt;/script&gt;
&lt;script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"&gt;&lt;/script&gt;
&lt;script&gt;!window.jQuery &amp;&amp; document.write(unescape('%3Cscript src="./javascripts/lib/jquery.min.js"%3E%3C/script%3E'))&lt;/script&gt;
&lt;script src="{{ root_url }}/javascripts/octopress.js" type="text/javascript"&gt;&lt;/script&gt;
{% include google_analytics.html %}
</code></pre>

<p>我对网页优化一无所知，但Espeset提到<code>js</code>会阻碍页面加载和渲染。如果我们把脚本移到<code>source/_includes/custom/after_footer.html</code>会怎样？</p>

<p><img src="https://danluu.com/images/octopress-speedup/octo_js_after_footer.png" alt="开始渲染时间：4.9秒"></p>

<p>这好多了！我们刚刚在加载时间和开始渲染时间上节省了大约4秒。</p>

<p>这些脚本标签加载了modernizer、jquery、octopress.js和一些google analytics的东西。这个<code>octopress.js</code>里面有什么？它主要是支持嵌入flash视频、delicious集成和github仓库集成的代码。有几项确实被我的网站使用了，但大部分代码是无用的。</p>

<p>另外，为什么有多个<code>js</code>文件？Espeset还提到连接是有限的资源，如果我们有很多不同的文件，我们会耗尽同时打开的连接数。让我们去掉所有未使用的<code>js</code>，并将剩余的<code>js</code>合并到一个文件中。</p>

<p><img src="https://danluu.com/images/octopress-speedup/octo_one_js_file.png" alt="开始渲染时间：1.4秒"></p>

<p>好多了！但等一下。我需要<code>js</code>做什么？据我所知，我的网站仍然使用octopress的<code>js</code>的唯一用途是让你可以通过点击右侧边栏来来回推它，而jquery和modernizer只对octopress中使用的js是必要的。我从未使用过它，根据页面分析，也没有其他人使用。让我们去掉它。</p>

<p><img src="https://danluu.com/images/octopress-speedup/octo_no_js.png" alt="开始渲染时间：0.7秒">
<img src="https://danluu.com/images/octopress-speedup/octo_no_js_detail.png" alt="视觉完成时间：1.2秒"></p>

<p>这并没有改变总加载时间多少，但浏览器开始渲染得更早了。现在网站在1.2秒后视觉完成，而最初是9.6秒——提高了8倍。</p>

<p>还有什么？在每篇文章底部，仍然有一些用于twitter和fb小部件的js，但这些都在渲染后加载，所以即使它们让“加载时间”数字看起来很糟，它们也不会真正影响用户体验。</p>

<p><img src="https://danluu.com/images/octopress-speedup/octo_no_js_pie.png" alt="字体真多！"></p>

<p>这是一个饼图，显示了页面中每种文件类型所占的字节数。显然，大部分负载用于字体。尽管我的参考文章是图像异常密集的博客文章，但字体占43.8%，图像占的比例非常小，以至于webpagetest甚至没有列出数字。我的浏览器不是已经有默认字体了吗？我们能直接用它们吗？</p>

<p><img src="https://danluu.com/images/octopress-speedup/octo_no_fonts.png" alt="开始渲染时间：0.6秒">
<img src="https://danluu.com/images/octopress-speedup/octo_no_fonts_detail.png" alt="视觉完成时间：0.9秒"></p>

<p>事实证明，我们可以。网页现在在0.9秒内视觉完成——提高了12倍。对于“重复视图”<sup id="fnref:R"><a href="#fn:R" rel="noopener noreferrer">2</a></sup>，改进不那么显著——只有8.6倍——但仍然很好。</p>

<p>剩下的一个明显问题是页眉加载了两个css文件，其中一个未压缩。这占用了两个连接，并发送了比必要的更多数据。压缩另一个css文件并合并它们，这会进一步加快速度。</p>

<p><img src="https://danluu.com/images/octopress-speedup/octo_one_css.png" alt="开始渲染时间：0.6秒">
<img src="https://danluu.com/images/octopress-speedup/octo_one_css_detail.png" alt="视觉完成时间：0.7秒"></p>

<p>视觉完成时间现在是0.7秒——提高了15.6倍<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">3</a></sup>。而且这是在图像异常密集的页面上。</p>

<p><img src="https://danluu.com/images/octopress-speedup/octo_one_css_waterfall.png" alt="主要是图像加载时间"></p>

<p>此时，在页面开始显示之前发生的唯一事情是：加载HTML、加载一个<code>css</code>文件和加载大图像（reliability.png）。</p>

<p>我们已经压缩了css，所以剩下要做的主要是让大图像更好。我已经对所有图像运行了<code>optipng -o7 -zm1-9</code>，但ImageOptim能够再削减4%的图像，带来了轻微改进。在我所有文章的所有图像中，ImageOptim比optipng额外减少了20%的图像，但在此情况下帮助不大。</p>

<p>我还尝试指定图像大小，看看这是否能让页面在图像下载完成前渲染，但结果差异不大。</p>

<p>之后，我想不出其他可以尝试的方法了，但webpagetest有一些建议。</p>

<p><img src="https://danluu.com/images/octopress-speedup/octo_suggestions.png" alt="Blargh github pages"></p>

<p>显然，我所在的服务器很慢（在初始请求后发送第一个字节得到D）。它还建议缓存静态内容，但当我查看个别建议时，它们大多针对我不托管/控制的小部件。我应该使用CDN，但Github Pages不会为裸域名将内容放在CDN上，除非你使用DNS别名记录，而我的DNS提供商不支持别名记录。这是两个停止使用Github Pages的原因（或者一个原因离开Github Pages，另一个原因获取另一个DNS提供商），所以我切换到了Cloudflare，这将第一个字节的时间缩短了100多毫秒。</p>

<p>注意，如果你将Cloudflare用于静态网站，你需要创建一个“Page Rule”并启用“Cache Everything”。默认情况下，Cloudflare不缓存HTML，这对于主要是HTML的静态博客来说毫无意义。如果你做了这里的优化，你也应该避免他们的“Rocket Loader”功能，它试图通过加载阻塞的javascript来异步加载js。“Rocket Loader”就像AMP一样，它可以加速大型臃肿的网站，但对于中等优化的网站来说却会减速。</p>

<p>以下是我在最初启用Cloudflare后意识到需要创建“Page Rule”之前发生的情况。</p>

<p><img src="https://danluu.com/images/octopress-speedup/octo_cloudflare_wat.png" alt="Cloudflare在1GB中节省了80MB"></p>

<p>这大约是2013年一天的流量。最初，Cloudflare提供我的CSS并重定向到Github Pages获取HTML。然后我内联了CSS，Cloudflare真的什么都没做。总体上，Cloudflare在1GB流量中提供了80MB，因为它只缓存图像，而这个博客图像相对较轻。</p>

<p>我没有谈内联CSS，但它很容易，并且在第一次访问时带来巨大的速度提升，因为这意味着只需要一个连接来显示页面，而不是两个顺序连接。对于未来的访问来说，这是一个缺点，因为它意味着每个页面都必须重新下载CSS，但由于我的大多数流量来自偶然发现单篇文章的人，他们不会点击其他任何内容，所以这是一个净胜利。在<code>_includes/head.html</code>中</p>

<pre><code>&lt;link href="{{ root_url }}/stylesheets/all.css" media="screen, projection" rel="stylesheet" type="text/css"&gt;
</code></pre>

<p>应该改为</p>

<pre><code>{\% include all.css %}
</code></pre>

<p>此外，css中有很多无意义的冗余。去掉那些作为不了解CSS的人可以看出无意义的东西（如支持delicious、支持Firefox 3.5及以下版本、firefox标记为语法错误的行，如<code>no-wrap</code>而不是<code>nowrap</code>）将剩余的CSS减少了约一半。还有很多重复，我期望CSS可以再减少4倍，但那需要真正了解CSS。只做这些事情，我们就达到了网页在0.4秒内视觉完成。</p>

<p><img src="https://danluu.com/images/octopress-speedup/octo_inline_css.png" alt="内联css"></p>

<p>这是<code>10.9/.4 = 27.5</code>倍的速度提升。对移动设备的影响更为显著；在那里，接近50倍。</p>

<p>我不确定该怎么想这一切。一方面，我很高兴能够将网站速度提升25-50倍。另一方面，我将这种幅度的提升与将纯Ruby代码移植到优化的C++、优化的C++到GPU或GPU到快速临时ASIC联系在一起。怎么可能一个对网页开发一无所知的人，通过观看一场演讲然后折腾25分钟就能获得这种提升？我原本希望能找到100毫秒的空闲，但结果发现Octopress设置中不仅有100毫秒，甚至有1000毫秒或10000毫秒的空闲。根据我见过的一项研究，从1000毫秒到3000毫秒会让你损失20%的读者和50%的点击率。我没有见过研究从400毫秒到10900毫秒的情况，因为网站如此缓慢的想法太荒谬了，以至于人们甚至没有研究这种可能性。但许多网站确实如此！<sup id="fnref:D"><a href="#fn:D" rel="noopener noreferrer">4</a></sup></p>

<h3 id="update">更新</h3>

<p>我发现很难折腾削减Octopress附带的庞大CSS文件，所以我移除了所有CSS，然后添加了几行以允许导航栏。这在上面的桌面基准测试中几乎没有区别，但对于慢速连接来说是一个明显的改进。<a href="//danluu.com/web-bloat/" rel="noopener noreferrer">对于56k连接以及高丢包连接，差异相当显著</a>。</p>

<p>从我做出这一改变的那天起，我的分析数据显示参与度和流量有显著改善。这里有很多混淆因素，无法说明是什么导致了这种变化（性能提升、完全缺乏样式等），但有几件事我觉得很有趣。首先，它似乎可能表明保持行长度短的建议是不正确的，因为如果它有很大影响，它会压倒其他变化并导致参与度降低而不是增加。其次，尽管Octopress设计被广泛使用和赞扬（在我开始写博客时，它似乎是程序员中最常用的博客主题），但它似乎导致博客（或者至少这个博客）比完全没有样式更少的读者。没有样式肯定不是最佳的，但没有任何样式胜过当时最流行的程序员博客样式，这有点好笑，这意味着它可能也胜过了wordpress、<a href="https://danluu.com/writing-non-advice/#svbtle" rel="noopener noreferrer">svtble</a>、blogspot、medium等，因为它们拥有与Octopress相同的大部分成分。</p>

<p><small></small></p><small>

<h4 id="resources">资源</h4>

<p>不幸的是，我所指的演讲视频仅限于<a href="https://www.recurse.com/scout/click?t=b504af89e87b77920c9b60b2a1f6d5e8" rel="noopener noreferrer">RC</a>校友访问。如果你是RC校友，<a href="https://community.hackerschool.com/t/monday-night-talks-84-daniel-espeset-on-the-anatomy-of-a-web-request/25" rel="noopener noreferrer">查看这个</a>。否则<a href="https://hpbn.co/" rel="noopener noreferrer">高性能浏览器网络</a>很棒，但更长。</p>

<h4 id="acknowledgements">致谢</h4>

<p>感谢Leah Hanson、Daniel Espeset和Hugo Jobling的评论/纠正/讨论。</p>

<p>我不是前端人员，所以我看待这些基准测试的方式可能完全错误。如果是这样，请<a href="https://twitter.com/danluu" rel="noopener noreferrer">让我知道</a>。</p>

</small><p><small></small></p>
<div>

<hr>

<ol>
<li id="fn:S">来自2013年9月的当前版本。其中一些问题可能已经修复，但根据其他人尝试更新其Octopress安装的极其痛苦的经历，似乎不值得尝试获取更新版本的Octopress。
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:R">为什么“重复视图”比“首次视图”慢？
 <a href="#fnref:R" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:1">如果你观看<a href="http://www.webpagetest.org/video/view.php?id=141115_AP_12A9.1.0" rel="noopener noreferrer">原始版本</a>和<a href="http://www.webpagetest.org/video/view.php?id=141116_RH_7WC.1.0" rel="noopener noreferrer">此版本</a>的视频，差异相当显著。
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:D">例如，<a href="//danluu.com/web-bloat/#appendix-experimental-caveats" rel="noopener noreferrer">slashdot</a>通过FIOS加载需要15秒。上面显示的测试是在Cable上进行的，这<a href="//danluu.com/web-bloat/" rel="noopener noreferrer">慢得多</a>。
 <a href="#fnref:D" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 9035 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/octopress-speedup/</link>
      <guid isPermaLink="false">https://danluu.com/octopress-speedup/</guid>
      <pubDate>Mon, 17 Nov 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>构建多久失败一次？</title>
      <description>[AI 摘要] 本文分析发现，开源项目的构建失败频率远高于工作项目，且易于预防却未被普遍解决。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文分析发现，开源项目的构建失败频率远高于工作项目，且易于预防却未被普遍解决。</div><p>我注意到，在开源项目中，构建失败和测试不通过的频率远高于“工作”项目。我不确定这多少是我的主观感受，多少是客观事实，因此我抓取了GitHub几个热门分类项目<sup id="fnref:C"><a href="#fn:C" rel="noopener noreferrer">1</a></sup>的Travis CI数据。</p>

<p></p>

<p><img src="https://danluu.com/images/broken-builds/reliability.png" alt="构建可靠性图表。感谢 nu、oryx、caffe、catalyst 和 Scala 项目。" width="640" height="1280"></p>

<p>作为参考，在我工作过的所有地方，构建系统的可靠性达到两个9（99%正常运行时间）会被认为是很差的。这意味着构建系统一年中有超过三天半的时间处于失败状态，或者每月有七个小时。即使达到三个9（99.9%正常运行时间），每月仍有约四十五分钟的宕机时间。如果没有强制性的系统来阻止人们提交有问题的代码，这勉强可以接受，但对于一个严肃对待构建系统正常工作的地方来说，这是相当糟糕的。</p>

<p>相比之下，两个9的可靠性远高于我所抽取数据的项目的平均水平<sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">2</a></sup>——40个项目中只有8个达到这个可靠性。几乎有两倍多的项目（40个中的15个）甚至达不到一个9（90%）的正常运行时间。而且我的样本严重偏向于更可靠的项目。有些项目足够知名，被列入了GitHub精心策划的推荐列表。这本身就造成了数据偏差。而且我只抓取了那些重视测试并设置了TravisCI的项目的数据<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">3</a></sup>，这引入了更强的偏差。</p>

<p>为了确保我没有抓取到坏的样本，我移除了任何初始的失败测试集（通常在人们尝试设置TravisCI且配置错误时会有大量失败），以及那些使用其他系统跟踪构建、仅将Travis作为备用方案的项目（例如Rust）<sup id="fnref:T"><a href="#fn:T" rel="noopener noreferrer">4</a></sup>。</p>

<p>为什么在工作中构建不会一直失败？工程师不喜欢等待别人来修复构建失败，而经理们可以做一个简单的计算：N个闲置工程师 × X小时的构建宕机时间 = $Y美元的浪费。</p>

<p>但同样的逻辑也适用于开源项目！浪费的不是美元，而是贡献者的时间。</p>

<p>Web程序员非常清楚，网页加载速度额外增加100毫秒的延迟会对转化率产生显著影响。那么，当一个潜在的贡献者花20分钟安装依赖项，再花一小时构建你的项目，结果却发现构建失败了，这对转化率又有什么影响呢？</p>

<p>我曾经深挖过这类失败以寻找错误，通常假设那一定是我机器特有的配置问题。但在花了数年时间调试我在干净构建运行<code>make check</code>时遇到的失败后，我发现那通常只是有人提交了有问题的代码。现在，如果我正考虑为一个项目做贡献或者试图修复一个bug，而构建无法工作，我会直接转向另一个项目。</p>

<p>频繁构建失败最糟糕的一点是它们很容易预防<sup id="fnref:E"><a href="#fn:E" rel="noopener noreferrer">5</a></sup>。Graydon Hoare 字面意思上称保持构建干净是“非火箭科学法则”，并编写了一个任何人都可以使用的开源工具（bors）来实现这个“非火箭科学”目标。然而，大多数开源项目仍然忍受着损坏和失败的构建，以及相关的开发者时间损失和开发者“转化率”损失。</p>

<p><small>请不要过度解读图表中的单个项目数据。我发现有趣的是一些趋势：DevOps项目往往比编程语言更可靠，编程语言比Web框架更可靠，而机器学习项目则参差不齐（但大多可靠）。但就单个项目而言，各种因素都可能导致其数据不好看。</small></p><small>

<p>感谢 Kevin Lynagh、Leah Hanson、Michael Smith、Katerina Barone-Adesi 和 Alexey Romanov 提出的意见。</p>

<p>另外，感谢 Puppetlabs 的 Michael Smith 友好提醒，并一起检查了 puppet 的构建数据，以确保我的脚本没有错误。这是我最具争议的博客文章之一，因为没人愿意相信自己项目的构建失败频率比其他项目的更高。但是，尽管只需大约一分钟就可以下载一个项目的数据，并使用本文中的链接进行健全性检查，但只有一个人真正和我一起查看了数据，而一堆人则在没有检查数据的情况下就告诉我它显然是错误的。</p>

<p>这并不是说我的脚本没有bug。这是一个快速编写的脚本，可能有bug，我随时欢迎提交bug报告！但一些被反复报告的非bug问题包括：从所有分支而非主分支获取数据、获取所有PR的数据而不仅是实际提交到主分支的代码的数据、以及使用失败构建次数而非构建宕机时间。我很确定，你验证这些说法错误所需的时间与提出这些说法的时间差不多，但这并不能阻止人们提出它们。</p>
</small><p></p>
<div>

<hr>

<ol>
<li id="fn:C">分类依据来自GitHub推荐的项目列表，这些列表似乎是人工策划的。
 <a href="#fnref:C" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:B">要是我也有测试覆盖率数据就好了！但我没有尝试获取，因为这是一个30分钟的快速项目，而且跨语言的测试覆盖率比较并不简单。不过，我抽查了一些项目，那些表现不佳的项目符合泰勒·科文所称的“低于平均水平定律”的工程版本——经常构建失败/损坏的项目，其测试覆盖率也往往非常不稳定。
 <a href="#fnref:B" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:1"><p>我使用了<a href="https://github.com/travis-ci/travis.rb" rel="noopener noreferrer">官方Travis API脚本</a>，并对其进行了修改，使其返回构建开始时间而非构建结束时间。即便如此，构建开始时间与代码提交时间并不完全一致，这引入了一些噪声。仅使用了针对主分支（通常是master）的数据。有些数据不完整，因为他们的脚本要么从Travis API服务器收到500错误，要么遇到了运行时语法错误。所有错误无论是否应用我的修改都会发生，这与本文的主题非常契合。</p>

<p>如果你想复现结果，请将<a href="https://github.com/danluu/dump/blob/master/travis-api/diff-from-original.diff" rel="noopener noreferrer">这个补丁</a>应用到官方脚本，使用适当的选项（通常是 --branch master，但不总是）运行它，然后汇总结果。你可以使用<a href="https://github.com/danluu/dump/blob/master/travis-api/parse.jl" rel="noopener noreferrer">这个脚本</a>，但如果你没有<a href="//danluu.com/julialang/" rel="noopener noreferrer">Julia</a>，可能自己动手更容易。</p>
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:T">我认为我已经过滤掉了所有实际使用不同测试服务的项目。如果我的列表中还有，请告诉我。这移除了一个可靠性为0.1个9和两个约为0.5个9的项目。顺便说一句，移除这些项目的初始Travis失败记录后，其中一些项目的可靠性提升了0.5到1个9，并完全剔除了一个Travis测试失败超过一年的项目。图中显示的数据比原始数据好得多，但依然不理想。
 <a href="#fnref:T" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:E"><p>技术上容易。文化上困难。Michael Smith 提出了间歇性失败的问题。当你遇到这种情况——无论是项目本身的问题还是CI构建本身的问题——人们就会开始提交有问题的代码。有些环境人们不会这样做——在我工作近十年的公司里，人们会追踪几乎任何出现的测试失败，即使（或特别是）那些莫名其妙就消失的失败。你如何说服人们如此在意？这很难。</p>

<p>如何说服人们使用像 bors 这样的系统，这样你就不必为了不破坏构建而费心？这要容易得多，尽管仍然比构建 bors 所涉及的技术问题更难。</p>
 <a href="#fnref:E" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 4474 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/broken-builds/</link>
      <guid isPermaLink="false">https://danluu.com/broken-builds/</guid>
      <pubDate>Mon, 10 Nov 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>关于静态类型系统优势的文献综述</title>
      <description>[AI 摘要] 本文献综述表明，现有实证研究对静态类型系统优势的支持力度很弱，大多数研究显示效果微小或存在方法论缺陷。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文献综述表明，现有实证研究对静态类型系统优势的支持力度很弱，大多数研究显示效果微小或存在方法论缺陷。</div>关于类型系统的言论在编程社区中流传甚广。其论断范围甚广，从常被重复的“当类型对齐时，一切自然生效”，到“不依赖类型安全性是不道德的（如果你有服务级别协议）”<sup id="fnref:P"><a href="#fn:P" rel="noopener noreferrer">1</a></sup>、"<a href="https://news.ycombinator.com/item?id=4369359" rel="noopener noreferrer">这归根结底是成本与收益、实际研究以及数学公理的问题，而非美学或感觉</a>"，以及<a href="https://twitter.com/posco/status/566130704157667328" rel="noopener noreferrer">我认为那些怀疑类型系统作用的程序员基本上是技术界的反疫苗者</a>。这些言论中的第一和最后一条来自广泛被引用的“类型”思想领袖。如果我听到关于动态语言的强烈主张，我可能会持怀疑态度，但我所处的圈子里并未听到关于动态类型语言的极端言论。无论如何，人们很少引用实际证据。

让我们来看看支持这些主张的实证证据。

<p></p>

<p><a href="#wat_summary" rel="noopener noreferrer">点击此处</a>，如果你只想看摘要而不想阅读所有研究。摘要的摘要是，大多数研究发现，即使有效应，也非常小。然而，这些研究可能并未涵盖你实际感兴趣的背景。如果你想要详细细节，以下是每项研究及其摘要，以及对研究的简短描述。</p>

<h3 id="a-large-scale-study-of-programming-languages-and-code-quality-in-github-ray-b-posnett-d-filkov-v-devanbu-p-http-dl-acm-org-citation-cfm-id-2635922"><a href="http://dl.acm.org/citation.cfm?id=2635922" rel="noopener noreferrer">A Large Scale Study of Programming Languages and Code Quality in Github; Ray, B; Posnett, D; Filkov, V; Devanbu, P</a></h3>

<p><strong>摘要</strong></p>

<blockquote>
<p>编程语言对软件质量有什么影响？这个问题长期以来一直备受争议。在本研究中，我们从GitHub收集了一个非常庞大的数据集（729个项目，8000万行源代码，29,000名作者，150万次提交，使用17种语言），试图为这个问题提供一些实证证据。这个相当大的样本量允许我们采用混合方法，结合多元回归建模、可视化和文本分析，研究语言特性（如静态与动态类型、强类型与弱类型）对软件质量的影响。通过对不同方法发现的结果进行交叉验证，并控制团队规模、项目规模和项目历史等混淆效应，我们报告语言设计确实有显著但温和的影响。最值得注意的是，强类型似乎略优于弱类型，在函数式语言中，静态类型也略优于动态类型。我们还发现函数式语言略优于过程式语言。值得注意的是，这些源于语言设计的温和效应被过程因素（如项目规模、团队规模和提交规模）所主导。然而，我们须提醒读者，即使是这些温和效应，也可能完全归因于其他无形的过程因素，例如，某些性格类型的人偏爱函数式、静态和强类型语言。</p>
</blockquote>

<p><strong>摘要</strong></p>

<p>作者研究了每种最受欢迎的20种语言加上TypeScript（减去CSS、shell和vim）在GitHub上星标数最高的50个仓库。对于每个项目，他们研究了所使用的语言。研究正文中的文字并不支持摘要中提出的强烈主张。此外，该研究似乎使用了一种有根本缺陷的方法论，无法揭示太多信息。即使方法论是健全的，该研究也使用了虚假数据，并且存在平克所称的<a href="http://languagelog.ldc.upenn.edu/nll/?p=1897" rel="noopener noreferrer">igon值问题</a>。

正如Gary Bernhardt指出的，该研究的作者<a href="https://twitter.com/garybernhardt/status/862907601557651457" rel="noopener noreferrer">似乎混淆了内存安全性和隐式强制转换</a>，并且<a href="https://twitter.com/garybernhardt/status/862907391536279554" rel="noopener noreferrer">做出了其他奇怪的陈述</a>，例如</p>

<blockquote>
<p>动态类型的支持者可能会争辩说，与其花很多时间纠正编译器中由可靠、保守的静态类型检查算法产生的烦人的静态类型错误，不如依靠强动态类型在错误出现时就捕获它们。</p>
</blockquote>

<p>该研究使用以下语言分类方案</p>

<p><img src="https://danluu.com/images/empirical-pl/lang_classes.png" alt="Table of classifications"></p>

<p>这些分类似乎是武断的，许多人都会不同意其中一些分类。由于结果是基于这些类别的汇总，而作者选择了武断的分类，这已经使得汇总结果值得怀疑，因为他们在这里有一定数量的自由度，并且做出了奇怪的选择。

为了获得语言层面的结果，作者查看了提交/PR日志以确定每种使用的语言有多少错误。据我所知，没有关联修复的开放问题不计入错误计数。只有通过他们的关键词搜索技术检测到的提交才被计算在内。使用这种方法，发现的错误数量至少与错误报告文化的联系一样紧密，而与实际发现的错误数量联系不紧密。

确定错误数量后，作者运行了回归分析，控制了项目年龄、开发人员数量、提交数量和代码行数。

</p><p><img src="https://danluu.com/images/empirical-pl/lang_defects.png" alt="Defect rate correlations"></p>

<p>这里有足够奇怪的相关性，即使方法论没有已知的缺陷，我也会怀疑作者是否捕捉到了因果关系。如果你不觉得奇怪Perl和Ruby彼此一样可靠，并且明显比Erlang和Java（它们也同等可靠）更可靠，后者又明显比Python、PHP和C（它们具有相似的可靠性）更可靠，并且TypeScript是所调查中最安全的语言，那么也许这对你来说通过了嗅觉测试，但即使不继续阅读，这看起来也很可疑。

例如，Erlang和Go被评为有很多并发错误，而Perl和CoffeeScript被评为并发错误很少。是Perl和CoffeeScript在并发方面比Erlang和Go更好更合理，还是人们在需要并发时更倾向于使用Erlang和Go更合理？作者指出Go可能有很多并发错误，因为有一个很好的工具来检测Go中的并发错误，但他们没有探讨大多数奇怪中间结果的原因。

至于TypeScript，Eirenarch指出，他们列出的三个作为TypeScript项目示例的所谓“前三大”TypeScript项目是bitcoin、litecoin和qBittorrent）。<a href="http://bitcoin.stackexchange.com/questions/22311/why-does-github-say-that-the-bitcoin-project-is-74-typescript" rel="noopener noreferrer">这些是C++项目</a>。因此，中间结果似乎不是TypeScript可靠，而是被错误识别为TypeScript的项目是可靠的。这些项目之所以可靠，是因为Qt翻译文件被识别为TypeScript，而事实证明，每行代码从其他项目的大量配置文件转储不会引起很多错误。这就像说一个项目每行代码的错误很少，因为它有一个巨大的README。这是最明目张胆的分类错误，但远非唯一一个。

例如，在他们所谓的“前三大”Perl项目中，一个是showdown，一个JavaScript项目；另一个是rails-dev-box，一个用于启动Rails开发环境的shell脚本和Vagrantfile。即使不了解后一个项目，人们也可能从其名称rails-dev-box预期它不是一个Perl项目，它正确地指示了一个与Rails相关的项目。

由于本研究使用GitHub以不准确著称的代码分类系统来对仓库进行分类，它充其量只是一系列与实际语言使用本身只是松散相关的因素的相关性。

还有更多的分析，但很多是基于将上述表格根据语言类型聚合成类别。由于我对这些结果持怀疑态度，我对基于聚合这些结果的任何结果也至少同样怀疑。本节几乎没有触及本研究的表面。即使只是粗略浏览，我们也看到了多个严重缺陷，其中任何一个都会使结果无效，此外还有许多<a href="http://languagelog.ldc.upenn.edu/nll/?p=1897" rel="noopener noreferrer">igon值问题</a>。似乎作者甚至没有查看他们在论文中放入的表格，因为如果他们看了，就会立刻发现（仅举一例），他们将一个名为“rails-dev-box”的项目列为三大Perl项目之一（它是一个70行的shell脚本，用于启动ruby/rails开发环境）。</p><p><em>由 mimo-v2.5 模型翻译，花费 16954 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/empirical-pl/</link>
      <guid isPermaLink="false">https://danluu.com/empirical-pl/</guid>
      <pubDate>Fri, 7 Nov 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>CLWB和PCOMMIT指令</title>
      <description>[AI 摘要] 文章介绍了Intel为解决非易失性内存性能瓶颈而推出的新指令CLWB和PCOMMIT。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章介绍了Intel为解决非易失性内存性能瓶颈而推出的新指令CLWB和PCOMMIT。</div><p>最新版的Intel手册中有几个<a href="https://software.intel.com/sites/default/files/managed/0d/53/319433-022.pdf" rel="noopener noreferrer">针对非易失性存储（如SSD）的新指令</a>。这是怎么回事？</p>

<p>在详细了解这些指令之前，我们先看看超高速非易失性随机存取存储器（NVRAM）所面临的问题。一个问题在于，下一代存储技术（如相变存储器、3D XPoint等）的速度将快到系统调用和其他操作系统的开销可能比实际磁盘访问成本更高<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">1</a></sup>。另一个问题是x86内存层次结构与持久性内存之间的阻抗失配。这两种情况基本上都是一个<a href="https://en.wikipedia.org/wiki/Amdahl%27s_law" rel="noopener noreferrer">阿姆达尔定律</a>问题，即某个组件性能提升过多，导致其他组件必须同步提升以跟上节奏。</p>

<p></p>

<p><a href="http://research.microsoft.com/pubs/198366/Asplos2012_MonetaD.pdf" rel="noopener noreferrer">Todor Mollov、Louis Eisner、Arup De、Joel Coburn和Steven Swanson的一篇优秀论文</a>讨论了第一个问题；我将在下文展示他们论文中的一张图表。</p>

<p><img src="https://danluu.com/images/clwb-pcommit/Moneta_overhead.png" alt="NVRAM操作的OS及其他开销"></p>

<p>图中所有标注均为“Moneta”，因为这是他们的系统名称（顺便说一句，这系统很酷；推荐阅读该论文了解其运作方式）。他们的“基准”情况比你从普通系统上得到的结果要好得多。他们进行了多项优化（例如，绕过Linux的IO调度器并尽可能消除上下文切换），这使得延迟比普通Linux系统降低了62%。尽管如此，事务处理的硬件与DMA开销（条形图中白色部分）依然被其他开销所淹没。请注意，他们将DMA成本视为硬件开销的一部分。</p>

<p>他们能够完全绕过操作系统并减少大量开销，但写入操作的主要成本仍然是开销，这一点不变。</p>

<p><img src="https://danluu.com/images/clwb-pcommit/Moneta_speedup.png" alt="NVRAM操作绕过OS的性能提升"></p>

<p>尽管无法消除所有开销，但他们在小型微基准测试和实际代码中都获得了相当显著的性能提升。所以这是一个问题。当你的I/O设备非常快时，操作系统会施加相当大的I/O税。</p>

<p>也许可以通过将NVRAM设备映射到内存区域并在必要时向其提交内容来绕过大部分问题。但这会遇到另一个问题，即如果想要实现类似事务语义的功能，缓存与NVRAM区域交互的方式存在阻抗失配。</p>

<p><a href="http://www.hpl.hp.com/techreports/2012/HPL-2012-236.pdf" rel="noopener noreferrer">Kumud Bhandari、Dhruva R. Chakrabarti和Hans-J. Boehm的这份报告中有更详细的描述</a>。我也将借用他们的一些图表。</p>

<p><img src="https://danluu.com/images/clwb-pcommit/HP_hierarchy.png" alt="大致的内存层次结构示意图"></p>

<p>我们有一个安全且持久的NVRAM区域，但在CPU能够访问它之前，它必须经过多个具有不同排序保证的层次结构。他们给出了以下示例：</p>

<blockquote>
<p>例如，考虑一个常见的编程模式：分配一个持久内存位置N，对其进行初始化，并通过将分配的地址赋给一个全局持久指针p来发布它。如果在初始化之前，对全局指针的赋值就已在NVRAM中可见（很可能是因为后者被缓存而尚未写入NVRAM），并且程序恰好在此刻崩溃，那么重启后解引用持久指针将读取未初始化的数据。假设采用回写（WB）缓存模式，可以通过在新分配的持久位置N被赋值给全局持久指针p之前插入缓存行刷新（cache-line flushes）来避免这种情况。</p>
</blockquote>

<p>到处插入<code>CLFLUSH</code>指令是可行的，但这会带来多少开销？</p>

<p><img src="https://danluu.com/images/clwb-pcommit/HP_synthetic.png" alt="读写操作在持久性方面的开销"></p>

<p>他们考察的四种内存类型（也是x86支持的四种）是回写（WB）、直写（WT）、写合并（WC）和不可缓存（UC）。WB是你在正常情况下处理的模式。内存可以被缓存，并在强制时回写。WT允许内存被缓存，但写操作必须直接写入内存，即内存与缓存保持同步。UC则完全不能被缓存。WC类似于UC，只是写操作在发送到内存之前可以合并。</p>

<p>R、W和RW基准测试仅测试内存的读写性能。WB显然是最优秀的（值越低越好）。如果你想直观感受WB比其他策略好多少，可以尝试用非WB内存模式启动操作系统。</p>

<p>我曾不得不这样做，因为我曾在一家芯片公司工作，当我们第一次拿到芯片时，我们通常不知道必须禁用哪些位来规避缺陷。最简单的进展方法通常是完全禁用缓存。这“行得通”，但即使像DOS这样最简化的操作系统，在没有WB内存时启动也会明显变慢。我记得Win 3.1需要差不多一小时，而Win 95则是一个数小时的过程。</p>

<p>_b基准测试强制写操作对内存可见。对于WB情况，这涉及一个<code>MFENCE</code>指令后跟一个<code>CLFLUSH</code>指令。具有可见性约束的WB性能显著低于其他替代方案。与无需排序和刷新的WB相比，其性能下降了好几个数量级。</p>

<p>他们还在一些真实的数据结构上运行基准测试，约束条件是数据必须持久可见。</p>

<p><img src="https://danluu.com/images/clwb-pcommit/HP_data_structures.png" alt="数据结构操作在持久性方面的开销"></p>

<p>普通WB内存的性能可能非常慢：性能可能只有不使用缓存时的一半。而这仅仅是摆脱缓存层次结构的开销——即使你的持久存储速度无限快也是如此。</p>

<p>现在，让我们看看Intel是如何解决这个问题的。有两个新指令：<code>CLWB</code>和<code>PCOMMIT</code>。</p>

<p><code>CLWB</code>的作用类似于<code>CLFLUSH</code>，因为它强制将数据写入内存。但是，它不强制缓存丢弃数据，这使得后续的读写操作快得多。此外，<code>CLFLUSH</code>仅与<code>MFENCE</code>有序，而<code>CLWB</code>还与<code>SFENCE</code>有序。以下是他们对<code>CLWB</code>的描述：</p>

<blockquote>
<p>将包含内存操作数指定的线性地址的缓存行（如果已修改）写回到内存，该缓存行来自缓存一致性域中缓存层次结构的任何级别。该行可以保留在缓存层次结构中的非修改状态。将行保留在缓存层次结构中是一种性能优化（硬件将其视为提示），以减少后续访问时缓存未命中的可能性。硬件可以选择将其保留在缓存层次结构的任何级别，在某些情况下，也可能使该行无效。源操作数是一个字节内存位置。

应该注意的是，处理器可以自由地从分配了允许推测读取的内存类型（例如WB、WC和WT内存类型）的系统内存区域推测性地获取和缓存数据。由于这种推测性获取可能在任何时候发生，且不与指令执行绑定，因此<code>CLWB</code>指令与<code>PREFETCHh</code>指令或任何推测性获取机制无序（即，数据可能在引用该缓存行的<code>CLWB</code>指令执行之前、期间或之后被推测性加载到缓存行中）。

<code>CLWB</code>指令仅通过存储屏障操作排序。例如，软件可以使用<code>SFENCE</code>、<code>MFENCE</code>、<code>XCHG</code>或带LOCK前缀的指令来确保之前的存储包含在回写中。<code>CLWB</code>指令无需由另一个<code>CLWB</code>或<code>CLFLUSHOPT</code>指令排序。<code>CLWB</code>与同一逻辑处理器对同一地址执行的更早存储隐式有序。

<code>CLWB</code>的执行与<code>PCOMMIT</code>的执行相互作用。<code>PCOMMIT</code>指令作用于已被内存接受的某些存储到内存的操作。对与旧存储相同的缓存行执行<code>CLWB</code>，会在该<code>CLWB</code>执行全局可见时导致该存储被内存接受。</p>
</blockquote>

<p><code>PCOMMIT</code>应用于整个内存范围，并确保该内存范围内的所有内容都已提交到持久存储。以下是他们对<code>PCOMMIT</code>的描述：</p>

<blockquote>
<p><code>PCOMMIT</code>指令导致某些存储到持久性内存范围的存储操作变为持久性（断电保护）。<sup>1</sup> 具体来说，<code>PCOMMIT</code>适用于那些已被内存接受的存储。

虽然所有存储到内存的操作最终都会被内存接受，但以下内容指定了软件可以采取的确保它们被接受的操作：

非时序存储到回写（WB）内存以及所有存储到不可缓存（UC）、写合并（WC）和直写（WT）内存的操作，在全局可见时即被内存接受。
如果在普通存储到回写（WB）内存变为全局可见之后，对该存储所在缓存行执行了<code>CLFLUSH</code>、<code>CLFLUSHOPT</code>或<code>CLWB</code>，则该存储在<code>CLFLUSH</code>、<code>CLFLUSHOPT</code>或<code>CLWB</code>执行本身全局可见时被内存接受。

如果在存储到持久性内存范围的操作已被内存接受之后执行了<code>PCOMMIT</code>，则该存储在<code>PCOMMIT</code>变为全局可见时变为持久性。这意味着，如果在执行更晚的持久性内存存储时，一个<code>PCOMMIT</code>执行是全局可见的，那么该存储无法在<code>PCOMMIT</code>所应用的存储之前变为持久性。

以下内容详细说明了<code>PCOMMIT</code>与其他操作之间的排序关系：

逻辑处理器不会确保由该逻辑处理器执行的先前存储和<code>CLFLUSHOPT</code>、<code>CLWB</code>在开始执行<code>PCOMMIT</code>之前全局可见。这意味着软件必须使用适当的屏障指令（例如<code>SFENCE</code>）来确保先前的存储到内存操作以及<code>CLFLUSHOPT</code>和<code>CLWB</code>对持久性内存范围的执行在全局可见（以便它们被内存接受），然后才能执行<code>PCOMMIT</code>。

逻辑处理器不会确保<code>PCOMMIT</code>的执行在开始后续存储之前全局可见。需要此类存储在<code>PCOMMIT</code>之前不全局可见（例如，因为较新的存储不能在<code>PCOMMIT</code>提交的存储之前变为持久性）的软件，可以通过在<code>PCOMMIT</code>和后续存储之间使用适当的屏障指令（例如<code>SFENCE</code>）来确保。

<code>PCOMMIT</code>的执行与<code>SFENCE</code>、<code>MFENCE</code>、<code>XCHG</code>或带LOCK前缀的指令以及串行化指令（例如<code>CPUID</code>）的执行是有序的。

<code>PCOMMIT</code>的执行与加载操作无序。软件可以使用<code>MFENCE</code>来将加载操作与<code>PCOMMIT</code>排序。

<code>PCOMMIT</code>的执行不会序列化指令流。</p>
</blockquote>

<p><code>CLWB</code>和<code>PCOMMIT</code>实际能提升多少性能，将取决于它们的具体实现。对这些指令进行基准测试并观察其表现将很有趣。无论如何，这是为解决WB/NVRAM阻抗失配问题所做的尝试。它并未直接解决操作系统开销问题，但这个问题在很大程度上可以通过无需额外硬件的方式规避。</p>

<p><strong>如果你喜欢这篇文章，可能也会喜欢<a href="//danluu.com/intel-cat/" rel="noopener noreferrer">关于Broadwell及更新Intel服务器CPU中缓存分区的文章</a></strong>。</p>

<p><small>感谢Eric Bron在手册中发现并指出这一点，感谢Leah Hanson、Nate Rowe和'unwind'指出排版错误。</small></p>

<p><small>如果你还觉得不够过瘾，Zvonimir Bandic指出了<a href="https://www.usenix.org/conference/fast14/technical-sessions/presentation/vucinic" rel="noopener noreferrer">Dejan Vučinić、Qingbo Wang、Cyril Guyot、Robert Mateescu、Filip Blagojević、Luiz Franca-Neto、Damien Le Moal、Trevor Bunker、Jian Xu和Steven Swanson关于从某类NVRAM获得1.4微秒延迟和700k IOPS的一篇论文</a>

如果你喜欢这篇文章，可能也会喜欢<a href="//danluu.com/new-cpu-features/" rel="noopener noreferrer">这篇关于“新”CPU功能的关联文章</a>。
</small></p>
<div>

<hr>

<ol>
<li id="fn:1">对于熟悉InfiniBand网络的高性能计算（HPC）和高频交易（HFT）人士来说，这应该听起来很熟悉。
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 6780 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/clwb-pcommit/</link>
      <guid isPermaLink="false">https://danluu.com/clwb-pcommit/</guid>
      <pubDate>Wed, 5 Nov 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>缓存：LRU与随机策略</title>
      <description>[AI 摘要] 本文对比分析了缓存替换策略中LRU与随机策略的性能，发现2随机选择策略在大容量缓存中表现接近甚至优于LRU。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文对比分析了缓存替换策略中LRU与随机策略的性能，发现2随机选择策略在大容量缓存中表现接近甚至优于LRU。</div><p>很久以前，我的<a href="http://pages.cs.wisc.edu/~sohi/" rel="noopener noreferrer">计算机体系结构教授</a>曾提到，在缓存中使用随机替换策略其实并不差。随机替换不差可能令人惊讶——当缓存已满需要剔除某物时，选择<a href="http://www.mathcs.emory.edu/~cheung/Courses/355/Syllabus/9-virtual-mem/LRU-replace.html" rel="noopener noreferrer">最近最少使用（LRU）</a>似乎是显而易见的选择，因为最近使用过的数据很可能再次被使用。如果有紧凑的循环，只要循环能放入缓存，LRU将是完美的选择；但如果循环过大无法完全放入，LRU每次都会导致未命中。而随机替换策略在循环过大时性能会优雅地降级。</p>

<p>在实际工作负载中，随机策略通常比其他算法表现更差。但如果我们采用两个随机选择（2-random）并在两者之间使用LRU策略会怎样？</p>

<p>以下是在类似Sandy Bridge的缓存（<a href="//danluu.com/3c-conflict/" rel="noopener noreferrer">8路组相联</a>，L1、L2、L3缓存分别为64KB、256KB和2MB）上使用SPEC CPU<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">1</a></sup>得到的相对未命中率。这些数值是比率（算法未命中率：随机未命中率）；数值越低表示性能越好。每个缓存在所有层级使用相同策略。</p>

<p></p>

<table>
<thead>
<tr>
<th>策略</th>
<th>L1 (64KB)</th>
<th>L2 (256KB)</th>
<th>L3 (2MB)</th>
</tr>
</thead>

<tbody>
<tr>
<td>2-random</td>
<td>0.91</td>
<td>0.93</td>
<td>0.95</td>
</tr>

<tr>
<td>FIFO</td>
<td>0.96</td>
<td>0.97</td>
<td>1.02</td>
</tr>

<tr>
<td>LRU</td>
<td>0.90</td>
<td>0.90</td>
<td>0.97</td>
</tr>

<tr>
<td>random</td>
<td>1.00</td>
<td>1.00</td>
<td>1.00</td>
</tr>
</tbody>
</table>

<p>随机和FIFO策略都明显劣于LRU或2-random。LRU和2-random表现相近，LRU在较小缓存中略占优势，而2-random在较大缓存中稍胜一筹。</p>

<p>为观察是否存在特殊情况，我们可以查看各个子基准测试的原始结果。下图展示了每个基准测试中L1、L2和L3的未命中率（均绘于同一列）：</p>

<p><img src="https://danluu.com/images/2choices-eviction/sandy-bridge-miss.png" alt="类似Sandy Bridge缓存的未命中率"></p>

<p>正如预期，当未命中率较高时LRU表现不如2-random，当未命中率较低时LRU表现更好。</p>

<p>目前尚不清楚2-random在L3缓存未命中率上超越LRU，是因为它在缓存较大时表现更好，还是因为它作为层级缓存的第三层而具优势。由于L1或L2中正在活跃使用的缓存行不会在L3中被触及，L3可能会驱逐该行（这会导致L1和L2同时被驱逐），因为对该行而言，它并非最近被使用过。这使得LRU作为L3缓存替换策略的优势不再明显。</p>

<p>为分离这些影响，我们来比较不同容量下非层级（单级）与层级缓存的相对未命中率<sup id="fnref:2"><a href="#fn:2" rel="noopener noreferrer">2</a></sup>。对于层级缓存，L1和L2容量如前所述（64KB和256KB），仅L3容量变化。下图展示了各策略（相对于随机替换）在所有SPEC子基准测试上的比率几何均值<sup id="fnref:G"><a href="#fn:G" rel="noopener noreferrer">3</a></sup>。该指标可能存在的缺点是：若存在极低未命中率，微小波动会对比率产生较大影响，从而可能主导均值，但我们可以通过观察结果分布来验证这一点。</p>

<p><img src="https://danluu.com/images/2choices-eviction/sweep-sizes-mean-ratios.png" alt="缓存容量在64K至16M间的未命中率比值"></p>

<p><img src="https://danluu.com/images/2choices-eviction/sweep-sizes-mean-ratios-l3.png" alt="L3缓存容量在512K至16M间的未命中率比值"></p>

<p>层级缓存情况下缺少小于512K的容量，因为存在256K的L2——此处采用包含式L3缓存，故L3容量小于L2并无实际意义。省略大于16M的容量是因为缓存过大时未命中率趋于收敛，缺乏研究价值。</p>

<p>观察单级缓存情况，LRU在较小缓存时略优于2-random（未命中率比值越低越好），随缓存容量增大2-random逐渐超越LRU。层级缓存情况类似，只是我们未深入考察LRU占优的小容量区间。</p>

<p>对比两种情况，结果虽有差异但足够相似，表明原始结果并非仅因观察层级缓存的最后一层而产生。</p>

<p>下图展示完整分布，以检验比值均值是否被微小结果所扭曲。</p>

<p><img src="https://danluu.com/images/2choices-eviction/sweep-sizes-miss.png" alt="缓存容量在64K至16M间的未命中率比值分布"></p>

<p><img src="https://danluu.com/images/2choices-eviction/sweep-sizes-miss-l3.png" alt="L3缓存容量在512K至16M间的未命中率比值分布"></p>

<p>对于特定缓存容量（图中单列），随机算法在未命中率相对较高时表现更好，在未命中率较低时表现较差。因此若仅考虑几何均值，它们反而处于劣势——若采用算术均值，结果将由较大未命中率主导（此时2-random和随机策略表现相对较好）<sup id="fnref:C"><a href="#fn:C" rel="noopener noreferrer">4</a></sup>。</p>

<p>根据均值比观察，2-random在大容量缓存中表现良好；根据结果分布观察，尽管受均值比指标的约束，它在大容量缓存中仍表现优异。</p>

<p>然而，实际常实现伪LRU策略，因为纯LRU成本过高。由于2-random需要存储与LRU相同的信息量，我们来考察伪2-random（约80%准确率）和伪3-random（两级锦标赛，每级约80%准确率）的表现。</p>

<p>由于随机和FIFO明显不是优秀的替换策略，后续图表中将不包含它们。同时因单级与多级缓存情况结果相似，我们仅分析更贴近实际的多级缓存情况。</p>

<p><img src="https://danluu.com/images/2choices-eviction/sweep-sizes-mean-ratios-l3-pseudo.png" alt="L3缓存容量在512K至16M间的伪策略未命中率比值"></p>

<p>由于伪2-random有20%时间表现如随机策略，80%时间表现如2-random，我们预期其性能介于二者之间，事实正是如此。尝试改进伪2-random的简单调整是采用伪3-random（从3个随机选择中驱逐最近最少使用的）。虽然仍不及真实2-random，但已相当接近，且在大于1M的缓存中仍优于LRU（及伪LRU）。</p>

<p>尚未探讨的关键变量是组相联度。为比较不同缓存容量下LRU与2-random的表现，下图展示LRU:2-random未命中率比值（红色表示LRU更优，绿色表示2-random更优）。</p>

<p><img src="https://danluu.com/images/2choices-eviction/sweep-sizes-assocs-mean-ratios.png" alt="不同容量与相联度下的缓存未命中率比值（容量64K至16M，相联度2至64）"></p>

<p>平均而言，提高相联度会加大两种策略的差异。与之前一致，LRU在小缓存更优，2-random在大缓存更优。未显示相联度1和2的情况，因两种策略在此情况下应完全相同。</p>

<p>仍存在大量未尝试的组合可能性。例如在不同缓存层级采用不同替换策略（L1和L2采用LRU而L3采用2-random似乎很有潜力）。也可尝试不同类型缓存的应用。我选择CPU缓存仅因其模拟器和基准测试轨迹易于获取，但在当今“万物皆可缓存”的时代，2-random可应用于众多其他领域<sup id="fnref:H"><a href="#fn:H" rel="noopener noreferrer">5</a></sup>。</p>

<p>对计算机体系结构研究者而言，根据这些数据，我推测2-random可能无法匹敌DIP等自适应策略（虽然存在可能——两者处于相近范围，但DIP基于不同工作负载和模拟器测试，无法完全确定）。不过，伪2-random策略的实现成本仅略高于伪LRU，远低于DIP。同时，伪3-random显著优于伪2-random，表明k-random可能随k值增加而持续改进。某些k-random策略甚至可能优于DIP。</p>

<p>我们已验证其有效性，但最初为何会考虑采用此策略？<a href="http://www.eecs.harvard.edu/~michaelm/postscripts/handbook2001.pdf" rel="noopener noreferrer">Mitzenmacher、Richa和Sitaraman的《双随机选择的力量：技术与成果综述》</a>给出了绝佳解释。数学直觉在于：若将n个球随机投入n个箱子，高概率下最大箱子球数为<code>O(log n / log log n)</code>，近似<code>O(log n)</code>；但若从k个随机箱子中选择负载最小的，高概率下最大负载仅为<code>O(log log n / log k)</code>。即使用两个随机选择，最大负载基本为<code>O(log log n)</code>，且每增加一个选择仅以常数因子降低负载。</p>

<p>此原理具有广泛适用性；<a href="http://brooker.co.za/blog/2012/01/17/two-random.html" rel="noopener noreferrer">负载均衡</a>和哈希分布天然契合“球与箱”模型。许多看似无关的应用（如<a href="http://citeseerx.ist.psu.edu/showciting?cid=1042246" rel="noopener noreferrer">电路布线</a>和<a href="http://www.math.cmu.edu/math/mathcolloquium.php?SeminarSelect=522" rel="noopener noreferrer">Erdős–Rényi</a>图）亦可借鉴此模型。</p>

<p><small>感谢Jan Elder和Mark Hill免费提供dinero IV，感谢Aleksandar Milenkovic提供SPEC CPU轨迹，感谢Carl Vogel、James Porter、Peter Fraenkel、Katerina Barone-Adesi、Jesse Luehrs、Lea Albaugh和Kevin Lynagh对图表绘制的建议，感谢Mindy Preston发现致谢中的拼写错误，感谢Lindsey Kuper指出术语问题，感谢Tom Wenisch建议探索CMP$im以开展未来工作，感谢Leah Hanson对全文的详尽评论。</small></p>
<div>

<hr>

<ol>
<li id="fn:1"><p>模拟使用<a href="http://pages.cs.wisc.edu/~markhill/DineroIV/" rel="noopener noreferrer">dinero IV</a>及<a href="http://www.ece.uah.edu/~lacasa/sbc/sbc.html" rel="noopener noreferrer">SBC轨迹</a>。选用这些资源是因为近几十年教授和研究生对模拟器代码的保护日趋严格，在GitHub上难以找到现代开源模拟器。所幸dinero IV支持带预取的层级缓存，能提供合理的一阶近似。</p>

<p>注意：175.vpr和187.facerec未包含在轨迹中，故本研究所有结果中均缺失这两项测试。</p>
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:2">容量受限于dinero IV的要求：缓存容量必须为2的幂。
 <a href="#fnref:2" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:G"><p>为何考虑比值的几何均值？不同基准测试的“基准”未命中率各异。例如181.mcf的未命中率远高于252.eon。若仅比较策略优劣，这些差异只是噪声。观察比值可消除噪声。</p>

<p>若仅比较两个策略，我们希望“在两者上均提升2倍”等价于“在一个上提升4倍而另一个仅1倍”，或“在一个上提升8倍而另一个仅1/2倍”。由于几何均值是结果乘积的n次方根，它具备此特性。</p>
 <a href="#fnref:G" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:C">通过观察图表中较高未命中率区域的绿色三角、红色方块、空心菱形和蓝色圆圈聚集簇，可见绿色三角通常高于红色方块，表明2-choices在未命中率较高时优于LRU。低未命中率区域过于拥挤难以辨析。为严谨起见，我查阅了局部放大图。
 <a href="#fnref:C" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:H">若您知道可用于其他领域的缓存模拟器，请<a href="https://twitter.com/danluu" rel="noopener noreferrer">告知我</a>！
 <a href="#fnref:H" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 7376 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/2choices-eviction/</link>
      <guid isPermaLink="false">https://danluu.com/2choices-eviction/</guid>
      <pubDate>Mon, 3 Nov 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>测试与非形式化推理之比较</title>
      <description>[AI 摘要] 文章批判了“非形式化推理比测试更重要”的观点，强调测试对复杂系统的必要性。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章批判了“非形式化推理比测试更重要”的观点，强调测试对复杂系统的必要性。</div><p>这是为黑客学院“每周论文精读”系列针对<a href="https://github.com/papers-we-love/papers-we-love/raw/master/design/out-of-the-tar-pit.pdf" rel="noopener noreferrer">《脱离焦油坑》</a>一文发表的即兴评论。</p>

<p>我认为论文第七至十节提出的核心观点相当有趣。然而，对于构成论文前60%内容的论点动机，我持保留意见。</p>

<p>我将避免博客常见的逐点反驳方式，仅针对一个我认为不仅错误且极具危害性的流行观点发表评论。</p>

<p>有人基于迪杰斯特拉的这段名言声称“非形式化推理”比“测试”更为重要<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">1</a></sup>：</p>

<blockquote>
<p>测试是完全不充分的……（它）能有效地证明程序存在缺陷，却永远无法证明缺陷不存在。</p>
</blockquote>

<p>他们进而提出诸多相关论断，例如“核心问题在于：对处于特定状态的系统或组件进行的任何测试，都无法反映该系统或组件处于其他状态时的行为”，最终得出结论：唯有无状态的简洁性才是可行的解决方案。当然，他们假定了简洁性确实可以实现。</p>

<p>我其实同意关于测试局限性的观点——若构建的系统复杂到无法进行形式化验证，确实难以避免缺陷。</p>

<p>然而，现实中存在大量系统因其固有复杂性而无法简化。以我的经验为例，没有人能完全理解现代高性能CPU的正确性并进行非形式化推理。这不仅是现状，数十年来一直如此。只要引入推测执行或缓存机制，系统就会变得如此复杂。这些技术本质上具有状态依赖性且异常复杂，其性能建模必须通过精确模拟运行时状态才能实现——因为具体结果对人类而言过于复杂难以推理，且数学上难以处理。可以制造简单的CPU，但无法兼顾高性能与简洁性。这不仅适用于CPU，性能复杂性会向整个技术栈层层渗透。</p>

<p>复杂性不仅存在于高性能软硬件领域。某些领域本身就极度复杂：美国税法长达7.3万页，面对如此复杂的系统，人们根本无法进行有效推理，而此类复杂系统并不罕见。</p>

<p>此外，人类本身就会犯错。欧几里得《几何原本》首个定理就存在谬误。安德鲁·格尔曼常引用这个“明显”错误却通过同行评审的概率研究案例。某款著名英特尔CPU的缺陷据称源于工程师因“确信正确”而省略测试。无论多么睿智博学，人类始终无法保证推理完全正确。</p>

<p>那么解决方案是什么？就是编写测试！对于任何复杂度超过某个阈值的系统，测试都是必要的。论文作者认为测试不充分，理由是状态空间过于庞大，针对某一状态的测试无法反映其他任何状态的情况。</p>

<p>若将系统视为不可知的黑盒，此观点成立。但实践表明这并不正确。存在大量<a href="http://researcher.watson.ibm.com/researcher/view_group.php?id=2987" rel="noopener noreferrer">单元测试工具</a>，能通过相似输入与状态关联性进行状态空间缩减，实现符号执行等。这些方法效果显著。</p>

<p>即使不使用形式化方法，传统测试也能证明这点。约翰·雷格尔指出，当<a href="http://embed.cs.utah.edu/csmith/" rel="noopener noreferrer">Csmith</a>发现缺陷时，测试用例缩减通常会连带暴露一系列其他问题。可见测试确实能反映临近状态的信息。</p>

<p>这不仅是理论论证。我在主要依靠测试的公司从事CPU设计/验证/测试工作七年半。期间仅发现两个客户报告的缺陷：其一是无软件类比的制造缺陷，其二是确实在缓存层次结构中存在的逻辑缺陷。尽管交付含缺陷芯片令人尴尬，但多数公司会认为七年半出现一个逻辑缺陷的可靠性已相当可观。</p>

<p>测试或许不足以发现所有缺陷，但完全能实现超越绝大多数软件公司需求的可靠性。</p>

<p><small>感谢（或或许该说“怨念”）大卫·阿尔伯特激励我撰写此文，也感谢戈维特·弗斯路伊斯修正笔误。</small></p>
<div>

<hr>

<ol>
<li id="fn:1">这类论断总有些古怪。就像天性与教养之争，将任一因素设为零都会导致不良结果，且二者相互作用使非零值的相对影响难以量化。
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 2784 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/tests-v-reason/</link>
      <guid isPermaLink="false">https://danluu.com/tests-v-reason/</guid>
      <pubDate>Mon, 3 Nov 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>汇编语言 vs. 内建函数</title>
      <description>[AI 摘要] 本文论证了在性能关键代码中使用内建函数往往不可靠且耗时，通过popcnt指令的基准测试对比，手写汇编通常能获得更优性能。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文论证了在性能关键代码中使用内建函数往往不可靠且耗时，通过popcnt指令的基准测试对比，手写汇编通常能获得更优性能。</div><p>时不时地，我会听到这样的说法：内建函数已经改进到足以安全地用于高性能代码了。这倒是不错。内建函数的承诺在于，你可以通过调用与特定汇编指令相对应的函数（内建函数）来编写优化代码。由于内建函数表现得像普通函数，它们可以跨平台使用。而且由于你的编译器拥有比你大脑更强大的计算能力，以及对每种CPU的详细模型，编译器应该能更好地进行微优化。尽管十年前就有人声称内建函数能让你的生活更轻松，但结果似乎从未如愿。</p>

<p>我上一次尝试内建函数是在2007年左右；更多关于当时它们为何毫无希望的信息，可以参见<a href="http://virtualdub.org/blog/pivot/entry.php?id=162" rel="noopener noreferrer">VirtualDub作者的这篇探讨</a>。我最近又试了一次，虽然它们有所改进，但仍然不值得投入精力。问题在于内建函数如此不可靠，以至于你必须在代码预期运行的每个平台、每个编译器上手动检查结果，然后不断调整内建函数，直到获得合理的结果。这比直接手写汇编更费劲。如果你不手动检查结果，很容易得到错误的结果。</p>

<p></p>

<p>例如，截至本文撰写时，前两个关于<code>popcnt 基准测试</code>的谷歌搜索结果（以及必应前三个中的两个）声称，英特尔的硬件<code>popcnt</code>指令比通过<a href="https://en.wikipedia.org/wiki/SSSE3" rel="noopener noreferrer">SSSE3</a> <code>pshufb</code>指令使用查找表来计算缓冲区中置位数的软件实现更慢。这其实是不对的，但这一点似乎并不明显，否则这个说法不会如此持久。让我们来看看，如果有人使用内建函数来编码实现，为什么他们会得出<code>popcnt</code>指令很慢的结论。</p>

<p>一个热门搜索结果包含了原生<code>popcnt</code>和使用<code>pshufb</code>的软件版本的示例代码及基准测试。<a href="http://www.strchr.com/media/crc32_popcnt.zip" rel="noopener noreferrer">他们的代码</a>需要MSVC，而我没有访问权限，但他们的第一个<code>popcnt</code>实现只是在一个循环中调用<code>popcnt</code>内建函数，这很容易复现为gcc和clang接受的形式。计时也很简单，因为我们只是对一个函数（该函数计算某个固定大小缓冲区中的置位数）进行计时。</p>

<pre><code>uint32_t builtin_popcnt(const uint64_t* buf, int len) {
  int cnt = 0;
  for (int i = 0; i &lt; len; ++i) {
    cnt += __builtin_popcountll(buf[i]);
  }
  return cnt;
}
</code></pre>

<p>这与我上面链接的代码略有不同，因为他们使用<code>popcnt</code>的双字（32位）版本，而我们使用的是四字（64位）版本。由于我们的版本在每次循环迭代中处理的工作量是其两倍，我预计我们的版本会比他们的版本更快。</p>

<p>运行<code>clang -O3 -mpopcnt -funroll-loops</code>会产生一个我们可以检查的二进制文件。在Mac上，我们可以使用<code>otool -tv</code>获取反汇编代码。在Linux上，可以使用<code>objdump -d</code>。</p>

<pre><code>_builtin_popcnt:
; address                        instruction
0000000100000b30        pushq   %rbp
0000000100000b31        movq    %rsp, %rbp
0000000100000b34        movq    %rdi, -0x8(%rbp)
0000000100000b38        movl    %esi, -0xc(%rbp)
0000000100000b3b        movl    $0x0, -0x10(%rbp)
0000000100000b42        movl    $0x0, -0x14(%rbp)
0000000100000b49        movl    -0x14(%rbp), %eax
0000000100000b4c        cmpl    -0xc(%rbp), %eax
0000000100000b4f        jge     0x100000bd4
0000000100000b55        movslq  -0x14(%rbp), %rax
0000000100000b59        movq    -0x8(%rbp), %rcx
0000000100000b5d        movq    (%rcx,%rax,8), %rax
0000000100000b61        movq    %rax, %rcx
0000000100000b64        shrq    %rcx
0000000100000b67        movabsq $0x5555555555555555, %rdx
0000000100000b71        andq    %rdx, %rcx
0000000100000b74        subq    %rcx, %rax
0000000100000b77        movabsq $0x3333333333333333, %rcx
0000000100000b81        movq    %rax, %rdx
0000000100000b84        andq    %rcx, %rdx
0000000100000b87        shrq    $0x2, %rax
0000000100000b8b        andq    %rcx, %rax
0000000100000b8e        addq    %rax, %rdx
0000000100000b91        movq    %rdx, %rax
0000000100000b94        shrq    $0x4, %rax
0000000100000b98        addq    %rax, %rdx
0000000100000b9b        movabsq $0xf0f0f0f0f0f0f0f, %rax
0000000100000ba5        andq    %rax, %rdx
0000000100000ba8        movabsq $0x101010101010101, %rax
0000000100000bb2        imulq   %rax, %rdx
0000000100000bb6        shrq    $0x38, %rdx
0000000100000bba        movl    %edx, %esi
0000000100000bbc        movl    -0x10(%rbp), %edi
0000000100000bbf        addl    %esi, %edi
0000000100000bc1        movl    %edi, -0x10(%rbp)
0000000100000bc4        movl    -0x14(%rbp), %eax
0000000100000bc7        addl    $0x1, %eax
0000000100000bcc        movl    %eax, -0x14(%rbp)
0000000100000bcf        jmpq    0x100000b49
0000000100000bd4        movl    -0x10(%rbp), %eax
0000000100000bd7        popq    %rbp
0000000100000bd8        ret
</code></pre>

<p>嗯，这很有趣。Clang似乎是在手动计算，而不是使用<code>popcnt</code>。它似乎在使用<a href="http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel" rel="noopener noreferrer">此处描述的方法</a>，类似于</p>

<pre><code>x = x - ((x &gt;&gt; 0x1) &amp; 0x5555555555555555);
x = (x &amp; 0x3333333333333333) + ((x &gt;&gt; 0x2) &amp; 0x3333333333333333);
x = (x + (x &gt;&gt; 0x4)) &amp; 0xF0F0F0F0F0F0F0F;
ans = (x * 0x101010101010101) &gt;&gt; 0x38;
</code></pre>

<p>对于不依赖任何特定硬件的简单实现来说，这还不错，但这将比单条<code>popcnt</code>指令花费更长时间。</p>

<p>我有一个非常旧的clang版本（3.0），所以让我在升级到3.4后再次尝试这个，以防他们“最近”添加了硬件<code>popcnt</code>支持。</p>

<pre><code>0000000100001340        pushq   %rbp         ; save frame pointer
0000000100001341        movq    %rsp, %rbp   ; new frame pointer
0000000100001344        xorl    %ecx, %ecx   ; cnt = 0
0000000100001346        testl   %esi, %esi
0000000100001348        jle     0x100001363
000000010000134a        nopw    (%rax,%rax)
0000000100001350        popcntq (%rdi), %rax ; “eax” = popcnt[rdi]
0000000100001355        addl    %ecx, %eax   ; eax += cnt
0000000100001357        addq    $0x8, %rdi   ; increment address by 64-bits (8 bytes)
000000010000135b        decl    %esi         ; decrement loop counter; sets flags
000000010000135d        movl    %eax, %ecx   ;  cnt = eax; does not set flags
000000010000135f        jne     0x100001350  ; examine flags. if esi != 0, goto popcnt
0000000100001361        jmp     0x100001365  ; goto “restore frame pointer”
0000000100001363        movl    %ecx, %eax
0000000100001365        popq    %rbp         ; restore frame pointer
0000000100001366        ret

</code></pre>

<p>这样好多了！我们得到了硬件<code>popcnt</code>！让我们将其与<a href="http://www.strchr.com/crc32_popcnt" rel="noopener noreferrer">此处介绍</a>的SSSE3 <code>pshufb</code>实现进行比较，该实现被呈现为执行popcnt的最快方式。我们将使用类似链接中的表格来显示速度，但我们将显示速率，而不是原始周期数，以便清楚不同大小之间的相对速度。速率单位是GB/s，即每秒可以处理多少GB的缓冲区。我们以块的形式向函数提供数据（从1kb到16Mb变化）；每一列是不同块大小的速率。如果我们查看每种算法在不同缓冲区大小下的速度，我们得到以下结果。</p>

<table>
<thead>
<tr>
<th>算法</th>
<th>1k</th>
<th>4k</th>
<th>16k</th>
<th>65k</th>
<th>256k</th>
<th>1M</th>
<th>4M</th>
<th>16M</th>
</tr>
</thead>

<tbody>
<tr>
<td>内建函数</td>
<td>6.9</td>
<td>7.3</td>
<td>7.4</td>
<td>7.5</td>
<td>7.5</td>
<td>7.5</td>
<td>7.5</td>
<td>7.5</td>
</tr>

<tr>
<td>PSHUFB</td>
<td>11.5</td>
<td>13.0</td>
<td>13.3</td>
<td>13.4</td>
<td>13.1</td>
<td>13.4</td>
<td>13.0</td>
<td>12.6</td>
</tr>
</tbody>
</table>

<p>效果不太好。与上面链接的基准测试相比，我们做得更好，因为我们使用的是64位<code>popcnt</code>而不是32位<code>popcnt</code>，但PSHUFB版本仍然快了近一倍<sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">1</a></sup>。</p>

<p>一个奇怪之处是<code>cnt</code>的累加方式。<code>cnt</code>存储在<code>ecx</code>中。但是，clang不是将<code>popcnt</code>的结果加到<code>ecx</code>上，而是决定将<code>ecx</code>加到<code>popcnt</code>的结果上。为了解决这个问题，clang不得不在每次循环迭代结束时将总和移回<code>ecx</code>。</p>

<p>另一个明显的问题是，我们在每次循环迭代中只执行一次<code>popcnt</code>，这意味着循环没有被展开，我们为每次<code>popcnt</code>支付了全部的循环开销。展开循环还可以让CPU从代码中提取更多的指令级并行性，尽管这有点超出了本博客文章的范围。</p>

<p>使用clang时，即使有<code>-O3 -funroll-loops</code>选项，这种情况也会发生。使用gcc，我们得到正确展开的循环，但gcc有其他问题，我们稍后会看到。现在，让我们尝试通过在每次循环迭代中多次调用<code>__builtin_popcountll</code>来手动展开循环。为简单起见，让我们尝试每次迭代执行四次<code>popcnt</code>操作。我不声称这是最优的，但应该会有所改善。</p>

<pre><code>uint32_t builtin_popcnt_unrolled(const uint64_t* buf, int len) {
  assert(len % 4 == 0);
  int cnt = 0;
  for (int i = 0; i &lt; len; i+=4) {
    cnt += __builtin_popcountll(buf[i]);
    cnt += __builtin_popcountll(buf[i+1]);
    cnt += __builtin_popcountll(buf[i+2]);
    cnt += __builtin_popcountll(buf[i+3]);
  }
  return cnt;
}
</code></pre>

<p>我们循环的核心现在有</p>

<pre><code>0000000100001390        popcntq (%rdi,%rcx,8), %rdx
0000000100001396        addl    %eax, %edx
0000000100001398        popcntq 0x8(%rdi,%rcx,8), %rax
000000010000139f        addl    %edx, %eax
00000001000013a1        popcntq 0x10(%rdi,%rcx,8), %rdx
00000001000013a8        addl    %eax, %edx
00000001000013aa        popcntq 0x18(%rdi,%rcx,8), %rax
00000001000013b1        addl    %edx, %eax
</code></pre>

<p>循环体周围的代码基本相同。我们每次循环执行四次<code>popcnt</code>操作，结果性能如下：</p>

<table>
<thead>
<tr>
<th>算法</th>
<th>1k</th>
<th>4k</th>
<th>16k</th>
<th>65k</th>
<th>256k</th>
<th>1M</th>
<th>4M</th>
<th>16M</th>
</tr>
</thead>

<tbody>
<tr>
<td>内建函数</td>
<td>6.9</td>
<td>7.3</td>
<td>7.4</td>
<td>7.5</td>
<td>7.5</td>
<td>7.5</td>
<td>7.5</td>
<td>7.5</td>
</tr>

<tr>
<td>PSHUFB</td>
<td>11.5</td>
<td>13.0</td>
<td>13.3</td>
<td>13.4</td>
<td>13.1</td>
<td>13.4</td>
<td>13.0</td>
<td>12.6</td>
</tr>

<tr>
<td>展开循环</td>
<td>12.5</td>
<td>14.4</td>
<td>15.0</td>
<td>15.1</td>
<td>15.2</td>
<td>15.2</td>
<td>15.2</td>
<td>15.2</td>
</tr>
</tbody>
</table>

<p>通过使用64位<code>popcnt</code>和展开循环，我们已经超过了据称更快的<code>pshufb</code>代码！但这很接近，在另一种编译器或芯片上可能会得到不同的结果。让我们看看是否能做得更好。</p>

<p>那么，最近备受关注的这个<a href="http://stackoverflow.com/questions/25078285/replacing-a-32-bit-loop-count-variable-with-64-bit-introduces-crazy-performance" rel="noopener noreferrer">popcnt错误依赖漏洞</a>是怎么回事？事实证明，<code>popcnt</code>对其目标寄存器存在错误依赖，这意味着即使<code>popcnt</code>的结果不依赖于其目标寄存器，CPU也认为它依赖，并在目标寄存器准备好之前等待才开始执行<code>popcnt</code>指令。</p>

<p>x86通常有两个操作数的操作，例如，<code>addl %eax, %edx</code>将<code>eax</code>和<code>edx</code>相加，然后将结果放入<code>edx</code>，因此一个操作依赖于其输出寄存器是常见的。在这种情况下，不应该有依赖关系，因为结果不依赖于输出寄存器的内容，但这很容易引入这样的错误，而且很难发现<sup id="fnref:D"><a href="#fn:D" rel="noopener noreferrer">2</a></sup>。</p>

<p>在特定情况下，<code>popcnt</code>有3个周期的延迟，但它被流水线化，使得每个周期可以执行一个<code>popcnt</code>操作。如果我们忽略其他开销，这意味着单个<code>popcnt</code>将花费3个周期，2个将花费4个周期，3个将花费5个周期，n个将花费n+2个周期，只要操作是独立的。但是，如果CPU错误地认为它们之间存在依赖关系，我们实际上就失去了流水线执行指令的能力，那么n+2就会变成3n。</p>

<p>我们可以通过购买AMD或VIA的CPU来解决这个问题，或者将<code>popcnt</code>结果放入不同的寄存器。让我们创建一个目标数组，这样我们就可以将每个<code>popcnt</code>的结果放入不同的位置。</p>

<pre><code>uint32_t builtin_popcnt_unrolled_errata(const uint64_t* buf, int len) {
  assert(len % 4 == 0);
  int cnt[4];
  for (int i = 0; i &lt; 4; ++i) {
    cnt[i] = 0;
  }

  for (int i = 0; i &lt; len; i+=4) {
    cnt[0] += __builtin_popcountll(buf[i]);
    cnt[1] += __builtin_popcountll(buf[i+1]);
    cnt[2] += __builtin_popcountll(buf[i+2]);
    cnt[3] += __builtin_popcountll(buf[i+3]);
  }
  return cnt[0] + cnt[1] + cnt[2] + cnt[3];
}
</code></pre>

<p>现在我们得到</p>

<pre><code>0000000100001420        popcntq (%rdi,%r9,8), %r8
0000000100001426        addl    %ebx, %r8d
0000000100001429        popcntq 0x8(%rdi,%r9,8), %rax
0000000100001430        addl    %r14d, %eax
0000000100001433        popcntq 0x10(%rdi,%r9,8), %rdx
000000010000143a        addl    %r11d, %edx
000000010000143d        popcntq 0x18(%rdi,%r9,8), %rcx
</code></pre>

<p>这样更好了——我们可以看到第一个popcnt输出到<code>r8</code>，第二个到<code>rax</code>，第三个到<code>rdx</code>，第四个到<code>rcx</code>。然而，这与原始版本有同样的奇怪累加方式，即不是将<code>popcnt</code>的结果加到<code>cnt[i]</code>，而是做相反的操作，这就需要在之后将结果移回<code>cnt[i]</code>。</p>

<pre><code>000000010000133e        movl    %ecx, %r10d
0000000100001341        movl    %edx, %r11d
0000000100001344        movl    %eax, %r14d
0000000100001347        movl    %r8d, %ebx
</code></pre>

<p>嗯，至少在clang（3.4）中是这样。Gcc（4.8.2）太聪明了，不会落入这种分离目标的陷阱，它会将代码“优化”回类似我们原始版本的样子。</p>

<table>
<thead>
<tr>
<th>算法</th>
<th>1k</th>
<th>4k</th>
<th>16k</th>
<th>65k</th>
<th>256k</th>
<th>1M</th>
<th>4M</th>
<th>16M</th>
</tr>
</thead>

<tbody>
<tr>
<td>内建函数</td>
<td>6.9</td>
<td>7.3</td>
<td>7.4</td>
<td>7.5</td>
<td>7.5</td>
<td>7.5</td>
<td>7.5</td>
<td>7.5</td>
</tr>

<tr>
<td>PSHUFB</td>
<td>11.5</td>
<td>13.0</td>
<td>13.3</td>
<td>13.4</td>
<td>13.1</td>
<td>13.4</td>
<td>13.0</td>
<td>12.6</td>
</tr>

<tr>
<td>展开循环</td>
<td>12.5</td>
<td>14.4</td>
<td>15.0</td>
<td>15.1</td>
<td>15.2</td>
<td>15.2</td>
<td>15.2</td>
<td>15.2</td>
</tr>

<tr>
<td>展开循环 2</td>
<td>14.3</td>
<td>16.3</td>
<td>17.0</td>
<td>17.2</td>
<td>17.2</td>
<td>17.0</td>
<td>16.8</td>
<td>16.7</td>
</tr>
</tbody>
</table>

<p>要获得一个适用于gcc和clang，且没有这些额外<code>mov</code>指令的版本，我们必须手写汇编<sup id="fnref:A"><a href="#fn:A" rel="noopener noreferrer">3</a></sup>：</p>

<pre><code>uint32_t builtin_popcnt_unrolled_errata_manual(const uint64_t* buf, int len) {
  assert(len % 4 == 0);
  uint64_t cnt[4];
  for (int i = 0; i &lt; 4; ++i) {
    cnt[i] = 0;
  }

  for (int i = 0; i &lt; len; i+=4) {
    __asm__(
        "popcnt %4, %4  \n\
        "add %4, %0     \n\t"
        "popcnt %5, %5  \n\t"
        "add %5, %1     \n\t"
        "popcnt %6, %6  \n\t"
        "add %6, %2     \n\t"
        "popcnt %7, %7  \n\t"
        "add %7, %3     \n\t" // +r means input/output, r means intput
        : "+r" (cnt[0]), "+r" (cnt[1]), "+r" (cnt[2]), "+r" (cnt[3])
        : "r"  (buf[i]), "r"  (buf[i+1]), "r"  (buf[i+2]), "r"  (buf[i+3]));
  }
  return cnt[0] + cnt[1] + cnt[2] + cnt[3];
}

</code></pre>

<p>这直接将汇编翻译成循环：</p>

<pre><code>00000001000013c3        popcntq %r10, %r10
00000001000013c8        addq    %r10, %rcx
00000001000013cb        popcntq %r11, %r11
00000001000013d0        addq    %r11, %r9
00000001000013d3        popcntq %r14, %r14
00000001000013d8        addq    %r14, %r8
00000001000013db        popcntq %rbx, %rbx
</code></pre>

<p>太好了！<code>add</code>的方向现在正确了，因为我们指定了它们应该做什么。</p>

<table>
<thead>
<tr>
<th>算法</th>
<th>1k</th>
<th>4k</th>
<th>16k</th>
<th>65k</th>
<th>256k</th>
<th>1M</th>
<th>4M</th>
<th>16M</th>
</tr>
</thead>

<tbody>
<tr>
<td>内建函数</td>
<td>6.9</td>
<td>7.3</td>
<td>7.4</td>
<td>7.5</td>
<td>7.5</td>
<td>7.5</td>
<td>7.5</td>
<td>7.5</td>
</tr>

<tr>
<td>PSHUFB</td>
<td>11.5</td>
<td>13.0</td>
<td>13.3</td>
<td>13.4</td>
<td>13.1</td>
<td>13.4</td>
<td>13.0</td>
<td>12.6</td>
</tr>

<tr>
<td>展开循环</td>
<td>12.5</td>
<td>14.4</td>
<td>15.0</td>
<td>15.1</td>
<td>15.2</td>
<td>15.2</td>
<td>15.2</td>
<td>15.2</td>
</tr>

<tr>
<td>展开循环 2</td>
<td>14.3</td>
<td>16.3</td>
<td>17.0</td>
<td>17.2</td>
<td>17.2</td>
<td>17.0</td>
<td>16.8</td>
<td>16.7</td>
</tr>

<tr>
<td>汇编</td>
<td>17.5</td>
<td>23.7</td>
<td>25.3</td>
<td>25.3</td>
<td>26.3</td>
<td>26.3</td>
<td>25.3</td>
<td>24.3</td>
</tr>
</tbody>
</table>

<p>终于！一个完全超越<code>PSHUFB</code>实现的版本。我们如何知道这应该是最终版本？我们可以从<a href="http://www.agner.org/optimize/instruction_tables.pdf" rel="noopener noreferrer">Agner的指令表</a>中看到，我们每周期最多可以执行一条<code>popcnt</code>指令。我恰好在一个3.4GHz的Sandy Bridge上运行过这个，所以我们有一个上限<code>8字节/周期 * 3.4G周期/秒 = 27.2 GB/s</code>。这已经很接近我们实际得到的<code>26.3 GB/s</code>了，这表明我们无法使其更快了<sup id="fnref:S"><a href="#fn:S" rel="noopener noreferrer">4</a></sup>。</p>

<p>在这种情况下，手写汇编版本比最初的内建函数循环快了大约3倍（不包括那个没有发出<code>popcnt</code>的clang版本）。碰巧的是，对于我们使用的编译器，使用<code>popcnt</code>内建函数的展开循环比<code>pshufb</code>版本稍快，但当我用<code>gcc</code>尝试时，两个展开版本中有一个并非如此。</p>

<p>很容易看出为什么有人会运行相同的基准测试，然后得出<code>popcnt</code>不是很令人满意的结论。也很容易看出，为什么为性能关键的代码使用内建函数可能是一个巨大的时间黑洞<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">5</a></sup>。</p>

<p><small>感谢<a href="https://github.com/graue/" rel="noopener noreferrer">Scott</a>对本文组织结构的评论，以及<a href="http://blog.leahhanson.us/" rel="noopener noreferrer">Leah</a>对几乎所有内容的详尽评论</small></p>

<p><strong>如果你喜欢这篇文章，你可能会喜欢<a href="//danluu.com/new-cpu-features/" rel="noopener noreferrer">这篇关于自80年代以来CPU如何变化的文章</a>。</strong></p>
<div>

<hr>

<ol>
<li id="fn:B"><a href="https://github.com/danluu/dump/blob/master/popcnt-speed-comparison/popcnt.c" rel="noopener noreferrer">参见此处</a>了解实际的基准测试代码。再想一下，这是一个极其糟糕的hack，我宁愿你不要看。
 <a href="#fnref:B" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:D">如果情况相反，硬件在应该依赖时没有意识到依赖关系，那很容易发现——任何有依赖关系的指令序列都可能产生错误的结果。在这种情况下，一些指令序列只是比应有的速度慢，而这并不容易检查。
 <a href="#fnref:D" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:A">此代码是<a href="http://stackoverflow.com/questions/25078285/replacing-a-32-bit-loop-count-variable-with-64-bit-introduces-crazy-performance" rel="noopener noreferrer">Alex Yee关于popcnt错误依赖漏洞的StackOverflow回答</a>的简化版本。
 <a href="#fnref:A" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:S"><p>这不完全正确，因为CPU有睿频加速技术，但这已经很接近了。暂且不谈这个，这个例子相当简单，但对于更复杂的代码，手动计算这些东西可能会很繁琐。幸运的是，<a href="https://software.intel.com/en-us/articles/intel-architecture-code-analyzer/" rel="noopener noreferrer">Intel架构代码分析器</a>可以为我们解决这个问题。它找到代码中的瓶颈（假设无限内存带宽且零延迟），并显示处理器为何以及如何遇到瓶颈，这通常足以确定是否有更多优化空间。</p>

<p>你可能已经注意到，当缓冲区大小大于我们的缓存时，性能会下降。可以进行粗略的估算来找出内存和缓存性能限制所施加的上限，但进行这些计算将占用比这个脚注更多的空间。你可以看到<a href="https://software.intel.com/en-us/forums/topic/480004" rel="noopener noreferrer">这里</a>一个简单情况的良好示例。Nathan Kurz和John McCaplin的评论特别好。</p>
 <a href="#fnref:S" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:1"><p>在运行这些基准测试的过程中，我还注意到<code>_mm_cvtsi128_si64</code>在gcc上产生了奇怪的糟糕代码（尽管在clang上没问题）。<code>_mm_cvtsi128_si64</code>是将SSE（SIMD）寄存器移动到通用寄存器（GPR）的内建函数。编译器对于变量应该驻留在寄存器还是内存中有很大的自由度。Clang意识到，如果结果即将被使用，从SSE寄存器移动值到GPR可能更快。Gcc决定节省一个寄存器，将数据从SSE寄存器移动到内存，然后让下一条指令在内存上操作（如果可能的话）。在我们的<code>popcnt</code>示例中，clang使用大约2倍的时间是因为没有展开循环，其余的则是因为没有跟进最新的CPU错误，这是可以理解的。很难想象为什么编译器会在即将对数据进行操作时进行寄存器到内存的移动，除非它根本没有进行优化，或者存在某种bug导致它不知道该指令的寄存器到寄存器版本。但至少它得到了正确的结果，<a href="https://social.msdn.microsoft.com/Forums/en-US/b2e688e6-1d28-4cf0-9880-735e6838db6a/a-bug-in-vc-compiler?forum=vsprereleaseannouncements" rel="noopener noreferrer">不像这个版本的MSVC</a>。</p>

<p>据称icc和armcc在处理内建函数方面更好，但它们对于大多数开源项目来说是不可能的选择。下载icc的免费非商业版本已经禁用了大半年，即使它恢复了，谁会相信它不会再次消失？至于armcc，我不确定它是否曾经有过免费版本？</p>
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 18145 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/assembly-intrinsics/</link>
      <guid isPermaLink="false">https://danluu.com/assembly-intrinsics/</guid>
      <pubDate>Sun, 19 Oct 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>谷歌工资操纵案，案号11-CV-02509-LHK，驳回原告对Adobe、苹果、谷歌和英特尔和解方案初步批准动议的命令</title>
      <description>&lt;pre&gt;&lt;code&gt;美国加利福尼亚北区联邦地区法院
圣何塞分庭

高科技员工反垄断集体诉讼
本文件涉及：
所有诉讼

案号：11-CV-02509-LHK
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;order-denying-plaintiffs-motion-for-preliminary-approval-of-settlements-with-adobe-apple-google-an</description>
      <content:encoded><![CDATA[<pre><code>美国加利福尼亚北区联邦地区法院
圣何塞分庭

高科技员工反垄断集体诉讼
本文件涉及：
所有诉讼

案号：11-CV-02509-LHK
</code></pre>

<h3 id="order-denying-plaintiffs-motion-for-preliminary-approval-of-settlements-with-adobe-apple-google-and-intel">驳回原告对与Adobe、苹果、谷歌和英特尔和解方案初步批准动议的命令</h3>

<p>本庭现审议由三位集体代表Mark Fichtner、Siddharth Hariharan和Daniel Stover（以下简称“原告”）提交的、关于与被告Adobe系统公司（“Adobe”）、苹果公司（“苹果”）、谷歌公司（“谷歌”）和英特尔公司（“英特尔”）（以下合称“剩余被告”）达成集体诉讼和解的初步批准动议。参见电子卷宗编号920。该和解方案规定，在集体成员放弃反垄断索赔的前提下，为集体提供总计3.245亿美元的赔偿。第四位集体代表Michael Devine（“Devine”）已提出反对意见，认为和解金额不足。参见电子卷宗编号934。原告已提交答复意见。参见电子卷宗编号938。原告、剩余被告和Devine于2014年6月19日出庭参加了听证会。参见电子卷宗编号940。此外，一些集体成员提交了支持和反对拟议和解方案的信函。电子卷宗编号914、949-51。本庭在考虑了诉状、信函、听证会上的论点以及本案卷宗后，基于下述理由，驳回初步批准动议。</p>

<h3 id="i-background-and-procedural-history">一、背景与程序历史</h3>

<p>Michael Devine、Mark Fichtner、Siddharth Hariharan和Daniel Stover代表与其处境相似的集体成员，指控其前雇主Adobe、苹果、谷歌、英特尔、Intuit公司（“Intuit”）、Lucasfilm有限公司（“Lucasfilm”）和Pixar（合称“被告”）存在反垄断违法行为。原告指控被告通过一系列双边协议达成一项总体协议，即不互相挖角对方的员工，这违反了《谢尔曼反垄断法》第1条（15 U.S.C. § 1）和《克莱顿反垄断法》第4条（15 U.S.C. § 15）。原告认为，该总体协议由六个双边协议（Pixar-Lucasfilm、苹果-Adobe、苹果-谷歌、苹果-Pixar、谷歌-Intuit、谷歌-英特尔）构成，压低了被告员工的工资。</p>

<p>本合并诉讼所依据的五起案件最初在加州高等法院提起，后被移交给联邦法院。参见电子卷宗编号532第5页。案件由法官Saundra Brown Armstrong关联，并将相关诉讼转移至圣何塞分庭。参见电子卷宗编号52、58。在分配给本法官后，案件根据双方约定予以合并。参见电子卷宗编号64。原告于2011年9月23日提交了合并起诉状，参见电子卷宗编号65，被告联合提交了驳回动议，参见电子卷宗编号79。此外，Lucasfilm于2011年10月17日单独提交了驳回动议。参见电子卷宗编号83。本庭部分同意、部分驳回联合驳回动议，并驳回了Lucasfilm的单独驳回动议。参见电子卷宗编号119。</p>

<p>2012年10月1日，原告提交了集体认证动议。参见电子卷宗编号187。该动议寻求认证一个涵盖所有七家被告员工的集体，或者作为替代方案，一个仅涵盖七家被告技术员工的更小集体。经过充分的诉状往来和听证后，本庭于2013年4月5日驳回了集体认证。参见电子卷宗编号382。本庭担心原告的文件证据和实证分析不足以确定在反垄断影响问题上，共同问题是否优先于个人问题。参见该文件第33页。此外，本庭对集体认证动议中关于技术员工集体的分析不足表示关切。参见该文件第29页。本庭允许原告修改动议以解决本庭关切。参见该文件第52页。</p>

<p>2013年5月10日，原告提交了修改后的集体认证动议，仅寻求认证范围更小的技术员工集体。参见电子卷宗编号418。被告于2013年6月21日提交了反对意见，电子卷宗编号439，原告于2013年7月12日提交了答复意见，电子卷宗编号455。修改后动议的听证会定于2013年8月5日举行。</p>

<p>2013年7月12日和30日，在集体认证最初被驳回且修改后的动议待决期间，原告与Pixar、Lucasfilm和Intuit（以下合称“已和解被告”）达成和解。参见电子卷宗编号453、489。原告于2013年9月21日提交了对与已和解被告和解方案的初步批准动议。参见电子卷宗编号501。未有人提交反对意见，本庭于2013年10月21日举行听证会后，于2013年10月30日批准了该动议。参见电子卷宗编号540。本庭于2014年5月1日举行了公平性听证会，电子卷宗编号913，并于2014年5月16日在考虑五项异议后，最终批准了和解方案以及相关的律师费、费用和激励奖金请求。电子卷宗编号915-16。针对已和解被告的判决于2014年6月20日作出。电子卷宗编号947。</p>

<p>在已和解被告和解后，本庭于2013年10月25日批准了原告的修改后集体认证动议，在一份长达86页的命令中认证了七家被告技术员工的集体（以下简称“集体”）。参见电子卷宗编号532。剩余被告根据《联邦民事诉讼规则》第23(f)条提请第九巡回上诉法院审查该命令。在充分的诉状往来后，包括国家和加州商会以及国家制造商协会提交的法庭之友简报，敦促第九巡回法院批准审查，第九巡回法院于2014年1月15日拒绝了审查请求。参见电子卷宗编号594。</p>

<p>与此同时，在本庭，剩余被告共提交了五项即决判决动议，并提交了排除和反对原告关于反垄断影响和损害赔偿的主要专家证人Edward Leamer博士证词的动议，该专家认为，如果没有这些互不挖角协议，集体成员本应获得的工资总损失超过30亿美元。<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">1</a></sup>本庭于2014年3月28日驳回了即决判决动议，并于2014年4月4日驳回了排除Leamer博士证词的动议，大部分驳回了反对其证词的动议。电子卷宗编号777、788。</p>

<p>2014年4月24日，原告和剩余被告的律师向本庭发出联合信函，表示他们已达成和解。参见电子卷宗编号900。该和解是在最终审前会议前两周、开庭前一个月达成的。<sup id="fnref:2"><a href="#fn:2" rel="noopener noreferrer">2</a></sup>收到联合信函后，本庭取消了开庭日期和审前截止日期，并为初步批准设定了日程。参见电子卷宗编号904。在律师发出信函后不久，媒体披露了和解总金额，本庭收到了三封来自个人（不包括Devine）的信函，对拟议和解方案提出异议，这是对媒体报道和解金额的回应。<sup id="fnref:3"><a href="#fn:3" rel="noopener noreferrer">3</a></sup>参见电子卷宗编号914。2014年5月22日，根据本庭的日程安排，原告提交了初步批准动议。参见电子卷宗编号920。Devine于2014年6月5日提交了反对意见。<sup id="fnref:4"><a href="#fn:4" rel="noopener noreferrer">4</a></sup>参见电子卷宗编号934。原告于2014年6月12日提交了答复意见。参见电子卷宗编号938。本庭于2014年6月19日举行了听证会。参见电子卷宗编号948。听证会后，本庭收到了一封反对拟议和解方案的集体成员信函和两封支持拟议和解方案的集体成员信函。参见电子卷宗编号949-51。</p>

<h3 id="ii-legal-standard">二、法律标准</h3>

<p>本庭必须根据《联邦民事诉讼规则》第23(e)条审查集体诉讼和解的公平性。该规则规定，“经认证集体的索赔、争议点或抗辩只能经法院批准才能和解、自愿撤诉或妥协。”该规则要求法院“以合理方式向所有受该提议约束的集体成员发出通知”，并进一步规定，如果和解“将约束集体成员，只有在举行听证会并认定其公平、合理且充分后，法院才能予以批准。”《联邦民事诉讼规则》23(e)(1)-(2)。法院监督集体诉讼和解的主要目的是确保“协议不是谈判方欺诈、滥用权利或串通的产物。” Officers for Justice v. Civil Serv. Comm'n of City &amp; Cnty. of S.F., 688 F.2d 615, 625 (9th Cir. 1982)。</p>

<p>地区法院将《规则》23(e)解释为要求批准集体诉讼和解分两步进行：“法院首先确定拟议的集体诉讼和解是否值得初步批准，然后在通知集体成员后，确定最终批准是否合理。” Nat'l Rural Telecomms. Coop. v. DIRECTV, Inc., 221 F.R.D. 523, 525 (C.D. Cal. 2004)。在最终批准阶段，第九巡回法院指出，“评估和解方案需要地区法院权衡多个因素：原告案件的强弱；进一步诉讼的风险、费用、复杂性和可能持续的时间；在整个审判过程中维持集体诉讼状态的风险；和解金额；已完成的证据开示程度和诉讼进展阶段；律师的经验和观点；政府参与者的存在；以及集体成员对拟议和解方案的反应。” Hanlon v. Chrysler Corp., 150 F.3d 1011, 1026 (9th Cir. 1998)。</p>

<p>与这些既定的、非详尽的最终批准因素相比，关于地区法院在初步批准阶段审查和解时应适用的标准，上诉权威相对较少。一些地区法院在评论者的支持下表示，相关问题是和解方案“是否在可能被批准的范围内”或“是否在合理范围内”。 In re Tableware Antitrust Litig., 484 F. Supp. 2d 1078, 1079 (N.D. Cal. 2007)；另见 Cordy v. USS-Posco Indus., No. 12-553, 2013 WL 4028627, at *3 (N.D. Cal. Aug. 1, 2013)（“初步批准和解并通知拟议集体是适当的，前提是拟议和解似乎是严肃、知情、非串通谈判的产物，没有明显缺陷，没有不当给予集体代表或集体部分成员优惠待遇，并且在可能被批准的范围内。”（内部引述省略））。为了进行这项分析，法院“必须考虑原告的预期收益与和解要约的价值。” In re Nat'l Football League Players' Concussion Injury Litig., 961 F. Supp. 2d 708, 714 (E.D. Pa. 2014)（内部引述省略）。</p>

<h3 id="iii-discussion">三、讨论</h3>

<p>根据本和解的条款，未退出集体且未选择退出的集体成员将放弃就本案涉及的索赔起诉剩余被告的权利。作为交换，剩余被告将支付总计3.245亿美元，其中原告律师可寻求最高25%（约8100万美元）的律师费、120万美元的费用，以及每位集体代表8万美元的激励付款。此外，和解协议允许剩余被告在通知后超过4%的集体成员选择退出时，按比例减少其必须支付的总额。<sup id="fnref:5"><a href="#fn:5" rel="noopener noreferrer">5</a></sup> 如果本庭批准所有请求的扣除项且没有进一步的退出者，集体成员将从本和解中平均获得约3,750美元。<sup id="fnref:6"><a href="#fn:6" rel="noopener noreferrer">6</a></sup><sup id="fnref:7"><a href="#fn:7" rel="noopener noreferrer">7</a></sup></p>

<p>本庭认为，总和解金额低于合理范围。本庭关注的是，尽管案件自上次和解以来一直朝着对集体有利的方向发展，但集体成员从本次与剩余被告的和解中获得的按比例补偿却少于一年前与已和解被告的和解。律师解释这一金额减少的唯一理由是，原告的案件存在弱点，使得集体面临无法获得赔偿的重大风险。然而，该风险在一年前原告与已和解被告和解时就已经存在，甚至更大，因为当时集体认证刚刚被驳回。</p>

<p>本庭首先将本次与剩余被告的和解与与已和解被告的和解进行比较，同时考虑每次和解达成时的事实情况。然后，本庭讨论原告案件的相对强弱，以评估本次和解的合理性。</p>

<h2 id="a-comparison-to-the-initial-settlements">A. 与初始和解的比较</h2>

<h3 id="1-comparing-the-settlement-amounts">1. 比较和解金额</h3>

<p>本庭认为，与已和解被告的和解为分析本次和解的合理性提供了一个有用的基准。与已和解被告的和解产生了一个总额为2000万美元的基金。参见电子卷宗编号915第3页。在批准这些和解时，本庭依据的事实是，已和解被告雇佣了8%的集体成员，并在集体期间支付了集体总薪酬的5%。参见电子卷宗编号539第16页第20-22行（原告律师在已和解被告初步批准听证会上的解释，即5%的数字“让你感觉到该和解在本案其余部分中所占的比例”）。如果剩余被告以与已和解被告相同（或更高）的比率和解，剩余被告的和解基金总额至少需要达到3.8亿美元。这个数字源于这样一个事实：剩余被告在集体期间支付了集体总薪酬的95%，而已和解被告仅支付了5%。<sup id="fnref:8"><a href="#fn:8" rel="noopener noreferrer">8</a></sup></p>

<p>在本次动议的听证会上，剩余被告的律师建议相关基准不是集体总薪酬，而是集体总人数。这将导致剩余被告的基准数字为2.3亿美元<sup id="fnref:92-divided-by-8-is-11-5-11-5-times-20-million-is-230-million"><a href="#fn:92-divided-by-8-is-11-5-11-5-times-20-million-is-230-million" rel="noopener noreferrer">0</a></sup>。律师至少建议，本庭应将和解金额与2.3亿至3.8亿美元的范围进行比较，本次和解落在该范围内。本庭驳回了律师的建议，该建议与卷宗记录相悖。律师没有提供任何依据说明每家被告雇佣的集体成员人数是一个相关指标。相反，相关问题一直是集体总薪酬。例如，在已和解被告的和解以及本次和解中，分配方案都要求通过将集体成员在集体期间的薪酬除以集体期间的总薪酬来确定每位集体成员的支付额。电子卷宗编号809第6页（指出已和解被告和解分配方案中的分母是“集体期间所有已批准索赔人在集体职位上支付的基本工资总额”）；电子卷宗编号920第22页（本次和解中相同）；另见电子卷宗编号539第16页第20-22行（原告律师关于集体总薪酬百分比对于将已和解被告和解与本案其余部分进行基准比较是相关的陈述）。在卷宗记录中，每家被告雇佣的集体成员百分比从未成为确定损害赔偿敞口的相关因素。因此，本庭驳回了剩余被告律师提出的指标。以已和解被告的和解为标准，剩余被告的适当基准和解金额至少应为3.8亿美元，比本次和解提供的金额高出5000多万美元。</p>

<p>剩余被告的律师还建议，以初始和解为基准是不合适的，因为剩余被告的和解金额规模在已和解被告的和解金额面前相形见绌。这个论点的前提是，对集体造成更大损害并从压低更大比例集体薪酬中获益更多的被告，应该比造成较少损害、从所谓不当行为中获益较少的被告支付更少。这个论点没有说服力。剩余被告据称从互不挖角协议中获得了95%的利益，并在集体薪酬损失方面造成了95%的损害。因此，剩余被告至少应支付95%的损害赔偿，但根据本次和解，他们不会这么做。</p>

<p>本庭还注意到，如果原告在审判中就其超过30亿美元的损害赔偿主张获胜，反垄断法规定自动三倍赔偿，参见15 U.S.C. § 15(a)，因此总损害赔偿判决可能超过90亿美元。虽然第九巡回法院尚未确定反垄断案件的和解金额是否必须与原告要求的单倍损害赔偿还是自动三倍损害赔偿进行比较，参见Rodriguez v. W. Publ'g Corp., 563 F.3d 948, 964-65 (9th Cir. 2009)，但本次和解将导致总回收额占原告专家提出的单倍损害赔偿的11.29%，或占三倍损害赔偿的3.76%。具体而言，Leamer博士计算被告所谓非法行为给集体造成的总损害为30.5亿美元。参见电子卷宗编号856-10。如果本庭批准本次和解，与所有被告的总和解金额将为3.445亿美元。这个总额相当于Leamer博士认为集体所受损害的单倍损害赔偿的11.29%，或者如果Leamer博士的损害数字被三倍计算，则为3.76%。</p>

<h3 id="2-relative-procedural-posture">2. 相对的诉讼地位</h3>

<p>剩余被告相对于已和解被告所获得的折扣尤其令人不安，因为两次和解之间案件诉讼地位的变化，本庭预计这些变化应该会增加而不是减少原告的议价能力。具体而言，在已和解被告和解时，原告正处于其案件的一个特别薄弱点。虽然原告已经通过了被告的驳回动议，但原告的集体认证动议被驳回，尽管是未带偏见地驳回。原告已重新提交了集体认证动议的诉状，但在与已和解被告和解时，还没有获得有利于他们的集体认证裁决。如果本庭最终批准认证，原告也不确定第九巡回法院是否会批准根据《联邦民事诉讼规则》第23(f)条进行审查并撤销认证。因此，在那时，被告拥有巨大的筹码。</p>

<p>相反，在初始和解达成后，案件的诉讼地位发生了戏剧性变化，对原告有利。具体而言，本庭在被告的强烈反对下批准了集体认证。在批准集体认证的86页命令中，本庭多次提到原告的证据“实质性”和“广泛”，并表示“在集体认证阶段，本庭无法识别一个案件拥有原告在本案中呈现的文件证据水平。”电子卷宗编号531第69页。此后，第九巡回法院拒绝了被告根据《联邦民事诉讼规则》第23(f)条审查集体认证命令的请求。本庭还驳回了被告的五项即决判决动议，并驳回了被告排除原告关于反垄断影响和损害赔偿的主要专家Leamer博士证词的动议。本次和解是在最终审前会议前仅两周、开庭前仅一个月达成的，届时本应呈现关于被告的损害证据。</p>

<p>总之，在本次和解达成时——在集体获得认证、集体认证的上诉审查被拒绝、被告的终结性动议和排除Leamer博士证词的动议被驳回之后——原告的处境要比与已和解被告达成和解时（当时集体认证被驳回）强大得多。这种诉讼地位的转变，本庭预计会增加原告的议价能力，使得最近金额按比例更低的和解更加令人不安。</p>

<h2 id="b-strength-of-plaintiffs-case">B. 原告案件的强度</h2>

<p>本庭现在转向原告针对剩余被告案件的强度，以评估本次和解的合理性。</p>

<p>在本次动议的听证会上，原告律师辩称，本次和解金额按比例低于先前和解的一个原因是，针对已和解被告（特别是Lucasfilm和Pixar）的文件证据比针对剩余被告的证据更有说服力。首先，本庭注意到，关于已和解被告的相关证据在针对剩余被告的审判中是可采信的，因为原告指控一个包括所有被告在内的总体协议。因此，关于Lucasfilm和Pixar在总体协议的创建及其预期效果中的角色的证据将是可采信的。</p>

<p>尽管如此，本庭注意到原告是正确的，即Lucasfilm和Pixar的高管有关于所谓协议的性质和目标的特别明确陈述。具体而言，Edward Catmull（Pixar总裁）在作证时承认互不挖角协议的存在是因为挖角“会打乱薪酬结构”。电子卷宗编号431-9第81页。同样，George Lucas（Lucasfilm前董事会主席兼首席执行官）表示，“我们不能与其他公司展开竞价战，因为我们没有那种利润率。”电子卷宗编号749-23第9页。</p>

<p>然而，同样有说服力的证据来自剩余被告的文件。对于谷歌和苹果尤其如此，其高管广泛讨论并执行了互不挖角协议。具体而言，正如本庭先前命令中详细讨论的，Steve Jobs（苹果联合创始人、前董事长兼前首席执行官、Pixar前首席执行官）、Eric Schmidt（谷歌执行董事长、董事会成员兼前首席执行官）和Bill Campbell（Intuit董事会主席、苹果联合首席董事兼谷歌顾问）是创建和执行互不挖角协议的关键人物。本庭现在转向事实认定者可能认为有说服力的针对剩余被告的证据。</p>

<h3 id="1-evidence-related-to-apple">1. 与苹果相关的证据</h3>

<p>有大量且令人信服的证据表明，Steve Jobs（苹果联合创始人、前董事长兼前首席执行官、Pixar前首席执行官）是所谓协议的核心人物，如果不是唯一核心人物的话。多位证人在其证词中证实了Jobs先生在互不挖角协议中的角色。例如，Eric Schmidt（谷歌执行董事长、董事会成员兼前首席执行官）表示，Jobs先生“认为你们不应该互相雇佣对方的，你知道，技术人员”，并且“在Jobs先生看来，我们打电话和雇佣人员是不合适的。”电子卷宗编号819-12第77页。Edward Catmull（Pixar总裁）表示，Jobs先生“非常坚持保护他的员工队伍。”电子卷宗编号431-9第97页。Sergey Brin（谷歌联合创始人）作证说，“我认为Jobs先生的观点是，人们不应该惹他生气。而我认为惹他生气的事情会是——雇佣，你知道，不管什么。”电子卷宗编号639-1第112页。因此，将有充分的证据表明Jobs先生参与将Lucasfilm和Pixar之间的原始互不挖角协议扩展到本案中的其他被告。协议扩展后，Jobs先生在执行这些协议方面发挥了核心作用。四组特定证据很可能对事实认定者具有说服力。</p>

<p>第一，在听说谷歌试图从苹果的Safari团队招募员工后，Jobs先生威胁了Brin先生，Brin先生回忆道，“如果你雇佣其中任何一个人，那就意味着战争。”电子卷宗编号833-15。<sup id="fnref:10"><a href="#fn:10" rel="noopener noreferrer">9</a></sup>在一封发给谷歌管理执行团队以及Bill Campbell（Intuit董事会主席、苹果联合首席董事兼谷歌顾问）的电子邮件中，Brin先生建议：“在讨论之前，我们不要向苹果的任何新员工发出录用通知或接触新人。”同上。Campbell先生随后写信给Jobs先生：“Eric[Schmidt]告诉我，他直接参与并坚决阻止了所有从苹果招聘的努力。”电子卷宗编号746-5。正如Brin先生在作证时所述，“Eric进行了——你知道，至少某种——进行了与Bill的谈话，以向Steve转达，让他冷静下来。”电子卷宗编号639-1第61页。正如Schmidt所述，“Steve不高兴，而Steve的不高兴绝对影响了我们招聘实践的改变。”电子卷宗编号819-12第21页。Danielle Lambert（苹果人力资源主管）也采取了相应行动以维持苹果方面的互不挖角协议，指示苹果招聘人员：“请将谷歌添加到您的‘免打扰名单’中。我们最近同意不互相招聘，因此如果您听说他们针对我们进行任何招聘，请务必告知我。”电子卷宗编号746-15。</p>

<p>第二，其他被告的首席执行官出于对Jobs先生的恐惧和尊重而维持了互不挖角协议。例如，2005年，在考虑是否与苹果达成互不挖角协议时，Bruce Chizen（Adobe前首席执行官）对“顶尖人才”流失表示担忧，如果Adobe不与苹果达成互不挖角协议，他说，“如果我告诉Steve现在是开放季节（高级经理除外），他会故意挖角Adobe以证明一个观点。了解Steve，他会针对我们一些顶尖的Mac人才，比如Chris Cox，并且会以诱使他们加入的方式（非凡的待遇和Steve的亲自劝说）。”<sup id="fnref:11"><a href="#fn:11" rel="noopener noreferrer">10</a></sup>电子卷宗编号297-15。</p>

<p>这就是苹果-Adobe协议的起源。具体而言，在Jobs先生于2005年5月26日向Chizen先生抱怨Adobe正在招聘苹果员工后，电子卷宗编号291-17，Chizen先生回应说，“我以为我们同意不招聘任何高级员工……我提议维持现状。愿意讨论。达成协议是件好事。”同上。Jobs先生并不满意，并通过威胁派遣苹果招聘人员去挖角Adobe员工作为回应：“好的，我会告诉我们的招聘人员，他们可以接触任何非高级总监或副总裁的Adobe员工。我这样理解您的立场对吗？”同上。Chizen先生立即让步：“我宁愿同意不主动招聘对方的任何员工……如果您同意，我会告知我的员工。”同上（斜体为原文所有）。第二天，Theresa Townsley（Adobe人力资源副总裁）向她的招聘团队宣布，“Bruce和Steve Jobs达成了一项协议，我们不得招聘任何苹果员工，反之亦然。”电子卷宗编号291-18（斜体为原文所有）。Adobe随后将苹果列入其“免接触名单”，指示Adobe员工不要主动联系苹果员工。电子卷宗编号291-11。</p>

<p>谷歌对Jobs先生采取了更激烈的行动。例如，当谷歌工程团队的一名招聘人员在2007年联系了一名苹果员工时，Jobs先生将消息转发给Schmidt先生，并表示，“如果您的招聘部门停止这样做，我将非常高兴。”电子卷宗编号291-23。谷歌通过“公开示众”该招聘人员并“在一小时内解雇”他来回应。同上。这种公开表演的目的是“（希望）防止未来再次发生”。同上。招聘人员被解雇后，Schmidt先生给Jobs先生发了电子邮件道歉，并告知Jobs先生招聘人员已被解雇。Jobs先生将Schmidt先生的电子邮件转发给一名苹果人力资源官员，仅表示“:)”。电子卷宗编号746-9。</p>

<p>在此之前一年，谷歌同样认真对待了Jobs先生的关切。具体而言，2006年，Jobs先生给Schmidt先生发电子邮件说，“我被告知谷歌的[原文如此]新手机软件团队正在无情地招聘我们iPod团队的人。如果这是真的，你能阻止吗？”电子卷宗编号291-24第3页。在Schmidt先生将此转发给谷歌的人力资源专业人员后，Arnnon Geshuri（谷歌招聘总监）准备了一份详细报告，称 extensive investigation 没有发现违反互不挖角协议的情况。</p>

<p>同样在2006年，谷歌在一名谷歌高管通过电子邮件询问Jobs先生谷歌是否可以雇用三名前苹果工程师在拟议的设施工作后，取消了在巴黎开设谷歌工程中心的计划，Jobs先生回应说“我们强烈希望你不要雇佣这些人。”电子卷宗编号814-2。整个互动始于谷歌请求Steve Jobs批准雇佣Jean-Marie Hullot，一名苹果工程师。卷宗记录不清楚Hullot先生是现任还是前任苹果员工。一名谷歌高管联系Steve Jobs询问谷歌是否可以向Hullot先生发出录用通知，Jobs先生没有及时回应谷歌高管的请求。此时，该谷歌高管求助于Intuit的董事会主席Bill Campbell，作为谷歌与Jobs先生之间的潜在大使。具体而言，该谷歌高管指出Campbell先生“在苹果和谷歌的董事会任职，因此Steve很可能会回他的电话。”电子卷宗编号428-6。就在Campbell先生联系Jobs先生的同一天，Jobs先生回应了谷歌高管，寻求关于该苹果工程师将从事什么工作的更多信息。电子卷宗编号428-9。一旦Jobs先生满意，他表示雇佣“对我而言没问题。”同上。然而，两周后，当Hullot先生和一名谷歌高管寻求Jobs先生批准雇佣Hullot的四名前苹果同事（三名是前苹果员工，一名已通知即将从苹果离职）时，Jobs先生迅速回应，表示这些雇佣不可接受。电子卷宗编号428-9。谷歌迅速取消了该计划，该谷歌高管恭敬地回应Jobs先生说，“Steve，基于您强烈希望我们不要雇佣前苹果工程师，Jean-Marie和我决定不在巴黎开设谷歌工程中心。”同上。该谷歌高管还将电子邮件线程转发给了Brin先生、Larry Page（谷歌联合创始人）和Campbell先生。同上。</p>

<p>第三，Jobs先生试图（但未成功）将互不挖角协议扩展到Palm，甚至威胁提起诉讼。具体而言，Jobs先生致电Edward Colligan（Palm前总裁兼首席执行官）要求Colligan先生达成互不挖角协议，并威胁如果Palm拒绝将对其提起专利诉讼。电子卷宗编号293 ¶¶ 6-8。Colligan先生通过电子邮件回复了Jobs先生，告诉Jobs先生Jobs先生“提议我们同意任何一方都不雇佣另一方的员工，无论个人意愿如何，这不仅是错误的，而且可能是非法的。”同上第4-5页。Colligan先生继续说道，“我们不能决定某人将在哪里工作，也不应该尝试。我不能仅仅因为某人现在为苹果工作就否认那些选择在Palm谋生的人这样做的权利，我也不会希望你对Palm的现任员工这样做。”同上第5页。最后，Colligan先生写道，“因一名员工决定离开苹果而以专利诉讼威胁Palm是不合理的。诉讼不会服务于我们任何一方的利益，也不会阻止员工在我们公司之间流动……我们最终都会花很多钱请很多律师。”同上第5-6页。Jobs先生回复Colligan先生如下：“这对苹果来说不可接受。”同上第8页。Jobs先生继续写道，“当你[引述]‘我们最终都会花很多钱请很多律师’时，我相信你意识到了我们各自公司财务资源的这种不对称性。”同上。Jobs先生总结道：“我的建议是在你做出最终决定之前看看我们的专利组合。”同上。</p>

<p>第四，苹果的文件有力地支持了原告关于影响的理论，即严格的工资结构和内部公平性考虑将导致被告进行薪酬结构调整，以减轻挖角带来的竞争威胁。苹果的薪酬数据显示，在集体期间的每一年，苹果都有一个“工作结构系统”，该系统包括根据一套离散的全公司工作级别对所有领薪员工进行分类和薪酬，并关联四个适用于“顶级”、“主要”、“国家级”和“小型”地理市场的基本工资范围。电子卷宗编号745-7第14-15页、第52-53页；电子卷宗编号517-16 ¶¶ 6、10 &amp; 附录B。每个工资范围都有一个“最低”、“中间”和“最高”数字。参见同上。苹果还创建了一个名为“Merlin”的人力资源和招聘工具，这是一个用于跟踪员工记录和绩效的内部系统，要求经理将员工评为四个预设级别之一。参见电子卷宗编号749-6第142-43页、第145-46页；电子卷宗编号749-11第52-53页；电子卷宗编号749-12第33页。正如Tony Fadell（前苹果高级副总裁、iPod部门负责人兼Steve Jobs顾问）所解释的，Merlin“会说，这是员工，这是级别，这里是工资范围，通过这个工具，我们就能——我们理解了边界是什么。”电子卷宗编号749-11第53页。超出这些规定的“指导方针”也需要额外的批准。电子卷宗编号749-7第217页；电子卷宗编号749-11第53页（“如果我们超出那个范围，那么我们就必须召集一堆人来批准任何超出该范围的内容。”）。</p>

<p>对内部公平性的担忧也渗透到苹果的薪酬计划中。Steven Burmeister（苹果薪酬高级总监）作证说，内部公平性——Burmeister将其定义为“员工的薪酬是否‘根据个人贡献相对于其所在团队或整个组织的其他员工是公平的’”这一概念——是经理在确定起薪时考虑的指导方针之一，如果不是全部的话。电子卷宗编号745-7第61-64页；电子卷宗编号753-12。事实上，正如Patrick Burke（前苹果技术招聘人员和人员配备经理）所解释的，在招聘新员工时，“将候选人与将加入其团队的其他成员进行比较”是决定我们给予其薪资的“最重要因素”。电子卷宗编号745-6第279页。</p>

<h3 id="2-evidence-related-to-google">2. 与谷歌相关的证据</h3>

<p>针对谷歌的证据同样令人信服。电子邮件证据显示，Eric Schmidt（谷歌执行董事长、董事会成员兼前首席执行官）至少解雇了两名违反互不挖角协议的招聘人员，并威胁要解雇更多人。如上所述，有直接证据表明Schmidt先生应Steve Jobs的要求解雇了一名招聘人员，该招聘人员曾试图招揽一名苹果员工。此外，在一封发给Bill Campbell（Intuit董事会主席、苹果联合首席董事兼谷歌顾问）的电子邮件中，Schmidt先生表示，他指示对另一名谷歌招聘人员进行有因解雇，该招聘人员曾试图招聘eBay的一名高管，而eBay在谷歌的“免接触名单”上。电子卷宗编号814-14。最后，正如下面将详细讨论的，Schmidt先生告知Paul Otellini（英特尔首席执行官兼谷歌董事会成员），Schmidt先生将解雇任何招聘英特尔员工的招聘人员。</p>

<p>此外，谷歌维持了一个正式的“免打扰”名单，将苹果、英特尔和Intuit归为一组，并获得了高管层的批准。电子卷宗编号291-28。该名单还包括其他公司，如Genentech、Paypal和eBay。同上。“免打扰”名单的草案提交给了谷歌管理执行委员会，该委员会由谷歌的高级高管组成，包括Schmidt先生、Larry Page（谷歌联合创始人）、Sergey Brin（谷歌联合创始人）和Shona Brown（前谷歌业务运营高级副总裁）。电子卷宗编号291-26。Schmidt先生批准了该名单。参见同上；另见电子卷宗编号291-27（Schmidt先生的电子邮件称：“这看起来非常好。”）。此外，有证据表明谷歌高管知道互不挖角协议可能导致法律麻烦，但仍然继续执行这些协议。当Brown女士询问Schmidt先生是否担心将“免打扰”名单的信息分享给谷歌的竞争对手时，Schmidt先生回应说，他更倾向于“口头分享，因为我不想留下纸质痕迹，以后因此被起诉？”电子卷宗编号291-40。Brown女士回应道：“口头分享很有道理。我同意。”同上。</p>

<p>谷歌对来自Facebook的竞争的回应也证明了所谓协议的影响。谷歌长期以来一直担心Facebook招聘对人才保留的影响。例如，在一封发给谷歌高级高管的电子邮件中，Brin先生在2007年表示“Facebook现象造成了真正的人才保留问题。”电子卷宗编号814-4。一个月后，Brin先生宣布了一项政策，即在任何谷歌员工收到Facebook录用通知后一小时内发出反要约。电子卷宗编号963-2。</p>

<p>2008年3月，Arnnon Geshuri（谷歌招聘总监）发现非诉讼方Facebook一直在主动联系谷歌的站点可靠性工程（“SRE”）团队。Geshuri先生的第一反应是建议联系Sheryl Sandberg（Facebook首席运营官），试图“要求她停止针对我们SRE团队的目标招聘努力”，并“考虑建立一个相互‘免打扰’协议，规定我们不主动联系彼此。”电子卷宗编号963-3。Geshuri先生还建议“从内部查看SRE团队的流失率”，并表示“我们可能希望考虑额外的个人留任激励或团队激励，以将SRE的流失率保持在尽可能低的水平。”同上（强调为原文所有）。最后，另一种建议是“发起一场积极的运动，主动联系他们公司并针对他们的人员——毫无保留。我们将坚定不移，势不可挡。”同上。作为回应，Bill Campbell（Intuit董事会主席、苹果联合首席董事兼谷歌顾问）在作为谷歌顾问的权限内建议“谁应该联系Sheryl <a href="or Mark [Zuckerberg]" rel="noopener noreferrer">Sandberg</a>以达成停火？我们必须达成休战。”同上。Facebook拒绝了。</p>

<p>2010年，谷歌针对Facebook的招聘改变了其薪酬结构，实行“Big Bang”，这为原告的反垄断影响理论提供了额外支持。具体而言，在谷歌失去大量员工给Facebook之后，谷歌开始研究Facebook对谷歌员工的招聘情况。电子卷宗编号190 ¶ 109。在开始这项研究一个月后，谷歌宣布了其“Big Bang”，内容包括将所有领薪员工的基本工资提高10%，并向所有员工提供1,000美元的即时现金奖金。电子卷宗编号296-18。Laszlo Bock（谷歌人力资源高级副总裁）解释说，Big Bang的理由包括：（1）“应对不断上升的流失率”；（2）支持更高的保留率，因为“更高的工资会产生更高的固定成本”；（3）“非常具有战略性，因为初创公司没有现金流来匹配，而大公司则（a）过于担心内部公平性和可扩展性而无法做到这一点，（b）没有利润率做到这一点。”电子卷宗编号296-20。</p>

<p>其他谷歌文件为原告的反垄断影响理论提供了进一步证据。例如，谷歌的首席文化官表示，“主动联系公司招聘是预期之内的，除非他们在我们的‘免打扰’名单上。”电子卷宗编号291-41。此外，谷歌发现，虽然推荐是招聘的最大来源，但“招聘机构和被动来源的候选人提供了最高的产出。”电子卷宗编号780-8。信息在员工之间的传播——如果存在积极的招聘——这一点对于原告的影响理论至关重要，也在谷歌的证据中得到体现。例如，一名谷歌员工表示，“对这种事情保密是不可能的。收到反要约的人不仅会与谷歌员工和前谷歌员工交谈，还会与其收到录用通知的竞争对手交谈（希望能提高要约），而那些竞争对手也会交谈，以此作为招聘更多谷歌员工的工具。”电子卷宗编号296-23。</p>

<p>谷歌的工资结构和内部公平性考虑也支持原告的影响理论。谷歌有许多工作族，工作族内有许多级别，级别内有许多工作头衔。参见例如电子卷宗编号298-7、电子卷宗编号298-8；另见Cisneros Decl., Ex. S (Brown Depo.) at 74-76（讨论谷歌使用的工资范围）；电子卷宗编号780-4第25-26页（作证说谷歌2007年的工资范围通常与2004年的结构相同）。在整个集体期间，谷歌使用具有最小值和最大值以及平均值或中位数的工资范围和薪酬带。电子卷宗编号958-1 ¶ 66；参见电子卷宗编号427-3第15-17页。正如Shona Brown（前谷歌业务运营高级副总裁）所解释的，“如果你讨论一个具体的职位[在谷歌]，你可以理解该职位处于某个工作阶梯的特定级别。”电子卷宗编号427-3第27-28页；电子卷宗编号745-11。Frank Wagner（谷歌薪酬总监）作证说，他可以通过公司内部网站找到谷歌工作的目标工资范围。参见电子卷宗编号780-4第31-32页（“问：如果你想确定某个工作族和某个级别的某个工作的目标工资范围，你能否上网或去某个地方……并找出该工作族和该级别的工资范围？……答：可以。”）。此外，谷歌认为内部公平性是一个重要目标。谷歌使用薪酬算法的部分目的是“通过将工资管理在合理范围内来确保内部公平性。”电子卷宗编号814-19。此外，因为谷歌“努力实现整体薪酬分配的公平性”，“高绩效但低工资的员工将获得比高绩效但高工资的员工更大的百分比加薪。”电子卷宗编号817-1第15页。</p>

<p>此外，谷歌分析并将其股权薪酬与苹果、英特尔、Adobe和Intuit等公司进行了比较，这些公司均被谷歌指定为“同行公司”，依据是它们满足“高科技公司”、“高增长公司”和“关键劳动力市场竞争对手”等标准。电子卷宗编号773-1。2007年，基于对谷歌与同行公司的部分分析，Bock先生和Dave Rolefson（谷歌股权薪酬经理）写道：“我们最大的劳动力市场竞争对手正在显著超越他们自己的指导方针，以击败谷歌争夺人才。”同上。</p>

<p>最后，谷歌自己的文件削弱了被告关于缺乏反垄断影响的主要理论，即薪酬决策将是一次性的，而不是全集体性的。Alan Eustace（谷歌高级副总裁）在评论对人才的竞争担忧和谷歌的反要约方法时指出，“有时对薪酬进行调整是有意义的，即使这会引入当前薪酬的不连续性，以留住我们最好的员工，并向招聘公司传达一个信息：我们将为留住我们最好的员工而战。”电子卷宗编号296-23。因为招聘“少数几个真正优秀的人”可能会激励“许多其他人效仿”，Eustace先生总结道，“你不能成为其他公司的富庶目标。”同上。根据他的说法，“长期的……正确方法不是将这些情况作为一次性事件来处理，而是采用<em>系统化的方法</em>处理薪酬，使得任何人很难获得更好的要约。”同上（强调为原文所有）。</p>

<p>谷歌在互不挖角协议之前对劳动力市场的影响最好由Meg Whitman（eBay前首席执行官）总结，她致电Schmidt先生“讨论[谷歌的]招聘实践。”电子卷宗编号814-15。正如Eric Schmidt告诉谷歌高级管理人员的那样，Whitman女士说“谷歌是硅谷的谈资，因为[你们]正在推高整体薪资水平。”同上。在这次谈话一年后，谷歌将eBay添加到其“免主动联系”名单中。电子卷宗编号291-28。</p>

<h3 id="3-evidence-related-to-intel">3. 与英特尔相关的证据</h3>

<p>也有令人信服的证据指向英特尔。谷歌对英特尔高管提出的执行互不挖角协议请求的反应，类似于上文讨论的谷歌对Steve Jobs执行协议的请求的反应。例如，在Paul Otellini（英特尔首席执行官兼谷歌董事会成员）于2007年9月26日收到关于谷歌成功招聘英特尔技术人员的内部投诉后，电子卷宗编号188-8（“Paul，我正在失去这么多人去谷歌……我们正在反击，但认为你应该知道。”），Otellini先生将电子邮件转发给了Eric Schmidt（谷歌执行董事长、董事会成员兼前首席执行官），并表示“Eric，你能帮忙解决这个问题吗？？？”同上。Schmidt先生照办了，将电子邮件转发给了他的招聘团队，该团队为Schmidt先生准备了一份关于谷歌活动的报告。电子卷宗编号291-34。第二天，Schmidt先生回复Otellini先生：“如果我们发现有招聘人员主动联系英特尔，我们将解雇该招聘人员”，这与执行苹果-谷歌协议时的补救措施相同。电子卷宗编号531第37页。在另一封发给Schmidt先生的电子邮件中，Otellini先生表示：“很抱歉再次打扰您讨论这个问题，但我的手下对谷歌继续招聘我们的关键人员非常不安。”参见电子卷宗编号428-8。</p>

<p>此外，Otellini先生意识到互不挖角协议可能带来法律麻烦。具体而言，Otellini先生在一封发给另一位英特尔高管的关于谷歌-英特尔协议的电子邮件中表示：“让我澄清一下。我们没有签署任何东西。我们与Eric有一个口头‘不招聘’的约定。我不希望这被广泛知晓。”同上。</p>

<p>此外，有证据表明Otellini先生知道英特尔未参与的其他互不挖角协议。具体而言，谷歌的Sergey Brin（联合创始人）和Schmidt先生作证说，他们会告诉Otellini先生谷歌与苹果有互不挖角协议。电子卷宗编号639-1第74:15页（“我确信我们会提到它。”）；电子卷宗编号819-12第60页（“我确信我在某个时候与Paul谈论过此事。”）。英特尔自己的专家证人作证说，Otellini先生很可能知道谷歌的其他双边协议，因为Otellini先生是谷歌董事会成员。电子卷宗编号771第4页。英特尔与苹果在同一天被添加到谷歌的“免主动联系”名单中，这一事实进一步表明英特尔参与了一个总体协议。电子卷宗编号291-28。</p>

<p>此外，尽管英特尔和谷歌是人才的竞争对手，但Otellini先生从谷歌“拿走”了一份讨论包括苹果和英特尔在内的同行公司奖金计划的谷歌文件。Cisneros Decl., Ex. 463。真正的竞争人才的对手在没有互不竞争协议的情况下不太可能分享如此敏感的奖金信息。</p>

<p>此外，与反垄断影响相关的关键文件也牵连到英特尔。具体而言，英特尔认识到主动联系的重要性，并在其“寻访完整指南”中表示，“主动联系候选人是招聘最有效的方式之一。”电子卷宗编号296-22。英特尔还将薪酬与其他“通常被认为与英特尔相当的科技公司”进行了基准比较，英特尔将这些公司定义为“半导体、软件、网络、通信和多元化计算机公司的混合体”。电子卷宗编号754-2。根据英特尔的说法，在2007年，这些可比公司包括苹果和谷歌。同上。这些文件表明，正如原告所主张的，互不挖角协议导致了集体成员工资的结构性、而非个人性的抑制。</p>

<p>此外，英特尔有一个“薪酬结构”，包含工作级别和工作分类。参见电子卷宗编号745-13第73页（“我们将工作分为三个类别——我们称之为工作族——研发、技术类和非技术类，还有更多……”）。公司根据员工的技能和经验将其分配到一个级别。电子卷宗编号745-11第23页；另见电子卷宗编号749-17第45页（解释说英特尔的每个人都被分配一个类似于工作级别的“分类”）。英特尔在整个公司标准化了工资范围；每个范围适用于多个工作，大多数工作跨越多个工资级别。电子卷宗编号745-16第59页。英特尔进一步将其工资范围细分为四分位数，英特尔的薪酬遵循“钟形曲线分布，大多数员工处于中间四分位数，处于底部和顶部四分位数的比例要小得多。”同上第62-63页。</p>

<p>英特尔还使用软件工具为经理提供员工工资范围的指导，该工具还会考虑市场参考范围和绩效。电子卷宗编号758-9。正如Randall Goodwin（英特尔技术开发经理）所解释的，“如果工具建议了某些内容，而我们认为我们想要提出超出其指导方针的变更，我们会写一些理由。”电子卷宗编号749-15第52页。同样，英特尔定期运行报告，显示其员工的工资范围分布。电子卷宗编号749-16第64页。</p>

<p>证据也支持英特尔工资结构的刚性。例如，在2004年的人力资源演示文稿中，英特尔表示，尽管“薪酬差异化受到英特尔精英管理哲学的期望”，但“短期和长期高绩效者的差异化值得怀疑。”电子卷宗编号758-10第13页。确实，英特尔指出“基于99年数据分析，历史上缺乏差异化。”同上第19页。作为关键的“脆弱性挑战”，英特尔确定：（1）“经理（无）能力在[焦点]区分”——“实际绩效加薪远低于系统生成的加薪”，“长期威胁关键人员的保留”；（2）“几乎没有或完全没有对高绩效者的实际薪酬差异化”；（3）“没有明确的差异化策略。”同上第24页（强调为原文所有）。</p>

<p>此外，英特尔使用内部公平性“来确定新员工和现有员工的工资率，这些工资率对应于每项工作对英特尔的相对价值。”电子卷宗编号749-16第210-11页；电子卷宗编号961-5。为协助该过程，英特尔在向新员工发出录用通知时使用一个生成“内部公平性报告”的工具。电子卷宗编号749-16第212-13页。用Ogden Reid（英特尔薪酬和福利总监）的话说，“我们的文化在很大程度上推崇平等主义……虽然我们在口头上支持精英管理，但我们真的更相信在宽泛的范围内平等对待每个人。”电子卷宗编号769-8。</p>

<p>英特尔一份2002年的人力资源文件（早于互不挖角协议）承认“新雇佣的英特尔员工与收购的英特尔员工之间基本工资/奖金目标的持续不一致”，以及“与准确划分这两类员工的工作级别相关的类似问题。”电子卷宗编号750-15。作为回应，英特尔计划：（1）“审查具有‘关键技能’的工作族的豁免工作级别分配。根据情况进行调整”；（2）“验证不一致的感知……评估对员工的影响。根据情况建议调整。”同上。英特尔一份人力资源文件证实，在2004年左右，“新员工工资溢价导致工资范围调整。”电子卷宗编号298-5第7页（强调为原文所有）。</p>

<p>英特尔会“将英特尔的工作代码和级别与市场调查的工作代码和级别进行匹配”，电子卷宗编号749-16第89页，并将其作为确定其“自身焦点流程或薪酬发放”的一部分，同上第23页。如果工作代码低于中位数，加减一定百分比，公司将进行“特殊市场调整”。同上第90页。</p>

<h3 id="4-evidence-related-to-adobe">4. 与Adobe相关的证据</h3>

<p>来自Adobe的证据也表明Adobe意识到其互不挖角协议的影响。Adobe人员认识到“苹果将是一个值得挖掘的招聘目标”，但知道他们不能这样做，因为“不幸的是，Bruce[Chizen（Adobe前首席执行官）]和苹果首席执行官Steve Jobs有一项君子协议，不挖角对方的人才。”电子卷宗编号291-13。Adobe高管也是进入、执行并试图扩展互不挖角协议的高管团体的一部分。具体而言，Chizen先生在发现苹果正在招聘Macromedia（Adobe后来收购的一家独立公司）的员工后，通过一封发给Jobs先生的电子邮件，确保苹果在Adobe收购Macromedia后，尊重苹果先前与Adobe和Macromedia的互不挖角协议。电子卷宗编号608-3第50页。</p>

<p>Adobe视谷歌和苹果为人才的主要竞争对手，并对Adobe是否“赢得人才战”表示担忧。电子卷宗编号296-3。Adobe进一步认为自己处于“六方竞争的福利地位”，其中包括谷歌、苹果和Intuit作为其他“竞争方”。参见电子卷宗编号296-4。2008年，Adobe将薪酬与包括谷歌、苹果和英特尔在内的九家公司进行了基准比较。电子卷宗编号296-4；另见电子卷宗编号652-6（显示在2010年，Adobe认为Intuit是“直接同行”，并认为苹果、谷歌和英特尔是“参考同行”，尽管Adobe并未实际与这些后几家公司进行薪酬基准比较）。</p>

<p>然而，尽管视其他被告为竞争对手，来自Adobe的证据表明Adobe知道其未参与的其他双边协议。具体而言，Adobe与其他被告分享了机密薪酬信息，尽管Adobe至少将部分其他被告视为竞争对手，且与他们没有双边协议。例如，Intuit和Adobe的人力资源人员交换了标注为“机密”的信息，关于每家公司今年将给予多少薪酬以及给予哪些员工。电子卷宗编号652-8。Adobe和Intuit分享了机密薪酬信息，尽管这两家公司没有双边互不挖角协议，并且Adobe将Intuit视为人才的直接竞争对手。在没有总体协议的情况下，真正的竞争对手不太可能分享如此敏感的薪酬信息。</p>

<p>与此同时，谷歌转发了一封电子邮件，明确讨论其“预算与其他科技公司相当”，并将谷歌的绩效加薪预算增长的精确百分比与Adobe、苹果和英特尔进行了比较。电子卷宗编号807-13。谷歌拥有Adobe绩效加薪预算增长的精确百分比，尽管谷歌和Adobe之间没有双边互不挖角协议。竞争对手之间分享此类敏感薪酬信息是总体协议的进一步证据。</p>

<p>Adobe认识到，在没有互不挖角协议的情况下，需要进行加薪，这呼应了原告的影响理论。例如，出于对一名员工（因其技术技能、智力和协作能力而被视为“明星表现者”）可能离开Adobe的担忧，因为“如果他愿意，他很容易就能在其他地方找到一份好工作”，Adobe考虑如何最好地留住他。电子卷宗编号799-22。在这样做的过程中，Adobe对该员工已经面试了四家公司并与在那里的朋友沟通过表示担忧。同上。因此，Adobe注意到该员工“了解他在市场上的价值”，并且该员工的大学朋友“每年的收入比他多约15,000美元”。同上。作为回应，Adobe决定立即给该员工加薪。同上。</p>

<p>Adobe的证据还支持了原告的影响理论，即Adobe的每个工作职位都被分配了一个工作头衔，每个工作头衔在Adobe的薪酬结构中都有一个相应的工资范围，包括最低、中间和最高工资。参见电子卷宗编号804-17第4页、第8页、第72页、第85-86页。Adobe期望其现有员工的工资分布符合“钟形曲线”。电子卷宗编号749-5第57页。为了帮助经理在设定和调整工资时遵守规定的范围，Adobe有一个在线薪酬规划工具以及薪酬矩阵，这些工具根据市场工资数据为经理提供指导方针。参见电子卷宗编号804-17第29-30页（“[E]ssentially the salary planning tool is populated with employee information for a particular manager, so the employees on their team [sic]. You have the ability to kind of look at their current compensation. It shows them what the range is for the current role that they're in . . . . The tool also has the ability to provide kind of the guidelines that we recommend in terms of how managers might want to think about spending their allocated budget.”）。如果员工低于推荐的最低工资范围，Adobe的做法是“作为年度审查的一部分将其调整至最低标准”并“标记他们”。同上第12页。偏离工资范围也会导致与经理的谈话，Adobe的官员解释道，“我们设置最低标准是有原因的，因为我们认为你需要在这个范围内才能具有竞争力。”同上。</p>

<p>内部公平性在Adobe很重要，正如在其他被告那里一样。正如Debbie Streeter（Adobe总奖励副总裁）所解释的，“Adobe总是将内部公平性作为一个数据点，因为如果你要从外部招聘某人，其薪酬高于现有员工中表现优秀的员工的薪酬，你需要在引入他们之前了解这一点。”电子卷宗编号749-5第175页。同样，在考虑是否发出反要约时，Adobe建议“内部公平性应始终被考虑。”电子卷宗编号746-7第5页。</p>

<p>此外，Donna Morris（Adobe人力资源全球高级副总裁）对“因压缩（市场推高新员工薪酬，高于现有员工）导致的内部公平性表示担忧”。电子卷宗编号298-9（“现实情况是，新员工要求的基本工资达到或超过中位数，因为市场日益激烈。”）。Adobe人员表示，由于预算固定，他们可能无法立即解决问题，“但如果市场保持激烈，可以在2006财年关注[压缩问题]”。<sup id="fnref:12"><a href="#fn:12" rel="noopener noreferrer">11</a></sup>同上。</p>

<h3 id="d-weaknesses-in-plaintiffs-case">D. 原告案件的弱点</h3>

<p>原告辩称，尽管这些证据很有说服力，但原告的案件也存在弱点，使得审判存在风险。原告认为这些风险是重大的。具体而言，原告指出了他们在向陪审团陈述案件时将面临的挑战：（1）说服陪审团在几对被告之间没有互不挖角协议的情况下，认定七名被告之间存在一个总体协议；（2）在被告打算聘请六名经济学家专家攻击原告专家方法论的情况下，证明损害赔偿；（3）克服集体成员的薪酬在过去十年中尽管经济不景气却有所增长的事实，并克服对科技工作者普遍存在的负面情绪，因为集体成员被认为且实际拥有财富。原告还指出了未决法律问题，例如未决的审前排除动议，以及未决的确定应适用本身违法原则还是合理原则的动议，这可能有助于被告证明双边协议具有促进竞争的目的。参见电子卷宗编号938第10-14页。</p>

<p>本庭认识到原告如果继续审判将面临重大风险。尽管如此，考虑到上述证据，本庭不能得出结论认为本次和解金额在合理范围内，特别是与已和解被告的和解相比，以及诉讼的后续发展。本庭进一步注意到，卷宗记录中的证据至少减轻了原告案件的部分弱点。</p>

<p>关于证明一个总体协议，有几项证据削弱了被告关于双边协议彼此无关的论点。重要的是，两个人，Steve Jobs（苹果联合创始人、前董事长兼前首席执行官）和Bill Campbell（Intuit董事会主席、苹果联合首席董事兼谷歌顾问），亲自达成或促成了本案中的每项双边协议。具体而言，Jobs先生和George Lucas（Lucasfilm前董事长兼首席执行官）在Jobs先生是Pixar高管时，制定了Lucasfilm和Pixar之间的初始互不挖角协议。此后，在Jobs先生的领导下，苹果与Pixar达成了一项协议，正如下面将讨论的，Pixar高管将该协议与Lucasfilm-Pixar协议进行了比较。还是Jobs先生，如上所述，联系了Sergey Brin（谷歌联合创始人）和Eric Schmidt（谷歌执行董事长、董事会成员兼前首席执行官）以创建苹果-谷歌协议。该协议是在Campbell先生的协助下达成的，他是Intuit的董事会主席，Jobs先生的朋友，也是谷歌的顾问。苹果-谷歌协议在谷歌董事会会议上被讨论，Campbell先生和Paul Otellini（英特尔首席执行官兼谷歌董事会成员）均出席了会议。电子卷宗编号819-10第47页。在Brin先生与Otellini先生、Schmidt先生与Otellini先生之间讨论后，英特尔被添加到谷歌的“免主动联系”名单中。Campbell先生随后利用他在谷歌的影响力，成功游说谷歌将Intuit（Campbell先生是其董事会主席）添加到谷歌的“免主动联系”名单中。参见电子卷宗编号780-6第8-9页。此外，在Jobs先生达成苹果-谷歌协议仅两个月后，苹果就向Bruce Chizen（Adobe前首席执行官）施压，要求其达成苹果-Adobe协议。电子卷宗编号291-17。正如本讨论所示，Jobs先生和Campbell先生是与所谓协议每个环节的形成关系最密切的个人，因为他们参与了每个环节的形成过程。</p>

<p>考虑到这个谈判和执行互不挖角协议的小团体高管的重叠性，这些高管知道其公司未参与的其他双边协议也就不足为奇了。例如，谷歌的Brin先生和Schmidt先生都作证说，他们会告诉英特尔的Otellini先生谷歌与苹果有互不挖角协议。电子卷宗编号639-1第74:15页（“我确信我们会提到它。”）；电子卷宗编号819-12第60页（“我确信我在某个时候与Paul谈论过此事。”）。英特尔自己的专家证人作证说，Otellini先生很可能知道谷歌的其他双边协议，因为Otellini先生是谷歌董事会成员。电子卷宗编号771第4页。此外，谷歌招聘人员知道Adobe-苹果协议。同上（谷歌招聘人员的备注，即苹果有“针对Adobe的严格的‘免打扰’政策”）。此外，谷歌的Schmidt先生作证说，基于对Jobs先生的了解，可以“合理推断”Jobs先生“会将[互不挖角协议]扩展到其他人”。电子卷宗编号638-8第170页。此外，正是这批高管成功且不成功地试图将该协议扩展到硅谷的其他公司，如eBay、Facebook、Macromedia和Palm，如上所述，这表明这些协议既非孤立也非一次性协议。</p>

<p>此外，六个双边协议包含几乎相同的条款，禁止每对被告主动招揽对方的任何员工。电子卷宗编号531第30页。此外，正如上文讨论的，被告认识到这些协议的相似性。例如，谷歌将苹果、英特尔和Intuit归入其“免主动联系”名单。此外，谷歌的“免主动联系”名单指出，苹果-谷歌协议和英特尔-谷歌协议在同一天开始生效。最后，在一封电子邮件中，Lori McAdams（Pixar人力资源和行政副总裁）明确比较了互不挖角协议，称“从现在开始，我们将遵循与Lucasfilm协议类似的君子协议与苹果合作。”电子卷宗编号531第26页。</p>

<p>关于原告必须反驳被告关于互不挖角协议有助于合作因此具有促进竞争作用的论点，没有任何文件证据将互不挖角协议与任何合作联系起来。记录合作的文件中没有一份提及广泛的互不挖角协议，记录广泛互不挖角协议的文件中也没有一份提及合作。此外，即使是被告的专家也承认，最接近合作的人并不知道互不挖角协议。电子卷宗编号852-1第8页。此外，被告的高管自己也承认缺乏任何合作目的。例如，Adobe的Chizen先生承认Adobe-苹果互不挖角协议“不仅限于苹果和Adobe正在合作的特定项目。”电子卷宗编号962-7第42页。此外，美国司法部（“DOJ”）也认定这些互不挖角协议“并非附属于任何合法的合作”，“比为任何合作努力的形成或实施所合理需要的范围更广”，并且“扰乱了劳动力市场中适用的正常价格设定机制。”电子卷宗编号93-1 ¶ 16；电子卷宗编号93-4 ¶ 7。司法部认定被告达成的协议是根据反垄断法本身即为非法的贸易限制。电子卷宗编号93-1 ¶ 35；电子卷宗编号93-4 ¶ 3。因此，尽管被告自诉讼开始以来一直声称互不挖角协议与合作有关联，具有促进竞争的目的，尽管所谓的合作是被告即决判决动议的核心，但被告未能提供令人信服的证据表明这些互不挖角协议与合作有关或具有促进竞争作用。</p>

<h3 id="iv-conclusion">四、结论</h3>

<p>本庭与本案已相伴近三年，在此期间，本庭不仅审查了大量实体动议，还审查了大量密封请求的文件。在这样做之后，本庭不能得出结论认为本次和解在合理范围内。正如本庭在即决判决命令中所述，有充分证据表明七名被告之间存在一个总体协议，包括“各种协议的相似性、达成和执行协议的少数交织的高管、被告对其他协议的了解、被告之间甚至没有双边互不挖角协议的公司之间机密薪酬信息的分享和基准比较，以及被告对互不挖角协议的扩展和试图扩展。”电子卷宗编号771第7-8页。此外，正如上文讨论的以及本庭在集体认证命令中讨论的，被告僵化的工资结构和内部公平性考虑的证据，加上被告自己高管的陈述，很可能在证明互不挖角协议的影响方面具有说服力：即集体范围的工资抑制。</p>

<p>鉴于这些证据，本庭对本次与剩余被告的和解金额按比例低于与已和解被告的和解金额感到不安。这一担忧因案件自那些和解以来朝着对原告有利的方向发展而加剧。在那些和解达成时，被告仍然可以在此法院击败集体认证，被告仍然可以成功寻求上诉审查并推翻任何集体认证，被告仍然可以在即决判决中获胜，或者被告仍然可以在排除原告主要专家的尝试中成功。相反，本次和解是在开庭前仅一个月达成的，此时被告的这些机会已经消失。尽管审判的不可预测性无疑会给原告带来挑战，但被告的敞口更大，无论是超过90亿美元的潜在损害赔偿，还是其他附带后果，包括本命令中讨论的证据以及将被揭露的其他证据和证词所受到的关注。诉讼地位和临近审判应该会增加原告的议价能力，而不是减少，比起一年前与已和解被告达成和解时。</p>

<p>本庭承认集体律师一直是集体的积极倡导者，并且自己出资进行这场诉讼，对抗资源极其丰富的对手。此外，原告的案件中很可能存在律师无法向本庭透露的弱点和挑战。尽管如此，本庭认为，剩余被告至少应该支付其公平份额，与已和解被告相比，后者在被告对原告拥有更大筹码的诉讼阶段解决了案件。</p>

<p>基于上述理由，本庭驳回原告对与剩余被告和解方案的初步批准动议。本庭进一步将案件管理会议定于2014年9月10日下午2点举行。</p>

<p>特此命令。</p>

<pre><code>日期：2014年8月8日
LUCY H. KOH
美国地区法官
</code></pre>
<div>

<hr>

<ol>
<li id="fn:1">Leamer博士在最初的集体认证动议中遭到了激烈攻击，本庭同意被告关于Leamer博士的部分主张，因此驳回了最初的集体认证动议。参见电子卷宗编号382第33-43页。
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:2">被告的审前排除动议、原告排除某些专家证词的动议、被告排除某些专家证词的动议、确定应适用本身违法原则还是合理原则的动议，以及强制开示动议，在达成和解时均悬而未决。
 <a href="#fnref:2" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:3">本动议中的原告表示，其中两封信函来自非集体成员，第三封信函来自一名可能撤回异议的集体成员。参见电子卷宗编号920第18页注11。在本命令发出时，异议尚未撤回。
 <a href="#fnref:3" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:4">Devine在反对意见中表示，该反对意见旨在取代他先前发送给本庭的信函。参见电子卷宗编号934注2。本庭未收到Devine的任何信函。因此，本庭仅考虑了Devine的反对意见。
 <a href="#fnref:4" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:5">原告还声称和解的行政管理成本为16万美元。
 <a href="#fnref:5" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:6">Devine计算出集体成员平均将获得3,573美元。该数字与本庭计算之间的差异可能是由于Devine的计算未考虑已有147人退出集体。本庭的计算结果是从和解金额（3.245亿美元）中减去请求的律师费（8112.5万美元）、费用（120万美元）、</li></ol></div><p><em>由 mimo-v2.5 模型翻译，花费 35873 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/google-wage-fixing/</link>
      <guid isPermaLink="false">https://danluu.com/google-wage-fixing/</guid>
      <pubDate>Thu, 14 Aug 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Verilog 赢了，VHDL 输了？——由你评判！</title>
      <description>[AI 摘要] 该文章详细描述了1997年一场Verilog与VHDL设计竞赛的过程与结果，旨在让读者自行评判两种语言的优劣。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 该文章详细描述了1997年一场Verilog与VHDL设计竞赛的过程与结果，旨在让读者自行评判两种语言的优劣。</div><p><i>这是 John Cooley 于 1997 年发布的一篇 USENET 存档文章，对比了 VHDL 和 Verilog 的竞争表现。</i></p>

<p>我知道我触动了某些人的神经。通常当我发布对特定会议或 EDA 产品的坦率评论时，我的电子邮箱“收件箱”里大约会收到85封回复。在我对最近的 Synopsys 用户组会议的评论中，我非常简要地报告说：在会议设计竞赛中，9名Verilog设计者中有8名成功完成了设计，而<em>没有</em>一名VHDL设计者成功。我对之前的简略表示了歉意，并承诺将在以后对设计竞赛做详细报告。文章发布后，我的电子邮箱“收件箱”简直成了 Verilog/VHDL 的贝鲁特战场，塞满了169封回复！一旦有风声泄露说详细的设计竞赛报告将发表在《集成系统设计》（原名《ASIC &amp; EDA》杂志）的 DAC 特刊上，我就开始接到 VHDL 国际组织主席 Mahendra Jain 和开放 Verilog 国际组织主席 Bill Fuchs 的电话。随后，一队受雇的说客（也就是所谓的公关代表）也打来了更多电话。当 VHDL 专栏作家 Larry Saunders 向《集成系统设计》的主编索取我的设计竞赛报告的提前副本时，我勃然大怒。他觉得我“准备对 VHDL 进行猛烈抨击”，想要写一篇反驳文章跟在我的文章后面……而所有这一切发生时，我还没开始写<em>哪怕一个字</em>！</p>

<p>因为我是一名独立顾问，依靠培训和从事<em>两种</em>HDL 的工作谋生，我可不想经历一场 VHDL 的塞勒姆审巫案，被公开指责与魔鬼秘密勾结来推广 Verilog。因此，我将呈现设计竞赛中发生的<em>一切</em>，无论好坏，然后由<em>你</em>来评判！在法庭证据结束时，我会请你们，陪审团，写一封电子邮件回复，我将在我的专栏里、在后续的《集成系统设计》中发布。</p>

<h4 id="the-unexpected-results">出乎意料的结果</h4>

<p>参赛者被给予90分钟时间，使用 Verilog 或 VHDL 来创建一个门级网表，设计一个最快的、完全同步的、可加载的9位加3减5上下计数器，该计数器需产生偶校验、进位和借位信号。</p>

<p>在竞赛的9名 Verilog 设计者中，只有1人未能得到最终的门级网表，因为他尝试编码一个超前进位校验生成器。在剩下的8人中，有3人的网表在功能测试向量上失败。5名获得完全功能的门级设计的 Verilog 设计者是：</p>

<pre>   Larry Fiedler     NVidea               3.90 nsec     1147 gates
   Steve Golson      Trilobyte Systems    4.30 nsec     1909 gates
   Howard Landman    HaL Computer         5.49 nsec     1495 gates
   Mark Papamarcos   EDA Associates       5.97 nsec     1180 gates
   Ed Paluch         Paluch &amp; Assoc.      7.85 nsec     1514 gates
</pre>

<p>令人惊讶的是，在同一时间内，竞赛中的5名 VHDL 设计者<em>无一人</em>能完成任何门级设计。</p>

<h4 id="not-vhdl-newbies-vs-verilog-pros">并非 VHDL 新手对决 Verilog 高手</h4>

<p>我从那些 VHDL 狂热者（他们并未参加竞赛）那里得到的第一个反应是：“嗯，这显然是 Verilog 老手碾压几个 VHDL 新手的情况。没什么大不了的。”嗯，他们说对了一部分。许多 Verilog 设计者确实非常擅长他们的工作——但 VHDL 设计者也同样出色！</p>

<p>我认识 LSI Logic 的 Prasad Paranjpe 很多年了。他曾在硅谷中心的加州大学圣克鲁兹分校推广部教授并至今仍在教授 VHDL 和综合课程。他曾是硅谷 VHDL 本地用户组的副主席。自1987年以来，他一直是一名全职 ASIC 设计师，并自1990年起就使用 VHDL 和 Synopsys（从1.3c版本开始）设计<em>真正的</em> ASIC。Prasad 的家庭电子邮件地址是“vhdl@ix.netcom.com”，家庭电话是 (XXX) XXX-VHDL。ASIC 设计师 Jan Decaluwe 在 Alcatel 以及后来作为欧洲 ASIC 设计公司 Easics 的创始人期间，一直为 ESNUG 发表富有洞察力的 VHDL 和综合帖子。（他们的公司格言：“Easics - VHDL 设计公司”。）另一位来自 LSI Logic/VHDL 的参赛者 Vikram Shrivastava，自1992年以来就一直在使用 VHDL/Synopsys 设计方法。这些人可不是新手！</p>

<h4 id="creating-the-contest">设计竞赛</h4>

<p>我采用双盲方式来组织这次设计竞赛。也就是说，我不仅让 Seva Technologies 的知名 VHDL 专栏作家 Larry Saunders 和 Verilog 专栏作家 Yatin Trivedi 对设计竞赛进行评论——他们彼此不知情，我还让来自 Synopsys 的 VHDL 方向的方法管理经理 Ken Nelsen 和来自 Ford Microelectronics 的 Verilog 设计师 Jeff Flieder 帮助检查设计竞赛是否存在概念或实施上的缺陷。</p>

<p>我设计竞赛时最初的考虑是避免 Synopsys Design Compiler 只需放置一个 DesignWare 部件就能快速完成设计的情况。然而，我也不想让参赛者尝试（并失败）设计一些奇怪的、不切实际的、没人真正理解的东西。因此，我限定了“标准”设计，这些设计所有工程师都熟悉——但加入了一些奇特的参数，以将 DesignWare 排除在外。我要求的不是简单的上下计数器，而是一个加3减5的计数器。位宽不是常见的8位，而是所有部分都是9位。</p>

<pre>                                  recycled COUNT_OUT [8:0]
                     o---------------&lt;---------------&lt;-------------------o
                     |                                                   |
                     V                                                   |
               -------------                     --------                |
  DATA_IN --&gt;-|   up-by-3   |-&gt;-----carry-----&gt;-| D    Q |-&gt;- CARRY_OUT  |
   [8:0]      |  down-by-5  |-&gt;-----borrow----&gt;-| D    Q |-&gt;- BORROW_OUT |
              |             |                   |        |               |
       UP --&gt;-|    logic    |                   |        |               |
     DOWN --&gt;-|             |-o-------&gt;---------| D[8:0] |               |
               -------------  | new_count [8:0] | Q[8:0] |-&gt;-o----&gt;------o
                              |                 |        |   |
                 o------&lt;-----o        CLOCK ---|&gt;       |   o-&gt;- COUNT_OUT
                 |                               --------           [8:0]
 new_count [8:0] |     -----------
                 |    |   even    |              --------
                 o--&gt;-|  parity   |-&gt;-parity--&gt;-| D    Q |-&gt;- PARITY_OUT
                      | generator |   (1 bit)   |        |
                       -----------           o--|&gt;       |
                                             |   --------
                                   CLOCK ----o


图1) 概述设计功能的基本框图
</pre>

<p>加入偶校验、进位和借位要求是为了给参赛者一些空间来进行重大的架构权衡，这些权衡可能意味着胜利与失败的区别。</p>

<p>当 UP 和 DOWN 同时为“低”时，计数器加载数据；当 UP 和 DOWN 同时为“高”时，计数器保持状态——这与世界上99%的可加载计数器的传统做法完全相反。</p>

<pre>                  UP  DOWN   DATA_IN    |    COUNT_OUT    
                 -----------------------------------------
                   0    0     valid     |   load DATA_IN
                   0    1   don't care  |     (Q - 5)
                   1    0   don't care  |     (Q + 3)
                   1    1   don't care  |   Q unchanged


图 2) 加载和上下计数规格。所有 I/O 事件发生在 CLOCK 的上升沿。
</pre>

<p>为了进一步增加趣味性，我选择使用 LSI Logic 300K ASIC 库，因为线负载和线延迟在这项技术中是一个重要因素。拥有“本地库”优势的一位精明的 VHDL 设计者，LSI Logic 的 Prasad Paranjpe，聪明地询问是否需要默认的线负载模型（他想使用零线负载模型以节省时序）。我回答说：“好尝试。是的，需要默认的线负载模型。”</p>

<p>为了让重点放在设计而非验证上，参赛者获得了由 Yatin Trivedi 和 Larry Saunder 的 Seva Technologies 提供的等效 Verilog 和 VHDL 测试平台。这些测试平台向参赛者创建的 Verilog/VHDL 源代码发送相同的18个测试向量，如果通过，就其竞赛目的而言，他们的设计被判定为“功能正确”。</p>

<p>对于 VHDL，参赛者可以选择使用 Synopsys VSS 3.2b 和/或 Cadence Leapfrog VHDL 2.1.4；对于 Verilog，参赛者可以选择使用 Cadence Verilog-XL 2.1.2 或 Chronologic VCS 2.3.2 以及各自的 Verilog/VHDL 设计环境。（Model Technology Inc. 的首席执行官 Bob Hunter 过于偏执于 Synopsys 员工可能看到他的 VHDL，因此不允许其参与竞赛。）LSI Logic 的库是 LCB 300K rev 3.1A.1.1.101。</p>

<p>我担心有些设计者可能不知道异或归约树是生成校验位的方式——但 Larry、Yatin、Ken 和 Jeff 都一致认为，任何不知道这一点的工程师都不应该被帮助赢得设计竞赛。作为最后时刻的提示，我在每个参赛者的目录中放入了一个“xor.readme”文件，其中列出了 LSI 300K 库中可用的两个异或门（EO 和 EO3）及其驱动强度和端口列表。</p>

<p>为了对综合友好，我让设计者保留了 Synopsys 不切实际的默认设置：所有输入具有无限的输入驱动强度，所有输出驱动零负载。</p>

<p>竞赛在同一天分三个时段进行。为了保持公平，我在这些时段中的指导原则是：刻意<em>不</em>修复/改进<em>任何东西</em>——无论多么令人沮丧！</p>

<p>在所有这些完成之后，Larry 和 Yatin 认为设计竞赛会太简单，而 Ken 和 Jeff 认为它难度正好合适。我询问他们四人是否看到竞赛中存在 Verilog 或 VHDL 特有的“陷阱”；四人都断然表示“没有”。</p>

<h4 id="murphy-s-law">墨菲定律</h4>

<p>竞赛一开始，墨菲定律——“事情如果有变坏的可能，不管这种可能性有多小，它总会发生”——就生效了。因为我们直到竞赛开始前可怕的3天才拿到 SUN 和 HP 工作站，我在安装所有 Verilog、VHDL、Synopsys 和 LSI 库时经历了一场噩梦般的连锁反应。在机器 ID 确定之前，没人能为软件生成许可证密钥——而直到竞赛开始前2天才确定机器ID！（事实上，我不得不放弃 HP 机器，因为大多数 EDA 供应商无法像为 SUN 工作站生成密钥那样快速地为 HP 机器生成密钥。）</p>

<p>LSI 300K 库直到竞赛开始前一小时才送到。Seva 的人在竞赛开始前15分钟发现并修复了 Verilog 测试平台（该问题在 VHDL 测试平台中不存在）中的一个 bug。</p>

<p>在第一阶段设计进行到大约50分钟时，一名工程师的机器崩溃了——而这恰好是所有 Verilog 仿真软件的许可证服务器！（幸运的是，此时所有 Verilog 设计者都已深入综合阶段。）不幸的是，这位机器崩溃的可怜设计者因为之前了解设计问题，不被允许在后续时段重新参赛。这台机器被重启，并在竞赛剩余时间内仅用作许可证服务器。</p>

<p>后勤噩梦再次露出狰狞的面目，因为两名设计者天真地问道：“John，你的 Synopsys 手册在哪里？”我内心尖叫：“哦天哪！哦天哪！哦天哪！”；外表却平静地回答：“这里没有任何软件的手册。你们必须使用可用的在线文档。”</p>

<p>当我意识到 LSI 库人员带来的八本数据手册中，有六本并非针对我们正在使用的<em>确切</em> LCB 300K 库时，更多小妖魔在我脑中跳舞——这些数据手册对于任何试图手工构建异或归约树的人至关重要——而一名 Verilog 参赛者刚刚浪费了宝贵的十分钟阅读了一本误导性的数据手册！（有两本 LCB 300K，一本 LCA 300K 和五本 LEA 300K 数据手册。）来自 HaL Computer 的 Verilog 设计师 Howard Landman 指出：“我可能浪费了15分钟试图解决这个问题，最后放弃并直接编写了功能校验——不过我使用了括号，希望 Synopsys 使用3输入异或门。”</p>

<p>然后，就在情况不能更糟的时候，大家发现当 Synopsys 的 Design Compiler 在一个新账户中首次运行时——它需要大约10到15分钟来为你构建你自己的 DesignWare 缓存。Verilog 参赛者、顾问 Ed Paluch 指出：“我以为第一次综合运行构建[此处删除了脏话] DesignWare 缓存<em>永远</em>不会结束！感觉像过了几天！”</p>

<p>尽管在我看来，这些头痛问题都没有损害竞赛的公正性，但在当时，我不得不不断提醒自己：“为了保持公平，无论多么令人沮丧，我都<em>不能</em>修复或改进<em>任何东西</em>。”</p>

<h3 id="judging-the-results">评判结果</h3>

<p>因为我不想评判源代码的<em>意图</em>，所有评判仅基于门级网表是否通过前面描述的18个测试向量。完成后，将设计读入 Synopsys Design Compiler，并移除所有约束。然后我应用命令“clocks_at 0, 6, 12 clock”，然后根据“report_timing -path full -delay max -max_paths 12”确定的最长路径作为比较设计的最终依据——确定来自 NVidia 的 Verilog 设计师 Larry Fiedler 以1147门、3.90纳秒的设计获胜。</p>

<pre>      reg [9:0] cnt_up, cnt_dn;   reg [8:0] count_nxt;

      always @(posedge clock)
      begin
        cnt_dn = count_out - 3'b 101;  // synopsys label add_dn
        cnt_up = count_out + 2'b 11;   // synopsys label add_up

        case ({up,down})
           2'b 00 : count_nxt = data_in;
           2'b 01 : count_nxt = cnt_dn;
           2'b 10 : count_nxt = cnt_up;
           2'b 11 : count_nxt = 9'bX;  // SPEC NOT MET HERE!!!
          default : count_nxt = 9'bX;  // avoiding ambiguity traps
        endcase

        parity_out  &lt;= ^count_nxt;
        carry_out   &lt;= up &amp; cnt_up[9];
        borrow_out  &lt;= down &amp; cnt_dn[9];
        count_out   &lt;= count_nxt;
      end


图 3) 获胜的 Verilog 源代码。（注意它未能满足当 UP 和 DOWN 同时为高时保持状态的规格要求。）
</pre>

<p>由于评判向任何想在场的人开放，Verilog 参赛者、备受尊敬的设计顾问 Kurt Baty 发出了双重惊讶的质疑，因为他知道自己的设计速度相当，但未能通过18个测试向量。（Kurt 是我的好朋友——我真的很享受就这个发现取笑他——尤其是他曾向许多人吹嘘他将赢得这场竞赛！）现场调查显示，Kurt 在竞赛最后一分钟意外保存了错误的设计。进一步的调查还发现，18个测试向量并未完全覆盖计数器的所有指定条件。Larry 的“获胜”门级 Verilog 设计未能满足当 UP 和 DOWN 同时为高时保持状态的规格要求——尽管他的设计成功通过了18个测试向量！</p>

<p>如果人工目视检查 Verilog/VHDL 源代码，以主观判断测试向量可能遗漏的地方是评判标准的一部分，那么 Verilog 设计师 Steve Golson 将获胜。我再次重申，根据定义，所有通过测试平台向量的设计都被视为“功能正确”。</p>

<h4 id="what-the-contestants-thought">参赛者的看法</h4>

<p>尽管 NASA 的 VHDL 设计师 Jeff Solomon 表示“我不喜欢将计数器的传统概念扭曲来设计竞赛问题的想法”，但其余12位参赛者都非常喜欢这个加3减5、9位、可加载、同步计数器的架构灵活性，该计数器具有偶校验、进位和借位功能。Verilog 设计师 Mark Papamarcos 总结了大多数人的意见：“我认为这个问题设计得相当好。存在潜在的资源共享问题，有一些机会将某些逻辑调度为与其他逻辑同时评估等。我第一次看到它时，以为实现会非常容易，我会有大量时间进行优化。我也注意到顶层目录中的2输入和3输入异或门，猜想这可能与什么有关，但当我在使向量匹配时遇到问题时，很快就放弃了任何巧妙的想法。”</p>

<p>有11位参赛者被已知校验位与加减奇数之间的明显关联所诱惑。只有一位 Verilog 设计者，来自惠普加拿大的 Oren Rubinstein，承诺采用此策略，但严重超时。回家后，Kurt Baty 在概念上帮助 Oren 完成了他的设计，而 Prasad Paranjpe 帮助完成了最终的综合。大约花了7小时脑力工作和8小时编码/仿真/综合时间（总共15小时）才得到3.05纳秒、1988门的最终设计。观察到花费了最初估计1.5小时的10倍时间才获得22%的速度提升，Oren 评论道：“就像现实生活中一样，不可能创建准确的工程设计时间表。”</p>

<p>两名 VHDL 设计师，LSI Logic 的 Prasad Paranjpe 和 Easics 的 Jan Decaluwe，都抱怨不得不处理 VHDL 中的类型转换。Prasad 坦言：“我简直不敢相信我会因为一个简单的类型错误而卡住。我使用了 IEEE std_logic_arith，它需要使用 unsigned 和 signed 子类型，而不是 std_logic_unsigned。”Jan 同意并补充道：“我遇到了 VHDL 或 VSS 的问题（我仍然不确定。）这个 case 语句无法分析：‘subtype two_bits is unsigned(1 downto 0); case two_bits'(up &amp; down)...’。但起作用的是：‘case two_bits'(up, down)...’。最后，我通过先将连接赋值给一个辅助变量解决了这个问题。”</p>

<p>Verilog 竞争者 Steve Golson 概述了大多数 Verilog 参赛者采用的“先获得一个工作设计，然后在综合中调整它”的策略：“我记得有一些愚蠢的拼写错误耽误了我；另外我在校验和进位/借位方面遇到了困难。一旦我有了一个功能正确的基线设计，我就开始修改它以进行最优综合。我的基本思路是将设计分成四个独立的模块：加法器、4:1多路选择器、异或逻辑（校验和进位/借位）以及仅包含触发器和其他三个模块实例的顶层计数器模块。我的策略是首先单独编译这三个（纯组合的）子模块。我对每个子模块使用简单的‘max_delay 0 all_outputs()’约束。顶层模块获得正确的时钟约束。然后‘dont_touch’这些设计，并编译顶层计数器模块（这仅构建触发器）。然后为了清理，我执行了‘ungroup -all’，接着是‘compile -incremental’（这使我的关键路径减少了将近1纳秒。）”</p>

<p>拼写错误和恐慌损害了许多参赛者的性能。来自国家半导体的 Verilog 设计师 Daryoosh Khalilollahi 说：“我以为我不能按时完成，但我刚好赶上了。我浪费了一些时间，因为我会遇到一个 Verilog 语法错误，这是因为我 Verilog ‘include’文件中有一个额外的、不需要的文件（verilog -f include）。”同样，来自 Hal Computers 的 Verilog 设计师 Howard Landman 从未意识到他的 Verilog 源代码中既包含完整的行为级<em>又</em>包含完整的手工实例化的校验树。（Synopsys Design Compiler 只优化掉了 Howard 的一个冗余校验树！）</p>

<p>平均而言，每位 Verilog 设计者在时间用尽前成功完成了两到五次综合运行。只有两名 VHDL 设计者，Jeff Solomon 和 Jan Decaluwe，成功启动（但未完成）了一次综合运行。在这两种情况下，我都因他们未能按时完成而取消了他们的参赛资格，但让他们的综合运行尝试完成。Jan 到得有点晚，所以我们在取消他的资格前给了他额外一些时间。他的未完成运行在21分钟后不得不被终止，因为另一组参赛者即将到达。（顺便说一句，由于我的失误，我意外地给了第三时段额外的6分钟设计时间。这个时段没有 Verilog 设计者，但 VHDL 设计者 Jeff Solomon、Prasad Paranjpe、Vikram Shrivastava 以及来自 Texas Instruments 的 Ravi Srinivasan 都受益于这个错误。）由于 Jeff 在最后一个时段，我给了他所有需要的时间来完成他的运行。在额外的17分钟（总计）后，他产生了一个门级设计，计时结果为15.52纳秒。在又过了28分钟后，他将时序降到了4.46纳秒，但他的设计未能通过功能向量测试。他的 VHDL 源代码中某处有错误。</p>

<p>失败的 Verilog 设计者 Kurt Baty 总结道：“John，我期待明年的设计竞赛，无论它采取何种形式或风格，也期待一个重振我名誉的机会。”</p>

<h4 id="closing-arguments-to-the-jury">致陪审团的结案陈词</h4>

<p>VHDL 狂热者可能在这个审判中提出的结案陈词可能是：“14名工程师所做的事情在统计上并不显著。即使是组织这场设计竞赛的人也承认最后时刻出现了各种问题。你们的工作站崩溃了，没有手册，LSI 数据手册有误导。测试向量不完整。一位关键的 VHDL 设计师在他的时段迟到后遇到了 Synopsys VHDL 仿真器的 bug。赢得这场竞赛的 Verilog 设计甚至没有完全满足规格！此外，这场竞赛并非旨在作为 Verilog 和 VHDL 哪种语言更好的公投——因此可能遗漏一些主要问题。”</p>

<p>Verilog 狂热者可能会结案陈词：“没有工程师能在为理想比较 Verilog 和 VHDL 所希望的人为条件下工作。十四名工程师可能在统计上不显著，但哪里有烟，哪里就有火。我在这里看到了工程师在日常设计中遇到的所有经典问题。我们都处理过工作站崩溃、版本控制不佳、工具 bug、规划不周和测试不完整。正因为这些现实，我认为这场设计竞赛<em>完美</em>地衡量了每种 HDL 在现实生活中的表现。而 Verilog 轻松获胜！”</p>

<p>陪审团的裁决将在下一期《集成系统设计》中公布。</p>

<h4 id="you-the-jury">你，陪审团……</h4>

<p>你，陪审团，现在被要求花十分钟思考你刚刚阅读的内容，并在150字或更少的篇幅内，将你的想法发送到“jcooley@world.std.com”。请不要给我发“VHDL 很烂。”或“Verilog 必须死！！！”——而是发送个人经历和/或观察，以增加讨论深度。可以有强烈/激烈的观点，但要用比热空气更有分量的东西来支持。（因为我不想陷入追逐授权的麻烦，默认设置是<em>无论</em>你发送给我什么，都可以完全出版。如果你希望发送包含可出版和不可出版材料的信件，请清楚标明哪些是哪些。）我不仅会重印回复的信件，还会发布有多少人报告了每种具体观点/经验的统计数据。</p>

<p><i>John Cooley<br>
兼职 EDA 消费者倡导者<br>
全职 ASIC、FPGA 和 EDA 设计顾问</i></p>

<p>附言：回复时，请注明你的职位、公司、你使用 Verilog 还是 VHDL、原因以及使用了多久。另外，请不要将这篇文章复制发回给我——我知道你为什么回复！:^)</p><p><em>由 mimo-v2.5 模型翻译，花费 12205 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/verilog-vs-vhdl/</link>
      <guid isPermaLink="false">https://danluu.com/verilog-vs-vhdl/</guid>
      <pubDate>Thu, 14 Aug 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>数据驱动的错误发现</title>
      <description>[AI 摘要] 本文探讨了通过分析用户行为数据来自动发现软件缺陷的方法，借鉴了硬件测试的实践。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文探讨了通过分析用户行为数据来自动发现软件缺陷的方法，借鉴了硬件测试的实践。</div><p><a href="https://danluu.com/everything-is-broken/" rel="noopener noreferrer">我已经记不清上一次整天没有遇到软件缺陷是什么时候了</a>。有好几周，由于一个缺陷导致邀请按钮在邀请屏幕上不显示，我无法邀请任何人参加Facebook活动。自从我搬到一个小城市后，谷歌地图一直给我非法且有时不可能的路线。而谷歌文档在我粘贴图片时经常挂起，显示一个忙碌图标，直到我删除图片。</p>

<p>缺陷逃过测试是可以理解的。测试很难。集成测试更难。端到端测试尤其困难。但有一个更简单的方法。像这样我每天遇到的缺陷中，有三分之一可以通过分析自动发现。</p>

<p></p>

<p>如果你觉得用分析来找缺陷听起来奇怪，问问硬件工程师关于性能计数器的事情。无论用户是否可访问，每个ASIC都有分析功能，以帮助设计者了解下一代芯片需要做出哪些更改。因为人们无论如何都会查看性能计数器，所以当<a href="http://acg.cis.upenn.edu/papers/micro05_storeq.pdf" rel="noopener noreferrer">转发路径</a>从未被使用，<a href="http://infoscience.epfl.ch/record/135571/files/micro01-way.pdf" rel="noopener noreferrer">路径预测</a>有奇怪分布，或预取缓冲区从未填满时，他们会注意到。<a href="https://danluu.com/discontinuities/" rel="noopener noreferrer">分析中的意外分布</a>表明存在误解，这通常是缺陷的标志<sup id="fnref:A"><a href="#fn:A" rel="noopener noreferrer">1</a></sup>。</p>

<p>Facebook记录所有用户操作。这可用于确定用户的死胡同。谷歌地图在“错误”转弯后重新路由。这可用于确定错误转弯是否源于糟糕的路线。谷歌文档可以跟踪所有撤销操作<sup id="fnref:U"><a href="#fn:U" rel="noopener noreferrer">2</a></sup>。这可用于确定用户何时遇到功能错误或缺陷<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">3</a></sup>。</p>

<p>我理解为什么从硬件实践借鉴到软件开发可能感觉奇怪。在大多数情况下，硬件工具比软件工具落后几十年。例如：当前的硬件工具包括在Linux上的模拟器，这些模拟器只部分从Windows移植过来，导致某些文本框需要正斜杠而其他需要反斜杠；在使用<code>default_nettype none</code>时无法编译的库<sup id="fnref:2"><a href="#fn:2" rel="noopener noreferrer">4</a></sup>；以及需要支持工程师的组件，因为它们被认为太容易出错，没有全职人员支持就无法工作。</p>

<p>但在测试方面，硬件远超软件。当我编写软件时，模糊测试被认为是最先进的技术。但在硬件领域，模糊测试没有特殊名称。它只是测试，为什么需要为“使用随机性的测试”起一个特殊名称？这就像为“通过运行代码测试”起一个名字。十多年前，我通过一个使用约束随机输入、符号执行和通过结构分析减少状态的工具进行<a href="http://en.wikipedia.org/wiki/POWER6" rel="noopener noreferrer">硬件测试</a>。对于小型单元，该工具能够生成形式正确性证明。对于大型单元，该工具自动生成并使用覆盖统计数据，并利用它们尽可能彻底地搜索多样化的状态空间。如果出现缺陷，会产生一个简短且易于调试的反例。此后，硬件测试工具已经有了很大改进。</p>

<p>但在软件领域，如果我随机选择一个想贡献的项目，它有测试就已经很幸运了。当测试存在时，通常是手写的，带有其固有的所有限制。偶尔，我很惊喜地发现一个软件项目使用<a href="http://hackage.haskell.org/package/QuickCheck" rel="noopener noreferrer">测试框架</a>，其功能只有十年前芯片设计中标准工具的1%。</p>

<p>考虑到<a href="//danluu.com/testing/" rel="noopener noreferrer">硬件缺陷与软件缺陷</a>的相对成本，更多努力投入硬件测试并不太令人惊讶。但这里有一个几乎不需要额外努力的情况。你已经拥有分析来衡量通过各种用户漏斗的转化率。这里唯一的新想法是，点击广告或进行购买不是唯一应该衡量的转化类型。在路口遵循路线是一种转化，粘贴图片后不立即删除是一种转化，打开模态对话框后使用它也是一种转化。</p>

<p>当然，无论是广告点击转化率还是缓存命中率，盲目优化单个数字会让你陷入局部最优，长期来看会损害你，而为应发送警报的转化率设置阈值并非易事。用户操作的空间组合巨大，因此需要明智地使用机器学习来确定合理的阈值。这需要时间和精力。但想想你投入优化点击的所有努力。你可能在几年前就发现<a href="http://www.kalzumeus.com/2009/03/07/how-to-successfully-compete-with-open-source-software/" rel="noopener noreferrer">用巨大的煎饼按钮替换无聊的文本可以给你3倍的点击率</a>；现在你正在优化1%和2%的提升。这很棒，表明你已经抓住了所有唾手可得的果实。但当你遇到一个阻止模态对话框任何进展的严重缺陷时，未来的点击率会如何？</p>

<p>如果这听起来工作量很大，找一个你已知并修复的缺陷，然后在你的日志数据中搜索遇到该缺陷的用户。通过提供严重损坏的产品疏远这些用户，对点击率的影响比一个难以找到的结账按钮大得多，而导致你拥有那个巨大结账按钮的相同过程也可以解决你的其他问题。众所周知，增加200毫秒的加载时间可能导致20%的用户关闭窗口。你认为让他们接触一个需要5,000毫秒用户交互来修复的缺陷会有什么影响？</p>

<p>如果这值得修复，拿出<a href="https://github.com/twitter/scalding" rel="noopener noreferrer">scalding</a>、<a href="http://research.google.com/pubs/pub36632.html" rel="noopener noreferrer">dremel</a>、<a href="http://cascalog.org/" rel="noopener noreferrer">cascalog</a>，或任何你最喜欢的数据处理工具。开始寻找没有意义的用户操作。开始寻找缺陷。</p>

<p><small> 感谢Pablo Torres捕捉到本文中的一个拼写错误 </small></p>
<div>

<hr>

<ol>
<li id="fn:A">并非所有芯片设计团队都系统地这样做（尽管他们应该这样做），但人们无论如何都会查看数字，并会看到异常。
 <a href="#fnref:A" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:U">撤销不仅仅是字面意义上的撤销；粘贴图片后因显示永远忙碌而删除它也算。
 <a href="#fnref:U" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn1"><p>这比听起来更糟。除了在文档中永远显示忙碌图标外，它还会使该会话与服务器断开连接，这可以通过另一种方式检测：如果某个用户操作总是随后断开连接，那就非常可疑。</p>

<p>此外，这两种故障模式本可以通过模糊测试发现，因为它们绝不应该发生。缺陷已经足够难以发现，纵深防御是唯一合理的解决方案。</p>
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn2">如果你和硬件工程师交谈，请称之为验证而不是测试，否则他们会认为你谈论的是DFT、测试硅片以检测制造缺陷，或其他软件中没有类似物的奇怪事情。
 <a href="#fnref:2" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 5098 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/bugalytics/</link>
      <guid isPermaLink="false">https://danluu.com/bugalytics/</guid>
      <pubDate>Sun, 6 Apr 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>编辑二进制文件</title>
      <description>[AI 摘要] 本文介绍在特定情况下（如非标准CPU或闭源软件缺陷）直接编辑二进制文件的必要性、方法及实例。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文介绍在特定情况下（如非标准CPU或闭源软件缺陷）直接编辑二进制文件的必要性、方法及实例。</div>编辑二进制文件是一项一年用上几次的小技巧。虽然不常需要，但一旦需要，便无可替代。当我提到修补二进制文件时，人们通常会有两种反应：完全震惊或毫无反应。在我看来，这是因为大多数人持有以下两种世界观之一：

<blockquote>
<ol>
<li><p>源代码存在。编译器对源代码进行某些处理使其可运行。若修改源代码，则会产生不同的效果。</p></li>

<li><p>处理器存在。处理器接收一些比特并解码它们以执行操作。若修改这些比特，则会产生不同的效果。</p></li>
</ol>
</blockquote>

<p>如果你持第一种观点，使用十六进制编辑器修改程序就是疯子的行为。如果你持第二种观点，编辑二进制文件则是世界上最自然的事情。为什么不直接编辑二进制文件呢？这通常是你达到目的的最简单方式。</p>

<p>例如，如果你使用<a href="http://forum.osdev.org/viewtopic.php?f=1&amp;t=26351" rel="noopener noreferrer">非Intel非AMD的x86处理器</a>，你就经常必须这样做。程序不会检查<a href="http://www.sandpile.org/x86/cpuid.htm" rel="noopener noreferrer">CPUID特性标志</a>，而是通过检查CPUID系列、型号和步进来确定特性，这导致在非标准CPU上出现错误行为。有时你必须进行编辑才能让程序使用最新的SSE指令，有时你必须进行编辑才能让程序运行。你可以尝试提交bug报告，但<a href="https://code.google.com/p/nativeclient/issues/detail?id=2508" rel="noopener noreferrer">直接编辑你的二进制文件要容易得多</a>。</p>

<p>即使你在主流的Intel CPU上运行，当遇到<a href="http://blogs.msdn.com/b/oldnewthing/archive/2012/11/13/10367904.aspx?Redirected=true" rel="noopener noreferrer">闭源软件中的缺陷</a>时，这些技巧也很有用。此外还有紧急情况。</p>

<p>前几天，我在一家中型初创公司工作的一位DevOps朋友告诉我，他们曾将一个内部alpha版本发布为外部版本，导致其自动更新机制将所有用户正常的二进制文件替换为一个有缺陷的实验版本。他们花了一分钟就弄清楚了发生了什么。更新会在几天内逐步推送给所有用户，这意味着坏版本只扩散到了 <code>1 / (60*24*2) = 0.03%</code> 的用户。但他们无法将旧版本推送到自动更新器中，因为客户端只接受更高版本号的更新。他们不得不经历整个构建和发布流程（耗时一小时），只为了发布一个与他们最后一个正常版本完全相同的版本。如果有人想到编辑二进制文件以增加版本号，他们本可以在一分钟而不是一小时内推送一个正常的更新，这将使问题只扩散到不超过 <code>0.06%</code> 的用户，而不是向 <code>2%</code> 的用户发送一个损坏的更新<sup id="fnref:E"><a href="#fn:E" rel="noopener noreferrer">1</a></sup>。</p>

<p>这并不像听起来那么难。我们来看一个例子。如果你打算经常做这类事情，你可能想使用像<a href="https://hex-rays.com/products/ida/support/download_freeware.shtml" rel="noopener noreferrer">IDA</a><sup id="fnref:D"><a href="#fn:D" rel="noopener noreferrer">2</a></sup>这样的真正反汇编器。但是，如果你只是偶尔需要这样做，简单的工具也能应付。我碰巧在一台不用于开发的Mac上，因此我将使用<a href="http://lldb.llvm.org/" rel="noopener noreferrer">lldb</a>进行反汇编，并使用<a href="http://ridiculousfish.com/hexfiend/" rel="noopener noreferrer">HexFiend</a>编辑此示例。Gdb、otool和objdump也适用于快速简便的反汇编。</p>

<p>这是一个简单的代码片段 <code>wat-arg.c</code>，应该很容易进行二进制编辑：</p>

<pre><code>#include &lt;stdio.h&gt;

int main(int argc, char **argv) {
  if (argc &gt; 1) {
    printf("got an arg\n");
  } else {
    printf("no args\n");
  }
}
</code></pre>

<p>如果我们编译这个文件，然后在二进制文件上启动lldb并单步进入main函数，我们可以看到以下机器码：</p>

<pre><code>$ lldb wat-arg
(lldb) breakpoint set -n main
Breakpoint 1: where = original`main, address = 0x0000000100000ee0
(lldb) run
(lldb) disas -b -p -c 20
;  address       hex opcode            disassembly
-&gt; 0x100000ee0:  55                    pushq  %rbp
   0x100000ee1:  48 89 e5              movq   %rsp, %rbp
   0x100000ee4:  48 83 ec 20           subq   $32, %rsp
   0x100000ee8:  c7 45 fc 00 00 00 00  movl   $0, -4(%rbp)
   0x100000eef:  89 7d f8              movl   %edi, -8(%rbp)
   0x100000ef2:  48 89 75 f0           movq   %rsi, -16(%rbp)
   0x100000ef6:  81 7d f8 01 00 00 00  cmpl   $1, -8(%rbp)
   0x100000efd:  0f 8e 16 00 00 00     jle    0x100000f19               ; main + 57
   0x100000f03:  48 8d 3d 4c 00 00 00  leaq   76(%rip), %rdi            ; "got an arg\n"
   0x100000f0a:  b0 00                 movb   $0, %al
   0x100000f0c:  e8 23 00 00 00        callq  0x100000f34               ; symbol stub for: printf
   0x100000f11:  89 45 ec              movl   %eax, -20(%rbp)
   0x100000f14:  e9 11 00 00 00        jmpq   0x100000f2a               ; main + 74
   0x100000f19:  48 8d 3d 42 00 00 00  leaq   66(%rip), %rdi            ; "no args\n"
   0x100000f20:  b0 00                 movb   $0, %al
   0x100000f22:  e8 0d 00 00 00        callq  0x100000f34               ; symbol stub for: printf
</code></pre>

<p>正如预期的那样，我们加载一个值，用 <code>cmpl   $1, -8(%rbp)</code> 将其与1进行比较，然后根据比较结果的跳转方向打印 <code>got an arg</code> 或 <code>no args</code>。</p>

<pre><code>$ ./wat-arg
no args
$ ./wat-arg 1
got an arg
</code></pre>

<p>如果我们打开十六进制编辑器，将 <code>81 7d f8 01 00 00 00; cmpl   1, -8(%rbp)</code> 改为 <code>81 7d f8 06 00 00 00; cmpl   6, -8(%rbp)</code>，这将导致程序检查6个参数而不是1个。</p>

<p><img src="https://danluu.com/images/edit-binary/edit-binary.png" alt="Replace cmpl with cmpl 6"></p>

<pre><code>$ ./wat-arg
no args
$ ./wat-arg 1
no args
$ ./wat-arg 1 2
no args
$ ./wat-arg 1 2 3 4 5 6 7 8
got an arg
</code></pre>

<p>很简单！如果你多做几次，很快就会养成用 <code>90</code><sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">3</a></sup> 打补丁覆盖内容的习惯。例如，如果我们把 <code>0f 8e 16 00 00 00; jle</code> 和 <code>e9 11 00 00 00; jmpq</code> 替换为 <code>90</code>，我们得到以下结果：</p>

<pre><code>   0x100000ee1:  48 89 e5              movq   %rsp, %rbp
   0x100000ee4:  48 83 ec 20           subq   $32, %rsp
   0x100000ee8:  c7 45 fc 00 00 00 00  movl   $0, -4(%rbp)
   0x100000eef:  89 7d f8              movl   %edi, -8(%rbp)
   0x100000ef2:  48 89 75 f0           movq   %rsi, -16(%rbp)
   0x100000ef6:  81 7d f8 01 00 00 00  cmpl   $1, -8(%rbp)
   0x100000efd:  90                    nop
   0x100000efe:  90                    nop
   0x100000eff:  90                    nop
   0x100000f00:  90                    nop
   0x100000f01:  90                    nop
   0x100000f02:  90                    nop
   0x100000f03:  48 8d 3d 4c 00 00 00  leaq   76(%rip), %rdi            ; "got an arg\n"
   0x100000f0a:  b0 00                 movb   $0, %al
   0x100000f0c:  e8 23 00 00 00        callq  0x100000f34               ; symbol stub for: printf
   0x100000f11:  89 45 ec              movl   %eax, -20(%rbp)
   0x100000f14:  90                    nop
   0x100000f15:  90                    nop
   0x100000f16:  90                    nop
   0x100000f17:  90                    nop
   0x100000f18:  90                    nop
   0x100000f19:  48 8d 3d 42 00 00 00  leaq   66(%rip), %rdi            ; "no args\n"
   0x100000f20:  b0 00                 movb   $0, %al
   0x100000f22:  e8 0d 00 00 00        callq  0x100000f34               ; symbol stub for: printf
</code></pre>

<p>请注意，由于我们用单字节指令替换了几个多字节指令，程序现在总共有更多指令。</p>

<pre><code>$ ./wat-arg
got an arg
no args
</code></pre>

<p>其他常见技巧包括用 <code>cc</code> 打补丁重定向到中断处理器，用 <code>db</code> 导致调试断点，知道要翻转哪个比特来改变比较或跳转的极性等。所有这些细节都在<a href="http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html" rel="noopener noreferrer">Intel架构手册</a>中有记载，但学习这些最容易的方法是一次性培养对它们的肌肉记忆。</p>

<p>祝你玩得开心！</p>
<div>

<hr>

<ol>
<li id="fn:E"><p>我实际上不建议在紧急情况下，如果你以前没做过的话，就这样做。推送一个已知的、会泄露未来版本细节的损坏二进制文件是坏事，但推送一个破坏你更新程序的更新则更糟。你至少需要几个能在睡梦中创建二进制补丁的人来对更改进行代码审查，以确保它看起来没问题，即使是在测试客户端上运行之后。</p>

<p>另一个解决方案，虽然不那么“好”，但危险性要小得多，那就是禁用更新服务器，直到新版本准备好。</p>
 <a href="#fnref:E" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:D">如果你没有1000美元，<a href="https://github.com/radare/radare2" rel="noopener noreferrer">r2</a>是一个不错的、免费的工具，具有类似IDA的功能。
 <a href="#fnref:D" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:1">在x86架构上
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 7436 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/edit-binary/</link>
      <guid isPermaLink="false">https://danluu.com/edit-binary/</guid>
      <pubDate>Sun, 23 Mar 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>那篇虚假的性别差距文章</title>
      <description>[AI 摘要] 该文驳斥了科技行业无性别薪资差距的流行说法，指出相关文章误读和选择性引用研究数据，忽视了持续存在的真实差距。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 该文驳斥了科技行业无性别薪资差距的流行说法，指出相关文章误读和选择性引用研究数据，忽视了持续存在的真实差距。</div><p>上周，《Quartz》发表了一篇题为<a href="https://news.ycombinator.com/item?id=7334659" rel="noopener noreferrer">“科技行业薪资不存在性别差距”</a>的文章。这引发了互联网上从不起眼的博客到<a href="http://www.smithsonianmag.com/smart-news/female-computer-scientists-make-same-salary-their-male-counterparts-180949965/?no-ist" rel="noopener noreferrer">《史密森尼》杂志网站</a>等各处的模仿性标题党文章。这些论断听起来异常肯定，但考虑到它们引用的主要研究仅针对恰好一年前获得学士学位的毕业生，更不用说该研究实际上得出了完全相反的结论。</p>

<p>让我们来看看所有这些文章都引用的<a href="http://www.aauw.org/files/2013/02/graduating-to-a-pay-gap-the-earnings-of-women-and-men-one-year-after-college-graduation.pdf?_ga=1.7578036.722397424.1379578621" rel="noopener noreferrer">AAUW研究</a>中的证据。</p>

<p><img src="https://danluu.com/images/gender-gap/actual_result.png" alt="你是相信我，还是相信你亲眼所见的事实？" width="626" height="390"></p>

<p></p>

<p>看起来，在“工程与工程技术”领域，女性的收入是男性的88%，在“计算机与信息科学”领域，则是男性的77%。</p>

<p>该研究控制了多种因素，试图找出薪资差距的来源。研究发现，在控制了自报工作时间、就业类型和学校质量等因素后，“超过三分之一的薪资差距无法用这些因素中的任何一个来解释，似乎仅归因于性别本身”。三分之一并非零，也不是12%或23%的三分之一。如果这听起来很小，请考虑一下2008年后经济中的平均加薪幅度，以及这23%的三分之一相当于多少年的经验。</p>

<p>《Quartz》的文章声称，既然整个差距可以被某些变量解释，那么这种差距就是个人选择造成的。事实上，该研究明确指出这种观点是错误的，并引用了<a href="http://scholar.google.com/scholar_case?case=2721168065773704326" rel="noopener noreferrer">Stender诉Lucky Stores案</a>和一项<a href="http://ideas.repec.org/a/bpj/evoice/v4y2007i4n5.html" rel="noopener noreferrer">相关研究</a><sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">1</a></sup>，指出“该案例说明了在薪资差距的‘可解释部分’中，歧视如何发挥作用：雇主错误地假设女性员工更偏好传统上由女性担任的薪酬较低的职位，并——有意或无意地——将男性和女性分配到不同的工作岗位，从而确保男性获得更高薪酬而女性获得更低薪酬”。女性实际上并非只是想要低薪工作；这与《Quartz》文章中的论断再次截然相反。</p>

<p>请注意，该研究有选择性地控制了缩小薪资差距的因素，但没有控制那些可能扩大差距的因素。例如，研究指出“女性在大学里的平均成绩高于男性，因此学术成就无法帮助我们理解性别薪资差距”。调整成绩因素会扩大薪资差距；如果调整所有可能的混淆因素，而不仅仅是缩小差距的因素，那么调整后的薪资差距只会更大。</p>

<p>AAUW研究并非《Quartz》文章引用的唯一证据。为了支持“尽管有强有力的证据表明性别薪酬平等，但人们普遍认为女性收入低于男性”这一结论，《Quartz》作者引用了另外三份证据。首先，美国劳工统计局（BLS）的数据显示，“按小时而非年薪计算，男女薪资差距为14%，而非23%”；但14%并非0%。其次，引用了一份<a href="http://www.bls.gov/cps/cpswom2012.pdf?_ga=1.211369494.241714935.1393863612" rel="noopener noreferrer">BLS报告</a>，该报告总体显示男性收入高于女性，但作者仅挑选了女性表现优于男性的一组数据（“每周工作30至39小时的女性……见表4”）；这一说法是错误的<sup id="fnref:2"><a href="#fn:2" rel="noopener noreferrer">2</a></sup>。第三，引用了一项80年代的研究，但其结论被2012年的AAUW报告直接反驳；该早期研究认为队列效应是性别差距的原因，但AAUW报告通过研究单一队列仍然发现了性别差距。</p>

<p>《史密森尼》杂志在收到批评后发布了一篇更正声明，但大多数被误导的文章仍未更正。</p>

<p>很明显，《Quartz》这篇文章的作者心中自有议程，挑选了支持该议程的证据，并写了一篇博客文章。许多博主未经核实引文就转发了这篇文章，并以其论点作为标题党来为自己的网站吸引点击量。如果这就是所谓的“数字原生新闻”的运作方式，我选择退出。</p>

<p><strong>如果你喜欢阅读这篇文章，可能也会喜欢<a href="//danluu.com/tech-discrimination/" rel="noopener noreferrer">这篇关于市场与歧视相互作用的文章</a>，以及<a href="//danluu.com/teach-debugging/" rel="noopener noreferrer">这篇简要解释为何这么多人从科学与工程领域退出的文章</a>。</strong></p>

<h3 id="updates">更新</h3>

<p><em>更新：更正已发布！我避免直接链接到原文作者，因为我认为经常出现的推特人身攻击和猎巫行动没有建设性，这真的是关于</em>什么是<em>对的，而不是</em>谁是<em>对的。作者显然不同意，因为在屏蔽该作者之前，我收到了无休止的侮辱。</em></p>

<p><em>Charlie Clarke好心地无视那些谩骂，解读了作者唯一一个具体的关于我“文盲”的指控，即脚注3并非由110.3四舍五入到111。结果发现，文章作者不仅没有从110.3四舍五入到111，而且完全引用了错误的来源，而另一个来源恰好有一个与111相近的数字。</em></p>
<div>

<hr>

<ol>
<li id="fn:B">这项研究中有很多好消息。过去四十年里，性别差距已经大大缩小。值得写一篇细致的文章来探讨为什么情况有所改善，为什么某些方面改善了而另一些方面却仍然顽固地停留在70年代水平。我非常期待读到这样的文章。
 <a href="#fnref:B" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:2">《Quartz》的文章声称“每周工作30至39小时的女性收入是男性的111%（见表4）”。表4是兼职工作者的分解数据。表中没有任何地方显示111%，除非是将110.3%四舍五入到111%；也许作者指的是表中按种族划分的数据，该数据显示在亚裔兼职工作者中，女性的小时工资是男性的110.3%。请注意，表3显示了全职工作者（占绝大多数）的分解数据，表明女性全职工作的收入远低于男性。为了找到一个支持其议点的数据，作者不仅必须查看兼职工作者，而且必须只查看亚裔女性兼职工作者，然后将0.3%向上取整为1%。
 <a href="#fnref:2" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 3976 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/gender-gap/</link>
      <guid isPermaLink="false">https://danluu.com/gender-gap/</guid>
      <pubDate>Sun, 9 Mar 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>那次甲骨文试图解雇测评其数据库的教授</title>
      <description>[AI 摘要] 1983年，威斯康星大学的教授发布数据库基准测试显示甲骨文性能极差，据称导致其创始人拉里·埃里森试图解雇该教授，并催生了软件行业广泛存在的禁止公开发布基准测试结果的“德威特条款”。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 1983年，威斯康星大学的教授发布数据库基准测试显示甲骨文性能极差，据称导致其创始人拉里·埃里森试图解雇该教授，并催生了软件行业广泛存在的禁止公开发布基准测试结果的“德威特条款”。</div><p>1983年，在威斯康星大学，迪娜·比特顿、大卫·德威特和卡罗琳·特比菲尔创建了一个<a href="http://pages.cs.wisc.edu/~dewitt/includes/benchmarking/vldb83.pdf" rel="noopener noreferrer">数据库基准测试框架</a>。他们的一些结果如下（数值越低越好）：</p>

<p>无索引连接</p>



<table>
<thead>
<tr>
<th>系统</th>
<th>joinAselB</th>
<th>joinABprime</th>
<th>joinCselAselB</th>
</tr>
</thead>

<tbody>
<tr>
<td>U-INGRES</td>
<td>10.2</td>
<td>9.6</td>
<td>9.4</td>
</tr>

<tr>
<td>C-INGRES</td>
<td>1.8</td>
<td>2.6</td>
<td>2.1</td>
</tr>

<tr>
<td>ORACLE</td>
<td>&gt; 300</td>
<td>&gt; 300</td>
<td>&gt; 300</td>
</tr>

<tr>
<td>IDMnodac</td>
<td>&gt; 300</td>
<td>&gt; 300</td>
<td>&gt; 300</td>
</tr>

<tr>
<td>IDMdac</td>
<td>&gt; 300</td>
<td>&gt; 300</td>
<td>&gt; 300</td>
</tr>

<tr>
<td>DIRECT</td>
<td>10.2</td>
<td>9.5</td>
<td>5.6</td>
</tr>

<tr>
<td>SQL/DS</td>
<td>2.2</td>
<td>2.2</td>
<td>2.1</td>
</tr>
</tbody>
</table>

<p>使用主索引（聚簇索引）的连接</p>

<table>
<thead>
<tr>
<th>系统</th>
<th>joinAselB</th>
<th>joinABprime</th>
<th>joinCselAselB</th>
</tr>
</thead>

<tbody>
<tr>
<td>U-INGRES</td>
<td>2.11</td>
<td>1.66</td>
<td>9.07</td>
</tr>

<tr>
<td>C-INGRES</td>
<td>0.9</td>
<td>1.71</td>
<td>1.07</td>
</tr>

<tr>
<td>ORACLE</td>
<td>7.94</td>
<td>7.22</td>
<td>13.78</td>
</tr>

<tr>
<td>IDMnodac</td>
<td>0.52</td>
<td>0.59</td>
<td>0.74</td>
</tr>

<tr>
<td>IDMdac</td>
<td>0.39</td>
<td>0.46</td>
<td>0.58</td>
</tr>

<tr>
<td>DIRECT</td>
<td>10.21</td>
<td>9.47</td>
<td>5.62</td>
</tr>

<tr>
<td>SQL/DS</td>
<td>0.92</td>
<td>1.08</td>
<td>1.33</td>
</tr>
</tbody>
</table>

<p>使用次索引（非聚簇索引）的连接</p>

<table>
<thead>
<tr>
<th>系统</th>
<th>joinAselB</th>
<th>joinABprime</th>
<th>joinCselAselB</th>
</tr>
</thead>

<tbody>
<tr>
<td>U-INGRES</td>
<td>4.49</td>
<td>3.24</td>
<td>10.55</td>
</tr>

<tr>
<td>C-INGRES</td>
<td>1.97</td>
<td>1.80</td>
<td>2.41</td>
</tr>

<tr>
<td>ORACLE</td>
<td>8.52</td>
<td>9.39</td>
<td>18.85</td>
</tr>

<tr>
<td>IDMnodac</td>
<td>1.41</td>
<td>0.81</td>
<td>1.81</td>
</tr>

<tr>
<td>IDMdac</td>
<td>1.19</td>
<td>0.59</td>
<td>1.47</td>
</tr>

<tr>
<td>DIRECT</td>
<td>10.21</td>
<td>9.47</td>
<td>5.62</td>
</tr>

<tr>
<td>SQL/DS</td>
<td>1.62</td>
<td>1.4</td>
<td>2.66</td>
</tr>
</tbody>
</table>

<p>投影操作（去除重复元组）</p>

<table>
<thead>
<tr>
<th>系统</th>
<th>100/10000</th>
<th>1000/10000</th>
</tr>
</thead>

<tbody>
<tr>
<td>U-INGRES</td>
<td>64.6</td>
<td>236.8</td>
</tr>

<tr>
<td>C-INGRES</td>
<td>26.4</td>
<td>132.0</td>
</tr>

<tr>
<td>ORACLE</td>
<td>828.5</td>
<td>199.8</td>
</tr>

<tr>
<td>IDMnodac</td>
<td>29.3</td>
<td>122.2</td>
</tr>

<tr>
<td>IDMdac</td>
<td>22.3</td>
<td>68.1</td>
</tr>

<tr>
<td>DIRECT</td>
<td>2068.0</td>
<td>58.0</td>
</tr>

<tr>
<td>SQL/DS</td>
<td>28.8</td>
<td>28.0</td>
</tr>
</tbody>
</table>

<p>无索引聚合操作</p>

<table>
<thead>
<tr>
<th>系统</th>
<th>MIN 标量</th>
<th>MIN 聚合函数 100部分</th>
<th>SUM 聚合函数 100部分</th>
</tr>
</thead>

<tbody>
<tr>
<td>U-INGRES</td>
<td>40.2</td>
<td>176.7</td>
<td>174.2</td>
</tr>

<tr>
<td>C-INGRES</td>
<td>34.0</td>
<td>495.0</td>
<td>484.4</td>
</tr>

<tr>
<td>ORACLE</td>
<td>145.8</td>
<td>1449.2</td>
<td>1487.5</td>
</tr>

<tr>
<td>IDMnodac</td>
<td>32.0</td>
<td>65.0</td>
<td>67.5</td>
</tr>

<tr>
<td>IDMdac</td>
<td>21.2</td>
<td>38.2</td>
<td>38.2</td>
</tr>

<tr>
<td>DIRECT</td>
<td>41.0</td>
<td>227.0</td>
<td>229.5</td>
</tr>

<tr>
<td>SQL/DS</td>
<td>19.8</td>
<td>22.5</td>
<td>23.5</td>
</tr>
</tbody>
</table>

<p>使用索引聚合操作</p>

<table>
<thead>
<tr>
<th>系统</th>
<th>MIN 标量</th>
<th>MIN 聚合函数 100部分</th>
<th>SUM 聚合函数 100部分</th>
</tr>
</thead>

<tbody>
<tr>
<td>U-INGRES</td>
<td>41.2</td>
<td>186.5</td>
<td>182.2</td>
</tr>

<tr>
<td>C-INGRES</td>
<td>37.2</td>
<td>242.2</td>
<td>254.0</td>
</tr>

<tr>
<td>ORACLE</td>
<td>160.5</td>
<td>1470.2</td>
<td>1446.5</td>
</tr>

<tr>
<td>IDMnodac</td>
<td>27.0</td>
<td>65.0</td>
<td>66.8</td>
</tr>

<tr>
<td>IDMdac</td>
<td>21.2</td>
<td>38.0</td>
<td>38.0</td>
</tr>

<tr>
<td>DIRECT</td>
<td>41.0</td>
<td>227.0</td>
<td>229.5</td>
</tr>

<tr>
<td>SQL/DS</td>
<td>8.5</td>
<td>22.8</td>
<td>23.8</td>
</tr>
</tbody>
</table>

<p>无索引选择操作</p>

<table>
<thead>
<tr>
<th>系统</th>
<th>100/10000</th>
<th>1000/10000</th>
</tr>
</thead>

<tbody>
<tr>
<td>U-INGRES</td>
<td>53.2</td>
<td>64.4</td>
</tr>

<tr>
<td>C-INGRES</td>
<td>38.4</td>
<td>53.9</td>
</tr>

<tr>
<td>ORACLE</td>
<td>194.2</td>
<td>230.6</td>
</tr>

<tr>
<td>IDMnodac</td>
<td>31.7</td>
<td>33.4</td>
</tr>

<tr>
<td>IDMdac</td>
<td>21.6</td>
<td>23.6</td>
</tr>

<tr>
<td>DIRECT</td>
<td>43.0</td>
<td>46.0</td>
</tr>

<tr>
<td>SQL/DS</td>
<td>15.1</td>
<td>38.1</td>
</tr>
</tbody>
</table>

<p>使用索引选择操作</p>

<table>
<thead>
<tr>
<th>系统</th>
<th>100/10000 聚簇</th>
<th>100/10000 聚簇</th>
<th>100/10000</th>
<th>1000/10000</th>
</tr>
</thead>

<tbody>
<tr>
<td>U-INGRES</td>
<td>7.7</td>
<td>27.8</td>
<td>59.2</td>
<td>78.9</td>
</tr>

<tr>
<td>C-INGRES</td>
<td>3.9</td>
<td>18.9</td>
<td>11.4</td>
<td>54.3</td>
</tr>

<tr>
<td>ORACLE</td>
<td>16.3</td>
<td>130.0</td>
<td>17.3</td>
<td>129.2</td>
</tr>

<tr>
<td>IDMnodac</td>
<td>2.0</td>
<td>9.9</td>
<td>3.8</td>
<td>27.6</td>
</tr>

<tr>
<td>IDMdac</td>
<td>1.5</td>
<td>8.7</td>
<td>3.3</td>
<td>23.7</td>
</tr>

<tr>
<td>DIRECT</td>
<td>43.0</td>
<td>46.0</td>
<td>43.0</td>
<td>46.0</td>
</tr>

<tr>
<td>SQL/DS</td>
<td>3.2</td>
<td>27.5</td>
<td>12.3</td>
<td>39.2</td>
</tr>
</tbody>
</table>

<p>如果您熟悉1983年的数据库领域，当时，<code>INGRES</code>是斯通布雷克和黄在伯克利的一个研究项目，后来已商业化。<code>C-INGRES</code>是商业版本，<code>U-INGRES</code>是大学版本。<code>IDM*</code>是<code>IDM/500</code>数据库机器，第一台广泛使用的商用数据库机器；<code>dac</code>表示带有“数据库加速器”，<code>nodac</code>表示没有。<code>DIRECT</code>是德威特于1977年启动的一个数据库机器研究项目。</p>

<p>在比特顿等人的研究中，甲骨文的性能表现异常糟糕。</p>

<p>拉里·埃里森对结果不满意，据说他曾<a href="https://starcounter.com/the-story-of-professor-dewitt/" rel="noopener noreferrer">试图</a><a href="http://www.citeulike.org/user/marclijour/article/6883245" rel="noopener noreferrer">解雇德威特</a>。鉴于在教授存在实际不当行为时解雇他们都已非常困难，埃里森成功让某人因在其领域进行合法研究而被解雇的概率几乎为零。据说，在德威特未被解雇之后，拉里禁止甲骨文雇佣威斯康星大学的毕业生，甲骨文还在其最终用户许可协议中添加了一项禁止发布基准测试结果的条款。多年来，许多主要的商业数据库供应商都增加了许可条款，使其数据库的基准测试变得非法。</p>



<p>如今，甲骨文会雇佣威斯康星大学的毕业生，但甲骨文仍然禁止对其数据库进行基准测试。甲骨文惊人的糟糕性能和拉里·埃里森的回应已成为历史；反基准测试条款现在通常被称为“德威特条款”，并且已从数据库扩展到所有软件，从<a href="https://news.ycombinator.com/item?id=7306121" rel="noopener noreferrer">编译器</a>到云服务<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">1</a></sup>。</p>

<p>与此同时，比特币用户创建了<a href="http://rt.com/news/bitcoin-assassination-market-anarchist-983/" rel="noopener noreferrer">匿名暗杀市场</a>——用户可以将钱放入一个资金池，该池会支付给刺杀特定目标的刺客。</p>

<p>匿名暗杀市场似乎是个笑话，那么匿名基准测试市场呢？想知道数据库在特定工作负载下能提供何种性能的人可以将钱放入一个资金池，然后支付给运行基准测试的人。</p>

<p>在当前的情况下，您经常会看到评论和博客文章，说某人原本使用<code>postgres</code>，但管理层让他们切换到“某个商业数据库”，而该数据库性能差得多，并且很难判断那个糟糕的数据库是甲骨文、微软SQL服务器，还是另一个数据库。</p>

<p>如果我们看看今天主要的商业数据库，三大商业数据库中有两个禁止发布基准测试结果。微软的SQL服务器最终用户许可协议中写道：</p>

<blockquote>
<p>未经微软事先书面批准，您不得披露任何基准测试结果……</p>
</blockquote>

<p>甲骨文写道：</p>

<blockquote>
<p>未经甲骨文事先同意，您不得披露任何程序基准测试结果。</p>
</blockquote>

<p>IBM 值得注意的是它实际上允许基准测试：</p>

<blockquote>
<p>被许可方可以向任何第三方披露对程序或其子组件的任何基准测试结果，前提是被许可方 (A) 公开披露基准测试中使用的完整方法（例如，硬件和软件设置、安装过程和配置文件），(B) 使用 IBM 或第三方提供的该程序的最新适用更新、补丁和修复程序，在指定操作环境中运行被许可方的基准测试程序，并且 (C) 遵循程序文档和 IBM 针对该程序的支持网站上提供的任何性能调整和“最佳实践”指南……</p>
</blockquote>

<p>这为人们提供了一个论据，即 IBM 可能比甲骨文或微软提供更好的性能，因为它是唯一一家不害怕人们发布基准测试结果的公司，但如果我们能有实际数据就更好了。</p>

<p><i>感谢丽娅·汉森和内森·沃尔斯的评论、修正和讨论。</i></p>
<div>

<hr>

<ol>
<li id="fn:1"><p>至少有一项云服务不仅禁止发布基准测试结果，甚至禁止“竞争性基准测试”——运行基准测试以查看竞争对手的表现。因此，有一个产品，有人建议我不要使用它，以避免任何不当的表象，因为我在办公室与从事云相关基础设施工作的人一起工作。</p>

<p>此类条款的一个例子是 Salesforce 协议中的以下条款：</p>

<blockquote>
<p>您不得出于监控服务可用性、性能或功能的目的，或出于任何其他基准测试或竞争目的访问服务。</p>
</blockquote>

<p>如果您曾经想知道为什么像 cloudharmony 这样的正常运行时间“基准测试”服务不包括 Salesforce，这很可能就是原因。您有时会推测 Salesforce 和其他有这些条款的公司知道他们的服务非常糟糕，以至于拥有公开基准测试结果比让其害怕公开基准测试结果更糟糕。</p>
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 8684 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/anon-benchmark/</link>
      <guid isPermaLink="false">https://danluu.com/anon-benchmark/</guid>
      <pubDate>Wed, 5 Mar 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>为什么学校不教调试？</title>
      <description>[AI 摘要] 作者通过亲身经历指出，工程教育普遍忽视教授系统性调试技能，这是导致许多学生失败的关键原因。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 作者通过亲身经历指出，工程教育普遍忽视教授系统性调试技能，这是导致许多学生失败的关键原因。</div><p>2000年秋天，我参加了第一门工程课程：<a href="http://courses.engr.wisc.edu/ece/ece352.html" rel="noopener noreferrer">ECE 352</a>，一门面向一年级计算机工程师的数字设计入门课。教室里挤得连站的地方都没有，满是候补学生，他们会在学期中因有人退选而找到座位。我们在迎新会上就被告知，一半的人撑不过这一年。上课时，我们再次被警告一半人注定会不及格，而ECE 352正是造成大量淘汰的关键课程。</p>

<p>课程进度很快。第一节课几乎没花时间讲教学大纲，迅速进入实质内容。后续课程都建立在前一课的基础上；如果有人没掌握前一课，就跟不上后面的。项目在两周后开始，同样也是层层递进；如果有人没完成前一个项目，就别指望完成下一个。</p>

<p>我和一位朋友不明白为什么有些人学得这么吃力；这些内容看起来就像常识。我们只需要用<a href="http://c2.com/cgi/wiki?FeynmanAlgorithm" rel="noopener noreferrer">费曼方法</a>就够了。</p>

<blockquote>
<ol>
<li>把问题写下来</li>
<li>拼命思考</li>
<li>写下答案</li>
</ol>
</blockquote>

<p>费曼方法在最后一个项目上让我们栽了跟头：设计一个<a href="http://i.stanford.edu/pub/cstr/reports/csl/tr/95/675/CSL-TR-95-675.pdf" rel="noopener noreferrer">除法器</a>，这是一个真实规模、比我们之前遇到的任何问题都复杂一个数量级的项目。布置项目的那天，教授力劝我们早点开始。接下来的几周，我们听到传言说有些同学没日没夜地工作，却毫无进展。</p>

<p>但在项目截止前一晚6点之前，我和朋友都忽视了这些迹象。有人觉得困难并不让我们意外，因为班上有一半的人在所有作业上都感到吃力。而我们是那些轻松搞定一切的另一半。我们本打算在截止日期前一晚开始，刚好在晚餐前完成。</p>

<p>我们错了。</p>

<p>在我们以为自己应该完成的时间点过后一小时，我们才刚刚开始；我们俩都没设计出能工作的模块。我们的失败方式不同，以至于无法有效交流。实验室里挤满了人——有些已经努力了好几周，有些和我们一样等到最后一刻——但到处都是坏消息：少数人第一次就成功做出了除法单元，但没人知道如何将一个不正确设计的修改成能做三年级算术运算的版本。</p>

<p>我开始使用我唯一的工具：拼命思考。这个以前万无一失的方法现在只给我带来困惑，因为项目太复杂，无法整体在脑中构图。我试着分别思考设计的各个部分，但这只让我发现错误在于各部分之间的相互作用；每个单独组件看起来都没问题。思考各部分之间的关系令人沮丧，总感觉答案近在咫尺，但专注于某一部分时，另一部分关键信息就会从脑中溜走。接下来的学期，我将获得足够的经验和能力，去处理复杂度再高一个数量级的设计，但那是三个月漫长练习之后的事，而那个晚上，我完全不知所措。</p>

<p>到晚上10点，我饿坏了，也想不出办法。我叫上大家去吃晚饭，希望从项目思考中抽离出来，但我们只能讨论这项目有多无望。当唯一的方法是丝毫不差地组装成千上万个部件时，我们怎么可能完成？这就像一款没有生命数、不能续关的疯狂雅达利游戏。任何错误都是致命的。</p>

<p>一些人决定从头开始；他们决定两人一组互相检查。我太固执不愿重来，又缺乏经验不知道还能尝试什么。回到实验室后，因为很多人已经放弃，实验室空了一半，我继续盯着我的设计看，仿佛第三小时的思考能带来什么新领悟。</p>

<p>没有。第四小时也没有。</p>

<p>然后，就在午夜刚过，我们晚餐结识的那些朋友中，好几个人报告说成功了。那些重新开始的人里，有一半设计出了能工作的版本。其他人则很沮丧，因为他们的设计仍然存在某种微妙、不明显的错误。当我与其中一个学生交谈时，我开始仔细查看他的设计。几分钟后，我意识到费曼方法不是唯一的出路：应该可以系统地反复应用一种机械技巧来找出我们问题的根源。在所有抽象概念之下，我们的项目纯粹由与非门（NAND gates，那些在我们工具箱里翻找太深发现动态逻辑的人可就惨了）构成，只有当两个输入都是1时才输出0。如果正确输出是0，那么两个输入都应该是1。如果输出错误地是1，那么至少有一个输入错误地是0。同样的逻辑可以反过来应用。我们递归地这样做，在不到半小时内就找到了我们两个设计中所有问题的根源。</p>

<p>我们兴奋地向周围的人解释我们新发现的技巧，带着他们走了几步。没有人觉得困难；即使是那些在之前每次作业都挣扎的人也是如此。一小时内，听到我们解释的那群人都完成了，我们也回家了。</p>

<p>我现在理解为什么班上一半的人在前面的作业上挣扎了。如果没有系统性解决问题方法的指导，任何没能直观领悟正确解法的人都将经历一个充满挫折的学期。那些像我一样高于平均水平但不算顶尖的人，大多能应付课程，要么是运气好，要么是在最终项目上浪费了大量时间。我甚至见过一些才华横溢的人，轻松读完整个学位，从未遇到过一个大到无法凭直觉理解的问题；但当他们在现实世界中遇到一个一千万行代码的代码库时，他们就会遭遇非常糟糕的经历。<a href="http://lwn.net/2000/0824/a/esr-sharing.php3" rel="noopener noreferrer">工程师越有才华，在学校外撞上调试墙的可能性就越大</a>。</p>

<p>我不明白的是，为什么学校不教系统性调试。这是工程学中最基本的技能之一：从问题的症状出发，逆向追溯找到根源。教最基础的部分最多只需要半小时——即使这么一点点，也足以挽救相当一部分被淘汰并转去非理工科专业的人。使用标准工程课程中逐步增加问题复杂度的序列，专注于调试的课程可以扩展到足以填满一个学期，这将足够涵盖一个令人头疼的真实世界缺陷：可能是一个系统每天崩溃一次，当使用硬件加速播放蓝光DVD时，同时有两个网络摄像头录制有明显运动的画面，并且一个1994年的晦涩基准测试正在运行<sup id="fnref:2"><a href="#fn:2" rel="noopener noreferrer">1</a></sup>。</p>

<p>这种情况并非ECE 352独有，甚至不仅限于威斯康星大学——我在<a href="https://engineering.purdue.edu/~ee202/pastexams/F13FE.pdf" rel="noopener noreferrer">EE 202</a>担任助教时也看到了同样的情况，那是普渡大学二年级的信号与系统课程。问题从除法器和布尔逻辑变成了快速傅里叶变换（FFT）和拉普拉斯变换<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">2</a></sup>，但回避教授基本技能的做法是一样的。从学生在我答疑时间问的问题来看，很明显，那些表现不佳的人并非挣扎于课程的基本概念，而是挣扎于代数：问题源于对例如<code>f(x+a)</code>和<code>f(x)+a</code>的区别没有直观理解。</p>

<p>当我向教授<sup id="fnref:Z"><a href="#fn:Z" rel="noopener noreferrer">3</a></sup>建议，为那些在高中从未清晰学过这些内容的学生花半小时复习代数时，我得到斩钉截铁的回答：这是浪费时间，因为有些人就是干不了工程。我被告知，等这学期结束我就不会这么天真了，因为有些人就是干不了工程。我被告知，帮助学生学习补习材料不会对他们有好处；反正他们也应付不了高级课程，因为有些学生就是干不了工程。我被告知普渡大学录取政策宽松，我应该预料到高失败率，因为有些学生就是干不了工程。</p>

<p>我同意少数学生可能需要异常大量的帮助，但奇怪的是，如果一个人有能力记住通过第一年工程课程和微积分所需的惊人内容量，而不去深入理解代数，他们就真的学不会去理解他们已经记住的代数吗？我不是什么伟大的老师，但我能够让答疑时间常来的学生中除了一个之外，都在一学期内跟上了进度。一个有经验的老师，即使不那么热衷教学，也能轻松地把内容教给所有人。</p>

<p>为什么我们要在课程中遗漏内容，然后让那些无法自己弄明白这些内容的学生不及格？为什么我们要把工程专业头两年变成某种入门仪式，而不是简单地教给他们成为优秀工程师所需的知识？尽管我们都在高谈阔论如何需要堵住<a href="http://www.vtcite.com/~vtcite/system/files/blickenstaff-1.pdf" rel="noopener noreferrer">STEM</a> <a href="http://www.itif.org/files/2010-refueling-innovation-exec-summ.pdf" rel="noopener noreferrer">教育</a> <a href="http://digitalcommons.ilr.cornell.edu/cgi/viewcontent.cgi?article=1137&amp;context=workingpapers&amp;sei-redir=1&amp;referer=http%3A%2F%2Fscholar.google.com%2Fscholar_url%3Fhl%3Den%26q%3Dhttp%3A%2F%2Fdigitalcommons.ilr.cornell.edu%2Fcgi%2Fviewcontent.cgi%253Farticle%253D1137%2526context%253Dworkingpapers%26sa%3DX%26scisig%3DAAGBfm1oWNg44e2TO8gFdV0XAK2yRRHpfQ%26oi%3Dscholarr#search=%22http%3A%2F%2Fdigitalcommons.ilr.cornell.edu%2Fcgi%2Fviewcontent.cgi%3Farticle%253D1137%2526context%253Dworkingpapers%22" rel="noopener noreferrer">教育</a> <a href="http://eric.ed.gov/?id=EJ789574" rel="noopener noreferrer">管道</a> <a href="http://books.google.com/books?hl=en&amp;lr=&amp;id=zgsvXQ4GbfkC&amp;oi=fnd&amp;pg=PA3&amp;dq=leaky+stem+education&amp;ots=24p6ZeYtp4&amp;sig=t2HNOrp2P9BGwq44w3k7mwZjteE#v=onepage&amp;q=leaky%20stem%20education&amp;f=false" rel="noopener noreferrer">漏洞</a>，但我们不仅没有堵住漏洞，反而还为管道泄漏得如此之快而自豪。</p>

<p><small>感谢Kelley Eskridge、@brcpo9等人提供意见/修正。</small></p><small>

<h3 id="elsewhere">其他地方</h3>

<ul>
<li><a href="https://blog.regehr.org/archives/849" rel="noopener noreferrer">John Regehr推荐四本调试书籍</a></li>
</ul>
<div>

<hr>

<ol>
<li id="fn:2"><p>这是一个我见过的实际CPU缺陷，追踪了大约一个月。而这还是这个缺陷的简单形式，它有一套导致大约每天重现一次的诱因——原始形式的缺陷几天才失败一次。我举这个例子不是因为它特别难：我能想到很多花了更长时间追踪、症状更奇怪的缺陷，包括一个我们最好的调试员花了六个月才弄明白的灾难性缺陷。</p>

<p>给外面那些做ASIC流片后调试的人说一下，这个芯片几乎没有完整的扫描链，我们唯一能把状态导出芯片的方法会干扰芯片状态，导致一些缺陷消失。好时光啊。但积极的一面是，在处理状态可见性差的不确定硬件缺陷之后，软件缺陷似乎很容易。最坏的情况，它们只是枯燥乏味，因为调试它们就是把事情逆向追踪到问题根源。
 <a href="#fnref:2" rel="noopener noreferrer"><sup>[返回]</sup></a></p></li>
<li id="fn:1">我的一位同事告诉我，在<a href="http://en.wikipedia.org/wiki/Cray" rel="noopener noreferrer">Cray</a>公司时，一位高级项目经理曾用“项目需要更多布尔逻辑”来形容工程资源的缺乏。从那以后，我就把数字设计工程师看作是消费咖啡因、产出布尔逻辑的人。我仍然不确定模拟魔术师们产出什么。
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:Z">当我担任EE 202助教时，有两位教授分别教授两个不同的班。那位告诉我学生不及格就是干不了工程的教授，反而是更受学生欢迎的教授。他和蔼可亲、有魅力，人们喜欢他。他的班上成绩也比那个不被学生喜欢的、被认为刻薄的教授班上的成绩要低。这门课的助教经历教会了我很多：人们根本不知道谁在做好工作、谁在帮助他们，也让我真正学会了信号与系统（我本科时上信号与系统I只是为了满足要求，考试时到场并pass，但实际上没学任何内容，所以要带学生学信号与系统II，我必须真正从头学习I和II的内容；在担任这门课助教前，我告诉系里我没上过这门课，可能应该助教另一门课，但他们不在乎，这又给我上了一堂很好的人生课）。
 <a href="#fnref:Z" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div></small><p><em>由 mimo-v2.5 模型翻译，花费 6929 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/teach-debugging/</link>
      <guid isPermaLink="false">https://danluu.com/teach-debugging/</guid>
      <pubDate>Sat, 8 Feb 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>程序员需要数学吗？</title>
      <description>[AI 摘要] 作者基于个人经历探讨数学在编程中的作用，认为数学有帮助但非所有职业必需，且享受数学是值得的。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 作者基于个人经历探讨数学在编程中的作用，认为数学有帮助但非所有职业必需，且享受数学是值得的。</div><p>亲爱的<a href="http://dpb.bitbucket.org/" rel="noopener noreferrer">David</a>,</p>

<p>恐怕我之前的即兴回应思考不周；当你提到要学习微积分III和线性代数，并因“Wolfram Alpha现在能处理所有这些”而遭到一位朋友反对时，我的第一反应是惊恐——这就是为什么我回复说，虽然我经常后悔没有认真上课，因为后来发现自己本可以好好利用那些技能，但我从未对自己说过“<a href="http://www.dartmouth.edu/~matc/MathDrama/reading/Wigner.html" rel="noopener noreferrer">浪费时间学习那个基础数学概念并充分理解它</a>。”</p>

<p></p>

<p>但这会不会是选择偏差？回忆我使用过的数学比不使用的更容易。为了验证，让我们看看我本科时上的九门数学课。如果我排除那些明显与数学相关的工作（纯数学和计算机理论，加上飞秒光学），只考虑我在非数学工作中是否使用了数学技能，我发现：三门课程的材料我连续使用了数月或数年（微积分I/II、线性代数和微积分III）；三门课程在短时间内非常有价值（组合数学、纠错码和计算学习理论）；一门课程如果我当时保留了相关信息本可以派上用场（研究生级别的矩阵分析）；一门课程的材料我只用过一次（数理经济学）；只有一门课我不记得直接应用于任何非数学工作（实分析）。以下是我如何最终使用这些的：</p>

<p><strong>微积分I/II<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">1</a></sup></strong>：对于处理真实物理事物以及受物理启发的算法至关重要。此外，我最有效的技巧之一是用泰勒级数或雷梅兹级数（或其他近似函数）替代复杂函数，当误差界限不太高且需要高速度时。</p>

<p><strong>线性代数</strong>：尽管我曾多年不用，但很难想象在我的职业生涯中避开线性代数，因为<a href="http://walpurgisriot.github.io/blog/2014/01/03/matrix-meditation.html" rel="noopener noreferrer">矩阵的通用性</a>。</p>

<p><strong>微积分III</strong>：与微积分I/II相同。</p>

<p><strong>组合数学</strong>：至少在面试中能给人留下深刻印象。我组合数学的非面试使用大多来自看到看似复杂问题的简化；与概率和<a href="https://danluu.com/randomize-hn" rel="noopener noreferrer">随机算法</a>结合得很好。</p>

<p><strong>纠错码</strong>：当你需要<a href="http://en.wikipedia.org/wiki/Error-correcting_code" rel="noopener noreferrer">纠错码</a>时，没有替代品。更一般地说，信息理论是无价的。</p>

<p><strong>研究生级别的矩阵分析</strong>：从学习到实际应用之间有十年的空白。但仍然值得，原因与线性代数重要相同。</p>

<p><strong>实分析</strong>：不记得任何直接应用，尽管这些材料对理解拓扑和测度论有用。</p>

<p><strong>计算学习理论</strong>：对于让机器学习中人们认为可怕的部分变得相当容易很有用，并为机器学习中更像炼金术而非工程的领域提供直觉。</p>

<p><strong>数理经济学</strong>：<a href="http://en.wikipedia.org/wiki/Lagrange_multiplier" rel="noopener noreferrer">拉格朗日乘子</a>有时派上用场，但更多用于工程而非编程。</p>

<p>九门中有七门。不差。所以我不确定如何调和我的经验与常见的观点，即除了计算机图形学和机器学习等少数深奥领域外，<a href="https://news.ycombinator.com/item?id=6949849" rel="noopener noreferrer">不</a><a href="https://news.ycombinator.com/item?id=519928" rel="noopener noreferrer">需要</a><a href="http://www.catb.org/~esr/faqs/hacker-howto.html#mathematics" rel="noopener noreferrer">理解</a><a href="https://news.ycombinator.com/item?id=3040286" rel="noopener noreferrer">教科书</a><a href="https://news.ycombinator.com/item?id=571090" rel="noopener noreferrer">算法</a>，更不用说像数学这样更抽象的概念。</p>

<p>一部分是我所得到工作的选择偏差；从事数学相关工作的公司更可能与我交谈。几周前，我与一群我们旧的<a href="https://www.hackerschool.com/" rel="noopener noreferrer">黑客学校</a>朋友进行了一次长谈，他们现在在职业招聘会上负责大量招聘；其中几位，他们的公司不在研究与工程的交叉点上，提到当遇到像我这样的人时，他们礼貌地试图结束讨论，因为他们知道我不会接受他们的工作<sup id="fnref:3"><a href="#fn:3" rel="noopener noreferrer">2</a></sup>。</p>

<p>但这不可能全是选择偏差。即使在完全非数学性质的工作中，我也从数学中获益良多。即使在远离数学的底层系统工作中，也不难找到一个简单的组合证明来表明一个看似愚蠢的解决方案实际上是最佳的，或者高概率正确；即使在做远超数值方法的工作时，有时瓶颈是一个函数，可以用一些入门级近似技术如泰勒展开或牛顿法更快速地计算。</p>

<p>回顾我的职业生涯，从理解算法和计算机架构中我获得了比数学更多的回报，但我真的很喜欢数学，我很高兴知道一点数学使我的职业生涯偏向更数学的工作，并在深度非数学的工作中给了我一些数学插曲。</p>

<p>综合考虑，我真正的立场比我想象的更轻松：如果你喜欢数学，为了纯粹解题乐趣而多上课程是值得的，但如果你的主要目标是从学术生涯转向编程，数学课程不是时间的最佳利用。</p>

<p><br><br>
敬礼，<br>
Dan</p>

<p><a href="http://itmozg.ru/news/1232/" rel="noopener noreferrer">这里提供俄语翻译</a></p>
<div>

<hr>

<ol>
<li id="fn:1">一位才华横溢但疯狂的讲师将两个学期以定理/证明为导向的<a href="http://www.amazon.com/gp/product/0471000051/ref=as_li_qf_sp_asin_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0471000051&amp;linkCode=as2&amp;tag=abroaview-20" rel="noopener noreferrer">Apostol教材</a>压缩成两个月，然后在我们用完书后开始讲授复分析。直到上这门课，我才意识到数学很有趣。这个脚注应该加在课程名称上，但rdiscount不允许在加粗文本中放置脚注。
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:3">顺便说一句，这完全不真实。看看产品导向的角色会是很棒的。就目前而言，我离任何实际客户有五个团队之遥。唉。我比上一份工作时更近了一步。
 <a href="#fnref:3" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 6181 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/math-bias/</link>
      <guid isPermaLink="false">https://danluu.com/math-bias/</guid>
      <pubDate>Thu, 9 Jan 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>数据对齐与缓存</title>
      <description>[AI 摘要] 页面对齐内存访问会引发缓存冲突导致性能下降，而适当错位访问能有效利用缓存空间。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 页面对齐内存访问会引发缓存冲突导致性能下降，而适当错位访问能有效利用缓存空间。</div><p>这是一个关于页面对齐与错位访问的<a href="https://github.com/danluu/setjmp-longjmp-ucontext-snippets/tree/master/benchmark/cache-conflict" rel="noopener noreferrer">玩具基准测试</a><sup id="fnref:A"><a href="#fn:A" rel="noopener noreferrer">1</a></sup>的图表；它显示了在不同工作集大小下，两者之间的性能比率。如果这个基准测试看起来像是人为的，它其实源于一个真实世界的例子，展示了在实际系统中使用漂亮的2的幂次方对齐或页面对齐所<a href="https://groups.google.com/forum/#!msg/comp.arch/_uecSnSEQc4/mvfRnOvIyzUJ" rel="noopener noreferrer">带来的灾难性性能影响</a><sup id="fnref:M"><a href="#fn:M" rel="noopener noreferrer">2</a></sup>。</p>

<p><img src="https://danluu.com/images/3c-conflict/sandy.png" alt="Sandy Bridge性能图" width="504" height="216">

<img src="https://danluu.com/images/3c-conflict/westmere.png" alt="Westmere性能图" width="504" height="504"></p>

<p>除了非常小的工作集（1-8）外，未对齐版本比页面对齐版本明显更快，并且在工作集大小达到512之前有一个较大的区域，性能比率相对稳定，但在我们的Sandy Bridge芯片上比Westmere芯片上更稳定。</p>

<p>要理解这里发生的情况，我们必须查看缓存如何组织数据。打个比方，考虑一个有10,000个许可证的1000个车位的停车库。采用直接映射方案（你可以称之为1路组关联<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">3</a></sup>），每个最后3位数字相同的10个许可证将被分配相同的车位，即许可证0618、1618、2618等，只允许停在车位618。如果你到达你的车位时别人占了它，你会把他们赶出去，他们必须开车回家。下次他们被叫来上班时，他们必须一路开车回到停车库。</p>

<p>相反，如果每个汽车的许可证允许它停在有一组十个可能车位的集合中，我们称之为10路组关联方案，这给了我们100个集合，每个集合有10个车位。每个集合现在由最后2位数字而不是最后3位定义。例如，许可证2618，你可以停在集合{018, 118, 218, …, 918}中的任何车位。如果他们都满了，你会踢出一个不幸的占据者并占据他们的位置，和以前一样。</p>

<p>让我们跳出比喻，回到我们的基准测试。主要区别在于，不只有一个车库缓存，而是一个层次结构，从最小（因此最快）的L1<sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">4</a></sup>，到L2和L3。汽车中的每个座位对应一个地址。在x86上，每个地址指向一个特定的字节。在我们运行的Sandy Bridge芯片上，我们有一个32kB的L1缓存，行大小为64字节，64个集合，8路组关联。在我们的比喻中，64的行大小对应一辆有64个座位的汽车。我们总是以64字节块传输数据，地址的最低log₂(64) = 6位指缓存行中的特定字节偏移。接下来的log₂(64) = 6位决定一个地址属于哪个集合<sup id="fnref:L"><a href="#fn:L" rel="noopener noreferrer">5</a></sup>。每个集合可以包含8个不同的东西，所以我们有64个集合 * 8行/集合 * 64字节/行 = 32kB。如果我们最优地使用缓存，我们可以存储32,768个项目。但是，由于我们访问的是页面（4k）对齐的东西，我们实际上失去了最低的log₂(4k) = 12位，这意味着每次访问都落入同一个集合，在我们的工作集过大而无法放入L1之前，我们只能循环8个东西！但如果我们把数据错位到不同的缓存行，我们将能够有效地使用8 * 64 = 512个位置。</p>

<p>类似地，我们的芯片有一个512集合的L2缓存，其中8个集合对我们页面对齐的访问有用，以及一个12288集合的L3缓存，其中192个集合对页面对齐的访问有用，分别给我们8集合 * 8行/集合 = 64和192集合 * 8行/集合 = 1536个有用的缓存行。对于错位一个缓存行的数据，我们有额外的6位有用的地址，这意味着我们的L2缓存现在有32,768个有用位置。</p>

<p>在上面的Sandy Bridge图表中，64到512之间有一个稳定相对性能的区域，因为页面对齐版本正在耗尽L3缓存，而未对齐版本正在耗尽L1。当我们超过512的工作集时，相对比率对齐齐版本更好，因为它现在是L2访问与L3访问的比较。我们的Westmere图表看起来有点不同，因为它的L3只有3072个集合，这意味着对齐版本只能在L3中保持到384的工作集大小。之后，我们可以看到溢出到主内存所带来的糟糕性能，这解释了为什么两个图表在384以上的形状不同。</p>

<p>为了可视化这一点，你可以认为一个32位指针对我们L1和L2缓存看起来像这样：</p>

<p><code>TTTT TTTT TTTT TTTT TTTT SSSS SSXX XXXX</code></p>

<p><code>TTTT TTTT TTTT TTTT TSSS SSSS SSXX XXXX</code></p>

<p>最低的6位被忽略，接下来的位决定我们属于哪个集合，最高的位是一个标签，让我们知道那个集合里实际是什么。注意页面对齐事物，即设置地址为</p>

<p><code>???? ???? ???? ???? ???? 0000 0000 0000</code></p>

<p>只是为了在我们的基准测试中方便。不仅对齐到任何大的2的幂会导致问题，生成彼此之间有2的幂次方偏移的地址也会导致同样的问题。</p>

<p>如今，缓存的重要性已被充分理解，所以，当有人请我查看与缓存相关的性能错误时，它通常是我们刚才谈论的那种：冲突未命中阻止我们有效使用我们的全部缓存<sup id="fnref:4"><a href="#fn:4" rel="noopener noreferrer">6</a></sup>。这不是唯一会发生这种情况的方式——银行冲突和虚假依赖也是常见问题，但我会把那些留给另一篇博客文章。</p>

<h4 id="resources">资源</h4>

<p>关于缓存和内存的更多内容，请参阅<a href="http://www.akkadia.org/drepper/cpumemory.pdf" rel="noopener noreferrer">每个程序员都应该知道的内存知识</a>。关于更广的内容，请参阅<a href="//danluu.com/new-cpu-features/" rel="noopener noreferrer">这篇"简短"的博客文章</a>，或<a href="http://www.amazon.com/gp/product/1478607831/ref=as_li_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=1478607831&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=HXBNFFJ3CIXMWUZP" rel="noopener noreferrer">现代处理器设计</a>这样的书。为了更广的内容（以上两个链接侧重于CPU和内存），请参阅<a href="http://www.amazon.com/gp/product/012383872X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=012383872X&amp;linkCode=as2&amp;tag=abroaview-20&amp;linkId=ZHRJAO77SUQ6V7GL" rel="noopener noreferrer">计算机体系结构：量化研究方法</a>，它涵盖了从数据中心级别的整个系统。</p>
<div>

<hr>

<ol>
<li id="fn:A">Sandy Bridge是i7 3930K，Westmere是移动版i3 330M。
 <a href="#fnref:A" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:M">或者任何在运行具有2路组关联或直接映射缓存的芯片上，为两个源数组和一个目标数组的计算而将数据对齐得太好的人。如果你为了进行缓存分块而以某种很好的方式设置数组，却不小心的话，这是令人惊讶地常见的。
 <a href="#fnref:M" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:1">别那样称呼它。人们会用奇怪的眼光看你，就像你把SQL发音为squeal或squll时一样。
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:B">在这篇文章中，L1指的是l1d。因为我们只关心数据，所以l1i不相关。为术语使用不当道歉。
 <a href="#fnref:B" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:L">如果看起来奇怪最低有效地址位用于集合索引，这是因为计算机体系结构的首要原则：让常见情况快速——谷歌即时搜索将“让常见”补全为“让常见情况快速”、“让常见情况快速 mips”和“让常见情况快速计算机体系结构”。绝大多数访问都是相邻的，因此将集合索引位向上移动会导致更多的冲突未命中。你也许可以尝试一个不是简单最低位的哈希函数，但大多数提议的方案伤害与帮助相当，同时增加了额外的复杂性。
 <a href="#fnref:L" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:4">缓存未命中通常用3C模型描述：冲突未命中，由我们刚才讨论的那种别名引起；强制性未命中，由首次访问内存位置引起；以及容量未命中，由工作集过大无法放入缓存引起，即使没有冲突未命中。像这样的页面对齐访问也使强制性未命中更糟，因为预取器不会预取超过页面边界。但如果你有足够多的数据让你将内容对齐到页面边界，你可能也无法做太多关于那的事情。
 <a href="#fnref:4" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 5609 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/3c-conflict/</link>
      <guid isPermaLink="false">https://danluu.com/3c-conflict/</guid>
      <pubDate>Thu, 2 Jan 2014 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>PCA并非万能药</title>
      <description>[AI 摘要] 本文通过面试案例论证线性方法（如PCA）并非解决所有数据科学问题的万能工具。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文通过面试案例论证线性方法（如PCA）并非解决所有数据科学问题的万能工具。</div><p>今年早些时候，我面试了一家知名科技初创公司——它是数百家声称面试难度更高、工作更具挑战性、员工比谷歌更聪明的公司之一<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">1</a></sup>。我的第一位面试官约翰带我进行了标准参观：微型厨房里摆满了健康零食和糖果；一群二十多岁的白人男性围在足球桌旁；明亮的空间配着可爱主题；一台巨型电视用于玩游戏；还有洗手间。最后，他带我进入一个衣帽间大小的会议室，我们开始谈正事。</p>

<p>经过常规的数据结构和算法环节后，我们进入了核心问题：如何为foo<sup id="fnref:2"><a href="#fn:2" rel="noopener noreferrer">2</a></sup>设计一个分类系统？我们讨论了设计权衡，但关键分歧在于算法选择。我说，如果在面试中需要编写代码，我会使用朴素矩阵分解算法，但我不认为能得到好结果，因为并非所有数据都容易分解。约翰不同意——他坚信PCA是任何分类问题的解决方案。</p>

<p></p>

<p>我们花了二十五分钟讨论数学基础——占用了面试一半时间——显然，我们谁也无法用理论说服对方。我转换思路，尝试用实证方法，引用了关于文本分类的旧研究结果：隐语义分析<sup id="fnref:C"><a href="#fn:C" rel="noopener noreferrer">3</a></sup>（只能捕捉词语间的成对相关性）与深度学习<sup id="fnref:B"><a href="#fn:B" rel="noopener noreferrer">4</a></sup>的对比。以下是LSA的结果：</p>

<p><img src="https://danluu.com/images/linear-hammer/PCA.png" alt="2-d LSA" width="321" height="329"></p>

<p>每种颜色代表一种文本类型，投影到二维空间；虽然降维程度可能过高，但这是可视化的有效方式。不同类别间有一定分离：绿点倾向于右下区域，黑点在上半部更密集等。但当文档相似且差异细微时，任何基于此的分类效果都不会太好。</p>

<p>以下是深度自编码器的结果：</p>

<p><img src="https://danluu.com/images/linear-hammer/deep_autoencoder.png" alt="2-d deep autoencoder" width="446" height="344"></p>

<p>虽然不完美，但效果好得多。</p>

<p>即使给出例子后，我仍无法与面试官达成共识，于是提议保留分歧并继续下一话题。这没什么大不了，毕竟只是面试。但我每月至少在面试外目睹一次这种对标准方法的误用，且总有人坚信只需将这种线性技术应用于任何问题。</p>

<p>工程师们最反感顾问们带着通用商业知识上门，收取500美元/小时咨询费，给出常识性建议却搞砸细节。但数据科学足够新潮，当人们自称数据科学家而非技术顾问时总能被谅解。我并非贬低数据科学（无论其定义如何）或线性方法<sup id="fnref:D"><a href="#fn:D" rel="noopener noreferrer">5</a></sup>——它们确实有用。但我不断看到有人试图用相同的四种线性方法解决眼前所有问题。</p>

<p>实际上，我正在写作时，女友正在隔壁房间参加某大公司数据科学组的电话面试。他们试图用多元回归预测系统性能，并通过回归系数将资源消耗分解到应用和查询层面，得出诸如"4000 QPS的foobar使用18% CPU"这类结论。他们目前向她提出的问题是：如何加速回归以使测试系统达到Web规模？</p>

<p>真正的问题是：为什么要这样做？几乎所有研究生计算机体系结构课程都要求编写或修改模拟器，原因在于：真实系统性能存在大量非线性断点——这类情况无法简单套用排队论模型，更不用说线性回归模型。但当你手头只有线性锤子时，非线性螺丝钉看起来都像钉子。</p>

<p><small><em>对此，John Myles White指出了一个好观点：线性与非线性并非正确的分析框架，目前缺乏恰当的术语体系来描述此类问题。抱歉用词不够精确。如需更严谨，可将文中的"线性"替换为"特定目标函数的简单方法"。</em></small></p>
<div>

<hr>

<ol>
<li id="fn:1">我读大学时，微软还是标杆。不知谁会成为下一个标杆。
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:2">我未透露具体问题，因对方要求保密面试题。此处描述的问题在矩阵分解方面存在相同根本缺陷。
 <a href="#fnref:2" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:C">若熟悉PCA但不熟悉LSA，<a href="http://stats.stackexchange.com/questions/65699/lsa-vs-pca-document-clustering" rel="noopener noreferrer">可将LSA视为类似PCA的方法</a>。
 <a href="#fnref:C" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:B"><a href="http://www.sciencemag.org/content/313/5786/504.abstract" rel="noopener noreferrer">http://www.sciencemag.org/content/313/5786/504.abstract</a>，<a href="http://www.cs.toronto.edu/~amnih/cifar/talks/salakhut_talk.pdf" rel="noopener noreferrer">http://www.cs.toronto.edu/~amnih/cifar/talks/salakhut_talk.pdf</a>。严格来说，这项工作已被2011年的一系列论文取代，这些论文证明<a href="http://www.stanford.edu/~acoates/papers/coatesleeng_aistats_2011.pdf" rel="noopener noreferrer">可以</a><a href="http://www.stanford.edu/~acoates/papers/coatesng_icml_2011.pdf" rel="noopener noreferrer">通过</a><a href="http://cs.stanford.edu/~jngiam/papers/NgiamKohChenBhaskarNg2011.pdf" rel="noopener noreferrer">"简单"算法</a>达到2006年研究的类似效果。但当前深度学习方法确实优于最佳"简单"特征学习方案，且这是首先想到的案例。
 <a href="#fnref:B" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn-D">有趣的是，我作为线性方法的忠实拥护者却写这篇文章。我常将复杂函数替换为一阶泰勒展开式。
 <a href="#fnref:D" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 3757 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/linear-hammer/</link>
      <guid isPermaLink="false">https://danluu.com/linear-hammer/</guid>
      <pubDate>Fri, 13 Dec 2013 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>为什么硬件开发如此艰难</title>
      <description>[AI 摘要] 本文论述了硬件开发依赖经验丰富的工程师，因其物理错误代价高昂且难以修复，与软件行业不同。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文论述了硬件开发依赖经验丰富的工程师，因其物理错误代价高昂且难以修复，与软件行业不同。</div><p>在CPU设计领域，大多数成功团队都拥有悠久的传统，并严重依赖经验丰富的工程师。当我们观察CPU初创公司时，那些成功退出的团队通常拥有一个合作数十年的核心团队。例如，PA Semi被苹果收购算是一次相对成功的退出，但这个团队从何而来？他们原本是SiByte团队，在SiByte被博通收购后离开，而SiByte则由许多来自DEC的人士组成，他们已经共事超过十年。我以前的公司情况类似：一位IBM院士聚集了<a href="http://conservancy.umn.edu/handle/120173" rel="noopener noreferrer">他在IBM共事过的最优秀人才</a>（其中一人是戴尔非常早期的员工和高管，当时戴尔还在做有趣的设计工作），然后离开创办了一家芯片初创公司。曾有不少CPU初创公司筹集了数千万至数亿美元资金，却严重依赖经验不足的劳动力；刚毕业的博士生和仅有几年经验的硬件工程师。据我所知，这类初创公司无一例外都失败了<sup id="fnref:2"><a href="#fn:2" rel="noopener noreferrer">1</a></sup>。</p>

<p>这与软件初创公司形成鲜明对比，后者常见由刚毕业（或辍学）的人士成功创立。微处理器领域为何会有所不同？尽管这并未阻止人们资助此类尝试，但从未听说过一支年轻的新团队能成功制造出高性能微处理器。</p>

<p>在软件领域，经常可以听到对经验的轻视，例如扎克伯格的言论："我想强调年轻和技术的重要性，年轻人就是更聪明。"即使人们没有明确贬低经验，也往往不重视它。截至本文写作时，Joel Spolsky的《<a href="http://www.joelonsoftware.com/articles/GuerrillaInterviewing3.html" rel="noopener noreferrer">Smart and gets things done</a>》可能是关于软件招聘最有影响力的文章。请注意，它说的是"聪明且能搞定事情"，而非"聪明、有经验且能搞定事情"。似乎只需要"聪明且能搞定事情"就够了，不需要经验。如果你更倾向于Paul Graham阵营而非Joel Spolsky阵营，在招聘方式上会有很大差异，但Paul的建议是相同的，即<a href="http://www.paulgraham.com/founders.html" rel="noopener noreferrer">经验并非他最重要的标准之一</a>，<a href="https://twitter.com/danluu/status/1356462960172883969" rel="noopener noreferrer">除非作为贬义</a>。</p>

<p>假设你想雇一名水管工或木匠，你会选择哪种？"聪明且能搞定事情"还是"经验丰富且高效"？在其他条件相同的情况下，我会选择"经验丰富且高效"，如果是紧急情况，更是如此。</p>

<p>无论你多么聪明，物理工作都不是你能从第一性原理推导出来的事情。以二战后的韩国为例。<a href="http://www.nationmaster.com/graph/eco_gdp_per_cap_in_195-economy-gdp-per-capita-1950" rel="noopener noreferrer">其人均GDP低于加纳、肯尼亚，仅略高于刚果</a>。由于各种原因，新政权不必处理遗留机构；他们希望韩国成为一等国家。</p>

<p><a href="http://delong.typepad.com/aeh/" rel="noopener noreferrer">我听说的故事是</a>，政府首先补贴水泥产业。经过多年生产水泥后，他们希望向产业链上游移动，开始更复杂的制造业。最终他们着手建造船舶，因为航运是他们想建立的出口经济的关键部分。</p>

<p>他们抽调了一些最优秀的商业人才，这些人在其他制造业中学到了管理和运营等技能。这些人知道自己没有造船的专业知识，因此将其外包。他们选择与苏格兰公司合作，因为苏格兰有着悠久的造船历史。这很合理，对吧？</p>

<p>结果行不通。由于历史和地理原因，苏格兰的造船厂并非全尺寸；他们分两半造船然后再组装。这对他们来说效果很好，因为他们自19世纪以来就进行规模化生产，到20世纪已拥有<a href="http://www.educationscotland.gov.uk/scotlandshistory/makingindustrialurban/shipbuilding/index.asp" rel="noopener noreferrer">世界知名的专业知识</a>。但当缺乏实践的韩国人试图按照苏格兰的蓝图和详细的分步说明造船时，结果是两半船体无法完全契合，组装后沉没了。</p>

<p>韩国人最终通过雇佣外国公司来当地造船，向人们展示如何操作，才启动了造船业。即使人们可能认为所有必要的知识都存在于书籍中、在大学课程中教授、并且可以以少量费用从专家那里获得，但让基本制造业顺畅运行花了数十年时间。现在，他们的制造业是世界级的，例如，根据《消费者报告》，现代和起亚生产的汽车可靠。从生产不可靠的经济型汽车到生产可靠可购买的汽车，花了十多年时间，就像丰田几十年前所做的那样。如果存在除了雇佣大量有经验者之外的质量捷径，那至今尚未被发现。</p>

<p>今天，任何程序员都可以学习Geoffrey Hinton的<a href="https://www.coursera.org/course/neuralnets" rel="noopener noreferrer">神经网络和深度学习课程</a>，并开始应用最先进的机器学习技术。在软件领域，你可以实时修复小错误。如果运行整个回归测试套件需要一整天，你会觉得自己很幸运，因为这意味着你处于少数认真对待测试的环境中。如果架构存在根本缺陷，你可以拿出Feathers的《<a href="http://www.amazon.com/gp/product/B005OYHF0A/ref=as_li_tf_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=B005OYHF0A&amp;linkCode=as2&amp;tag=abroaview-20" rel="noopener noreferrer">Working Effectively with Legacy Code</a>》并反复应用修复。</p>

<p>这并不是说软件不难，但有许多有价值的问题不需要十年来之不易的经验就可以着手解决。但如果你想要造一艘船，而你"仅仅"有十年的木工、铣削、金属加工等经验，那么，祝你好运。你需要它。对于大型船舶，"小"修复可能需要数天或数周，根本性缺陷意味着你的船会沉没，你损失了半年的工作和数千万美元。当进展到现代高性能微处理器这样的复杂程度时，生产中发现的小错误会耗费三个月时间和数百万美元。架构中的根本性缺陷将耗费你五年时间和数亿美元<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">2</a></sup>。</p>

<p>物理错误代价高昂。没有撤销按钮，编辑不仅仅是按几个键；更改会消耗真实的物理资源。你需要足够的智慧和经验来完全避免常见错误——尤其是那些无法修复的错误。</p>

<p><i>感谢Sophia Wisdom的评论/修正/讨论。</i></p>

<h3 id="cpu-internals-series">CPU内部系列</h3>

<ul>
<li><a href="https://danluu.com/cpu-bugs/" rel="noopener noreferrer">CPU错误</a></li>
<li><a href="//danluu.com/new-cpu-features/" rel="noopener noreferrer">80年代以来的新CPU特性</a></li>
<li><a href="//danluu.com/branch-prediction/" rel="noopener noreferrer">分支预测简史</a></li>
<li><a href="//danluu.com/integer-overflow/" rel="noopener noreferrer">实际代码中分支和整数溢出检查的代价</a></li>
<li><a href="//danluu.com/hardware-unforgiving/" rel="noopener noreferrer">为什么CPU开发如此艰难</a></li>
<li><a href="//danluu.com/why-hardware-development-is-hard/" rel="noopener noreferrer">Verilog很糟，第1部分</a></li>
<li><a href="//danluu.com/pl-troll/" rel="noopener noreferrer">Verilog很糟，第2部分</a></li>
</ul>

<h3 id="2021-comments">2021年评论</h3>

<p>回想起来，我认为我对软件的看法过于乐观了。如果我们谈论的是产品市场契合度和成功，我认为本文的态度没有错，经验很少或没有的人确实经常创造爆款。但现在我在行业里待了一段时间，并与初创公司和大公司的众多人士讨论过基础设施问题后，我认为创建高质量的软件基础设施所需的经验不亚于创建高质量的实体物品。那些认为不需要如此，并雇佣一批顶尖学校聪明人来构建基础设施的公司，最终得到了低质量、不可靠、昂贵且难以操作的基础设施。事实证明，如果你拥有非常好的产品市场契合度，你的基础设施不需要完美运行。你的公司可以生存甚至<a href="https://danluu.com/programmer-moneyball/" rel="noopener noreferrer">在基础设施只有2个9的正常运行时间且成本比竞争对手高一个数量级的情况下蓬勃发展</a>，或者<a href="https://danluu.com/nothing-works/" rel="noopener noreferrer">如果你的产品架构意味着它根本无法正确工作</a>。你的收入会比本可以实现的少，但关键部分都在产品端。如果你对比那些拥有经验不足工程师且未产出可用产品的芯片公司，那么，即使你尝试，也无法销售一个无法工作的产品。如果你非常幸运，比如恰好在正确的时间创办了一家深度学习芯片公司，你可能让大公司收购你那个无法工作的产品。但是，对于微处理器来说，获得这样的退出要困难得多。</p>
<div>

<hr>

<ol>
<li id="fn:2">将<a href="//danluu.com/glenn-henry-interview/" rel="noopener noreferrer">我以前的公司</a>与另一家在同一年成立的x86初创公司进行比较很有启发性。两家大致同时启动。两家都有由聪明人组成的优秀团队。我们的竞争对手甚至有著名的软件和商业人士。但值得注意的是，他们的硬件实现者并非一个由合作数十年的行业资深人士组成的核心团队。我们用了大约两年时间加上1500万美元的资金开发出了可用的x86芯片。我们的目标是生产低成本芯片，并且做到了。他们花了五年时间，超过2.5亿美元的资金。他们最初的目标是生产高性能低功耗处理器，但他们严重偏离了性能目标，被迫进入低成本领域。最终，他们的性能比我们差，芯片面积大了50%（因此生产成本高出50%以上），使用的团队规模是我们四倍。他们最终倒闭了，因为他们不可能以我们四倍的消耗率生存下去，而且性能更弱。但在此之前，他们烧掉了9.69亿美元的资金（包括2.3亿美元来自专利诉讼）。
 <a href="#fnref:2" rel="noopener noreferrer"><sup>[返回]</sup></a></li>

<li id="fn:1"><p>经验重要性的一个有趣副作用是，年龄歧视并不影响我工作过的领域。30岁的我，对于从事过微处理器设计的人来说，奇怪地年轻。我前公司的核心人员都在60岁左右。他们沿途也招募了一些年轻人，但30岁？年轻得离谱。新公司的人年轻得多：我周围都是来自<a href="http://en.wikipedia.org/wiki/Cray#Cray_Research_Inc._and_Cray_Computer_Corporation:_1972_to_1996" rel="noopener noreferrer">Cray和SGI</a>的前超算人员，他们勉强到50岁，还有几个来自<a href="http://en.wikipedia.org/wiki/Synplicity" rel="noopener noreferrer">Synplicity</a>和<a href="http://www.deshawresearch.com/" rel="noopener noreferrer">DESRES</a>的年轻人，40岁在这个领域算很年轻。并非所有硬件人员都那么老。在公司的另一个部门，有一些在FPGA领域成长起来的人，那个领域宽容得多。在那组人里，我想我遇到过一个只比我大几岁的人。玩笑归玩笑，在那些愿意花十年培养人的大公司里，你会看到更年轻的人在复杂项目上进行RTL设计。但是，在追求快速发展的初创公司和小型硬件团队中，很少会雇佣没有十年经验的人从事设计工作。</p>

<p>有一群人甚至比FPGA领域的人更年轻，甚至比我更年轻，他们在使用Arduino和微控制器，从事业余电子和消费产品。我真的很想知道这些人中有多少会决定从事大规模系统设计。从某种意义上说，这是必然的，随着领域成熟，解决方案变得更复杂。我好奇的是另一方面：硬件复兴会激发人们对超级计算机、微处理器和仓库级计算机的兴趣吗？</p>
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 6223 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/hardware-unforgiving/</link>
      <guid isPermaLink="false">https://danluu.com/hardware-unforgiving/</guid>
      <pubDate>Sun, 10 Nov 2013 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>如何打击开源贡献的积极性</title>
      <description>[AI 摘要] 本文批评了开源项目因忽视贡献者的积极性而形成的系统性问题，这阻碍了新手参与贡献。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文批评了开源项目因忽视贡献者的积极性而形成的系统性问题，这阻碍了新手参与贡献。</div><p>当你发现一个错误或看到一个开源项目缺失某个功能时，你首先会做什么？查看项目页面并提交一个补丁！</p>

<p><img src="https://danluu.com/images/discourage-oss/send-us-116-pull-requests.png" alt="Send us a pull request! (116 open pull requests)" width="1012" height="152"></p>

<p>哦。也许他们的信息太鼓励人了，以至于每周都会收到数百个拉取请求，而积压的任务也并不那么糟。</p>

<p><img src="https://danluu.com/images/discourage-oss/why-is-this-ignored.png" alt="Multiple people ask why this bug fix is being ignored. No response." width="745" height="472"></p>

<p>也许并非如此。像我这样的大傻瓜，即使看到了这种情况，还是提交了一个拉取请求。综合考虑，我应该庆幸自己还能<a href="http://www.jwz.org/blog/2013/02/wow-remember-when-people-tracked-bugs-welcome-to-the-next-level/" rel="noopener noreferrer">提交拉取请求</a>。如果我真的足够幸运，也许他们有一天会<a href="http://www.jwz.org/doc/cadt.html" rel="noopener noreferrer">抽空看一眼</a>。</p>

<p></p>

<p>我并非特指这个项目。我能理解这种情况是如何发生的。你是一个可以合并拉取请求的开发者，但你并不负责分类处理错误和拉取请求；你有日常工作、自己负责的项目，以及编码之外的生活。也许你偶尔会看看仓库，合并好的拉取请求，并对需要更多工作的请求发表评论，但你不会查看所有116个未处理的拉取请求；谁有那种时间呢？</p>

<p>这种行为，从任何个人的角度来看都极其合理，却导致了系统性的失败，对新的开源贡献者来说是一种负担。我经常被问到如何开始参与开源。我很容易忘记，起步可能很难，因为我最初参与贡献的几个项目对问题和拉取请求的响应时间是以小时计算的<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">1</a></sup>。但许多人的经历并非如此愉快。他们给几个项目贡献了几个补丁，结果都被忽视了，完全不知道接下来该怎么办。不需要<a href="http://lostechies.com/derickbailey/2012/12/14/dear-open-source-project-leader-quit-being-a-jerk/" rel="noopener noreferrer">极端的个人行为</a>就能制造出一个不友好的环境。</p>

<p>这种情况让我对某些项目望而却步。在我上一份工作中，我致力于让一个著名的开源项目达到生产质量，在几个月内修复了数百个错误。当我有空时，我考虑将这些更改推送给开源社区。但当我查看该项目的邮件列表时，我看到了一片被完全忽视的良好补丁的荒原，提交者会在列表里提醒几次，然后就放弃了。花一周时间将我们的知识产权从项目中分离出来，以提交一组很可能被忽视的补丁，这值得吗？不值得。</p>

<p>如果你是一个有此类问题项目的提交权限所有者，请<a href="http://en.wikipedia.org/wiki/Diffusion_of_responsibility" rel="noopener noreferrer">负责管理</a>传入的拉取请求流程（或者不要在你的仓库描述中要求拉取请求）。这不必是永久性的；只是在你建立好系统之前<sup id="fnref:2"><a href="#fn:2" rel="noopener noreferrer">2</a></sup>。这样做不仅会为你的项目吸引更多贡献者，还能帮助打破成为开源贡献者的一个障碍。</p>

<p><img src="https://danluu.com/images/discourage-oss/plz-review.png" alt="joewiz replies to an month old comment. Asks for review months later. No reply." width="747" height="522"></p>

<p><small></small></p><small>

<p>关于本文中提到的仓库的更新，请<a href="//danluu.com/everything-is-broken/#github" rel="noopener noreferrer">查看这篇针对破坏性更改的回应</a>。</p>

</small><p><small></small></p>
<div>

<hr>

<ol>
<li id="fn:1">赞扬 <a href="https://github.com/xianyi/OpenBLAS" rel="noopener noreferrer">OpenBlas</a>、<a href="https://github.com/mozilla/rust" rel="noopener noreferrer">Rust</a>、<a href="https://github.com/levskaya/jslinux-deobfuscated" rel="noopener noreferrer">jslinux-deobfuscated</a> 和 <a href="https://github.com/softprops/np" rel="noopener noreferrer">np</a>，他们对新贡献者极其友好。
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:2">我并非暗示这很简单。如果你的项目缺乏接纳文化，这可能会很困难，但也有一些<a href="http://paulmillr.com/posts/github-pull-request-stats/" rel="noopener noreferrer">受欢迎、高流量的项目设法做到了</a>。如果一切方法都失败了，你总可以试试<a href="http://felixge.de/2013/03/11/the-pull-request-hack.html" rel="noopener noreferrer">拉取请求技巧</a>。
 <a href="#fnref:2" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 2853 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/discourage-oss/</link>
      <guid isPermaLink="false">https://danluu.com/discourage-oss/</guid>
      <pubDate>Sun, 27 Oct 2013 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>随机化HN</title>
      <description>[AI 摘要] 文章探讨了HN网站首页门槛问题，建议引入随机性以平衡内容曝光，并提及HN尝试后的反馈。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章探讨了HN网站首页门槛问题，建议引入随机性以平衡内容曝光，并提及HN尝试后的反馈。</div><p>你是否注意到，在像HN这样的网站上，进入首页有一个有趣的阈值？确切的阈值取决于流量大小，但对于那些不是极度受欢迎的文章来说，当文章票数达到N-1时，会有这样一个时刻。或许有60%的几率，票数会增加，文章被推到首页，然后获得大量投票。也许有40%的几率，它永远不会得到那一票，导致它永远默默无闻。</p>

<p>对于一篇预期会获得50票的文章，有60%的几率获得100多票，而40%的几率只获得2票，这是非最优的。理想情况下，每篇文章都应始终获得其预期票数，并在首页停留预期时间，让读者根据文章的受欢迎程度来接触它。然而，由于随机偶然，许多有趣的内容从未登上首页，结果，那些登上首页的内容获得了高于最优水平的曝光。</p>

<p>在流量低的网站上，如lobste.rs和一些较小的subreddits，它们一旦发布就将内容推到首页，你会看到同样的问题，只是符号相反：它们把几乎没人关心的内容放在首页，只为了让少数人关心的东西获得足够的曝光以被点赞。在reddit上，用户通过大量反对大多数提交来“修复”这个问题，将它们推离首页，导致的问题与HN的问题本质相同。</p>

<p>相反，网站没有实施一些简单易优化的东西，而是堆砌临时规则。Reddit实现了rising页面，但它未能解决问题。在低流量的subreddit上，如r/programming，阈值太高，几乎总是空的。在高流量的subreddit上，任何被足够点赞以至于进入rising页面的内容都已经很成功了，一篇文章是否成功很大程度上取决于最初的几个投票者是否恰好点赞而不是反对，也就是说，进入rising页面的问题与通常登上榜首的问题没有区别。</p>

<p>HN试图通过手动惩罚某些域名和关键词来解决问题。这对95%的未受惩罚的帖子没有帮助。对于那些未能登上首页的帖子，明显的解决办法是删除并重新提交帖子，如果它第一次没有登上首页，但这现在是足以被封禁的行为。当然，人们正在绕过这个，HN也有针对绕过的办法，以此类推。这是无休止的。这就是“简单”临时解决方案的问题所在。</p>

<p>有一个简单的解决办法，但它违反直觉。通过为文章的排名添加少量随机噪声，我们可以平滑登上首页与默默无闻之间的不连续性。数学很简单，但直觉甚至更简单。想象一个过度简化的模型：对于每篇文章，每个读者以固定概率点赞，而首页比新页面吸引更多的眼球。结果如下。如果你愿意，可以用更现实的模型来练习，但结果是一样的。</p>

<p>在可以接受近似结果时，添加噪声来平滑不连续性是一个常见的技巧。我最近使用它来解决经典的浮点数问题：将一个小数加到一个大数上导致没有变化，当将许多小数加到某些大数上时就会有问题。一个简单的应用示例是保持一个降低精度的计数器，使用<code>loglog(n)</code>位来存储值。令<code>countVal(x) = 2^x</code>和<code>inc(x) = if (rand(2^x) == 0) x++</code>。像理解何时应用泰勒级数一样，这是一个简单的技巧，如果人们以前没见过，往往会印象深刻。</p>

<p><strong>更新</strong>：HN尝试过这个！Dan Gackle告诉我它效果不佳（它导致许多低质量垃圾短暂登上首页然后消失。我认为这可以通过调整一些参数来修复，但HN最终选择的解决方案，即让人（或多人）将被认为优质或有趣的提交放入“第二次机会队列”，将提交推送到首页，比任何没有直接人为干预的简单随机化算法通过参数调整所能实现的都更好。我认为这同样适用于管理，新的dang/sctb管理 regime导致评论质量显著提高，可能比今天任何基于ML的自动化解决方案都要好——Google和FB拥有世界上最先进的自动化系统，但结果质量远不如我们在HN上看到的。</p>

<p>另外，在撰写本文时（2013年），进入首页的阈值通常是2-3票，因此一个偶然路过并喜欢某个提交的人在新页面检查时的边际影响非常大。即使在非高峰时间（2019年），阈值似乎要高得多，减少了随机性。此外，HN的流行度增加导致新页面上低质量内容的大量增加，这会减少任何特定“优质”提交在随机被提升到首页的30个项目中获得曝光的机会。这并不意味着当前系统没有问题：大多数人似乎根据文章标题而不是内容来点赞和评论（要验证这一点，请阅读那些标题错误的文章的评论，在有人指出之前——通常很明显，大多数评论者甚至没有浏览过文章，更不用说阅读了），但那是另一个帖子的主题。</p>
<div>

<hr>

<ol>
<li id="fn:A">另一种看待方式是，这是对点赞的A/B测试（尽管，严谨地说，它更接近多臂老虎机）。另一种是阅读首页和新页面的人群分布不同，随机化首页可以防止阅读新页面的小圈子产生不当影响。
 <a href="#fnref:A" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:1">如果你想自己做这个练习，pg曾经说过HN的公式是：(votes - 1) / (time + 2)^1.5。分母的幂可能已经调整过，但只要它大于1.0，你就会得到合理的结果。
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:2"><a href="http://en.wikipedia.org/wiki/Kahan_summation_algorithm" rel="noopener noreferrer">Kahan求和算法</a>不够，因为根本原因与我上面给出的简化示例相同。
 <a href="#fnref:2" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:3">假设我们使用一个rand函数，返回一个介于0到n-1之间的非负整数。当x = 0时，我们从1开始计数，<a href="http://julialang.org/" rel="noopener noreferrer">就像上帝所愿那样</a>。inc(0)肯定会增加，所以我们增加并正确计数到countVal(1) = 2^1 = 2。接下来，我们以1/2的概率增加；我们需要增加两次才能增加x。这完美符合，因为countVal(2) = 2^2 = 4，所以我们希望在增加x之前增加两次。然后我们以1/4的概率增加，依此类推。
 <a href="#fnref:3" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:4">参见<a href="http://www.amazon.com/gp/product/0521835402/ref=as_li_tf_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0521835402&amp;linkCode=as2&amp;tag=abroaview-20" rel="noopener noreferrer">Mitzenmacher</a>关于随机化算法的优秀介绍，其中还解释了所有你需要知道的数学。如果你已经在睡梦中使用Chernoff界限，并且想要更深入的内容，<a href="http://www.amazon.com/gp/product/0521474655/ref=as_li_tf_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=0521474655&amp;linkCode=as2&amp;tag=abroaview-20" rel="noopener noreferrer">Motwani &amp; Raghavan</a>非常棒。
 <a href="#fnref:4" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 6381 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/randomize-hn/</link>
      <guid isPermaLink="false">https://danluu.com/randomize-hn/</guid>
      <pubDate>Fri, 4 Oct 2013 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>编写安全的Verilog代码</title>
      <description>[AI 摘要] 本文主张使用命名约定作为轻量级静态检查方法，以在硬件设计中捕获常见错误。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文主张使用命名约定作为轻量级静态检查方法，以在硬件设计中捕获常见错误。</div><p><img src="https://danluu.com/images/pl-troll/davidalbert-pl-troll.png" alt="PL troll: a statically typed language with no type declarations. Types are determined entirely using Hungarian notation" width="1160" height="448"></p>

<p>钓鱼？这就是人们编写Verilog代码的方式<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">1</a></sup>。在我以前的公司，我们有一个形式化方法博士团队编写了一个代码检查工具，该工具根据我们的命名约定对代码进行类型检查。对于我们这款小型CPU芯片，构建模型（编译）大约需要五分钟，运行一次简短测试需要十到十五分钟，而长时间测试则需要消耗数月的CPU时间。一个能在几秒钟内运行的代码检查工具的价值是显而易见的，甚至无需考虑追踪波形数小时才能找出测试失败原因的情况<sup id="fnref:2"><a href="#fn:2" rel="noopener noreferrer">2</a></sup>。</p>

<p>让我们看看一些最常用的命名约定。</p>

<p></p>

<h3 id="pipeline-stage">流水线级</h3>

<p>当对硬件进行<a href="http://en.wikipedia.org/wiki/Pipeline_(computing)" rel="noopener noreferrer">流水线</a>设计时，你会得到同一信号的多个版本，每个版本对应信号经过的流水线阶段。即使没有静态检查，你也会希望有某种简单的方法来区分它们，因此你可能将它们命名为<code>foo_s1</code>、<code>foo_s2</code>和<code>foo_s3</code>，分别表示它们来自第一、第二和第三级。在任何特定阶段，信号最可能与同一阶段的其他信号交互；访问其他阶段的逻辑通常是一个错误。虽然有访问其他阶段信号的理由，比如旁路路径和查看多个阶段的控制逻辑，但保留在单个阶段内的逻辑非常常见，因此在访问其他阶段信号时，通过“转换”或添加注释来禁用检查并不太费力。</p>

<h3 id="clock-domain">时钟域</h3>

<p>在不进行同步的情况下访问不同时钟域中的信号，就像在不进行同步的情况下从多个线程访问数据结构一样。有点类似。但更糟。严重得多。从亚稳态（信号处于0和1之间）驱动组合逻辑会消耗大量功耗<sup id="fnref:3"><a href="#fn:3" rel="noopener noreferrer">3</a></sup>。这里，我不仅仅是指效率低下。如果你拿一块90年代末的高功率芯片并移除散热片，即使在正常操作下，它也会熔化自身并粘在插座上。现代芯片的最大可能功耗如此之高，以至于如果禁用热调节，即使有散热片，芯片也会自毁。处于中间值的浮动逻辑不仅消耗大量功率，还绕过了芯片通过降低时钟频率来节能的通常能力<sup id="fnref:4"><a href="#fn:4" rel="noopener noreferrer">4</a></sup>。使用未经同步的跨时钟域信号是个坏主意，除非你喜欢随机错误、高功耗和偶尔的物理熔毁。</p>

<h3 id="module-region">模块 / 区域</h3>

<p>在高速设计中，使用来自其他模块而未先寄存的信号是一个错误。这会在仿真中悄然潜入；你只有在查看时序报告时才会注意到。在我参与的上一款芯片中，生成时序报告大约需要两天时间<sup id="fnref:and-you-couldn-t-concurrently-generate-a-new-one-for-each-build-due-to-the-license-costs-6"><a href="#fn:and-you-couldn-t-concurrently-generate-a-new-one-for-each-build-due-to-the-license-costs-6" rel="noopener noreferrer">0</a></sup>。如果你意外地引用了来自远处模块的信号，不仅该路径无法满足时序预算，综合工具还会分配资源试图加快该路径，从而拖慢所有其他路径<sup id="fnref:7"><a href="#fn:7" rel="noopener noreferrer">5</a></sup>，使整个时序报告变得毫无价值<sup id="fnref:9"><a href="#fn:9" rel="noopener noreferrer">6</a></sup>。</p>

<h3 id="pl-trolling">PL 游钓</h3>

<p>我在新工作中一直感觉缺少了什么，在没有进行任何静态检查的情况下编写Verilog。我推迟编写自己的检查工具，因为静态分析是那种需要博士学历才能做的可怕事情，对吧？而且为SystemVerilog编写解析器是一个极其庞大的任务<sup id="fnref:10"><a href="#fn:10" rel="noopener noreferrer">7</a></sup>。但事实证明，不需要太多的解析器，而且我讨论过的所有事情都足够简单，以至于在开始半小时后，我就有了一个能发现七个缺陷、只有两个误报的工具。我预计到项目结束时，代码量会增加到4倍，那么半小时的工作能发现28个缺陷，这还没考虑其中两个缺陷是在频繁使用的宏中发现的。</p>

<p>我想今天就到这里吧，但还有很多其他简单的事情可以检查，肯定会发现缺陷（例如，检查声明或赋值但未使用的寄存器/逻辑）。每当我想要解决一个独立的挑战时，也有很多不太简单的事情（例如，检查应该进行时钟门控或电源门控但没有进行的地方，这在统计上不难做，但静态分析上就不简单了）。</p>

<p>哈。看起来也没那么糟。我现在的职称已晋升为初级PL游钓手。</p>
<div>

<hr>

<ol>
<li id="fn:1">通常人们也会使用后缀和前缀。
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:2">当然，你应该编写自己的工具来脚本化与波形查看器的交互，因为波形查看器的界面太差了，但这是另一篇博客文章的内容了。
 <a href="#fnref:2" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:3">在<a href="http://en.wikipedia.org/wiki/CMOS" rel="noopener noreferrer">静态CMOS</a>中，电源与输出之间有一个晶体管网络，地线与输出之间也有一个对应的网络。作为一级近似，除了切换时，这两个网络中应该只有一个处于导通状态，这就是为什么切换逻辑门比静止逻辑门消耗更多功率——除了用于放电输出驱动电容的功耗外，还会短暂出现电源到地的直接连接。如果你陷入半开状态，就会形成电源到地的持续连接。
 <a href="#fnref:3" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:4">理论上，电源门控可能有帮助，但你不能随意对芯片上过热的某个部分进行电源门控。
 <a href="#fnref:4" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:7">有多个原因导致这会完全破坏时序报告。首先，对于任何高速设计，都没有足够的快速（宽）互连线可用。逻辑门在底层，导线在其上方。导线在更高层更宽更快，但进出快速导线存在拥塞问题，而且快速导线相对较少。它们的数量如此之少，以至于人们需要预先规划模块的放置位置，以便有足够的快速互连来满足时序需求。如果你占用一些快速导线来加快某个慢速路径，任何依赖通过该区域快速路径的东西都会受损。其次，综合工具试图将源放置在靠近汇点的位置，以减少拥塞和延迟。如果你将一个汇点放在远离其他汇点的网络上，源会迁移到中间位置，以尝试满足所有汇点的需求。这是递归性的糟糕情况，会将所有二阶源从其最优位置拉走，如此等等。
 <a href="#fnref:7" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn:9">使用某些工具时，你可以让它们避免优化那些超过一定裕量而无法满足时序的路径，但总存在某个窗口，一个糟糕的路径会破坏整个时序报告，而且通常确实存在真正的关键路径需要综合工具投入所有资源才能使其及时跨越芯片。
 <a href="#fnref:9" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
<li id="fn10">SV标准长达1300页，相比之下C++为800页，C为500页，Java为300页，Erlang为30页。
 <a href="#fnref:10" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div><p><em>由 mimo-v2.5 模型翻译，花费 4539 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/pl-troll/</link>
      <guid isPermaLink="false">https://danluu.com/pl-troll/</guid>
      <pubDate>Sun, 15 Sep 2013 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Verilog 很奇怪</title>
      <description>[AI 摘要] 本文探讨了 Verilog 作为硬件描述语言的缺陷、历史背景、使用问题及可能的现代替代方案。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文探讨了 Verilog 作为硬件描述语言的缺陷、历史背景、使用问题及可能的现代替代方案。</div><p>Verilog 是美国最常用的硬件设计语言（在欧洲，VHDL 更常见）。可惜它如此繁复。如果你浏览过 Stack Overflow 上的 Verilog 问题，你会发现大量被降级的提问，询问“为什么我的代码不工作？”，而这些代码不仅有点小错误，而且完全错误。</p>

<p><img src="https://danluu.com/images/so_verilog.png" alt="6 个问题，除一个外均为负分"></p>

<p></p>

<p>让我们看<a href="http://stackoverflow.com/questions/18678426/verilog-store-counter-value-when-reset-is-asserted" rel="noopener noreferrer">一个例子</a>：“想法是在重置时存储计数器的值……我得到 DRC 违规，且内存 bufreadaddr、bufreadval 都被优化掉了。”</p>

<pre><code>always @(negedge reset or posedge clk) begin
  if (reset == 0) begin
    d_out &lt;= 16'h0000;
    d_out_mem[resetcount] &lt;= d_out;
    laststoredvalue &lt;= d_out;
  end else begin
    d_out &lt;= d_out + 1'b1;
  end
end

always @(bufreadaddr)
  bufreadval = d_out_mem[bufreadaddr];
</code></pre>

<p>我们想要一个计数器来跟踪自重置以来的周期数，并将其存储在一个以 resetcount 为索引的类似数组的结构中。如果你读过一些 Verilog 语义，这看起来是解决问题的自然方式。发帖者了解 Verilog 中的状态元素使用‘&lt;=’，以便所有元素同时更新。每次时钟边沿，我们递增 d_out。当 reset 为 0 时，我们存储该值并重置 d_out。这能有什么问题？</p>

<p>问题在于 Verilog <a href="http://en.wikipedia.org/wiki/Verilog#History" rel="noopener noreferrer">最初是作为描述仿真的语言设计的</a>，因此它具有描述事件间任意交互的构造。当 X 从 0 变为 1 时，执行 Y。听起来很简单。但后来有人想用 Verilog 来表示硬件。你能写的大多数语句都不会转化为任何有意义的硬件。你的综合工具会帮助性地匹配最接近的可用硬件，或者如果写的东西无法转换，就什么也不产生。如果你幸运，可能会得到一些警告。</p>

<p>看上面的代码，综合工具会看到有一个叫 d_out 的东西，应该是一个在不该重置时被设置的时钟元素，否则异步重置。这是合法的硬件构造，所以它会产生一个 N 位触发器和一些逻辑，使其成为重置为 0 的计数器。顺便说一句，这一段曾包含一个指向 <a href="http://en.wikipedia.org/wiki/Flip-flop" rel="noopener noreferrer">http://en.wikipedia.org/wiki/Flip-flop(electronics) 的链接，但自从我切换到 Hugo 后，我对带括号的 URL 链接都坏了，所以如果你想了解什么是触发器，也许尝试复制粘贴该 URL 到你的浏览器窗口。</a></p><a href="http://en.wikipedia.org/wiki/Flip-flop" rel="noopener noreferrer">

<p>现在，关于我们本应在重置时存储的值呢？综合工具会看到它在一个时钟块内。但它不应该在时钟有效时做任何事情；只在重置有效时才执行。这很不寻常。会发生什么？嗯，这取决于你使用哪个版本的综合工具，以及该工具的程序员如何决定实现未定义行为。</p>

</a><p><a href="http://en.wikipedia.org/wiki/Flip-flop" rel="noopener noreferrer">然后是那个应该读取存储值的块。意图是创建一个 64:1 </a><a href="http://en.wikipedia.org/wiki/Multiplexer" rel="noopener noreferrer">多路复用器</a>。暂且不提这么宽的多路复用器带来的周期时间问题，该块没有时钟，所以综合工具必须推断某种组合逻辑。但是，输出只应在 bufreadaddr 变化时改变，而不是在 d_out_mem 变化时改变。在我们的仿真语言中描述这很容易，但综合工具会产生绝对不是用户想要的东西。更不用说 laststoredvalue 与 bufreadvalue 没有意义地连接。</p>

<p>一个合理的 Verilog 描述怎么可能在硬件中变成完全错误的东西？你可以把硬件看作某种状态，纯函数连接状态元素。这使得用函数式语言建模硬件很自然。另一种自然的方式是用面向对象。类描述硬件如何工作。类的实例是将放在芯片上的实际硬件。再一种自然的描述方式是声明式，你写下硬件必须遵守的约束，综合工具输出满足这些约束的东西。</p>

<p>Verilog 这些都做不到。要编写能产生正确硬件的 Verilog，你必须先想象你要产生的硬件。然后，你必须想出如何在这种奇怪的类似 C 的仿真语言中描述它。那将被综合成类似于你第一步想象的东西。</p>

<p>作为软件工程师，如果 99% 的有效 Java 代码最终被翻译成产生随机结果的东西，即使未翻译的 Java 代码测试通过，你会感觉如何？而且，顺便说一句，要测试翻译后的 Java 代码，你必须经历一个多天的编译过程，之后你的测试将比生产代码运行慢 2 亿倍。如果你考虑在一些沙盒生产机器上测试，当然可以，但推送到任何数量的生产机器需要 8 位数的成本，耗时 3 个月。但别担心，你可以运行未翻译的代码，只比生产慢 200 万倍<sup id="fnref:1"><a href="#fn:1" rel="noopener noreferrer">1</a></sup>。使用静态类型语言的人经常抱怨运行时错误，这些错误在具有更强类型的语言中可以通过静态检查轻松避免。我们硬件人员已经习惯了绝大多数合法的 Verilog 构造产生不可综合的垃圾，以至于我们根本不觉得奇怪，编写朴素的 Verilog 代码不仅没有编译时错误，甚至没有运行时错误。</p>

<p>老派硬件工程师会告诉你这没问题。语言如此反直觉以至于几乎所有初学者编写的代码不仅错误而且荒谬，这没问题。“你所要做的就是弄清楚设计，然后将其翻译成 Verilog”。他们会告诉你，你对正在发生的事情的心理模型基本上与语言提供的构造无关，这完全没问题，而且一旦他们有经验就永远不会犯错，就像一些有经验的 C 程序员会错误地告诉你，他们现在从不遇到与安全相关的缓冲区溢出、双重释放或内存泄漏。这让我想起与汇编程序员的对话，他们告诉我一旦你写好函数，汇编与高级语言一样高效。没有与老派汇编程序员交谈过的程序员会认为我在编造，但我认识一些人仍然坚持认为汇编与任何高级语言一样高效。但这样的人很少，而且越来越少。在硬件领域，我们每隔几年就培养出一代新的人，他们认为 Verilog 与任何语言一样高效！</p>

<p>我甚至不会深入讨论 Verilog 如此不具表达性，以至于许多公司使用临时工具在 Verilog 中嵌入脚本语言或从脚本语言生成 Verilog。</p>

<p>已经有一些尝试比将临时脚本语言塞入 Verilog 更好，但它们都失败了。作为一种易于添加语法的函数式语言，Haskell 是 Verilog 代码生成的自然选择；它催生了 ForSyDe、Hydra、Lava、HHDL 和 Bluespec。但 ForSyDe、Hydra、Lava 和 HHDL 的采用率几乎为零，不是因为语言缺陷，而是因为政治上很难让人们使用基于 Haskell 的语言。Bluespec 表现更好，但它通过使语言看起来像 C、放弃原始 Haskell 语法并引入 Bluespec SystemVerilog 和 Bluespec SystemC 来实现。对 Haskell 的反感如此严重，以至于当我们讨论我新工作中的硬件风格时，有人建议禁止任何基于 Haskell 的解决方案，即使 Bluespec 已在公司内几个项目中有效使用。</p>

<p>基于 Scala 的解决方案看起来更有希望，不是出于任何技术原因，而是因为 Scala 不那么可怕。Scala 在类型系统方面成功地将现代世界带给了比 ML、Ocaml、Haskell、Agda 等加起来更多的程序员。也许在硬件世界中也会如此。<a href="https://chisel.eecs.berkeley.edu" rel="noopener noreferrer">Chisel</a> 有趣。像 Bluespec 一样，它的模拟比 Verilog 快得多，且不可综合的表示是语法错误。它没有那么高级，但它是唯一一种我能够与硬件人员讨论而没有人反对 Haskell 是坏主意的现代类型系统的硬件描述语言。</p>

<p>商业供应商大多朝相反方向发展，因为类似 C 的语言让人感到温暖舒适。其中许多正在推动从 SystemC 甚至直接从 C 或 C++ 进行高级硬件综合。这些解决方案在政治上也难以推销，但这次是因为行业历史，而不是语言。推动高级综合的供应商有着几十年的过度承诺和交付不足的记录。我已经记不清多少次听到人们以“为什么我们应该相信这次它们是真的？”来 dismiss 现代产品。</p>

<p>未来是什么？在本地，我设法说服了团队中的几个人 Chisel 值得一看。目前，没有基于 Haskell 的解决方案在考虑之列。我接受建议。</p>

<h3 id="cpu-internals-series">CPU 内部系列</h3>

<ul>
<li><a href="https://danluu.com/cpu-bugs/" rel="noopener noreferrer">CPU 缺陷</a></li>
<li><a href="//danluu.com/new-cpu-features/" rel="noopener noreferrer">80 年代以来的新 CPU 特性</a></li>
<li><a href="//danluu.com/branch-prediction/" rel="noopener noreferrer">分支预测简史</a></li>
<li><a href="//danluu.com/integer-overflow/" rel="noopener noreferrer">真实代码中分支和整数溢出检查的成本</a></li>
<li><a href="//danluu.com/hardware-unforgiving/" rel="noopener noreferrer">为什么 CPU 开发很难</a></li>
<li><a href="//danluu.com/why-hardware-development-is-hard/" rel="noopener noreferrer">Verilog 很糟糕，第 1 部分</a></li>
<li><a href="//danluu.com/pl-troll/" rel="noopener noreferrer">Verilog 很糟糕，第 2 部分</a></li>
</ul>

<p><small>附言：亲爱的硬件人员，抱歉过度简化了这么多。我开始写脚注解释我略过的一切，直到我意识到我的脚注比帖子还长。精简后的脚注可能会在未来成为独立的博客文章。一个很长的脚注我将简要总结：语义正确的 Verilog 仿真本质上比 Bluespec 或 Chisel 等慢，因为事件模型的复杂性。EDA 供应商设法从 Verilog 中获得不错的性能，但通过雇佣大量世界顶级仿真人员来攻克问题，就像 JavaScript 快不是因为语言的任何特性，而是因为有很棒的人在 VM 上工作。当一个依靠微不足道资助预算的小团队能够产生一种语言和仿真基础设施，在现有工具中脱颖而出时，你应该明白一些事情。</small></p><small>

<p>你可能想知道我为什么没提到 linters。它们是个好主意，出于我不理解的原因，我做硬件开发的三家公司中有两家没有使用 linters。如果你问周围，每个人都会同意这是个好主意，但即使 linter 的运行成本在数千到数万美元范围内，工程师的成本在数十万美元范围内，在政治上也无法让团队使用 linter，即使团队有权使用每年数万或数十万美元许可证的工具。即使 linter 是明摆着的好事，每年在硬件开发上花费数百万到数千万美元的公司通常不使用它们，而好的 SystemVerilog linter 都超出了在 StackOverflow 上提问并被降级到无影无踪的人的价格范围。</p>
<div>

<hr>

<ol>
<li id="fn:1">根据我上一次参与芯片的大致数字。我们拥有两大主要商业模拟器的许可证，我们很幸运能在较快的模拟器上，对于在硅中运行于 2GHz 的芯片，在综合前达到 500Hz。别让我开始谈论开源模拟器。对于大多数 ASIC 工作，速度至少好 10 倍。另外，如果你不在过程中包含时序/寄生提取，你可能可以更快地完成综合。
 <a href="#fnref:1" rel="noopener noreferrer"><sup>[返回]</sup></a></li>
</ol>
</div></small><p><em>由 mimo-v2.5 模型翻译，花费 6885 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/why-hardware-development-is-hard/</link>
      <guid isPermaLink="false">https://danluu.com/why-hardware-development-is-hard/</guid>
      <pubDate>Sat, 7 Sep 2013 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>关于 danluu.com 博客</title>
      <description>[AI 摘要] 该博客旨在填补技术解释的空白，并存档互联网上已消失的内容。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 该博客旨在填补技术解释的空白，并存档互联网上已消失的内容。</div><h3 id="about-the-blog">关于本博客</h3>

<p>这个博客最初只是为了记录一些看似有趣但未被充分重视的领域的想法。此后，它发展到每月获得<a href="https://danluu.com/blog-ads/#update" rel="noopener noreferrer">数百万次访问</a>，我看到它经常被教授们在他们的课程和 StackOverflow 上引用。</p>

<p>这令人受宠若惊，但更重要的是，我认为这表明人们极其缺乏<a href="//danluu.com/teach-debugging/" rel="noopener noreferrer">易于理解的技术主题解释</a>。这里的内容，我的大多数同事都知道（也许除了我提出新想法的三四篇文章）。只是他们不写博客，而我写。我不打算<a href="https://sites.google.com/site/steveyegge2/you-should-blog" rel="noopener noreferrer">说服你开始写博客</a>，因为这必须是你自己想做的事，但我要指出，你的知识正等待填补一个巨大的空白。当我开始写这个博客时，我几乎以为没人会读；当然，Joel Spolsky 和 Steve Yegge 创造了广受欢迎的博客，但那是在几乎没人写博客的时代。现在有数百万博客，新博客根本无法获得关注。事实证明并非如此。</p>

<p>这个网站还保存了一些已从互联网上消失的内容，例如<a href="//danluu.com/subspace-history/" rel="noopener noreferrer">这款90年代电子游戏《次元空间》的历史</a>、<a href="//danluu.com/su3su2u1/physics/" rel="noopener noreferrer">su3su2u1 的物理学入门</a>、<a href="//danluu.com/su3su2u1/hpmor/" rel="noopener noreferrer">su3su2u1 对《哈利·波特与理性之道》的评论</a>、<a href="//danluu.com/symbolics-lisp-machines/" rel="noopener noreferrer">Dan Weinreb 关于 Symbolics 和 Lisp 机器的历史</a>、<a href="//danluu.com/open-social-networks/" rel="noopener noreferrer">这篇关于开放与封闭社交网络的讨论</a>、<a href="//danluu.com/mit-stanford/" rel="noopener noreferrer">这篇关于硅谷与波士顿、斯坦福与麻省理工学院差异的讨论</a>、<a href="//danluu.com/threads-faq/" rel="noopener noreferrer">comp.programming.threads 常见问题解答</a>以及<a href="//danluu.com/microsoft-culture/" rel="noopener noreferrer">这篇关于2000年微软文化的演讲</a><a href="//danluu.com/mcilroy-unix" rel="noopener noreferrer">。</a></p>

<p>附：如果你喜欢这个博客，你可能也会喜欢<a href="https://www.recurse.com/scout/click?t=b504af89e87b77920c9b60b2a1f6d5e8" rel="noopener noreferrer">RC</a>，我听说它被称为"程序员的极客营地"。</p><p><em>由 mimo-v2.5 模型翻译，花费 1664 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/about/</link>
      <guid isPermaLink="false">https://danluu.com/about/</guid>
      <pubDate>Sun, 1 Sep 2013 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>延迟缓解策略（约翰·卡马克）</title>
      <description>[AI 摘要] 该文章探讨了虚拟现实系统中从运动到光子的延迟来源与缓解策略，旨在将延迟降低至20毫秒以下。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 该文章探讨了虚拟现实系统中从运动到光子的延迟来源与缓解策略，旨在将延迟降低至20毫秒以下。</div><p><i>这是一篇约翰·卡马克的旧文章存档，该文章似乎已从互联网上消失</i></p>

<h4 id="abstract">摘要</h4>

<p>虚拟现实（VR）是从延迟角度来看，对延迟要求最苛刻的人机交互应用之一。用户头部的物理运动与来自头戴显示器的更新光子到达眼睛之间的时间延迟，是提供高质量体验最关键的因素之一。</p>

<p>人类的感觉系统能够检测到视觉或，尤其是，听觉领域中非常小的相对延迟，但当绝对延迟低于大约20毫秒时，通常无法察觉。当今的交互式3D系统的延迟通常是这个数字的几倍，但使用相同硬件组件的替代配置可以达到这个目标。</p>

<p>下文讨论了整个系统中的延迟来源，以及减少主机系统处理中延迟的技术。</p>

<h4 id="introduction">引言</h4>

<p>根据头部追踪传感器更新头戴显示器（HMD）中的图像，这是一个与大多数人机交互任务微妙不同的挑战。使用传统的鼠标或游戏控制器时，用户是在有意识地操控界面以完成任务，而虚拟现实的目标是在无意识层面接受体验。</p>

<p>用户可以适应具有显著延迟的控制系统，并仍然执行具有挑战性的任务或享受游戏；成千上万的人喜欢玩早期的网络游戏，即使在按键和看到屏幕响应之间有400毫秒以上的延迟。</p>

<p>如果VR系统中存在大量延迟，用户可能仍然能够执行任务，但这是通过一种回报率低得多的方式实现的，即将他们的头部作为控制器使用，而不是接受他们的头部自然地在一个稳定的虚拟世界中移动。感知到头部运动响应的延迟也是模拟器眩晕症的主要原因之一。其他影响VR体验质量的技术因素，如头部追踪精度和准确性，可能会与延迟的感知相互作用；或者像显示器分辨率和色彩深度这样的因素，在很大程度上与其正交。</p>

<p>总系统延迟为50毫秒会让人感觉响应良好，但仍有微妙的滞后感。在头戴显示器中观察延迟影响的最简单方法之一是，看着一条清晰的垂直边缘，沿着视线向量左右滚动头部。延迟会表现为垂直线随着头部运动而明显倾斜；视角感觉像是“被头部运动拖着走”。当延迟足够低时，虚拟世界会令人信服地感觉就像你在旋转对一个稳定世界的视角。</p>

<p>可以使用传感器数据外推来缓解一些系统延迟，但即使对人头运动有复杂的模型，在运动启动和改变时仍会出现伪影。解决问题永远比缓解问题要好，因此应积极追求真正的延迟缓解，仅将外推用于平滑传感器抖动问题并仅执行少量预测。</p>

<h4 id="data-collection">数据收集</h4>

<p>通常无法内省地测量VR系统的完整系统延迟，因为主机处理器外部的传感器和显示设备对总延迟有显著贡献。一种有效的技术是录制高速视频，该视频同时捕获引发的物理运动和最终的显示更新。然后可以通过单步处理视频并计算两个事件之间的视频帧数来确定系统延迟。</p>

<p>在大多数情况下，由于传感器速率、显示速率和摄像机速率之间的混叠，得到的时间测量值会有显著的抖动，但传统应用通常显示总延迟在数十个240 fps视频帧范围内。</p>

<p>在一个未加载的Windows 7系统上，禁用合成的Aero桌面界面时，一个游戏鼠标拖动一个显示在180 Hz CRT显示器上的窗口，可以在鼠标首次被看到移动的同一个240 fps视频帧中显示响应，证明端到端延迟低于四毫秒。这需要许多系统协同工作：鼠标每秒更新500次，无任何过滤或缓冲。操作系统立即处理更新，并立即执行GPU加速渲染直接渲染到帧缓冲区，没有页面翻转或缓冲。显示器接受视频信号时没有缓冲或处理，屏幕荧光物质在微秒内开始发射新的光子。</p>

<p>在一个典型的VR系统中，许多事情远非最优，有时导致端到端延迟超过100毫秒。</p>

<h4 id="sensors">传感器</h4>

<p>检测一个物理动作可以像观察按钮按下的电路闭合一样简单，也可以像分析实时视频流以推断位置和方向一样复杂。</p>

<p>在旧时代，执行I/O端口输入指令可以直接触发ISA总线适配卡上的模数转换，给出大约一微秒的延迟，并且没有采样抖动问题。如今，传感器本身就是系统，可能具有内部管道和队列，信息甚至在放入USB串行总线传输给主机之前就需要遍历这些队列。</p>

<p>模拟传感器在随机噪声和传感器带宽之间存在固有的矛盾，信号在返回之前通常会进行模拟和数字滤波的某种组合。有时这种滤波是过度的，这会带来显著的延迟并完全去除细微的运动。</p>

<p>在旧式串行端口或无线链路上的通信带宽延迟在某些情况下可能很重要。如果传感器消息占用了通信通道的全部带宽，则仅传输消息就会增加等于传感器重复时间的延迟。视频数据流甚至会给现代有线链路带来压力，这可能促使使用数据压缩，如果未以流水线方式明确实现，通常会增加一个完整帧的延迟。</p>

<p>滤波和通信是恒定的延迟，但大多数传感器更新的离散打包特性引入了可变延迟，或称为“抖动”，因为传感器数据用于与传感器帧率不同的视频帧速率。这种延迟范围从传感器数据包在查询前刚刚到达时的接近零，到传感器消息重复时间的值。大多数USB HID设备以每秒125个样本的速度更新，产生高达8毫秒的抖动，但某些USB硬件可以每秒接收1000次更新。操作系统可能在消息到达和用户模式应用程序有机会处理它之间施加额外的高达几毫秒的随机延迟，即使在未加载的系统上也是如此。</p>

<h4 id="displays">显示器</h4>

<p>在旧的CRT显示器上，来自视频卡的电压直接调制电子枪的电压，导致屏幕荧光物质在从帧缓冲区读取像素后几微秒内开始发射光子。</p>

<p>早期的LCD在滚动或动画期间以“拖影”而闻名，在图像更改后数十毫秒仍显示旧图像的痕迹，但在过去二十年中已取得重大进展。LCD像素的转换时间取决于转换的起始值和结束值，但当今一个良好的面板的切换时间约为十毫秒，而用于主动3D和游戏优化的显示器的切换时间可以小于这个时间的一半。</p>

<p>现代显示器还被期望在改变实际显示元素之前对接收的信号执行各种处理。当今典型的全高清显示器会接受720p或隔行复合信号，并将其转换为1920×1080物理像素。24 fps的电影素材将被转换为60 fps刷新率。立体输入可能从并排、顶底或其他格式转换为主动显示器的帧顺序格式，或被动显示器的隔行格式。可能会应用内容保护。许多面向消费者的显示器开始应用运动插值和其他复杂算法，这些算法需要多帧缓冲。</p>

<p>其中一些处理任务可以通过仅缓冲单条扫描线来完成，但其中一些从根本上需要一个或多个完整帧的缓冲，并且显示器供应商往往实现通用情况而没有针对可以低延迟或无延迟完成的情况进行优化。一些消费类显示器最终在内部缓冲三个或更多帧，导致即使输入数据本可以直接馈送到显示矩阵也会产生50毫秒的延迟。</p>

<p>一些不太常见的显示技术在速度上优于LCD面板；OLED像素可以有远低于一毫秒的切换时间，激光显示器与CRT一样即时。</p>

<p>一个微妙的延迟点是大多数显示器在图像从计算机扫描出来时增量呈现图像，这导致屏幕底部在60 fps显示器上比屏幕顶部晚16毫秒变化。这在静态显示器上很少是个问题，但在头戴显示器中，它可能导致世界在头部旋转时似乎向左右剪切，或“摆动”，因为源图像是为某一时刻生成的，但不同部分在不同时间呈现。这种效应通常被LCD HMD的切换时间所掩盖，但在快速切换的OLED HMD中很明显。</p>

<h4 id="host-processing">主机处理</h4>

<p>游戏或VR应用的经典处理模型是：</p>

<pre><code>读取用户输入 -&gt; 运行模拟 -&gt; 发出渲染命令 -&gt; 图形绘制 -&gt; 等待垂直同步 -&gt; 扫描输出

I = 输入采样和相关计算
S = 模拟 / 游戏执行
R = 渲染引擎
G = GPU绘制时间
V = 视频扫描输出时间
</code></pre>

<p>所有延迟都基于大约16毫秒的帧时间、逐行扫描显示器以及零传感器和像素延迟。</p>

<p>如果应用程序的性能需求远低于系统能够提供的性能，不采用并行重叠的简单实现通常会提供相当好的延迟值。但是，如果与视频刷新同步运行，即使系统速度无限快，最小延迟仍将是16毫秒。对于大多数手眼任务来说，这个速率感觉不错，但在头戴显示器或鼠标光标响应性中，仍然可以感觉到可感知的滞后。</p>

<pre><code>充足性能，垂直同步：
ISRG------------|VVVVVVVVVVVVVVVV|
.................. 延迟 16 – 32 毫秒
</code></pre>

<p>在非常快的系统上不进行垂直同步运行将提供更好的延迟，但仅在屏幕的一部分区域上可见，并且带有可见的撕裂线。撕裂线的影响与被撕裂的两帧之间的差异以及撕裂线可见的时间量有关。撕裂线在连续发光的LCD上比在CRT或激光投影仪上看起来更糟，并且在60 fps显示器上比在120 fps显示器上更糟。部分抵消这一点的是，慢切换LCD面板模糊了撕裂线相对于更快显示器的影响。</p>

<p>如果渲染足够多的帧，使得每条扫描线都有一幅独特的图像，那么效果将是“滚动快门”，而不是可见的撕裂线，图像会感觉是连续的。不幸的是，即使每秒渲染1000帧，给出屏幕上大约15个由撕裂线分隔的条带，在快速切换的显示器上仍然相当令人反感，并且很少有场景能够以该速率渲染，更不用说为了在1080P显示器上实现真正的滚动快门而高出60倍的速率。</p>

<pre><code>充足性能，不同步：
ISRG
VVVVV
..... 延迟 5 – 8 毫秒，约 200 帧/秒
</code></pre>

<p>在大多数情况下，性能是一个持续的关注点，因此采用并行流水线架构以允许多个处理器并行而不是顺序工作。GPU上的大命令缓冲区可以缓冲整个帧的绘制命令，这允许它们与CPU上的工作重叠，这通常以增加延迟为代价显著提高帧率。</p>

<pre><code>CPU:ISSSSSRRRRRR----|
GPU:                |GGGGGGGGGGG----|
VID:                |               |VVVVVVVVVVVVVVVV|
    .................................. 延迟 32 – 48 毫秒
</code></pre>

<p>当模拟和渲染的CPU负载不再适合单帧时，可以使用多个CPU核心并行产生更多帧。在某些情况下，可以在不增加延迟的情况下减少帧执行时间，但模拟和渲染的自然划分通常已被用于实现有效的流水线并行操作。缓冲以实现最大重叠的工作队列方法如果在关键的用户响应路径上，可能会导致额外的帧延迟。</p>

<pre><code>CPU1:ISSSSSSSS-------|
CPU2:                |RRRRRRRRR-------|
GPU :                |                |GGGGGGGGGG------|
VID :                |                |                |VVVVVVVVVVVVVVVV|
     .................................................... 延迟 48 – 64 毫秒
</code></pre>

<p>即使应用程序以完美的60 fps运行，它仍然可能有超过50毫秒的主机延迟，而目标为30 fps的应用程序的延迟可能是其两倍。传感器和显示器延迟在此基础上可以增加大量额外的延迟，因此实现20毫秒从运动到光子的延迟目标是具有挑战性的。</p>

<h4 id="latency-reduction-strategies">延迟减少策略</h4>

<h4 id="prevent-gpu-buffering">防止GPU缓冲</h4>

<p>赢得帧率基准测试竞赛的驱动力导致驱动程序编写者积极缓冲绘制命令，甚至有案例中驱动程序为了提高“性能”而忽略了对glFinish()的显式调用。今天的围栏原语似乎可靠地遵循了绘制原语，但缓冲区交换的语义仍然令人担忧地不精确。推荐的用于与垂直回描同步并空闲GPU的命令序列是：</p>

<pre><code>SwapBuffers();
DrawTinyPrimitive();
InsertGPUFence();
BlockUntilFenceIsReached();
</code></pre>

<p>虽然这应该总是在任何符合标准的驱动程序上防止过度的命令缓冲，但如果驱动程序透明地实现三重缓冲，它可能无法提供准确的垂直同步时间点。</p>

<p>为了最小化与GPU同步的性能影响，重要的是在同步完成后立即有足够的工作准备发送给GPU。GPU何时可以开始执行命令的确切细节是平台特定的，但可以使用glFlush()或等效调用显式启动执行。如果发出绘制命令的代码进行得不够快，GPU可能完成所有工作并因“流水线气泡”而空闲。由于发出绘制命令的CPU时间可能与绘制所需的GPU时间几乎没有关系，这些流水线气泡可能导致GPU绘制帧的时间明显长于完全缓冲的情况。将较大和较慢的操作安排先执行将提供一个缓冲，将尽可能多的准备工作推到同步点之前也是如此。</p>

<pre><code>以最小缓冲运行GPU：
CPU1:ISSSSSSSS-------|
CPU2:                |RRRRRRRRR-------|
GPU :                |-GGGGGGGGGG-----|
VID :                |                |VVVVVVVVVVVVVVVV|
     ................................... 延迟 32 – 48 毫秒
</code></pre>

<p>平铺渲染器（如大多数移动设备中的）本质上需要整个场景的命令缓冲才能生成其第一个像素块，因此在发出任何命令之前进行同步将破坏更多的重叠。在现代渲染引擎中，每帧可能有多次场景渲染以处理阴影、反射和其他效果，但增加延迟仍然是该技术的根本缺点。</p>

<p>当今的高端多GPU系统通常配置为AFR（交替帧渲染），其中每个GPU被允许用两倍的时间渲染单帧，但总体帧率得以维持，因为有两个GPU产生帧。</p>

<pre><code>交替帧渲染双GPU：
CPU1:IOSSSSSSS-------|IOSSSSSSS-------|
CPU2:                |RRRRRRRRR-------|RRRRRRRRR-------|
GPU1:                | GGGGGGGGGGGGGGGGGGGGGGGG--------|
GPU2:                |                | GGGGGGGGGGGGGGGGGGGGGGG---------|
VID :                |                |                |VVVVVVVVVVVVVVVV|
     .................................................... 延迟 48 – 64 毫秒
</code></pre>

<p>与CPU工作负载的情况类似，可以让两个或多个GPU以某种方式合作渲染单帧，在恒定时间内提供更多的工作，但这增加了复杂性，并且通常提供较低的总体加速。</p>

<p>立体渲染的一个有吸引力的方向是让双GPU系统中的每个GPU渲染一只眼睛，这将提供最大性能和最小延迟，代价是应用程序需要在两个独立的渲染上下文中维护缓冲区。</p>

<p>防止GPU缓冲的缺点是吞吐量性能可能下降，在高负载条件下导致更多的丢帧。</p>

<h4 id="late-frame-scheduling">晚期帧调度</h4>

<p>模拟任务中的大部分工作并不直接依赖于用户输入，或者对其中一帧的延迟不敏感。如果用户处理最后完成，并且在需要之前才对输入进行采样，而不是在帧开始时存储，那么总延迟可以减少。</p>

<p>预测整个世界的一般模拟工作所需的时间非常困难，但仅针对玩家视角对传感器输入响应的工作可以做到本质上的确定性。如果将其从主要模拟任务中分离出来并延迟到接近帧结束时，可以减少近一整帧的延迟。</p>

<pre><code>晚期帧调度：
CPU1:SSSSSSSSS------I|
CPU2:                |RRRRRRRRR-------|
GPU :                |-GGGGGGGGGG-----|
VID :                |                |VVVVVVVVVVVVVVVV|
                    .................... 延迟 18 – 34 毫秒
</code></pre>

<p>调整视角是最延迟敏感的任务；由其他用户命令产生的动作，如武器动画或与世界中其他对象的交互，通常对额外的一帧延迟不敏感，可以在下一帧的通用模拟任务中处理。</p>

<p>晚期帧调度的缺点是它引入了严格的调度要求，通常需要忙等待来满足，浪费电力。如果你的帧率是由视频回描决定的而不是任意的时间片，那么图形驱动程序在准确确定当前扫描输出位置方面的帮助会很有用。</p>

<h4 id="view-bypass">视角旁路</h4>

<p>实现类似或稍大延迟减少的另一种方式是允许渲染代码根据更新的用户输入采样，修改游戏代码传递给它的参数。</p>

<p>在最简单的层面上，用户输入可用于计算从上一次采样到当前采样的增量，该增量可用于修改游戏提交给渲染代码的视图矩阵。</p>

<p>这种增量处理方式侵入性最小，但通常会有用户输入不应影响渲染的情况，例如电影过场动画或玩家死亡时。可以认为，从一开始就为虚拟现实设计的游戏应避免这些情况，因为在HMD中无响应的视角是令人迷惑和不愉快的，但传统游戏设计中有许多此类情况。</p>

<p>可以提供一个二进制标志来禁用旁路计算，但将其泛化是有用的，即游戏提供一个带有嵌入状态的对象或函数，该函数从传感器输入数据生成渲染参数，而不是让游戏提供视图参数本身。除了处理忽略传感器输入的简单情况外，生成器函数还可以包含额外的信息，例如基于方向修改位置的头/颈定位模型，或相对于更新的视图定位的其他模型列表。</p>

<p>如果游戏和渲染代码并行运行，重要的是参数生成函数不要引用任何游戏状态，以避免竞争条件。</p>

<pre><code>视角旁路：
CPU1:ISSSSSSSSS------|
CPU2:                |IRRRRRRRRR------|
GPU :                |--GGGGGGGGGG----|
VID :                |                |VVVVVVVVVVVVVVVV|
                      .................. 延迟 16 – 32 毫秒
</code></pre>

<p>输入每帧仅采样一次，但同时被模拟任务和渲染任务使用。现在一些输入处理工作被模拟任务和渲染任务重复执行，但通常是最小的。</p>

<p>由生成器函数生成的参数的延迟现在减少了，但与世界的其他交互，如枪口闪光和物理响应，仍然保持与标准模型相同的延迟。</p>

<p>修改形式的视角旁路可以允许基于平块的GPU实现与非平块GPU相似的视角延迟，或者允许非平块GPU通过以下步骤实现100%利用率而没有流水线气泡：</p>

<p>禁止执行GPU命令，强制它们被缓冲。OpenGL只有已弃用的显示列表功能可以近似实现这一点，但可以制定一个控制扩展。</p>

<p>所有依赖于视图矩阵的计算必须从缓冲对象独立引用它，而不是从内联参数或复合模型视图投影（MVP）矩阵引用。</p>

<p>在所有命令发出并且下一帧开始之后，采样用户输入，将其通过参数生成器运行，并将生成的视图矩阵放入缓冲对象供绘制命令引用。</p>

<p>启动绘制命令执行。</p>

<pre><code>平块优化视角旁路：
CPU1:ISSSSSSSSS------|
CPU2:                |IRRRRRRRRRR-----|I
GPU :                |                |-GGGGGGGGGG-----|
VID :                |                |                |VVVVVVVVVVVVVVVV|
                                       .................. 延迟 16 – 32 毫秒
</code></pre>

<p>如果新的视图矩阵与渲染任务期间使用的矩阵相比发生了足够大的变化，为避免绘制某些模型而执行的任何视锥体剔除可能是无效的。可以通过使用更大的视锥体视场进行剔除，以及基于剔除视锥体限制的硬件裁剪平面来保证干净的边缘（如果需要），但这会以一定的性能为代价来缓解此问题。剔除错误，即本应被剔除的物体遮挡的明亮物体可见，是非常令人分心的，但在快速旋转期间屏幕边缘临时干净地侵入黑色几乎不会被注意到。</p>

<h4 id="time-warping">时间扭曲</h4>

<p>如果你完美地知道渲染一帧需要多长时间，可以通过晚期帧调度整个渲染任务来节省一些额外的延迟，但由于帧渲染时间的广泛可变性，这是不切实际的。</p>

<pre><code>晚期帧输入采样视角旁路：
CPU1:ISSSSSSSSS------|
CPU2:                |----IRRRRRRRRR--|
GPU :                |------GGGGGGGGGG|
VID :                |                |VVVVVVVVVVVVVVVV|
                          .............. 延迟 12 – 28 毫秒
</code></pre>

<p>然而，渲染图像上的后处理任务可以预期在相当可预测的时间内完成，并且可以更容易地进行晚期调度。屏幕上的任何像素，连同相关的深度缓冲区值，都可以转换回世界空间位置，然后可以重新变换到不同的屏幕空间像素位置，以适应修改后的视图参数集。</p>

<p>在使用您手头的最佳信息（可能使用了旁路视图参数）绘制一帧后，不要直接显示它，而是获取最新的用户输入，生成更新的视图参数，并计算一个变换，将渲染的图像扭曲到一个近似于其在更新参数下的位置。使用该变换，将渲染的图像扭曲成屏幕上反映新输入的更新形式。如果屏幕上存在需要保持固定的二维叠加层，则必须在扭曲操作后绘制或合成它们，以防止它们随着视图参数的变化而错误地移动。</p>

<pre><code>晚期帧调度时间扭曲：
CPU1:ISSSSSSSSS------|
CPU2:                |RRRRRRRRRR----IR|
GPU :                |-GGGGGGGGGG----G|
VID :                |                |VVVVVVVVVVVVVVVV|
                                    .... 延迟 2 – 18 毫秒
</code></pre>

<p>如果场景渲染时的视图参数与最终扭曲时的视图参数之间的差异仅是方向的变化，则在图像过滤的限制范围内，扭曲后的图像几乎可以完全正确。相对于屏幕计算的效果，如基于深度的雾（相对于基于距离的雾）和告示板精灵将略有不同，但不会令人反感。</p>

<p>如果扭曲除了方向变化外还涉及平移，几何轮廓边缘开始引入伪影，其中内部视差会揭示原始渲染中不可见的表面。没有轮廓边缘的场景，如盒子内部，可以被显著扭曲，并且只显示纹理密度的变化，但平移扭曲现实场景将导致沿边缘的涂抹或间隙。在许多情况下，这些很难被注意到，并且当运动停止时它们总是会消失，但第一人称视角的手和武器是一个突出的情况。可以通过限制平移扭曲量、压缩或使被扭曲场景的深度范围恒定以限制动态分离，或者将断开的近场对象渲染为单独的平面，在扭曲后合成它们来缓解此问题。</p>

<p>如果将图像扭曲到具有相同视场的目标，大多数扭曲将使新图像的某些角落或边缘未定义，因为没有源像素扭曲到它们的位置。可以通过渲染比目标所需更大的视场来缓解此问题；但简单地留下未渲染的像素为黑色出人意料地不那么碍眼，尤其是在宽视场HMD中。</p>

<p>正向扭曲，其中源像素被放置在其新位置，为任意变换提供了最佳精度。在极限情况下，帧缓冲区和深度缓冲区可以被视为高度场，但数百万个半像素大小的三角形将具有严重的性能成本。使用某个深度缓冲区分辨率分数的三角网格可以将成本降低到非常低的水平，而将渲染的图像视为单个四边形的简单情况可以避免所有轮廓伪影，代价是平移下的像素位置不正确。</p>

<p>反向扭曲，其中根据扭曲图像中的位置估计源渲染中的像素，可能更方便，因为它完全在片段着色器中实现。对于简单的方向变化，它可以产生相同的结果，但如果考虑每像素深度信息，除非投入大量努力搜索邻居以找到最佳源像素，否则在几何边界附近会引入额外的伪影。</p>

<p>如果需要，通过沿着从被扭曲的像素到源图像中变换位置的线进行几次采样，在反向映射中包含运动模糊是简单的。</p>

<p>反向映射还允许通过视频扫描输出修改扭曲的可能性。可以提前预测视图参数，直到扫描输出将读取最后一行像素，这可以用于生成第二个扭曲矩阵。要应用的扭曲可以在两个矩阵之间基于正在处理的像素行进行插值。这可以校正逐行扫描头戴显示器上的“摆动”效应，其中在快速切换显示器上显示顶行和底行的16毫秒时间差导致快速旋转下感知的世界剪切。</p>

<h4 id="continuously-updated-time-warping">持续更新的时间扭曲</h4>

<p>如果存在必要的反馈和调度机制，可以增量地执行扭曲，而不是在帧底预测扭曲变换应该是什么并一次扭曲整个屏幕，同时在新的输入到达时持续更新扭曲矩阵。</p>

<pre><code>持续时间扭曲：
CPU1:ISSSSSSSSS------|
CPU2:                |RRRRRRRRRRR-----|
GPU :                |-GGGGGGGGGGGG---|
WARP:                |               W| W W W W W W W W|
VID :                |                |VVVVVVVVVVVVVVVV|
                                     ... 延迟 2 – 3 毫秒，500Hz传感器更新
</code></pre>

<p>这样做的理想接口是某种形式的“扫描输出着色器”，它将在“刚好及时”用于视频显示时被调用。几个视频游戏系统，如Atari 2600、Jaguar和Nintendo DS，都有半条扫描线到几条扫描线的缓冲区，正是以这种方式填充的。</p>

<p>在没有新硬件支持的情况下，仍然可以增量地将扭曲直接执行到正在被视频扫描的前缓冲区，并且根本不执行缓冲区交换操作。</p>

<p>可以有一个CPU核心专门用于以视频输出消耗的速度扭曲扫描线的任务，在每条扫描线更新时间扭曲矩阵以混合最近到达的传感器信息。</p>

<p>GPU可以比传统CPU更高效地执行时间扭曲操作，但GPU在视频扫描输出期间忙于绘制下一帧，并且由于深度流水线和广泛上下文状态的任务切换难度，GPU绘制操作目前无法以高精度调度。然而，现代GPU开始允许计算任务与图形操作并行运行，这可能允许一部分GPU专门用于执行扭曲操作，同时由CPU更新共享参数缓冲区。</p>

<h4 id="discussion">讨论</h4>

<p>视角旁路和时间扭曲是互补的技术，可以独立应用或一起应用。时间扭曲可以从任意视图时间/位置的源图像扭曲到任何其他图像，但内部视差和屏幕边缘钳制的伪影通过使用尽可能新的源图像来减少，而视角旁路渲染有助于提供这一点。</p>

<p>需要模拟状态更改的操作，如翻转开关或开火，仍然需要通过完整的管道，产生32-48毫秒的延迟，具体取决于结果最终显示在屏幕上的扫描线，并且平移信息在视角旁路渲染的16-32毫秒延迟下可能无法完全忠实表示，但关键的头部朝向反馈可以在60 Hz显示器上以2-18毫秒的速度提供。结合低延迟传感器和显示器，这通常会被感知为即时的。持续时间扭曲开辟了低于3毫秒延迟的可能性，这可能跨入人机交互中很大程度上未探索的阈值。</p>

<p>传统计算机界面通常不像虚拟现实那样对延迟敏感，但敏感的用户可以在鼠标响应上分辨出相同的约20毫秒的差异，使得即使在没有VR焦点的应用程序中也值得应用这些技术。</p>

<p>一个特别有趣的应用是在“云游戏”中，简单的客户端设备或应用程序将控制信息转发到远程服务器，服务器流式传输游戏的实时视频。这为用户提供了显著的便利性优势，但固有的网络和压缩延迟使其对于动作导向的游戏来说质量较低。视角旁路和时间扭曲都可以在服务器上执行，恢复网络施加的大部分延迟。如果云游戏客户端更复杂，时间扭曲可以在本地执行，理论上可以将延迟降低到与本地应用程序相同的水平，但可能明智的做法是将总时间扭曲量限制在大约30或40毫秒，以限制与源图像的距离。</p>

<h4 id="acknowledgements">致谢</h4>

<p>Zenimax允许我公开发布此内容。</p>

<p>Hillcrest Labs提供惯性传感器和实验固件。</p>

<p>Emagin提供OLED显示器访问。</p>

<p>Oculus提供原型Rift HMD。</p>

<p>Nvidia提供具有当前扫描线编号访问权限的实验驱动程序。</p><p><em>由 mimo-v2.5 模型翻译，花费 14190 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/latency-mitigation/</link>
      <guid isPermaLink="false">https://danluu.com/latency-mitigation/</guid>
      <pubDate>Tue, 5 Mar 2013 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>卡拉·斯威舍尔采访杰克·多西</title>
      <description>[AI 摘要] 卡拉·斯威舍尔通过推特线程采访杰克·多西，讨论了推特平台的健康、内容审核、政治影响及公司战略，并展示了当前对话形式的挑战。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 卡拉·斯威舍尔通过推特线程采访杰克·多西，讨论了推特平台的健康、内容审核、政治影响及公司战略，并展示了当前对话形式的挑战。</div><p><i>这是2019年2月12日卡拉·斯威舍尔/杰克·多西访谈的文字记录，通过解析原始推文生成，以便能线性阅读。虽然Twitter有一个“时刻”功能试图追踪本次对话，但它无法区分子线程，因此你看不出线程结束和普通回复的区别。这份线性化的访谈记录用分页符标记了每个线程的中断，并在相关处提供了来自上游线程的上下文（以灰色文本表示）。</i></p>

<p><a href="https://twitter.com/karaswisher/status/1095440667373899776" rel="noopener noreferrer"><strong>卡拉</strong></a>：我在我的推特访谈中，穿着我最汗湿的@soulcycle装备，在@soulcycle总部，@Laur_Katz已就位。另外，我搞定了@cheezit。#karajack</p>

<p><a href="https://twitter.com/karaswisher/status/1095442716346011649" rel="noopener noreferrer"><strong>卡拉</strong></a>：噢，嗨 @jack。让我先设定一下。首先，我对胡须护身符或马克·扎克伯格给你上的奇怪食物不感兴趣（尽管我个人对这两者都感到困惑）。其次，我希望能得到非常具体的回答。</p>

<p><a href="https://twitter.com/jack/status/1095442976497496064" rel="noopener noreferrer">杰克</a>：明白。我这边的安排是：我周二在家工作。在我的厨房里。用TweetDeck。身边没人，也没有人连接到我的TweetDeck。只有我，专注于回答你的问题！</p>

<p><a href="https://twitter.com/karaswisher/status/1095443173260681217" rel="noopener noreferrer"><strong>卡拉</strong></a>：很好，那我们开始吧</p>

<p><a href="https://twitter.com/jack/status/1095443608340115457" rel="noopener noreferrer">杰克</a>：准备好了</p>

<hr>

<p><a href="https://twitter.com/karaswisher/status/1095442857987661825" rel="noopener noreferrer"><strong>卡拉</strong></a>：正如@ashleyfeinberg所写：“逼他给出一个清晰、明确的例子，几乎任何事，多西就会关机。” 我认为这并非不公平的评价。第三，我会融入观众的问题，但为了不混乱，我们保持在一条回复线程里。</p>

<p><a href="https://twitter.com/jack/status/1095443050271145984" rel="noopener noreferrer">杰克</a>：成交</p>

<hr>

<p><a href="https://twitter.com/karaswisher/status/1095442999616724993" rel="noopener noreferrer"><strong>卡拉</strong></a>：向观众明确一下，并没有一个新的活动产品，比如一个玻璃屋，让人们能看到我们但不能评论。我会提问，然后回应@jack的回答。所以可能会是一场混乱。</p>

<p><a href="https://twitter.com/jack/status/1095443402131271680" rel="noopener noreferrer">杰克</a>：明确一点，我们对这样的体验很感兴趣。但还没有任何建成的东西。这让我们能感知一下它会是什么样子，以及我们需要关注什么。如果这里真有东西的话！</p>

<p><a href="https://twitter.com/karaswisher/status/1095443579147902977" rel="noopener noreferrer"><strong>卡拉</strong></a>：嗯，一个活动产品会很不错。看看我为什么总说你们行动不够快。</p>

<hr>

<p><a href="https://twitter.com/karaswisher/status/1095443416148787202" rel="noopener noreferrer"><strong>卡拉</strong></a>：总体上，这是我的心情，我认为很多人在谈论修复社交媒体和科技的弊端时也是如此：为什么你们行动不快点？为什么行动不快点？为什么行动不快点？</p>

<p><a href="https://twitter.com/jack/status/1095444282742173696" rel="noopener noreferrer">杰克</a>：这是我们不断自问的问题。过去我认为我们试图同时做太多事。现在我们更擅长按影响来确定优先级。相信我们应该关注的首要事情是某人的身体安全。这一条陈述会引出很多后果。</p>

<p><a href="https://twitter.com/karaswisher/status/1095444602545418246" rel="noopener noreferrer"><strong>卡拉</strong></a>：看来推特在考虑/思考对话健康（这涉及到安全）方面，已经停滞了大约18-24个月。你们取得了哪些实际进展？能给我具体指出来吗？</p>

<hr>

<p><a href="https://twitter.com/karaswisher/status/1095443704448397312" rel="noopener noreferrer"><strong>卡拉</strong></a>：你知道我最近的热门话题是科技责任。你给硅谷打几分？你自己呢？</p>

<p><a href="https://twitter.com/jack/status/1095444570060419072" rel="noopener noreferrer">杰克</a>：我自己？C。我们取得了一些进展，但进展零散且感觉不够。改变体验不够有意义。而且我们把大部分责任都放在了虐待行为的受害者身上（这是个巨大的失败）。</p>

<p><a href="https://twitter.com/karaswisher/status/1095444895156789253" rel="noopener noreferrer"><strong>卡拉</strong></a>：嗯，这就像是告诉我我病了，然后让我自己负责治好它。是你创造了这个产品，你运营这个平台。说这是个巨大的失败，对许多人来说都是在推脱。对我来说就是。</p>

<p><a href="https://twitter.com/jack/status/1095445502131195904" rel="noopener noreferrer">杰克</a>：把责任放在受害者身上？是的。这是承认我们必须在执行和促进健康对话方面积极主动。这是我们健康工作的首要任务。我们必须改变产品的很多基本面才能解决。</p>

<p><a href="https://twitter.com/karaswisher/status/1095445998288207872" rel="noopener noreferrer"><strong>卡拉</strong></a>：请具体一点。我看到很多在这方面都是“摸胡子”（无意冒犯你的林肯造型，但它确实如此）。你们正在改变什么？具体点。</p>

<p><a href="https://twitter.com/jack/status/1095446428065837056" rel="noopener noreferrer">杰克</a>：首要的是，我们正在寻找主动执行和促进健康的方法。这样举报/屏蔽就是最后的手段。我们试图解决的问题是把这些工作从用户手中拿走。</p>

<p><a href="https://twitter.com/karaswisher/status/1095446571905441793" rel="noopener noreferrer"><strong>卡拉</strong></a>：好吧，说出三个举措。</p>

<hr>

<p></p><p>杰克：我自己？C。我们取得了一些进展，但进展零散且感觉不够。改变体验不够有意义。而且我们把大部分责任都放在了虐待行为的受害者身上（这是个巨大的失败）。
</p>

<p><a href="https://twitter.com/karaswisher/status/1095445631278243840" rel="noopener noreferrer"><strong>卡拉</strong></a>：另外，我儿子编码得了个C，那也不是什么悲剧。你得C意义重大得多。</p>

<p><a href="https://twitter.com/jack/status/1095446128844189696" rel="noopener noreferrer">杰克</a>：同意意义重大。这是我们最需要解决和修复的问题。我指出，我们把大部分责任放在受害者身上，这是我们的失败。这就是目前服务的运作方式。</p>

<p><a href="https://twitter.com/karaswisher/status/1095446310247907328" rel="noopener noreferrer"><strong>卡拉</strong></a>：好吧，但我真的想深入探讨一下如何做。为了平衡推特能提供的好处，你们愿意承受多大的负面影响？请具体说明。</p>

<p><a href="https://twitter.com/jack/status/1095447340809220097" rel="noopener noreferrer">杰克</a>：这正是我们必须深入思考的平衡。但在这样做时，我们必须审视产品如何运作，以及滥用行为发生最多的地方：回复、提及、搜索和趋势。这些是人们利用最多的共享空间。</p>

<p><a href="https://twitter.com/karaswisher/status/1095447844595662852" rel="noopener noreferrer"><strong>卡拉</strong></a>：那么，滥用行为最常发生在何处？</p>

<p><a href="https://twitter.com/jack/status/1095448481215336448" rel="noopener noreferrer">杰克</a>：在平台内？很可能是在回复里。这就是为什么我们更积极地主动将不良行为者降级到中间页面之后，例如。</p>

<p><a href="https://twitter.com/karaswisher/status/1095448920757600258" rel="noopener noreferrer"><strong>卡拉</strong></a>：为什么不干脆更严格地踢出违规者？看来你们容忍了很多。如果推特管理我的房子，我的孩子们会吃拉面、玩《荒野大镖客2》，还穿着脏袜子。</p>

<p><a href="https://twitter.com/jack/status/1095449592710148096" rel="noopener noreferrer">杰克</a>：
我们针对违反我们政策的行为采取了所有能采取的措施。我们目前的系统大部分是被动地应对用户举报。如果他们不举报，我们就看不到。这无法规模化。因此需要专注于主动处理。</p>

<p><a href="https://twitter.com/karaswisher/status/1095450001101209600" rel="noopener noreferrer"><strong>卡拉</strong></a>：但你们为什么没看到？运营一个平台，对用户在上面的行为给予一些关注，这似乎很基本？你能给我一些见解，为什么之前没做到吗？</p>

<p><a href="https://twitter.com/jack/status/1095450592200151040" rel="noopener noreferrer">杰克</a>：我认为我们过去试图同时做太多事，这导致了答案稀释，没有任何影响力。我们需要在全球范围内解决的问题很多。我们必须根据影响来分配资源。否则我们不会有多大进展。</p>

<p><a href="https://twitter.com/karaswisher/status/1095451260088668161" rel="noopener noreferrer"><strong>卡拉</strong></a>：明白了。但你是否认为，你们所有人无法设身处地理解不安全的感觉（女性、有色人种、LGBTQ和其他边缘群体）可能是问题之一？（新话题即将到来）</p>

<p><a href="https://twitter.com/jack/status/1095451822049783808" rel="noopener noreferrer">杰克</a>：我认为这是公平且真实的。毫无疑问。我们的组织必须反映我们试图服务的人群。我们多年前建立信任与安全委员会的原因之一，就是获取反馈并审视自己。</p>

<p><a href="https://twitter.com/karaswisher/status/1095451952253677569" rel="noopener noreferrer"><strong>卡拉</strong></a>：是的，但我要三个具体例子。</p>

<hr>

<p></p><p>杰克：首要的是，我们正在寻找主动执行和促进健康的方法。这样举报/屏蔽就是最后的手段。我们试图解决的问题是把这些工作从用户手中拿走。</p>

<p><a href="https://twitter.com/karaswisher/status/1095447228615983106" rel="noopener noreferrer"><strong>卡拉</strong></a>：或者，也许，告诉我你认为你在治疗的疾病是什么？我认为不知道疾病就无法治疗。还是说你们创造了病毒？</p>

<p><a href="https://twitter.com/jack/status/1095447781571932160" rel="noopener noreferrer">杰克</a>：好问题。这就是为什么我们专注于理解对话健康意味着什么。我们在数字对话中看到了大量对健康的威胁。我们首先关注的是平台外的影响（身体安全）。这明确了政策和执行的优先级。</p>

<p><a href="https://twitter.com/karaswisher/status/1095448255301861376" rel="noopener noreferrer"><strong>卡拉</strong></a>：我还是不明白。“平台外影响”到底是什么？你们不会建立一支警察部队，对吧？你们是911吗？</p>

<p><a href="https://twitter.com/jack/status/1095448881054150656" rel="noopener noreferrer">杰克</a>：不，不是警察部队。我的意思是，我们首先必须考虑线上活动如何影响身体安全，以此作为我们努力的优先事项。我认为像我们这样的公司没有足够承认或关注这一点。</p>

<p><a href="https://twitter.com/karaswisher/status/1095449262639513600" rel="noopener noreferrer"><strong>卡拉</strong></a>：所以你们确实看到了你们的所作所为与人们现实生活危险之间的联系？你能明确说出来吗？我甚至无法@finkd去承认他创造的东西导致了真实的悲剧。</p>

<p><a href="https://twitter.com/jack/status/1095450020193525761" rel="noopener noreferrer">杰克</a>：我看到了这种联系，这就是为什么我们需要将身体安全置于一切之上。这正是我们现在正在弄清楚如何做到的事情。我们还没有所有的答案。但这就是重点。我认为这澄清了很多我们需要做的工作。当然不是全部。</p>

<p><a href="https://twitter.com/karaswisher/status/1095450868902703106" rel="noopener noreferrer"><strong>卡拉</strong></a>：在这点上，我给你们打F，这还算客气的。我无意显得混蛋，但你们所有科技公司都花了非常慢的速度才开始关注这个问题。你认为这是为什么？我认为是因为许多创造推特的人从未感到过不安全。</p>

<p><a href="https://twitter.com/jack/status/1095451280443506689" rel="noopener noreferrer">杰克</a>：很可能是一个原因。我确信缺乏多样性无助于培养对人们在推特上，特别是女性，每天所经历的事情的同理心。</p>

<p><a href="https://twitter.com/karaswisher/status/1095451773983227904" rel="noopener noreferrer"><strong>卡拉</strong></a>：那么，为了结束这个话题，我再试一次。请给我三个你们为解决这个问题而采取的具体事情。具体。</p>

<p><a href="https://twitter.com/jack/status/1095452345423429632" rel="noopener noreferrer">杰克</a>：1. 我们更新了政策。
2. 我们优先考虑主动执行，以减轻受害者的负担。
3. 我们在产品中赋予了更多控制权（例如静音没有头像或关联电话/邮箱的账户）。
4. 对协同行为/游戏化打击更严厉。</p>

<p><a href="https://twitter.com/karaswisher/status/1095452756331106307" rel="noopener noreferrer"><strong>卡拉</strong></a>：1. 哪些？
2. 如何做？
3. 好的，静音但那是很久以前的事了
4. 还有什么？
我认为人们迫切需要具体细节。</p>

<p><a href="https://twitter.com/jack/status/1095453539650830337" rel="noopener noreferrer">杰克</a>：1. 例如错代性别政策。
2. 使用机器学习将不良行为者降级到中间页面之后。
3. 不是很久以前，但我们未来的工作将大部分是产品功能。
4. 不确定问题是什么。我们建立了一整套模型来最小化系统的游戏化。</p>

<p><a href="https://twitter.com/karaswisher/status/1095453806442266624" rel="noopener noreferrer"><strong>卡拉</strong></a>：谢谢。我指的是第4点更具体的信息。但看看推特清理那篇。</p>

<p><a href="https://twitter.com/jack/status/1095454064047845376" rel="noopener noreferrer">杰克</a>：刚刚回复了那个。没看到推特清理那篇。</p>

<p><a href="https://twitter.com/karaswisher/status/1095454371494719493" rel="noopener noreferrer"><strong>卡拉</strong></a>：我想离开马克加入的线程！好像他需要更多我似的。</p>

<p><a href="https://twitter.com/jack/status/1095454523311644672" rel="noopener noreferrer">杰克</a>：他经常看这个吗？</p>

<p><a href="https://twitter.com/karaswisher/status/1095454681034342400" rel="noopener noreferrer"><strong>卡拉</strong></a>：不，他忙着修理Facebook呢。才不是！（他让你看起来不错）</p>

<p><a href="https://twitter.com/karaswisher/status/1095455420020375552" rel="noopener noreferrer"><strong>卡拉</strong></a>：我要开一个新的线程，让大家更容易关注（@waltmossberg刚发短信给我说这是个“混乱的地狱坑”）。待在那个线程里。好吗？</p>

<p><a href="https://twitter.com/jack/status/1095455732458110976" rel="noopener noreferrer">杰克</a>：好的。对话确实不容易跟随。这正是我们做这件事的原因。我相信修复这类事情会有所帮助。</p>

<p><a href="https://twitter.com/karaswisher/status/1095455941133324288" rel="noopener noreferrer"><strong>卡拉</strong></a>：是啊，这里就是唐人街，杰克。</p>

<hr>

<p></p><p>杰克：首要的是，我们正在寻找主动执行和促进健康的方法。这样举报/屏蔽就是最后的手段。我们试图解决的问题是把这些工作从用户手中拿走。</p>

<p><a href="https://twitter.com/jack/status/1095446924855898112" rel="noopener noreferrer">杰克</a>：其次，我们不断更新我们的政策以解决当今出现的问题。我们将它们植根于基本人权（联合国），并将身体安全作为首要任务。其次是隐私。</p>

<p><a href="https://twitter.com/karaswisher/status/1095447726794424320" rel="noopener noreferrer"><strong>卡拉</strong></a>：当你说身体安全时，我有点困惑。你具体指什么？你不是警察部队。事实上，社交媒体公司建造了没有警察、消防部门、垃圾清理或路标的城市。我认为。你觉得这个比喻怎么样？</p>

<p><a href="https://twitter.com/jack/status/1095448163479998464" rel="noopener noreferrer">杰克</a>：我指的是平台外、离线的影响。人们用他们在网上看到的东西在离线世界做什么。人肉搜索就是一个威胁身体安全的好例子。协同骚扰活动也是。</p>

<p><a href="https://twitter.com/karaswisher/status/1095448510319726598" rel="noopener noreferrer"><strong>卡拉</strong></a>：那么你如何阻止那个？我是说，普通警察部队也无法阻止。看来你的工作是在一开始就不让事情发展到那一步。</p>

<p><a href="https://twitter.com/jack/status/1095449174873563136" rel="noopener noreferrer">杰克</a>：完全正确。我们能在产品和政策上做些什么来降低概率。再次，我认为我们或其他公司在这方面的努力还不够。</p>

<hr>

<p><a href="https://twitter.com/karaswisher/status/1095456464855019520" rel="noopener noreferrer"><strong>卡拉</strong></a>：好的，新线程 @jack</p>

<p>你怎么看待推特休息和清理？你为什么认为会这样？我不能说我听到很多人说在离开推特一段时间后感觉“良好”：<a href="https://twitter.com/TaylorLorenz/status/1095039347596898305" rel="noopener noreferrer">https://twitter.com/TaylorLorenz/status/1095039347596898305</a></p>

<p><a href="https://twitter.com/jack/status/1095457041844334593" rel="noopener noreferrer">杰克</a>：感觉糟透了。我希望人们离开推特时，能感觉学到了东西，并在某种程度上感到有力量。当这不是普遍的氛围时，我会感到沮丧，这激励我去弄清楚。这就是我的愿望。</p>

<p><a href="https://twitter.com/karaswisher/status/1095457517033963521" rel="noopener noreferrer"><strong>卡拉</strong></a>：但他们为什么会这样感觉？这是你创造的。</p>

<p><a href="https://twitter.com/jack/status/1095458365516374016" rel="noopener noreferrer">杰克</a>：我们带着一个意图创造了这个东西。世界向我们展示了它想如何使用它。很多都很棒。很多是意料之外的。很多是负面的。我们不够迅速地去观察、学习和改进。</p>

<hr>

<p></p><p><strong>卡拉</strong>：好的，新线程 @jack</p>

<p><a href="https://twitter.com/karaswisher/status/1095456686977019905" rel="noopener noreferrer"><strong>卡拉</strong></a>：顺着这个思路，这对你个人有什么影响？</p>

<p><a href="https://twitter.com/jack/status/1095457906848231424" rel="noopener noreferrer">杰克</a>：我对推特倾向于鼓励愤怒、草率的看法、短期思维、回音室以及碎片化的对话和思考也感到不满。它们能修复吗？我相信我们可以做很多事情来解决。而且很可能必须改变更多的基本面才能做到。</p>

<p><a href="https://twitter.com/karaswisher/status/1095458590679289857" rel="noopener noreferrer"><strong>卡拉</strong></a>：但你发明了它。你可以控制它。行动缓慢不是一个好借口。</p>

<p><a href="https://twitter.com/jack/status/1095459084785004544" rel="noopener noreferrer">杰克</a>：这是现实。我们过去试图同时做太多事，没有专注于最重要的事情。这导致了行动迟缓。我们的技术栈和我们能多快发布东西也有关系。最近这方面已经改进了很多。</p>

<hr>

<p><a href="https://twitter.com/karaswisher/status/1095458179285172225" rel="noopener noreferrer"><strong>卡拉</strong></a>：好的，再试一次 @jack，在另一个新线程！这个是关于@realDonaldTrump的：</p>

<p>因为推特，我们对唐纳德·特朗普的想法了解更多，对此我们都百感交集。</p>

<p><a href="https://twitter.com/karaswisher/status/1095459010751410176" rel="noopener noreferrer"><strong>卡拉</strong></a>：你是否考虑过暂停唐纳德·特朗普？他的推文因为他是公众人物而受到某种程度的保护，但如果他是一个“普通”用户，他过去会被暂停吗？</p>

<p><a href="https://twitter.com/jack/status/1095459760567083008" rel="noopener noreferrer">杰克</a>：我们对所有账户都适用相同的服务条款。我们服务条款中最具争议的部分是“具有新闻价值/公共利益”条款，也就是你提到的“保护”。这并不默认适用于所有公众人物，但确实适用于全球领导人，以便了解他们的想法。</p>

<p><a href="https://twitter.com/karaswisher/status/1095460546831417345" rel="noopener noreferrer"><strong>卡拉</strong></a>：这对许多人来说似乎值得怀疑。让我换种方式问：你会封禁哪位具有历史意义的新闻人物？是否有人坏到该被封禁？具体点。说个名字。</p>

<p><a href="https://twitter.com/jack/status/1095461409624711168" rel="noopener noreferrer">杰克</a>：我们必须根据我们的政策和用户在我们服务上的行为来执行。并且随着当前时代的发展而演进。我无法根据个人来回答。必须专注于人们如何使用技术的模式。</p>

<p><a href="https://twitter.com/karaswisher/status/1095461706044768256" rel="noopener noreferrer"><strong>卡拉</strong></a>：一个名字都不说？好吧，但我认为这在很多人看来是推脱。我有一个长长的名单。</p>

<p><a href="https://twitter.com/jack/status/1095462135717482502" rel="noopener noreferrer">杰克</a>：我认为专注于用例更持久，因为这让我们能更广泛地采取行动。很可能这些不是孤立的情况，而是会扩散的东西。</p>

<p><a href="https://twitter.com/karaswisher/status/1095462374612582401" rel="noopener noreferrer"><strong>卡拉</strong></a>：能得到具体的例子真的会很好，因为你们做的很多事情在许多人看来是难以理解的。</p>

<hr>

<p></p><p><strong>卡拉</strong>：好的，再试一次 @jack，在另一个新线程！这个是关于@realDonaldTrump的：</p>

<p><a href="https://twitter.com/karaswisher/status/1095459296492560384" rel="noopener noreferrer"><strong>卡拉</strong></a>：当@realDonaldTrump不再是总统时，推特的业务/参与度会受损吗？</p>

<p><a href="https://twitter.com/jack/status/1095460252852482048" rel="noopener noreferrer">杰克</a>：我不认为我们的服务或业务依赖于任何一个账户或个人。我得说，政治对话的数量因为他的存在而显著增加，但这只是推特上的一个体验。有多个推特，都基于你关注谁。</p>

<p><a href="https://twitter.com/karaswisher/status/1095461276686471168" rel="noopener noreferrer"><strong>卡拉</strong></a>：好的，新问题（请回答你可能封禁的新闻历史人物）：自2016年以来，推特最大的单一改进是什么，表明你们已准备好迎接2020年大选？</p>

<p><a href="https://twitter.com/jack/status/1095461785610534912" rel="noopener noreferrer">杰克</a>：我们在打击自动化和协同活动方面的工作。与政府机构合作，改善关于威胁的沟通。</p>

<p><a href="https://twitter.com/karaswisher/status/1095461973926518784" rel="noopener noreferrer"><strong>卡拉</strong></a>：你能给出一个更详细的成功例子吗？</p>

<p><a href="https://twitter.com/jack/status/1095462610462433280" rel="noopener noreferrer">杰克</a>：我们在本国内部分享了2018年的回顾，并在墨西哥大选中也进行了大量测试。印度大选即将到来。在中期选举中，我们能够监控在线和线下的破坏努力，并能够在推特上阻止这些行动。</p>

<hr>

<p></p><p><strong>卡拉</strong>：好的，新问题（请回答你可能封禁的新闻历史人物）：自2016年以来，推特最大的单一改进是什么，表明你们已准备好迎接2020年大选？</p>

<p><a href="https://twitter.com/karaswisher/status/1095461526935429120" rel="noopener noreferrer"><strong>卡拉</strong></a>：我们有什么信心认为俄罗斯或其他国家支持的行为者不会对明年的大选造成严重破坏？</p>

<p><a href="https://twitter.com/jack/status/1095463103561490432" rel="noopener noreferrer">杰克</a>：我们应该预期各国政府与平台之间将开展更多合作来应对。这会给我信心。也要保持一些怀疑。这是健康的。我们越是能公开进行这项工作并分享我们的发现，就越好。</p>

<p><a href="https://twitter.com/karaswisher/status/1095463591099224064" rel="noopener noreferrer"><strong>卡拉</strong></a>：
我仍然非常需要具体的细节。[表情包图片：给我一些具体细节。我爱具体细节，具体细节是最棒的部分！]</p>

<hr>

<p></p><p>杰克：我认为专注于用例更持久，因为这让我们能更广泛地采取行动。很可能这些不是孤立的情况，而是会扩散的东西。</p>

<p><a href="https://twitter.com/karaswisher/status/1095462715332665351" rel="noopener noreferrer"><strong>卡拉</strong></a>：我要转向商业问题了，因为时间不多了，而且这个系统正如我所想的那样混乱：从MAU（月活跃用户）转向DAU（日活跃用户）是怎么回事？为什么转向？我们应该如何解读这个小得多的数字？</p>

<p><a href="https://twitter.com/jack/status/1095463620190789632" rel="noopener noreferrer">杰克</a>：我们希望每天对人们有价值。而不是每月。这是对我们自己的更高要求。当然，从绝对数字上看可能小了，但使用推特的人是世界上最有影响力的人之一。他们驱动对话。我们相信我们能最好地实现增长。</p>

<p><a href="https://twitter.com/karaswisher/status/1095463921748721664" rel="noopener noreferrer"><strong>卡拉</strong></a>：好的，那么推特上目前最令人兴奋的有影响力的人是谁？请具体说明</p>

<p><a href="https://twitter.com/jack/status/1095465039438393344" rel="noopener noreferrer">杰克</a>：对我个人而言？我喜欢@elonmusk使用推特的方式。他专注于解决生存性问题并公开分享他的想法。我非常尊重这一点，以及随之而来的所有起起落落。</p>

<p><a href="https://twitter.com/karaswisher/status/1095465160167301121" rel="noopener noreferrer"><strong>卡拉</strong></a>：那@AOC呢？</p>

<p><a href="https://twitter.com/jack/status/1095465322071486464" rel="noopener noreferrer">杰克</a>：完全同意。她正在掌握这种媒介。</p>

<p><a href="https://twitter.com/karaswisher/status/1095465494407270400" rel="noopener noreferrer"><strong>卡拉</strong></a>：她早已超越了掌握。她能说流利的推特语。</p>

<p><a href="https://twitter.com/jack/status/1095465722862395392" rel="noopener noreferrer">杰克</a>：没错</p>

<p><a href="https://twitter.com/karaswisher/status/1095465850423951360" rel="noopener noreferrer"><strong>卡拉</strong></a>：另外，你们打算雇佣一个人来有效地担任你的二号人物吗？</p>

<p><a href="https://twitter.com/jack/status/1095466504689115136" rel="noopener noreferrer">杰克</a>：我认为最好将这种责任分散给多个人。这减少了依赖性，公司在未来领导层方面也有更多选择。</p>

<hr>

<p></p><p><strong>卡拉</strong>：我要转向商业问题了，因为时间不多了，而且这个系统正如我所想的那样混乱：从MAU转向DAU是怎么回事？为什么转向？我们应该如何解读这个小得多的数字？</p>

<p><a href="https://twitter.com/karaswisher/status/1095463105021308929" rel="noopener noreferrer"><strong>卡拉</strong></a>：另外：2016年你离卖掉推特有多近？发生了什么？</p>

<p>按照你在《纽约时报》讨论的，把公司交给公共信托呢？</p>

<p><a href="https://twitter.com/jack/status/1095464676094205952" rel="noopener noreferrer">杰克</a>：我们最终决定保持独立会更好。我很高兴我们这么做了。自那以后我们取得了很大进展。我们也变得更加专注。绝对喜欢向第三方开放更多东西的想法。不确定那会是什么样子。推特接近于一个协议。</p>

<p><a href="https://twitter.com/karaswisher/status/1095464894705684481" rel="noopener noreferrer"><strong>卡拉</strong></a>：快点回答其他问题！我还有更多问题！如果你想用这个方法，快点！</p>

<p><a href="https://twitter.com/jack/status/1095465159202467842" rel="noopener noreferrer">杰克</a>：卡拉，我正在尽力加快速度</p>

<p><a href="https://twitter.com/karaswisher/status/1095465222951895040" rel="noopener noreferrer"><strong>卡拉</strong></a>：驾！</p>

<hr>

<p></p><p><strong>卡拉</strong>：我要转向商业问题了，因为时间不多了，而且这个系统正如我所想的那样混乱：从MAU转向DAU是怎么回事？为什么转向？我们应该如何解读这个小得多的数字？</p>

<p><a href="https://twitter.com/karaswisher/status/1095462909990309888" rel="noopener noreferrer"><strong>卡拉</strong></a>：另外：推特还在考虑订阅服务吗？比如“推特高级版”之类的？</p>

<p><a href="https://twitter.com/jack/status/1095464073771114496" rel="noopener noreferrer">杰克</a>：一直在尝试新模式。Periscope有超级心，这让我们了解了直接贡献。我们需要弄清楚订阅的价值交换。对我们来说，必须是价值非常高，我们才会直接收费。</p>

<hr>

<p></p><p>杰克：完全同意。她正在掌握这种媒介。</p>

<p><a href="https://twitter.com/karaswisher/status/1095465790483116032" rel="noopener noreferrer"><strong>卡拉</strong></a>：好的，最后几个是关于你的，我们需要长篇大论，因为这里的系统很混乱，推特用户是这么说的：</p>

<ol>
<li>自你回归担任CEO以来，推特错过的最大机会是什么？</li>
</ol>

<p><a href="https://twitter.com/jack/status/1095466230427742208" rel="noopener noreferrer">杰克</a>：更早专注于对话。我们花了太长时间才走到那里。太分心了。</p>

<p><a href="https://twitter.com/karaswisher/status/1095466622956064770" rel="noopener noreferrer"><strong>卡拉</strong></a>：被什么分心了？是什么事情让你们所有人分心，导致了社交媒体上这个明显的烂摊子？</p>

<p><a href="https://twitter.com/jack/status/1095467434440417282" rel="noopener noreferrer">杰克</a>：试图同时做太多事。没有专注于我们唯一的核心优势是什么：对话。这导致了真正稀释的战略和方法。以及大量的被动反应。</p>

<p><a href="https://twitter.com/karaswisher/status/1095467977208668160" rel="noopener noreferrer"><strong>卡拉</strong></a>：说到这个（对话），我们很快做一个有声音的吧，就像这个</p>

<p><a href="https://www.youtube.com/watch?v=oiJkANps0Qw" rel="noopener noreferrer">https://www.youtube.com/watch?v=oiJkANps0Qw</a></p>

<hr>

<p></p><p><strong>卡拉</strong>：她早已超越了掌握。她能说流利的推特语。</p>

<p></p><p>杰克：没错</p>

<p><a href="https://twitter.com/karaswisher/status/1095465976626380801" rel="noopener noreferrer"><strong>卡拉</strong></a>：为什么你仍然说自己是两家上市公司的CEO？坚持认为你可以同时从事两个都需要全力以赴的工作，意义何在？</p>

<p><a href="https://twitter.com/jack/status/1095467046949642240" rel="noopener noreferrer">杰克</a>：我专注于在两家公司都建立领导层。我的愿望或雄心并不是为了CEO的头衔而担任多家公司的CEO。我正在尽一切努力帮助两家公司。努力不仅仅取决于一个人。这是一个团队。</p>

<hr>

<p><a href="https://twitter.com/karaswisher/status/1095467352395825152" rel="noopener noreferrer"><strong>卡拉</strong></a>：最后一个问题：看在上帝的份上，请尽快来我的Recode Decode播客，因为模拟对话似乎是一种更好的提问和回答方式。我认为推特也同意，这次对话表明了这个线程有多难进行。话虽如此，谢谢你尝试。真的。</p>

<p><a href="https://twitter.com/jack/status/1095468327978205184" rel="noopener noreferrer">杰克</a>：这个线程很难。但我们学到了很多东西来修复它。需要让它感觉更连贯、更容易跟随。极具挑战性。谢谢你和我一起尝试。知道这不容易。会考虑其他形式！</p>

<p><a href="https://twitter.com/karaswisher/status/1095468672397832192" rel="noopener noreferrer"><strong>卡拉</strong></a>：做一个活动的玻璃屋，让人们可以观看而不投掷石头。小贴士：推特对话很糟糕。</p>

<p><a href="https://twitter.com/jack/status/1095469020508082177" rel="noopener noreferrer">杰克</a>：是的。而且它们不必是糟糕的。需要弄清楚这个问题。这整个体验就是我们需要解决的问题陈述。</p>

<hr>

<p></p><p>杰克：这个线程很难。但我们学到了很多东西来修复它。需要让它感觉更连贯、更容易跟随。极具挑战性。谢谢你和我一起尝试。知道这不容易。会考虑其他形式！</p>

<p><a href="https://twitter.com/karaswisher/status/1095469018310422529" rel="noopener noreferrer"><strong>卡拉</strong></a>：我的孩子饿了，他说即使我很刻薄，你也应该和我做一个真正的采访。只是说说。</p>

<p><a href="https://twitter.com/jack/status/1095469279514750977" rel="noopener noreferrer">杰克</a>：我不觉得你刻薄。尝试总是好的。</p>

<p><a href="https://twitter.com/karaswisher/status/1095469622130888705" rel="noopener noreferrer"><strong>卡拉</strong></a>：我孩子也不觉得。他只想去吃晚饭。</p>

<p><a href="https://twitter.com/jack/status/1095469788439035905" rel="noopener noreferrer">杰克</a>：去吃吧！谢谢，卡拉。</p><p><em>由 mimo-v2.5 模型翻译，花费 19694 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/karajack/</link>
      <guid isPermaLink="false">https://danluu.com/karajack/</guid>
      <pubDate>Tue, 12 Feb 2013 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Jonathan Shapiro 对 BitC 的回顾思考</title>
      <description>[AI 摘要] 本文解释了 Jonathan Shapiro 停止开发 BitC 语言的原因，包括编译模型、类型系统局限、继承缺失以及类型类实例一致性问题。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文解释了 Jonathan Shapiro 停止开发 BitC 语言的原因，包括编译模型、类型系统局限、继承缺失以及类型类实例一致性问题。</div><p><em>这是乔纳森·夏皮罗（Jonathan Shapiro）"BitC 回顾思考"的存档，该文似乎已从互联网上消失；当时，BitC 的目标领域与 Rust 相同</em></p>

<pre><code>Jonathan S. Shapiro shap at eros-os.org
Fri Mar 23 15:06:41 PDT 2012
</code></pre>

<p>现在，大家应该都清楚我已经停止了 BitC 的工作。是时候解释一下<em>为什么</em>了。</p>

<p>一个答案是，在我加入微软后，Coyotos 的工作就停止了，而我现在专注的工作<em>并不真正需要</em>（或看不出获益于）BitC。众所周知，我们一生的时间有限。但仅凭这一点还不足以让我完全停止。</p>

<p>第二个答案是，BitC 按其当前形式无法继续推进。我遇到一系列需要彻底重新设计语言和类型系统、然后从头开始实现新版本的问题。第一个实现的经验表明，这将花费相当长的时间，在没有外部支持和资金的情况下，我实在无法承担。编程语言的研究工作并不容易获得资助。</p>

<p>但第三个答案可能最引人兴趣：我不再相信类型类（type classes）从其当前的语言设计角度来看是"可行"的。这是这里唯一重要的科学教训。</p>

<p>总体而言，当前设计存在四个主要症结：</p>

<ol>
<li>编译模型。</li>
<li>当前类型系统在处理按引用（by-reference）和引用（reference）类型方面的不足。</li>
<li>缺少某种形式的继承。</li>
<li>实例一致性（instance coherence）问题。</li>
</ol>

<p>在我看来，前两个问题是可解的，尽管第二个问题几乎需要完全重新实现编译器。最后一个（实例一致性）似乎没有任何通用的解决方案，并且它引发了关于使用类型类进行方法重载的概念性担忧。这个问题非常重要，我将在此讨论前三个主题，并将最后一个作为单独的笔记来讨论。</p>

<p>继承是 BitC 邮件列表上人们可能（并且有时确实）激烈争论的话题。因此，就此主题说几句简短的话可能是相关的。</p>

<h3 id="prefacing-comments-on-objects-inheritance-and-purity">关于对象、继承和纯度的前言</h3>

<p>BitC 最初被设计为一种[命令式]函数式语言，因为我们专注于软件验证。函数式语言的类型和语义规范领域有<em>很多人</em>在研究。我们（作为一个领域）<em>大致</em>知道如何做，而这正是我们霍普金斯的团队在开始时不太了解的领域。软件验证是一个已知的难题，在命令式语言上进行已经是一个挑战，而且这似乎不是一群新手语言研究者反抗领域当前趋势的好地方。看起来，选择我们自己的战场更好。我们知道继承和类型推导之间存在交互作用，而且<em>看起来</em>通过巧妙编译的类型类可以达到许多相同的运算结果。因此，我早期决定<em>不</em>在语言中包含继承。</p>

<p>对我来说，作为一个程序员，移除继承和对象是一个非常不情愿的决定，因为它牺牲了将大量现有 C++ 代码转译到更安全语言中的任何可能性。事实证明，你无法真正从一个成功的系统语言中移除底层的语义挑战。系统语言<em>需要</em>某种机制来进行存在封装（existential encapsulation）。实现这种封装的<em>机制</em>并非真正的问题；一旦你引入了那种封装，就会引发对象与子类型（subtyping）带来的大多数验证问题，而一旦你这样做，你不妨就获得对象的好处。本质上剩下的问题是对 Self 类型的建模，并且出于一系列原因，一旦引入封装，在系统语言中拥有 Self 类型是相当必要的。所以，你最终会在<em>某种程度上</em>被推向对象类型系统。在八年后回看，我现在可以肯定地说，这是完全显而易见的！</p>

<p>我强烈认为多重继承是一团糟。关于单继承的争论，在我看来，仍然在很大程度上是信仰问题。继承和虚方法当然不是封装的<em>唯一</em>方式，它们可能是也可能不是最好的原语机制。我一直对将大量软件引入安全、高性能的语言更感兴趣，而不是在语言设计的这个领域进行创新。如果转译当前代码是某种目标，我们需要非常类似于继承的东西。</p>

<p>我们最初将对象排除在 BitC 之外的最后一个原因是纯度（purity）。我想保留一个强大的、纯的子集语言——同样是为了便于验证。当时我所知的对象语言都是高度有状态的，我无法想象如何做一种非命令式的面向对象语言。实际上，我<em>仍然</em>不确定我是否能看到如何为 BitC 所关注的那类应用实际地实现这一点。但随着我们对验证价值的信心减弱，我为了验证而愿意受纯度限制的意愿也迅速消退了。</p>

<p>纯子集语言的<em>另一个</em>论据与推进并发有关，但当我真正开始深入研究 BitC 的并发支持时，我越来越认为这种方法与人们试图解决的并发问题类型并不匹配，并且实践中对不可变状态的需求和使用比纯编程方法能处理的要微妙得多。纯子程序显然扮演着重要角色，但它们还不够。</p>

<p>而且我<em>仍然</em>不相信单子（monads）。:-)</p>

<h3 id="compilation-model">编译模型</h3>

<p>BitC 的目标之一是在传统的静态独立编译方案下获得可接受的性能。这可能是我目光短浅，但运行时进行的复杂优化让我从稳健性和保证的角度感到非常紧张。我知道今天的字节码虚拟机在进行非常激进的优化并且取得了相当大的成功，但存在一些担忧：</p>

<ul>
<li>对于一种稳健的语言，我们希望<em>最小化</em>免除类型检查和[最终]验证的代码规模和复杂性。运行时代码就属于这类。单是垃圾收集器就已经大到足以引起保证问题了。再增加一个庞大而复杂的优化器会严重降低保证故事的可信度。</li>
<li>运行时优化对启动时间有非常负面的影响，</li>
<li>尤其是在事务处理的背景下。IBM（在 DB/2 中）和其他公司提供了大量硬数据。这就是"数据库中的 Java"从未流行起来的原因之一。随着系统中进程和组件实例化频率的增加，启动延迟变得越来越令人担忧。稳健的系统不会回收子系统。</li>
<li>运行时优化为应用程序的运行时环境增加了<em>巨大</em>的空间开销。虽然运行时编译器的<em>代码</em>可以被共享，但运行时编译器的<em>状态</em>不能，并且有相当多的状态。</li>
<li>运行时优化——特别是当它"按需"进行时——给性能指标引入了方差和不可预测性。对于一些我感兴趣的应用，我需要"稳态"性能。如果代码在运行时得到优化，即使只是适度的常数因子的改进，实时调度也会成为一个非常令人困惑的挑战。</li>
<li>运行时优化器<em>产生</em>的代码很难跨地址空间共享，尽管这可能在其他编译模型中也没有得到很好的解决。</li>
<li>如果存在运行时优化，应用程序就会开始依赖它来获得性能。也就是说：出于社会原因，"可选的"运行时优化往往会很快变成必需的。</li>
</ul>

<p>需要明确的是，我<em>不是</em>反对持续编译（continuous compilation）。我实际上认为这是个好主意，并且我认为有一些相当引人注目的用例。我<em>确实</em>认为运行时优化器应该用强类型、安全的语言来实现。我<em>也</em>认为 HotSpot 技术花了相当长的时间才稳定下来，这应该被视为一个警示故事。很可能我列举的许多问题/担忧都可以解决——但可能<em>不会很快</em>。对于我最重要的应用，对保证的担忧是首要的。因此，从语言设计的角度来看，我很乐意利用持续编译，但我不想设计一种为了达到合理的基线性能而<em>需要</em>持续编译的语言。</p>

<p>优化器的复杂性问题，当然，在传统编译器上也同样可以被严肃提出。你总得在<em>某个地方</em>优化。但我对动态翻译的经验告诉我，一次处理一件事情要容易得多，也更容易推理。一旦我们有了一个高置信度的、用安全语言编写的优化器，<em>然后</em>才可能在高置信度系统中将其集成到运行时。在此之前，关注点分离（separation of concerns）应该是当务之急。</p>

<p>严格来说，应该指出，运行时编译实际上并非 BitC 或任何其他字节码语言所必需的。只有当你将运行时加载与编译器抽象表示（见下文）结合，并允许具有抽象表示的类型出现在运行时加载库的签名中时，运行时编译才变得必要。在此之前，可以在代码生成和执行之间保持适当的阶段分离。请继续阅读——我会在下文解释其中一些内容。</p>

<p>无论如何，我一开始就预料到强抽象类型会引发这方面的担忧，并最初采取了以下观点：</p>

<ul>
<li>像内核之类的东西可以进行全程序编译。这有效地消除了对运行时优化器的需求。</li>
<li>像关键系统组件之类的东西本来也希望静态链接，所以它们<em>也</em>可以作为全程序编译问题来处理。</li>
<li>对于其他所有情况，我希望采用一种"模板扩展"（template expansion）的方法进行运行时编译。这不会承担优化器的全部复杂性；它只是将运行时链接和加载扩展以包含跨度（span）和偏移量（offset）的解析。这仍然是大量代码，但它<em>并不复杂</em>，并且适合进行严格——甚至是形式化——的规范。</li>
</ul>

<p>几年后我才意识到，模板扩展的想法无法产生可接受的基线性能。问题在于抽象类型、运算符重载和内联之间的相互作用。</p>

<h3 id="compiler-abstracted-representations-vs-optimization">编译器抽象表示 vs. 优化</h3>

<p>类型具有表示（representations）。这有时似乎让编程语言界的某些成员感到有点不舒服。一个需要保持距离的东西。非常像一袋装满狗屎的密封袋（在此插入卡通画）。从系统人员的角度来看，我很遗憾地报告，比特放在哪里，它们有多大，以及它们的组装方式确实<em>重要</em>。如果你恰好是个狗主人，你会注意到"比特即狗屎"的比喻在这里仍然成立。我们这些系统人员似乎注定每天要涉水穿过计算系统的管道，所以这也许不足为奇。嗯。</p>

<p>无论如何，编程语言界为了先研究类型问题，将表示问题搁置一旁。我认为他们没有忘记实用性，但可以说表示问题不是当前主流编程语言研究的重点。甚至有一种学派认为，表示是一个相当令人讨厌的问题，应该由编译器"通过魔法"来处理，命令式操作也应该如此处理。对于系统代码，这种方法行不通，因为我们需要处理的很多表示和布局都是硬件强加给我们的。</p>

<p>无论如何，类型<em>确实</em>有表示，并且了解这些表示对于即使是最简单的编译器优化也至关重要。所以我们需要小心，不要将类型<em>过度</em>抽象化，以免破坏编译模型。</p>

<p>在 C 中，"+"运算符是原语，编译器总是可以直接选择合适的操作码。其他"核心"算术运算也类似。现在尝试一个思想实验：假设我们将程序中每个此类核心运算的使用都替换为一个在运行时实现的、功能等效的过程调用。你只需要对<em>用户</em>操作这样做——编译器引入的用于执行地址算术等操作的加法总是在具体类型上进行的，所以这些仍然可以高效生成。但即使它只针对用户操作，这显然也会严重损害程序的性能。你可以用运行时优化器恢复这些性能，但这很复杂。</p>

<p>在 C++ 中，"+"运算符可以重载。但是（1）原语类型的绑定不能被替换，（2）我们静态地知道其他类型的绑定和表示<em>是什么</em>，（3）我们可以借助内联来控制哪些操作会在运行时涉及过程调用。我并不是在建议我们想被迫手动控制。关键点是，编译器对操作的实现有足够的可见性，可以在静态编译时内联原语运算符（以及许多其他运算符）。</p>

<p>为什么这在 C++ 中是可能的，而在 BitC 中却不是？</p>

<p>在 C++ 中，抽象类型（模板）的实例化发生在一个编译器可见所有相关表示的完整知识的环境中。这些信息可能并不都在程序员的范围内，但编译器可以跨作用域追踪，找到所有部分，将它们组装起来并理解它们的形状。这就是 C++ "显式实例化"模型的由来。这也导致了大量"内部"类型声明和实现代码迁移到头文件中，这往往会限制模板的使用，并增加每个编译单元处理的头文件行数——我们曾在一个非常早期（模板之前）的 C++ 产品上测量过，发现每个"源代码"行要处理超过 150 行头文件。自那以后，这个比例至少增长了十倍，并且（由于模板）很可能达到了二十倍。</p>

<p>这一切都很痛苦，但正是它使得静态编译时的模板扩展成为可能。从<em>编译器</em>的角度来看，涉及的类型（更重要的是，表示）根本没有被抽象化。在 BitC 中，<em>这两者</em>在静态编译时<em>都</em>被抽象化了。直到链接时，所有的表示才都掌握在手。</p>

<p>正如我上面所说，我们可以想象扩展链接模型来处理这个问题。所有那些头文件信息都是为了处理<em>表示</em>问题，而不是类型检查。表示，归根结底，是大小、对齐和偏移。即使我们不知道具体的值，我们<em>确实</em>知道所有这些在编译时都是常量，并且我们需要在编译时计算的结果完全由这些常量的和与倍数组成。我们可以想象在静态编译时将它们视为<em>不透明</em>常量，并在链接时填入空白。这或多或少就是我心中链接时模板扩展的概念。概念上：将所有的偏移量和大小留"空"，依赖链接器来填充它们，就像处理重定位（relocation）一样。</p>

<p>这种方法的问题在于它移除了优化和寄存器分配所需的关键信息，并且不支持内联。在 BitC 中，我们可以<em>并且确实</em>将这种实例化一直扩展到原语运算符！也许更重要的是，扩展到原语访问器（accessors）和修改器（mutators）。原因是我们希望能够编写像"a + b"这样的表达式，并说"该表达式类型良好，前提是存在一个合适的 + 的解析：('a,'a)-&gt;'a"。这是一种很好的<em>类型</em>化操作的方式，但它让 'a 的表示完全被抽象了。这意味着我们无法看到它们何时是原语类型。这意味着我们<em>恰好</em>（或者在大多数情况下）陷入了将所有用户发起的"+"操作生成为过程调用的境地。现在，令人惊讶的是，这实际上并非世界末日。我们可以想象发明某种形式的"高级汇编器"，我们的静态代码生成器知道如何将其翻译成机器码。如果静态代码生成器这样做，运行时加载器就可以承担发出过程调用的责任，并在适当时刻替换为内部调用。这将导致我们失去代码共享，但在非嵌入式目标上可能是可以容忍的。</p>

<p>不幸的是，这种高级汇编器对优化有一些相当糟糕的影响：首先，我们不再知道为了优化目的"+"运算符的<em>成本</em>。我们不知道特定用法的"+"需要多少个周期，但更重要的是，我们不知道它会生成多少字节的代码。没有这些信息，有一长串优化决策我们无法在静态编译时做出。其次，在静态代码生成时，我们不再有足够的信息来执行一长串<em>基本</em>的寄存器和存储优化，因为我们不知道哪些过程调用实际上会使用寄存器。</p>

<p>你听到的那种吱吱嘎嘎的声音，正是运行时代码生成器随着成长而变得臃肿并失去可靠性的声音。虽然这种机制的实际影响不会像我描绘的那么糟——因为许多用户类型<em>不是</em>抽象的——但这种机制的<em>复杂性</em>确实如我所提议的那样糟糕。实际上，我们最终将代码生成和优化推迟到了链接时。这个想法可以追溯到（至少）大卫·沃尔（David Wall）在 1980 年代中期关于链接时寄存器优化的工作。自那以后，它已经以许多变体被探索。这是一个引人注目的想法，但有其利弊。</p>

<p>这里发生的事情是，BitC 中的类型<em>过于</em>成功地为静态编译而抽象化了。结果是一大<em>袋</em>狗屎，所以也许编程语言界的人们是对的。:-)</p>

<h3 id="two-solutions">两种解决方案</h3>

<ul>
<li>最明显的解决方案——由 C++ 采用——是重新设计语言，使表示问题不对编译器隐藏。这实际上是一个值得考虑的解决方案。C++ 中的问题与其说是每个源代码行对应的头文件行数，不如说是 C 预处理器要求我们<em>重新</em>处理这些行<em>每个</em>编译单元的事实。BitC 缺乏（有意地）与 C 预处理器相当的东西。</li>
<li>另一种可能性是转向可能被称为"安装时编译"（install time compilation）的方式。发布某种形式的字节码，并在安装时进行静态编译。这能让你恢复所有从经典编译方法中合理预期的代码共享和优化，它从系统的角度开辟了一些有趣的设计点选项，并且（仔细考虑的话）它可以被改造以适应现有系统。今天有些平台（尤其是手机）我们基本上已经这样做了。</li>
</ul>

<p>你不想跨越的设计点是动态<em>加载</em>其中加载的接口携带具有抽象表示的类型。在那个点上，你实际上是在将自己绑定到运行时代码生成<em>上</em>，尽管我确实有一些关于如何缓解这个问题的想法。</p>

<h3 id="conclusion-concerning-compilation-model">关于编译模型的结论</h3>

<p><strong>如果静态、独立编译是必需的，那么每当使用抽象类型时，编译器就必须跨模块边界查看源代码。也就是说：任何具有抽象类型的函数都必须公开其源代码级的实现。</strong></p>

<p><strong>实际的替代方案是使用一种高级中间形式，结合安装时或运行时的代码生成。这当然是可行的，但这超出了我认为我能承担的范围。</strong></p>

<p>这都是可管理和可实现的。不幸的是，这不是我们选择的路径，所以这基本上意味着要从头开始。</p>

<h3 id="insufficiency-of-the-type-system">类型系统的不足</h3>

<p>在某个时候，我们已经使 BitC 足够工作，可以开始构建库代码。你可能不会惊讶，我们在库中要做的第一件事就是 IO。我们发现我们无法在类型系统中处理类型化的输入。为什么不行？</p>

<p>即使你准备在 IO 库内进行动态分配，在某个抽象层级上，你需要实现一个类似于"inputStream.read(someObject: ByRef mutable 'a)"的操作。这有一些变体，但重点是，在某个时刻你希望能够将传入的字节移动到先前分配的存储中。到目前为止还不错。</p>

<p>不幸的是，为了限制类型系统的功能蔓延，我（不幸的是，事后证明是不明智地）宣布我们只需要在参数处处理 ByRef 类型。Swaroop 比我意图的更字面地理解了这句话。他注意到，如果这<em>真的</em>是 ByRef 需要被处理的唯一地方，那么你可以内部将"ByRef 'a"视为 'a，只在参数的标识符记录上保留一个标记，以指示在代码生成时需要额外的解引用。这实际上相当聪明，除了它无法很好地扩展到类型类及其实例之间的签名匹配。因为<em>read</em>的参数类型是<em>ByRef 'a</em>，InputStream 就是这样一个类型类。</p>

<p>所以我们现在面临几个问题。第一个是我们需要在编译器内使 ByRef 'a 成为一等类型，以便我们能够统一它；第二个是我们需要处理这将带来的隐式强制转换问题。也就是说：在复制边界处来回转换 ByRef 'a 和 'a。强制转换部分还不算太糟；ByRef 从不被推导，与 ByRef 相关的类型强制转换发生在与 const/mutable 强制转换完全相同的地方。我们已经在类型检查器中有一个清晰隔离的位置来处理这个问题。</p>

<p>但即使 ByRef 不被推导，它也可以通过统一在代码中传播。而<em>这</em>会导致安全违规！ByRef 在语法上被限制只出现在参数处，这有（有意的）后果，即确保与引用进入栈的生命周期相关的安全限制得到遵守——这就是我最初强加 ByRef 只能出现在参数处限制的原因。一旦 ByRef 类型可以统一，语法限制就不再保证生命周期限制的执行。要理解原因，请看：</p>

<pre><code>  define byrefID(x:ByRef 'a) { return x; }
</code></pre>

<p>一个<em>本应</em>是向下引用的东西最终被返回到了栈上。Swaroop 的解决方案很聪明，部分原因是他默默地防止了这个传播问题。在某种意义上，他的实现并没有真正将 ByRef 视为一种类型，所以它不能传播。但*正因如此*，他没有将其视为类型，我们也无法在实例和类型类之间进行必要的匹配检查。</p>

<p>事实证明，能够这样做是<em>有用的</em>。抽象可变"属性"（property，在 C# 的意义上）的基本要求是，我们在语言中有能力构造一个函数，返回要修改的东西的<em>位置</em>。这个位置通常在栈上，因此返回这个位置<em>完全像</em>上面的例子。"ByRef 只在参数处"的限制实际上非常保守，我们知道它阻止了我们最终想要做的某些事情。我们有个模糊的概念，以后会通过引入区域类型（region types）来修复它。</p>

<p>结果，"以后"不得不变成"现在"，因为当 ByRef 类型变成一等类型时，区域类型是重新建立生命周期安全性的正确方式。但<em>添加</em>区域类型带来了两个问题（这就是为什么我们希望推迟它们）：</p>

<ul>
<li>添加区域类型意味着重写类型检查器，并重新验证推导算法的可靠性和完备性，<em>并且</em></li>
<li>这不仅仅是重写。区域引入了子类型（subtyping）。子类型和多态（polymorphism）相处得不好，所以我们需要回头做大量研究。</li>
</ul>

<p>带有区域子类型的区域多态（Region polymorphism with region subtyping）以前肯定已经有人做过，但我们也在另一个场景中考虑子类型（见下文）。这推动我们朝向一个类型类（kinding）系统和不同的类型系统发展。</p>

<p>因此，为了解决 ByRef 问题，我们几乎需要从头开始重新设计类型系统和编译器。考虑到编译器中积累的混乱，这无论如何可能都是一件好事，但 Swaroop 现在是微软的全职员工，我没有时间和资源独自处理这个。</p>

<h3 id="conclusion-concerning-the-type-system">关于类型系统的结论</h3>

<p><strong>回想起来，很难想象一个强类型命令式语言不会以一等方式对位置（location）进行类型化。如果语言同时支持显式拆箱（unboxing），它实际上就必须处理位置的生命周期和逃逸（escape）问题，这使得某种形式的内存区域类型（memory region typing）几乎不可避免。</strong></p>

<p><strong>仅仅因为这个原因，即使没有其他原因，一个带有拆箱的命令式语言的类型系统也必须包含某种形式的子类型。为了确保终止，这对类型推导的使用施加了一些限制。好的方面是，一旦引入子类型，你就能在语言中做许多没有它很难做到的有用事情。</strong></p>

<h3 id="inheritance-and-encapsulation">继承与封装</h3>

<p>我们与继承的第一次交锋实际上出现在编译器本身中。尽管我们尽了最大努力，BitC 编译器的 C++ 实现并未完全避免继承，因此它没有直接的 BitC 翻译版本。即使我们更改了编译器的代码，也有大量的第三方库我们希望能够转译。其中许多依赖于[单]继承。没有至少某种形式的接口（类型）继承，我们甚至无法作为外部对象与那些库进行良好的接口交互。</p>

<p>除了编译器，我们还需要一种封装机制。我一直在研究"capsules"，但很快就能明确，capsules 实际上是子类（subclassing）的一种退化形式，试图回避这个问题不会让我取得任何进展。</p>

<p>我几乎可以想象，通过添加"ThisType"和继承的<em>接口</em>来获得我需要的东西。但这两个功能的组合引入了子类型。实际上，从类型系统的角度来看，这个组合等同于单继承子类（single-inheritance subclassing）。</p>

<p>而我越是盯着接口看，就越开始问自己，为什么接口不能就是一个类型类。这让我从新的方向面对了实例一致性（instance coherence）问题，这个问题已经让我头疼不已。它也让我意识到，接口之所以有效，部分原因是它们总是针对单一类型（ThisType）进行参数化——一旦你知道了那个，所有其他的绑定就由类型构造函数或显式指定所确定。</p>

<p>而引入 SelfType 比引入子类型是一个更大的问题。这意味着要完全脱离 System F&lt;:，进入 Cardelli <em>等人</em>的对象类型系统。这不仅仅是重新实现类型检查器以支持我们已有的类型系统的变体。它意味着完全重新形式化类型系统，并学习如何在不同的模型中思考。</p>

<p>可行，但时间不允许我们在已构建的框架或编译器内完成。在这一点上，我决定需要从头开始。我们从 BitC 工作的各个部分学到了很多，但有时你必须后退一步，才能向前迈出更多的步伐。</p>

<h3 id="instance-coherence-and-operator-overloading">实例一致性与运算符重载</h3>

<p>BitC 主要借鉴了 Haskell 的类型类。类型类不仅仅是类型限定符的基础；它们提供了*特设多态*（ad hoc polymorphism）的机制。一种语言纯粹主义者所不顾的、真正的语言实际上确实需要的特性。</p>

<p>问题在于，对于一个给定的类型类，在一个给定的类型上可能有多个类型类实例。因此，最终可能有一个像这样的函数：</p>

<pre><code>define f(x : 'x) {
  ...
  a:int32 + b  // 类型在静态编译时完全解析
  return x + x  // 类型直到实例化时才能解析
}
</code></pre>

<p>问题：当 'x 实例化为<em>int32</em>时，我们不知道使用哪个"+"实例。为了使"a+b"中的"+"有意义，我们需要为 +:(int32, int32)-&gt;int32 进行静态编译时的解析。我们从 Arith(int32) 获得这个。到目前为止还不错。但如果 'x 被实例化为<em>int32</em>，我们将得到由调用者提供的类型类实例。问题在于，无法保证这是与我们之前看到的<em>相同</em>的 Arith(int32) 实例。</p>

<p>Haskell 中的解决方案是强加一个<em>特设</em>规则，即对于给定应用程序中每个唯一的类型元组，你只能实例化一次类型类。这类似于 C++ 中的做法：你只能有一个全局运算符在特定类型上的重载。如果在该类型上存在多个重载，你会得到链接时错误。这个限制在 C++ 中之所以可以容忍，主要是因为运算符重载非常有限：</p>

<ol>
<li>可重载的运算符集合很小且不可扩展。</li>
<li>大多数可以作为方法满意地处理，这使得它们的解析是明确的。</li>
<li>大多数<em>不能</em>作为方法处理的运算符是算术运算，并且人们想要扩展这些运算的实践限制是存在的。</li>
<li>剩下的高度重载的全局运算符与 I/O 相关。这些在一种足够多态的语言中<em>可以</em>是方法。</li>
</ol>

<p>在像 BitC 这样允许更丰富使用运算符重载的语言中，这些特性似乎不太可能足够。</p>

<p>但在 Haskell 和 BitC 中，重载也扩展到了<em>类型属性</em>。例如，有一个类型类"Ord 'a"，它声明类型 'a 是否允许排序。问题：大多数允许排序的类型允许不止一种排序！我们知道存在一种排序<em>事实</em>并不能真正告诉我们应该<em>使用</em>哪种排序。而且我们不能在 Haskell 或 BitC 中为 'a 引入<em>两种</em>排序而不造成实例一致性问题。归根结底，实例一致性问题的存在是因为语言设计以一种相当于非作用域的方式执行方法解析。</p>

<p>但至少，你可以希望看到，BitC 和 Haskell 中对重载的更重使用对"单一实例"规则施加了更大的压力。在我看来，足以使该规则站不住脚。而且来自能力（capability）世界，我对那些有环境权限（ambient authority）气味的东西有过敏反应。</p>

<p>现在我们可以在某种程度上解决这个问题，通过对实例可以合法定义的位置（哪个编译单元）强加任意限制。但就像"过度抽象类型"的问题一样，我们似乎不断在类型类问题上绊倒。当多变量类型类（multi-variable type classes）出现时，还有其他问题。</p>

<p>归根结底，如果没有其他形式的支持，类型类作为重载解析的机制似乎效果不佳。</p>

<p>类型类的第二个问题是，你无法在静态编译时解析运算符。而如果实例是显式命名的，对实例的引用往往会变成一等值（first-class values）。此时，运算符引用根本无法再在静态时解析，而我们实际上重新发明了运算符方法！</p>

<h3 id="conclusion-about-type-classes-and-overloading">关于类型类和重载的结论：</h3>

<p><strong>类型类的概念（更准确地说：限定类型 qualified types）是诱人的，但如果没有合理的方法来处理实例一致性和词法解析，它就不能为运算符重载提供一个令人满意的基础。类型类实例和对象实例之间存在着令人不安的密切关系，需要编程语言界进一步探索。重要的区别可能是实践性的而非概念性的：类型类实例是编译时常量，而对象实例是运行时值。这对类型化没有重大影响，但在命名、绑定和[人类]概念化方面导致显著差异。</strong></p>

<p><strong>多参数类型类（multi-parameter type classes）仍然存在未解决的形式化问题。其中许多似乎在多态对象类型系统中有自然的实际解决方案，但实现方面的考虑促使了装箱（boxed）和拆箱（unboxed）类型之间的类型类区分，这相当令人不满。</strong></p>

<h3 id="wrapping-up">总结</h3>

<p>当前的结果<em>极其</em>令人沮丧。虽然这里的盲点是真实的，但学术研究界的要求驱使我们花了近三年时间寻找一种在可变性（mutability）上进行完全推导的方法。这是一个巨大的努力，它推迟了我们认识到我们完全基于错误类型的底层类型系统的事实。虽然我继续认为可变性推导有一些价值，但我认为原始推导机制中一个相当无关紧要的缺陷由于<em>政治</em>原因而阻止了整个项目在更大范围内的成功，这是一个耻辱。如果没有那个干扰，我想我们很可能已经从 I/O 和实例一致性问题中学到了足够多，能够在我们还有团队的时候转向不同的类型系统，今天我们就会拥有一个可用且有用的语言。</p>

<p>抛开学界的干扰不谈，可以合理地问为什么我们没有构建小型的"概念测试"程序来作为我们设计的健全性检查。有几个答案，都不太令人满意：</p>

<ul>
<li>研究语言可以在原语类型（特别是整数）上采用简化，而系统语言不能。这正是将我们推向类型类的第一步原因，我们知道对未装箱类型的多态在文献中尚未得到太多关注，而且我们知道可变性推导从未被做过。我们人力有限，所以我们选择首先关注这些问题。</li>
<li>我们知道参数多态和子类型相处得不好，所以我们想避免这种组合。不幸的是，我们过于成功地回避子类型太久了，而它们原来是不可避免的。</li>
<li>在最初的几年里，我们非常关注软件验证，这<em>也</em>强烈驱使我们远离基于对象的语言和子类型。这蒙蔽了我们。</li>
<li>作为"系统"人员进入语言设计，在一个缺乏类型系统深厚专业知识和兴趣的系里工作，有大量主题内容需要学习。我们失败的一些原因对编程语言界的人来说是"显而易见的"，但另一些则不是。我们对"系统"语言的渴望驱使我们以不同于编程语言界通常的方式和优先级来探索这个领域。</li>
</ul>

<p>我认为我们确实做出了一些有趣的贡献。我们现在知道如何（即实现）对未装箱类型进行多态，同时实现显著的代码共享，并且我们理解如何处理推导的可变性。这两个在未来都将非常有用。我们也对高级类型系统有了很多了解。</p>

<p>无论如何，BitC 按其当前形式显然需要搁置并重新设计。我对于如何<em>继续</em>这项工作有相当清晰的想法，但这必须等到有人愿意为此付费的时候。</p><p><em>由 mimo-v2.5 模型翻译，花费 17125 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/bitc-retrospective/</link>
      <guid isPermaLink="false">https://danluu.com/bitc-retrospective/</guid>
      <pubDate>Fri, 23 Mar 2012 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>封闭的社交网络是否不可避免？</title>
      <description>[AI 摘要] 文章探讨了封闭社交平台是否会因网络效应而主导未来，以及开放网络自由是否更可取。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 文章探讨了封闭社交平台是否会因网络效应而主导未来，以及开放网络自由是否更可取。</div><p><em>这是约2010年一次关于多种话题的Google Buzz旧对话存档，包括封闭平台是否会主导社交领域是否必然。</em></p>

<p><strong>Piaw</strong>：社交网络将主要被网络效应主导。这意味着从长远来看，Facebook将主导所有其他平台。</p>

<p><strong>Rebecca</strong>：……这也是为什么没有任何一家公司应该主导它。“社交图谱”及其相关应用应该像互联网一样，是分布式的，不局限于一家公司的服务器。我希望围绕这场争论的叙事能围绕这个理念展开，而不是围绕整个硅谷“谁是最天才的创新者”这种自吹自擂的不真实领域。感谢上帝蒂姆·伯纳斯·李不是来自硅谷，否则我们根本就不会有现在所知的互联网。</p>

<p>我想我不应该这么刻薄，向你展示我有时多么讨厌你们的叙事方式。但我认为这一次，不像往常那样，这不仅仅无害、可爱且令人愉悦——你们所有人集体搞砸了一些真正重要的事情，我很恼火。</p>

<p><strong>Piaw</strong>：网络效应的运作方式是，一家公司将控制它。这是不可避免的。</p>

<p><strong>Rebecca</strong>：不，这不是不可避免的！不可避免的是要么一家公司控制它，要么没有公司控制它。如果你们当时在写互联网发明的叙事，你们会争论整个互联网生活在一家公司的服务器上、由隐藏的专有协议协调是必然的。而这显然是疯了。</p>

<p><strong>Piaw</strong>：我明白了，社交图谱将是集体拥有的。这有道理，但我不明白为什么Facebook会有动机去促成这件事。</p>
<p><strong>Rebecca</strong>：当然没有！这就是为什么我咬着指甲，希望某其他公司能扮演白马骑士的角色，拯救世界，将社交图谱（或者更准确地说，是生活在它们之上的应用程序的API）从任何受单一公司控制的希望中解放出来。当然，其他公司也没有太大动机这么做——其他公司很可能只是嫉妒地注视着Facebook，希望自己先到达那里。蒂姆·伯纳斯·李为世界做了伟大的事情，但他没有变得富有，也没有给股东带来巨大回报，因此他创造的价值叙事并不包含在标准的公司炒作机器或激励机制中。</p>
<p>谷歌是唯一一家拥有合适定位、大致合适的动机，并可能拥有合适内部文化来成为新社交互联网“蒂姆·伯纳斯·李”的公司。这就是我所希望的，而且我有些沮丧的是，他们似乎没有在这种情况下有效地上场，尤其是在他们在Android中扮演类似角色时做得如此出色的情况下。</p>
<p><strong>Rebecca</strong>：这些评论背后潜藏着一种世界观，也许我应该试着解释一下。我一直对这个感到紧张，因为它包含了一些奇怪的想法，但我想知道你的看法。</p>
<p>这是一个非常奇怪的论断：马克·扎克伯格不是资本家，因此不应该用资本家的逻辑来评判他。在我被你斥为疯子之前，请停下来思考一分钟。成为资本家的本质属性是什么？</p>
<p>例如，当耐克去印度尼西亚建立血汗工厂，如果不满低薪和恶劣条件的共产主义者威胁要反抗时，他们会被告知“这是资本主义，无论它多么令人厌恶，资本主义都会让我们富有，所以闭嘴，保持沉默！”他们真正说的是什么？耐克给穷人带来缝纫机和分销网络，使他们能够制造和销售他们原本无法制造和销售的东西，因此他们变得更具生产力，从而更富有。生产能力是稀缺的，耐克正在给印度尼西亚带来这种稀缺资源的一块（并从其他人那里拿走，比如美国工人）。所以印度尼西亚变得更富有，即使血汗工厂的工人暂时受苦。</p>
<p>那么，马克·扎克伯格是否正在给美国工人带来稀缺生产能力的一块，从而使美国工人更富有？的确，他正在为人们创造他们以前没有的生产能力——编写社交应用的可能性，比如社交游戏。这是“创新”，它确实让我们变得更富有。</p>
<p>但这种财富并不遵循资本主义逻辑的规则。特别是，这种生产能力的财富，与建立缝纫机所创造的财富不同，它并不具有从根本上驱动资本主义逻辑的那种内在稀缺性。耐克可以为美国人或印度尼西亚人建立缝纫机，但不能同时为所有人建立。但蒂姆·伯纳斯·李不必做出这样的选择——他可以设计协议，让世界各地的每个人都能创造新事物，而无需限制他们选择的方式。</p>
<p>但——这是关键点——尽管没有自然稀缺，但很可能存在“人为”稀缺。微软可以混淆Windows API，并将IE与Windows绑定。Facebook可以封闭社交图谱，并强迫所有应用生活在其服务器上。像这样的“资本家”可以从这种人为稀缺中榨取租金。他们可以利用资本主义言辞的情感吸引力来为他们的寻租行为辩护。人们已经习惯于相信，当本地公司变得富有时，美国工人整体上也会变得更富有——它对印度尼西亚有效，为什么对我们无效？而Facebook和微软的员工正在变得更富有。Q.E.D.（证毕）。</p>
<p>但他们变得更富有的方式与血汗工厂员工变得更富有的方式不同。血汗工厂员工正变得比他们原本能够达到的更具生产力，尽管资本家的行为令人厌恶。但如果扎克伯格或盖茨行为可憎，通过创建一个围墙花园，这可能使他的员工更富有，意思是给他们更多的钱，但“更多的钱”与“更多的财富”不同。更多的财富意味着为工人提供更多的生产能力，而不是给个别员工更多的报酬。在制造业经济中，这两者是联系在一起的，所以人们忘记了它们并不相同。</p>
<p>事实上，像这样的诡计减少而非增加了所有人可用的生产能力，通过创造一种本不必稀缺的生产工具的人为稀缺性，目的仅仅是为了从中榨取租金。没有真正的财富来自这种榨取。总的来说，它让我们变得更穷，而不是更富。</p>
<p>这就是为什么像谷歌在Android上采取的行动，打破了iPhone的封锁，即使它没给谷歌赚钱，也应该被视为真正的财富创造者，即使它是否给谷歌股东赚钱尚不清楚。财富意味着我可以构建我以前无法构建的东西——如果我愿意，我可以在Android手机上安装Scheme或Haskell解释器，而我被禁止在iPhone上安装它。这对我很重要！谷歌对Firefox和Chrome的支持，加速了开放Web标准和HTML5的采用，也对我意义重大。我是一名美国工人，我因谷歌而变得更富有，意思是更多可用的生产能力可供我使用，即使谷歌在直接增加我薪水方面对我的个人财富贡献不大。</p>
<p><strong>Rebecca</strong>：（这个想法本身结果就长得惊人，在回顾时，原始评论的最后两段是一个略有不同的想法，所以我将它们分成一个不同的部分。）</p>
<p>我很沮丧谷歌面临很多反垄断类型的攻击，而我认为反垄断框架正是错误的思考方式。这场战斗不像罗斯福的大托拉斯拆分之战；它更像工业革命初期扬基商人与旧农业贵族利益之间的更早期的战斗，后者本会扼杀工业化。而谷歌一直是真正创造财富方面最一致的公司，它没有人为地限制它向各地开发者提供的生产能力。其他公司，如微软或Facebook，尽管它们是“新经济”，尽管它们是“创新者”，尽管它们似乎以大量金钱的形式产生了大量“财富”，但实际上更像旧贵族，而不是拼搏的扬基新人。在许多方面，它们正在减缓真正的革命，而不是加速它。</p>
<p>我一直不愿意过多谈论这些想法，因为我担心被称为疯狂的共产主义者。但我很沮丧谷歌成为误导性反垄断逻辑的目标，并且可能会被削弱到无法继续成为抵御真正“新经济”滥用行为的堡垒，而它在过去五年里一直如此。这种防御对独立开发者意义重大，我不希望看到它消失。</p>
<p><strong>Phil</strong>：+100，Rebecca。令人惊讶的是，在硅谷，软件自由的言辞几乎没有任何影响力，而几乎在世界其他地方都有。</p>
<p><strong>Rebecca</strong>：谢谢——我担心我超长的评论是否属于垃圾信息，很高兴听到有人欣赏它们。我很难让我的想法变短，但我正在利用这个Buzz对话来练习。</p>
<p>我对“软件自由”群体推销其信息的方式并不完全满意。我有一段时间在Richard Stallman隔壁办公室工作，经常被他训斥。然而，我认为他的信息太狭窄且激进化。但另一方面，当我深入思考时，我也意识到在许多方面它还不够激进……</p>
<p>我们为什么要谈论自由？为了说明这一点，我有时会讲一个过去的小故事。我年轻时，父亲给我读《海底两万里》，并称其为未来科幻的经典之作。不幸的是，我并不印象深刻。它看起来一点也不“未来化”：它似乎是一个古老的幻想。为什么？当然，令人印象深刻的是，一位作者在1869年正确预测人们将乘坐潜艇在海底航行。但它看起来一点也不像未来，甚至不像过去的景象。为什么？因为在潜艇中四处游弋的是维多利亚时代的绅士，周围是恰如其分地恭敬的维多利亚时代的仆人。</p>
<p>未来主义者总是以一种特定的方式搞错他们的故事：当他们说技术改变世界时，他们会讲述能够使人们做新的和令人兴奋的事情的奇妙小玩意儿的故事。他们完全忽略了这并不是真正的“变革”——严肃的、大规模的、剧烈的社会变革——真正含义。当技术真正实现了变革的梦想时，它并不意味着它使贵族能够在海底游荡。它的意思是它使贵族的管家梦想着不再当管家——一个不是通过暴力或革命，而是通过经济独立来实现的自由梦想。一个技术变革的梦想——真正重大的技术变革——不是闪亮小玩意儿的梦想，而是自由的梦想，是技术所促成的社会和经济解放的梦想。</p>
<p>让我们回到我们的印度尼西亚血汗工厂工人。尽管在许多方面，这份工作解放了她——摆脱了农场、垃圾场或妓院的繁重劳动——但她也相当受奴役。为什么？假设她会缝制高端篮球鞋，耐克以每双150美元的价格出售——远远高于她每月甚至每年的工资。为什么她只分得她劳动成果的一小部分？因为她依赖于耐克提供给她的生产能力，所以尽管这笔交易很糟糕，但这是她能得到的最好条件。</p>
<p>这就是新技术的用武之地。人们谈论信息革命，好像它是关于计算机或软件的，但我真正想说的是，它是关于社会（缓慢地）弄清楚如何实现组织自动化。我们已经学会了有效地实现制造业自动化，但现代经济的工作并非都是制造。耐克提供的什么服务使她如此依赖这笔痛苦的交易？其中一部分服务是他们提供的制造能力——缝纫机——但缝纫机并不昂贵或难以获得，即使是穷人也能拥有。更重要的是耐克提供的组织服务：所有品牌、物流、供应链管理以及将一双在印尼缝制的运动鞋送到美国热切消费者手中的零售服务。有人可能会争辩说，耐克低效地执行这些服务，因此即使我们的女缝纫工高效且能干，耐克也必须从运动鞋的销售利润中抽取不合理的高比例来支持其余低效、昂贵、完全未自动化的努力。</p>
<p>这就是技术变革的用武之地。慢慢地，它使得所有这些组织服务变得自动化、精简和高效成为可能。这正是谷歌所从事的业务。有人说谷歌是“广告”业务，但称谷歌所做的为“广告”，掩盖了他们只是提供初步交锋的深刻经济转型的真正故事。</p>
<p>想想最近在《纽约时报》上写博客的定制会议桌制造商（<a href="http://boss.blogs.nytimes.com/2010/12/03/adwords-and-me-exploring-the-mystery/" rel="noopener noreferrer">http://boss.blogs.nytimes.com/2010/12/03/adwords-and-me-exploring-the-mystery/</a>）。他说他去年支付给谷歌75,124.77美元。这笔钱代表什么——谷歌满足了什么需求，值得他每年支付七万多美元？你可能会说，他们是在获取公司广告预算，但直到你考虑到没有谷歌这家公司根本不存在之前。在谷歌之前，你经常能偶然发现制造定制会议桌的小企业吗？这是一种新现象！正确的看法是，这七万并不是正常的广告预算——相反，可以将其视为这个人不再需要为之工作的通用会议桌制造商收入的一部分。因为谷歌为他提供了品牌、客户服务等，这些是那家旧公司效率低下且缺乏创造性地在做的事情，这位博主有机会为自己创业。他每年支付谷歌七万美元以获得这种特权，但这可能远低于他的旧管理层从他的劳动成果中抽取的利润（更不用说他正在逃避的控制和被扼杀的创造力问题）。谷歌不是在销售“广告”：谷歌在销售自由。谷歌正在向全世界的工人销售摆脱锁链的机会——以非暴力的方式，没有任何革命性的言辞——但即使没有这种言辞，这项服务仍然关乎经济解放和社会变革。</p>
<p>当我听到埃里克·施密特谈论谷歌广告业务的未来计划时，我感到奇怪，因为他似乎在向华尔街讲述一个宏大的未来，在那里谷歌将捕获耐克广告预算的很大一部分（通过展示广告等）。这似乎既是一个过于雄心勃勃的幻想；也奇怪地远不够雄心勃勃。因为我认为谷歌业务的真正发展——不是今天，不是明天，不是明年，甚至不是下一个十年，但最终且不可阻挡地（假设谷歌能够抵御命运和文化衰落的变迁）——不是谷歌捕获了耐克的广告预算。而是谷歌捕获了耐克总收入的很大一部分，这笔钱由那些不再需要为耐克工作的工人支付给谷歌，因为谷歌或谷歌传统中的继任者为他们提供了一个更高效、更灵活的替代供应商，来提供耐克管理层目前提供的服务。</p>
<p><strong>Rebecca</strong>：（我再次查看我的评论，意识到它更长得可怕。我的想法在我脑子里很短，但即使我试图尽可能快速有效地写下来，它们也不再简短了！我再次看到评论有两个部分：第一，解释我们正在谈论的“自由”的基本概念；第二，将其与我们最初的讨论联系起来。为了保持某种程度的合理性，我把它一分为二。）</p>
<p>我想埃里克·施密特永远不会在华尔街面前这样说。当“我们会埋葬你们！”千真万确时，没有人会站起来边说边用鞋敲桌子。旧“新经济”的缔造者们也没有说过这样的话：扬基商人从未对他们的贵族政治对手说，他们打算最终彻底摧毁他们的社会秩序。1780年，没有任何讨论预言谢尔曼向大海进军的毁灭性暴力。我不确定他们自己是否知道，如果他们被告知那是他们劳动可能带来的结果之一，他们可能也不想听到。新阶级想要变革，想要机会，想要自由，但他们不想要流血！如果他们被逼入绝境而不得不寻求它，那对他们和任何人来说一样可怕。真正的变革不是任何人想要吹嘘的事情——它太可怕了。</p>
<p>但它仍然需要面对，因为在短期内，这种转变几乎不是必然或必然顺利的。如果我们的相当于谢尔曼向大海进军可能在未来等待着我们，我们可能想在为时已晚之前思考如何管理或避免它。</p>
<p>一个主要困难是，正如我在上条评论中解释的那样，虽然“信息自动化”如果得到正确发展，有潜力打破生产能力稀缺的基本规律，从而“解放全世界的工人”，然而这种潜力可以被攫取，并转化为“人为”稀缺性，这不能解放工人，反而进一步奴役他们。这样做也有很大的动机，因为这是处于正确位置和正确时间的人快速赚取大量金钱的最简单方式。</p>
<p>我将微软视为一家明确选择企业战略，通过“人为稀缺”赚钱的公司。我将谷歌视为一家做出类似明确选择，通过“销售自由”赚钱的公司，具体来说是避免那些创造人为稀缺的技巧，即使这无助于甚至损害他们的直接业务前景。</p>
<p>那么Facebook呢？谢丽尔·桑德伯格（显然是Facebook业务发展的策划者）在这个关键问题上的立场如何？一百年后，当所有你的“天才”和“创新”，你建造的所有让贵族们愉悦并被“未来主义者”如此颂扬的小玩意儿几乎被遗忘时，你在这个问题上做出的选择将被铭记。这很重要。</p>
<p>桑德伯格女士在这个问题上的哲学似乎同样明确：她只是想尽可能多地为Facebook创造多样化的收入来源。很难想象还有比理查德·斯托曼更不具意识形态对立的人了。自由还是稀缺，她不在乎：如果这是一种赚钱的方式，她就想要它。尽可能多的不同方式！她想要所有！我很难对此太过激动，特别是出于其他原因我支持桑德伯格女士的成功。即便如此，我宁愿是谷歌控制这场技术进步，因为谷歌在这个问题上的偏好如此明确和毫不含糊。</p>
<p>我不在乎谁是“天才创新者”，谁是“大输家”，无论这个或那个公司是否接过了进步的旗帜，谁会变得富有，哪家公司会吸引所有超级明星，或者所有其他对你来说似乎是关键问题的问题，但我确实关心你的工作是否朝着实现你的技术潜力迈出一步，以赋能全世界的工人，而不是减缓或阻碍它。既然谷歌在这个问题上做出了最明确的正确方向的偏好，那意味着我希望谷歌获胜。这太重要了，不能交给那些对他们的价值观模棱两可的人控制，无论我对这种务实的态度有多基本的同情。</p>
<p><strong>Baris Baser</strong>：+100！解放社交图谱！我希望我能分享这里在我的Buzz帖子上发生的叙事，但我只会把它插进去。</p>
<p><strong>Rob</strong>：谷歌在Orkut上确实搞砸了——他们让Facebook带着皇冠珠宝跑掉了。</p>
<p><strong>Helder Suzuki</strong>：我相信Facebook的主导地位最终会像今天的信用卡公司一样受到挑战。但我认为这对Facebook来说会快得多。</p>
<p>有很多不同之处，但我喜欢这个比较，因为信用卡公司曾经利用强大的网络效应来主导和屏蔽市场竞争。看看他们（Visa、American Express、Mastercard），他们今天基本上只剩下品牌。如今我们只是知道“信用卡”支付（以及利润率）在不久的将来会非常不同。</p>
<p>同样，我不认为社交图谱能在长期内保护Facebook的“市场”。就像今天建立一个POS网络比几年前容易得多一样，社交图谱在未来几年将变得微不足道。</p>
<p><strong>Rebecca</strong>：太棒了！有人在读我冗长且高度理智的评论。谢谢大家！</p>
<p><strong>Piaw</strong>：我不同意Helder的观点，尽管我同意Rebecca认为谷歌拥有社交图谱更好。Facebook完成的神奇壮举是让普通用户提供并上传他/她的所有个人信息。这是极其困难的：亚马逊做不到，谷歌也做不到。我不认为这是技术上困难的事情，但做到这一点所需的社会工程需要临界质量。这就是为什么我认为Facebook（仍然）被低估了。</p>
<p><strong>Rob</strong>：@Piaw - 我认为这是历史的偶然。当Facebook开始时，他们要求学生证才能加入。这建立了一种“真实姓名”的文化并保留下来，而其他任何人都无法复制。</p>
<p><strong>Piaw</strong>：@Rob：难以复制的历史偶然正是Facebook成为如此好的身份验证机制的原因。例如，如果我能让所有评论者透露他们的真实身份，我愿意不审核我的博客。例如，我在Quora上见过的最低质量的论点就是那些一方是匿名的。</p>
<p><strong>Elliotte Rusty Harold</strong>：这很烦人，我想分享Rebecca的评论。不是原始帖子，但我似乎无法做到。:-)</p>
<p><strong>Rebecca</strong>：在另一个对话中，有人将Buzz评论中的某个特定点链接到Hacker News（<a href="http://news.ycombinator.com/item?id=1416348" rel="noopener noreferrer">http://news.ycombinator.com/item?id=1416348</a>）。我不确定他们是怎么做到的。这有点奇怪，因为然后人们是在没有上下文的情况下看到的。这些评论是针对特定上下文量身定制的。</p>
<p>你想在哪里分享它？我不确定我是否准备好面对太大的观众；在一个网络偏僻的角落练习写作并获得反应是有目的的。毕竟，我说的是一些在硅谷可能具有冒犯性或令人反感的内容，无论如何，都非常大胆——对我有益的是与一群精选的朋友交谈，从他们那里获得关于它是否以及如何奏效的反馈。我不介意公开，但目前我也不介意默默无闻。</p>
<p><strong>Rebecca</strong>：说到这个，Piaw，我有点紧张地想知道你对我谈论“软件自由”的方式会有什么反应。我曾一度考虑成为斯托曼或ESR传统的软件自由倡导者，但更理智，更具历史视角，并且（希望）少一些论战式的疯狂。然而，加入理智和历史视角也增加了与真正的知识分子和历史学家碰撞的困难，这使整个事情变得充满风险，所以出于这个原因以及其他原因，我一直拖延着。</p>
<p>这次讨论让我重新挖掘了这个整个项目，部分原因是我真的想知道你的反应。然而，你只对Facebook的评论做出了反应，而不是更普遍的软件自由论战。你对此有何看法？</p>
<p><strong>Piaw</strong>：我大多没有对自由软件的论战做出反应，因为我同意你所说的。我同意像Adwords和谷歌这样的东西使得以前不存在的业务成为可能。例如，Facebook最近向我展示了一则Suzanne Vega音乐会的广告，我肯定不会知道它，但如果不是时间冲突，我肯定会想去。我想能够“喜欢”那个广告，这样Facebook就能向我展示更多类似的广告！</p>
<p>我同意Facebook的社交图谱成为一个开放系统会让世界变得更好吗？是的，也不是。从Facebook控制较少的角度来看，我认为这是一件好事。但我是否认为我想让任何人都能访问它？不。人们已经被训练成对Facebook中任何小程序请求的数据访问都点击“同意”，而我不需要在Facebook上被垃圾信息淹没——Facebook垃圾信息少得多的一个主要原因是，我的朋友比普通营销人员更尴尬向我发送垃圾信息，当他们确实发送垃圾信息时，通常是我感兴趣的东西，这使得它不是垃圾信息。</p>
<p>但是，是的，我确实希望我的Buzz评论（以及你的）都能传播到Facebook/Friendfeed等，世界成为一个巨大的开放社区，拥有受信任/经过验证的用户，并且完全没有垃圾信息（或者至少，我可以屏蔽未经身份验证的匿名评论者）。我屏住呼吸等待那个吗？不。</p>
<p>我感激Facebook使互联网的一部分（尽管是围墙花园部分）完全经过身份验证，因此更加有用。我认为大多数人并不明白这有多重要，这有多强大，而这正是Facebook值得华尔街给出的任何估值的原因。</p>
<p><strong>Baris</strong>：Piaw，这个讨论中潜藏着一个更根本的问题。最终，人们会倾向于聚集在兴趣相似的人周围，等待资源在那里增长（Facebook），还是会前往资源成熟、健康且增长迅速的地方（谷歌？）</p>
<p>人们最终会去谷歌，那里目前存在惊人的技术，并且根据当前趋势可能会放大（自动驾驶汽车、人脸识别、语音识别、实时语言翻译、完美的地理数据、拥有光明未来的移动操作系统、无与伦比的并行计算等），还是会首先加入他们朋友所在的当前最大的社交网络Facebook，然后等待技术到达那里？</p>
<p>假设性地看待这个问题：人们会搬到一个非常大的城市，等待它变得惊人，还是会搬到一个已经惊人的城市，等待其他人效仿？或者人们是否满足于拥有一堆惊人的小城市？</p>
<p><strong>Piaw</strong>：Baris，我认为你的类比还不完全正确。正确的类比是：你更喜欢住在一个小社区，有时你必须走很远才能得到你想要/感兴趣的东西，但相对没有犯罪，还是你喜欢住在一个大城市，那里很容易得到你想要的东西，但你会收到大量垃圾信息，偶尔有人会闯入你家？</p>
<p>世界上显然有两种人，这就是为什么郊区和大城市都存在。</p>
<p><strong>Baris</strong>：“大量垃圾信息，偶尔有人会闯入你家？”我认为这有点过于严厉/笼统了……沿用这个类比，我认为它变得更加主观，即真的取决于你在那个城市里的身份，你住在哪里，你拥有什么，你如何无忧无虑地生活等等。</p>
<p><strong>Piaw</strong>：对。Facebook已经成功地围绕这种自我中心设计了一个网站。你可以通过有选择地挑选朋友，成为你小镇的明星，或者你可以成为大都市的中心，接受所有人作为朋友。如果是后者，那么当你的“朋友”发布你的不雅照片让你与老板产生麻烦时，你就放弃了你的隐私。</p>
<p><strong>Nick</strong>：谷歌是唯一一家拥有合适定位、大致合适的动机，并可能拥有合适内部文化来成为新社交互联网“蒂姆·伯纳斯·李”的公司。</p>
<p>我同意谷歌在社交领域做得不好，但肯定比那强吧！</p>
<p><strong>Rebecca</strong>：哦，你对蒂姆·伯纳斯·李的工作印象不深？是最初的HTML标准你不喜欢，还是最近的W3C的东西？我承认两者都有可抱怨的地方。</p>
<p><strong>Nick</strong>：在我看来，TBL只是运气好。他在WWW上的原始工作很好，但我认为很难说他对它的成功负责——当然不比像Marc Andreessen这样的人更负责，他在Mosaic的初始成功之后，成功模式重复出现。</p>
<p><strong>Rebecca</strong>：@Piaw（在前面一点的地方）所以你发现我的自由软件论战如此无可非议，以至于几乎不值得评论？它难道不是在理智上有点激进，有所有那些“不是资本家”和“稀缺性质的改变”之类的东西吗？当我告诉Lessig部分的基本故事（不是在谷歌的背景下，因为那是很多年前），并就如何与经济学家交谈征求他的建议时，他警告我，我使用的词语包含太多古怪的理智激进主义的警示铃，经济学家会立即因为使用它们而不做任何进一步考虑就摒弃我。</p>
<p>令我惊讶的是，工程师们会毫无怨言地接受令人震惊的奇怪想法。我想，在Stallman和ESR的陪伴下，我是一个激烈的理智保守派和实用主义者，既然工程师们已经接受了他们的风格作为至少一种有效的讨论方式（即使他们不同意他们的政治观点），相比之下我显得温和。当然，与历史学家或经济学家交谈是另一回事，因为他们并不已经接受这是一种有效的讨论方式。</p>
<p>实际上，对我非常有用的是，有这个讨论线程可以展示给那些可能认为我是怪人的人看，因为它证明了在我的世界里，没有人对这种说话方式眨一下眼。</p>
<p>顺便说一句，我是因为克鲁格曼开始思考这个主题的。在九十年代末，我是克鲁格曼的狂热粉丝，风格现在很流行——“克鲁格曼总是对的”——但当时有点奇怪，当时他只是另一个几乎没人听说过的MIT经济学教授。然而，当他谈到技术时（<a href="http://pkarchive.org/column/61100.html" rel="noopener noreferrer">http://pkarchive.org/column/61100.html</a>），我认为他错了，这让我非常沮丧，因为我也几乎像宗教般相信他总是对的。在另一篇文章中（<a href="http://pkarchive.org/personal/howiwork.html" rel="noopener noreferrer">http://pkarchive.org/personal/howiwork.html</a>）他说，“倾听外邦人”对他非常重要，即“注意聪明人在说什么，即使他们没有你的习俗或使用你的分析语言。”但他也说，“我对那些批评模型构建者不现实简化的人没有同情心，他们以为通过避免清楚陈述自己的假设就能达到更高的复杂性。”所以对我来说很清楚，只要我愿意清楚地陈述我的假设，他就愿意听我解释为什么他错了。</p>
<p>在我确切知道我要说什么之前，我的计划是弄清楚我的假设以满足他的标准，然后请他帮助我完成其余的工作，将其全部转化为一个真正的经济模型。那时他只是我上过课的一位MIT教授，不是著名的《纽约时报》专栏作家、诺贝尔奖获得者名人，所以这个计划似乎很自然。MIT的教授们不反对学生指出错误，只要学生对此负责。我花了一段时间努力弄清楚我的假设是什么（假设？我有假设？）。当我确实这样做了，我有点惊恐地意识到，执行我的计划意味着骚扰他，要求他为我写一本新的《国富论》！（那时他已经去了普林斯顿，并开始变得出名，所以我的计划在后勤上比我计划的更困难。）我最初没有意识到我会要求什么，或者整个事情会如此令人生畏。</p>
<p>我向Lessig询问该怎么办（Lessig是我认识的唯一一个生活在两个世界里的人），Lessig给我讲了理智可敬的规则。所以看来要写新的《国富论》的将是我的责任，或者至少写足够多的部分来证明其中所包含思想的可敬性。我试图成为一名计算机科学学生，而不是经济学家，所以那种程度的努力几乎不适合我的计划。我试图通过在计算机科学实验室（现在的CSAIL）的“危险思想”研讨系列上做一次演讲来寻求帮助，但在我交谈过的教授中，只有David Clark对这种必要性表示同情。然而，他也非常清楚地表示，支持研究生与经济学家一起工作的资源有限，实际上只限于他正在推动的协议工作相关的非常具体的网络中立性之类的事情，而不是我思考的那种普遍世界观。所以我很惊讶地发现这种事情落在了学术文化不同部分之间的裂缝中。</p>
<p>我仍然不确定该怎么办，但我越来越倾向于忽视Lessig（隐含的）建议，即对我的理智可敬缺乏而表示歉意和辩护。那将需要我无法负担的程度的努力，因为我仍然专注于证明自己是一名计算机科学家，而不是人文学科的知识分子。（拥有这个讨论线程来指出在这点上相当有用。）我可以直接放弃（我确实放弃了一段时间），但我越来越沮丧的是，技术发展得比处理它所需的理智和社会进步快得多。人们似乎认为强大的技术本身就是一件好事，但事实并非如此：只有在强大的制度控制其权力的情况下，它才能为社会提供净效益——没有这样的控制，它可能是极其有害的。从这个角度来看，“新经济”不是好消息——“新”的意思是所有旧的制度现在都过时了，它们的控制不再起作用。而学术文化在文化上错位，确保没有人在真正处理这个问题。一幅美好的图景，不是吗？</p>
<p><strong>Nick</strong>：@Rebecca：我不理解你的论点。为什么谷歌销售广告比Facebook销售广告更关乎自由？</p>
<p>的确，Facebook没有向第三方提供他们的社交图谱和/或人口统计数据，但谷歌也没有向第三方提供一个人的搜索历史。为什么一个比另一个糟糕得多？</p>
<p><strong>Piaw</strong>：Rebecca，我认为更多数据更开放是理想状态。然而，我把它看作纯粹的学术讨论，原因就像我看待用TeX写《独立自行车旅行》是学术讨论一样。当然它可能发生，但发生的可能性渺茫到几乎为零，以至于我对讨论不感兴趣。</p>
<p>现在，我同意技术和其采用确实比我们的智慧和对其的控制增长得更快。然而，我不认为信息技术是罪魁祸首。人类重大的长期问题更多地与作为能源的化石燃料有关，而这是相当古老的技术。你可以解决世界上所有的隐私问题，但如果我们到2100年得到一个失控的温室行星，一切都将无济于事。因为那个，你找不到我为隐私或Facebook社交图谱的开放性而激动。如果Facebook确实让我个人反感，那么我会关闭我的账户。否则，我将继续利用他们工程师的工作成果。</p>
<p><strong>Elliotte</strong>：Rebecca，回过头来重读你的评论，我不确定你的分析是否正确，但我也不确定它是错的。当然，我不是经济学家。从我非经济学家的角度来看，它似乎值得进一步思考，但我也怀疑经济学家已经思考了很多。我首先要做的就是和几个经济学家聊聊，看看他们是否说了类似“哦，那是德弗罗的生产能力理论”之类的话。</p>
<p>我想我没看到你写的任何特别激进，当然也没有任何冒犯性的东西。你肯定不是第一个注意到软件经济与稀缺性的关系不同于实物商品的人。我也不会认为这与资本主义不相容。它只是与与现实世界相连的经济学家不相信的那种特定宗教资本主义观不相容。奥地利学派的神学意识形态家和电视新闻的喋喋不休的政客会称你为共产主义者（或者如今更可能是社会主义者），但你可以忽略他们。他们声称的信念实际上只是对经济学的拙劣模仿，与现实世界只有最微小的相似之处。</p>
<p>你听到很多这些幻想世界理论家的声音，是因为他们在过去的40年左右得到了公司和极度富有的人的充分资助，明确的目标是为财富辩护。在学术上，这最明显地体现在芝加哥大学，甚至在电视新闻中宣扬的伪经济学中更明显。在极端情况下，这些有偿的推销者宣扬自由放任的神学信念，认为市场是完全有效和理性的，因此市场所做的一切必然是正确的；但最近的经济危机让大多数人意识到这位皇帝没有穿衣服。从事科学而非神学的经济学家不理会这个祭司阶层。我希望流行媒体也能如此。</p>
<p><strong>Helder</strong>：我不太同意Rebecca提出的稀缺性观点。</p>
<p>一般来说，如果一家公司从某事中赚钱，那是因为他们正在生产某种财富，否则他们无法在经济上持续。它不必是像工厂里的生产性财富，它可以是文化的（例如电视节目），或其他东西。</p>
<p>即使你想到人为稀缺性，一家公司也只有在已经拥有巨大势头的情况下才能做到（例如Windows或Facebook的主导地位）。人为稀缺性单看是糟糕的，但它更像是基于已占主导地位的市场地位的“局部”优化。</p>
<p>也许Facebook、微软和其他公司如果“不被允许”充分利用他们的封闭系统，一开始就不会蓬勃发展。世界有一个封闭的Facebook和专有的Windows API比根本没有Facebook或Windows要好。</p>
<p>电视制作人在发行他们的节目和电影以实现利润最大化时，会尽力创造合适的稀缺性。如果他们采取某种自由开放的哲学，在第一天就发布他们的内容供下载，他们会破产，并在长期内破坏财富。</p>
<p><strong>Rebecca</strong>：谢谢大家的精彩评论！我感谢有机会回应这些反对意见，因为这是一个微妙的问题，我当然明白我的立场背后的推理远非显而易见。我今天无法完成，因为我需要外出一整天，而且我可能最好花点时间思考如何使回复尽可能清晰和简短。</p>
<p><strong>Rebecca</strong>：好的，我有大约四种不同的反对意见需要回答，我不想让它尽可能简短，所以我认为我会仔细安排，这样我就可以在一个回答中使用的想法来帮助我解释下一个。这意味着我将按Elliot、Piaw然后Nick和Helder的顺序回答。</p>
<p>事实上，我花了近一周的时间来撰写和编辑一个我满意并相信我尽可能浓缩的回答。尽管我努力了，它仍然相当长。然而，你们对我第一版的反应给我留下深刻印象，有些关键点我需要花时间来澄清：</p>
<ol>
<li><p>我不应该试图在一篇短文中谈论一个“不是资本主义”的系统，因为这太容易被误解了。我在下面的论证中花了相当大的篇幅，将Elliot对资本主义宗教观点的否认与明确否认资本主义终结的宗教观点相匹配。</p></li>
<li><p>Piaw也提出了一个好问题“为什么这很重要？”这并不明显；只有当你意识到几十年指数级技术增长能多么戏剧性地改变世界时，你才能明白。既然这个主题相当疯狂且难以看清，我试图用过去的一个形象来帮助我们预测未来的人们会如何以不同于我们看待自己的方式看待我们。我想让你深刻理解为什么后代很可能对什么重要、什么不重要做出非常不同的判断。</p></li>
<li><p>最后，我相当随意地说我想以标准传统谈论软件自由，只是更具理智和历史视角。在我写这个的时候，我意识到历史视角实际上改变了立场的实质，这一点我需要清楚说明。</p></li>
</ol>
<p>最后，我想再次退后一步，在一般意义上将这一切置于我试图实现的目标的背景下，并对你们对我理智激进的断言的反应进行一些评论。</p>
<p>这些回复被分成几个部分，这样如果整个内容太长，你可以选择你喜欢的部分。但给Piaw的长回答包含的关键思想是其余部分的关键。</p>
<p><strong>Rebecca</strong>：那么，首先，@Elliot——“我不是第一个注意到软件与稀缺性的关系不同于实物商品的人”但我对这种区别的看法并非通常的：我不是重复大家都谈论的无限可复制的东西，而是关注生产能力的稀缺性（或日益缺乏）。这种谈论方式更直接地挑战经济理论的基本假设，因此在理智上更激进：在形式上，它挑战了资本主义存在的理由。但你也没有买我“与资本主义不相容”的论点，我很高兴你没有，因为这让我有机会提到，就像你想要摆脱资本主义宗教观点一样，我也想特别摆脱资本主义终结的宗教观点。</p>
<p>马克思谈到了“资本主义的终结”作为某种神奇的体系，在这个体系中，工人们能够夺取生产资料（工厂）的所有权，并在没有资本所有权的情况下使经济运作。他同时也预言资本主义必将终结，毕竟，封建主义已经终结。但如果你将这两个断言结合起来，并解决逻辑三段论，你会得出断言封建主义终结是因为农奴夺取了生产资料（农场）的所有权，并在没有土地所有权的情况下使经济运作。这不是真的！我在爱荷华州长大。那里有地主拥有的土地面积超过大多数传说中的中世纪国王。没有人挑战他们的所有权，然而没有人会称那个制度为封建主义。为什么？因为他们的田地是由机器收割的，而不是农奴。封建主义的终结不是因为土地所有阶级改变了他们的土地所有方式。是因为土地劳作阶级，即农奴，离开农场去工厂找更好的工作；而地主们不再在意，因为他们最终用机器取代了农奴。封建主义的终结不是土地所有权的终结，而是与那个所有权相关的社会地位和一系列特权的终结。如果你的附庸是机器，你无法对他们作威作福。</p>
<p>同样，在非宗教的资本主义终结观中，它的到来不会是因为资本家阶级，拥有工厂的阶级，会消失或改变他们的做法，而是因为无产阶级会消失——他们会离开去做其他更好的工作，而工厂主将用机器取代他们。事实上，你可以看到这已经在发生。你是无产阶级吗？我是吗？如果我创建一个STL模型并通过Shapeways打印出来，我是在制造东西，但我不是无产阶级。Shapeways确实在筹集资金购买他们的打印机，严格来说这使他们成为“资本家”，但在社会意义上他们不是资本家，因为他们与我的关系具有马克思激烈反对的那种不同的权力结构。我不是一个被他们作威作福的“无产者”。这不是马克思所设想的大规模革命；它几乎微妙到你会完全错过它。如果资本主义终结了而没有人注意到呢？</p>
<p><strong>Rebecca</strong>：接下来@Piaw——Piaw说他认为信息技术不是技术领域中增长快于我们控制的最大冒犯者；例如他认为全球变暖是更紧迫的当务之急。</p>
<p>我完全同意信息技术和相关社会变革当前产生的问题，现在与全球变暖相比是小问题。如果我们能先解决最直接和紧迫的问题，等到它们变得足够大时再处理其他问题，那将很好。但新经济问题有一个独特的特征，它们之所以紧迫，并不是因为它们必然迫在眉睫或巨大（目前），而是因为如果它们被放任不管，它们可能会摧毁政治系统有效处理这些问题或任何其他问题的能力。</p>
<p>我是一个相信通过过去镜头理解现在的人：因为我们对很多年前发生的事情有更多的视角，我们可以通过理解现在发生在我们身上的事情与很久以前发生的事情的相似性来解释现在并预测我们的未来。为此，我想指出与上一个“新经济”早期试图推动新自由理念却遭遇认为他们在无足轻重的问题上小题大做的人的反对的一个虚构形象的类比。（这个形象是虚构的，这也是我的一部分观点——请耐心听我说。）我的形象来自音乐剧《1776》中的一个戏剧性场景（其概要可以在<a href="http://en.wikipedia.org/wiki/1776_%28musical%29" rel="noopener noreferrer">http://en.wikipedia.org/wiki/1776_%28musical%29</a>找到，第七场），其中一位“令人讨厌且不受欢迎”的约翰·亚当斯几乎因为坚持在《独立宣言》中加入谴责奴隶制的条款而让爱德华·拉特利奇和其余南方代表团投反对票。他放弃了这一坚持，只有当富兰克林的论点说服他改变主意时，即与英国人的战斗比任何关于这个问题的争论都更重要——“我们必须团结在一起，否则我们将被绞死”。</p>
<p>事实上，类似的事情从未发生过：正如维基百科页面上的历史注释所说，当时每个人都完全同意这个问题太不重要了，不值得为之战斗，更不用说像音乐剧中描绘的那样发生大对抗，拉特利奇戏剧性地但不太可能地唱了一首令人毛骨悚然的美丽歌曲来为三角贸易辩护：“从糖蜜到朗姆酒到奴隶”。这个场景被插入是为了满足现代观众的情感，即无论这样的对抗是否发生过，它都应该发生过。</p>
<p>为什么我们的情感与现实如此不同？为什么我们强加给过去一个想法，认为战斗对他们来说应该是重要的，即使它不是，认为约翰·亚当斯应该在他坚持美国自由立国价值观时令人讨厌和不受欢迎，即使他没有，认为富兰克林应该极不情愿地论证与英国人的战斗更重要，即使他从未做出过那样的论证（因为这是不言而喻的），认为爱德华·拉特利奇应该是一个同样顽固的奴隶制辩护者，即使他也不是（后来他解放了自己的奴隶）。我们强加这种错误的叙事，是因为我们正透过一个我们对未来有所了解而真实的参与者对此一无所知的镜头向后看。理解这一点很重要，因为相对于后代，我们可能处于类似的位置——他们会认为我们应该进行一场战斗，而事实上我们无意进行，因为他们将知道一些关于我们自身未来的事情，我们现在很难想象。</p>
<p>所以请原谅我在这个观点上唠叨：这是我对Piaw问题以及Nick和Helder反对意见回答的关键。它需要一点篇幅来设定场景，因为对我来说，让我的听众回到一个与我们自身非常不同的历史心态并不容易。但我想进行这个练习，以便从中提取出关于政治思维方式在面对剧烈技术变革时如何以及为何转变的普遍理解——我们可以用它来预测我们自己的未来以及我们政治的不断变化的形态。</p>
<p>这个故事背后的真实人物不知道而我们现在知道的东西是什么？从约翰·亚当斯开始：要理解为什么真实的约翰·亚当斯在1776年不会非常令人讨厌地将他的自由理念强加给奴隶主，要意识到他的自由理念，如果用经济而非道德的术语重述，将是断言“美国所有公民都应享有绝对的权利，离开他们出生的农场，在工厂里寻找更好的工作。”但在1776年大谈特谈这样的权利将是荒谬的。工厂并不多，而且它们效率低下，提供的工作充其量也不吸引人。例如，在杰斐逊写的时候，他非常认真地认为农业生活比工业生活更道德：在1776年，这种情绪似乎很合理，因为坦率地说，工厂生活很可怕。因此，1776年的政治家，如亚当斯或汉密尔顿，他们对工业化如此着迷，带着歉意的态度推动他们的痴迷，就好像他们只是在谈论他们自己的个人偏好，他们煞费苦心地表明他们不打算将其强加给任何人。真实的约翰·亚当斯远不像我们想象的版本那样令人讨厌：我们想象他不同只是因为我们希望他不同。</p>
<p>我们希望他与真实的他不同，因为有一个重要的事实，1776年的人们可能在理智上理解，但完全没有开始理解其全部社会意义：工厂正以指数级速度变得更好，而农场将永远保持不变。类似摩尔定律的技术增长率并不是一个新现象。十九世纪初棉纺织品生产的改进让观察者感到震惊，就像今天的芯片或内存一样——在棉花纺纱完成后，其他进步依次成为焦点，正如www.theatlantic.com/magazine/archive/1999/10/beyond-the-information-revolution/4658/的文章试图让我们印象深刻的那样。我们忘记了戏剧性的技术指数级改进并不是新现象。我们也忘记了如果指数级增长运行几十年，它会改变事情……它改变的程度比任何人在这样一段增长开始时敢于想象的还要大。</p>
<p>这就引出了我们故事中其他做出我们现在希望他们做出不同选择（他们后来也后悔了）的人物。爱德华·拉托利奇和托马斯·杰斐逊并没有真正为奴隶制辩护；他们相当公开地表示对此感到不安，但他们认为这种不安不足以采取任何行动。这种立场在1776年也是有道理的：地主们从古代就拥有奴隶，但古代的奴隶制与当时下层阶级可用的其他选择相比并不特别繁重——有著名的故事讲述机灵的希腊和罗马奴隶赢得自由并上升到社会高层。拉托利奇和杰斐逊可能认为他们是在为他们的奴隶提供类似的交易，总的来说，这还不算太糟。</p>
<p>他们错了。美国的奴隶制被证明是独特的，完全不同于古代的奴隶制。我的美国历史老师将其呈现为一个“悖论”，即建立在自由理想上的国家也是世界上最残酷的奴隶制系统的家园。但我认为这个“悖论”相当容易理解：它是同一现象的两个部分。问问这个问题：为什么古代奴隶主能够相对温和？因为他们对自己的地位也相对安全——他们的奴隶和他们一样清楚，下层阶级没有多少其他更好的选择。莎莉·海明斯，杰斐逊的情人，当她在法国与他在一起时考虑过逃跑，但杰斐逊成功地说服她，留在他身边会得到更好的交易。他不必用锁链把她带回家：她放弃了在法国自由的可能性，自愿回来了（尽管有些惆怅）。</p>
<p>但随着时间的推移，北方的工厂工作沿着摩尔定律的轨迹发展，最终下层阶级可用的替代方案开始看起来比人类历史上任何时候都好。哈丽特·塔布曼偷运到加拿大的奴隶到达后，发现海明斯如果离开杰斐逊也无法指望的选项呈指数级改善。结果，人类历史上第一次，奴隶必须被锁链束缚。</p>
<p>用我之前使用的更抽象的术语来说，当束缚奴隶与主人的稀缺机会是真实的时，奴隶制相对温和，但当其他机会变得可用时，这种“真实稀缺”变成了“人为稀缺”，必须通过锁链——和法律——来强制执行。这就是奴隶主转变为某种独特残酷的原因：为了保护他们的生活方式，他们不仅需要把他们的奴隶锁起来，他们还需要接管社会的政治和法律机器来使那些锁链合法。单议题政治家出现了——一个进入政治生活的动机不是理解或解决国家面临的问题、倾听其他观点或达成妥协，也不是正常政治家做的任何自然事情，而仅仅是为了争取一个议题：将维护其选民生活方式所必需的“人为稀缺”写入法律，并玩弄任何残酷的政治手段来使那些法律保持有效。政治暴力并非不存在——最近的社论“当国会全副武装且危险时”（www.nytimes.com/2011/01/12/opinion/12freeman.htm）提醒我们，当今政治中对暴力的煽动与1830年代、40年代和50年代的政治暴力相比是温和的。19世纪60年代初是几十年灾难的高潮，我们希望开国元勋们能够预见并避免它。我们希望他们还有时间就奴隶制进行争论时进行争论——在支持它的精英毒化政治系统以提供其辩护之前。</p>
<p>他们在晚年也这样希望：在杰斐逊拒绝将奴隶制作为《独立宣言》辩论中的重要问题四十五年后，他被“夜半火铃”惊醒，即密苏里妥协。这场战斗的消息使他意识到现实情况，他给一位朋友写道：“我们抓住了狼的耳朵，我们既不能抓住它，也不能安全地放开它。正义在一个天平上，自我保存在另一个天平上……我遗憾的是，我将在这样的信念中死去：76年一代人为了获得自治和幸福而对他们国家的无谓牺牲，将被他们儿子们不明智和不值得的激情所抛弃，我唯一的安慰是我将不会活着为此哭泣。”</p>
<p>因此，在他拒绝处理一个“不重要的”、“学术的”问题四十五年后，他说起那个决定的后果是“我唯一的安慰是我将不会活着为此哭泣”。他没有料到他儿子们“不明智和不值得的激情”——就他个人而言，当经济条件不再给予奴隶制道德辩护时，他乐于让奴隶制逐渐消失。然而，下一代有不同的想法——他们想不惜一切代价保留他们的特权。到那时，他的选择由他所交往的人定义：既然他是弗吉尼亚人，他将不得不为弗吉尼亚而战，反对他所信仰的一切。他本想回到他还能做出自己选择的时候，但那个时代已经过去，无论现在控制他的激情多么“不明智和不值得”，他都别无选择，只能被它们席卷而去。</p>
<p>这就是我关于为什么我们应该关注“不重要的”和“学术的”问题的论点。在1776年，同样“学术”地向前看，通过七十五年的指数级增长来预测1860年的经济条件，并利用这种预测来激励对当时条件下看似荒谬的抽象原则进行认真思考，并在几十年后成为燃眉之急。然而我们希望他们那样做，而且在他们的晚年，他们也热切地希望自己那样做了。这似乎很奇怪：为什么在1776年为1860年做计划？为什么在2010年为2085年做计划？为什么不等到我们到了那座桥再说？让下一代担心他们自己的问题；我们为什么要为他们思考？我们有自己的燃眉之急要担心！2085年的预期问题是抽象的、学术的，对我们来说无关紧要。为什么不放任不管，专注于我们当前的燃眉之急呢？</p>
<p>困难在于，如果我们不放任不管，如果我们不把关于我们的价值观的战斗荒谬地投射到未来，并开始处理随着几十年的时间和技术变革而转变的冲突形态，我们很可能失去政治行动自由来非暴力地解决这些问题——或者处理任何其他问题。我们会“抓住狼的耳朵”。我们希望1776年的领导者们能够设想并认真对待1860年的问题，因为在1776年他们仍然是理性的，可以相互交谈并有效地达成妥协。到1860年，这个选项不再可用。问题在于，当这类问题最终不再是“学术的”，不再是知识分子的梦想，而成为数百万人的燃眉之急时，火就烧得太热了。太多有权势的人选择“抓住狼的耳朵”。这只狼很可能吞噬整个政治和法律系统，使其无法处理那个问题或任何其他问题，直到恢复政治实体的唯一选择是内战。一旦发生这种情况，每个人都会热切希望他们能回到战斗“仅仅是学术的”那个时代。</p>
<p>我大约在2003年构思了这个故事，因为从1998年开始，我想给一个无名的焦虑起个名字（在这之间，我经过一番挣扎，弄清楚我最相信哪个历史形象）。当我确定时，我考虑去找克鲁格曼，用这个故事来发脾气，说他绝对必须停止忽视那些试图与他谈论“自由”的怪人。但我犹豫了：我担心整个论证会显得理智可疑且情感操纵。此外，这个故事预测的直接危险——政治会退化为1830年代那种单议题瘫痪——在2003年似乎有点荒谬。克鲁格曼对2002年的选举结果不满意，但情况没那么糟。但现在我感到有些后悔，事情恶化得比我想象的快。我没有准确预测发生的事情。我非常关注科技，所以我没有预料到为拥有“抓住狼的耳朵”的权贵服务的政客会得到你能想象到的最古老的传统经济势力——银行和石油——的资助。这个结果与这个论点并不矛盾：当新经济承诺新自由时，如此传统的资本主义获得前所未有的残酷性，正是这种推理路线所预测的。我担心克鲁格曼会生我的气，因为我在2003年没有打扰他，因为他想要的额外政治行动自由可能比他讨厌非常不合理的理智论证更甚。</p>
<p><strong>Rebecca</strong>：既然我已经打好了基础，我就更容易回答Nick和Helder了。你们俩基本上都在告诉我，我不可理喻且令人讨厌。我将完全同意这个反对意见，与斯托曼大相径庭。我不可理喻且令人讨厌。然而，这种疯狂是有普遍方法的：正如我在上面解释的形象中，我本质上是在推动几十年后才有意义的价值观，并将它们拉回到当前时间，诚然有些不合时宜。我认为这样做的主要原因不是因为我提倡的价值观现在就必然绝对适用（像斯托曼会说的那样），而是因为现在开始这场战斗比以后处理它容易得多。现在战斗的原因恰恰是因为对手仍然理性，而他们以后可能不是。与斯托曼不同，我想强调我对对我立场的合理反对的尊重（和感激）。我的对手不太可能射杀我，这不是一个理所当然的特权，我想在拥有时加以利用。</p>
<p>具体回应你们的反对意见：Helder抱怨说我称为“剥削人为稀缺”的策略是公司收回原始投资所必需的——如果不允许，该服务根本就不会存在，那会更糟。Nick反对说，Facebook计划收入的80%或90%与谷歌的来源基本相同，那么为什么我只因为另外10%或20%而抱怨？那就是我所抱怨的——他们的一部分收入来自封闭平台并向开发者征税——但这只是桑德伯格女士多元化收入计划的一小部分，我承认其余部分在逻辑上与谷歌的策略很难区分。在两种情况下，都很容易说我是采取了极其不合理的强硬立场。</p>
<p>让我们深入剖析我有多么不理智。在两种情况下，不理智都源于我总体论点中的一个问题：我说马克·扎克伯格不是资本家，也就是说，他没有筹集资金购买使他的工人更具生产力的实物——但这不完全正确。Facebook的数据中心很昂贵，它们是允许他的员工完成工作的必要条件。</p>
<p>关于这个主题的最好的故事也可能是一个例外，证明了这个规则。科技大亨创业最“资本主义”的故事是Larry和Sergey如何通过刷爆信用卡购买一太字节磁盘开始的记述（<a href="http://books.google.com/books?id=UVz06fnwJvUC&amp;pg=PA6#v=onepage&amp;q&amp;f=false" rel="noopener noreferrer">http://books.google.com/books?id=UVz06fnwJvUC&amp;pg=PA6#v=onepage&amp;q&amp;f=false</a>）。这个故事本可以由霍雷肖·阿尔杰撰写——它如此精确地遵循了标准资本家创业的脚本。但尽管如此，L和S并没有制造所有标准的资本主义噪音。我很早就成为谷歌的粉丝，甚至可能从他们的原始太字节中提取过数据，我从未听说过他们需要对我施加限制来收回他们的投资。一年后，当我在他们的招聘活动上与谷歌员工交谈时，我觉得他们对完全没有收入策略的异常乐观感到奇怪。但他们还是变富了。现在那个太字节的成本不到100美元。</p>
<p>最后一点是关键：并不是说投资不重要且需要收回。而是它们的规模正在以指数级速度缩小。在上一节中，我强调了几十年技术指数级改进的巨大变革效应，以及将自己的价值观向前投射到那些几十年的重要性，即使这意味着做出目前看来荒谬的断言。需要收回的投资是真实且重要的，但它们也在缩小，以指数级速度。因此，为了收回投资而断言限制人们机会自由的权利的经济基础充其量是暂时的。而且，正如我在上一部分所描述的，在现在是真实但很快将是人为的稀缺基础上断言特权是……危险的。即使你真诚地认为你会随着时代的变化而改变，你可能会在真诚的恐惧中发现，当需要做出新选择时，你不再有选择权。你的选择将由你交往的人决定。我之前没有说，但我对Facebook最担心的一件事是，他们似乎与高盛联手。即将成为亿万富翁的科技大亨向劳埃德·布兰克芬学习政治策略，这并不让我高兴。</p>
<p>我很高兴你提出反对意见，并允许我花时间更仔细地解释这一点，因为实际上我的观点比我最初账户所暗示的更微妙和细致——我最初账户是为了节省空间而简化的。（Lessig告诉我我必须写一个不超过五页的账户才有望被倾听，我对此有些绝望。我做不到！这个问题有超过五页的复杂性！如果我试图简化到某个程度以下，我就会把事情搞砸。你正在目睹我公开与这个问题作斗争。）</p>
<p>“资本主义终结”的神话还有另一个部分，我应该花时间来否认。神话说仿佛有一个清晰的历史时刻，一位报喜天使出现并宣告新社会秩序的到来。现实并非如此。在历史书中，当几页纸涵盖了杰斐逊在《独立宣言》中划掉他谴责奴隶制的句子与他写下“夜半火铃”信之间的四十五年时，似乎像是那样。但在真实的、活生生的生活中，四十五年是一个成年人的大部分时间。当杰斐逊写“正义在一个天平上，自我保存在另一个天平上”时，他能指出过去四十五年中天平移动到另一边的某个具体时刻吗？没有一个具体的时刻：只有工业生活生产力持续但不戏剧性的指数级增长，其道德劣等性在四十年前似乎如此明显。他没有注意到，过渡时刻没有吹响号角（如果存在这样一个时刻的话），所以当他听到警报，意识到世界已经改变了多少时，已经太迟了。</p>
<p>同样地，我认为我们现在能做出的唯一明确判断是，我们正处于转型的中间。在某个时间点，磁盘驱动器如此昂贵，存储在其上的数据如此微不足道，所有者收回投资的权利显然超过了所有其他关注点。几十年后会有另一个时间点，那时磁盘驱动器如此便宜，存储在其上的数据对用户和经济的健康如此关键，以至于“软件自由”的权利显然超过了硬件所有者的权利。会有某个时间点“太早”为新自由而战，也会有某个时间点“太晚”。在这两个时间点之间？只是一个漫长、缓慢、稳定的改变步伐。在那段时间里，无论我们如何恳求她给出明确性，正义之手可能拒绝将重量放在天平的任何一边。我们可能生活在两个时代之间，无法做出非黑即白的判断，一切都是灰色的。</p>
<p>但人们想要明确的判断：他们想知道什么是对的，什么是错的。这种灰色是危险的，因为它开启了一个权力真空，被最糟糕的类型热切填补，引起无名的焦虑，并导致政治恐慌。那么你能做什么呢？我认为不可能对哪个时代的价值观应该适用做出非黑即白的判断。但可以肯定地说，值得保留政治行动的自由，以便在那场战斗变得明确适当的时刻捍卫新时代的价值观。我真的很不介意公司做任何必要的事情来收回他们的初始投资——只要它是暂时的。但我有什么保证，如果Facebook实现了其500亿美元的市值，他们不会用这笔钱来收买政客，以证明他们无限期地继续他们的做法？他们与高盛的关系并没有让我对此放心。我更信任谷歌能够允许，甚至参与，诚实的政治讨论。这才是我真正担心的问题。公司购买政治话语的速度和有效性令我惊讶，即使我有理由预测到这一点。当权贵们“抓住了狼的耳朵”时，他们会变得真正可怕。</p>
<p><strong>Rebecca</strong>：你可能有道理，这种论点并不像以前那样非正统。毕竟，许多真正的经济学家一直在谈论“新经济”——彼得·德鲁克在《超越信息革命》（上面链接）中，哈尔·瓦里安在《信息规则》中，拉里·萨默斯在演讲《新国富论》中，甚至克鲁格曼在他简短的文章《动力与微芯片》中，以及大卫·布鲁克斯最近的社论《协议社会》中调查的一群年轻经济学家。但我并不像布鲁克斯那样对观察结果感到满意：“值得注意的是[这些‘新经济’理论家]正在从数学建模转向社会学和人类学等领域。”有一种感觉认为我的态度现在比正统更正统——尽管我在这里勾勒的论点不是数学模型，但它非常有意识地设计成数学模型，并且我强烈同意克鲁格曼的态度，认为没有数学就不够好。经济学应该是一门科学，数学纪律迫使理智诚实，并提供抵御腐败的堡垒。</p>
<p>我震惊的是，这轮“新经济谈论”如此松散、草率且新闻化……而且因为它在理智上如此草率，很难判断它是否腐败。例如，虽然我像读过的任何东西一样喜欢德鲁克1999年文章中的历史观察和由此得出的结论，他对电子商务革命性影响的赞美读起来如此像点com广告，几乎令人尴尬。不过公平地说，文章结尾确实有一些暗示，意识到工业革命不仅仅是被技术仙尘魔法洒落，还涉及痛苦的社会动荡的某些方面——即便如此，他的故事如此奇怪地乐观，特别是鉴于他显然深刻理解历史思维，他应该知道得更好……人们想知道他是在试图不得罪谁。同样，我们能信任萨默斯声称的天才，还是他从各种银行获得的数百万美元，嗯，影响了他的思考？如此等等……克鲁格曼是我唯一能确定信任的人了。以此为业的人应该做得更好！即使我理解Lessig关于我理智激进的观点，我也很难想要遵循它，因为我的部分挑战是想让这些人展示足够的理智严谨性来证明我可以信任他们。</p>
<p>公平地说，我承认布鲁克斯观点的一部分，即新经济有一种“人类学”的东西：它倾向于让所有人发疯。想想十九世纪新兴的工业富人——穿着像伪贵族一样讲究，高顶礼帽、手杖、做作的口音，也许还有买来的婚姻给自己弄个头衔——他们像布谷鸟钟一样疯狂！深刻、剧烈、技术驱动的变化会让人这样。但仅仅因为它是疯狂的，并不意味着它没有适合数学建模的方法。克鲁格曼写道（<a href="http://pkarchive.org/personal/incidents.html" rel="noopener noreferrer">http://pkarchive.org/personal/incidents.html</a>）他年轻时，阿西莫夫的《基地三部曲》激励他梦想长大后成为“心理历史学家，使用他对社会数学的理解在银河帝国崩溃时拯救文明”，但他说“不幸的是，还没有这样的东西（还没有）。”你认为他会被一个真正成为“心理历史学家”的机会所诱惑吗？</p>
<p>我从未打算独自进行这场战斗。我将其转化为经济和历史语言的全部原因是我希望说服那些以此为业的人为我承担它。我负担不起独自战斗：我没有时间陷入政治争论，我也负担不起与可能想要为之工作的人树敌。我在这里提出这些论点主要是因为与其他科技人员辩论的记录将有助于向知识分子证明我的严肃性。我有些困难让你们理解，但我想我会有一场极其艰难的战斗去说服知识分子我不是“狼来了”——他们已经听过这种论点被误用太多次了。我不得不承认我是狼来了，但我这样做的原因是这次真的有狼！</p>
<p><strong>Piaw</strong>：问题是，Rebecca：在1776年不可能进行关于自由/奴隶制的争论。后来带来的变化使它能够在以后很久才成为可能。内战是可怕的，但我真的不确定是否可能更早改变制度。</p>
<p><strong>Ruchira</strong>：嗨Rebecca，</p>
<p>我还没有读完这个长对话。但如果你还不熟悉竞争性与非竞争性以及排他性与非排他性的概念</p>
<p><a href="http://en.wikipedia.org/wiki/Rivalry_(economics" rel="noopener noreferrer">http://en.wikipedia.org/wiki/Rivalry_(economics</a>)</p>
<p>这些术语可能有助于将你与其他人关于你所谈论的问题的思想联系起来。特别参见公共物品下的“可能的解决方案”：</p>
<p><a href="http://en.wikipedia.org/wiki/Public_good" rel="noopener noreferrer">http://en.wikipedia.org/wiki/Public_good</a></p>
<p><strong>Daniel Stoddart</strong>：我以前说过，现在再说一遍：我不会那么快就把谷歌排除在社交之外。哦，我知道像Scoble那样嘲笑Buzz很酷，说他在Quora上有更多关注者。但那有点像拿苹果和橘子做比较。</p>
<p><strong>Ruchira</strong>：Rebecca：好的，现在我已经读完了整个长对话。我确实认为你的观点很重要，但我还没有完全消化，无法形成自己的意见（这需要判断它与其他重要问题的关联）。只有几个无关紧要的想法：</p>
<p>1) 如果你害怕失去自由，要提防军工复合体。你在其他地方描述了它的一些好处，但这就是为什么你不应该被这些好处所迷惑，就像你不应该被你所描述的问题所迷惑一样。思考不受触动且无需负责的国防开支的长期后果，以及维持其不受触动且无需负责的地位的现状相互交织的属性。它们与信息隐藏和缺乏透明度根本相关。</p>
<p>2) 存在一种心理历史学：cliodynamics。 <a href="http://cliodynamics.info/" rel="noopener noreferrer">http://cliodynamics.info/</a> 据我所知，它还没有充分发展到可以应用于未来。</p>
<p><strong>Ruchira</strong>：Rebecca：说到这一点，我想知道你对Noam Scheiber的文章“为什么维基解密会杀死大企业和大政府”<a href="http://www.tnr.com/article/politics/80481/game-changer" rel="noopener noreferrer">http://www.tnr.com/article/politics/80481/game-changer</a>的看法。他肯定在思考技术将如何引起社会组织方式的巨大变化。</p>
<p><strong>Helder</strong>：（注：我没有仔细阅读全部内容）在某些封闭系统的情况下，使其开放的成本（以及缺乏商业合理性）以及保护业务的普遍需要远远超过了投资回报的需要。所以这并不完全是关于ROE。</p>
<p>此外，社会的技术发展和新业务资本需求规模的缩小（例如太字节成本），通常并不有利于封闭系统业务，它可能只会削弱它。你可以拥有一个围墙花园，但随着外部地面水平上升，墙会越来越矮。看看操作系统如何变得越来越不重要，因为大多数活动转向浏览器。另一个例子（也许有待观察？）是我在我第一条评论中提到的信用卡行业。</p>
<p><strong>Rebecca</strong>：感谢阅读这篇冗长的帖子并给我反馈！</p>
<p><strong>Ruchira</strong>：Helder：Facebook为它的开发者缩短了围墙（我确信Zynga认为他们因Facebook而增长了财富）。这直接导致了隐私方面的强烈抗议（围墙花园不再有围墙）。</p>
<p><strong>Rebecca</strong>：希望它能引发思考！你可能也会对David Singh Grewal的《网络权力》感兴趣<a href="http://amzn.to/h72nNJ" rel="noopener noreferrer">http://amzn.to/h72nNJ</a> 它讨论了许多相关问题，并且没有假定太多背景知识（因为它针对多个学科），所以我作为像你一样的局外人发现它非常有帮助。之后，你可能（也可能不会）对协调问题感兴趣——如果你感兴趣，Richard Tuck的《搭便车》<a href="http://amzn.to/f9goyT" rel="noopener noreferrer">http://amzn.to/f9goyT</a> 可能有用。</p>
<p><strong>Rebecca</strong>：谢谢，Ruchira，提供链接。</p><p><em>由 mimo-v2.5 模型翻译，花费 33539 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/open-social-networks/</link>
      <guid isPermaLink="false">https://danluu.com/open-social-networks/</guid>
      <pubDate>Fri, 1 Jan 2010 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>波士顿与硅谷相比如何？麻省理工和斯坦福与这有何关联？</title>
      <description>[AI 摘要] 该文章通过多人对话探讨波士顿与硅谷创业环境差异，重点分析麻省理工与斯坦福大学文化如何影响区域创新模式，指出波士顿重学术研究而轻商业化的特点及其后果。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 该文章通过多人对话探讨波士顿与硅谷创业环境差异，重点分析麻省理工与斯坦福大学文化如何影响区域创新模式，指出波士顿重学术研究而轻商业化的特点及其后果。</div><p><em>这是一段关于麻省理工与斯坦福、硅谷与波士顿对比的旧版Google Buzz对话存档</em></p>

<p>没有理由认为波士顿地区不能像硅谷一样成为创业热土。相比之下，纽约则存在诸多不利于创业的因素。然而，保罗·格雷厄姆最终放弃了波士顿，这说明该地区必然存在某些阻碍创业生态形成的因素。</p>

<p><strong>凯文</strong>：这与资金、人才或其它什么因素无关。关键在于“企业家密度”。</p>
<p>波士顿或许拥有资金、人才和智慧，但它是否具备创业精神以及足够的人口密度？</p>

<p><strong>玛丽亚</strong>：根据<a href="http://www.xconomy.com/boston/2009/01/22/paul-graham-and-y-combinator-to-leave-cambridge-stay-in-silicon-valley-year-round/" rel="noopener noreferrer">这篇报道</a>，格雷厄姆表示原因主要是个人因素，与即将出生的孩子有关，他不希望成为东西海岸奔波的父母。但紧接着他又说：“波士顿就是没有硅谷那样的创业文化。它在除硅谷外的地区中创业文化最浓，但第一名和第二名之间的差距巨大；在两地交替停留时这一点体现得最为明显。”<br>这里有一篇<a href="http://www.xconomy.com/boston/2009/03/10/paul-graham-on-why-boston-should-worry-about-its-future-as-a-tech-hub-says-region-focuses-on-ideas-not-startups-while-investors-lack-confidence/" rel="noopener noreferrer">采访</a>。<br>有趣的是，格雷厄姆此前似乎对波士顿地区有所偏爱：<br><a href="http://www.paulgraham.com/cities.html" rel="noopener noreferrer">http://www.paulgraham.com/cities.html</a><br><a href="http://www.paulgraham.com/siliconvalley.html" rel="noopener noreferrer">http://www.paulgraham.com/siliconvalley.html</a></p>

<p><strong>丽贝卡</strong>：我认为他的偏爱源于他欣赏波士顿的知识层面，这让他对其创业文化不如硅谷感到遗憾。我理解这种感受。我猜我最近看到这里的创业氛围有所起色，这让我有点后悔自己暂时将知识追求置于任何可能的创业冲动之上。</p>

<p><strong>斯考伯</strong>：我不认为波士顿是第二名。西雅图、特拉维夫甚至博尔德在我看来都更好。</p>

<p><strong>皮奥</strong>：西雅图确实有大量亚马逊和微软的百万富翁资助创业公司，只是他们不太受关注。我没想到博尔德是创业活动的热土。</p>

<p><strong>丽贝卡</strong>：关于“没有理由波士顿不能成为创业热土……”的评论。文化很重要。麻省理工的文化更重学术而非创业，哈佛更是如此。我讲个故事：九十年代初，互联网刚起步时，我曾在麻省理工计算机俱乐部闲逛，有人提议可以通过注册域名然后转卖赚钱。在场所有人都认为这是他们听过的最蠢的主意。太疯狂了。当时域名几乎任人挑选，你们知道的。在场的每个人其实都明白自己正把钱往外推。但我们都属于这个俱乐部，文化上需要觉得自己“高于”用那种方式赚钱的层次。<br>后来，九十年代末，我与菲利普·格林斯彭交往甚密，他当时在写一本关于数据库支撑网页开发的书。他因做的工作学术性不足、未能产生新想法而备受教授们诟病。他差点毕业不了，因为他被视为太具创业精神、太商业化、不够原创。这种事会在斯坦福发生吗？我读过拉吉夫·莫特瓦尼的访谈，他说当谷歌创始人还是研究生时，他总会翻找出额外的硬盘给他们使用。我觉得这在麻省理工不会发生：教授不会给研究生那么多资源只是为了让他们构建自己将来要商业化的东西。他们很可能会被抱怨没有进行足够的“真正科学研究”。当时很多人对格林斯彭颇有怨言，认为他在创业时“占用”了麻省理工的资源，人们对此也毫不避讳。<br>我不太喜欢麻省理工的这一点。</p>

<p><strong>皮奥</strong>：我有个朋友曾拒绝了网景的全职录用（在他实习之后），选择回校读研。当时他说：“我读研不是为了发财。”多年后他说：“我成功了……成功地没发财。”</p>

<p><strong>丹</strong>：作为被提到的那位朋友（我于1996和1997年在网景实习），我相当确信如果当时退学，我不会变得非常富有。相反，通过坚持学术道路，我得以通过副业咨询过上体面的生活，而且学术界的报酬也不算微薄。<br>当然，如果我在1993年彻底放弃学术加入网景（我承认当时确实很心动），情况会大不相同。</p>

<p><strong>皮奥</strong>：好吧，总会有新的热门创业公司的。:-) 里德·哈斯廷斯1995年就这么跟我说过。</p>

<p><strong>丽贝卡</strong>：一位具有硅谷习惯的风险投资家（在这儿是个极其稀有且奇怪的生物）最近在麻省理工设立了办公室，我试着给他讲讲“陶托，你已不在堪萨斯”的故事。也就是说，我试图告诉他，从斯坦福学生那里赚钱的习惯在麻省理工行不通。并不是说投资麻省理工的学生赚不到钱——如果足够耐心，也许能赚更多，甚至多得多。但这只有在充分理解麻省理工文化何其不同、并基于对投资对象本质的认知采取不同策略时才可行。不过我没跟他说清楚；也许我应该退一步，更全面地谈谈麻省理工文化的核心差异。你知道，如果我这么做，也许波士顿市长办公室也想听听。嗯……你给了我一个主意。</p>

<p><strong>玛丽亚</strong>：说到这个，菲利普刚发布了他参加波士顿天使投资会议的经历：<br><a href="http://blogs.law.harvard.edu/philg/2010/06/01/boston-angel-investors/" rel="noopener noreferrer">http://blogs.law.harvard.edu/philg/2010/06/01/boston-angel-investors/</a><br>他照例进入了暴躁老汉模式。我想象他坐在前廊的摇椅上，对着会议演讲者挥舞手杖。有趣的引述：<br>“有人问将资本投向巴西、中国等快速发展国家是否更合理，演讲者回应说当天使投资人更多是为了乐趣而非投资回报。（不确定谁会觉得‘乐趣’包括与沮丧的创业者一起参加董事会，但对我个人而言，我宁愿开直升机或去海滩。）……”<br>“没人思考过波士顿是否真的需要更多天使投资人或风险资本。没人能举出一个好创业公司无法获得融资的例子。然而，有像Facebook这样的创业公司，因为加州有更优的资本和其它资源而搬到了加州……”<br>“会议上无人能回答一个宏观问题：在私营经济GDP萎缩的背景下，我们为何还需要资本？”</p>

<p><strong>皮奥</strong>：GDP问题很容易回答。并非所有行业都在萎缩。例如，硅谷目前正在迅猛增长。否则我也无法帮人们谈判30%的薪酬增长（好吧，接近50%，取决于计算方式）。盈利状况极佳的上市前公司数量也出奇地多。<br>而且就个人而言，我认为除非对当地文化有深刻把握，否则投资中国、巴西等地是自找麻烦，所以那个用“有趣”来回答问题的人是个白痴。<br>Facebook被Accel要求搬到帕洛阿尔托这件事，绝对是波士顿风险资本家应该自责的。但那次“被迫搬家”对Facebook非常有利。他们在帕洛阿尔托招募到了杰夫·罗斯柴尔德、马克·奎亚特科夫斯基、史蒂夫·格里姆、保罗·布赫特、桑吉夫·辛格等许多人才，这些人无论如何都不会为了Facebook搬到波士顿。在我看来，无论怎么看，留在波士顿对Facebook都不是最佳选择。至少在波士顿的创业环境显著改善之前不是。</p>

<p><strong>玛丽亚</strong>：GDP问题很容易回答。并非所有行业都在萎缩。例如，硅谷目前正在迅猛增长。我猜医疗技术和生物技术仍在增长。还有什么？<br>有人在评论中指出这点，菲利普也回应了；他认为天使投资人不太可能获得良好的投资回报（部分引用）：“……我们确实需要一些资金来源……但美国金融体系的每个环节，从风险投资到投行，都是为经济扩张设计的。这意味着对我们当前的经济来说它规模过剩了。因此，额外资本的回报应该会非常小……”<br>不过他没提供任何佐证。</p>

<p><strong>皮奥</strong>：社交网络和社交游戏增长迅猛且快速。</p>

<p><strong>丽贝卡</strong>：谢谢玛丽亚指出菲利普的博文。我觉得其中关键的引述是：“波士顿地区曾经是创业公司蓬勃发展的可持续之地吗？当制造计算机所需的技能极其稀有时，小型机制造商取得了成功。一旦这些技能……变得普及，几乎所有新公司都在加利福尼亚、德克萨斯、西雅图等地创立。当构建功能完整的互联网应用需要掌握前沿技术时，波士顿地区孕育了许多开创性的互联网公司，例如Lycos。一旦普通程序员能够……高效工作时，波士顿就变得无足轻重了。”菲利普是说波士顿只能在具备独特技能优势时才能竞争。这是因为其商业和商业化能力相对糟糕，以至于当技术技能商品化时，其他城市表现得更好。<br>但它确实经常率先获得尖端的技术见解和技能——然后却完全搞砸了后续发展。这让我感到沮丧。现在想想，波士顿的领导者们似乎也对此感到沮丧。但我认为他们试图将波士顿重塑为硅谷形象的做法是错误的。如果我们试图成为你们，至多也只是你们可怜的影子。我们只能通过做好自己而取得成功，同时变得更擅长它。<br>存在一个根本问题：处于前沿的人对实用事物不感兴趣，否则他们就不会涉足前沿领域。尽管现在听起来可能很奇怪，但架设第一百台网络服务器的那个人，是个相当不切实际的理想主义者。当只有99台服务器（还记得吧，当时还没有浏览器）时，谁需要网络服务器呢？我们当时嘲笑他，而他仅仅出于一种深刻的学术信念——认为这是未来——无论看起来多么愚蠢——而为其价值辩护。我一再目睹不切实际的理想主义者们孤独的执念在两三年内变得实用，五到八年内变得有利可图，七到十二年内发展成庞大的产业。<br>因此，如果那个将在十多年后成为巨大产业的雏形概念首先在波士顿出现，为什么我们无法利用它？问题在于，那些磨练技能于雏形概念（这些概念至少要五年后才能真正盈利）的人，按照定义是不切实际的，太不切实际以至于不懂得如何利用先发优势。但如果波士顿能想办法将这些人与能利用对未来形态的早期预警、并能利用他人无法企及的技能优势的实用派人士配对，它或许能成为赢家。这需要一种非常特殊的实用主义，不同于标准的硅谷模式。<br>也许我错了；也许市场并不奖励先发优势，尤其是当它意味着处于实用性的尖端时。你们怎么看？</p>

<p><strong>皮奥</strong>：领先时代5到10年是糟糕的。你要做的只是领先18个月甚至12个月，这样你刚好有足够时间在市场爆发前构建产品。我的书也涵盖了这部分。:-)</p>

<p><strong>玛丽亚</strong>：丽贝卡，我对波士顿地区了解不够，无法形成意见。我在这里住了两年，但肯定没深入核心（如果存在“核心”的话，我还没看到）。我猜波士顿的人口规模不足以成为任何事物的超级中心，但这只是盲目猜测。<br>即便如此，这份旧调查（2004年）显示波士顿在生物技术领域排名第二，仅次于圣地亚哥：<a href="http://www.forbes.com/2004/06/07/cz_kd_0607biotechclusters.html" rel="noopener noreferrer">http://www.forbes.com/2004/06/07/cz_kd_0607biotechclusters.html</a>。那么，如果这里的人普遍缺乏商业兴趣或“不切实际”，为何波士顿在生物技术领域如此成功？（文中片段：“……当圣地亚哥最成功的生物技术公司IDEC制药与Biogen合并成为Biogen Idec（纳斯达克：BIIB）时，它正式将总部迁至Biogen的家乡马萨诸塞州剑桥。吃瘪吧，圣地亚哥！”）<br>当你谈到某种人“不切实际”时，我不认为这真是问题所在。此类人在追求自己特定野心时可以非常务实。但他们的兴趣可能不在于将想法商业化。一些极其聪明、技能高超的人就是不在乎金钱和商业，甚至可能鄙视它们。<br>即便如此，我仍难以相信新英格兰的知识分子比硅谷的同行们更重学术。新英格兰确实有清教徒伦理，但我不认为这驱动了商业文化。</p>

<p><strong>丽贝卡</strong>：玛丽亚，谢谢指出我表述不清（我有点像在拿你练习解释一些东西，我稍后可能会更正式地说明，所以一直刷你的评论区。希望你不介意。）你提问“为何波士顿在生物技术领域如此成功，如果这里的人普遍缺乏商业兴趣？”让我意识到我说的不是普通人——波士顿和其他地方一样，有很多商人。我说的是特定一类人，或者更具体地说，是一种特定的关系。记得我对比拉吉夫·莫特瓦尼对待谷歌创始人与麻省理工计算机实验室对待菲利普的报道吗？总体上，我想说的是，像帕洛阿尔托或剑桥这样的大学城，会吸引雄心勃勃、寻求帮助实现野心的年轻人，也会吸引一群渴望吸引此类年轻人并助其实现抱负的成年人，他们之间存在一种具有（或许是无言的）条款和期望的特征性关系。我真正想探讨的是，这些条款和期望在麻省理工与（我听说的）斯坦福非常不同。尽管直接参与这种关系的人可能不多，但它仍然决定了这座城市能成就什么、不能成就什么，因为正是雄心勃勃的年轻人的能量与经验丰富的成年人的指导相结合，才使得成就伟业成为可能。<br>我的印象是，斯坦福最有野心的人梦想着创立下一个大型互联网公司，如果他们展现出足够的精力和才华，就会给教授留下深刻印象，教授便会翻开他们的名片夹，告诉自己的风险投资家网络：“这孩子如果得到支持，会让你们赚大钱。”那些知道这位教授曾多次言中的风险投资家会信任这个判断。因此，怀揣此类梦想的孩子们进入斯坦福，并努力以特定方式打动他们的教授，因为这能让他们快速获得特定类型的成功。<br>而在波士顿，最受教授培养的有抱负的学生梦想有所不同：他们可能梦想破解强人工智能，或发现编程语言的基本属性以实现容错或并行编程，或真正理解λ演算的演算体系，或革新个人基因组学，或构建《银翼杀手》式合成生物学的基础。如果教授对学生的精力和才华印象深刻，他们会翻开DARPA（以及NSF和NIH）项目经理的名片夹，告诉他们：“这孩子现在做的事不实用也无利可图，未来很多年内都不会，但支持这项工作对美国未来的经济和军事竞争力至关重要。”那些知道这位教授曾多次言中的项目经理会信任这个判断。这样，孩子就被引上了通往成功的快车道——但这是一种与斯坦福孩子所追求的非常不同的成功，也是另一种会努力争取进入这条轨道的孩子。成功的意义非常不同，至少在短期内，更偏学术而远非实用。<br>这就是我指波士顿“更不关心商业、更不切实际、更不创业”的含义。并非这里没有很多具备这些特质的人。但那个让雄心勃勃的年轻人有机会做些独特、别处无法完成之事的“生态系统”——它确实存在，但以一种非常不同的方式存在——并不培养商业化技能或对技术即时实用应用的兴趣。<br>也许这没什么不对：波士顿的生态系统只是促成另一种类型的成就。然而，我能理解波士顿市长为何沮丧，因为被波士顿生态系统赋能的有抱负的年轻人，其工作可能对美国长期的经济和军事竞争力至关重要，但可能对波士顿经济帮助不大！“长期”中常发生的情况是，波士顿获得资助的研究工作发展到可以盈利实用的阶段时，就被加利福尼亚、西雅图、纽约等地商业化了……资助这些工作的DARPA项目经理对这一结果心满意足，但我能想象波士顿市长不会！那个孩子可能也不会百分百满意，因为提供给他的成功远非硅谷式的成功——那是一项繁重的工作，近乎苦行僧式且自我牺牲，最终主要让远在天边的其他人利用他的劳动成果变得极其富有。他最多只能分到微薄的一杯羹。<br>人们可能更希望波士顿的教授名片夹里有两部分。第一部分是所有相关DARPA项目经理的姓名，教授首先翻这部分。第二部分是合适的联合创始人、友善的投资人姓名，等学生辛苦工作五到七年使技术变得实用后，教授翻第二部分，再次为学生安排合适的创业公司首席科学家之类的位置。<br>这种事并非没有发生。它确实发生。但没有达到应有的频率，我认为原因在于维护一个真正优秀的名片夹需要付出大量努力。这些教授很忙，他们没有足够的精力同时以两种方式成为顶级生态系统的核心。<br>如果波士顿市长对波士顿在这场交易中经济上吃亏（我认为确实如此）感到不满，一个务实的做法是帮助这些教授充实他们名片夹的第二部分，或者尝试建立另一个以适当方式启用名片夹的导师网络。如果他选择后者，应该明白这第二个网络不应试图复制斯坦福的类似体系（因为至多也只是拙劣的仿制品），而应特别适合吸纳那些波士顿生态系统独有的DARPA项目毕业生。这样他就能以独特而非仅仅拙劣模仿硅谷的方式，使波士顿成为创业中心。这就是我说波士顿应更好地做自己，而非试图成为硅谷拙劣复制品的意思。</p>

<p><strong>皮奥</strong>：我喜欢这个思路，丽贝卡。反例是：Facebook。Facebook显然对商业化一个非常成熟（事实上已被尝试并失败多次，因为时机未到）的东西感兴趣。然而Facebook不得不去帕洛阿尔托融资。因此，商业文化必须足够改变，让有钱人愿意冒险投资像四年前那样的Facebook这类高风险项目。<br>自己投资过创业公司后，我发现这绝对极具挑战。说服自己冒这个风险值得，即使它只是投资组合中相对小的一部分，都需要很大勇气。要形成足够的关键数量，你必须在先前项目中有足够成功，以建立那种信心，让你愿意资助四年前的Facebook。我不认为自己有能力在种子阶段资助谷歌或Facebook，而我一生都住在硅谷、在创业公司工作，所以如果有人应该对风险感到适应，那应该是我。</p>

<p><strong>丹</strong>：丽贝卡：关于“为DARPA翻开名片夹”的附注。实际上并不完全是那样。更像是“嘿，孩子，你应该去读研”，然后你写推荐信送孩子进入顶尖学校。你当然会引导孩子去你认为能做出出色工作的研究组，基于你对“出色”的主观标准。<br>我作为教授的观点：如果我有个本科生说“我想读研”，我就照上文做。如果说“我想去一家很酷的创业公司工作”，我就拿出名片夹里的风险投资家联系方式。</p>

<p><strong>丽贝卡</strong>：丹：我知道。我为戏剧效果过度简化了，因为加以限定会让我的故事更长，而它已经超出了评论的合理长度极限。当然，硅谷版本的故事也没那么简单。<br>我见过足够杰出的本科生（甚至是高中生——有些惊人的天才出现在麻省理工）能获得直接支持。但要明白，我说的主要是研究生——毕竟，我的比较对象是谷歌创始人和拉吉夫·莫特瓦尼的关系，那发生在他们读研期间。练习是比较他们遇到的机会与麻省理工同样杰出、精力充沛、雄心勃勃的学生能获得的机会，并讨论其异同。也许我不该称这样的人为“孩子”，但这简化并缩短了我的故事，而它的长度本已到极限。感谢反馈；我在拿你测试这个故事，知道哪些说法有效、哪些无效很有用。</p>

<p><strong>丽贝卡</strong>：皮奥：我理解个人投资创业公司非常可怕。我认识一些波士顿天使投资人（私人交情多于职业关系），听他们讲他们的天使团体多么谨慎的故事。我应该解释一些背景：波士顿市政府最近宣布了一项支持波士顿创业公司的重大举措，并计划改造“大挖掘”工程在波士顿港口附近一些衰败建筑旁腾出的土地，创建新的创新区。我在思考他们能做些什么让此类举措成功而非陷入痛苦的尴尬（这很容易发生）。所以我考虑的是市政府的投资重点，而不是像你这样的个人投资者。<br>城市投资各种疯狂的东西，比如奥运会场馆，它们亏损惨痛……但当你想到城市从每个额外访客身上收取6%的酒店税，从额外宣传中获益，并从新居民搬入时收取更多房产税时，突然看起来就没那么糟了。波士顿之所以落后，是因为在DARPA停止资助某项技术（因其已发展到可商业化阶段）与谨慎的波士顿天使投资人愿意资助之间存在资金缺口——而其他州填补了这个缺口，靠马萨诸塞州税款产出的成果致富。这不可能让地方政府太高兴。<br>也许波士顿市或州政府会有动力做点什么来填补这个缺口。他们可能更能容忍直接亏损，因为即使是回报平平、或极慢产生巨额回报、但成功吸引人才到该城市的创业项目，也会通过酒店税、房产税、宣传等让他们赚钱——或者至少不会损失他们已经在大学上的巨额投资！我曾短暂为一位由波士顿社区资本（一个我认为将精力分配在发展经济适用房和支持被认定为对波士顿有社会回报的选定创业公司之间的组织）资助的人工作。当你一半投资组合是经济适用房时，你对风险和回报的看法可能不同！我非常钦佩他们是多么出色的投资者——慷慨、有帮助且有耐心。耐心对我们是必要的，因为波士顿的年轻天才们所进入的领域时间跨度很长——我的朋友们在研究合成生物学，但要等到你能买到《银翼杀手》式的蛇还有很长很长的路要走。<br>再次感谢反馈。你帮助我理解自己没说清楚的地方。</p>

<p><strong>玛丽亚</strong>：丽贝卡，你说“我真正想探讨的是，这些条款和期望在麻省理工与（我听说的）斯坦福非常不同”。<br>我最初读你的评论时，以为你在说波士顿创业公司的一般商业环境。但现在我认为你主要说的是互联网创业公司，或者至少是基于计算机科学工作的创业公司。你是在说麻省理工的计算机科学系尤其不擅长引导学生走向创业方向，因为他们过于注重学术课题。<br>麻省理工和斯坦福的计算机科学和商学院排名都顶尖。也许问题在于斯坦福商学院更倾向于从计算机科学系“挖掘”资源，而麻省理工不是？</p>

<p><strong>道格</strong>：丽贝卡，你对麻省理工与斯坦福的描述听起来很对（虽然我不太了解斯坦福）。有趣的是，我记得加州大学伯克利分校与你描述的麻省理工非常相似：加州最聪明/最有抱负的学生最终投身于BSD、Postgres、Gimp或Gnutella，而非商业化。好吧，我没跟上伯克利自90年代中期以来的动态，但自伯克利软件系统公司之后，那里有过任何重要的创业公司吗？</p>

<p><strong>皮奥</strong>：道格：Inktomi。在当时非常重要。</p>

<p><strong>丹</strong>：约翰·奥斯陶特围绕Tcl创办了公司。埃里克·奥尔曼围绕sendmail创办了公司。迈克·斯通布雷克做了Ingres，但在互联网热潮开始时那已是旧闻了。玛戈·塞尔策围绕伯克利数据库创办了公司。不过他们都不是伯克利本科生，塞尔策是研究生。尹锡基做了几家互联网相关的创业公司，但没有哪家有谷歌或雅虎那样的知名度。</p>

<p><strong>丽贝卡</strong>：丹：我更多在想你说的不涉及本科生、而是建议他们读研的事。有时麻省理工处于那种安宁沉稳的学术模式，在本科生准备好攻读博士学位时引导他们加入合适的研究组。但有时并非如此。让我讲讲计算机俱乐部关于安装第一台网络服务器的故事。那是当时全球约第一百台网络服务器，它的维护者拿着一张荒谬的“证明”网络指数增长的图表（即从0到100左右呈指数增长的图，他向前外推至一百多万——你知道标准的完全站不住脚的论点——只不过这个异常大胆地荒谬）拦住我。然而他带着如此强烈的激情和信念为之辩护，仿佛在说这张图表应该说服我抛开一切只做建设互联网这一件事，因为这是唯一重要的事！<br>我用能找到的最粗的棍子挡开了他：我决心在教育上物有所值，做习题、拿好成绩（我那时在乎），绝不会让这被这疯狂的互联网执念影响。但情况持续如此。互联网人群随着时间壮大，他们更加坚持自己在做唯一重要的事，我应该抛开一切加入他们。我是本科生这点对任何人都不重要。本科生参与，研究生参与，所有人都参与。这不只是一个研究项目；最终众多不同研究项目融合在一起，成为整个社区的集体痴迷，一种完全“不参与就落伍”的氛围。我很想说我参与了。但我没有；我只是坐在办公室沙发上做习题，证明定理、解薛定谔方程，并用能找到的最粗的棍子挡开他们。我决心在麻省理工接受“真正的教育”，获得物有所值的教育，你知道的。<br>我的观点是，当麻省理工的生态系统真正发挥作用时，它能处理比普通研究项目庞大得多的项目，因为它能聚集足够多的研究项目协同工作，吸引足够多的研究生，也吸纳本科生和其他人，最终社区拥有远超普通研究生完成博士所需能量的情感凝聚力和行动力。<br>还有些别的事情，虽然我无法确切报道，因为我当时太小，没看到全貌。你可能会问：如果麻省理工在90年代有如此集中于某事的情感能量，那么现在它在类似地做什么？我必须痛苦地说，答案是：它为自己曾经辉煌的空壳状态感到沮丧和痛苦。<br>为什么？因为在2000年布什当选后，他砍掉了许多教授长期合作的那个版本的DARPA。90年代时我不明白这点——像个孩子一样把身边发生的事视为理所当然，没看到使其成为可能的资金——但现在我明白，90年代麻省理工互联网人群付出的那种情感能量需要大量资金，需要其背后有智慧的力量，而这种规模的资金和规划只能来自军方，而非NSF。<br>最近我目睹教授们明显觉得他们有权动员大量学生进行真正大型项目，却试图从NSF找钱，他们耗尽时间疯狂写基金申请，永远得不到足够资金让自己满意，抱怨学术界的怯懦，希望能再次与老DARPA朋友合作。他们不开心，因为他们仅仅在做大而成功的研究项目，但仅仅一个研究项目还不够……当麻省理工真正是麻省理工时，它能做更多。当它仅仅是一系列成功但无凝聚力、NSF资助的研究项目集合时，它只是自身辉煌的空壳。正如我所说，波士顿的“生态系统”本身有做独特之事的能力，但它独特的方式与硅谷的方式截然不同。<br>这可能显得晦涩，是关于一所遥远大学资金困境的故事，但也许你应该了解，因为它可能影响你的生活。你应该关心的原因在于，当麻省理工资金充足、真正做自己时，它正在构建如今让你致富之物的基础。<br>人们可能将技术与财富的关系想象成势能的故事：当你谈到寻找“产品/市场”契合点时，就像把一块大石头推上山，直到山顶达到“契合”，然后石头滚下，你投入的能量释放出来产生大量财富。在硅谷，你专注于把石头推上矮坡——就像皮奥说的，推不超过12-18个月，直到“契合”发生。<br>但麻省理工在其黄金时代可以挑战大得多的山丘——整个社区可以专注于用十年时间只为把一块大石头推上一座大山。90年代痴迷的互联网人群注入系统的势能此后一直在你的生活中释放。他们把一块大石头推过一座大山，然后让它砸向你，接着你推它越过小障碍，并在此过程中赚了很多钱，你以为自己获利的势能完全来自自己。确实有些来自你们，但并非全部。有些来自我们。如果我们没有在推另一块这样的石头，如果我们不能将别的东西推过巨大的山丘撞向你们，那么未来对你们而言可能不复往昔。担心吧。<br>那么你可能会问，这个故事结局如何？如果我声称90年代麻省理工有强烈的情感能量投入发展互联网，为何那些人没有散播开去在波士顿创立互联网产业？如果我们曾经是赢家，如何沦为输家？这个充满活力、凝聚力的群体发生了什么？<br>我可以告诉你，因为多年来我抵制这种痴迷的情感能量吸引，到最后开始让步。起初我说“绝对不行！”然后说“不行！”然后说“也许以后”，然后说“好的，肯定以后”……等到我最终考虑“以后”时，（也许是我人生的常态）“以后”变成了“太迟”。到2000年，我准备好加入人群，按麻省理工的方式重塑自己成为一名互联网人。所以我最终在它崩塌时严重投入。因为2000年左右，对你而言几乎是互联网时代的开始，对我们却是终结。<br>这个周末我在想如何讲这个故事，我在脑子里用漫画风格构思，想讲一个“波士顿失败者寓言”，用我所有荒谬错误作为整个城市困境的缩影。我可以自嘲吧，没人会因此生气吧？简短的故事是：2000年左右，互联网人群实现了他们的产品/市场契合，DARPA开了香槟——你们赢了！恭喜！现在去商业化吧！——然后把我们推出巢穴，让我们去完成技术商业化的标准任务——那些你们闭眼都能完成的任务。我就在那儿，在过渡期的核心。我想给你们讲一个关于我那段荒谬努力的漫画故事，让你们笑我。<br>但当我试图解释是什么让我如此艰难时，令我大为惊讶的是，我突然哭得很厉害。整个周六晚上我都在想这事，一直在哭。我压抑了这段记忆，认定自己没那么在乎——但实际上它太可怕，无法面对。所有你们不假思索能做的事，对我们都痛得厉害。胜利的宣告，“产品/市场契合”的实现，你比任何事都渴望的东西，我——我认为我认识的许多人——都将其视为巨大的创伤。这大概是我为何反应如此激烈、刷爆你的评论区，因为我对此有巨大的压抑的个人创伤。我意识到自己有一个比先前计划更真诚的故事要讲。<br>例如，我反思之前关于城市花钱方向的评论，觉得自己听起来像个十足的混蛋。我真的在建议城市把本该花在贫困黑人婴儿住房上的钱，花在那些有其他众多丰厚经济机会的过度教育白人孩子身上吗？我有什么资格提这种建议？如果我真心这么想，我负有巨大的举证责任。<br>所以我将尝试把更真诚的故事与至少勾勒如何应对此举证责任结合起来（并尽量简短，以保持垃圾评论成分最低。javascript变慢了，所以我先到此为止，稍后继续。）</p>

<p><strong>鲁奇拉</strong>：插曲（希望丽贝卡快点继续！）：丽贝卡说“那种规模的资金和规划只能来自军方，而非NSF。”确实，查阅《纽约时报》这张联邦预算图表可能有用：<a href="http://www.nytimes.com/interactive/2010/02/01/us/budget.html" rel="noopener noreferrer">http://www.nytimes.com/interactive/2010/02/01/us/budget.html</a><br>我将引用图中2011年的一些数字（这些数字可能已变化，但不同部门间的相对规模差异不大）。我主要按研究预算规模降序列出行业，但我将“一般科学与技术”部门（包括NSF）列在“卫生”部门（包括NIH）之前，因为丽贝卡对比了军方与NSF。<br>“国防”部门的“研发、测试和评估”部分为767.7亿美元。我猜DARPA、ONR等都列在此处。<br>“一般科学与技术”部门位于右下方。“国家科学基金会项目”部分获得73.6亿美元。另有1亿美元用于“国家科学基金会及其他”。“科学、探索及NASA支持活动”部分获得127.8亿美元。（我不知道涉及国家安全的卫星技术在多大程度上也包含在内，或是在48.9亿美元的“太空行动”部分，或在1.8亿美元的“NASA监察长、教育及其他”部分。）“能源部科学项目”部分获得51.2亿美元。“国土安全部科技项目”部分获得10.2亿美元。<br>在“卫生”部门，“国立卫生研究院”部分获得320.9亿美元。“疾病控制、研究和培训”部分获得61.3亿美元（大概包括CDC）。还有“其他卫生研究和培训”1.4亿美元，“糖尿病研究及其他”0.95亿美元。<br>在“自然资源与环境”部门，“国家海洋和大气管理局”获得56.6亿美元。“监管、执法和研究项目”获得38.6亿美元（这是整个EPA吗？）。<br>在“社区与区域发展”部门，“国家基础设施创新与金融基金”（今年新增）获得40亿美元。<br>在“农业”部门（大概包括USDA资助的研究），“研究和教育项目”获得19.7亿美元，“研究和统计分析”获得2.5亿美元，“综合研究、教育和推广项目”获得0.25亿美元。<br>在“交通”部门，“航空研究与技术”获得11.5亿美元，顺便说一句这将是大幅增长（130%）。（麻省理工最近不是发现了一种能将喷气燃料效率提高75%的方法吗？）<br>在“商业与住房信贷”部门，“科学与技术”获得9.4亿美元。我觉得这相当神秘。<br>在“教育、培训、就业”部门，“研究和一般教育辅助：其他”获得11.4亿美元。“教育科学研究所”获得7.4亿美元。<br>在“能源”部门，“核能研发”获得8.2亿美元，“研发”获得0.24亿美元（我猜是能源部之外的部分）。<br>在“退伍军人福利与服务”部门，“医疗与假肢研究”获得5.9亿美元。<br>在“收入保障”部门有一小部分“儿童研究与技术援助”0.52亿美元。不确定这是什么意思。</p>

<p><strong>丽贝卡</strong>：我将从一个看似离题的话开始，希望通过它触及麻省理工与斯坦福的核心差异：最近我参加海军陆战队宣传活动，问征兵官陆军和海军陆战队有何不同。既然两者都训练士兵作战，为何不合并？他激烈地回答说他们必须分开，因为有一个他们完全对立的简单属性：他们对招募对象退役后生活的设想。他将陆军描述为一个有两个目标的组织：第一，训练好士兵；第二，给他们技能，让他们退役后的生活有个好起点。如果你想当参议员，你可以在陆军起步，建立人脉，获得工作技能，简历上有“光荣服役”，并普遍用它作为向上爬的起点。陆军致力于创造一批以陆军为职业生涯起点的赢家。<br>相比之下，他说海军陆战队只有一个目标：他们想创造最优秀的士兵，精英中的精英，他们可以在最困难、最危险的情况下信任这些士兵，让陆军士兵在后方活着。这种精英训练，他说，需要付出代价。代价是，你接受的培训不为你进入平民世界做任何准备。你可能成为海军陆战队中最优秀的人，然后回家发现你没有可在平民世界出售的就业技能，你几乎无法被雇佣，你必须从底层重新开始。而重新开始比第一次起步难得多。这可能是巨大的创伤。传说海军陆战队不会回归平民生活并成为赢家：相反，他们经常自我毁灭——“向平民生活过渡”对他们而言可能异常艰难。<br>他平静地、毫无歉意地说了这些。我说他是征兵官吗？他激烈地说：“我不会试图招募你！我想让你明白作为海军陆战队你要付出的代价有多痛苦。我坦率地说，这很可能不适合你！你可能想要它的唯一原因是你比任何事都想成为一名士兵，而且不是普通士兵，而是精英中的精英。”他在说：我们不帮助校友起步，我们让他们自我毁灭，我们对此毫无歉意——这只是你接受精英训练付出的代价！<br>这个故事触及我想说的斯坦福与麻省理工本质差异的核心。斯坦福像陆军：对其最优秀的学生，它有两个目标——让他们成为工程师，让他们离开后成为赢家。麻省理工像海军陆战队：它只有一个目标——让其最优秀学生成为工程精英，那些他们可以真实地告诉DARPA项目经理的人：你们可以完全信任这些工程师掌握美国经济和军事竞争力的未来。接受进入精英训练有一个奇特属性，就像征兵官认为海军陆战队训练具有的奇特属性：即使训练极其严格，一旦离开，你可能发现自己完全没有可出售的技能。<br>你获得的技能，用于构建领先市场需求十年的基础设施，可能与商业世界需求的技能毫无交集。你不仅没有准备好成为赢家，甚至可能没有准备好基本就业。你离开后从底层重新开始。比底层更糟：你可能被训练得商业实体认为反感的习惯（如本能地不愿快速处理指针，或在面试结束前就有与老板争执的可悲倾向）。这可能造成极大的创伤。正如前海军陆战队士兵经历艰难的“向平民生活过渡”，麻省理工的精英项目毕业生经历创伤性的“向商业生活过渡”。麻省理工的领导者对此毫无歉意：正如海军陆战队所说，这只是你接受精英训练付出的代价！<br>这是我可以用来向波士顿市政官员呼吁的一般理由。还有更多要解释，但这个想法的雏形大致是这样的：就像城市常花钱帮助前海军陆战队士兵过渡到平民生活，理由是他们代表有价值的人力资本，不应被允许自我毁灭，城市若理解麻省理工高强度DARPA项目毕业生的特殊困境，并帮助他们“过渡到商业生活”，可能带来回报。这对他们也有好处！即使那些只懂思考未来十年基础设施的人不具备普遍商业价值，如果你把他们放在普通商业人士附近，他们的视角会以有用的方式影响对方。波士顿本可以借此催化自己的互联网产业——不是指望麻省理工学生商业化他们的工作（他们（除了菲利普可能例外）天生不会），而是让那些想商业化某物但不知从何入手的人，有机会向积累了近十年经验的互联网人群学习。<br>说到这个，我想说——有趣的是你提到Facebook。你认为马克·扎克伯格是波士顿的社交网络先知，如果波士顿付钱留住他就能赢。我觉得这很奇怪——扎克伯格从根本上是你们中的一员，不是我们中的一员。他离开是对的。但我会问你一个你可能从未想过的问题。假设互联网没有在那个时候进入公众意识；假设世界故意无视它几年，那么从DARPA资助的研究项目到商业命题的过渡会晚几年发生。麻省理工有一群互联网人不断要求DARPA允许他们构建“下一个东西”，这里的“下一个”定义为“市场将在十年后发现想要的东西”。所以如果这群人获得几年政府支持，他们会构建什么？<br>我很确定它将是社交网络基础设施，不像Facebook，真的，而更像Diaspora的提案。我不确定，但记得在98/99年，所有情感能量都指向那个方向。技术上还不可能构建，但一旦可能，那就是人们想要的。我觉得奇怪的是，现在人人都在谈论社交网络及如何设计；这让我觉得似曾相识，是十年前的回响。如果城市或州在DARPA放弃他们后接手，再给他们一点时间，一点政府支持——比如一个马萨诸塞州DARPA——他们本可以让波士顿成为，不是大型社交网络公司的所在地，而是开放社交网络基础设施的发源地，以及其衍生的所有专业知识和小型产业。而且这本该在多年前就开始！这就是波士顿本可通过更好地做自己，而非拙劣模仿你们，成为领导者的方式。</p>

<p><strong>丹</strong>：我认为你或许夸大了DARPA的影响。DARPA资助大学活动主要有两类。第一类资助教授，支付博士后、研究生，有时是全职研究人员的薪水。第二类DARPA也资助与学术界关系不大的团体，比如伯克利的BSD项目（尽管我不知道他们是否有DARPA资金，他们没做“发表或灭亡”的学术研究；他们生产了伯克利Unix）。<br>像麻省理工这样的地方的本科生，获得了令人印象深刻的计算机科学沉浸式体验，其严谨性和活力在大多数其他地方无法获得（尽管伯克利基本上克隆了6.001课程，其他地方也如此）。他们称之为“从消防水管里喝水”是有原因的。麻省理工、伯克利和80年代末90年代初的其他大学计算机科学系学生多得不知道怎么办，于是他们提高了专业的难度，培养了非常优秀的学生，而其他人则转向了更轻松的追求。<br>关键的转折点在于大学里的流行文化以及教授如何对待他们的“明星”学生。期望是什么？在麻省理工，期望是你读研，拿博士，成为研究员。在斯坦福，期望是你去赚大钱。<br>近年来DARPA资金减少（或更准确地说，微观管理和短视）或许可归因于托尼·泰瑟的领导。他现在已离开，“新DARPA”正计划大力回归。我们拭目以待。<br>最后一点：我不买陆军与海军陆战队的类比。麻省理工与斯坦福培养学生的方式类似，在让他们出去赚钱的准备方面，大量麻省理工人确实成功地赚到了钱。麻省理工不乏从研究中衍生的公司，尤其是Akamai。我们讨论的差异并非黑白分明，不是陆军与海军陆战队之别。它们更微妙但仍然显著。</p>

<p><strong>丽贝卡</strong>：是的，我听说“去泰瑟化的DARPA”了。我应该提到这点，但为了保持（模糊的）简短而省略了。是的，我是为了能简单陈述我可能提出的请求而夸大其词，用城市或州政府官员能理解的术语表达。也许这不负责任；这就是为什么我先拿给你们测试，给你们机会对我吼叫，告诉我你们是否认为这么想过。<br>他们正在寻找一个叙事来解释波士顿为何将互联网产业领导地位让给硅谷，并据此找到行动方向。所以我特别谈到波士顿曾经是互联网技术领导者，以及可能导致其失去优势的弱点。保罗·格雷厄姆说波士顿的弱点在于它“太擅长”其他事情，因此我想具体讲一个关于其他事情是什么、为何这会导致致命弱点的故事——在特定方面“太强”如何也能让你变弱。<br>我当然在夸大，但也许因为我在对抗另一种我发现有害的倾向：对事物发生的内在情感逻辑永远模糊不清的倾向。如果一个有能力、有凝聚力、有活力的社区突然消散，未能实现其潜力，并解散了，了解是什么弱点导致这个惊人结果可能很重要，这样你才知道如何寻求帮助以防止下次重演。<br>说实话，我不太相信你的反对意见。我常纳闷为何我听到关于事物发生的内在情感逻辑如此软弱、模糊的叙事。模糊的叙事让你无力解决问题！我并非坚持夸大其事的权利，而是坚持探究驱动我周围世界因果情感逻辑的权利。我有时感觉我在对抗某种力量，它想挫败我实现这个目标——我怀疑这种力量有时并非总是源于理性，而是源于男性不愿为“显得强大”而承认弱点的倾向。伪装强大可能在世界真正能力的货币中付出高昂代价，因为通常真正让世界变好的最重要行动是求助。我真的对那位海军陆战队印象深刻，他愿意承认付出的代价、面对的创伤。那家伙不需要伪装强大！所以也许我以他为榜样坚持这点。我们有政府官员主动提出帮助我们；我们的社区许多最伟大成就都源于政府支持；我们不该浪费这个寻求可能帮助的机会。这个叙事可能有误；这就是我先测试的原因。我接受批评。但我不想因为害怕夸大而不敢开口求助，从而错过一个向主动提供帮助者求助的机会。</p>

<p><strong>丹</strong>：当然，波士顿最大的优势是该地区拥有大量大学。全国其他地方无法比拟。毫不奇怪，波士顿及周边也有大量高科技公司。我上面忘了提另一个麻省理工衍生公司iRobot（Roomba机器人公司），它也生产多种军用机器人。<br>就波士顿“输给”硅谷的互联网革命而言，想想网景的创立。几个伊利诺斯人和一个堪萨斯人。他们完全可能去任何地方。（简化故事的话）他们与一个天使投资人（吉姆·克拉克）联手，他把他们拽到硅谷，他们很快雇了一大批前SGI人才并快速起步。他们能去波士顿吗？当然。但他们没有。<br>似乎不同城市正在发展自己的专业领域，人们因此汇聚。例如，达拉斯在电信领域占据一席之地，所有大玩家（北电、阿尔卡特、思科等）都在那里开展电信工作。在休斯顿，不消说，一切都关于油田工程。并不是说休斯顿有什么特别的税收优势或城市/州资助吸引这些公司来这里。相反，整个行业（至少白领部分）都在休斯顿，许多大炼油厂就在附近（但足够远，闻不到味道）。<br>大波士顿地区历史上是小型机公司的所在地，尤其是DEC和Data General。他们的整个世界被工作站和个人电脑摧毁。DEC现在是惠普中逐渐消失的一部分，DG是EMC中逐渐消失的一部分。问题是大波士顿地区未来会成为什么磁石，以及你能利用现有优势做些什么来促成它。当然，波士顿地区大学培养的聪明人才不缺。问题是你能否激励他们留下。<br>我建议你可以通过在剑桥及周边提供廉价办公空间（一个“孵化器”）并建立本地风险资本池来取得进展。我认为你无法预先决定希望城市的专业领域是什么。你基本上只能希望它有机发展。而且，一旦你看到趋势出现，可能想通过财务手段加以强化。</p>

<p><strong>托马斯</strong>：BBN（做DARPA资助研究）长期被视为麻省理工与现实世界之间的中转站。</p>

<p><strong>皮奥</strong>：看来Hacker News上这个话题还有另一段对话：<a href="http://news.ycombinator.com/item?id=1416348" rel="noopener noreferrer">http://news.ycombinator.com/item?id=1416348</a>。我喜欢对话碎片化。</p>

<p><strong>道格</strong>：对话碎片化可能很烦人，但你真的想让所有那些Hacker News读者在这条帖子下留言吗？</p>

<p><strong>皮奥</strong>：为什么不？这样我就不用两个地方追踪了。</p>

<p><strong>鲁奇拉</strong>：Hacker News上的hga说：“申请者的自我筛选如此强烈（麻省理工在没有专业主任的情况下坚持了十几年），无论办公室现在给学院涂上什么光泽，它能改变的只有这么多。例如，麻省理工仍然是一个所有专业学生必须学习（或通过免修考试）一年微积分和经典物理（按麻省理工速度教授）的地方。”<br>嗯，加州理工所有专业的要求是：两年微积分、两年物理（包括量子物理）、一年化学、一年生物学（生物学要求是我在校后新增的）；新生化学实验室和另一个入门实验室；以及总共四年人文与社会科学课程。我所知加州理工附近的主要孵化器是Idealab。当然JPL（喷气推进实验室）以及好莱坞CGI和动画也吸引了加州理工毕业生。加州理工新生班规模也远小于斯坦福或麻省理工。<br>我不知道如何评估加州理工毕业生向当地工业过渡的相对成功度，与斯坦福或麻省理工相比如何，有人知道吗？</p>

<p><strong>丽贝卡</strong>：评论教会了我哪些地方没说清楚，这是最糟糕的之一。当我说“向商业世界过渡”时，我主要不是指毕业生向工业过渡。我更多想的是一个项目在实现产品/市场契合时所经历的过渡。<br>你可能不觉得这是什么大事，因为当公司启动项目时，它们通常有相当具体的市场目标和计划，以及市场采纳其产品时该做什么。没有困难过渡，因为它们从一开始就在计划。毕竟，这就是公司的全部意义！但一个十年期的研究项目没有这样的计划。网络服务器狂热者不知道市场何时会采纳他的“产品”——记得那时浏览器还很原始——也不知道当市场采纳时它会是什么样子。有些项目甚至更久：一位编程语言教授说，从一个新编程语言概念的提出到其广泛采用，预期时间是三十年。这占据了人生一大段。<br>当你花大半生参与一个除了小圈子无人关心的研究项目，当人们终于注意、商业机会出现、钱开始从天而降时，这是巨大的冲击！你完全没为此计划。你听过菲利普如何获得他第一个合同（后来成为ArsDigita）的故事吗？我找不到确切故事，但大概是这样：他在网上发布了一些论坛软件的代码，惠普打电话让他为他们安装配置。他说：“不行！我很忙！走开！”他们说：“我们付你10万美元。”他震惊了：“你们为两周的工作付我10万美元？”<br>他并非真计划让钱开始从天而降。他说，当他开始做互联网应用时，人们说他疯了，这行没前途。我记得第一次在公交车广告上看到URL时，简直惊呆了——我那些做网页的朋友一直都在做，我从未真正相信它们会被采纳。URL毕竟太极客了！我是说，说真的，如果某个狂野的呆子告诉你，五年后人们会在公交车侧面印“http://”，你会怎么想？我关注他们在做的事是因为他们觉得酷，我也觉得酷，而我压根不信别人会采纳的事实无关紧要。所以当世界真的采纳时，它进入了一个我们都未准备好、没人计划过、我们未曾考虑过培养技能去应对的新世界。我猜这有点难传达，因为在公司不会这样。你不会仅仅因为觉得酷就做某事，完全不信别人会赞同，然后当世界对你曾认为深奥呆子般的痴迷疯狂时，完全措手不及、彻底震惊。</p>

<p><strong>皮奥</strong>：我想我们都被人们开始交换电子邮件地址、然后网站等等的速度震惊了。我惊呆了。但真正盈利花了很长时间！在有人成功前，有约20家搜索引擎公司成立然后失败！</p>

<p><strong>丽贝卡</strong>：当然；你提到了实际存在的大问题。问题是：以何种模式向当地政府求助才合理？如果你处于那种10万美元支票不费吹灰之力从天而降（你似乎没为争取它们做任何努力）的情况，那么只有地球上最大的混蛋才会声称自己“贫困且值得”政府帮助。没屋顶的贫困黑人婴儿是贫困且值得的；从天而降的富裕白人烦人呆子不是。但要记住，尽管菲利普在故事中看似没费多少力气，他也说在90年代末，他构建网络应用已有十年。1999年地球上还有谁拥有十年网页应用开发履历？<br>正如皮奥所说，获取潜在财富并非只是伸手接住从天而降的钱那么简单。恰恰相反。它不容易；事实上，极其困难。当然，菲利普说得好像很容易，但当你想到在1999年积累他那样的履历有多难时就不觉得了。<br>当当地政府谈论想吸引创新者到波士顿，将城市变成创新中心时，我的本能反应是——我们算什么，切碎的肝脏吗？但接着我意识到，当他们说想吸引创新者时，他们真正想要的不是创新者，而是那些能以合理、可控（最好是短）时间进行创新，然后像水银一样迅速转身，在别人之前攫取创新全部回报——并给城市和州政府缴纳大笔税款的人！那些才是有吸引力的创新者！那些才能让波士顿成为市长想要的那种创新中心。像科技广场或斯塔塔中心的创新者，就不太符合。我们绝对属于切碎的肝脏部门。<br>这很伤人。想到波士顿市长若我们当时（约2000年）更擅长迅速转身，重塑自己，能够攫取我们当时技术上领先于任何人的创新的全部、部分、哪怕极小部分回报，我们现在可能得到更多尊重，这让人难过。但重塑自己并不容易！特别是当你意识到我们正在重塑的状态有点像海军陆战队——一种近乎苦行的状态，给你呆子版的军用口粮、一顶帐篷、也许两周洗一次澡，完全没培训任何可立即出售的技能——但也给了你一个社区、一种身份、一个目标、一种你从未想过会改变的自我认知。但突然我们“赢了”，突然有了改变的巨大压力。这就像被扔进深水池却没学过游泳，是的我们沉了，我们像块石头一样沉下去，几乎没扑腾几下就直冲池底。所以我们现在不受尊重。但这是合理的期望吗？波士顿市长到底想要什么？是的，波士顿作为创新中心的意义（因为它已经是了，它傻到试图成为它已经是的东西！）存在问题，不是市长希望的。我理解他的沮丧。但我认为他最好与他的城市合作，接纳其所有有问题的无能与光荣，而非试图将其重塑为它本非的他物形象。</p>

<p><strong>丽贝卡</strong>：关于问题创新者，我回想起计算机实验室里所有人都认为囤积域名是有史以来最蠢主意的场景。我认为攫取创新回报很容易，而注册域名是世界上最容易的事。我记得当时甚至免费。如果我没记错的话，它们一开始免费，然后宝洁公司批量注册了每个与“肥皂”有哪怕模糊词源关联的名字，这时系统管理员说“糟了！”并引入注册费以阻止此类行为——当然，这对宝洁没什么威慑。他们确实想彻底拥有肥皂的概念。（我发现宝洁是域名争夺战的第一个参与者很有趣——他们并非世界眼中尖端科技公司——但在解决肥皂营销问题时，他们悄然主导。）<br>我如何解释我们连免费注册域名（这样就能将税收留在马萨诸塞州）这种最简单努力都不愿付出？<br>回想起来，我认为这不是能力不足、缺乏远见、或是作为波士顿和马萨诸塞州纳税人失职。而是别的东西：一种近乎“永远忠诚”式的集体精神，让人觉得囤积别人可能想要的域名以图日后获利是不光彩的。有人可能会问，为何你不提前囤积，好让别人以后再囤积？这种荣誉对任何人都没好处！<br>但你要明白，这正处于开始阶段，域名系统全新，其采纳尚不确定。这些人正试图说服世界接受这个他们设计的、并热切希望被采纳的系统。在这种情况下，荣誉确实重要。要求世界接受一个好名字已被占用的命名系统看起来不好。那时你确实会注意到（比如“肥皂”）被占了——问题不是有什么可用，而是谁占了什么。你会觉得拿走一个酷名字伤害不大：最近我听说有人为“cool.com”出价3800万美元。那是很多钱！——如果世界拥有的系统除了，你知道，一个酷名字外所有名字都可用，伤害会那么大吗？但有一种集体精神非常担心一旦开了先例，不知会走向何方。<br>基础设施中更深层、更难言说的其他方面，这种集体精神更关键。你可以操纵基础设施使个人更容易获利——但这会损害基础设施本身。因此存在强烈的集体纪律，维护着为个人利益而贬低基础设施价值的意愿。<br>这部分解释了为何当时间来临时，我们无法迅速转身，攫取巨额利润。做到那点意味着不仅要改变我们擅长的事，还要改变我们认为正确的事。<br>回想起来，我纳闷为何人们没更害怕？当我们选择不注册“cool.com”或类似名字时，为何没想到生活艰难、未来不确定、而钱确实影响你能做什么？我认为这种集体精神之所以可能，是因为某种自信——这个群体觉得自己是协议的一部分：作为做我们自己的回报，政府会永远照顾我们。不是直到产品达到足够产品/市场契合、适合期待投资回报的时候。是永远。<br>这个故事可能提供了不同视角，说明为何当波士顿市长宣布想让城市成为创新中心时会受伤。他已有的创新者是切碎的肝脏吗？嗯，可以理解他不太满意这个故事里的创新者，因为他们算不上税基。但这与我们以为和政府达成的协议完全对立。</p><p><em>由 mimo-v2.5 模型翻译，花费 29423 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/mit-stanford/</link>
      <guid isPermaLink="false">https://danluu.com/mit-stanford/</guid>
      <pubDate>Fri, 1 Jan 2010 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Bioware的工作与生活平衡</title>
      <description>[AI 摘要] 该文通过论坛存档，揭示了Bioware游戏开发工作室长期存在强制性加班、同情加班等压榨员工的工作文化问题。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 该文通过论坛存档，揭示了Bioware游戏开发工作室长期存在强制性加班、同情加班等压榨员工的工作文化问题。</div><p><i>这是一篇现已不复存在的论坛中一个名为“警惕Bioware”的帖子的部分存档，其中包含了该论坛的评论以及一个现已关闭的、首次尝试归档此内容的博客的评论。原始帖子在发布后不久就被删除，取而代之的是“庞大可怕的公司对抗渺小的我”这类内容。</i></p>

<p><i>尽管下面的评论似乎主要针对Bioware位于埃德蒙顿的主工作室，但我认识当时Bioware奥斯汀工作室的一名员工，通过他我了解到了“同情加班”这个术语，即因为其他团队正在“加班”，你也必须留在办公室。我以前从未听说过这个概念，于是查阅了一下，大约在2008年找到了以下这个帖子。</i></p>

<p><i>如今在2024年搜索“同情加班”，结果已寥寥无几。仅有的几个结果中，一个是Bioware前主管在2011年发布的题为“热爱加班”的博客文章，其中这样写道：</i></p>

<blockquote>
<p><i>如果你发现自己处于同情加班中，即使你自己没有需要处理的错误，也不要因此而生气。去测试你制作的游戏吧！并享受它的本来面目，享受你为之贡献的一份成果。如果这还不够让你开心，那就想想你发现的每一个错误都会转交给你的同事，从而让他们更痛苦。（不过请尽量提出建设性意见。）</i></p>
</blockquote>

<p><i>另一个仅有的结果是2013年Bioware招聘营销博客上的一篇文章，一位开发者指出：“我们显然正在摒弃‘同情加班’的概念。”在下面2008年的帖子中，人们对管理层关于已废除同情加班的承诺是否兑现进行了激烈辩论。即使忽略我所认识的那位Bioware员工的评论，这些后来的Bioware员工评论，特别是2013年的招聘营销文章，似乎表明同情加班直到2008年之后很久才被废除。</i></p>

<hr>

<p><i>Milez5858</i></p>

<p>EA因其雇佣历史而备受诟病。</p>

<p>对于任何考虑去Bioware工作的人，也请务必警惕他们。</p>

<p>他们采用近乎邪教式的手段来让员工保持对公司路线的忠诚，但别被愚弄了。一旦你不遵循那条路线，就会立刻被扫地出门。</p>

<p>他们喜欢雇佣来自国外的工人，因为他们不了解加拿大的劳动法。他们经常在没有警告的情况下解雇员工。这在加拿大是违法的。你必须警告人们其绩效问题，并在解雇某人之前给予一定数量的警告。</p>

<p>他们在这方面比EA更聪明，提供免费食物、冰淇淋和现场其他设施，但所有这些加起来就是很多额外的无偿加班时间。
</p><hr>
<i>Milez5858</i><p></p>

<p>顺便说一句，你可能会说我的帖子有点私人化，但我从未为Bioware工作过。我有好几个朋友在那里工作。去年就有三个因为拒绝在每周只付40小时薪水的情况下工作更长时间，并且想多陪陪妻子和孩子而被解雇。</p>

<p>我那些还留在那里的朋友来自国外，感觉有点被困住了。他们不快乐，但只会在非常私密的情况下承认，因为他们担心会被列入黑名单之类的。</p>

<p>我是个局外人。我比这些可怜的孩子赚得多，而且我每周只工作大约35个小时。我一直把我的生活放在雇主之前，但在工作中非常努力和敬业。当我看到，我认为是邪教式的精神控制，施加在这些迷恋Bioware创始人Ray和Greg传奇的年轻男性身上时，我几乎感到恶心。</p>

<p>我过去曾认为在游戏行业工作会很酷，现在我非常高兴自己没有进入这个行业。</p>

<p>我肯定会把这个网站作为资源推荐给他们，并希望像其他任何娱乐行业一样，他们能以某种形式组织起来。这个行业对人做的事情绝对是非人道的。
</p><hr>
<i>Arty</i><p></p>

<p>地狱是快乐的吗？</p>

<p>还有，EA不是收购了Bioware吗？</p>

<p>只是问问，没有抱怨。欢迎加入！</p>

<hr>

<p><i>Anguirel</i></p>

<p>是的，Bioware和Pandemic被EA收购了。然而，听起来这些事情在收购之前很久就开始了。这听起来更像是所谓的个人崇拜（就像你在暴雪、Maxis、id或Ion Storm看到的那样），一个或几个人拥有如此巨大的声誉，能够或多或少无限量地获得相当不错的新员工——于是，某个（可能是介于该人和普通员工之间的某个人）人就利用这一点，把员工逼得比他们应得的更紧。</p>

<p>在某些情况下，围绕特定个体建立的崇拜保护了他们免受恶劣工作条件的影响（我听说在Will Wright还在的时候，Maxis就享有这种幸运，其内部的条件比EA的其他部门好得多），但在许多情况下，它导致了一种态度：“如果你不喜欢，我们可以找无数人来取代你。”EA过去肯定一直如此，其他几个工作室现在仍然如此，只是在那里工作的人对此更沉默（而且我认为没有其他工作室达到EA传说中持续加班的程度）。
</p><hr>
<i>Milez5858</i><p></p>

<p>是的，这些事情在EA收购Bioware之前很久就已经发生了。</p>

<p>你说得非常对，就是个人崇拜。人们认为Ray和Greg在照顾他们。Bioware Pandemic以近十亿美元的价格被出售。为了更小的利益都有人可能被暗杀。谁能真的相信老板们会站在那里说：“你知道吗……除非我确信员工得到了很好的照顾，否则我不能接受你这十亿美元”？这违背逻辑。</p>

<p>我怀疑情况只会变得更糟。现在被迫离开公司的人也被告知，他们必须将股票卖回给公司。</p>

<p>我知道的不多，无法判断这是否合法，但对我来说这听起来很可疑。我已经建议人们先咨询律师，但没有人愿意在已经够麻烦的情况下再惹麻烦。不幸的是，正是这种态度阻碍了游戏行业组织起来。</p>

<p>我很想说这种令人作呕的恐吓是异常现象，但这只会是困扰行业的彻头彻尾的谎言。也许我们应该从开发过程中剔除这种无处不在的傲慢，我疯狂到相信这可以在不使用U开头（工会）的情况下实现。
</p><hr>
<i>Anonymous</i><p></p>

<p>事实是，Bioware的生活并不像这篇文章暗示的那么糟糕，也不像暗示的那么差。但大部分情况下没那么糟。</p>

<p>原文指出Bioware使用邪教手段。我不知道什么是邪教手段，所以我无法给出简单的回答，但我知道Bioware使用传统手段来营造良好的士气。他们提供免费早餐。如果你加班，他们会给你送来晚餐。他们分发油人队（冰球队）门票、戏剧票或卡丁车票之类的。他们允许你离开办公桌一小时，去休息室在沙发上小睡或玩电子游戏。或者他们允许你离开去办事或出去喝一小时咖啡。他们还有公司会议，展示正在进行的各种项目的进展。关于最后一点，有人认为这是了解其他项目的好方法，而有人认为这只是让员工对一切保持兴奋的一种方式。这些是邪教手段吗？</p>

<p>Bioware以忠诚著称。而经理们则以懦弱和回避冲突而闻名。这一点怎么强调都不为过。事实上，人们开玩笑说他们几个月没好好工作了，却依然得到很高的评价、加薪或更多的股票期权。作为一名全职员工被Bioware解雇是极其罕见的事件，以至于公司会召开会议或发送电子邮件来解释解雇原因。当员工得到糟糕的评价时，会得到很多机会来改正。这些都与合同工不同。表现不佳的合同工不会被解雇，但他们的合同不会续签。优秀的合同工总是会被提供转为全职的机会（因为合同工有加班费，转为全职可以省钱），如果没有空缺，他们的合同也会续签。</p>

<p>我对人力资源部了解不多，所以不知道招聘策略，但我可以同意很多员工来自世界各地或埃德蒙顿地区以外。人们总是认为他们在埃德蒙顿附近找不到优秀的人才。除了说我知道有很多地方在进行招聘，我不能评论更多。</p>

<p>工作时间绝对是个问题，我完全同意：</p>

<p>有些人认为每个项目周期情况都有所好转，但只是换汤不换药。在早期，员工可能在一年或两年内每周工作30到40小时，然后到了最后阶段，发现有太多的工作要做。他们在最后几个月每周至少工作双倍的时间。在后来的项目中，事情更多地由项目经理和制作人控制，这演变成了所谓的“死亡加班”或“死亡行军”。员工在游戏发售前一两年就开始每周工作50小时。这并没有增加太多工作时间，没有人抱怨太多，但如果员工想做计划却不知道自己是否需要工作，就会出现问题。经理们很擅长确保员工能够休假，并为加班工作提供大量的额外休假补偿，但员工仍然被要求付出很多。</p>

<p>随着发售日期临近，员工的工作时间越来越多。可能从周二和周四的早9点到晚9点开始，然后变成周一到周四的早9点到晚9点。接着是周六的上午10点到下午4点。然后周五晚上也变成早9点到晚9点。接着是周六的早9点到下午5点。在紧急情况下，他们甚至会要求周日也许中午12点到下午4点。</p>

<p>当员工认为游戏很糟糕且无法做好时，士气会变得低落。这时管理层会告诉员工，他们决定将游戏推迟到完成后再发售，并且延长了发布日期。这对游戏本身有好处，但当员工想到在他们如此努力地赶工期之后还要工作这么久时，士气仍然会受到影响。例如，以最近的《质量效应》为例，员工被告知游戏将在2006年圣诞节发售。然后推迟到2007年2月，再后来是春季，然后是6月。但我们知道游戏直到11月才发售。这确实让员工感到疲惫。</p>

<p>每次游戏延期，管理层都会在几周内减少工作时间，或者提供额外几天的休息，或者一个四天的周末来让员工恢复精力。加上人们会说“在过去我们每周工作90、100甚至更多小时。现在只有50或60小时，有时偶尔70小时，这已经很棒了。”这是为了让你记住你在制作游戏，应该为每周只在Bioware工作50或60小时来制作“有史以来最好的游戏”而感到高兴。</p>

<p>游戏发售之后，员工会休长假，有时是几周。然后他们慢慢恢复工作。每周可能只被分配20或30小时的工作。这可以持续几个月，之后员工才恢复正常的每周40小时工作制。而在项目推进发售的许多个月或一年里，情况会重复这个循环。</p>

<p>这并不是对Bioware的控诉。视频游戏行业的所有公司都这样做。不公平地指出Bioware是个例外，但对我来说它是个不错的公司。</p>

<p>人们经常因为这些原因离开。过去一年里离开了更多的人。也许是两年。也许是三年。我不确定。但Bioware雇佣了很多人，而离开的人通常不如被雇佣的人优秀，所以结果还算过得去。许多Bioware的员工希望更多人离开。也许随着没有“混日子”的人（因为没人被解雇），可以有一支更强大的团队，工作效率更高。</p>

<p>当EA配偶事件曝光时，Bioware的每个人都对此感到好奇。但随后的报告关于EA配偶的丈夫在做什么工作以及他所处的情况，Bioware的每个人都意识到他们的情况远没有那么糟糕。Bioware的员工享受着管理层的支持和漫长的项目周期，但没有每周100小时的工作。一些员工如果能以最高标准完成工作，确实可以在很长一段时间内保持朝九晚五的工作时间。</p>

<p>但人们仍然希望整个行业能够改善。许多Bioware的员工会非常高兴，如果没有任何其他改变，只是永远不需要工作超过40或45小时。但也有许多Bioware的员工是工作狂，他们永远不会工作少于50或60小时，他们总是创造危险的先例，并不公平地控制节奏。</p>

<p>Bioware的每个人都意识到EA现在拥有他们，但没有人对此多加考虑。一切都一样，除了钱更多了。很容易忘记Bioware曾经被EA收购，因为文化没有改变，也没有EA的人进来大喊必须改变做事方式。考虑到Bioware并不制作真正畅销的游戏，可能确实值得EA这样的公司进来说这是如何操作的。Bioware的游戏在一两年后最终能卖出一两百万份，但他们从来没有过那些大作在第一个月就能卖出500万份的首发成绩，而这正是Bioware非常渴望的。</p>

<p>如果你想从事视频游戏行业，无论你去哪里，你都会工作很长时间，但Bioware是一个很好的地方，因为他们确实对你很好。许多人离开Bioware后写信回来说他们后悔了。有些人回来了。有些人确实在其他地方找到了更好的生活，但也说Bioware的生活很好，他们怀念它的许多方面。我认为更多的人离开Bioware是为了逃离埃德蒙顿（Deadmonton）这个“死气沉沉”的地方，而不是其他任何原因！！</p>

<p>Bioware审查那篇文章（如果他们做了的话）并不让我惊讶，因为他们非常控制自己的形象。他们只想要关于他们的正面谈论，并希望保护脆弱的自我和员工的士气。审查负面宣传并不能让负面宣传变得真实。Bioware只是不希望这些内容流传出去。
</p><hr>
<i>Anonymous</i><p></p>

<p>我经历过相当多次的加班（在夏季三个月内加班超过400小时），参与过各种项目。我的体会是：</p>

<p>1) 不要对此蛮不讲理。如果你需要加班，承认原因并制定一个合理的加班计划。</p>

<p>人们会想尽办法获得空闲时间。持续超过两周的加班太多，中间没有休息，会导致错误增多，而不是工作成果增多。</p>

<p>2) 认识到一天正常工作日中，实际能为公司带来效益的工作大约平均只有5-6小时。通过加班，你可以让员工在办公室每天待12小时以上，但额外的工作时间相比收益并不划算。</p>

<p>3) 我认识的几乎每个开发者都非常了解这个行业及其运作方式。他们大多数人将家庭和生活放在工作之上，如果加班或公司看起来不公平或管理不善，他们会毫不犹豫地立即辞职。再次强调，不要在加班问题上蛮不讲理。</p>

<p>4) 我不想花几年时间制作一个烂游戏。那是对我生命和时间的浪费。如果你有头脑地规划加班，我会为你加班。如果你不这样做，我会把我的生命用在更有意义的事情上。</p>

<p>匿名用户关于Bioware的描述听起来非常像正常情况，我愿意在这样的公司工作。我想离开我的办公桌去玩个演示之类的，我喜欢人们信任我，是的，我会在截止日期前完成任务。事实上，我认为每个游戏公司都应该这样运作。我很高兴这些年来我没有经历过其他任何方式。
</p><hr>
<i>Anonymous</i><p></p>

<p>虽然那样的工作时间可能是典型或预期的，但它们绝不是可以原谅的。那些要求员工在项目期间长时间每周工作60小时的公司，要么是a) 项目经理无能，要么是b) 在利用他们的团队。</p>

<p>仅仅因为这是视频游戏行业，很多人认为这只是理所当然。我对此表示反对，Erin Hoffman（EA配偶）也是如此。谢谢你，Erin，把这种垃圾事情曝光。</p>

<p>不是每家公司都那样。我现在在一家很棒的游戏公司工作，我们正在为Wii制作一款3A级游戏。我们在某些时候遇到了一些困难，但我们的加班时间是每周50小时，而且几乎从不连续进行加班。</p>

<p>认为你有时必须默默忍受游戏行业的想法？是的，绝对如此。作为一个行业，我们的制作实践还太年轻，而且涉及大量资金。认为你必须出于对项目的热爱而被迫长时间（60小时或以上）工作的想法？作为一个负责任的公司所有者，你需要回头增加一些资源来解决问题，或者开始削减范围。因为在那个时候，你是在为自己的错误而剥削你的团队。

</p><hr>

<p><i>Anonymous</i></p>

<p>哇，你们能认为每周60小时的工作是现代美国公司的某种例外，真是幸运。我是个玩家，不是游戏行业从业者，但我在好莱坞从事电影制作，这些工作时间在这里确实是家常便饭。我指的是每天，早上9点上班，晚上7:30之前不能离开，而且你被期望带剧本或小说回家，为第二天做阅读和撰写工作。而且薪水也不高，“创意总监”的平均薪水在5万美元左右，你每周工作65小时，还不包括任何在办公室之外要求的阅读或额外工作。</p>

<p>我不是说这是可以原谅的，只是说这似乎是当今美国的趋势，它影响了许多行业，从银行到律师、咨询、视频游戏和电影。可悲的事实是，在大多数人10或12小时的工作日里所做的“工作”，如果员工不是如此信奉在场主义，或者认为你坐在办公桌前的时间等于你作为工人的生产力，这些工作可以在5小时内完成。</p>

<p>总之，我只想插话说，这些剥削并非游戏行业的特有症状，而是美国白领工作的普遍现象，所以……小心点！事实是，在这些竞争激烈的行业中，有太多愿意接受这些剥削的工人，我们几乎无力组织起来或保护自己。祝你们好运。
</p><hr>
<i>Anonymous</i><p></p>

<p>我在Bio工作过几年，我可以证实楼主说的是真的。Greg和Ray多年来在游戏行业培养了一种印象，即他们 somehow 比其他公司更好。他们不是。我不是说他们是怪物，因为个人而言他们都挺酷的。然而，在商业方面，他们相当无情。我认为主要是Ray。他们不断承诺情况会好转，但并没有。Bio的几乎每个项目都有长期加班，因为项目负责人总是计划超出他们能交付的范围。所以，员工受苦，而负责人得到赞扬，获得更大更多股份。最新的例子是《质量效应》。那个项目有9个月的加班期。有些人差点神经崩溃。他们实施了同情加班，而他们也承诺已经废除了。那就是整个团队必须在现场，以防万一出什么问题，即使他们自己没事可做。但这真正的原因是政治考量，确保程序员不会因为艺术家早点下班或谁谁谁而生气。我认为在EA管理下情况只会变得更糟。最终人们会意识到Bioware和其他加班的开发商没什么两样。

</p><hr>

<p><i>Anonymous</i></p>

<p>（顺便提一下，我之前发过帖，但鉴于有这么多匿名发帖者，我澄清一下，我就是那个提到夏季加班400小时的人）</p>

<p>我从未理解或必须承受同情加班——即使我们的项目也有加班，但更多是关于分担工作量，或者只是意识到，是的，程序员确实有更多的工作要做。</p>

<p>我最近和一位美国朋友交谈，她对我有4周的暑假和1周的寒假感到惊讶。她很难想象，因为她从未有过。她一年最多只休息1周。加上每天10小时以上的工作，我无法理解你们如何应对这些。</p>

<p>我在北欧工作，90%的时间每天工作8小时，在我目前的地方，我可以用10个手指计算出总的加班时间。</p>

<p>当你年轻时，400小时的加班和睡在睡袋里可能不是什么大事，但快30岁时，你会对自己的权利等更感兴趣。那次400小时的经历对我来说是个很好的教训，让我知道不该怎么做。这很有价值，但再也不会了。我花了超过一年的时间才从那次经历中恢复过来，修复了我的朋友和社交生活。
</p><hr>
<i>Anonymous</i><p></p>

<blockquote>
<p>我认为更多的人离开Bioware是为了逃离埃德蒙顿（Deadmonton）这个“死气沉沉”的地方，而不是其他任何原因！！</p>
</blockquote>

<p>嘿！去你的！</p>

<p>（埃德蒙顿人）</p>

<p>我认识一些Bioware的人。他们对公司的忠诚度令人惊讶——如果，像之前的发帖者所说，有一种被培养出来的印象认为Bioware比其他游戏公司更好，那么这绝对已经深入人心。他们开玩笑说感觉像是几乎住在一个自给自足的生态建筑里。</p>

<p>我记得其中一个人轻描淡写地认为EA收购Bioware没什么大不了的，是正常的商业行为，为什么会有改变等等。我确信这本意是为了让人安心，但考虑到EA臭名昭著的记录，这听起来天真且不以为然。</p>

<p>这是我第一次看到他们加班时间的具体数字，如果属实的话，这令人失望。这听起来像是管理不善，我猜我对这家公司还有一些童话般的看法。我希望为了Bioware好，情况不会变得更糟。
</p><hr>
<i>Anonymous</i><p></p>

<p>一位匿名用户（或者说，从这一页来看，我是之前为Bioware辩护的那个匿名用户）回应另一位——他们实施了同情加班，而他们也承诺已经废除了。</p>

<p>我的回应是他们没有实施那个（？）。人们当然要待命，但他们没有告诉每个人都必须到岗，因为有些人有工作要做。那是不正确的。如果一个经理认为他们的团队在冲刺阶段有交付成果，那么他会告诉团队必须到场。如果有人落后了，他们必须到场。如果有人能帮助团队，他们必须到场。但这几乎总是由个别经理决定，如果一个员工仅仅因为经理要求就必须到场，那么这应该归咎于那个特定的经理，那个经理应该和Casey或项目经理讨论过。</p>

<p>我并非不同意你帖子的其余部分，那些和我之前说的大致相同。但这一点不准确。
</p><hr>
<i>Anonymous</i><p></p>

<p>回应你的同情加班论点；这当然是经理的错，但你真的认为有人会违抗Casey而不受罚吗？他们从不直截了当地说人们必须出于政治原因到场，他们用各种方式来包装。我所知道的是，我有不止几个晚上在工作到凌晨2点或3点，因为可能会出问题，尽管我住的地方离公司不到30分钟车程，并且告诉他们可以随时打电话给我。我还知道，我因为总是要求在完成工作后离开而受到批评，因为这给那些被迫留下的人造成了不良印象。</p><p><em>由 mimo-v2.5 模型翻译，花费 10997 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/bioware/</link>
      <guid isPermaLink="false">https://danluu.com/bioware/</guid>
      <pubDate>Sat, 31 May 2008 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>Symbolics Lisp机器的历史</title>
      <description>[AI 摘要] 该文章是Symbolics公司创始人Dan Weinreb对Richard Stallman关于Lisp机器公司起源叙事的反驳，并分析了Symbolics失败的原因。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 该文章是Symbolics公司创始人Dan Weinreb对Richard Stallman关于Lisp机器公司起源叙事的反驳，并分析了Symbolics失败的原因。</div><p><em>这是Dan Weinreb关于Symbolics和Lisp机器的评论存档。</em></p>

<h3 id="rebuttal-to-stallman-s-story-about-the-formation-of-symbolics-and-lmi">对Stallman关于Symbolics和LMI成立经过的叙事的反驳</h3>

<p>Richard Stallman多年来一直在讲述一个关于Lisp机器公司起源以及它们对MIT人工智能实验室影响的故事。他已将这个故事发表在一本书和一篇被广泛引用的论文中，你可以在 <a href="http://www.gnu.org/gnu/rms-lisp.html" rel="noopener noreferrer">http://www.gnu.org/gnu/rms-lisp.html</a> 找到。</p>

<p>他的叙述存在严重偏见，并且在很多地方完全是错误的。以下是我对真正发生之事的个人视角。</p>

<p>Richard Greenblatt关于成立一家Lisp机器公司的提议有两个前提。第一，不应有外部投资。这完全不现实：一家制造计算机硬件的公司需要资本。第二，Greenblatt本人应担任CEO。Lisp机器项目的其他成员极度怀疑Greenblatt管理公司的能力。因此，Greenblatt和其他人分道扬镳，各自成立了两家公司。</p>

<p>Stallman将此定性为“背后捅刀”，并称Symbolics决定“不讲道义”，这纯属无稽之谈。根本不存在任何“背后捅刀”。Symbolics是极其谨慎的。Stallman将Symbolics描述为“寻找方法摧毁”LMI，这纯粹是幻想。</p>

<p>Stallman声称Symbolics“挖走了所有黑客”，并且“AI实验室现在束手无策”、“没人预料到AI实验室的黑客群体会被摧毁，但它确实被摧毁了”，以及Symbolics“摧毁了MIT”。首先，即使如Stallman所愿，只有一家Lisp机器公司，完全相同的人也会离开AI实验室。其次，Symbolics仅从AI实验室雇佣了四名全职和一名兼职人员（见下文）。</p>

<p>Stallman接着说：“所以Symbolics想出了一个计划。他们对实验室说：‘我们将继续让你们使用我们对系统的修改，但你们不能将其放入MIT Lisp机器系统。相反，我们会让你们访问Symbolics的Lisp机器系统，你们可以运行它，但仅此而已。’”换句话说，Symbolics开发的软件没有免费提供给LMI。这有什么奇怪的吗？无论如何，那不是Symbolics的“计划”；它是MIT许可协议的一部分，LMI签署的正是同一份协议。LMI的修改也同样是LMI专有的。</p>

<p>接下来他说：“过了一段时间，我得出的结论是，我甚至最好不去看他们的代码。当他们发布测试版公告和发布说明时，我会看看有哪些功能，然后自己实现。等到他们有正式版本时，我也有了。”首先，他确实在看Symbolics的代码；我们好几次抓到他这么做。但其次，即使他没看，复制别人已经设计好的东西比自己设计要容易得多。他复制的是增量改进：这里一个新的编辑器命令，那里一个新的Lisp实用工具。这只是Symbolics所做软件开发中极小的一部分。</p>

<p>他将此定性为对Symbolics的“惩罚”，这是愚蠢的。他所做的对Symbolics从未产生任何实际影响。在现实中，Symbolics很少与LMI竞争销售。LMI的存在与Symbolics的利润底线关系甚微。</p>

<p>在我澄清事实之际，最初的（基于TECO的）Emacs是由Guy L. Steele Jr.和David Moon创建和设计的。在他们使其运行起来，并且它已成为AI实验室的标准文本编辑器之后，Stallman接管了维护工作。</p>

<p>以下是Symbolics创始人的名单。请注意，Bruce Edwards和我之前曾在MIT AI实验室工作，但在Symbolics成立之前已经离开去其他地方工作了。Henry Baker并非Stallman所说的“黑客”之一。</p>

<ul>
<li>Robert Adams（首任CEO，加利福尼亚）</li>
<li>Russell Noftsker（此后任CEO）</li>
<li>Minoru Tonai（CFO，加利福尼亚）</li>
<li>John Kulp（来自MIT等离子体物理实验室）</li>
<li>Tom Knight（来自MIT AI实验室）</li>
<li>Jack Holloway（来自MIT AI实验室）</li>
<li>David Moon（一半时间在MIT AI实验室）</li>
<li>Dan Weinreb（来自劳伦斯利弗莫尔实验室）</li>
<li>Howard Cannon（来自MIT AI实验室）</li>
<li>Mike McMahon（来自MIT AI实验室）</li>
<li>Jim Kulp（来自IIASA，维也纳）</li>
<li>Bruce Edwards（来自IIASA，维也纳）</li>
<li>Bernie Greenberg（来自Honeywell CISL）</li>
<li>Clark Baker（来自MIT LCS）</li>
<li>Chris Terman（来自MIT LCS）</li>
<li>John Blankenbaker（硬件工程师，加利福尼亚）</li>
<li>Bob Williams（硬件工程师，加利福尼亚）</li>
<li>Bob South（硬件工程师，加利福尼亚）</li>
<li>Henry Baker（来自MIT）</li>
<li>Dave Dyer（来自USC ISI）</li>
</ul>

<h3 id="why-did-symbolics-fail">Symbolics为什么失败了？</h3>

<p>在之前一篇博客文章的评论中，有人问我Symbolics为什么失败了。以下内容虽然简化了，但应该足够说明问题。我的老朋友们非常欢迎发表评论进行修正或补充，当然，也欢迎所有人发表评论。</p>

<p>首先，请记住在1980年左右Symbolics成立的时候，认真的计算机用户使用的是分时系统。为一个人配备一整台计算机的想法是大胆的，几乎是异端的。每家计算机公司（想想Prime、Data General、DEC）都自己做硬件和自己的软件套件。那时没有PC，没有Mac，没有工作站。在MIT人工智能实验室，十五位研究人员共享一台CPU为0.001 GHz、主存为0.002 GB的计算机。</p>

<p>Symbolics向两类客户销售，我称之为主要客户和次要客户。主要客户将Lisp机器用作软件开发环境。最初的目标市场是MIT AI实验室本身，然后是类似的机构：大学、公司研究实验室等。次要客户将Lisp机器用于运行由其他方编写的应用程序。</p>

<p>我们在主要客户中取得了巨大成功。我认为如果我们的营销做得更好，本可以找到更多这样的客户。例如，你知道Symbolics有一个世界一流的软件开发环境，支持Fortran、C、Ada和其他流行语言吗？它在编辑器中具有惊人的语义理解能力、强大的调试器、允许不同语言相互调用等功能。我们在这些方面投入了大量工作，但它们从未被宣传或广告过。</p>

<p>但我们知道，真正成功的唯一途径是开发次要市场。ICAD制造了一个先进的基于约束的计算机辅助设计系统，该系统只能在Symbolics机器上运行。可悲的是，他们是唯一这样做的公司。为什么？</p>

<p>世界在我们脚下迅速改变了。新的“工作站”计算机类别出现了：Sun、Apollo等等。实现Lisp的新技术被发明出来，使得良好的Lisp实现可以在传统硬件上运行；虽然不如我们的完美，但对大多数目的来说已经足够好。因此，我们特殊的Lisp架构带来的真正增值突然被削弱了。大量有用的Unix软件出现了，并且可以在Unix工作站之间移植：不再需要每个厂商开发一整套软件。工作站厂商得以借助Intel、Motorola和IBM生产的日益快速、廉价的CPU发展，Symbolics很难与他们竞争。我们Symbolics很晚才承认这一点。即使当它变得不那么正确时，我们仍然相信自己的“教条”。它已根植于我们的企业文化中。如果你质疑它，同事们会觉得你“就是不明白”，不是“圈内人”，可以说。这扼杀了客观分析。（这是一个很容易陷入的问题——别让它发生在你身上！）</p>

<p>次要市场通常有需要使用工作站（以及后来的PC）硬件的原因。他们经常需要与在Symbolics上运行不了的其他软件交互。或者他们想与其他不在Symbolics上运行的应用程序分摊硬件成本。Symbolics机器逐渐被视为与“通用”Unix工作站（以及后来的Windows PC）相比的“专用硬件”。它们成本高昂，却不能用于范围越来越广的可用Unix软件。很少有供应商愿意制造只能运行在“专用硬件”上的产品。（感谢ICAD；我们爱你！）</p>

<p>此外，许多Symbolics的销售基于基于规则的专家系统的承诺，这些系统的早期例子是用Lisp编写的。基于规则的专家系统是好东西，今天被广泛使用（但通常不是用Lisp）。但某些学者及其行业对其进行了过度炒作，导致1988年左右出现了巨大的反弹。“人工智能”失宠了；“人工智能寒冬”来临了。</p>

<p>（Symbolics确实也启动了自己的项目，为PC开发Lisp，称为CLOE，并且与其他Lisp公司（特别是Gold Hill）合作，以便客户可以在Symbolics上开发，然后在传统机器上部署。我们并非完全愚蠢。关键在于，对Lisp的兴趣实在下降太多了。）</p>

<p>与此同时，在Symbolics内部，存在巨大的管理冲突，导致许多高管辞职，他们被董事会用新的CEO取代，这些新CEO工作不力，并且没有远见看到正在发生什么。Symbolics签署了大型新办公室和新工厂的长期租约，预期的增长没有到来，并且由于办公空间过剩而无法转租，这消耗了大量资金。进行了一轮又一轮的裁员。我们越来越多的人意识到发生了什么，而Symbolics没有做出反应。我在1988年与几位同事离开了，创办了Object Design, Inc.，为当时全新的主流面向对象语言C++开发面向对象数据库系统（在创建了面向对象Lisp数据库系统Statice之后）。（该公司非常成功，目前作为Progress Software的ObjectStore分部存在（www.objectstore.com）。我期待着明年夏天的20周年重聚派对。）</p>

<p>Symbolics确实试图应对这种情况，首先制造可以插入传统计算机的Lisp机器板卡。一个问题是他们总是押错宝。MacIvory是一块Symbolics Ivory芯片（是的，我们自己制造CPU芯片），插入Macintosh（哎呀，不是领先平台）的NuBus（哎呀，早已消失）。后来，他们最终放弃了与大型芯片制造商的竞争，使用了一块来自主流制造商的快速芯片：DEC Alpha架构（哎呀，被HP/Compaq淘汰了，本应该用Intel的）。到这时，一切都太少、太晚了。</p>

<p>在之前博客文章上发表评论的人提到了Eve Philips的一篇MIT硕士论文（见 <a href="http://www.sts.tu-harburg.de/~r.f.moeller/symbolics-info/ai-business.pdf" rel="noopener noreferrer">http://www.sts.tu-harburg.de/~r.f.moeller/symbolics-info/ai-business.pdf</a>），标题是“如果它能工作，它就不是人工智能：对人工智能初创企业的商业化审视”。这是我第一次听说，但显然她得到了Tom Knight的帮助，Tom是另一位Symbolics联合创始人，对Symbolics历史的了解和我一样多，甚至更多。让我们看看她是怎么说的。</p>

<p>嘿，这看起来很棒！值得一读！她绝对知道自己在说什么，而且读起来很有趣。它让我想起了许多过去的回忆。如果你曾经想创办一家公司，你可以从阅读像这里这样的“战争故事”中学到很多。</p>

<p>以下是我阅读时的一些评论。这篇论文大部分内容是关于AI软件供应商的，但他们的命运对Symbolics有重大影响。</p>

<p>哦，当然，DARPA在80年代末削减资金非常重要。许多Symbolics主要市场的客户最终是由DARPA研究资助的。</p>

<p>是的，基于规则的专家系统确实取得了一些令人兴奋的成功。Inference为美国运通公司开发的“授权助手”（帮助接听电话的人确保你没有欺诈性地使用运通卡）运行在Symbolics机器上。我在这里了解到，它的投资内部收益率达到了45-67%，这非常令人印象深刻。</p>

<p>这篇论文有一个时代错误：“很少有提供语言的大型软件公司（即微软）提供任何形式的Lisp支持。”在这些事件发生时，微软的主导地位还有多年才会到来。例如，记住第一个可行的Windows操作系统3.1版本是在1990年发布的。但她的核心观点是正确的。</p>

<p>她说“人工智能界存在大量的傲慢，虽然并非完全无根据，他们认为Lisp将改变计算机系统无处不在的运行方式。”这绝对正确。它并不像听起来那么错误：Lisp的许多思想已经成为主流，特别是托管（垃圾回收）存储，Lisp也因其被接受而部分归功于面向对象编程。我毫不怀疑Lisp对Java产生了巨大影响，进而影响了C#。请注意，微软的公共语言运行时技术目前由杰出的Patrick Dussud领导，他是来自第三家MIT Lisp机器公司——德州仪器的Lisp大师。</p>

<p>但那时我们真的相信Lisp。我们对任何试图用C编写专家系统的人只有蔑视；这是我们企业文化的一部分。我们确实认为Lisp会“改变世界”，类似于“六十年代”的人认为世界可以被“和平、爱与欢乐”改变。抱歉，没那么容易。</p>

<p>这让我想起，我极力推荐Richard Gabriel的书《软件的模式：软件社区的故事》（<a href="http://www.dreamsongs.com/Files/PatternsOfSoftware.pdf" rel="noopener noreferrer">http://www.dreamsongs.com/Files/PatternsOfSoftware.pdf</a>），它涉及技术从实验室走向市场过程。Gabriel是五位主要Common Lisp设计师之一（与Guy Steele、Scott Fahlman、David Moon和我），但这里的关键点远不止于Lisp。这是Gabriel系列论文的集大成之作，始于他最初的“更差即更好”。这里的思想得到了更深入的发展。他的见解独特且极具说服力。</p>

<p>好了，回到Eve Philips：在第5章中，她描述了“人工智能硬件产业”，从MIT Lisp机器开始。她说对了吗？嗯，她说“14位AI实验室黑客加入了他们”；请看我之前关于这个数字的帖子，但在上下文中，这是一个非常次要的问题。其余的故事是正确的。（她甚至提到了我指出的房地产问题！）她也充分证明了Symbolics管理和营销的弱点。这是一部优秀的作品。</p>

<p>Symbolics非常有趣。我们曾取得过巨大成功并上市。我的同事们是你能期望共事的最有能力、最讨人喜欢的技术人才之一。我从他们那里学到了很多。我无论如何都不会错过这段经历。</p>

<p>离开后，我以为我再也见不到Lisp了。但现在我发现自己在ITA Software，我们正在编写一个巨大、复杂的事务处理系统（一个新的航空公司预订系统，最初为加拿大航空服务），其核心是Common Lisp。我们几乎肯定拥有世界上最大的Common Lisp程序员团队。我们的开发环境还可以，但我真的希望我还能再有一台Lisp机器。</p>

<h3 id="more-about-why-symbolics-failed">更多关于Symbolics为什么失败的原因</h3>

<p>我刚读到Alvin Graylin、Kari Anne Hoir Kjolaas、Jonathan Loflin和Jimmie D. Walker III（未注明所属机构和日期）的《Symbolics, Inc: A failure of heterogeneous engineering》，见 <a href="http://www.sts.tu-harburg.de/~r.f.moeller/symbolics-info/Symbolics.pdf" rel="noopener noreferrer">http://www.sts.tu-harburg.de/~r.f.moeller/symbolics-info/Symbolics.pdf</a></p>

<p>这是一篇优秀的论文，如果你对Symbolics的遭遇感兴趣，这是必读之作。</p>

<p>该论文的论点基于一个叫做“异构工程”的概念，但很难看出除了“把公司经营好”之外他们具体指什么。他们用花哨的方式说，你不能只做技术，你还必须做营销、销售、财务等等，这相当明显。他们对Symbolics长期愿景的广泛分歧描述得非常正确，我应该在我的文章中提到这是Symbolics最大的问题之一。研发的随意方向，常常与公司其他部门不协调，这里描述得很好（他们有很好的信息源，包括Dave Moon大量典型而严厉诚实的邮件）。马萨诸塞州剑桥的软件部门和加利福尼亚州伍德兰希尔斯（后来的查茨沃斯）的硬件部门之间的分离也是一个真正的问题。他们说“一旦有了资金，Symbolics就像一个刚发现财富的彩票中奖者一样花钱”，这完全正确。功能蔓延确实极其严重。这篇论文还有Symbolics的财务数据，相当有趣且有启发性，显示收入在1986年前稳步增长，随后在1987年至1989年间收入下降、收益为负。</p>

<p>以下是一些我不同意的观点。他们说“在增长年份里，Symbolics一直在寻找CEO”，直到聘请了Brian Sear。我很确定那只是在问题开始后才发生的。我不同意Brian Sear关于我们没有照顾好现有客户的说法；我们确实非常努力，我认为这是这么多前Symbolics客户如此怀旧的原因之一。我不认为Russell是对的，他说“许多Symbolics机器是由通过星球大战计划资助的研究人员购买的”，这一点他们重复了很多次。然而，许多机器确实是通过DARPA资助的，如果你用“DARPA”替代所有关于“星球大战”的说法，那么他们所说的就是正确的。声称“Lisp机器的数量可能超过了Lisp程序员的数量”是夸张。并非没有人考虑过比研究人员更广阔的市场；相反，我们打算销售给增值经销商和原始设备制造商。“VAR和OEM”这个短语几乎是一种咒语。不幸的是，我们只成功做到过一次（ICAD）。虽然他们说Sun机器“可以用于许多其他应用”是正确的，但有趣的是原因：为什么Sun上有很多可用的应用程序？Unix作为一个可移植平台的兴起（在当时是一个新概念）以及Sun的价格，与此有很大关系。他们没有考虑为什么Apollo失败了。</p>

<p>还有很多。向作者们，无论你们在哪里：非常感谢！</p><p><em>由 mimo-v2.5 模型翻译，花费 8843 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/symbolics-lisp-machines/</link>
      <guid isPermaLink="false">https://danluu.com/symbolics-lisp-machines/</guid>
      <pubDate>Fri, 16 Nov 2007 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>子空间/连续体历史</title>
      <description>[AI 摘要] 本文追溯了经典科幻多人在线游戏《子空间》及其后继作品《步兵在线》从开发、发行到版权易手的坎坷历史。</description>
      <content:encoded><![CDATA[<div style="background:#f0f4f8;border-left:3px solid #3b82f6;padding:12px 16px;border-radius:6px;margin:12px 0;font-size:14px;color:#555"><strong>[AI 摘要]</strong> 本文追溯了经典科幻多人在线游戏《子空间》及其后继作品《步兵在线》从开发、发行到版权易手的坎坷历史。</div><p>档案来源于未知出处。可能来自Gravitron？</p>

<p>关于历史：</p>

<h3 id="chapter-1">第一章</h3>

<p>（大约）1995年12月，一切开始。
Rod Humble希望创建一个类似《空中战士》但在线的游戏，他向维珍互动娱乐公司提出了这个想法，他们回应的大意是“给你钱，祝你好运”。
Rod联系了Jeff Petersen，问他是否有兴趣帮助创建一款纯在线游戏，Jeff同意了。
为了克服延迟问题，Jeff决定测试一个模拟牛顿物理学原理的引擎——运动中的物体倾向于保持相同向量，并在此基础上构建了预测公式。
他们还请来了Juan Sanchez，Rod曾与他合作过，为他们设计游戏图形。
无论如何，在取得一些初步进展后，他们决定将其公开给玩家测试，并将其代号命名为Sniper。
他们找了一些人试玩并提供反馈。
经过短暂的Alpha测试期后，他们认为已经学到了足够多，决定停止项目。
当这个消息宣布时，来自社区的强烈反响给他们留下了深刻印象，让他们决定继续开发。
他们在1996年初中期进入Beta阶段，并将其命名为SubSpace。
从此，游戏进入了真正的开发周期，并通过口碑相传向许多人开放和宣传。
Michael Simpson（Blackie）被维珍互动娱乐公司（VIE）指派为来自其Westwood工作室部门的外部制作人，担任推广代理人、社区经理和整体公共关系负责人。
Jeff和Rod开始准备离开VIE。</p>

<h3 id="chapter-2">第二章</h3>

<p>1997年底，SubSpace正式进入零售周期，开始收集预订并取消了演示版特权（演示版客户端限制为仅15分钟游戏时间，且只能访问前四种飞船）。
其背后原因是维珍互动娱乐公司（VIE）正在走下坡路，逐渐亏损（唯一使其长期维持下去的是Westwood和《命令与征服》系列），并试图在任何可能的机会上套现。
1998年初，一项由Juan主导、主要由Michael宣传的小规模工作开始了SubSpace 2的开发，但它很快化为泡影，之后再未被讨论。
跳到1998年，VIE将SubSpace归类为B级产品，这意味着它没有广告预算。
此外，他们仅生产了大约1万份副本，并以每盒30美元的价格只投放给少数几家零售商。
同样，VIE还错失了将SubSpace卖给微软的机会，将其纳入“Xbox Live”（The Zone）中，这本可以确保游戏的长期成功和延续，并获得一笔可观的收入。
这笔交易因为当时拥有VIE的Viacom的干预而失败，直到完全搞砸。
Rod和Jeff对此感到愤怒，意识到一切都结束了，并在合同到期前几个月（他们签订了1年合同，有权选择每年续约一年）通知VIE他们打算离开并独立，他们试图与VIE谈判建立开发商-发行商关系，但自然没有成功，双方分道扬镳。
1998年10月，风声从内部人士和传闻中传出（后来被证明是真实的），关于VIE破产以及SubSpace被遗弃、没有支持也没有开发团队的传言开始流传。
尽管Michael极力否认，但这个可怕的消息被证实是真的，不久之后，VIE正式宣布关闭SubSpace并完全撤出支持，同时提交了第11章破产申请并出售剩余资产（Viacom已经将Westwood连同Michael一起卖给了电子艺界）。
Brilliant Digital Entertainment（Kazaa/Altnet.com）的所有者创建了一家名为Ozaq2的资产控股公司，现在是SubSpace版权的唯一持有者。
那时，最初的开发者们早已离开。</p>

<h3 id="chapter-3">第三章</h3>

<p>1998年初/1997年底，前SubSpace开发者：Rod、Jeff和Juan加入了Origin Systems，该公司与他们签订了制作《Crusader Online》的合同。
然而不幸的是，在制作出Alpha版本后，Origin执行了一项条款，声明如果他们不喜欢演示版就可以终止项目，于是项目被终止了。
Nick Fisher（又名trixter，正如在SubSpace中所知）找到了他们，他们共同组建了Harmless Games，他们的第一个任务是利用已有的成果进一步开发成一款可行的盈利在线游戏，《Crusader Online》的演示版被重新命名为《步兵在线》（Infantry (online)）。
顺便说一句，我不知道他们制作的内容是否就是后来所谓的“《Crusader: No Mercy》”（所谓的《Crusader》在线版本，仅发布过一张可能为假的截图）。
Nick创建了GameFan Network，将托管warzone.com和《步兵在线》的游戏服务器，以及其他网站和交易。
Jeff让游戏迅速通过Pre-Alpha阶段，并快速推进到适合进行Alpha测试的阶段。
Larry Cordner受聘为游戏创建内容编辑器，尽管他不会长时间获得报酬（并在转移到SOE时消失/被解雇）。
到1998年10月，Harmless Games的网站上线，附带了“最”官方的《步兵在线》专区，这是整个网站中唯一受到关注的部分。
Juan为Rod创作了Harmless Games的标志——三硝基泰迪熊（Tri-Nitro-Teddy）。
Jeremy Weeks受聘创作《步兵在线》的次要概念艺术。
1998年11月，Harmless Games正式宣布《步兵在线》，Alpha测试即将开始。
Juan在完成部分美术工作后，离开了团队。
1999年3月，Harmless Games正式宣布BrainScan（由Nick创立的公司）作为游戏的发行商，在多次尝试签订发行交易失败后，Beta测试将在该年晚些时候开始，完整的按次付费计划也将不久后推出。
Rod和Jeff就Rod希望通过其关系将《步兵在线》带到Verant/Studio 989（后来更名为SOE）的愿望产生冲突，Rod最终离开了《步兵在线》和Harmless Games，前往索尼在线娱乐公司（SOE）担任高级职位（据信是游戏开发高级副总裁）。
2000年底，由于互联网泡沫破裂，express.com未能支付GameFan Network的费用（当然是广告横幅收入），GFN因无力承担数百万美元债务（以及暗中BrainScan的失败）而崩溃，《步兵在线》的服务器面临关闭，开始寻找新的托管方/发行商。
于是他们联系了Rod，最终Nick拥有的所有知识产权（包括类似ICQ的EGN2程序）都卖给了SOE，《步兵在线》和Jeff也在其中，交易金额“未披露”（据Nick说，这笔交易为他赚了大约600万美元）。
SOE的“The Station”宣布收购《步兵在线》。
《步兵在线》仍在GFN最后剩余的在线服务器上运行，由于某种原因，有人（无论服务器托管方是谁）忘了将其关闭，直到10月底它才被关闭，漫长的昏迷期开始了。</p>

<h3 id="chapter-4">第四章</h3>

<p>2000年11月，《步兵在线》在SOE重新上线。
不久之后，《宇宙裂隙》（Cosmic Rift）开始开发，这是一款SubSpace克隆游戏，于2001年4月公开宣布。
Jeff变得越来越缺席，最终完全从《步兵在线》及其开发中消失（我们后来得知，他是被Rod拉走，调到EQ项目和《星球大战：星系》项目去了）。
SOE部分“解雇”了Jeremy，但后来又重新雇佣了他。
然后在2002年4月，地狱之火喷出了硫磺：《步兵在线》将转为按次付费模式，随之而来的是一系列被违背的承诺，以及EQ客户支持人员被任命为游戏执行制作人。
玩家群体中滋生了大量的不满和悲伤。
一些人开始从Beta时代开始运行私人的、功能有限的服务器。
《步兵在线》的玩家中，那些能访问Beta软件的人（包括Gravitron在内），对Jeff遭遇的不公、游戏的遭遇、Rod的背叛感到愤慨，无法再忍受SOE持续的滥用、虐待和谎言，于是他们采取了激进行动，收集所有可能的可用材料（主要是Beta客户端、Beta服务器和编辑工具），并通过将其公开发布给公众和任何需要的人，来表明立场和观点（尽管这预料之中地激怒了Jeff并疏远了他）。
Rod一头扎进了EQ的深渊，Jeff从雷达上消失了。
其他人继续着各自的生活、工作和项目。</p>

<h3 id="chapter-5">第五章</h3>

<p>关于SubSpace状况的补充说明。</p>

<p>关于VIE之后的SS：
一位名叫Robert Oslo的挪威人，其别名Baudchaser，联系了一家名为Inet的芬兰ISP。
省略大量事件（和糟心事）后，他，与一位被称为Xalimar（一位Exodus/C&amp;W的员工，名字我想不起来了）的人，成为SS火炬的两位传承者，他们安排了游戏区域的托管。
BaudChaser组建了SubSpace Council，并在他停留期间一直负责维持SS的运行，并打击了许多作弊、工作人员（滥用职权）和悲伤行为。
最终Inet停止了托管SS，现在主要由Xalimar一人承担托管核心SS区域的重担。
Priit Kasesalu显然一直在玩这个游戏，后来开始为SSC（SubSpace Council）现任负责人、前Vangel AML联赛系统管理员Alex Zinner（又名Ghost Ship）工作，破解服务器软件，并最终通过逆向工程原始SS创建了自己的客户端，可能以某种方式访问了源代码。</p>

<p>关于2003年中的“SubSpace 2”传闻：
Brilliant Digital Entertainment（BDE）的所有者希望创建一个完美的点对点网络（Altnet.com），他们需要一个旗舰产品来向投资者证明他们的方法是正确且可行的。
为此，他们联系了一家名为Horizon的公司，该公司专注于点对点技术。
Horizon正在开发一种名为Horizon's DEx的点对点技术，后来Horizon更名为SilverPlatter，其技术更名为Alloy。
大约在2002-2003年，他们计划在E3展会上使用BDE的Altnet来展示这种技术的表现——“太空指挥官”（Space Commander），据推测，是SubSpace的重制版，并作为第一款基于点对点技术的大型多人在线游戏。
然而，SilverPlatter最终因故破产，自那以后，除了这次E3展示外，对BDE试图使用其拥有的SubSpace知识产权的情况一无所知。</p>

<h3 id="chapter-6">第六章</h3>

<p>补充更新：</p>

<p>（哇，我必须把这个东西通过一个语法校正应用）
大约在2004-2005年间，Rod以执行制作人/生产副总裁身份离开SOE（如今SOE看起来像一艘即将沉没的漏水船），加入Maxis领导模拟人生项目。
2005年10月，在一系列裁员中，Jeremy Weeks（yankee）被SOE解雇，显然这次是永久性的，《步兵在线》原本就不多的一丝希望现在化为乌有。
Jeff据信仍在SOE工作。
Juan出现在Pendamic Studios，为Lucas Arts工作，参与《星球大战：前线I &amp; II》的开发（并且有一个网站 www.allinjuan.com）。
大约在那年晚些时候或2006年初，一位名为Mar的高级版主玩家面对所有者持续的虐待/忽视而爆发，出于反SOE行动，发布了最新的编辑器工具，但他的努力很快被压制，并且未知是否有人获得了该软件，他当然被取消了身份，随后被禁止进入游戏。
2006年2月，Rod被找到并表达了他的观点，他被指控在担任SOE游戏开发高管期间没有向《步兵在线》提供援助，而他显然处于可以帮助的位置：</p>

<hr>

<p>你知道吗？你说得对。
当时我完全专注于关乎整个公司生存的EQ重大问题。
回想起来，《步兵在线》本可以利用额外的资源发展成更大的产品（尽管我会说它获得的资源已经比其他类似用户基数的游戏要多）。
有点讽刺的是，现在我对图形化MUD类游戏完全厌倦了，像《步兵在线》这样的游戏再次让我感兴趣。
所以，是的，当时我本可以多做一点。希望这是一个教训。
总之，我希望这能算作一个诚实的解释。
我能想象作为玩家那一定有多么令人沮丧。
祝好，</p>

<p>Rod</p>

<hr><p><em>由 mimo-v2.5 模型翻译，花费 6059 tokens</em></p>]]></content:encoded>
      <link>https://danluu.com/subspace-history/</link>
      <guid isPermaLink="false">https://danluu.com/subspace-history/</guid>
      <pubDate>Wed, 1 Feb 2006 00:00:00 +0000</pubDate>
    </item>
  </channel>
</rss>
