整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

李开复张亚勤重磅对谈,顶级AI大咖共话未来,200万网友在线围观

辑部 发自 凹非寺

量子位 报道 | 公众号 QbitAI

“一起花光比尔盖茨的8000万美金,来不来?”

23年前,李开复跟张亚勤这样“画饼”,于是亚洲最牛的计算机研究院就此诞生!

如今,他们一个是最具影响力的VC代表,他创办的创新工场已成为很多技术创新和前沿科技企业的精准捕手,他出版的书籍,不少登上畅销书排行榜。

另一个则是清华智能产业研究院院长、清华智能科学讲席教授,前不久他刚当选中国工程院外籍院士。加之此前美国艺术与科学院院士、澳洲国家工程院院士,成为“三院院士”

结果在MEET2022智能未来大会的现场,两人时隔多年首次同台,在近两百万观众面前,揭秘相识往事,也分享了各自对于科技发展趋势的看法。

从网友的反应上看,这次巅峰对话足以满足他们的期待。甚至还吸引了数十家主流媒体关注报道。

巅峰对话还有哪些亮点?以及十余位顶级AI大咖分享更多精彩内容,老样子,我们详细道来,一文看尽。

李开复张亚勤揭秘相识往事

李开复张亚勤的巅峰对话环节,主要讨论了三个方面。

首先,他们回忆起23年前回国创建微软亚洲研究院MSRA的事情。

当时李开复给张亚勤打电话,就“画了很大的饼”。他们想一同打造中国计算机的黄埔军校,以此证明给全世界看,中国人也能做最顶级的科研。

结果就在张亚勤决定回国,到北京第一天就被李开复修理了。

嗯,理发的理。

张亚勤回忆到,当时跟沈向洋一同回国,头发因为很长时间没打理,他们就被李开复带去理发了。

如今回过头看,当初说要建立个亚洲最牛、全球一流的计算机实验室这一个目标,在张亚勤看来已经实现,尤其在培养人才这方面。

而且过程中,也有让他们感到意外和大受震撼的进展。

比如李开复举例,谁能想到后来的AI四小龙的创始团队,都有微软亚洲研究院的背景。

还有像现在顶尖高校、大企业CTO以及一些创业公司,都有特别多当时培养的人才。

我们感觉特别欣慰,有种桃李满天下的感觉。

接着,他们探讨了当下最流行议题和技术风向。

比如元宇宙

李开复认为,它肯定会到来的,但五年之内不会有特别巨大的公司或应用出现。

张亚勤则补充,要用开放的心态去看待元宇宙。

一方面,如果说,元宇宙是真实世界与虚拟世界的融合,但以真实世界为主的话。

很多公司已经在做了,目前产品形态是技术的一种拓展。

另一方面,要说元宇宙是完全的虚拟世界,和真实世界没关系,那可能走得就太远了一点。现在也有不少炒作,技术也并不成熟,还需要不断地发展。

再比如,科学家创业的热议趋势。

两位老朋友一致认为,科学家创业需要一个企业家伙伴

张亚勤还补充道,科学家还需要专注。如果决定创业,那就出来做这件事。他认为科学家同时上课做科研,还要管理公司是很困难的。

最后他们放眼未来,有哪些领域和赛道值得看好。

张亚勤提及了AI与生物计算和生命科学交叉、无人驾驶和智慧交通,以及IOT。

而李开复Pick的第一个与张亚勤相同,除此之外还有机器人领域,尤其在工业制造上的应用,以及自然语言理解。

尤其是自然语言理解,李开复认为它在近几年的发展跟当年CNN、DNN一样,正从感知智能迅速迈向认知智能。未来AI一旦超越人类,就能做很多辅助、取代人类的工作。

而至于自身的未来小目标,张亚勤还是继续3.0人生——把AIR做起来,李开复则是想用行动证明做高科技的投资回报要比互联网更高。

哦对,这当中还有个小插曲。

当两位被问到,如果有项超能力——可以复制对方的能力,那会如何选择。

李开复首先就说,会复制张亚勤12岁就能读大学的天才能力。

而张亚勤,最想复制李开复吃遍美食还不胖”的能力。

此外,亚勤还说想复制他对未来的洞察,以及可以用简单的语言把复杂事物表述总结出来的能力。

李开复和张亚勤的巅峰对话,由量子位总编辑李根主持,在对话环节最后,他表示能够促成开复和亚勤的这样一次“老友对谈”,是量子位一直以来的愿望——

不仅是因为两位大咖今时今日的地位和成就,更是因为他们在23年前作出的回中国的决定,某种程度上来讲,奠定了如今智能未来的基础。

而且更关键的是,开复和亚勤,还都在继续为产业培育人才、鼓励创新,是中国智能产业领域当之无愧的两座高峰。

清华张亚勤:下个十年是AI与生物制药融合的大好时机

实际上,在巅峰对话开始前,清华大学智能科学讲席教授、清华智能产业研究院院长张亚勤,还以开场主题演讲的形式,分享了他对趋势——特别是AI+生命科学的判断。

清华智能产业研究院AIR于2020年成立,其使命是用人工智能技术赋能产业推动社会进步。

清华AIR选择了三个方向作为突破点:智能交通、智慧物联、智慧医疗。张亚勤这次分享的重点是智慧医疗方向中,人工智能如何赋能生命科学。

他认为整个信息产业过去三十年最大的突破就是数字化,从开始的内容数字化、企业数字化,到现在进入物理世界的数字化和生物世界的数字化。

一方面我们的身体从大脑、器官,到细胞、蛋白质、基因、分子都在数字化,另一方面人工智能算法、算力和系统的快速进展让大量数据有了使用的场所。

以前新药研发需要超过十几年的周期,十亿美元的投入, AI正在改变这种状况。

新冠疫苗去年年底进入临床试验,今年大规模使用,这可能是人类历史上最快的一次计算机科学包括人工智能加速疫苗开发的例子。

另外迁移学习用少量原始数据加上动物模型快速发现了对罕见病的药物,几何深度学习找出了广谱、稳定的新冠抗体,对变种株也有效,Swin Transformer用于测序基因里90%的未编码部分……

张亚勤总结道,AI和生命科学有很多可合作的地方,能让生物制药更快速、精准、安全,更经济、普惠。

但同时也有很多壁垒,算法的透明性、可解释性、隐私安全、伦理等挑战,以及如何把两个行业无缝连接起来。

由此研究院提出了「AI+生命科学破壁计划」作为前沿研究任务,跨越两个领域的鸿沟、打破壁垒促进AI与生命科学的深度交叉融合,构建AI+生命科学的研究和技术生态。

张亚勤看见了生物世界的数字化和AI技术的进展,相信下个十年是生物制药和人工智能融合的大好时机,也是行业发展的最大的机遇。

百度吴甜:技术创新持续为产业发展注入新动能

百度集团副总裁、深度学习技术及应用国家工程实验室副主任吴甜解读了技术创新与产业发展的关系。

根据中国信息通信研究院的数据,2020年我国数字经济已经达到了39.2万亿元,占GDP总值的38.6%,位居世界第二。未来这个数值的绝对值和相对比例都会持续增加,数字产业化和产业数字化齐头并进。

产业发展角度,可以看到产业使用人工智能的场景广泛且分散,技术与产业的结合越来越深入、专业,未来前景会更大更广阔。

技术发展角度,人工智能呈现出明显的融合创新趋势,包括软硬一体融合、跨模态多技术融合、知识与深度学习融合、技术与场景融合。

虽然底层技术越变越复杂,但所幸能够通过开源开放的人工智能平台降低门槛,使AI开发变得越来越容易。

如金融领域常见的智能合同解析与管理场景,传统都是靠人工方式从合同中提取三十多个维度信息,效率低,而保险的产品迭代速度又很快,相应的保险条款也在增加和变化,人工识别一份合同需要30分钟。技术工程师在开发平台上使用ERNIE训练了条款智能解析模型,并持续进行迭代优化,部署到保险业务平台中,提供智能解析能力,对合同文本实现了智能解析,达到通过智能辅助后单份合同解析时长缩短为1分钟。

像这样的变化,在各个行业当中都在发生。

吴甜总结道,一方面是产业的需求越来越旺盛,越来越多和广泛,另一方面技术本身也给我们带来新的想象空间,技术创新持续为产业当中运用人工智能技术注入新的动能,注入新的活力。

IBM谢东:如何让技术创新驱动环境智能和企业可持续发展

IBM副总裁、大中华区首席技术官谢东博士为我们分享「加速科技创新,共赢可持续未来」的议题。

从全球发展状况来看,可持续发展是我们共同面对的战略议题。世界经济论坛2021年全球风险报告指出,未来十年企业面临前的三大业务风险都与环境相关。

在中国双碳目标下,可持续发展不光是我们所有企业的社会责任,更加已经成为企业必须面对的战略议题。对于企业应该如何面对这些挑战,新技术突破会给行业带来哪些转变?

谢东博士从三个维度做了梳理。

企业治理角度,IBM非常注重可持续发展问题,早在50年前,制定了首个企业环境的政策,2000年提出二氧化碳减排目标;与全球各行业客户建立可持续发展咨询委员会。

当中还为助力中国企业实现碳中和制定四阶段战略建议,包括确保合规、优化流程及供应链、重塑业务、引领行业。

基于环境问题,IBM推出环境智能套件,涵盖人工智能、数据分析、环境数据分层、混合云、物联网与区块链。

在技术平台助力环境议题和创新的维度,以IBM位于苏黎世的云上自主化学实验室RoboRXN为例,全球可以通过网络直接访问到实验室,远程完成了从文献检索到一些科研探索,再到功能验证各个环节。

过去两年,化学实验室RoboRXN采用的免费AI模型,已经为学生、科学家和实验者完成了近100万次反应预测。

而背后能支撑这一系列环境和可持续发展创新的底层计算技术又是怎样的?谢东博士提到了最新推出的2nm芯片技术、带有片内AI加速器的处理器Telum,以及前不久发布的突破127 量子位量子处理器。谢东博士认为,量子计算机规模化商用可能已经在不远的未来了。

小冰李笛:AI相比人类创作者,不存在瓶颈期

小冰公司首席执行官李笛认为,有时候人们会过于高估人工智能在IQ方向的进展,却低估了人工智能在EQ方面蕴藏的巨大潜力。

那些出现在人类身边、与人类共存的「AI being」都应该有自己擅长和不擅长的领域,有自己的性格和观念,无所不知、无所不能的AI反而是面目不清的。

在迪拜世博会中国馆,正在展出AI画家夏语冰的一系列水墨画作品《山水精神》。

夏语冰除了创作能力也有着自己的面容、口音和创作观念,与另一位AI画家山东大哥完全不同。

李笛指出,如果要赋予AI创造力,它对不同事情的观念要有一致性,并反映在其所创造的东西上,才能让人类不感到违和。

当人工智能习得一定创作能力的时候,和人类创作者区别是什么?

第一,人类创作到了巅峰之后便开始滑坡,人工智能没有巅峰,要么是停滞的、要么会继续向上攀登,时间周期非常久。

第二,人类在同一时间只能专注地做一件事情,但人工智能是可以高并发的。

创造力只是小冰框架中的一小部分,最难的是如何赋予AI有趣的灵魂,真正和人类交流。

李笛看到人工智能在EQ方面蕴藏的巨大潜力,他相信我们这一代是与多样的人工智能生活在一起的第一代人类。

Rokid祝铭明:元宇宙更应专注虚实融合

Rokid创始人CEO祝铭明则在大会上探讨了AR智能眼镜行业的应用落地探索。

当前大家谈元宇宙,很多人谈的是创想与未来,Rokid关注的是技术落地能力,主要有5个方面:

感知——理解——协同——展现(光学技术、图形引擎)——数字资产/内容(创作、生产工具)

祝铭明介绍,Rokid是一家产品平台型公司,除了上述五种能力,还会考虑一些载体去做和大家进行交互,也根据自己思考分成了四个象限。

横轴代表以穿戴性、佩戴性为出发点去衡量,从专用场景到日常佩戴(从左到右)。

纵轴是以展现能力为一个衡量点去思考,从内容属性到工作属性(从上到下)。

第一层,感知能力,如半导体、传感器等技术。

第二层,关注在感知基础上如何理解世界,理解周边的环境、理解人、理解事。

第三层为协同,深度思考人和人、人和事物之间的协同关系,但不是创造虚拟世界,而是融合真实世界和数字世界。

第四层,视觉和感官层的展现能力,背后涉及光学、图形引擎、算法、空间引擎等技术。

最后,为数字资产(数字内容生产)。包括创作工具、生产工具、管理、安全等方面的能力。

过程中,祝铭明还强调,Rokid做人机交互有着不同的阶段,从最早指令型的人机交互,到后面图形化的所见即所得的人机交互,对人类越来越友好。

在分享的最后,他提出了自己对于元宇宙的思考。

如果元宇宙是一种发展方向,那我们觉得元宇宙不应该是局限于虚拟世界,如何将人与真实世界和虚拟世界做一个完整的融合,不应该割裂开,这是我们一直在主导的事情。

他看到了人机交互的巨大潜力,他相信在未来,真实世界跟数字世界将进行融合而非割裂。

亚信科技欧阳晔:5G把AI 能力投送到边缘


亚信科技首席技术官、高级副总裁欧阳晔带来了《5G网络助推边缘AI》的主题演讲。

以2006年AI第三次发展浪潮开始作为节点到现在的15年间,通信领域与AI相关的学术成果发表数量是之前15年的6.42倍。同时随着5G技术与业务的发展,云端智能需向边缘迁移。

通信技术作为数字化转型的基础设施,该如何利用5G通信技术把AI能力投送到千行百业的边缘触点?

欧阳晔博士介绍了5G网络投送AI能力到达边缘的三种模式:

  • 5G网络切片,可以理解成在现有的公有网络里构建一层专用的高速隧道。
  • 5G独立专网,企业搭建的私有网络。
  • 5G混合专网,专网与公网共享基站的模式。

AI能力投送到边缘后并不是就能直接应用到各种to B和to C场景,而是通过第五代移动通信边缘计算平台承载多种通用目的技术(如AI,数字孪生,数据治理与AIoT等)构建云边端协同整体方案。

随后欧阳晔介绍了基于五代移动通信边缘计算平台的智慧电厂、智慧工地和智慧园区3个典型场景案例。

欧阳晔指出通信和AI两个领域的发展相关性将越来越强,他相信在未来,应用层厂家、通信基础设施厂家以及运营商合作伙伴要共同努力,共同触及通信人工智能未来十年的发展。

京东何晓冬:对话本质上是博弈与决策,语言只是一种表现形式

京东集团副总裁、京东探索研究院常务副院长何晓冬则分享了多模态智能人机交互技术在复杂场景中的进化,以及技术落地给人类创造价值。

提到人工智能,往往会想到语音识别,图像识别、人脸识别、甚至机器翻译。

对话智能是种什么智能?某种程度上讲是一种融合性智能,前面提到单点基础技术都被融合在其中。

简单说,对话智能就是希望打造一个机器,它能够自如像人一样跟你对话交流沟通关怀,完成任务。

表现上看,它是种非常自然地交互方式;从技术上看,它需要感知智能、情感智能、认知智能、多轮对话管理。

何晓冬认为,对话即决策,本质上来说对话本质上是博弈与决策,语言只是一种表现形式。

接着,何晓冬介绍了在京东是如何迭代技术、让这项技术为更多用户所用、创造更多的价值。

简单来说,两种模式并行:打造前沿的技术能力,同时把前沿的技术推向千行百业应用落地。

刚刚过去的双十一,何晓冬团队通过智能人机对话系统打造的京东智能客服言犀,累计服务了超过7.4亿咨询量、16.5万的第三方商家,智能物流预约外呼超过了193万通电话,完成2.1亿次的质量检测,在整个京东的售前、售中、售后、物流各个环节实现了客服服务全链路的场景智能化。

大会现场何博士还给我们演示了智能人机对话系统服务成都顾客的真实案例~

而为了让机器人做得更好,京东还打造了五个维度的服务评价质量指数,来评价机器人和人之间的差距,展示京东在智能人机对话领域深刻的沉淀。

除了服务京东本身以外,他们的多模态智能人机交互技术还运用到更多行业当中去,比如政府的政务热线、金融行业的业务客服等。

展望未来五年,人工智能的三大支柱数据、算力、算法,都会得到进一步升级场景、系统、算力。

数据升级成持续运作的“活”场景数据;单点的算法进一步提升至综合性的AI系统,包括多算法互相协同、联合优化,这样才能打造真正端到端、高价值的AI系统。

商汤杨帆:AI算法下一个时代是端到端系统化整合

商汤科技联合创始人、商汤集团副总裁杨帆认为,赋能产业升级是AI的核心价值所在。

今天在各行各业都有对AI的刚需存在。

如工业检测分析的痛点是效率低、漏检率高和标准不统一、检测人员水平差异大等。城市管理分析中事故的偶发性高、分散,需要大量人力投入。

杨帆说,眼下AI企业会越来越面临一种「幸福的烦恼」。

幸福之处在于刚需大量存在,烦恼之处在于刚需碎片化、场景多样化,AI企业提供技术创新的成本,包括边际成本比较高,造成了AI产业进一步发展面临的供需匹配失调的问题。

要解决这些烦恼一方面需要有通用能力的基础设施为人工智能产业创新提供支撑,另一方面需要跨组织之间的联合和协作。

杨帆从在商汤做算法的经历出发,总结出算法的三个时代。

第一个时代像手工业,非常依赖个体科学家的个人水平。

第二个时代就是过去这5年,像工业化流水线,能够用更多系统把算法创造的各个环节整合打通,持续的规模化的生产技术创新。

下一个时代该是什么?杨帆认为技术创新会走向更加通用、低成本高效,就像工业流水线趋势是自动化智能化。

从底层基础设施,到硬件,再到软件和上游应用,形成端到端系统化模式,把各个环节进行更加标准化的定义以及有机组合。

端到端整合可以带来更高的安全性以及更低的网络时延,同时把算法打造成像乐高积木块一样,提供给产业内的大家共同去打造有价值的应用。

杨帆看到了行业刚需当下没有被很好的满足,他相信平台化、规模化、低成本高效率的工具体系,会让整个产业技术创新走得更快。

思谋刘枢:AI技术正在成为一种新型生产要素

思谋科技联合创始人兼技术负责人刘枢在大会分享了思谋科技如何用AI推动制造业数字化智能化转型。

人工智能在推动经济发展的同时,也在创造虚拟劳动力,去做人类不想做、做不好、不能做的事情,同时当人工智能在很多行业落地的时候,可以拉动其上下游协同发展和创新。

因此作为两年即长成独角兽的思谋科技,认为“人工智能技术正在成为一种新的生产要素”。

埃森哲曾预测到2035年,人工智能可以将年经济增长率额外提升1.6%,人工智能作为单独技术将额外带来8千亿美金增长,而如果作为生产要素去评估,将带来6万亿美金的规模增长。

再来看制造业的发展,总共经历了三个阶段:自动化-信息化-智能化,如今智能制造蕴含着巨大发展机遇。

智能制造,关键在于智能——即让制造拥有大脑,自动化只是手臂,把决策变为现实。但实际落地过程中,思谋科技遇到了些有意思的问题,这与熟悉的自然场景有许多不同。

首先,数据极度短缺。在工业场景里面,很多时候每一种缺陷的图片数量都达不到10。

第二,工业验收要求非常高。以手机为例,如果要求手机成品良率99%,假设一台手机含200个零件,那么每个零件良率都需达到99.99%。

第三,被检形态非常多。常用器件就可能有成百上千种不同的形状。

第四,缺陷难以区分。

刘枢认为,只有当系统可以自动实现算法组合和部署,人类只需要少量定制化算法设置的时候,才有可能实现AI跨领域规模产业化。

如果没有自动实现算法组合和部署的系统,在高端制造业实现AI全面产业化会非常艰难。

以智能手机为例,零件供应商平均来讲有400个,每个供应商有五个制程,每个制程又有15条产线,如果一条产线都做一个模型,大概要做3万个模型。

再放眼全行业前五的品牌,每个品牌6款产品,则需要90万个不同的算法模型,这其中还不算第二年、第三年的软硬件升级迭代。

为了达到这一目标,思谋科技研发了AI算法平台SMAP,以及沉淀了AI Know-How的DataFlow系统。

最后,刘枢还分享了智能制造的核心原则:普适性,计算为先和永不间断的学习。

当AI系统设计能够自动化,当AI部署和运营能够自动化的时候,就一定能够实现新一代智能产业的变革。

数牍科技蔡超超:隐私计算,构建下一代数据流通底层的关键设施

数牍科技联合创始人兼CTO蔡超超分享的主题是《隐私计算构建下一代数据流通底层的关键设施》。

刚才我们提到智能驾驶场景,就和隐私计算有很大的相关性。

智能汽车在运行过程中产生大量图片、音频甚至位置数据,都涉及到用户的隐私。这些数据的高效开发利用需要在保护数据安全的情况下进行,需要用到隐私计算。

隐私计算是一种在数据不可见的前提下,让信息进行价值流通的工程和技术体系,涉及多方安全计算,联邦学习、可信执行环境、差分隐私、同态加密、区块链等多种技术。

蔡超超同时认为隐私计算体系不是一个单一的系统,它其实是一个网络,一个底层平台,包含了不同的参与方。

每个参与方的主体可能是人、是车或其他设备,这些主体都会有自己的ID,比如身份证、电话号码、设备号,在数据合作之前需要有共同的语言把ID有效统一对齐起来,才能进行安全的数据协作。

基于隐私计算的ID系统可以做到匿名化、原始ID和敏感信息不可追溯、不可还原。

接下来,让多方在不交换原始数据的前提下进行安全合规的数据协作,应用于反欺诈、反洗钱、精准营销、联合风控等一系列场景。

蔡超超看到了有数据价值交换的地方就有隐私计算的需求,他相信隐私计算平台发展过程中需要注重安全可靠性、有完整的数据科学应用体系以及工业级落地能力。

智驾周圣砚:以规模化迎接智能驾驶平权时代

智驾科技创始人兼CEO周圣砚在大会上分享了智驾视角下自动驾驶产业以及今后发展是如何的。

一开场,周圣砚就举了一个小例子:如果问消费者,什么品牌汽车做的比较好,消费者能轻易列出答案。但如果问自动驾驶有哪些比较好的公司,可能大多数消费者都没办法答出。

为什么会有这样的现象?如果回顾互联网行业的发展,就会发现它之所以能蓬勃发展,是因为覆盖了最广大用户群体。

回顾过去几年自动驾驶的发展一直非常曲折,原因在于大家一直在技术路径和商业模式上产生强烈的争议。如今这些争议依旧存在,但同时也确实感受到自动驾驶正在实现并产生社会的正向价值

SAE把自动驾驶从工程学的角度分了L0-L5不同驾驶等级,智驾科技MAXIEYE今天从解决问题的角度重新分解自动驾驶等级。

首先,即第一个等级,需要解决的是安全问题,比如车辆的紧急制动系统。

第二个等级,驾驶过程中的舒适性问题,比如高速公路实现的全速巡航系统。

第三个等级,解决出行效率问题。智驾科技MAXIEYE认为在结构化道路,比如城市道路和高速公路,可以实现点到点的自动驾驶功能。

第四个等级,优化交通能源的问题,比如干线无人物流。前三个等级叫做人机共驾,最后一个等级才叫做无人驾驶。

最后,他希望与行业一起迎接智能驾驶科技平权时代的到来。

从市场维度,产品全面下探最广泛的5-15万元车型,将覆盖最广大的用户群体;从消费者维度,提供消费者用得起愿意用的智能驾驶产品;从产业链维度,全行业开放共创,建立行业共识和技术协同,全产业链去共同打造智能驾驶科技平权时代。

周圣砚看到自动驾驶需要覆盖更广大用户群体,他相信未来AI将以数据驱动方式助力智能驾驶系统越用越聪明。

自动驾驶圆桌论坛:量产、安全,变局时刻

大会的最后阶段,自动驾驶行业大佬们围绕「量产」展开了激烈讨论。

先来介绍一下各位嘉宾:

腾讯交通平台部总经理、自动驾驶总经理苏奎峰

过去一年腾讯从自动驾驶测试工具链,以及智能联网示范区、智慧高速等车路协同解决方案两个方面助力产业发展。

无人驾驶公司驭势科技联合创始人、首席系统架构师彭进展

驭势科技开展了无人车在多个场景的商业化运营,拥有在机场、工厂等场景几百台车7x24小时、365天持续不断的运营能力。

无人卡车公司主线科技CEO张天雷

主线科技专注于自动驾驶卡车,在几个港口物流枢纽还有京津高速、京沪高速上运行的车辆规模有150辆车左右,每天都在持续地运行。

那么开始第一个话题,2021年怎么就成了自动驾驶量产集中的一年?

首先三位嘉宾都认为政策很关键,三个团队创业之初都获得了资金支持,来自国家自然科学基金。

另外今年从北上广深到武汉、长沙、无锡再到衡阳,无论一线还是二三线城市都开始积极推动无人驾驶落地。

张天雷提出第二个因素:场景

张天雷觉得物流场景尤其是封闭的完全无人的,还有高速以及一些城区限定区域内的场景,从今年开始包括到明年年底很有可能有很多批量的应用出来。

彭进展的角度是技术进展,无人驾驶如果不能把安全员拿掉,就体现不出真正的价值。

只有真正把安全员拿掉,你的客户和合作伙伴才会相信这件事真的能成功。

最后苏奎峰总结发言,政策、场景和技术全都交汇在今年这一点,无人驾驶量产终于到来。

第二个话题:量产之后,大众都「看见」了自动驾驶,未来行业会面临哪些新的挑战?

虽然问题比较宽泛,不过三位的表达不约而同的集中在了安全这个点。

苏奎峰提到,只要自动驾驶的量产规模大了,原本的一些小概率事件也会变成常发事件,这不代表技术不好,但确实有许多长尾的问题需要预见和克服。

他强调:

我们在安全性、稳定性上要有敬畏心。

彭进展认为无人驾驶在安全上的优势在于实时性,实时监控反映,实时通过AI去控制。

虽然理论上可以计算出无人驾驶事故概率就是远远低于人类驾驶,但实际中还需要做进一步验证。

每天都会发生人为因素引起的交通事故,但对于大众来说并不算什么新闻。期待有一天,大众对自动驾驶出事故也有一个平常的心态。

张天雷则指出没有一个系统能够百分之百的保证总是正常运行,我们要做的是把出错的代价降到最小,核心的问题是不要有人员的伤亡。

在任何时候,自动驾驶的安全性永远排在第一位。为了系统的安全性,付出再多代价也是需要的。

圆桌讨论的最后一部分,是每人分享一件行业中最感到意外的事。

苏奎峰:

到头来是新能源加速了自动驾驶落地。

彭进展:

震撼最大的是行业真的做到把安全员拿掉了。

张天雷:

特斯拉投入巨大精力做数据驱动,造了世界上排行第五的超算机群来训练模型,这是中小型国家都做不到的。

从那时开始,大家明白了一个道理,自动驾驶是个军备竞赛。

因为看见,所以相信

过去的一年,是复杂变化的一年。

但前沿科技始终是社会发展的重要动力,也蕴藏着无比的机遇。AI大模型、自动驾驶、生物计算等领域正在加速改变世界,前沿计算、新型储能等方向新探索不断涌现。

与此同时,前沿技术的落地也愈发如火如荼。新技术、新产品让我们的生活越来越好,越来越有趣。

不过一切也并非一帆风顺。

量子位创始人兼CEO孟鸿表示,前沿科技的发展总是起起伏伏,发展的范式会改变,上升的道路会改变,但前进的趋势不会改变。

今年到场的来宾,都是因为对前沿科技有一份坚定的相信,进而选择在这个方向上不断推动世界前进。

整场大会下来可以看到,他们今年交出的商业化落地答卷,在更深入现实的地方被交出。

而这也只是今年诸多技术创新案例中的一隅。

作为人工智能年度最佳落地参考,「2021人工智能年度评选」结果也已揭晓。在过去2个月时间里,共有数百家科技企业、机构和个人报名参与评选。

最终评选出50大领航企业、20大最具价值创业公司、30大创业领袖、20大技术领袖、10大最佳产品以及10大最佳解决方案等在内人工智能领域年度奖项。

这些无一不在印证本次MEET智能未来大会主题:因为看见,所以相信。

希望让更多人看到前沿科技的进展和落地,让更多人进一步相信前沿科技背后蕴藏的巨大价值。

那么这一年,你看见了什么?从而又相信着什么?

Ps,也许量子位最新发布的「2021人工智能年度评选」,可以给你参考,链接在此:https://mp.weixin.qq.com/s/E3wcXr3PA0uZAZ1N-lgThg。

Pps,如果想回顾精彩内容,回放链接在此!
微吼:http://live.vhall.com/127740714
微博:https://weibo.com/l/wblive/p/show/1022:2321324707747747987514
百家号:https://live.baidu.com/m/media/multipage/liveshow/index.html?room_id=5008651533
CSDN:https://live.csdn.net/room/wl5875/tVtNmdeX
斗鱼:https://v.douyu.com/show/85BAvqrrERB7G4Lm?ap=1

— 完 —

量子位 QbitAI · 头条号签约

关注我们,第一时间获知前沿科技动态



是现代量子化学的奠基人,也是分子生物学研究的开拓者之一。凭借对量子力学原理和分子结构的深刻理解,少壮得志、堪称天才的鲍林以他非凡的删繁就简和构建模型的能力,一次次在化学和生物学难题上攻城略地,风光无两。然而盛名之后的他,却因执迷维生素C神话而成为医学史上的笑谈,其人生的最后25年令人唏嘘不已……


撰文 ∣ 何笑松(加州大学戴维斯医学院退休教授)


莱纳斯·鲍林(Linus Pauling)出生于1901年,是20世纪最伟大的化学家之一,因其对化学键及分子结构的一系列重要研究成果获得1954年的诺贝尔化学奖。鲍林也是20世纪五、六十年代最著名的和平主义活动家,他坚决反对核武器及一切形式的战争,并因此在1963年获得诺贝尔和平奖。鲍林因此成为有史以来唯一单独获得两项诺贝尔奖的个人。

但从1960年代开始,头戴两顶诺奖桂冠、正值事业和名誉巅峰期的鲍林却一步一步地成为维生素C的最有力推手。这一转变是如何发生的?

维生素C神话的第一次登场


鲍林的维生素情结始于1941年,当时年仅40岁的鲍林得了肾病。在一位著名医生的建议下,鲍林接受了当时少有人用的低蛋白无盐饮食疗法,辅以维生素补充剂,病情得到控制。这一亲身经历,为鲍林以维生素治病的观念打下了根深蒂固的基础。

1950年代中期,作为当代最伟大化学家之一的鲍林,逐渐形成了一套关于人类健康的综合理论。他相信人的生命可被视为无数化学反应的总和,其中最重要的是酶催化的产生能量的反应,导致细胞繁殖和遗传复制的反应,大脑和神经中的电化学反应,以及抗原-抗体反应。如果这一切反应都顺利进行,就意味着健康;哪一项反应受阻或者停止了,就意味着疾病。要想实现理想的健康状态,就必须使所有的化学反应都在最佳状态下运行,也就是所有的化学分子,包括营养成分、催化剂及反应产物,都处于适当的平衡状态。鲍林为这种平衡造出一个词汇“正分子”,意指“正确的分子,正确的数量”,以此为基础的医学就是“正分子医学”,它着眼于将病人所需要的重要分子补充到最佳浓度,以消除疾病。

1966年3月的一天,65岁的鲍林在纽约接受了一项科学成就奖。他在颁奖仪式上发表获奖感言时,提到希望自己再活25年,以便看到几项科学研究的结果。回到加州后,鲍林收到一封信,来自一个名叫斯通(Irwin Stone)的陌生人。斯通参加了纽约的颁奖仪式,听到鲍林的愿望,于是他写信建议鲍林像他一样,每天服用3000毫克(3克)维生素C,这样不仅再活25年不成问题,而且可能还要长的多。

被鲍林称为“生物化学家”的斯通是何许人?他在美国洛杉矶的一所脊椎推拿疗法学院学过两年化学,后来从加州一所名为“唐斯巴赫大学”,未经美国联邦政府教育部认证的野鸡函授大学得到一个博士学位。斯通从来没有发表过任何一篇科研论文,而著作等身的鲍林居然听信了他的建议,开始服用大剂量维生素C,从每天500毫克,1克,直到18克,超过了美国食品和药品管理局(FDA)推荐的成年人每日摄入量的200倍。鲍林发现这果然有效,他宣称从来没有自我感觉如此健康,如此精力旺盛,连年年令他备受折磨的感冒也不太发生了。1969年,鲍林开始通过媒体宣传,医生应该多多鼓励公众服用大剂量维生素C。

针对鲍林的言论,有一位协助FDA制定维生素C推荐摄入量的临床营养学家致信鲍林,要求他提供证据。鲍林的答复是在1971年出版的《维生素C与普通感冒》,书中汇集了他找到的关于维生素C的健康效应的研究结果,声称对于大多数人,每天服用1克维生素C,可以将感冒的发病率降低45%;而对有些人来说,剂量可能还要更大。一旦感冒的症状出现,就应该每小时服用半克至1克维生素C,连服几个小时;如果这样的剂量还不见效,那就再加大到每天4至10克[1]

鲍林的这本新书立即登上了全美畅销书排行榜,激起了巨大反响。几年不到,就有四分之一的美国人遵从鲍林的建议,服用巨量维生素C。1976年,这本书再版后改名为《维生素C与普通感冒及流感》[2],书中推荐的剂量更高了。

不仅如此,鲍林在书中还建议为了保持“最佳健康状态”,应对感染及其它压力,多数人每天应服用至少2300毫克维生素C。在他后来出版的又一本面向大众的畅销书《怎样活得更长更好》[3]中,鲍林声称由于不同个体之间生物化学上的差异巨大,维生素C的每日最佳摄入量可以少至250毫克,多到20克以上,这样才能达到许多哺乳动物肝脏内自行合成后释放到血液中的维生素C 的水平。这是多少维生素C?以山羊为例,一只健康的成年山羊每天能合成13克维生素C,遭到压力时还要高得多!

鲍林对他的理论身体力行,自称长年每天服用12克维生素C,出现感冒症状时更是增加到40克!尽管主流医学界的绝大多数医生和营养师反对鲍林的理论,并且指出长期服用巨量维生素C可能引起慢性腹泻及肾结石,千千万万的美国人还是狂热地追随鲍林的倡议,以身试药。

维生素C与感冒

现代医学的主流是循证医学,它强调任何医疗决策应该以来自科学研究的最佳证据为基础,同时结合医生的个人临床实践经验,并且考虑病人的价值观和愿望。鲍林宣称的巨量维生素C对于预防及治疗感冒的奇效,究竟有何科学证据?

任何科学证据的确立,必须服从一套严格的规则,也就是科学方法。简单地说,这是一套精心设计、可以区分因果关系和巧合关系的逻辑系统,利用它对有效的实验进行分析,才能为特定的医学问题提供科学答案。必须强调的是,并非所有的实验都符合条件。有效的实验必须是设计正确、数据采集可靠、统计分析无误、结果解释合理。有效实验的一个重要标志是能被其他人重复进行,而且得到相同结果。

关于维生素C与感冒的关系,关键的问题是两个:1. 大剂量维生素C能否预防感冒发生?2. 大剂量维生素C能否减轻感冒症状,缩短感冒病程?从1930年代维生素C被发现,特别是实现工业化生产后,就有医学研究人员多次研究过它对感冒的作用。大多数研究的结论是补充维生素C不能预防感冒;对于治疗感冒,维生素C的作用充其量是略微减轻症状,而且并不需要鲍林提倡的高剂量[4]。鲍林的《维生素C与普通感冒》一书出版后,由于它的巨大影响,同时出于对鲍林的尊重,又有美国、加拿大及荷兰的研究人员进行了一系列研究,分别试验了不同剂量维生素C对于预防和治疗感冒的作用,结论还是毫无效果。

