整合营销服务商

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

免费咨询热线:

V8 编译浅谈-JavaScript 在 V8 中的解析过程

介:本文是一个 V8 编译原理知识的介绍文章,旨在让大家感性的了解 JavaScript 在 V8 中的解析过程。

作者 | 子弈
来源 | 阿里技术公众号

一 简介

本文是一个 V8 编译原理知识的介绍文章,旨在让大家感性的了解 JavaScript 在 V8 中的解析过程。本文主要的撰写流程如下:

  • 解释器和编译器:计算机编译原理的基础知识介绍
  • V8 的编译原理:基于计算机编译原理的知识,了解 V8 对于 JavaScript 的解析流程
  • V8 的运行时表现:结合 V8 的编译原理,实践 V8 在解析流程中的具体运行表现

本文仅代表个人观点,文中若有错误欢迎指正。

二 解释器和编译器

大家可能一直疑惑的问题:JavaScript 是一门解释型语言吗?要了解这个问题,首先需要初步了解什么是解释器和编译器以及它们的特点是什么。

1 解释器

解释器的作用是将某种语言编写的源程序作为输入,将该源程序执行的结果作为输出,例如 Perl、Scheme、APL 等都是使用解释器进行转换执行:

2 编译器

编译器的设计是一个非常庞大和复杂的软件系统设计,在真正设计的时候需要解决两个相对重要的问题:

  • 如何分析不同高级程序语言设计的源程序
  • 如何将源程序的功能等价映射到不同指令系统的目标机器

中间表示(IR)

中间表示(Intermediate Representation,IR)是程序结构的一种表现方式,它会比抽象语法树(Abstract Syntax Tree,AST)更加接近汇编语言或者指令集,同时也会保留源程序中的一些高级信息,具体作用包括:

  • 易于编译器的错误调试,容易识别是 IR 之前的前端还是之后的后端出的问题
  • 可以使得编译器的职责更加分离,源程序的编译更多关注如何转换成 IR,而不是去适配不同的指令集
  • IR 更加接近指令集,从而相对于源码可以更加节省内存空间

优化编译器

IR 本身可以做到多趟迭代从而优化源程序,在每一趟迭代的过程中可以研究代码并记录优化的细节,方便后续的迭代查找并利用这些优化信息,最终可以高效输出更优的目标程序:

优化器可以对 IR 进行一趟或者多趟处理,从而生成更快执行速度或者更小体积的目标程序(例如找到循环中不变的计算并对其进行优化从而减少运算次数),也可能用于产生更少异常或者更低功耗的目标程序。除此之外,前端和后端内部还可以细分为多个处理步骤,具体如下图所示:

3 两者的特性比较

解释器和编译器的具体特性比较如下所示:

需要注意早期的 Web 前端要求页面的启动速度快,因此采用解释执行的方式,但是页面在运行的过程中性能相对较低。为了解决这个问题,需要在运行时对 JavaScript 代码进行优化,因此在 JavaScript 的解析引擎中引入了 JIT 技术。

4 JIT 编译技术

JIT (Just In Time)编译器是一种动态编译技术,相对于传统编译器而言,最大的区别在于编译时和运行时不分离,是一种在运行的过程中对代码进行动态编译的技术。

5 混合动态编译技术

为了解决 JavaScript 在运行时性能较慢的问题,可以通过引入 JIT 技术,并采用混合动态编译的方式来提升 JavaScript 的运行性能,具体思路如下所示:

采用上述编译框架后,可以使得 JavaScript 语言:

  • 启动速度快:在 JavaScript 启动的时候采用解释执行的方式运行,利用了解释器启动速度快的特性
  • 运行性能高:在 JavaScript 运行的过程中可以对代码进行监控,从而使用 JIT 技术对代码进行编译优化

三 V8 的编译原理

V8 是一个开源的 JavaScript 虚拟机,目前主要用在 Chrome 浏览器(包括开源的 Chromium)以及 Node.js 中,核心功能是用于解析和执行 JavaScript 语言。为了解决早期 JavaScript 运行性能差的问题,V8 经历了多个历史的编译框架衍变之后(感兴趣的同学可以了解一下早期的 V8 编译框架设计),引入混合动态编译的技术来解决问题,具体详细的编译框架如下所示:

1 Ignition 解释器

Ignition 的主要作用是将 AST 转换成 Bytecode(字节码,中间表示)。在运行的过程中,还会使用类型反馈(TypeFeedback)技术并计算热点代码(HotSpot,重复被运行的代码,可以是方法也可以是循环体),最终交给 TurboFan 进行动态运行时的编译优化。Ignition 的解释执行流程如下所示:

在字节码解释执行的过程中,会将需要进行性能优化的运行时信息指向对应的 Feedback Vector(反馈向量,之前也被称为 Type Feedback Vector),Feeback Vector 中会包含根据内联缓存(Inline Cache,IC)来存储的多种类型的插槽(Feedback Vector Slot)信息,例如 BinaryOp 插槽(二进制操作结果的数据类型)、Invocation Count(函数的调用次数)以及 Optimized Code 信息等。

这里不会过多讲解每个执行流程的细节问题。

2 TurboFan 优化编译器

TurboFan 利用了 JIT 编译技术,主要作用是对 JavaScript 代码进行运行时编译优化,具体的流程如下所示:

图片出处 An Introduction to Speculative Optimization in V8。

需要注意 Profiling Feedback 部分,这里主要提供 Ignition 解释执行过程中生成的运行时反馈向量信息 Feedback Vector ,Turbofan 会结合字节码以及反馈向量信息生成图示(数据结构中的图结构),并将图传递给前端部分,之后会根据反馈向量信息对代码进行优化和去优化。

