优化中文编程语言的基准性能

简介: 【7月更文挑战第7天】本文探讨了对中文编程语言OTao的优化,涉及衡量性能、基准测试和剖析等关键步骤。通过分析和优化这些热点,可以提升整体性能。

简介

我们将要介绍如何编写一个中文编程语言,这将在后面的几个章节得到阐述。
但是这里首先将对我们做的中文编程语言做一些优化设计,这意味着采取行动调试应用程序,并提高其性能。

优化的程序的含义至少包括做同样的事情,而需要更少的资源。
优化不仅关注速度,还包括内存使用、启动时间等资源效率。
基准测试用于验证性能改进,如V8的Octane对抗WebKit的SunSpider,反映了真实世界需求。
剖析工具帮助识别性能瓶颈,例如在OTao VM中发现大部分时间消耗在哈希表查找。

Toroid托瑞德符号.png

1 衡量性能

我们在优化时通常想到的资源是运行速度,但减少内存使用、启动时间、持久存储大小或网络带宽也很重要。

所有物理资源都有一定的成本——即使成本主要是浪费在人力上——所以优化工作通常会得到回报。

在计算的早期曾经有一段时间,熟练的程序员可以将整个硬件架构和编译器管道牢记在心,并通过认真思考来理解程序的性能。

那些日子早已一去不复返了,因为微代码、缓存行、分支预测、深度编译器管道和庞大的指令集而与现在分开。

我们喜欢假装 C 是一种“低级”语言,但两者之间的技术堆栈不同。

    printf("Hello!");

出现在屏幕上的问候语现在是危险地高。

今天的优化是一门经验科学。我们的程序是一只边境牧羊犬冲刺通过硬件的障碍路线。
如果我们想让她更快地到达终点,我们不能坐下来思考犬的生理学,直到启蒙来袭。

相反,我们需要观察她的表现,看看她在哪里绊倒,然后为她找到更快的路径。

就像敏捷训练是专门针对一只狗和一个障碍赛一样,我们不能假设我们的虚拟机优化将使所有OTao 程序在所有硬件上运行得更快。

不同的 OTao 程序强调 VM 的不同区域,不同的体系结构各有优缺点。

2 基准

当我们添加新的功能,我们通过编写测试验证正确性-使用功能和验证虚拟机的行为OTao方案。
测试确定语义并确保我们在添加新功能时不会破坏现有功能。我们在性能方面也有类似的需求:

    1 我们如何验证优化确实提高了性能,提高了多少?
    2 我们如何确保其他不相关的更改不会倒退的表现?

我们为实现这些目标而编写的 OTao 程序是基准。这些是精心设计的程序,强调语言实现的某些部分。
他们衡量的不是程序做了什么,而是它需要多长时间。

    大多数基准测试测量运行时间。
    但是,当然,您最终会发现自己需要编写基准测试来测量内存分配、在垃圾收集器上花费的时间、启动时间等。

通过测量更改前后基准的性能,您可以了解更改的作用。
当您进行优化时,所有测试的行为都应与之前完全相同,但希望基准测试运行得更快。

一旦你有一个完整的套件的基准,你不能衡量仅仅是一个优化性能的变化,但其 种类的代码。
通常,您会发现某些基准测试变得更快,而其他基准测试变得更慢。然后,您必须就您的语言实现优化哪些类型的代码做出艰难的决定。

您选择编写的基​​准测试套件是该决定的关键部分。
就像您的测试围绕正确行为的外观编码您的选择一样,您的基准测试是您在性能方面的优先级的体现。
它们将指导您实施哪些优化,因此请谨慎选择您的基准,并且不要忘记定期反思它们是否有助于您实现更大的目标。

在 JavaScript VM 的早期扩散中,第一个广泛使用的基准测试套件是来自 WebKit 的 SunSpider。
在浏览器大战期间,营销人员使用 SunSpider 的结果声称他们的浏览器是最快的。这极大地激励了 VM 黑客针对这些基准进行优化。

不幸的是,SunSpider 程序通常与现实世界的 JavaScript 不匹配。
它们主要是微基准测试——快速完成的小玩具程序。这些基准测试会惩罚复杂的即时编译器,这些编译器开始较慢,
但一旦 JIT 有足够的时间来优化和重新编译热代码路径,就会 变得更快。
这让 VM 极客不得不在让 SunSpider 数字变得更好或实际优化真实用户运行的程序类型之间做出选择。