可是鲍林坚定地认为,根据他对同样的试验结果作出的不同解释,他是正确的,其他人都错了。他还提出每个人可以用如下方法自行确定合适的维生素C剂量:如果每天吃1克,一冬天得了两次或者三次感冒,那么就应该加大剂量试试;如果感冒的次数少于预期,那么就可以相信是维生素C起了作用。

遗憾的是,这距离真正的科学方法何止十万八千里。不妨试想以下几种可能发生的情况:

其一,你记错了自己感冒的次数,这将造成数据采集的错误。

其二,你去年在医院工作,得过三次感冒。今年退休在家,同时开始吃维生素C,只得了一次感冒。你相信这是因为吃维生素C预防了感冒,但更大的可能性是由于环境变化,你被感冒病毒感染的机会少了。如果是这样的话,你吞下的维生素C和少患感冒就只是巧合。

其三,你吃着维生素C,同时得了一场轻微的感冒。由于你对维生素C的信仰如此坚定,因此下意识地决定不予理会,这就是心理作用造成的安慰剂效应。

科学实验的设计必须能够避免这些可能的误差。为了防止记忆错误,对实验对象必须密切跟踪随访。为了消除巧合的影响,实验对象的数目要足够大,随访期要足够长。为了避免安慰剂效应,实验必须采用双盲法。具体做法是,募集一批受试者,随机分派到实验组和对照组,以确保两组人的遗传背景、生活环境等情况尽可能相似。每一个受试者本人及其家人,以及为受试者提供试验药物的研究人员,都不知道受试者被分在哪一组。有些研究甚至只用同卵双胞胎作为受试者,将其中一人分入实验组,另一人分到对照组。尤其是同卵双胞胎儿童,不仅遗传基因完全相同,而且通常生活于同一家庭环境,对于临床医学实验的价值更高。研究人员给实验组每天服用不同剂量的维生素C,对照组服用外观、味道等与维生素C相似的安慰剂,然后跟踪观察一段时间,比较两组的流感发病率和发病后的症状严重程度和病程长短。

感冒临床实验的一种做法是直接给自愿受试者接种感冒病毒。这种策略的好处是所有的受试者都接触到相同的感冒病毒,发病率高而且病情较为一致,所需要的受试者人数较少。1967年和1973年,先后有研究人员进行了这样的实验,给实验组每人每天服用3克维生素C,对照组服用安慰剂,所有受试者在鼻腔接种感冒病毒。结果发现,两组所有的人都得了感冒,严重程度没有差别。

另一种策略是观察自然发生的感冒病例。这样的研究在各国进行过多次,每次涉及的受试者少则几百人,多则几千人。结果如何?没有一次试验发现维生素C能显著降低总的感冒发病率。有些试验发现维生素C的确能略微减轻症状,缩短病程(例如从平均5.8天缩短到5.5天),问题是这样几个小时的差别有什么实际意义吗?

1975年发表的一项研究结果十分有趣,值得一提[5]。这次实验历时9个月,一共有三百多名受试者参加,都是美国国立卫生研究院(NIH)的工作人员。受试者被随机分为四组:第一组自始至终每天服用安慰剂;第二组在感冒发生前服用安慰剂,发生感冒后每天服用3克维生素C;第三组在感冒发生前每天服用3克维生素C,发生感冒后改为安慰剂;第四组在感冒发生前后每天分别服用3克和6克维生素C。研究从九月份开始,到次年五月份结束,包括了感冒高发的秋、冬、春三季。所有受试者都被告知这是一项采用双盲对照法的临床试验,目的是研究维生素C对预防和治疗感冒的作用,并且签署了知情同意书。

这次实验的设计百密一疏,出了一个纰漏:研究人员为了赶在秋季到来时开始实验,没有把用胶囊封装的安慰剂的味道调成和维生素C一样的酸味。在研究过程中,陆续有一些受试者中途退出,尤其是服用安慰剂的对照组,退出的比例特别高。研究人员对此顿生疑窦,于是在9个月的研究结束后进行了一项问卷调查,发现大约一半的受试者禁不住好奇心的诱惑,打开胶囊,凭着药粉的味道,正确地猜出所吃的是维生素C还是安慰剂。分到安慰剂对照组的一些受试者觉得自己得不到服用维生素C的好处,干脆中途打了退堂鼓。

由于有一半受试者不符合双盲法的要求,研究人员只能将猜出自己吃了什么药的受试对象挑出来,单独进行分析。得到的结果令人吃惊:不知道自己分在哪一组、也就是符合实验设计的双盲法的受试对象,得了感冒后如果服用安慰剂,平均病程是6.3天,如果服用维生素C,平均病程是6.7天,表明维生素C对缩短病程无效。而那些猜出自己分在哪一组的受试者,得了感冒后如果服用安慰剂,平均病程是8.6天,如果服用维C,平均病程只有4.7天。表面看来,似乎维生素C将病程缩短了3.9天。但是如果将知道自己吃了什么的受试者与对此不知情的受试者比较,如果吃的是维生素C,前者的平均病程比后者短了2天;如果吃的是安慰剂,前者的平均病程比后者长了2.3天!这一结果歪打正着地表现了安慰剂效应的巨大影响:如果你相信吃下去的东西——维生素或者其它补品——有益健康,你就可能感觉它的确有效;如果你相信自己吃的是毫无治疗作用的安慰剂,你甚至可能感觉病情变得更糟(称为“反安慰剂效应”),哪怕药物的实际效果,无论是维生素C的正面效果还是安慰剂的负面效果,根本就不存在!

1986年,澳大利亚悉尼大学的教授特拉斯韦(Truswell AS)在《新英格兰医学杂志》发文,回顾总结了自1970年以来进行的22次关于维生素C与感冒的随机双盲法临床研究结果。有12项研究发现维生素C对预防感冒发生、减轻症状、缩短病程无效。5项研究发现维生素C对预防没有作用,对减轻症状有轻微作用,但在统计学上的差别不显著。其余5项研究发现维生素C没有预防作用,但可以缩短感冒病程,虽然程度有限但在统计学上的差别是显著的。特拉斯韦教授的结论是:“维生素C对于预防感冒显然没有作用。”他同时承认,“有一些证据表明,维生素C对治疗感冒有微弱的效果,但是……每天250毫克与1000毫克或者4000毫克的效果是一样的。”[6]250毫克维生素C是什么概念?两杯橙汁中所含维C就有那么多。如果只是想用维生素C来减轻感冒症状,犯得着冒着腹泻和其它严重副作用的风险,遵照鲍林的指示每天吞下几克维生素C药片吗?

但是鲍林认为,巨量维生素不仅对感冒有奇效,还能治疗癌症。

维生素C与癌症

1970年代,鲍林与一名英国肿瘤外科医生卡梅隆(Ewan Cameron)合作,研究静脉注射或者口服巨量维生素C对癌症的治疗作用。二人在1976年和1978年发表了两篇论文[7, 8],报告了在100名晚期癌症病人中的试验结果:接受每天10克巨量维生素C治疗后,病人的平均存活期是1000名未服用维生素C病人的3至4倍。两篇论文都发表在《美国科学院院报》。这是一份影响因子很高的学术期刊,但通常并不刊登有关临床医学试验的论文,而且有时受人诟病的一点是,科学院院士本人在《院报》上发表的有些论文,没有经过和非院士作者的论文同样严格的同行审稿过程。

除了治疗晚期癌症,鲍林还提出,服用维生素C可以预防癌症。1979年,他在一篇文章中宣称“我目前的估计是,从预防性地服用维生素C做起,单靠维生素C就可以把癌症的发病率和死亡率都降低75%。”[9]

NIH 癌症研究所的癌症治疗临床试验分部主任杜伊斯(William DeWys)检查了鲍林和卡梅隆的实验设计,发现存在严重的缺陷[10]。维生素C治疗组完全由卡梅隆的病人构成,对照组则是其他医生的病人,这两组病人的选择标准不一致,所以也就没有可比性。实验组的病人存活期长,很可能是由于这组病人在实验开始前的病情就比对照组轻。

为了验证鲍林和卡梅隆所发表的结果,美国梅奥医学中心在1978年进行了一项前瞻性双盲对照试验, 受试者为晚期肺癌或消化道癌症患者,所有病人都经过肿瘤组织活检,证实其病情已经不再适合任何手术、化疗或放疗。受试者被随机分为两组,实验组每天口服10克维生素C,对照组口服味道和维生素C相似的安慰剂。试验结果于1979年以“高剂量维生素C疗法对晚期癌症无效”为题,在《新英格兰医学杂志》发表,文中报告实验组和对照组的平均存活期都是7周左右,存活最久的一个病人来自对照组[11]

梅奥中心的研究结果发表后,鲍林投书《新英格兰医学杂志》,声称梅奥团队的研究对象都接受过化疗,免疫功能已经受损,因此不能从维生素C疗法获益。尽管梅奥团队先前已经做过研究,知道所用的化疗对免疫功能的影响并不严重,但他们对鲍林的批评,以及其他研究人员提出的建议还是认真对待,又进行了两次试验,其中一次所用的受试者没有接受过任何化疗[12, 13]。前后三次试验一共包括三百多名病人,三次试验的结果一样,实验组和对照组的平均存活期没有显著差别,高剂量维生素C疗法对晚期癌症无效。

可是此时的鲍林已经接受不了任何与他的观点不一致的研究结果,哪怕这样的结果来自他最信任的助手。

1973年,鲍林在北加州斯坦福大学附近创建了“正分子医学研究所”,自任所长兼董事会主席,他的弟子罗宾逊(Arthur Robinson)任副所长。罗宾逊博士是鲍林在加州大学圣迭戈分校任访问教授时物色到的一名优秀学生,后来长期担任鲍林的助理,辅佐他的研究工作。研究所成立一年以后,改名为“鲍林科学及医学研究所”,罗宾逊成为正所长。研究所的主要课题就是为鲍林的维生素C理论提供实验证据。

1977年,罗宾逊用小鼠进行了一项实验,研究维生素C对癌症的作用。所用的是一种特别的小鼠,没有毛发,皮肤裸露,受紫外线照射后容易诱发皮肤癌。他给一半小鼠喂食大剂量维生素C,按体重换算相当于人类的每天5至10克,另一半不喂维C,作为对照。结果发现,维C不仅没有降低癌症发病率,反而使皮肤癌的发病率提高了一倍!不仅如此,对于已经形成的肿瘤,每日相当于1至5克人类剂量的维C可以促进,而不是抑制肿瘤生长,只有将维C的剂量加大到相当于每天100克的人类剂量时,才能抑制肿瘤的生长,而这样的剂量已经接近维C对小鼠的致死剂量了!

罗宾逊知道鲍林夫妇二人长年服用巨量维生素C,这不由得令他担忧。岂料鲍林拒绝承认他的数据,而且不顾罗宾逊已经追随他16年的情谊,下令董事会将罗宾逊解雇,他的实验小鼠被杀死,实验记录被封存,其中一部分被销毁。鲍林还公开宣称罗宾逊的研究工作不够专业,数据不可靠。罗宾逊忍无可忍,一纸诉状将鲍林和他的研究所告上法庭。1983年,诉案以庭外和解告终,罗宾逊得到57.5万美元的赔偿,其中42.5万作为他所遭口头及书面诽谤的补偿[14]

维生素C与其它一些维生素之所以是维持我们身体健康所必需的营养成分,是由于它们除了具有协助细胞能量代谢的辅酶作用外,还有抗氧化剂的作用。吃进体内的食物在细胞内通过氧化反应燃烧后才能产生能量。其它一些进入体内的有害物质也是通过氧化反应转化为无毒的产品被排出体外。但是氧化反应能产生一些称为自由基的分子,它们因失去电子而带有正电荷,能与带有负电荷的细胞膜、DNA、蛋白质等成分结合,对这些细胞组分造成损害,包括可能致癌的基因突变。自由基还能损伤向心脏供血的血管,诱发冠心病。而维生素C以及维生素A、E,Ω-3 脂肪酸(鱼油的主要成分)等具有抗氧化剂作用的分子能够给自由基提供所缺少的电子,消除它们对细胞组分的损害。这就是一般认为多吃水果蔬菜鱼类等健康食品有助于延缓衰老,防止癌症和心血管病的一个原因;也正因如此,抗氧化功能成为许多保健营养品最大的卖点。