这里的去优化是指让代码回退到 Ignition 进行解释执行,去优化本质是因为机器码已经不能满足运行诉求,例如一个变量从 string 类型转变成 number 类型,机器码编译的是 string 类型,此时已经无法再满足运行诉求,因此 V8 会执行去优化动作,将代码回退到 Ignition 进行解释执行。

四 V8 的运行时表现

在了解 V8 的编译原理之后,接下来需要使用 V8 的调试工具来具体查看 JavaScript 的编译和运行信息,从而加深我们对 V8 的编译过程认知。

1 D8 调试工具

如果想了解 JavaScript 在 V8 中的编译时和运行时信息,可以使用调试工具 D8。D8 是 V8 引擎的命令行 Shell,可以查看 AST 生成、中间代码 ByteCode、优化代码、反优化代码、优化编译器的统计数据、代码的 GC 等信息。D8 的安装方式有很多,如下所示:

  • 方法一:根据 V8 官方文档 Using d8 以及 Building V8 with GN 进行工具链的下载和编译
  • 方法二:使用别人已经编译好的 D8 工具,可能版本会有滞后性,例如 Mac 版
  • 方法三:使用 JavaScript 引擎版本管理工具,例如 jsvu,可以下载到最新编译好的 JavaScript 引擎

本文使用方法三安装 v8-debug 工具,安装完成后执行 v8-debug --help 可以查看有哪些命令:

# 执行 help 命令查看支持的参数
v8-debug --help

Synopsis:
  shell [options] [--shell] [<file>...]
  d8 [options] [-e <string>] [--shell] [[--module|--web-snapshot] <file>...]

  -e        execute a string in V8
  --shell   run an interactive JavaScript shell
  --module  execute a file as a JavaScript module
  --web-snapshot  execute a file as a web snapshot

SSE3=1 SSSE3=1 SSE4_1=1 SSE4_2=1 SAHF=1 AVX=1 AVX2=1 FMA3=1 BMI1=1 BMI2=1 LZCNT=1 POPCNT=1 ATOM=0
The following syntax for options is accepted (both '-' and '--' are ok):
  --flag        (bool flags only)
  --no-flag     (bool flags only)
  --flag=value  (non-bool flags only, no spaces around '=')
  --flag value  (non-bool flags only)
  --            (captures all remaining args in JavaScript)

Options:
    # 打印生成的字节码
  --print-bytecode (print bytecode generated by ignition interpreter)
        type: bool  default: --noprint-bytecode

    
    # 跟踪被优化的信息
     --trace-opt (trace optimized compilation)
        type: bool  default: --notrace-opt
  --trace-opt-verbose (extra verbose optimized compilation tracing)
        type: bool  default: --notrace-opt-verbose
  --trace-opt-stats (trace optimized compilation statistics)
        type: bool  default: --notrace-opt-stats

    # 跟踪去优化的信息
  --trace-deopt (trace deoptimization)
        type: bool  default: --notrace-deopt
  --log-deopt (log deoptimization)
        type: bool  default: --nolog-deopt
  --trace-deopt-verbose (extra verbose deoptimization tracing)
        type: bool  default: --notrace-deopt-verbose
  --print-deopt-stress (print number of possible deopt points)

    
    # 查看编译生成的 AST
  --print-ast (print source AST)
        type: bool  default: --noprint-ast

    # 查看编译生成的代码
  --print-code (print generated code)
        type: bool  default: --noprint-code

    # 查看优化后的代码
  --print-opt-code (print optimized code)
        type: bool  default: --noprint-opt-code

    # 允许在源代码中使用 V8 提供的原生 API 语法
  --allow-natives-syntax (allow natives syntax)
        type: bool  default: --noallow-natives-syntax

2 生成 AST

我们编写一个 index.js 文件,在文件中写入 JavaScript 代码,执行一个简单的 add 函数:

function add(x, y) {
    return x + y
}

console.log(add(1, 2));

使用 --print-ast 参数可以打印 add 函数的 AST 信息:

v8-debug --print-ast ./index.js

[generating bytecode for function: ]
--- AST ---
FUNC at 0
. KIND 0
. LITERAL ID 0
. SUSPEND COUNT 0
. NAME ""
. INFERRED NAME ""
. DECLS
. . FUNCTION "add" = function add
. EXPRESSION STATEMENT at 41
. . ASSIGN at -1
. . . VAR PROXY local[0] (0x7fb8c080e630) (mode = TEMPORARY, assigned = true) ".result"
. . . CALL
. . . . PROPERTY at 49
. . . . . VAR PROXY unallocated (0x7fb8c080e6f0) (mode = DYNAMIC_GLOBAL, assigned = false) "console"
. . . . . NAME log
. . . . CALL
. . . . . VAR PROXY unallocated (0x7fb8c080e470) (mode = VAR, assigned = true) "add"
. . . . . LITERAL 1
. . . . . LITERAL 2
. RETURN at -1
. . VAR PROXY local[0] (0x7fb8c080e630) (mode = TEMPORARY, assigned = true) ".result"

[generating bytecode for function: add]
--- AST ---
FUNC at 12
. KIND 0
. LITERAL ID 1
. SUSPEND COUNT 0
. NAME "add"
. PARAMS
. . VAR (0x7fb8c080e4d8) (mode = VAR, assigned = false) "x"
. . VAR (0x7fb8c080e580) (mode = VAR, assigned = false) "y"
. DECLS
. . VARIABLE (0x7fb8c080e4d8) (mode = VAR, assigned = false) "x"
. . VARIABLE (0x7fb8c080e580) (mode = VAR, assigned = false) "y"
. RETURN at 25
. . ADD at 34
. . . VAR PROXY parameter[0] (0x7fb8c080e4d8) (mode = VAR, assigned = false) "x"
. . . VAR PROXY parameter[1] (0x7fb8c080e580) (mode = VAR, assigned = false) "y"

