V8 引擎:基于类型推测的性能优化原理

发表于 2年以前  | 总阅读数:407 次

介绍

本文的会介绍一些关于V8内基于推测的优化的技术,以此来告诉大家,为什么需要TypeScript。

我们将以一段函数的执行未展开,从函数执行的角度来看看,一段代码如何被执行,优化,再最后,你会了解,为什么TypeScript更好。

看完本文后,你不需要记住文章中出现的繁杂的指令和代码,只需要在你的脑海中存在一个印象,避免写出糟糕的代码,以及,尽量使用TypeScript。

如何执行代码?

作为介绍的第一部分,我们会用一段简短的篇幅带大家看看,你的代码如何被执行

当然,如果用简单的流程图表示,你可以把上面的过程理解为这样一个线性的执行过程,当然可能并不严谨,稍后我们会继续介绍。

下面让我们从一段具体的代码来看一下这个过程。

一段简单的代码

function add(x, y) {
    return x + y;
} 
console.log(add(1, 2))

如果你在chrome的DevTools console中运行这段代码,你可以看到预期的输出值3。

根据上面的流程图,这段代码被执行的第一步,是被解析器解析为AST,这一步我们用d8 shell 的Debug版本中使用 –print-ast 命令来查看V8内部生成的AST。

$ out/Debug/d8 --print-ast add.js
…-
-- AST ---
FUNC at 12
. KIND 0
. SUSPEND COUNT 0
. NAME "add"
. PARAMS
. . VAR (0x7fbd5e818210) (mode = VAR) "x"
. . VAR (0x7fbd5e818240) (mode = VAR) "y"
. RETURN at 23
. . ADD at 32
. . . VAR PROXY parameter[0] (0x7fbd5e818210) (mode = VAR) "x"
. . . VAR PROXY parameter[1] (0x7fbd5e818240) (mode = VAR) "y

很多人可能或多或少接触过AST的概念,这里不多赘述,只是用一张简单的图表示下上面的过程。

最开始,函数字面量add被解析为树形表示,其中一个子树用于参数声明,另外一个子树用于实际的的函数体。在解析阶段,不可能知道程序中名称和变量的绑定关系,这主要是因为“有趣的变量声明提升规则”以及JavaScript中的eval,此外还有其他原因。

一旦我们构建完成了AST,它便包含了从中生成可执行字节码的所有必要信息。AST随后被传递给BytecodeGenerator ,BytecodeGenerator 是属于Ignition 的一部分,它以函数为单位生成字节码(_其他引擎并不一定以函数为单位生成的_)。你也可以在d8中使用命令–print-bytecode来查看V8生成的字节码(或者用node端)

$ out/Debug/d8 --print-bytecode add.js
…[
generated bytecode for function: add]
Parameter count 3
Frame size 0
12 E> 0x37738712a02a @ 0 : 94 StackCheck
23 S> 0x37738712a02b @ 1 : 1d 02 Ldar a1
32 E> 0x37738712a02d @ 3 : 29 03 00 Add a0, [0]
36 S> 0x37738712a030 @ 6 : 98 Return
Constant pool (size = 0)
Handler Table (size = 16)

上面过程中为函数add生成了一个新的字节码对象,它接受三个参数,一个内部的this引用,以及两个显式形参x和y。该函数不需要任何的局部变量(所以栈帧大小为0),并且包含下面这四个字节码指令组成的序列

StackCheck
Ldar a1
Add a0, [0]
Return

为了解释这段字节码,我们首先需要从较高的层面来认知解释器如何工作。V8的解释器是基于寄存器架构(register machine)的(相对的是基于栈架构,也是早期V8版本中使用的 FullCodegen 编译器)。Ignition 会把指令序列都保存在解释器自身的(虚拟)寄存器中,这些寄存器部分被映射到实际CPU的寄存器中,而另外一部分会用实际机器的栈内存来模拟。

有两个特殊寄存器a0和a1对应着函数在机器栈(即内存栈)上的形式参数(在函数add这个例子中,有两个形参)。形参是在源代码中声名的参数,它可能与在运行时传递给函数的实际参数数量不同。每个字节码指令执行得到的最终值通常被保存在一个称作累加器(accumulator)的特殊寄存器中。堆栈指针(stack pointer )指向当前的栈帧或者说激活记录,程序计数器( program counter)指向指令字节码中当前正在执行的指令。下面我们看看这个例子中每条字节码指令都做了什么。

  • StackCheck 会将堆栈指针与一些已知的上限比较(实际上在V8中应该称作下限,因为栈是从高地址到低地址向下生长的)。如果栈的增长超过了某个阈值,就会放弃函数的执行,同时抛出一个 RangeError 来告诉我们栈溢出了。
  • Ldar a1将寄存器a1的值加载到累加器寄存器中(Ladr 表示 LoaD Accumulator Register)
  • Add a0, [0] 读取寄存器a0里的值,并把它加到累加器的值上。结果被再次放到累加器中。