鲍林关于大剂量维生素C抗癌的理论,其逻辑倒也清楚:既然食物中的维生素C等抗氧化剂有助于防癌,那就是多多益善,应该大量补充。但他忽略了一个重要的事实:自由基既能对细胞造成损害,又是免疫系统消灭癌变细胞以及致病微生物的一大利器。维持健康的关键在于保持细胞内的氧化与抗氧化反应的平衡,长期服用超大剂量的抗氧化剂有可能破坏这种平衡,造成严重后果。在罗宾逊之后,又有各国研究人员进行了多次大规模的调查和研究,对象总数超过20万人,发现服用巨量维生素(包括维C)的确增加了罹患多种癌症的风险[15]

维生素C与心脏病

鲍林在他1986年出版的《怎样活得更长更好》一书中宣称,巨量维生素能“改进你的总体健康状况……增加你生活的乐趣,帮助控制心脏病、癌症及其它疾病,延缓衰老过程。”

针对维生素C与心脏病的关系,1992年,91岁高龄的鲍林与德国医生拉思(Matthias Rath)合作,在一份不受主流医学界承认,不被美国国立医学图书馆的MEDLINE 数据库收录的刊物《正分子医学杂志》发表了一篇文章,题为《一项终将导致消除人类致命心血管病的统一理论》 [16]

根据鲍林的这个理论,大约4千万年前,人类的远祖生活在温暖的热带地区,以植物为主食,各种植物性食物中富含维生素C,不需要在体内自行合成,结果导致一个合成维C的关键酶——L-古洛糖酸内酯氧化酶的基因发生突变,永远失去了像其它动物一样自行合成维C的能力。后来随着栖息地的迁移以及地球气候变冷、冰川期到来,来自食物的维生素C营养成分大幅度减少,致使坏血病的发病率提高。而坏血病的主要特征是胶原蛋白、弹性蛋白等结缔组织的主要成分的合成途径受损,使血管壁的强度减弱,容易破裂,导致出血及死亡。坏血病因此成为冰川期对人类祖先的进化影响最大的自然选择压力。这种选择压力使得能促进动脉粥样硬化的遗传变异成为有利于生存的因素,因为动脉粥样硬化是由于血液中的低密度脂蛋白(坏胆固醇)、脂蛋白a等成分在血管内壁沉积,形成斑块,增加了血管壁的厚度及强度,从而减少了坏血病造成的出血和死亡。换句话说,动脉粥样硬化是在人类祖先的进化过程中,通过自然选择得到的在缺乏维生素C时有利于生存的性状;动脉粥样硬化造成的心血管病,则是这种选择给今天的人类留下的健康隐患。

尽管今天的人类已经不幸地继承了那些能促进动脉粥样硬化的遗传基因,好在鲍林告诉我们,维生素C是对抗不利遗传基因的救星,它几乎对已知的任何一种心血管病的危险因素都有补救作用。维生素C能在代谢水平上抑制脂蛋白a的合成,促进胆固醇转化为胆酸后经由胆管排出,从而减少血管斑块的形成和心血管病的发生。但是要使维生素C的功效得以充分发挥,补充的量就必须达到人类的远祖在失去合成维C能力之前,体内每天能制造的数量——10至20克。鲍林在文中没有说明这个数据从何而来,大概是根据已知的今天一些哺乳动物合成维生素的能力推测的。

文章的结尾总结道:“鉴于缺乏维生素C是人类心血管病的共同原因,补充维生素C就是这一疾病的普适疗法。现有的流行病学及临床研究结果相当可信地证明了这一点。这一理论在临床上进一步证实后,心血管病将不再是我们这一代以及未来人类的死亡原因。”

《统一理论》一文发表两年后,1994年8月19日,鲍林因前列腺癌与世长辞。去世之前他接受过放射线治疗。鲍林宣称长年服用的维生素C将他的癌症推迟了20年。这是个无法验证的假设,自然不足为凭。

鲍林在他这最后一篇重要文章中言之凿凿地提到的实验证据是什么?

1990年,鲍林和拉思曾在《美国科学院院报》发表一篇研究论文[17],报告豚鼠的饲料中如果缺少维生素C,就会造成动脉粥样硬化,而且血管斑块中含有脂蛋白a。如果给豚鼠按每千克体重每天补充40毫克维生素C(大约相当于2至3克的人类剂量),就可以防止脂蛋白a在血管壁沉积、造成粥样硬化。鲍林据此推断,维生素C对人类也有同样效果。

虽然豚鼠和人一样不能自行合成维生素C,豚鼠毕竟不同于人类。不管鲍林的统一理论看上去多么漂亮,他所描绘的利用维生素C来永远消除致命心血管病的美好愿景能否实现,归根结底还要靠人体研究来检验。

2016年,美国康涅狄格州立大学的两位学者回顾总结了自1993年以来发表的关于维生素C与心脏健康的研究结果,包括7项观察性的流行病学调查和6项随机双盲法临床试验。得到的发现是:维生素C的缺乏与心血管疾病死亡率的提高相关;但是对于补充维生素C能否降低心血管病的发病率和死亡率,证据依旧不足。许多研究没有发现补充维生素C和心血管病的任何关联,少数研究发现补充维生素C略有益处,但值得注意的是,也有一些研究发现维生素C补充剂增加了心血管疾病的风险,而且剂量不过是每天1克,只有已被确认的可耐受最高摄入量(2克)的一半。鉴于现有的研究结果不完全一致,两位作者审慎地建议,对此还需要做进一步的研究[18]

九泉之下的鲍林如若有知,还有耐心继续等待吗?

鲍林的遗产

鲍林的一生为人类留下的遗产是极为丰富而复杂的。天才的鲍林是将量子力学引入化学、将分子结构引入医学、将蛋白质的氨基酸组分变化引入进化生物学的第一人。他是反对核武器和一切形式战争的坚强斗士。而晚年的鲍林则成为年销售额320亿美元的维生素保健品行业的精神教父。

是什么因素使得曾经依靠严谨深入的研究和思辨,脚踏科学证据的基石步步攀登,征服了一座又一座科学高峰的鲍林,转变为面对科学证据仍拒绝承认错误、一意孤行的鲍林?

一种解释是鲍林在他的科研生涯中,学术上曾经多次遭遇到强烈的反对意见,而最终被证明为错误的都是对方。久而久之,他终于形成了自己永远正确的潜意识。

鲍林的同行,同为诺贝尔化学奖得主的佩鲁茨(Max Perutz)在高度赞誉鲍林的成就后,这样评论道:“(维生素C)竟然成为鲍林最后25年中关心的头等大事,使他作为化学家的崇高声望受损,实在是个悲剧。这也许是由于他最大的弱点:虚荣心。换成爱因斯坦,假如有人对他提出不同意见,他会仔细思考,发现自己错了,会很高兴地改正,因为这使他避免了失误。而鲍林则永远不会承认自己的错误。” 佩鲁茨随后披露,他读了鲍林关于蛋白质α-螺旋的论文后,发现其中一处计算有误,就向鲍林指出,还以为鲍林会为此高兴,岂料鲍林不但不表示感谢,反而愤怒地加以反击,“因为他绝不能忍受别人发现了被他自己遗漏的东西。”[15]

1954年诺贝尔化学奖颁奖仪式的当天晚上,几百名瑞典大学生手持火炬游行,祝贺鲍林成为新的诺奖得主。鲍林应邀发表演说:“作为老一辈人,我想给你们一点忠告,应该怎样对待长辈。”他清澈的声音在广场上回荡,“当一个年长的大人物对你说话时,认真恭敬地听,但别相信他。除了你自己的智慧,不要信任其它任何东西。一个老人,不管是白头发或者没头发,哪怕他得过诺贝尔奖,也可能是错的。你们必须永远保持怀疑,永远独立思考。”学子们报以雷鸣般的掌声和欢呼。

当年,鲍林就是凭借这种怀疑一切、不迷信权威的批判精神,开辟了化学和分子生物学研究的新天地。

今天,在我们不假思索地吞下几克维生素C,或者一把其它保健营养品前,是不是也应该遵照鲍林的建议,先问几个为什么,做一番独立的研究和思考?


主要参考资料

1. US National Library of Medicine. Profiles in Science: Linus Pauling. https://profiles.nlm.nih.gov/spotlight/mm.

2. OSU Libraries. Linus Pauling Online. http://scarc.library.oregonstate.edu/digitalresources/pauling/.


其他参考文献

[1] Pauling L. Vitamin C and the Common Cold. San Francisco: WH Freeman, 1976.

[2] Pauling L. Vitamin C, the Common Cold and the Flu. San Francisco: WH Freeman, 1976.

[3] Pauling L. How to Live Longer and Feel Better. New York: WH Freeman, 1986.

[4] Marshall CW. 2002. Vitamin C: Do High Doses Prevent Colds? https://www.quackwatch.org/01QuackeryRelatedTopics/DSH/colds.html)

[5] Karlowski TR et al. Ascorbic acid and the common cold: A prophylactic and therapeutic trial. 1975. JAMA 231:1038-42.

[6] Truswell AS. Ascorbic acid (letter). N Engl J Med. 1986. 315:709.

[7] Cameron E & Pauling L. Supplemental ascorbate in the supportive treatment of cancer: Prolongation of survival times in terminal human cancer. Proc Natl Acad Sci USA. 1976. 73: 3685-9.

[8] Cameron E & Pauling L. Supplemental ascorbate in the supportive treatment of cancer: reevaluation of prolongation of survival times in terminal human cancer. Proc Natl Acad Sci USA 1978. 75: 4538-42.

[9] Pauling L. On cancer and vitamin C. Prevention 1979. 31: 54.

[10] DeWys WD. How to evaluate a new treatment for cancer. Your Patient and Cancer 1982. 2: 31-6.

[11] Creagan ET et al. Failure of high-dose vitamin C (ascorbic acid) therapy to benefit patients with advanced cancer. A controlled trial. N Engl J Med. 1979. 301: 687-90.

[12] Moertel CG et al. High-dose vitamin C versus placebo in the treatment of patients with advanced cancer who have had no prior chemotherapy. A randomized double-blind comparison. N Engl J Med. 1985. 312: 137-41.

[13] Tschetter L et al. A community-based study of vitamin C (ascorbic acid) in patients with advanced cancer. Proceedings of the American Society of Clinical Oncology. 1983. 2: 92.

[14] Barrett S. High Doses of Vitamin C Are Not Effective as a Cancer Treatment. 2011. https://www.quackwatch.org/01QuackeryRelatedTopics/Cancer/c.html

[15] Offit PA. Pandora’s Lab. Washington DC: National Geographic, 2017.

[16] Rath M & Pauling L. A Unified Theory of Human Cardiovascular Disease Leading the Way to the Abolition of This Disease as a Cause for Human Mortality. Journal of Orthomolecular Medicine. 1992. 7: 5-12.

[17] Rath M & Pauling L. Immunological evidence for the accumulation of lipoprotein(a) in the atherosclerotic lesion of the hypoascorbemic guinea pig. Proc Natl Acad Sci USA. 1990. 87: 9388-90.

[18] Moser MA & Chun OK. Vitamin C and Heart Health: A Review Based on Findings from Epidemiologic Studies. Int J Mol Sci. 2016 17(8). pii: E1328. doi: 10.3390/ijms17081328.

特 别 提 示

1. 进入『返朴』微信公众号底部菜单“精品专栏“,可查阅不同主题系列科普文章。

2. 『返朴』开通了按月检索文章功能。关注公众号,回复四位数组成的年份+月份,如“1903”,可获取2019年3月的文章索引,以此类推。

版权说明:欢迎个人转发,任何形式的媒体或机构未经授权,不得转载和摘编。转载授权请在「返朴」微信公众号内联系后台。