我们以图形化的方式来描述生成的 AST 树:

VAR PROXY 节点在真正的分析阶段会连接到对应地址的 VAR 节点。

3 生成字节码

AST 会经过 Ignition 解释器的 BytecodeGenerator 函数生成字节码(中间表示),我们可以通过 --print-bytecode 参数来打印字节码信息:

v8-debug --print-bytecode ./index.js

[generated bytecode for function:  (0x3ab2082933f5 <SharedFunctionInfo>)]
Bytecode length: 43
Parameter count 1
Register count 6
Frame size 48
OSR nesting level: 0
Bytecode Age: 0
         0x3ab2082934be @    0 : 13 00             LdaConstant [0]
         0x3ab2082934c0 @    2 : c3                Star1 
         0x3ab2082934c1 @    3 : 19 fe f8          Mov <closure>, r2
         0x3ab2082934c4 @    6 : 65 52 01 f9 02    CallRuntime [DeclareGlobals], r1-r2
         0x3ab2082934c9 @   11 : 21 01 00          LdaGlobal [1], [0]
         0x3ab2082934cc @   14 : c2                Star2 
         0x3ab2082934cd @   15 : 2d f8 02 02       LdaNamedProperty r2, [2], [2]
         0x3ab2082934d1 @   19 : c3                Star1 
         0x3ab2082934d2 @   20 : 21 03 04          LdaGlobal [3], [4]
         0x3ab2082934d5 @   23 : c1                Star3 
         0x3ab2082934d6 @   24 : 0d 01             LdaSmi [1]
         0x3ab2082934d8 @   26 : c0                Star4 
         0x3ab2082934d9 @   27 : 0d 02             LdaSmi [2]
         0x3ab2082934db @   29 : bf                Star5 
         0x3ab2082934dc @   30 : 63 f7 f6 f5 06    CallUndefinedReceiver2 r3, r4, r5, [6]
         0x3ab2082934e1 @   35 : c1                Star3 
         0x3ab2082934e2 @   36 : 5e f9 f8 f7 08    CallProperty1 r1, r2, r3, [8]
         0x3ab2082934e7 @   41 : c4                Star0 
         0x3ab2082934e8 @   42 : a9                Return 
Constant pool (size = 4)
0x3ab208293485: [FixedArray] in OldSpace
 - map: 0x3ab208002205 <Map>
 - length: 4
           0: 0x3ab20829343d <FixedArray[2]>
           1: 0x3ab208202741 <String[7]: #console>
           2: 0x3ab20820278d <String[3]: #log>
           3: 0x3ab208003f09 <String[3]: #add>
Handler Table (size = 0)
Source Position Table (size = 0)
[generated bytecode for function: add (0x3ab20829344d <SharedFunctionInfo add>)]
Bytecode length: 6
// 接受 3 个参数, 1 个隐式的 this,以及显式的 x 和 y
Parameter count 3
Register count 0
// 不需要局部变量,因此帧大小为 0 
Frame size 0
OSR nesting level: 0
Bytecode Age: 0
         0x3ab2082935f6 @    0 : 0b 04             Ldar a1
         0x3ab2082935f8 @    2 : 39 03 00          Add a0, [0]
         0x3ab2082935fb @    5 : a9                Return 
Constant pool (size = 0)
Handler Table (size = 0)
Source Position Table (size = 0)

add 函数主要包含以下 3 个字节码序列:

// Load Accumulator Register
// 加载寄存器 a1 的值到累加器中
Ldar a1
// 读取寄存器 a0 的值并累加到累加器中,相加之后的结果会继续放在累加器中
// [0] 指向 Feedback Vector Slot,Ignition 会收集值的分析信息,为后续的 TurboFan 优化做准备
Add a0, [0]
// 转交控制权给调用者,并返回累加器中的值
Return 

这里 Ignition 的解释执行这些字节码采用的是一地址指令结构的寄存器架构。

关于更多字节码的信息可查看 Understanding V8’s Bytecode。

4 优化和去优化

JavaScript 是弱类型语言,不会像强类型语言那样需要限定函数调用的形参数据类型,而是可以非常灵活的传入各种类型的参数进行处理,如下所示:

function add(x, y) { 
    // + 操作符是 JavaScript 中非常复杂的一个操作
    return x + y
}

add(1, 2);
add('1', 2);
add(, 2);
add(undefined, 2);
add([], 2);
add({}, 2);
add([], {});

为了可以进行 + 操作符运算,在底层执行的时候往往需要调用很多 API,比如 ToPrimitive(判断是否是对象)、ToString、ToNumber 等,将传入的参数进行符合 + 操作符的数据转换处理。

在这里 V8 会对 JavaScript 像强类型语言那样对形参 x 和 y 进行推测,这样就可以在运行的过程中排除一些副作用分支代码,同时这里也会预测代码不会抛出异常,因此可以对代码进行优化,从而达到最高的运行性能。在 Ignition 中通过字节码来收集反馈信息(Feedback Vector),如下所示:

为了查看 add 函数的运行时反馈信息,我们可以通过 V8 提供的 Native API 来打印 add 函数的运行时信息,具体如下所示:

function add(x, y) {
    return x + y
}

// 注意这里默认采用了 ClosureFeedbackCellArray,为了查看效果,强制开启 FeedbackVector
// 更多信息查看: A lighter V8:https://v8.dev/blog/v8-lite
%EnsureFeedbackVectorForFunction(add);
add(1, 2);
// 打印 add 详细的运行时信息
%DebugPrint(add);