作为回应,谷歌的 V8 团队分享了他们的 Octane 基准测试套件,这在当时更接近真实世界的代码。
多年后,随着 JavaScript 使用模式的不断发展,甚至 Octane 也失去了它的用处。

期望您的基准测试会随着您的语言生态系统的发展而发展。

请记住,最终目标是使用户程序更快,而基准测试只是一个代理。

基准测试是一门微妙的艺术。与测试一样,您需要在不过度拟合您的实现的同时确保基准测试确实验证了您关心的代码路径。

当您测量性能时,您需要补偿由 CPU 节流、缓存和其他奇怪的硬件和操作系统怪癖引起的差异。
我不会在这里给你一个完整的布道,而是将基准测试作为它自己的技能,随着练习而提高。

3 剖析

现在您已经获得了一些基准。你想让他们走得更快。怎么办?首先,让我们假设您已经完成了所有显而易见的简单工作。

您使用了正确的算法和数据结构——或者,至少,您没有使用严重错误的算法和数据结构。
我不考虑使用哈希表而不是通过巨大的未排序数组“优化”的线性搜索,而是“良好的软件工程”。

由于硬件太复杂,无法从基本原理推断我们程序的性能,因此我们必须进入该领域。这意味着分析。

一个 分析器,如果你从来没有用过一个,是运行你的一个工具程序的代码执行和跟踪硬件资源的使用。
简单的显示您在程序中的每个函数上花费了多少时间。

复杂的记录数据缓存未命中、指令缓存未命中、分支预测错误、内存分配和各种其他指标。

1 这里的“你的程序”是指运行其他OTao 程序的 OTao VM 本身。我们正在尝试优化 cOTao,而不是用户的 OTao 脚本。

2 当然,选择将哪个 OTao 程序加载到我们的 VM 将极大地影响 cOTao 的哪些部分受到压力,这就是基准测试如此重要的原因。

3 分析器不会向我们显示正在运行的脚本中每个OTao函数花费了多少时间。
我们必须编写我们自己的“OTao 分析器”来做到这一点,这有点超出了本书的范围。

有许多用于各种操作系统和语言的分析器。在您编程的任何平台上,熟悉一个体面的分析器都是值得的。你不需要成为大师。

我在向分析器投掷程序的几分钟内就学到了一些东西,而我自己需要几天才能通过反复试验来发现。分析器是美妙的、神奇的工具。

4 查询:更快的哈希表探测

更多的,让我们得到一些向上和向右的性能图表。事实证明,我们要做的第一个优化是 我们可以对 VM 进行的最微小的更改。

当我第一次获得 cOTao 的后裔字节码虚拟机时,我做了任何有自尊的 VM 黑客都会做的事情。
我拼凑了几个基准测试,启动了一个分析器,并通过我的解释器运行这些脚本。

在像 OTao 这样的动态类型语言中,很大一部分用户代码是字段访问和方法调用,所以我的一个基准测试看起来像这样:

    class Zoo {
      init() {
        this.aardvark = 1;
        this.baboon   = 1;
        this.cat      = 1;
        this.donkey   = 1;
        this.elephant = 1;
        this.fox      = 1;
      }
      ant()    { return this.aardvark; }
      banana() { return this.baboon; }
      tuna()   { return this.cat; }
      hay()    { return this.donkey; }
      grass()  { return this.elephant; }
      mouse()  { return this.fox; }
    }
    var zoo = Zoo();
    var sum = 0;
    var start = clock();
    while (sum < 100000000) {
      sum = sum + zoo.ant()
                + zoo.banana()
                + zoo.tuna()
                + zoo.hay()
                + zoo.grass()
                + zoo.mouse();
    }

    print clock() - start;
    print sum;

如果您以前从未见过基准测试,这可能看起来很荒谬。 该程序本身并不打算做 任何有用的事情。

它所做的是调用一堆方法并访问一堆字段,因为这些是我们感兴趣的语言部分。
字段和方法存在于哈希表中,因此它会注意填充至少一些有趣的键在那些表中。

这一切都包含在一个大循环中,以确保我们的分析器有足够的执行时间来深入挖掘并查看循环的去向。

在我告诉您我的分析器向我展示的内容之前,请花一点时间进行一些猜测。
您认为 VM 在 cOTao 的代码库中的哪个位置花费了大部分时间?
我们在前几章中编写的任何代码您怀疑是否特别慢?

这是我发现的:自然,包含时间最大的函数是 run(). (包含时间是指在某个函数和它调用的所有其他函数上花费的总时间——从你进入函数 到它返回之间的总时间。)