为什么这条指令是这样的?以一条JS 语句为例

var dest = src1 + src2 // op dest, src1,src2

var dest += src; //op dest, src

+src; // op src

分别表示三地址指令,二地址指令,一地址指令,我在后面分别标注了转换后的机器指令。三地址和二地址指令都指定了运算后储存结果的位置。

但在一地址指令中,没有指定目标源。实际上,它会被默认存在一个累加器”(accumulator)的专用寄存器,保存计算结果。

其中Add运算符的[0]操作数指向一个「反馈向量槽( feedback vector slot)」,它是解释器用来储存有关函数执行期间看到的值的分析信息。在后面讲解TurboFan 如何优化函数的时候会再次回到这。

  • Return 结束当前函数的执行,并把控制权交给调用者。返回值是累加器中的当前值。

当最终生成了上面这段字节码后,会被送入的VM ,一般会由解释器进行执行,这种执行方式是最原始也是效率最低的。我们可以在下一部分了解到,这种原始的执行会经历什么。

关于字节码的解释,这里不会做过多的赘述,如果你感兴趣,可以扩展阅读 「Understanding V8’s Bytecode」 (https://medium.com/dailyjs/understanding-v8s-bytecode-317d46c94775) 一文,这篇字节码对V8的字节码的工作原理提供了一些深入的了解。

为什么需要优化

现在,我相信你已经对V8如何执行一段代码有了一个简单的认识。在正式进入我们的主题之前,还需要解释一个很关键的问题,为什么我们需要优化。为了回答这个问题,我们需要先看下下规范

关于规范的更多了解,可以去这里查找 https://tc39.es/ecma262/#sec-toprimitive

我们再以看最常见的 ToPrimitive 为例,需要经过非常繁琐的求值过程,而这些过程都是为了解决操作类型的动态性

在JavaScript中的“+”运算符已经是一个相当复杂的操作了,在最终执行一个数值相加之前必须进行大量的检查

而如果引擎要想让这些步骤是能够在几个机器指令内完成以达到峰值性能(与C++相媲美),这里有一个关键能力—-推测优化,通过假设可能的输入。例如,当我们知道表达式x+y中,x和y都是数字,那么我们就不需要处理任何一个是字符串或者其他更糟糕的情况—-操作数是任意类型的JavaScript对象,也就不需要对所有参数调用一遍 ToPrimitive 了。

换句话说,如果我们能够确定x,y 都是数字类型,我们自然就很容易对这个函数执行进行优化,消除冗余的IR指令。

「而从执行的角度来说,动态类型性能瓶颈很大程度是因为它的动态的类型系统,与静态类型的语言相比,JavaScript 程序需要额外的操作来处理类型的动态性,所以执行效率比较低。」

那么如何确认x,y都是数字,我们又如何优化呢?

基于推测的优化

因为 JavaScript 动态语言的特性,我们通常直到运行时才知道值的确切类型,仅仅观察源代码,往往不可能知道某个操作的可能输入值。所以这就是为什么我们需要推测,根据之前运行收集到的值的反馈,然后假设将来总会看到类似的值。这种方法听起来可能作用相当有限,但它已被证明适用于JavaScript这样的动态语言。

你可能会联想到CPU的分支预测能力,如果是这样,那么恭喜你,你并没有想错。

我们再回到这段代码

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

你可能已经想到了,作为一个动态类型的语言,推测的第一步,就是要收集到足够多的信息,来预测 add 在今后的执行中会遇到的类型。

所以,首先向你介绍反馈向量(Feedback Vector),它是我们执行预测最核心的成员之一:负责储存我们收集到的信息。

反馈向量(Feedback Vector)

当一段代码被初次执行时,它所执行的往往是解释器产生的字节码。当这段字节码每次的执行后,都会会产生一些反馈信息,这些反馈信息会被储存在「反馈向量」(过去叫类型反馈向量) 中,这个特殊的数据结构会被链接在闭包上。如果从对象结构的角度来看,反馈向量和其他相关的内容会是这样。

其中 SharedFunctionInfo,它包含了函数的一般信息,比如源位置,字节码,严格或一般模式。除此之外,还有一个指向上下文的指针,其中包含自由变量的值以及对全局对象的访问。

关于自由变量和约束变量的概念, 闭包 (计算机科学)

反馈向量的大致结构如下,slot是一个槽,表示向量表里面的一项,包含了操作类型和传入的值类型,

IC Slot IC Type Value
1 Call UNINIT
2 BinaryOp SignedSmall

比如,第二个是一个 BinaryOp 槽,二元操作符类似“+,-”等能够记录迄今为止看到的输入和输出的反馈。先不用纠结它的含义,后面我们会具体介绍。

如果你想查看你的函数所对应的反馈向量,可以在你的代码中加上专门的内部函数 %DebugPrint ,并且在d8中加上命令 –allow-natives-syntax 来检查特定闭包的反馈向量的内容。

源代码:

function add(x, y) {
return x + y;
} 
console.log(add(1, 2));
%DebugPrint(add);

在d8 使用这个命令 –allow-natives-syntax 运行,我们看到 :

$ out/Debug/d8 --allow-natives-syntax add.js
DebugPrint: 0xb5101ea9d89: [Function] in OldSpace
- feedback vector: 0xb5101eaa091: [FeedbackVector] in OldSpace
- length: 1
SharedFunctionInfo: 0xb5101ea99c9 <SharedFunctionInfo add>
Optimized Code: 0
Invocation Count: 1
Profiler Ticks: 0
Slot #0 BinaryOp BinaryOp:SignedSmall

我们看到调用次数(Invocation Count)是1,因为我们只调用了一次函数add。此时还没有优化代码(根据Optimized Code的值为0)。反馈向量的长度为1,说明里面只有一个槽,就是我们上面说到的二元操作符槽(BinaryOp Slot),当前反馈为 SignedSmall。

这个反馈SignedSmall代表什么?这表明指令Add只看到了SignedSmall类型的输入,并且直到现在也只产生了SignedSmall类型的输出。

但是什么是SignedSmall类型?JavaScript里面并不存在这种类型。实际上,SignedSmall来自己V8中的一种优化策略,它表示在程序中经常使用的小的有符号整数(V8将高位的32位表示整数,低位的全部置0来表示SignedSmall),这种类型能够获得特殊处理(其他JavaScript引擎也有类似的优化策略)。

「值的表示」

V8通常使用一种叫做指针标记(Pointer Tagging)的技术来表示值,应用这种技术,V8在每个值里面都设置一个标识。我们处理的大部分值都分配在JavaScript堆上,并且由垃圾回收器(GC)来管理。但是对某些值来说,总是将它们分配在内存里开销会很大。尤其是对于小整数,它们通常会用在数组索引和一些临时计算结果。

在V8中存在两种指针标识类型:分别是是Smi(即 Small Integer的缩写)和堆对象( HeapObject,就是JavaScript的引用类型),其中堆对象是分配在内存的堆中,图中的地址即指向堆中的某块地方。

我们用最低有效位来区分堆对象(标志是1)和小整数(标志是0)。对于64位结构上的Smi,至少有32位有效位(低半部)是一直被置为0。另外32位,也就是Word的上半部,是被用来储存32位有符号小整数的值。


仅仅是一次的执行,还不足以让引擎这么快下定决心,相信add 函数随后的执行都是Smi 类型。那么我们先来看看,如果在随后的执行中,我们传入不一样的类型会怎么样。

反馈向量的变化

反馈类型SignedSmall是指所有能用小整数表示的值。对于add操作而言,这意味着目前为止它只能看到输入类型为Smi,并且所产生的输出值也都是Smi(也就是说,所有的值都没有超过32位整数的范围)。下面我们来看看,当我们调用add的时候传入一个不是Smi的值会发生什么。

function add(x, y) {
return x + y;
}
console.log(add(1, 2));
console.log(add(1.1, 2.2));
//调用100ci
%DebugPrint(add);

在d8加入命令 –allow-natives-syntax ,然后看到下面结果

$ out/Debug/d8 --allow-natives-syntax add.js
DebugPrint: 0xb5101ea9d89: [Function] in OldSpace
…
- feedback vector: 0x3fd6ea9ef9: [FeedbackVector] in OldSpace
- length: 1
SharedFunctionInfo: 0x3fd6ea9989 <SharedFunctionInfo add>
Optimized Code: 0
Invocation Count: 2
Profiler Ticks: 0
Slot #0 BinaryOp BinaryOp:Number

首先,我们看到调用次数现在是2,因为运行了两次函数add。然后发现BinaryOp 槽的值现在变成了Number,这表明对于这个加法已经有传入了任意类型的数值(即非整数)。此外,这有一个反馈向量的状态迁移图,大致如下所示:

反馈状态从 None 开始,这表明目前还没有看到任何输入,所以什么都不知道。状态Any表明我们看到了不兼容的(比如number和string)输入和输出的组合。状态Any意味着Add(字节码中的)是多态。相比之下,其余的状态表明Add都是单态(monomorphic),因为看到的输入和产生的都是相同类型的值。下面是图中名词解释:

  • SignedSmall 表示所有的值都是小整数(有效数值为是32位或者31位,取决于Word的在不同架构上的大小),均表示为Smi。
  • Number 表明所有的值都常规数字 (这包括小整数).
  • NumberOrOddball 包括其他能被转换成 Numberundefinednulltruefalse
  • String :所有输入值都是字符串
  • BigInt 表示输入都是大整数。

需要注意一点,反馈只能在这个图中前进(从 NoneAny),不能回退。如果真的那样做,那么我们就会有陷入去优化循环的风险。那样情况下,优化编译器发现输入值与之前得到反馈内容不同,比如之前解释器生成的反馈是 Number,但现在输入值出现了 String,这时候已经生成的反馈和优化代码就会失效,并回退到解释器生成的字节码版本。当下一次函数再次变热(hot,多次运行),我们将再次优化它,如果允许回退,这时候优化编译器会再次生成相同的代码,这意味着会再次回到 Number 的情况。如果这样无限制的回退去优化,再优化,编译器将会忙于优化和去优化,而不是高速运行 JavaScript 代码。

优化管道(The Optimization Pipeline)

现在我们知道了解释器Ignition 是如何为函数add收集反馈,下面来看看优化编译器如何利用反馈生成最小的代码,因为_越小的机器指令代码块,意味着更快的速度_。为了观察,我将使用一个特殊的内部函数OptimizeFunctionOnNextCall()在特定的时间点触发V8对函数的优化。我们经常使用这些内部函数以非常特定的方式对引擎进行测试。

function add(x, y) {
return x + y;
} 
add(1, 2); // Warm up with SignedSmall feedback.
%OptimizeFunctionOnNextCall(add);
add(1, 2); // Optimize and run generated code

在这里,给函数add传递两个整数型值来明确call site “x + y”的反馈会被预热为小整数(表示_这个call site全部传递的都是小整数,对于优化引擎来说将来得到的输入也会是小整数_),并且结果也是属于小整数范围。然后我们告诉V8应该在下次调用函数add的时候去优化它(用TurboFan ),最终再次调用add,触发优化编译器运行生成机器码。

TurboFan 拿到之前为函数add生成的字节码,并从函数add的反馈向量表里提取出相关的反馈。优化编译器将这些信息转换成一个图表示,再将这个图表示传递给前端,优化以及后端的各个阶段(见上图)。在本文不会详细展开这部分内容,这是另一个系列的内容了。我们要了解的是最终生成的机器码,并看看优化推测是如何工作的。你可以在d8中加上命令 –print-opt-code来查看由TurboFan 生成的优化代码。

这是由TurboFan 在x64架构上生成的机器码,这里省略了一些无关紧要的技术细节(,下面就来看看这些代码做了什么。

# Prologue
leaq rcx,[rip+0x0]
movq rcx,[rcx-0x37]
testb [rcx+0xf],0x1
jnz CompileLazyDeoptimizedCode
push rbp
movq rbp,rsp
push rsi
push rdi
cmpq rsp,[r13+0xdb0]
jna StackCheck

第一段代码检查对象是否仍然有效(对象的形状是否符合之前生成机器码的那个),或者某些条件是否发生了改变,这就需要丢弃这个优化代码。这部分具体内容可以参考 Juliana Franco 的 “Internship on Laziness“。一旦我们知道这段代码仍然有效,就会建立一个栈帧并且检查堆栈上是否有足够的空间来执行代码。

# Check x is a small integer
movq rax,[rbp+0x18]
test al,0x1
jnz Deoptimize
# Check y is a small integer
movq rbx,[rbp+0x10]
testb rbx,0x1
jnz Deoptimize
# Convert y from Smi to Word32
movq rdx,rbx
shrq rdx, 32
# Convert x from Smi to Word32
movq rcx,rax
shrq rcx, 32

然后从函数主体开始。我们从栈中读取参数x和y的值(相对于帧指针rbp,比如rbp+1这样的地址,请参考栈帧概念),然后检查两个参数是否都是 Smi 类型(因为根据“+”得到的反馈,两个输入总是Smi)。这一步是通过测试最低有效位来完成。一旦确定了参数都是Smi,我们需要将它转换成32位表示,这是通过将值右移32位来完成的。如果x或y不是Smi,则会立即终止执行优化代码,接着负责去优化的模块就会恢复到之前解释器生成的函数add的代码(即字节码)。

# Add x and y (incl. overflow check)
addl rdx,rcx
jo Deoptimize
# Convert result to Smi
shlq rdx, 32
movq rax,rdx
# Epilogue
movq rsp,rbp
pop rbp
ret 0x18

然后我们继续执行对输入值的整数加法,这时需要明确地测试溢出,因为加法的结果可能超出32位整数的范围,在这种情况下就要返回到解释器版本,并在随后将add的反馈类型提升为Number(之前说过,反馈类型的改变只能前进)。

最后我们通过将带符号的32位值向上移动32位,将结果转换回Smi表示,并将结果返回存到累加器rax 。

我们现在可以看到生成的代码是高度优化的,并且适用于专门的反馈。它完全不去处理其他数字,字符串,大整数或任意JavaScript对象,只关注目前为止我们所看到的那种类型的值。这是使许多JavaScript应用程序达到最佳性能的关键因素。

为什么需要TypeScript

在上面的介绍中,我们竭力避免了对JavaScript 对象的访问,如果有对象加入,这将会变成一个很复杂的话题。但为了更好的展开这个话题,我们还是需要提一下,关于对象的优化是V8中极其重要的一部分。例如,以下面这个对象为例

var o = {
    x: ''
}

var o1 = {
    x: ''
    y
}

//o1. o2

对于像 o.x这样的属性访问,若o始终具有相同的形状(形状同结构,即相同的属性以及属性是相同的顺序,例如o的结构一直是{x:v},其中v的类型是String),我们会把如何获得o.x的过程信息缓存起来,构造成一个隐藏类( Hidden Class)。在随后执行相同的字节码时,不需要再次搜索对象o中x的位置。这种底层实现被称为内联缓存– inline cache (IC)。

你可以在Vyacheslav Egoro写的这篇文章 “What’s up with monomorphism?” 中了解更多关于ICs和属性访问的细节。

总而言之,你现在应该了解到,作为一门弱类型的语言,从最早的SELF和smalltalk 语言开始,研究者就在不断去优化这种弱类型语言的执行效率。

「从执行的角度来说,动态类型性能瓶颈很大程度是因为它的动态的类型系统,与静态类型的语言相比, JavaScript 程序需要额外的操作来处理类型的动态性,所以执行效率比较低。」

说了这么多,最关键的一点

「确定你的代码将要看到的类型很重要」

再加上另外一句话:

「作为动态语言,你的程序可能在90%的时间里,都在处理和代码逻辑无关的事情。即:确认你的代码是什么形状」

从传统的JavaScript 角度来说,

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

你无法很好的保证 add 函数将要看到的类型,哪怕你确实想要这么做。但在一个大型的系统中,维护每一个函数和对象的形状,极其困难。

你可能在前99次都保证了add 看到的都是Smi 类型,但是在第100次,add 看到了一个String,而在这之前,优化编辑器,即TurboFan,已经大胆的推测了你的函数只会看到Smi,那么这时候

Ops!

优化编辑器将不得不认为自己做了个错误的预测,它会立即把之前的优化丢掉。从字节码开始重新执行。

而如果你的代码一直陷入优化<->去优化的怪圈,那么程序执行将会变慢,慢到还不如不优化。

大多数的浏览器都做了限制,当优化/去优化循环发生的时候会尝试跳出这种循环。比如,如果 JIT 做了 10 次以上的优化并且又丢弃的操作,那么就不继续尝试去优化这段代码。

所以,到这里你应该明白了,有两点准则:

  1. 「确保你的代码是什么形状很重要」

但比第一条更重要的是:

  1. 「确保你的代码固定在某个形状上」

而编写TypeScript ,从工程和语言的层面上帮助你解决了这两个准则,你可以畅快的使用TypeScript,而无需担心你是否不小心违背了上面两条准则。

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/YHD28SvIMTyJOTcj24wyuw

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237267次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8108次阅读
 目录