通过 --allow-natives-syntax 参数可以在 JavaScript 中调用 %DebugPrint 底层 Native API(更多 API 可以查看 V8 的 runtime.h 头文件):


点击链接查看原文V8 编译浅谈,关注公众号【阿里技术】获取更多福利!

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

TML 实例

<!DOCTYPEhtml><html><head><metacharset="utf-8"><title>菜鸟教程(runoob.com)</title></head><body><h1>我的第一个标题</h1><p>我的第一个段落。</p></body></html>

实例解析

  • DOCTYPE 声明了文档类型

  • 位于标签 <html> 与 </html> 描述了文档类型

  • 位于标签 <body> 与 </body> 为可视化网页内容

  • 位于标签 <h1> 与 </h1> 作为一个标题使用

  • 位于标签 <p> 与 </p> 作为一个段落显示

<!DOCTYPE html> 在HTML5中也是描述了文档类型。

什么是HTML?

HTML 是用来描述网页的一种语言。

  • HTML 指的是超文本标记语言: HyperText Markup Language

  • HTML 不是一种编程语言,而是一种标记语言

  • 标记语言是一套标记标签 (markup tag)

  • HTML 使用标记标签来描述网页

  • HTML 文档包含了HTML 标签文本内容

  • HTML文档也叫做 web 页面

HTML 标签

HTML 标记标签通常被称为 HTML 标签 (HTML tag)。

  • HTML 标签是由尖括号包围的关键词,比如 <html>

  • HTML 标签通常是成对出现的,比如 <b> 和 </b>

  • 标签对中的第一个标签是开始标签,第二个标签是结束标签

  • 开始和结束标签也被称为开放标签和闭合标签

<标签>内容</标签>

HTML 元素

"HTML 标签" 和 "HTML 元素" 通常都是描述同样的意思.

但是严格来讲, 一个 HTML 元素包含了开始标签与结束标签,如下实例:

HTML 元素:

<p>这是一个段落。</p>

Web 浏览器

Web浏览器(如谷歌浏览器,Internet Explorer,Firefox,Safari)是用于读取HTML文件,并将其作为网页显示。

浏览器并不是直接显示的HTML标签,但可以使用标签来决定如何展现HTML页面的内容给用户:

HTML 网页结构

下面是一个可视化的HTML页面结构:

<html>

<head>

<title>页面标题</title>

</head>

<body>

<h1>这是一个标题</h1>

<p>这是一个段落。</p>

<p>这是另外一个段落。</p>

</body>

</html>

只有 <body> 区域 (白色部分) 才会在浏览器中显示。

HTML版本

从初期的网络诞生后,已经出现了许多HTML版本:

版本发布时间
HTML1991
HTML+1993
HTML 2.01995
HTML 3.21997
HTML 4.011999
XHTML 1.02000
HTML52012
XHTML52013

<!DOCTYPE> 声明

<!DOCTYPE>声明有助于浏览器中正确显示网页。

网络上有很多不同的文件,如果能够正确声明HTML的版本,浏览器就能正确显示网页内容。

doctype 声明是不区分大小写的,以下方式均可:

<!DOCTYPE html>

<!DOCTYPE HTML>

<!doctype html>

<!Doctype Html>

通用声明

HTML5

<!DOCTYPE html>

HTML 4.01

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"

"http://www.w3.org/TR/html4/loose.dtd">

XHTML 1.0

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

查看完整网页声明类型 DOCTYPE 参考手册。

中文编码

目前在大部分浏览器中,直接输出中文会出现中文乱码的情况,这时候我们就需要在头部将字符声明为 UTF-8。

HTML 实例

<!DOCTYPEhtml><html><head><metacharset="UTF-8"><title>页面标题</title></head><body><h1>我的第一个标题</h1><p>我的第一个段落。</p></body></html>

如您还有不明白的可以在下面与我留言或是与我探讨QQ群308855039,我们一起飞!

者:[美] 里德 · 霍夫曼,微软的董事会成员,曾任PayPal的创始董事

出版社:中信出版社

出版时间:2019年9月

在闪电式扩张的三种核心方法中,第一种也是最基础的方法是设计能实现指数增长的创新商业模式。

互联网时代的创业故事就是这种创新商业模式的故事。不妨回想一下.com时代,其时间范围大致是从1995年网景公司首次公开募股到2000年纳斯达克开始崩盘。在此期间,大量初创企业和几乎每家老牌公司都试图做好互联网业务,但几乎所有公司都失败了。原因在于,大多数公司只是试图将现有商业模式剪切并粘贴到新的网络媒体上。

你不能将一个物种的心脏移植到另一个物种身体里,并期望它茁壮成长。

如果你在1995年问股票市场分析师哪些公司最有可能主宰互联网,多数人都会指向微软和时代华纳这种已经占据市场的巨头。还有些人会提到像eToys这样的“单一业务”网络初创企业,它结合了久经考验的商业模式和新颖的在线媒体。

然而,当网络泡沫破灭后的一地狼藉消失时,仍然生气勃勃的最成功的公司是围绕全新商业模式设计的少数初创企业,比如亚马逊、eBay和谷歌。像eToys这种公司试图在各种市场上模仿亚马逊,但没有亚马逊的前台和后台创新,一旦金融市场开始要求利润而不仅仅是昂贵的收入增长,它们就会灰飞烟灭。