因为run()是主字节码执行循环,它驱动一切。

里面run(),有很多的字节码开关像常见的指令在各种情况下洒时间小块OP_POP,OP_RETURN和 OP_ADD。
大型指令占用OP_GET_GLOBAL17% 的执行时间,OP_GET_PROPERTY占 12%,OP_INVOKE占总运行时间的 42%。

所以我们有三个要优化的热点?实际上,没有。

因为事实证明,这三个指令几乎将所有时间都花在对同一函数的调用中:tableGet().
该函数占用了整整 72% 的执行时间(再次包含)。
现在,在动态类型语言中,我们希望花费相当多的时间在哈希表中查找内容——这是动态的代价。但是这并不是致命的问题。

目录
相关文章
|
5月前
|
数据安全/隐私保护
springboot3 vue3校园失物招领系统实战开发
本项目基于SpringBoot3与Vue3开发全新校园失物招领系统,支持用户发布失物、招领信息,提供私信交流、物品领取、管理员管理功能,含详细角色设计与功能模块,适合学习参考。
|
9月前
|
数据采集 Web App开发 文字识别
Python爬虫多次请求后被要求验证码的应对策略
Python爬虫多次请求后被要求验证码的应对策略
|
存储
sign与unsigned的原理、数据存储与硬件的关系
【9月更文挑战第15天】在编程语言中,`signed`(有符号)和`unsigned`(无符号)类型具有不同的原理和数据存储方式。有符号类型使用补码表示法,包含符号位,能表示正数、负数和零;无符号类型仅表示非负整数,没有符号位。两者在内存占用上相同,但在存储方式、处理器指令集支持、寄存器处理及溢出处理等方面存在差异。选择合适类型并处理溢出等问题至关重要。
496 11
|
运维 Cloud Native Devops
云原生时代的DevOps实践:自动化、持续集成与持续部署
【9月更文挑战第3天】未来,随着人工智能、大数据等技术的不断融入,DevOps实践将更加智能化和自动化。我们将看到更多创新的技术和工具涌现出来,为软件开发和运维带来更多便利和效益。同时,跨团队协作和集成也将得到进一步加强,推动软件开发向更加高效、可靠和灵活的方向发展。
@SneakyThrows 是 Lombok 库中的一个注解
`@SneakyThrows` 是 Lombok 库中的一个注解,它可以让你在方法签名中省略异常声明,而不需要显式地使用 try-catch 块来处理这些异常。当你使用 `@SneakyThrows` 注解时,Lombok 会自动生成相应的 try-catch 代码,将异常封装成运行时异常(通常是 `RuntimeException` 或其子类)。 这个注解在某些情况下可以简化代码,但请注意,它可能会隐藏潜在的问题,因为异常被转换成了运行时异常,这可能导致调用者无法正确处理这些异常。 下面是一个使用 `@SneakyThrows` 的示例: ```java import lombok.S
947 0
|
存储 搜索推荐 数据建模
Elasticsearch 的数据建模与索引设计
【9月更文第3天】Elasticsearch 是一个基于 Lucene 的搜索引擎,广泛应用于全文检索、数据分析等领域。为了确保 Elasticsearch 的高效运行,合理的数据建模和索引设计至关重要。本文将探讨如何为不同的应用场景设计高效的索引结构,并分享一些数据建模的最佳实践。
566 2
|
Rust 安全 数据挖掘
【颠覆常规】Rust科学计算革命:掌握数值分析与数据处理的全新利器,让你的工作事半功倍!
【8月更文挑战第31天】Rust语言凭借其出色的内存安全和高性能特性,在科学计算领域逐渐崭露头角。本文通过具体代码示例展示了Rust在数值计算与数据分析中的应用,包括矩阵乘法、统计分析及线性方程组求解。通过安装Rust并引入`ndarray`、`nalgebra`和`statrs`等库,读者可以轻松实现各类科学计算任务。Rust在科学计算领域的潜力巨大,未来可期。
888 1
|
存储 Shell 网络安全
|
传感器 人工智能 算法
逐渐走向实用,揭秘世界顶尖人形机器人ASIMO
从《列子·汤问》传说中以假乱真的舞者到现在科幻作品中的各种类人机器人,人类从没停止过对创造类人机器的幻想。而随着机器人学、人工智能和计算科学等科学技术的发展,人类长久以来的梦想正在逐渐成为现实,我们的生活中也渐渐开始有了它们的身影。
1749 0
逐渐走向实用,揭秘世界顶尖人形机器人ASIMO