繁体   English   中英

编程操作需要多长时间?

[英]How Long Do Programming Operations Take?

我们都可能从 Peter Norvigís 的文章《十年内自学编程》中看到这张表:

电脑动作 时间 [以纳秒为单位]
执行典型指令 1
从 L1 缓存 memory 获取 0.5
分支误判 5
从二级缓存 memory 获取 7
互斥锁/解锁 25
从主 memory 获取 100
通过 1Gbps 网络发送 2K 字节 20,000
从 memory 顺序读取 1MB 250,000
从新磁盘位置获取 [seek] 8,000,000
从磁盘顺序读取 1MB 20,000,000
将数据包从美国发送到欧洲并返回 150,000,000

问题是,我不知道这在现实世界中意味着什么。

你看,现在所有最流行的语言都非常抽象,你基本上不需要自己接触一块 memory。 我只知道“我输入了东西,计算机 go brrrrr”,或者“我输入了东西,计算机 go [TASK NOT RESPONDING] 然后 Z34D1F91FB2E514B8566FABrr1A75A”。 当您只知道函数和库并且唯一可用的时序信息以位和字节为单位时,优化很难。

我将理解我最终编写的每一行代码的 memory 含义,但现在,我想知道的是:

您的平均编程操作需要多长时间?

像:

  • 读取和操作字符串,
  • 创建基元,
  • 比较两个基元,
  • 比较两个对象(由于许多不同的实现,可能没有简单的答案),
  • 迭代一个循环(我假设它属于“典型指令”),
  • 将控制权转移到(或“调用”)方法/函数,
    • 与将所有内容放在一个方法中?
  • 访问一个“列表”(而不是一个数组,我认为这也是一个典型的指令),
  • 初始化 object,
    • 关于这一点,当 class 被实例化时,object 中包含的每个方法是否会导致运行时损失?
    • 超类中的方法是否会导致额外的运行时损失?
    • 原型 inheritance(如果在该语言中可用)是否比经典的占用空间少?
  • 创建匿名类/更改现有函数(取决于语言)与完全创建新的 class,
    • 运行时占用空间与 Lambda 表达式(如果在该语言中可用)?
  • 并创建一个从方法中读取值的新变量,而不是不断地读取相同的方法?
    • 例如int foo = bar.getValue(); if (foo == 0 || foo == 1)... int foo = bar.getValue(); if (foo == 0 || foo == 1)...
    • if (bar.getValue() == 0 || bar.getValue() == 1)...

我也对其他语言的含义感兴趣——也就是说,如果某些语言在某些任务上比其他语言更好。 例如,我知道与 Java 之类的语言相比,JS 中的数组操作很糟糕,而且我想在 lisp 之类的语言中更改函数会容易得多,那么在某些语言中哪些操作会比其他语言更有效?

谢谢!

一方面,数字是有问题的。 我会这样纠正:

  • 执行典型指令:0.5ns
  • 从 L1 缓存 memory 获取:2ns
  • 分支误判:20-50ns
  • 从二级缓存中获取:6ns
  • 互斥锁/解锁:无竞争时为 2-5ns,拥塞时为 150ns,即当其他 CPU 内核竞争互斥锁时。 这是访问最近被另一个 CPU 内核修改的缓存行的成本,这些缓存一致性协议很慢。

其中的 rest 或多或少是正确的,尽管 SSD 将磁盘延迟提高了几个数量级。

现在回到你的问题。 有 3 组语言/运行时。

  1. 编译为本机代码,如 C++ 或 Golang。 通常但并非总是如此,这是可用的最快选项。

  2. 解释,像大多数 Lua 和 Python 实现。 解释器在设计上很慢,但是其中许多调用了许多本机函数。 对于这些东西,成本接近本机代码,没有用于间接跳转和编组 arguments / 返回值的少量开销。 例如,如果您编写并测试读取和操作大量字符串的 python 代码,则可以肯定 Python 运行时将调用本机代码,如果您使用内置方法操作它们,将会非常快。

  3. 及时编译,如 Java、.NET 或现代 JavaScript 实现。 这些变化很大。 最佳 C# 或 Java 实现接近本机代码的性能。 JavaScript 通常不会,因为该语言是动态类型的,并且很难从中生成快速代码。

读取和操作字符串

如果一个字符串需要 1MB,那么在你的表中读取它就是“从内存中顺序读取 1MB”。 操作会有所不同,具体取决于您对它们的确切操作。

创建基元

如果您的意思是定义一个局部变量,对于使用本机堆栈的语言(所有编译的,以及其他一些如 C#),成本是“执行典型指令”。 对于只使用堆的语言,成本是 malloc(),稍微贵一些。

比较两个原语

对于强类型语言“执行典型指令”,对于动态类型“间接跳转”。

比较两个对象

变化很大。 比较指针是“执行典型指令”。 比较值,取决于值,可能会非常昂贵。

迭代一个循环

事实上,对于大多数语言来说,这是典型的指令,但是其中一些有时可以在长循环中更快地做到这一点,使用自动矢量化或引擎盖下的并行化。

将控制权转移到(或“调用”)方法/函数

一方面,许多语言可以根据需要和可能的情况自动内联。 否则,function 或非虚拟方法的成本是一些典型的指令。 对于虚方法可以更多。 当预测时(您在循环中多次调用相同的虚拟方法,并且每次都解析到相同的代码位置)大致相同,当未预测“分支错误预测”的成本时

访问“列表”

无法回答,因为不同运行时库中的实现方式差异太大。 在 Java 或 C# 等语言中,列表由数组支持并共享性能特征。 如果您的意思是依赖于缓存的“链表”,在最好的情况下,每个元素“从 L1 缓存中获取”,在最坏的情况下,“从主内存中获取”每个被访问的元素。

初始化 object

取决于太多的事情。 在某些情况下甚至可以免费。

实例化 class 时,object 中包含的每个方法是否会导致运行时损失?

在大多数语言中,没有。 在非常动态类型的语言中可以是“是”,即使 Python 不够动态,JavaScript 可能是。

问题的 rest 过于依赖特定的语言/运行时。 也在你的代码上。 如果编译器可以推断出具体的实现,许多 C++ 编译器可能会去虚拟化方法调用。 如果您的getValue方法只返回一个字段而不是计算事物,大多数编译器/运行时/解释器将内联调用,这两个版本将生成等效代码。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM