什么是 VM,为什么动态语言需要 VM?

2022-09-01 16:35:54

因此,例如,Python和Java有VM,C和Haskell没有( 如果我错了,请纠正我)

想想两边有什么语言,我找不到原因。Java在很多方面都是静态的,而Haskell提供了很多动态功能。


答案 1

这与静态与动态无关。

相反,它是关于独立于底层硬件平台(“构建一次,随处运行” - 理论上......

实际上,这也与语言无关。可以编写一个 C 编译器来为 JVM 生成字节码。可以编写一个生成 x86 机器代码的 Java 编译器。


答案 2

让我们暂时忘记 VM(我保证,我们将回到下面的内容),并从这个重要事实开始:

C 没有垃圾回收。

对于提供垃圾回收的语言,必须有某种“运行时”/运行时环境/事物来执行它。

这就是为什么Python,Java和Haskell需要“运行时”,而C,没有,可以直接编译成原生代码。

请注意,psyco是一个Python优化器,它将Python代码编译为机器代码,但是,许多机器代码包括对C-Python运行时函数的调用,例如,等。PyImport_AddModulePyImport_GetModuleDict

Haskell/GHC与psyco编译的Python处于类似的状态。s被添加为简单的机器指令,但更复杂的东西分配对象等,调用运行时。Int

还有什么?

C没有“例外”

如果我们要向 C 中添加异常,我们生成的机器代码将需要为每个函数和每个函数调用做一些事情。

如果我们同时添加“闭包”,就会添加更多内容。

现在,与其在每个函数中重复这个样板机器代码,不如让它调用一个子程序来做必要的事情,比如PyErr_Occurred

所以现在,基本上每个原始的源代码行都映射到对某些函数的某些调用和较小的唯一部分。

但是,只要我们在原始源代码行中做这么多事情,为什么还要为机器代码而烦恼呢?

这里有一个想法(顺便说一句,让我们把这个想法称为“虚拟机”)。

让我们来表示你的Python代码,例如:

def has_no_letters(text):
  return text.upper() == text.lower()

作为内存中的数据结构,例如:

{ 'func_name': 'has_no_letters',
  'num_args': 1,
  'kwargs': [],
  'codez': [
    ('get_attr', 'tmp_a', 'arg_0', 'upper'),  # tmp_a = arg_0.upper
    ('func_call', 'tmp_b', 'tmp_a', []),  # tmp_b = tmp_a() # tmp_b = arg_0.upper()
    ('get_attr', 'tmp_c', 'arg_0', 'lower'),
    ('func_call', 'tmp_d', 'tmp_c', []),
    ('get_global', 'tmp_e', '=='),
    ('func_call', 'tmp_f', 'tmp_e', ['tmp_b', 'tmp_d']),
    ('return', 'tmp_f'),
  ]
}

现在,让我们编写一个执行此内存中数据结构的解释器。

让我们讨论一下与直接来自文本的解释器相比,这样做的好处,以及编译为机器代码的好处。

VM 相对于直接来自文本的解释器的优势

  • 在执行代码之前,VM 系统会提供所有语法错误。
  • 评估循环时,VM 系统不会在每次运行时都分析源代码。
    • 使 VM 比直接来自文本的解释器更快。
    • 因此,直接解释器使用长变量名运行速度较慢,使用短变量名运行速度更快。这鼓励人们编写蹩脚的数学家式代码,例如wt(f, d(o, e), s) <= th(i, s) + cr(a, p * d + o)

与编译为机器代码相比,VM 的优势

  • 描述程序的内存数据结构或“VM代码”可能比样板全机器代码更紧凑,后者一次又一次地为每行原始代码做同样的事情。这将使 VM 系统运行得更快,因为需要从内存中获取的“指令”更少。
  • 创建 VM 比创建编译器以编写机器代码要简单得多。您现在可能可以在不知道任何汇编/机器代码的情况下执行此操作。