《返朴》,科学家领航的好科普。国际著名物理学家文小刚与生物学家颜宁共同出任总编辑,与数十位不同领域一流学者组成的编委会一起,与你共同求索。关注《返朴》(微信号:fanpu2019)参与更多讨论。二次转载或合作请联系返朴公众号后台。

开发者,在后端仅提供原始数据集的情况下,如何让所有搜索逻辑都在前端完成?不仅如此,还要能够实现预测用户输入、忽略错别字输入、快速自动补齐等智能功能?本文的作者就深入 JavaScript 技术特性,谈一谈 React、性能优化以及多线程那些事儿。



作者 | Leo Fabrikant

译者 | 弯月,责编 | 郭芮

出品 | CSDN(ID:CSDNnews)

以下为译文:



第一篇 问题阐述


我有一个看似很简单的任务:“有一个从后端检索的数据集,其中包含13,000个数据项,每一项都是冗长罗嗦的名称(科学组织)。使用这些数据创建一个带自动补齐功能的搜索栏。”

你也不觉得很难,对不对?

难点1:

不能使用后端。后端只能提供原始的数据集,所有搜索逻辑都必须在前端完成。

难点2:

开发人员(我):“这些组织名称这么长,需要花点心思。如果我们只是运行简单的字符串匹配,而且用户可能会输错或出现拼写错误,那么就很难搜索到任何结果。”

客户:“你说的对,我们必须加入一些智能的功能,预测用户的输入,忽略输入的错别字。”

注意:一般情况下,我不建议你在未经项目经理同意下,提示客户没有提到的复杂功能!这种情况称为特征蔓延(feature creep)。在上述例子中,只是恰巧我一个人负责这个合同,我有足够的精力,所以我认为这是一个有趣的挑战。

难点3:

这是最大的难点。我选择的智能(也称为“模糊”)搜索引擎非常慢……

随着搜索词的长度加长,这个搜索算法库的搜索时间会迅速增加。此外,看看下面这个庞大的列表,里面的数据项极其冗长,用户需要输入一个很长的搜索词才能出现自动补齐提示。别无他法,搜索引擎跟不上用户的打字速度,这个UI可能会废掉。




我不知道是不是因为我选择的这个搜索算法库太过糟糕,我也不知道是不是因为所有“模糊”搜索算法都是这样的情形。但幸运的是,我没有费心去寻找其他选择。

尽管上述难点大多数是我自己强加上去的,但我依然决定坚持到底,努力优化这种实现。虽然我知道这可能不是最佳策略,但是这个项目的情况允许我这么做,结果将说明一切,最重要的是,对我来说这是一次绝佳的学习体验和成长的机会。



第二篇 问题剖析


在第一个实现中,我使用了UI的react-autocomplete和react-virtualized库。

render () {
 return (
 <Autocomplete
 value={this.state.searchTerm}
 items={this.getSearchResults()}
 renderMenu={this.reactVirtualizedList}
 renderItem={this.renderItem}
 getItemValue={ item => item.name }
 onChange={(e, value) => this.setState({searchTerm: value})}
 />
 )
 }


Autocomplete组件需要传递以下几项:value属性,需要传入输入框中的searchTeam;items属性,传入搜索结果;以及renderMenu函数,该函数将搜索结果列表传递给react-vertualized。

react-virtualized能够很好地处理大型列表(它只会渲染列表显示在屏幕上的一小部分,只有滚动列表时才会更新)。考虑到我们需要渲染的组件并不多,我认为应该不会有太严重的性能问题。

更新操作的声明周期也很简单:

  • 用户输入'a'
  • Autocomplete的onChange处理函数触发一次渲染,此时this.state.searchTeam = 'a'
  • 该渲染会调用getSearchResults方法,在这个方法中,搜索引擎使用'a'作为搜索关键字计算搜索结果,然后将结果传递给react-virtualized负责渲染。


getSearchResults = () => {
 const {searchTerm} = this.state;
 return searchTerm ? this.searchEngine.search(searchTerm) : []
 // searchEngine.search is the expensive search algorithm
};


我们来看看结果如何:




哎呀……很糟。在按住删除键时的的确确能感觉到UI的停顿,因为键盘触发delete事件太快了。

不过至少模糊搜索好用了:'anerican'正确地解释成了'American'。但随着搜索关键字的加长,两个元素(输入框和搜索结果)的渲染过程完全跟不上用户输入的速度,延迟非常大。

尽管我们的搜索算法的确很慢,但如此大的延迟并不是由于单次搜索时间太长导致的。这里还有另外一个现象需要理解:我们管它叫UI阻塞。理解这个现象需要深入了解Chrome的DevTools性能评测工具。

性能评测

可能你不熟悉这个工具,但是你要知道,熟练使用性能评测工具是深入理解JavaScript的最好方式。这不仅因为它能提供有用的信息帮你解决各种问题,而且按照时间显示出JavaScript的执行过程能够帮助你更好地理解好的UI、React、JavaScript事件循环、同步异步执行等概念。

在下面每一节,我都会给出性能分析过程,以及从这些过程中推断出的有趣的结论。这些数据可能会让你眼花缭乱,但我会尽力给出合理的解释!

首先评测一下在Autocomplete中按下两个键时的情况:



X轴:时间,Y轴:按照类型排列的事件(用户输入、主线程函数调用)


理解基本的序列非常重要:

首先是用户的键盘输入(Key Characer)。这些输入在JavaScript的主线程上触发了Event(keypress)事件,该事件又触发了我们的onChange处理函数,该函数会调用setState(图上看不见,因为它太小了,但它的位置在整个栈的最开头附近)。这一系列动作标志着重新计算的开始,计算setState对组件会产生何种影响。这一过程称为更新,或者叫做重新渲染,它会调用几个React生命周期方法,其中就包括render。这一切都发生在一个执行栈(有时称为“调用栈”或简称为“栈”),在图中由每个Event (keypress)下方竖直排列的框表示。每个框都是执行栈中的一次函数调用。

这里不要被render这个词误导了。React的渲染并不仅仅是在屏幕上绘制。渲染只是React用来计算被更新的元素应当如何显示的过程。如果仔细查看第二个Event (keypress),就会发现在Event (keypress)框的外面有个小得几乎看不见的绿条。放大一些就能看到这是对浏览器绘制的调用:




这才是UI更新被真正绘制到屏幕上,并在显示器上显示新帧的过程。而且,第一个Event (keypress)之后没有绘制,只有第二个后面才有。

这说明,浏览器绘制(外观上的更新)并不一定会在Event (keypress)事件发生并且React完成更新之后发生。

原因是JavaScript的事件循环和JavaScript对于任务队列的优先级处理。在React结束计算并将更新写入DOM之后(称为提交阶段,发生在每个执行栈末尾的地方),你可能会以为浏览器应该开始绘制,将DOM更新显示在屏幕上。但是在绘制之前,浏览器会检查JavaScript事件队列中是否还有其他任务,有的任务会比绘制更优先执行。

当JavaScript线程忙于渲染第一个keypress时,产生第二个用户输入(如键盘按下事件)就会出现这种情况(你可以看到第二个Key Character输入发生在第一个keypress执行栈依然在运行时)。这就是前面提到的UI阻塞。第二个Event (keypress)阻塞了UI更新第一个keypress。

不幸的是,这会导致巨大的延迟。因为渲染本身非常慢(部分原因是因为渲染中包含了昂贵的搜索算法),如果用户输入非常快,那么很大可能会在前一个执行栈结束之前输入新的字符。这会产生新的Event (keypress),并且它的优先级比浏览器绘制要高。所以绘制会不断被用户的输入拖延。

不仅如此,甚至在用户停止输入后,队列中依然滞留了许多keypress时间,React需要依次对每个keypress进行计算。所以,即使在输入结束后,也不得不等待这些针对早已不需要的searchTeams的搜索!




注意最后一个Key Character发生后,还有4个Event (keypress)滞留,浏览器需要处理完所有事件才能重绘。

改进方法

为了解决这个问题,重要的是要理解好的UI的要素是什么。实际上,对于每次键盘输入,用户期待的视觉反馈包括两个独立的要素:

  • 用户输入的键显示在输入框中;
  • 搜索结果根据新的searchTeam进行更新。


理解用户的期望才能找到解决方案。尽管Google搜索快得像闪电一样,但用户无法立即收到搜索结果的事情也屡见不鲜。一些UI甚至会在请求搜索结果时显示加载进度条。

重要的是要理解,对于反应迅速的UI而言,第一种反馈(按下的键显示在输入框中)扮演了非常重要的角色。如果UI无法做到这一点,就会有严重的问题。

查看性能评测是解决问题的第一个提示。仔细观察这些长长的执行栈和render中包含的昂贵的search方法,我们会发现,更新输入框和搜索结果的一切操作都发生在同一个执行栈内。所以,两者的UI更新都会被搜索算法阻塞。

但是,输入框的更新不需要等待结果!它只需要知道用户按下了哪个键,而不需要知道搜索结果是什么。如果我们有办法控制事件执行的顺序,输入框就有机会先更新UI,再去渲染搜索结果,这样就能减少一些延迟。因此,我们的第一个优化措施就是将输入框的渲染从搜索结果的渲染中分离出来。

注意:熟悉Dan Abramov在React JSConf 2018上的演讲的人应该能回忆起这个场景。在他的幻灯片中,他设计了一个昂贵的更新操作,随着输入值的增加,屏幕上需要渲染的组件也越来越多。这里我们遇到的困难非常相似,只不过是随着搜索关键字长度的增加,单个搜索函数的复杂度会增加而已。在Dan的演讲中,他演示了时间切片(Time Slicing),这个React团队在开发中的功能也许可以解决这个问题!我们的尝试会以相似的方案解决问题:找到一个方法来改变渲染的顺序,防止昂贵的计算再次阻塞主线程的UI更新。



第三篇 异步渲染(componentDidUpdate)


注意:本篇讨论的优化最后以失败告终了,但我认为值得讲一讲,因为它能帮助我们更好地理解React的componentDidUpdate生命周期。如果你非常熟悉React,或者只想看看怎样改善性能问题,那么可以直接跳到下一篇。

拆分组件

由于我们想把昂贵的搜索结果更新从输入框更新中拆分出来,所以我们应该自己设计一个组件,并放弃使用react-autocomplete库提供的一站式解决方案Autocomplete:

//autocomplete.js
render () {
 return (
 <div>
 <input
 onChange={ e => this.setState({searchTerm: e.target.value})}
 value={this.state.searchTerm}/>
 <SearchResults
 searchEngine={this.props.searchEngine}
 searchTerm={this.state.searchTerm}/>
 </div> 
 )
}


然后在SearchResults中,我们需要异步触发searchEngine.search(searchTerm),而不应该在更新searchTerm的同一个渲染中进行。

我最初的想法是利用SearchResults的componentDidUpdate,让searchEngine异步工作,因为听上去这个方法似乎是在更新之后异步执行的。

//searchResults.js
componentDidUpdate(prevProps) {
 const {searchTerm, searchEngine} = this.props;
 if(searchTerm && searchTerm !== prevProps.searchTerm) {
 this.setState({
 searchResults: searchEngine.search(searchTerm)
 })
 }
}
render () {
 return <ReactVirtualizedList searchResults={this.state.searchResults}/>
}


我们将昂贵的searchEngine移动到了componentDidUpdate中,这样就不用在render方法中调用,而是等到更新之后再执行。我希望在输入框更新之后的另一个执行栈中执行render,并两者之间执行一次绘制。我想象的新的更新生命周期如下:

  • 输入框使用新的searchTerm渲染;
  • React把带有新的searchTerm的componentDidUpdate searchEngine任务放入队列;
  • 浏览器绘制输入框的更新,然后处理队列中的searchEngine任务。理论上,浏览器不会把这个任务放在绘制任务之前,因为它不涉及用户交互事件;
  • componentDidUpdate在输入框绘制之后执行,计算搜索结果并生成新的更新。


很不幸,认为componentDidUpdate会在浏览器更新之后运行是一个常见的误解。我们来看看这个方法的性能评测:




看到问题了吗?componentDidUpdate跟最初的keypress事件是在同一个执行栈上执行的