从那时起,同样的故事在多次浪潮中不断重演。当然,这些成功案例都是科技公司。但正如我们所见,仅靠技术创新是不够的,即使它对未来的影响巨大。像Craigslist(大型网上免费分类广告网站)、维基百科和IMDb(互联网电影数据库)这样的服务是有影响力的早期互联网创新者,仍然从未靠自身实现大量(财务)价值。

当创新技术通过创新商业模式推出创新产品和服务时,将真正创造价值。尽管谷歌、阿里巴巴和脸书的商业模式看似显而易见,甚至是必然的,但从事后来看,它们在推出时并未受到广泛赞赏。要真正理解为何这些商业模式能成功,我们首先需要明确定义“商业模式”。

正如安德烈亚·奥万斯在发表于2015年1月《哈佛商业评论》上的文章《什么是商业模式?》中恰如其分地指出的,它足以让你绞尽脑汁!就本书而言,我们将重点关注基本定义:一家公司的商业模式描述了它如何通过生产、销售和支持产品来创造财务收益。

亚马逊、谷歌和脸书等公司与普通公司,甚至其他成功高科技公司的区别在于,它们始终能设计和执行使自身快速实现庞大规模和可持续竞争优势的商业模式。当然,没有一种适用于所有公司的完美商业模式,而且试图找到这样一种商业模式也是浪费时间。

但多数优秀的商业模式都有某些共同特征。如果你想找到最适合你公司的商业模式,那么在设计时应该尽量放大四个关键增长因素,并尽量缩小两个关键增长限制因素。

第一个增长因素:市场规模

商业模式需要考虑的最基本的增长因素是市场规模。这种对市场规模的关注可能是显而易见的,对于初创企业来说,它是融资演讲稿最基本的内容,但如果你想创建一家规模庞大的公司,就需要从基础开始,摒弃服务于过小市场的想法。

大型市场既有大量潜在客户,也有不同有效渠道来接触这些客户。后面这一点很重要,由“世界上每个人”组成的市场可能看起来很大,却无法以任何有效方式被接触到。我们在考察推广这个关键增长因素时,将深入讨论这个问题。

判断市场规模,或者什么是融资演讲稿和风险资本家常说的TAM(潜在市场规模)并不容易。预测潜在市场规模及其将来的增长前景,是闪电式扩张的主要不确定因素之一。但正如我们将在爱彼迎和优步的案例中看到的,当其他人仍畏葸不前时,正确预测市场规模并据此进行投资,也是获得意外高收益的主要机会之一。

理想情况下,市场本身也会快速增长,这使小型市场具有吸引力,并使大型市场的吸引力难以抗拒。

在硅谷,对风险投资的竞争给企业家施加了巨大压力,使他们将重点放在追求大型市场的想法上。风险投资公司可能向投资者—养老基金和大学捐赠基金等有限合伙人—筹集数亿甚至数十亿美元,这些投资者寻求高于市场的收益率,以补偿它们从私营公司身上碰运气,而不只是投资于可口可乐这类全球性大公司的风险。

为了实现这些高于市场的收益率,风险投资基金至少需要将投资者的钱翻3倍。1亿美元的风险投资基金需要在典型的基金生命周期(7~10年)内获得3亿美元收益,以实现高于市场的内部收益率(15%~22%)。一笔10亿美元的基金需要30亿美元收益。由于多数风险投资要么亏本,要么勉强维持收支平衡,风险资本家实现这些积极目标的唯一现实方式就是依靠少量非常成功的投资。

多数风险资本家都会根据市场规模过滤投资机会。如果一家公司无法实现“风投规模”(通常是年销售额至少为10亿美元的市场),那么大多数风险投资公司都不会投资,即使对方是一家好公司,原因只是它的规模还不足以帮助风险投资公司实现收益3倍于投资金额以上的目标。

评估市场规模时,除了从现有竞争者手中抢占市场份额之外,还要考虑如何通过降低成本和改进产品吸引新客户来扩大市场,这一点至关重要。

2014年,纽约大学斯特恩商学院的金融学教授阿斯沃斯·达莫达兰估计优步可能价值约60亿美元,根据是其最终赢得价值1 000亿美元的全球出租车市场的10%,也就是价值100亿美元的市场的能力。根据优步自己的预测,2016年,该公司处理了超过260亿美元的支付金额。可以肯定地说,100亿美元的市场是被严重低估的数字,因为优步及其竞争对手的易用性和低成本扩大了运输服务市场。

正如在线文件存储公司Box的创始人阿龙·利维在2014年的一条推文中指出的:“根据现有运营者的市场确定颠覆性创新者的市场规模,就好比根据1910年的马匹数量确定汽车行业的市场规模。”

导致低估市场规模的另一个因素是忽略了扩张到其他市场的可能。亚马逊最初叫亚马逊图书,定位为“地球上最大的书店”。但杰夫·贝佐斯一直打算将图书销售作为亚马逊向外扩张的桥头堡,以实现他的“百货商店”巨大愿景。如今,亚马逊主导了图书销售行业,但由于市场扩张强劲,图书销售额还不到亚马逊总销售额的7%。

在苹果公司的财务业绩中可以看到相同情况。2017年第一季度,苹果通过出售个人计算机创造了72亿美元收入,这是该公司开创并一度占据主导地位的领域。这个数字当然很漂亮,但是,在同一个财政季度,苹果的总收入高达784亿美元,这意味着苹果的原始市场只占其总销售额的不到10%。

我在格雷洛克的同事杰丽·陈曾帮助黛安娜·格林将VMware的虚拟化软件扩展为一项庞大的业务,杰丽指出:“每家价值10亿美元的企业都是从价值1000万美元的企业开始的。”