componentDidUpdate并不会在绘制结束后执行,因此执行栈跟上一篇一样昂贵,我们只不过是将昂贵的search方法移动到了不同位置而已。尽管这个解决方案并不能改善性能,但我们可以借此机会理解React的生命周期componentDidUpdate的具体行为。

虽然componentDidUpdate不会在另一个执行栈上运行,但它确实是在React更新完组件状态并将更新后的DOM值提交之后才执行的。尽管这些更新后的DOM值还没有被浏览器绘制,它们依然反映了更新后的UI应有的样子。所以,任何componentDidupdate内执行的DOM查询都能访问到更新后的值。所以,组件确实更新了,只是浏览器中看不见而已。

所以,如果想要做DOM计算,一般都应该在componentDidUpdate中进行。在这里很方便根据布局改变进行更新,比如根据新的布局方式计算元素的位置或大小,然后更新状态等。

如果componentDidUpdate每次触发改变布局的更新时都要等待实际的浏览器绘制,那么用户体验会非常糟糕,因为用户可能会在布局改变时看到两次屏幕闪烁。

注(React Hooks):这个差异也有助于理解新的useEffect和useLayoutEffect钩子。useEffect就是我们在这里尝试实现的效果。它会让代码在另一个执行栈中运行,从而在执行之前浏览器可以进行绘制。而useLayoutEffect更像是componentDidUpdate,允许你在DOM更新之后、浏览器绘制之前执行代码。



第四篇 异步渲染(setTimeout)


上一篇我们拆分了组件:

//autocomplete.js
render () {
 return (
 <div>
 <input
 onChange={ e => this.setState({searchTerm: e.target.value})}
 value={this.state.searchTerm}/>
 <SearchResults
 searchEngine={this.props.searchEngine}
 searchTerm={this.state.searchTerm}/>
 </div> 
 )
}


但我们没能让昂贵的searchEngine在另一个执行栈上运行。那么,还有什么办法能实现这一点呢?

有两个常见的方法可以设置异步调用:Promise和setTimeout。

Promise

//searchResults.js
componentDidUpdate(prevProps) {
 const {searchTerm, searchEngine} = this.props;
 if(searchTerm && searchTerm !== prevProps.searchTerm) {
 /* stick the update with the expensive search method into
 a promise callback: */
 Promise.resolve().then(() => {
 this.setState({
 searchResults: searchEngine.search(searchTerm)
 })
 })
 }
}
render () {
 return <ReactVirtualizedList searchResults={this.state.searchResults}/>
}


我们来看看性能评测:



(anonymous)是Promise.then()回调函数


又失败了!

理论上Promsie的回调函数是异步的,因为它们不会同步执行,但实际上还是在同一个执行栈中运行的。

仔细看看性能评测就会发现,回调函数被放在了Run Microtasks下,因为Promise的回调函数被当作了微任务。浏览器通常会在完成正常的栈之后检查并运行微任务。