但无论是开辟新市场,扩大现有市场,还是依靠相邻市场获得投资者想要的“10亿美元级企业”,你都需要有一条通往目标的合理路径。这自然而然地引出了我最爱与企业家讨论的增长因素之一:推广。

第二个增长因素:推广

强大、可扩张的业务所需的第二个增长因素是推广。在硅谷,许多人都喜欢专注于打造用史蒂夫·乔布斯的名言来说“绝妙至极”的产品。打造出伟大产品肯定是好事—后面我们将讨论产品质量低劣这个增长限制因素,但是,一个残酷的事实是,推广出色的优秀产品几乎总能击败推广不佳的伟大产品。

Dropbox是一家拥有伟大产品的公司,但它的成功是因为其出色的推广。在里德的“规模化大师”播客采访中,该公司的创始人兼首席执行官德鲁·休斯敦说,他认为太多初创企业忽视了推广的重要性:

硅谷的大部分正统观念都是推出优秀产品。我认为这是因为硅谷的大多数公司都无法生存到推出产品的阶段之后。你必须擅长推出产品,然后你必须善于吸引用户,再然后你必须善于建立商业模式。如果你遗漏了这根链条中的任何环节,整个链条就会瓦解。

在“移动优先”时代,推广挑战变得更加严峻。在网络时代,搜索引擎优化和电子邮件链接广泛适用,而且是成功的推广渠道,但与之不同,移动应用程序商店几乎不提供偶然发现产品的机会。你在访问苹果或谷歌的应用程序商店时,是搜索某个特定产品,很少有人安装应用程序只是装着玩。

因此,成功的商业模式创新者(例如Instagram、WhatsApp、Snap)必须找到大范围推广其产品而无须花费很多钱的创造性方法。这些推广方法分为两大类:利用现有网络和病毒式传播。

1.利用现有网络

新公司很少有投资于广告宣传所需的渠道或资源,因此,它们必须找到创造性方法来利用现有网络推广产品。

当我在PayPal时,我们推广支付服务的主要工具之一是结算eBay上的交易。当时,eBay已经是电子商务领域最大的竞争者之一,并且到2000年年初已有1 000万注册用户。我们通过开发软件来利用这个用户群,使eBay卖家可以极其轻松地在其所有eBay商品中自动添加“用PayPal支付”按钮。令人惊讶的是,即使eBay自身有与之竞争的支付服务Billpoint,客户仍然会这样做!卖家需要手动将Billpoint添加到他们的每件商品中,而PayPal为他们代劳了。

多年以后,爱彼迎利用在线分类服务Craigslist实现了类似壮举。根据YC的迈克尔·塞贝尔的建议,爱彼迎建立了一个系统,允许并鼓励房东将他们的房源交叉发布到规模大得多的Craigslist上。房东被告知,“将你的房源从爱彼迎转发到Craigslist,会使你的月均收益增加500美元”,并且只需点击一个按钮即可实现。这需要严谨的技术技巧—与许多平台不同,Craigslist没有允许其他软件与之交互的应用程序编程接口(API),但它是用于推广创新而非产品创新的技术创新。“这是一种新颖的方法,”爱彼迎的创始人柏思齐谈到了整合,“没有其他网站能实现如此天衣无缝的整合。我们做得非常成功。”

当然,利用现有网络也会有缺点。现有网络提供的好处,现有网络也可以将其拿走。Zynga是一家领先的社交游戏公司,它利用脸书进行推广取得了巨大成功,但在脸书决定停止允许Zynga游戏玩家向脸书好友发布进度后,Zynga不得不大幅重新设计其推广模式。Zynga的创始人马克·平卡斯富有远见地建立了足够强大的垄断地位,这才能应变求存。

相比之下,在谷歌调整其算法,对其所称的“垃圾”网站内容降权之后,利用谷歌的搜索平台创造网站流量和广告收入的所谓内容农场(如Demand Media)从未恢复元气。

尽管存在这些危险,但利用现有网络可能是商业模式的关键部分,尤其是当这些网络可以提供“助推火箭”时,日后可以用病毒式传播或网络效应作为补充。

2.病毒式传播

当某个产品的用户带来新用户,而这些新用户又带来更多用户时,就会发生“病毒式”推广,这非常像传染性病毒从一个宿主传播到另一宿主的过程。病毒式传播可能是自然产生的—产生于产品的正常使用过程中,也可以通过某种奖励来激励。

推出领英后,我和团队投入了大量时间和精力来研究如何改善自然产生的病毒式传播;也就是说,如何让现有用户更方便地邀请好友使用这项服务。我们采用的一种方法是改进传播工具,它们中的一部分后来成了病毒式传播的标准工具,比如地址簿导入器。举例来说,我们开发了允许领英调取用户的Outlook联系人的软件,这使他们能非常轻松地邀请最重要的联系人。

但同样重要的是一种意想不到的病毒式传播来源。事实证明,用户希望将其领英主页作为互联网上的主要职业身份。这样一种将用户的全部详细职业履历集于一处的介绍页面,不仅为用户创造了价值,也为浏览页面的人创造了价值,同时让浏览者意识到他们应该拥有自己的领英简历。因此,我们增加了公开简历作为一个系统工具,以提升用户的价值定位和我们的病毒式增长率。

在PayPal,我们将自然产生的病毒式传播和激励产生的病毒式传播结合起来。支付产品本质上适合病毒式传播。如果有人使用PayPal通过电子邮件向你汇款,你必须设置一个账户才能收到款项。但我们通过货币激励措施增强了这种自然产生的病毒式传播。

如果你推荐一个朋友使用PayPal,你将得到10美元,你的朋友也将得到10美元。这种自然产生的病毒式传播和激励产生的病毒式传播的组合使PayPal每天增长7%~10%。随着PayPal网络的增长,我们将激励标准均降低到5美元,直至最终彻底取消了货币激励。