更多信息:Jake Archibald有一篇非常好的演讲(https://medium.com/r/?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DcCOL7MC4Pl0),解释了JavaScript事件循环在微任务方面的处理,还深入讨论了许多我涉及到的话题。

尽管了解这一点很好,但并没有解决问题。我们需要新的执行栈,这样浏览器才有机会在新的执行栈开始之前进行绘制。

setTimeout

//searchResults.js
componentDidUpdate(prevProps) {
 const {searchTerm, searchEngine} = this.props;
 if(searchTerm && searchTerm !== prevProps.searchTerm) {
 /* stick the update with the expensive search method into
 a setTimeout callback: */
 const setTimeoutCallback = () => {
 this.setState({
 searchResults: searchEngine.search(searchTerm)
 })
 }
 setTimeout(setTimeoutCallback)
 }
}
render () {
 return <ReactVirtualizedList searchResults={this.state.searchResults}/>
}


性能评测:



绘制很难看到,它太小了,但蓝线的位置的确发生了绘制


哈哈!成功了!注意到这里有两个执行栈:Event (keypress)和Timer Fired (searchResults.js:49)。两个栈之间发生了一次绘制(蓝线的位置)。这正是我们想要的!来看看漂亮的UI!



按住删除键时依然有明显的延迟


有很大的改进,但依然很令人失望……这个页面的确变好了,但依然能感觉到UI的延迟。我们来仔细看看性能测试。




我们需要一些技巧才能分析这个性能测试报告并找出延迟的原因。使用性能报告有几个小技巧:

性能评测工具着重显示了两个特别有用的Key Input Interactions,每个都有不同的性能度量:

  • Key Down:从键盘按下到Event (keypress)处理函数被调用的时间间隔;
  • Key Character:从键盘按下到浏览器绘制更新的时间间隔。


理想状态下,Key Down交互应该非常短,因为如果JavaScript主线程没有被阻塞的话,事件应该在键盘按下之后立即出发。所以,看到长长的Key Down就意味着发生了UI阻塞问题。

在本例中,长长的Key Down的原因是它们触发时,主线程还在被昂贵的setTimeoutCallback阻塞,比如最后的Key Down。很不幸,Key Down发生时,运行昂贵的search的setTimeoutCallback刚刚开始,也就是说,Key Down只有等待search的计算结束才能触发事件。这个评测中的search方法大约花了330毫秒,也就是1/3秒。从优秀的UI角度而言,1/3秒的主线程阻塞实在太长了。

特别引起我注意的是最后一个Key Character。尽管它关联了Key Down的结束并且触发了Event (keypress),浏览器并没有绘制新的searchTerm,而是执行了另一个setTimeoutCallback。这就是Key Character交互花了两倍时间的原因。

这一点着实让我大跌眼镜。将search移动到setTimeoutCallback的目的,就是让浏览器能够在调用setTimeoutCallback之前进行绘制。但是在最后的Event (keypress)之前依然没有绘制(蓝线的位置)。

结论是,我们不能依赖于浏览器的队列机制。显然浏览器并不一定将绘制排在超时回调之前。如果超时回调需要占用主线程330毫秒,那么也会阻碍主线程,导致延迟。



第五篇 多线程


在上一篇中,我们拆分了组件,并成功地使用setTimeout将昂贵的search移动到了另一个执行栈中,因此浏览器无需等待 search完成,就可以绘制输入框的更新:

//autocomplete.js
render () {
 return (
 <div>
 <input
 onChange={ e => this.setState({searchTerm: e.target.value})}
 value={this.state.searchTerm}/>
 <SearchResults
 searchEngine={this.props.searchEngine}
 searchTerm={this.state.searchTerm}/>
 </div> 
 )
}
//searchResults.js
componentDidUpdate(prevProps) {
 const {searchTerm, searchEngine} = this.props;
 if(searchTerm && searchTerm !== prevProps.searchTerm) {
 const setTimeoutCallback = () => {
 this.setState({
 searchResults: searchEngine.search(searchTerm)
 })
 }
 setTimeout(setTimeoutCallback)
 }
}
render () {
 return <ReactVirtualizedList searchResults={this.state.searchResults}/>
}


不幸的是,这依然没有解决每个searchTerm导致search阻塞主线程330毫秒并导致UI延迟的问题。

JavaScript的单线程实在太糟糕了……突然我想到了一个方法。

最近我在阅读渐进式Web应用,其中有人使用Service Worker来实现一些过程,比如在另一个线程中进行缓存。于是我开始学习Service Worker。这是一个全新的API,我需要花点时间来学习。

但在学习之前我想先通过实验来验证一下增加额外的线程是否真的能够提高性能。

用服务器端来模拟第二个线程

我以前就做过搜索和自动补齐提示的功能,但这次特别困难的原因是需要完全在前端实现。以前我做过用API来获取搜索结果。其中一种思路是,API和运行API的服务器实际上相当于前端应用利用的另一个线程。

于是,我想到可以做一个简单的node服务器让searchTerm访问,从而实现在另一个线程中运行昂贵的搜索。这个功能非常容易实现,因为这个项目中已经设置过开发用的服务器了。所以我只需添加一个新的路径:

app.route('/prep-staging/testSearch')
 .post(bodyParser.json(), (req, res) => {
 const {data, searchTerm} = req.body
 const engine = new SearchEngine(data)
 const searchResults = engine.search(searchTerm)
 res.send({searchResult})
 })


然后将SearchResults中的setTimeout改成fetch:

//searchResults.js
componentDidUpdate(prevProps) {
 const {searchTerm, searchEngine, data} = this.props;
 if(searchTerm && searchTerm !== prevProps.searchTerm) {
 /* ping the search route with the searchTerm and update state
 with the results when they return: */
 fetch(`testSearch`, {
 method: 'POST',
 body: JSON.stringify({data, searchTerm}),
 headers: {
 'content-type': 'application/json'
 }
 })
 .then(r => r.json())
 .then(resp => this.setState({searchResults: resp.searchResults}))
 }
}
render () {
 return <ReactVirtualizedList searchResults={this.state.searchResults}/>
}


现在是见证奇迹的时刻!




注意看删除!

太棒了!输入框的更新几乎非常完美。而另一方面,搜索结果的更新依然非常缓慢,但没关系!别忘了我们的首要任务就是让用户在输入时尽快获得反馈。我们来看看性能测试报告:




注意主线程中间漂亮的空白,它不再是之前层层叠叠的样子了。性能优化中经常会遇到这种情况。主线程中的空白越多越好!

从顶端可以看到testSearch花费的时间。最长的一个实际上花了800毫秒,让我很吃惊,但仔细想想就会发现,并不是search花了这么长时间,而是我们的Node服务器也是单线程的。它在第一个搜索完成之前无法开始另一个搜索。由于输入比搜索快得多,所以搜索会进入队列然后被延迟。搜索函数实际花费的时间是前一个搜索完成之后的部分,大约315毫秒。

总的来说,将昂贵的任务放到服务器线程中,就可以将堆叠的栈移动到服务器上。所以,尽管依然有改进的空间,但主线程看起来非常好,UI的响应速度也更快了!

我们已经证明这个思路是正确的,现在来实现吧!

做了一点研究后我发现,Server Worker并不是正确的选择,因为它不兼容Internet Explorer。幸运的是,它还有个近亲,叫做Web Worker,能兼容所有主流服务器,API更简单,而且能完成我们需要的功能!



第六篇 Web Worker


Web worker能够在JavaScript的主线程之外的另一个线程上运行代码,每个Web worker都由一个脚本文件启动。启动方式非常简单:

//searchResults.js
export default class SearchResults extends React.Component {
 constructor (props) {
 super();
 this.state = {
 searchResults: [],
 }
 //initiate the webworker:
 this.webWorker = new Worker('...path to webWorker.js') 
 //pass it the 13,000 item search data to initialize the searchEngine with:
 this.webWorker.postMessage({data: props.data})
 //assign the handler that will accept the searchResults when it sends them back:
 this.webWorker.onmessage = this.handleResults
 }
 componentDidUpdate(prevProps) {
 const {searchTerm} = this.props;
 if(searchTerm && searchTerm !== prevProps.searchTerm) {
 //change our async search request to a .postMessage, the messaging API of webWorkers:
 this.webWorker.postMessage({searchTerm})
 }
 }
 handleResults = (e) => {
 const {searchResults} = e.data
 this.setState({
 searchResults
 })
 }
 render () {
 return <ReactVirtualizedList searchResults={this.state.searchResults}/>
 }
}


下面是webWorker.js脚本,在SearchResults的构造函数中进行初始化:

//webWorker.js
self.importScripts('...the search engine script, provides the SearchEngine constructor');
let searchEngine; 
let cache = {} 
//thought I would add a simple cache... Wait till you see those deletes now :)
function initiateSearchEngine (data) {
 //initiate the search engine with the 13,000 item data set
 searchEngine = new SearchEngine(data); 
 //reset the cache on initiate just in case
 cache = {}; 
}
function search (searchTerm) {
 const cachedResult = cache[searchTerm]
 if(cachedResult) {
 self.postMessage(cachedResult)
 return
 }
 const message = {
 searchResults: searchEngine.search(searchTerm)
 };
 cache[searchTerm] = message;
 //self.postMessage is the api for sending messages to main thread
 self.postMessage(message)
}
/*self.onmessage is where we define the handler for messages recieved
from the main thread*/
self.onmessage = function(e) {
 const {data, searchTerm} = e.data;
 /*We can determine how to respond to the .postMessage from 
 SearchResults.js based on which data properties it has:*/
 if(data) {
 initiateSearchEngine(data)
 } else if(searchTerm) {
 search(searchTerm)
 }
}


可以看到,我还加了些额外的代码。这里我加了缓存,这样之前搜索过的searchTerms就可以立即返回结果了。如此一来,最耗性能的用户交互(常按删除键)的效率就提高了。

我们来看看运行情况:




太棒了……非常快!这看起来很不错啊,实话说,做到这个样子就可以直接发布了!

但是,现在就发布多没劲啊……

从用户体验的角度来说,这个UI已经非常好了。如果仔细观察,其实依然能看到搜索结果的延迟,但几乎察觉不到……不过幸运的是,从性能测试报告中可以看到低效率的地方:




请无视灰色的条,我也不知道它们是怎么来的。

报告中最左侧的线程是我们关注的线程。Main线程已经折叠了,因为里面只有大量的空白,意味着主线程的性能非常好,不会成为任何主要的性能瓶颈。

可以看到,Worker线程里堆满了search调用。从最后一个Key Character输入(蓝线位置)之后就会看到其后果。Worker线程中的最后一个search实际上推迟了3个search才返回。测量一下会发现,延迟大约有850毫秒。而且大部分都是不必要的,因为那三个search都没用,我们不再需要它们返回的结果了。

你也许在想:“这已经很好了!再优化下去性价比不高啊!”

我不这样认为。首先,不要低估尝试新事物和探索带来的价值。如果你从来没做过探索,就很可能无法评价是否值得,因为你不知道你能有哪些收获,以及你将投入多少时间和努力。所以,我认为这种探索带来的经验和知识是无价的。随着知识的积累,你能更好地评价投入的时间是否值得,以及是否应该考虑这些问题。你可以做出更好的决定!

其次,别忘了这并不是过早优化。这些优化都是根据性能评价做出的,我们可以测量出效果。不管怎样评价,如果能改善850毫秒的延迟,那都是非常重大的改进。

最后(但不是唯一),别忘了移动端!虽然我不会在本文中介绍,但我在研究这个问题时,我也跟踪了移动端的性能,现在的条件下依然有能察觉得到的性能延迟。

不管怎么说,我们来解决这个问题!



第七篇 确保searchTerm


前面的性能评测揭示的最明显的问题就是,即使对于无用的searchTerm也会运行昂贵的搜索。所以目前的解决方案之一就是在执行昂贵的搜索之前确保searchTerm是最新的。只要在webWorker脚本中加入confirmSearchTerm就可以非常容易地实现:

//webWorker.js
self.importScripts('...the search engine script, provides the SearchEngine constructor');
let searchEngine; 
let cache = {}
function initiateSearchEngine (data) {
 searchEngine = new SearchEngine(data);
 cache = {};
}
function search (searchTerm) {
 const cachedResult = cache[searchTerm]
 if(cachedResult) {
 self.postMessage(cachedResult)
 return
 }
 const message = {
 searchResults: searchEngine.search(searchTerm)
 };
 cache[searchTerm] = message;
 self.postMessage(message)
}
function confirmSearchTerm (searchTerm) {
 self.postMessage({confirmSearchTerm: searchTerm})
}
self.onmessage = function(e) {
 const {data, searchTerm, confirmed} = e.data;
 if(data) {
 initiateSearchEngine(data)
 } else if(searchTerm) {
 /*check if the searchTerm is confirmed, if not, send a confirmSearchTerm message 
 to compare the searchTerm with the latest value on the main thread */
 confirmed ? search(searchTerm) : confirmSearchTerm(searchTerm)
 }
}


这里还给SearchResults handleResults加了个额外的条件,监听confirmSearchTerm的请求:

//searchResults.js
export default class SearchResults extends React.Component {
 constructor (props) {
 super();
 this.state = {
 searchResults: [],
 }
 this.webWorker = new Worker('...path to webWorker.js') 
 this.webWorker.postMessage({data: props.data})
 this.webWorker.onmessage = this.handleResults
 }
 componentDidUpdate(prevProps) {
 const {searchTerm} = this.props;
 if(searchTerm && searchTerm !== prevProps.searchTerm) {
 this.webWorker.postMessage({searchTerm})
 }
 }
 handleResults = (e) => {
 const {searchResults, confirmSearchTerm} = e.data;
 /* check if confirmSearchTerm property was sent, if so compare it to the 
 latest searchTerm and send back a confirmed searchTerm message */
 if (confirmSearchTerm && confirmSearchTerm === this.props.searchTerm) {
 this.webWorker.postMessage({
 searchTerm: this.props.searchTerm,
 confirmed: true
 })
 } else if (searchResults) {
 this.setState({
 searchResults
 })
 }
 }
 render () {
 return <ReactVirtualizedList searchResults={this.state.searchResults}/>
 }
}


我们来看看性能评测,看看有没有改进:




很难看出结果,因为它们运行得太快了,但在每次Worker的执行栈之前都会执行confirmSearchTerm(见蓝线)。

说明的确管用了:Worker在每次运行昂贵的search方法之前都会确认搜索是否必要。而且可以看到,顶部的橙色部分有4个Key Character输入,但只运行了三个搜索。这里我们成功地去掉了一个不必要的搜索,节约了最多330毫秒。但之前我们看到,多个额外的搜索会进入队列然后再不必要地运行,而现在我们完全避免了这个问题。所以,节约的时间非常显著,特别是在移动端上。

但仔细观察就会发现我们依然在浪费时间:




最后一个搜索使用了最新的searchTerm,但依然要至少等待当前的搜索完成后才能开始。浪费了84毫秒(蓝色高亮部分)!据我所知,执行栈一旦开始就无法取消。那么我们是不是无计可施了呢?



第八篇 Web Worker阵列


如果多加一个线程的效果很好,那么加4个会怎样?

实话实说,现在这些只是出于兴趣……但说真的,最后这次优化确实在移动端上带来了人眼能察觉到的改善……

无论怎样,现在我打算用Web Worker阵列!




为了出这份报告,我用人类最快的速度输入的!

图中可以看到,顶端每个橙色的Key Character都有一个ww worker线程,能立即开始搜索(橙色条)。不再需要等待前一个搜索结束。而且每个ww在结束后就能用于下一次搜索。

这是因为我们设置了workerArray.js这个web worker作为分发器。图中看不到,因为它执行得太快了,但对于每个Key Character,workerArray都会执行一个微笑的执行栈,用来处理主线程传来的搜索请求消息,然后分发给第一个可用的ww worker。

我们成功地解决了“搜索堆”的问题。可以认为用增加车道的方式解决了交通拥堵。

为什么用4个搜索worker呢?因为我在测试时的输入速度从来没能到过需要第五个worker的速度(增加worker带来的改进非常微小)。

结果发现,从Key Character输入到search执行之间没有任何延迟。除非找到另一个更有效的搜索算法,否则我们已经成功地移除了所有性能瓶颈。

别忘了,我并没有说这是最佳的解决方案。是否每台手机都能处理4个web worker?性能改善是否值得付出这些额外的代码复杂度?是否还有其他安全方面的考量,导致代码更加复杂?

这些问题都非常重要,正是这些问题会最终引出这样的解决方案。

但至少现在,这个Web worker阵列非常优秀!我们来看看实际效果:




感谢你耐心地阅读完所有的篇节!

如果说这一系列优化有什么感想的话,那就是:

  • 不要害怕尝试新事物,不要害怕不寻常的方案。
  • 方案是否好用并不重要,它们带来的经验和知识才是最有价值的!


如果你有兴趣,可以看看下面Web worker阵列的代码。



课外作业


下面的代码中有个小问题。需要一个非常微小的修改才能使它更强壮,最多两行代码。你能找到问题所在并修复吗?

//searchResults.js
export default class SearchResults extends React.Component {
 constructor (props) {
 super();
 this.state = {
 searchResults: [],
 }
 //initiate the worker array:
 this.workerArray = new WorkerArrayController({
 data: props.data,
 handleResults: this.handleResults,
 arraySize: 4
 });
 }
 componentDidUpdate(prevProps) {
 const {searchTerm} = this.props;
 if(searchTerm && searchTerm !== prevProps.searchTerm) {
 this.workerArray.search({searchTerm})
 }
 }
 handleResults = (e) => {
 const {searchResults} = e.data
 this.setState({
 searchResults
 })
 }
 componentWillUnmount () {
 this.workerArray.terminate();
 }
 render () {
 return <ReactVirtualizedList searchResults={this.state.searchResults}/>
 }
}


SearchResults组件初始化WorkerArrayController。

//workerArrayController.js
export default class WorkerArrayController {
 constructor ({data, handleResults, arraySize}) {
 this.workerArray = new Worker('... path to workerArray.js');
 let i = 1;
 this.webWorkers = {};
 while (i <= arraySize) {
 const workerName = `ww${i}`;
 this.webWorkers[workerName] = new Worker(`...path to ww1.js`);
 /* Creates a MessageChannel for each worker and passes that channel's 
 ports to both workerArray dispatcher and the worker so 
 they can communicate with each other */
 const channel = new MessageChannel();
 this.workerArray.postMessage({workerName}, [channel.port1]);
 this.webWorkers[workerName].postMessage({data}, [channel.port2]);
 i++;
 }
 this.workerArray.onmessage = handleResults;
 }
 search = (searchTerm) => {
 this.workerArray.postMessage({searchTerm});
 }
 terminate() {
 this.workerArray.terminate();
 for (const workerName in this.webWorkers) {
 this.webWorkers[workerName].terminate();
 }
 }
}


WorkerArrayController用4个ww初始化workerArray web worker,并传递MessageChannel端口给它们,这样它们能够互相通信。

//workerArray.js
const ports = {};
let cache = {};
let queue;
function initiatePort (workerName, port) {
 ports[workerName] = port;
 const webWorker = ports[workerName];
 webWorker.inUse = false;
 webWorker.onmessage = function handleResults (e) {
 const {searchTerm, searchResults} = e.data;
 const message = {searchTerm, searchResults};
 /* If all workers happen to be inUse, the message gets saved to the
 the queue and passed to the first worker that finishes */
 if(queue) {
 webWorker.postMessage(queue);
 webWorker.inUse = true;
 queue = null;
 } else {
 webWorker.inUse = false;
 }
 cache[searchTerm] = message;
 self.postMessage(message);
 }
}
function dispatchSearchRequest (searchTerm) {
 const cachedResult = cache[searchTerm];
 if(cachedResult) {
 self.postMessage(cachedResult);
 return
 }
 const message = {searchTerm};
 for (const workerName in ports) {
 const webWorker = ports[workerName];
 if(!webWorker.inUse) {
 webWorker.postMessage(message);
 webWorker.inUse = true;
 return
 }
 }
 queue = message;
}
self.onmessage = function (e) {
 const {workerName, searchTerm} = e.data;
 if(workerName) {
 initiatePort(workerName, e.ports[0]);
 } else if(searchTerm) {
 dispatchSearchRequest(searchTerm);
 }
}


workerArray初始化端口对象用于通信,并跟踪每个ww worker。它还初始化了缓存和队列,万一所有端口都被占用的情况下用来跟踪最新的searchTerm请求。

//ww1.js
self.importScripts('...the search engine script, provides the SearchEngine constructor');
let searchEngine;
let port;
function initiate (data, port) {
 searchEngine = new SearchEngine(data);
 port = port;
 port.onmessage = search;
}
/* search is attached to the port as the message handler so it
runs when communicating with the workerArray only */
function search (e) {
 const {searchTerm} = e.data;
 const message = {
 searchResults: searchEngine.search(searchTerm)
 };
 port.postMessage(message)
}
/* self.onmessage is the handler that responds to messages from
the main thread, which only fires during initiation */
self.onmessage = function(e) {
 const {data} = e.data;
 initiate(data, e.ports[0]);
}


原文:Secrets of JavaScript: A tale of React, performance optimization and multi-threading

本文为 CSDN 翻译,转载请注明来源出处。