激励措施不一定是用货币,和PayPal一样,Dropbox使用了类似方法,结合自然产生的病毒式传播(用户与非用户共享文件时)和激励产生的病毒式传播(基本账户持有者每成功推荐一个用户,将获得500 MB的额外存储空间;高级账户持有者每成功推荐一个用户,将获得1 GB的额外存储空间)来实现增长。

尽管Dropbox通过投资与戴尔等著名计算机制造商建立了合作关系,但德鲁·休斯敦将推动该公司快速增长归功于病毒式传播,它帮助其用户数量在短短10天内从推出时的10万增长到20万,然后在短短7个月后飙升至100万。

如果你的推广策略侧重于病毒式传播,那么你还必须关注用户留存率。如果新用户刚进门就转身离开,那么吸引新用户进门并不能帮助你实现增长。根据德鲁的说法,Dropbox沮丧地发现,激活率显示只有40%的注册用户真正将文件存入Dropbox并将其链接到计算机。在接受我的“规模化大师”播客采访时,德鲁描述了一个让人回想起电视剧《硅谷》(Silicon Valley)的场景(但结局更幸福):

我们所做的是继续投资Craigslist,并送给任何在网站上停留时间达到半小时的访客40美元—一个穷人的可用性测试。我们就像在说:“好,请坐。你的电子邮件中有一封Dropbox的邀请函,请用这个电子邮件地址共享一个文件。”我们测试的5个人中没人成功,甚至没人接近成功。这真是令人震惊。我们想:“噢,天哪,这是有史以来最糟糕的产品。”于是我们在这张Excel电子表格中列出了大概80处不足,然后逐一打磨这些体验中的粗疏之处,这才看到我们的激活率开始攀升。

病毒式传播几乎总是需要免费或免费增值的产品(即,在某个节点前免费,然后用户必须付费升级,例如,Dropbox提供2GB的免费存储空间)。我们不记得有哪家公司利用付费产品的病毒式传播实现了大规模扩张。

最强大的推广创新之一是将两种策略结合起来。脸书通过利用社交网络自然产生的病毒式传播(用户邀请其他用户加入进来)和以校园为中心的现有网络(向大学逐家推广产品)来实现这一目标。当考虑网络效应时,我们将深入讨论脸书的推广策略。

第三个增长因素:高毛利率

企业家经常忽视的一个关键增长因素是高毛利率的力量。毛利润是销售额减去销售成本,它可能是长期单位经济的最佳指标。毛利率越高,每一美元销售额对公司来说就越有价值,因为这意味着每有一美元销售额,公司就有更多现金可用于增长和扩张。

许多高科技企业默认具有较高毛利率,这就是为什么这个因素经常被忽视。软件业务的毛利率很高,因为复制软件的成本基本为零。软件即服务(SaaS)业务的销货成本略高,因为它需要提供某种服务,但是由于有亚马逊这样的云提供商,这种成本一直在变小。

相比之下,“传统经济”业务的毛利率往往较低。和在商店销售商品或在餐馆提供餐点一样,种植小麦是一项低利润率业务。亚马逊的成功中最令人惊奇的一点是,它能建立基于零售业的大规模业务,而这通常是低利润率行业。即使是现在,亚马逊也非常依赖其高利润的软件即服务业务—亚马逊网络服务(AWS)。2016年,亚马逊网络服务占亚马逊营业收入的150%,这意味着零售业务实际上已经出现亏损。

我们在本书中关注的多数高价值公司的毛利率都超过60%、70%甚至80%。2016年,谷歌的总收入为546亿美元,销售额为897亿美元,毛利率为61%;脸书的总收入为239亿美元,销售额为276亿美元,毛利率为87%。2015年,领英的毛利率为86%。

正如我们讨论过的,亚马逊属于异常情况,2016年其总收入为477亿美元,销售额为1 360亿美元,毛利率为35%。然而,即使是亚马逊的毛利率,也高于通用电气这种“高利润”传统公司,后者在2016年的总收入为322亿美元,销售额为1 197亿美元,毛利率为27%。

高毛利率是一个强大的增长因素,因为正如下面所阐述的,并非所有收入都天生平等。这里的关键观点是,尽管毛利润对卖方来说很重要,但它们与买方无关。

你买东西时可曾考虑过毛利率?你会因为皇堡的利润率低于巨无霸而选择汉堡王而不选择麦当劳吗?通常,你只关注价格以及购买商品带来的享受。这意味着出售低利润产品并不一定比出售高利润产品更容易。因此,如果可能的话,公司应该设计高毛利率的商业模式。

其次,高毛利率业务对投资者具有吸引力,投资者通常会为此类业务产生现金的能力支付溢价。正如著名投资者比尔·格利在其2011年的博客文章中所写,“并非所有收入都天生平等”,“当所有条件相同时,投资者更喜欢在更高收入下创造出更高边际利润的公司。出售同一软件的更多拷贝(增量成本为零)是一项可以很好地进行规模化的业务”。

当公司为私营时,对投资者有吸引力使公司更容易以更高估值筹集更多资金(稍后我们将深入研究为什么这如此重要),并降低公司上市时的资金成本。这种获取资本的途径是能够为超高速增长融资的关键因素。

重要的是注意潜在毛利率与实际毛利率之间的差异。许多闪电式扩张公司,比如亚马逊或中国硬件制造商华为和小米,其产品定价都有意最大化市场份额而非毛利率。正如杰夫·贝佐斯喜欢说的,“你的利润就是我的机会”。小米明确将净利润率控制在1%~3%,它将这一做法的灵感归功于Costco(开市客)。在其他所有因素相同的情况下,投资者对潜在毛利率较高的公司估值总是远远高于已经最大化实际毛利率的公司。

最后,公司的大部分运营挑战都是根据收入或单位销量而非毛利率来衡量的。如果你有100万个客户,每年产生1亿美元销售额,那么不管你的毛利率是10%还是80%,为这些客户提供服务的成本并不会改变,你仍然需要雇用足够多的员工来响应他们的客服支持请求。但是,当你有8 000万美元而不是1 000万美元的毛利润可用时,更容易提供良好的客服支持。

相反,同样是达到1 000万美元毛利润,向每年产生1 250万美元销售额的12.5万名客户出售商品并提供服务,与向每年产生1亿美元销售额的100万名客户出售商品并提供服务相比,实现前一目标要容易得多。8倍的客户数量, 8倍的收入,意味着8倍的销售人员、客户服务代表、会计师等等。

设计高毛利率的商业模式可以让你获得更大的成功机会和更高的成功回报。正如我们将在后面的章节中看到的,高毛利率甚至帮助非科技企业[例如西班牙服装零售商Zara(飒拉)]成长为全球巨头。

第四个增长因素:网络效应

市场规模、推广和毛利率都是推动公司增长的重要因素,但最后一个增长因素的关键作用在于,它使这种增长能维持足够长时间,以建立具有极高价值和持久独家优势的市场。虽然过去20年前三个增长因素有所改善,但全球互联网使用率的增长将网络效应推向了经济生活中前所未有的水平。

网络效应变得日益重要,是技术在经济中的主导地位提高的主要原因之一。

1996年年底,世界上最有价值的5家公司是通用电气、荷兰皇家壳牌、可口可乐公司、NTT(日本电报电话公司)以及埃克森美孚,这些都是传统的工业和消费品公司,依赖庞大的规模经济和有数十年历史的品牌推动其价值。仅仅21年后,在2017年第四季度,这个名单就变得截然不同:苹果、谷歌、微软、亚马逊和脸书。这是一个非凡的转变。

事实上,虽然苹果和微软在1996年年底已经是知名公司,但亚马逊彼时只是一家私营初创企业,拉里·佩奇和谢尔盖·布林还是斯坦福大学的研究生,距离他们创建谷歌还有两年时间,而马克·扎克伯格尚未迎来他的犹太教成年礼。那么,这21年间发生了什么?网络时代来了,这就是答案。

现在,技术以我们祖辈无法想象的方式将所有人联系起来。目前,超过20亿人有智能手机(其中许多是由苹果公司制造,或使用谷歌的安卓操作系统),这使他们始终与全球一切网络息息相关。任何时候,他们都能找到世界上几乎任何信息(通过谷歌),购买世界上几乎任何产品(通过亚马逊或阿里巴巴),或与世界上几乎任何其他人交流(通过脸书、WhatsApp、Instagram、微信)。

在这个高度互联的世界中,越来越多的公司能够利用网络效应来实现超大规模增长和利润。

我们将在本书中使用通俗易懂的网络效应定义:当增加任何一个用户都会增加产品或服务对于其他用户的价值时,这种产品或服务就会产生积极的网络效应。

经济学家将这种效应称为“需求方规模经济”,或者更一般地称为“正外部性”。

网络效应的神奇之处在于它会产生正反馈循环,从而导致超线性增长并创造价值。这种超线性效应使网络中的任何节点都很难从现有选择切换到替代选择(“客户锁定”),因为任何新竞争者都几乎不可能与加入现有网络的价值相提并论。(这些网络中的节点通常是客户或用户,比如传真机的典型案例或最近的脸书案例,但也可能是数据要素或其他对于企业富有价值的基本资产。)

由此产生的“规模收益递增”现象通常会导致最终均衡,即单个产品或公司在市场中占主导地位并获得该行业的大部分利润。因此,聪明的企业家努力创造(聪明的投资者希望投资)这些网络效应初创企业也就不足为奇了。

从eBay到脸书再到爱彼迎的几代初创企业都利用这些驱动因素建立了市场主导地位。要实现这些目标,关键是准确理解网络效应的作用原理。我在格雷洛克的同事西蒙·罗思曼是全球首屈一指的网络效应专家之一,他为eBay开创了价值140亿美元的自动化市场。

西蒙警告说:“很多人都试图通过添加资料简介等方式利用网络效应,他们推论,‘市场上提供了资料简介这项功能,因此,如果我添加资料简介,就会产生网络效应’。”然而,建立网络效应的实际情况很复杂。最优秀的闪电式扩张公司不是简单地模仿具体功能,而是研究不同类型的网络效应并将其设计到公司的商业模式中。

【钛媒体作者介绍:本文内容节选自《闪电式扩张》作者,[美] 里德 · 霍夫曼。他是著名企业家和投资人,现任微软的董事会成员,是全球知名的职业人脉服务商LinkedIn 领英的创始人。曾任PayPal的创始董事】

《闪电式扩张》将会纳入钛媒体Pro版书库,敬请大家关注前沿书库的上新动态~每位Pro专业用户一年可以在书库中任意选择三本书,由钛媒体免费赠送哦~点击链接、登录,进入“前沿书库”选书:http://www.tmtpost.com/pro

  • 人工智能相关好书:http://www.tmtpost.com/3122712.html
  • 创业事项相关好书:http://www.tmtpost.com/2788508.html
  • 培养领导力相关好书:http://www.tmtpost.com/2678549.html

更多精彩内容,关注钛媒体微信号(ID:taimeiti),或者下载钛媒体App