揭开AI编程语言Mojo比Pyhon快6.8万倍的5个秘密!

20240403133200

最近(2024年3月29日),号称比Python快6.8万倍的Mojo编程语言开源啦!6.8万倍?你敢相信这个数字是真的吗?不过,就连Mojo官网都把这个结果贴了出来(见下图),这就很难让你不对这个数字引起好奇。很显然,Mojo官方的结果难免有“自卖自夸”的嫌疑,但至少说明在某些特殊的场景下确实得到了这个数字,官网不会造假。那么,究竟是什么原因让Mojo能比Python快这么多呢?下面我们就揭开这一神秘的面纱,也借此机会让我们进一步了解Mojo这门比较火的编程语言。

20240403200358

Mojo简介

Mojo编程语言是由Modular公司开发的,旨在为人工智能领域提供统一的编程框架。它是基于Python语法的超集,结合了Python的易用性和C语言的性能,支持多核向量单元加速器单元等硬件功能。Mojo能够对大量低级AI硬件进行编程,模型扩展性更强,为开发者提供卓越的性能体验。Mojo的创始人是Chris Lattner,他是Swift语言的创始人,也参与了LLVM和Clang的开发。他与Google的机器学习产品经理Tim Davis共同创立了Modular公司,并在2022年推出了Mojo语言。

特殊的例子

可以猜想,这个6.8万倍的结果是在一个特殊的例子上完成,具体来说,它计算和绘制了Mandelbrot集,就是下面的分图案。这是一个非常简单但是又非常耗费计算资源的例子,测试者也给出了选择这个作为例子的理由:

  • 简单表达:只有很少的代码
  • 纯计算:曼德勃罗集没有内存开销
  • 容易并行
  • 可向量化

所以6.8万倍的第1个秘密就是这个计算场景非常适合发挥Mojo的所有优势,这是经典的以己之长比别人之短

20240403201325

# 代码示例:下面函数中z是复数
MAX_ITERS = 1000
def mandelbrot_kernel(c): 
  z = c
  nv = 0
  for i in range(MAX_ITERS):
    if abs(z) > 2:
      break
    z = z*z + c
    nv += 1
  return nv

编译语言vs解释语言

众所周知,Python是解释型语言,性能上天然会有一些劣势。Mojo是虽然语法上兼容Python(很多写法上是一样的),但却是一个编译语言。除此之外,Mojo除了像Python一样支持动态类型(在运行的时候才知道变量的类型),还支持另一种静态类型的写法(见下面代码示例),当使用静态类型的时候编译器可以提前对代码做出很多针对性的优化,提升性能。6.8万倍的第2个秘密就是这Mojo是一门支持静态类型的编译语言

fn mandelbrot_2(c: ComplexFloat64) -> Int:
    var z = c
    var nv = 0
    for i in range(1, MAX_ITERS):
        if z.squared_norm() > 4:
            break
        z = z.squared_add(c)
        nv += 1
    return nv

向量化

前面两个秘密其实还谈不上多神秘,很容易理解和想到。我认为接下来谈到的这个才算是Mojo真正厉害的地方。

正如宣传所说,Mojo是面向人工智能的语言,人工智能计算的特点是什么?大量的向量计算。于是Mojo对向量计算进行了针对性的优化,并且这种优化深入到了底层硬件。为此,Mojo内置了SIMD类型。

单指令多数据(SIMD)是一种并行处理技术,内置于许多现代CPU、GPU和定制加速器中。SIMD允许您一次对多个数据执行单个操作。例如,如果您想对数组中的每个元素求平方根,可以使用SIMD来并行化工作。

Mojo中的SIMD类型就是专门负责针对不同的CPU/GPU进行这种优化的,具体是实现细节在这里就不展开了。在原作者测试的机器上,CPU具有512bit长的向量寄存器,这意味着CPU可以一次操作512/64=8个双精度浮点数,理论上可以实现8x的加速,实测结果是实现了6x以上的加速。此外,原作者在SIMD的基础上还进行了进一步针对CPU的指令的优化:现代 x86 系统具有多个融合乘加(FMA)单元,使其能够在每个时钟周期执行多个 FMA。这一优化也将速度再原有基础上提升了一倍多,不过这一技巧很难适用于所有的计算场景,不多讨论。

鉴于Mojo内置了SIMD数据类型,所以实现上面的优化并不算复杂,这一向量化加速的技术还真是非常适合人工智能计算的场景呢。6.8万倍的第3个秘密就是SIMD向量化加速

多线程加速

Python实是单线程的,如果要利用多核CPU的特性还需要一些特殊的处理,很不方便。Mojo是原生支持多线程的,可以很方便利用多核CPU的特性。用多核CPU对比Python的单核CPU,这不是作弊吗?确实,不过在这里我们先不谈公平问题,先看看在Mojo中是如何实现多线程加速的。

fn compute_row(chunk_idx:Int):
  let y = chunk_size * chunk_idx
  let cy = min_y + y * scale_y

  @parameter
  fn compute_vector[simd_width:Int](w:Int):
      let cx = min_x + iota[DType.float64, simd_width]() * scale_x
      output.simd_store[simd_width](Index(h,w), 
                                  mandelbrot_kernel(
                                               ComplexSIMD[DType.float64, 
                                               simd_width](cx,cy))
  vectorize[num_ports * simd_width, compute_vector](width)

  # !!! 重点代码在这里
  with Runtime(num_cores()) as rt:
      parallelize[compute_row](rt, height)

实事求是的说,在Mojo中实现并行确实方便的多啊!无论如何还是要为这一特性点个赞。所以6.8万倍的第4个秘密就是多线程并行加速

原作者测试的机器具有88个CPU,通过这一“作弊行为”,直接将性能在原有的基础上提升了 30 倍,效果那是相当明显😀。但是你可能好奇,为什么没有提升到88倍呢?

负载均衡和数据倾斜

我相信“负责均衡”和“数据倾斜”这两个概念至少有一个你是比较熟悉的,通俗点讲它们都反应了一个问题:分工不均,活都让少数人干了。这就是上面的例子中为什么88核CPU只实现了30倍加速的原因:计算在88个CPU中并不是均匀分布的。那么如何进一步优化呢?

方法其实也并不复杂,我们可以把任务进一步拆分成更小的单元,拆分的越细,平均分配给每个cpu之后越不容易产生“分工不均”的现象(如果你写过Spark,应该听过这个最佳实践:任务/Task的数量最好是Executor/Core数量的2-3倍,而不是等于)。值得庆幸的是,Mojo 包含一个高性能并发运行时,因此我们不必自己创建线程池或进行循环选择和执行。Mojo 的运行时包含高级功能,可以充分利用像这样的多核系统。

# 只需要对原来代码做很少改动
with Runtime(num_cores()) as rt:
    let partition_factor = 16 # Is autotuned.
    parallelize[compute_row](rt, height, partition_factor * num_cores())

6.8万倍的第5个秘密就是负载均衡

总结

以上每一个优化都会使得性能提升几倍到数十倍不等,这些数字相乘之后确实就得到了6.8万这一耸人听闻的数字。一路学习了解下来,我觉得Mojo确实是一门相当不错的编程语言,同时Mojo团队也是很懂营销啊!

关注【黑客悟理】,不错过任何奇奇怪怪的知识

参考资料

  1. https://www.modular.com/blog/how-mojo-gets-a-35-000x-speedup-over-python-part-1
  2. https://www.modular.com/blog/how-mojo-gets-a-35-000x-speedup-over-python-part-2
  3. https://www.modular.com/blog/mojo-a-journey-to-68-000x-speedup-over-python-part-3
  4. https://mojocn.org/

    如果你喜欢我的文章,欢迎到我的个人网站关注我,非常感谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/513479.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

怎样在Linux搭建NTP服务器

搭建 NTP(Network Time Protocol)服务器可以帮助你在局域网内提供时间同步服务,让网络中的设备都使用统一的时间。以下是在 Linux 系统上搭建 NTP 服务器的基本步骤: 安装 NTP 服务器软件: 在终端中执行以下命令安装 N…

Webpack部署本地服务器

Webpack部署本地服务器 目录 Webpack部署本地服务器目的认识模块热替换(HMR)什么是 HMRHMR 通过如下几种方式, 来提高开发的速度如何使用 HMRhost 配置 目的 完成自动编译 常用方式: webpack-dev-server webpack-dev-server 是一个用于开发环境的 Web 服…

Class类

1. Class类的理解 针对于编写好的 .java 源文件进行编译(使用 javac.exe),会生成一个或多个 .class 字节码文件。接着,我们使用 java.exe 命令对指定的 .class 文件进行解释运行。这个解释运行的过程中,我们需要将 .class 字节码文件加载到内…

本地储存、jQuery

文章目录 1. 本地储存1. window.sessionStorage2. window.localStorage案例:记住用户名 2. jQuery入门jQuery 的概念jQuery 的入口函数jQuery 的顶级对象 $jQuery 对象和 DOM 对象 3. jQuery 常用API1. jQuery 选择器1.基础选择器2.层级选择器隐式迭代(重…

C++(set和map详解,包含常用函数的分析)

set set是关联性容器 set的底层是在极端情况下都不会退化成单只的红黑树,也就是平衡树,本质是二叉搜索树. set的性质:set的key是不允许被修改的 使用set需要包含头文件 set<int> s;s.insert(1);s.insert(1);s.insert(1);s.insert(1);s.insert(2);s.insert(56);s.inser…

Vue.js---------Vue基础

能够说出Vue的概念和作用能够使用vue/cli脚手架工程化开发能够熟练Vue指令 一.vue基本概念 1.学习vue Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 渐进…

2024 ccfcsp认证打卡 2022 09 01 如此编码

2022 09 01 如此编码 题解1题解2 题解1 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int n sc.nextInt(); // 天数int m sc.nextInt(); // 科目数int[] b new int[n 1]; // 存放结果的数…

笔记: JavaSE day15 笔记

第十五天课堂笔记 数组 可变长参数★★★ 方法 : 返回值类型 方法名(参数类型 参数名 , 参数类型 … 可变长参数名){}方法体 : 变长参数 相当于一个数组一个数组最多只能有一个可变长参数, 并放到列表的最后parameter : 方法参数 数组相关算法★★ 冒泡排序 由小到大: 从前…

Paddle实现人脸对比

人脸对比 人脸对比&#xff0c;顾名思义&#xff0c;就是对比两个人脸的相似度。本文将用Paddle实现这一功能。 PS&#xff1a;作者肝了整整3天才稍微搞明白实现方法 数据集准备 这里使用百度AI Studio的开源数据集&#xff1a; 人脸数据_数据集-飞桨AI Studio星河社区 (b…

【React】vite + react 项目,配置项目路径别名 @

vite react 项目&#xff0c;配置项目路径别名 1 安装 types/node2 在 vite.config.ts 中添加配置&#xff1a;3 配置路径别名的提示 使用 vite 开发 react 项目时&#xff0c;可以通过一下步骤配置路径别名&#xff1a; 1 安装 types/node npm i -D types/node2 在 vite.con…

Lumos学习王佩丰Excel第一讲:认识Excel

最近发现自己在操作excel的一些特殊功能时会有些不顺手&#xff0c;所以索性找了一个比较全的教程&#xff08;王佩丰excel24讲&#xff09;拿来学习&#xff0c;刚好形成文档笔记&#xff0c;分享给有需要但没有时间看视频的朋友们。整体笔记以王老师授课的知识点去记录&#…

Spring拓展点之SmartLifecycle如何感知容器启动和关闭

Spring为我们提供了拓展点感知容器的启动与关闭&#xff0c;从而使我们可以在容器启动或者关闭之时进行定制的操作。Spring提供了Lifecycle上层接口&#xff0c;这个接口只有两个方法start和stop两个方法&#xff0c;但是这个接口并不是直接提供给开发者做拓展点&#xff0c;而…

算法基础--递推

&#x1f600;前言 递推算法在计算机科学中扮演着重要的角色。通过递推&#xff0c;我们可以根据已知的初始条件&#xff0c;通过一定的规则推导出后续的结果&#xff0c;从而解决各种实际问题。本文将介绍递推算法的基础知识&#xff0c;并通过一些入门例题来帮助读者更好地理…

力扣 392. 判断子序列

题目来源&#xff1a;https://leetcode.cn/problems/is-subsequence/description/ C题解1&#xff1a;在t中按顺序一个一个寻找s的元素。 class Solution { public:bool isSubsequence(string s, string t) {bool flg false;int m s.size(), n t.size();if(m 0) return tr…

vue项目打包优化之-productionSourceMap设置

productionSourceMap 是一个用于配置生产环境下是否生成 source map 文件的选项。在 webpack 中&#xff0c;source map 文件是一种映射关系文件&#xff0c;可以将编译后的代码映射回原始源代码&#xff0c;方便开发者在调试时定位问题。 在生产环境中&#xff0c;通常不建议暴…

线程池小项目【Linux C/C++】(踩坑分享)

目录 前提知识&#xff1a; 一&#xff0c;线程池意义 二&#xff0c;实现流程 阶段一&#xff0c;搭建基本框架 1. 利用linux第三方库&#xff0c;将pthread_creat线程接口封装 2. 实现基本主类ThreadPool基本结构 阶段二&#xff0c;完善多线程安全 1. 日志信息打印…

大模型放进推荐系统怎么玩?微软亚研全面总结

在大模型时代&#xff0c;似乎任何自然语言处理任务在大模型加持下都完成了一轮升级改造&#xff0c;展现出前所未有的高效与效果。语义理解、情感分析还是文本生成这些常规任务自然是不必说&#xff0c;但也有一些任务比如推荐&#xff0c;简单粗暴的训练LLMs的思路并非明智之…

回溯算法|78.子集

力扣题目链接 class Solution { private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& nums, int startIndex) {result.push_back(path); // 收集子集&#xff0c;要放在终止添加的上面&#xff0c;否则会漏掉自…

HarmonyOS 应用开发之非线性容器

非线性容器实现能快速查找的数据结构&#xff0c;其底层通过hash或者红黑树实现&#xff0c;包括HashMap、HashSet、TreeMap、TreeSet、LightWeightMap、LightWeightSet、PlainArray七种。非线性容器中的key及value的类型均满足ECMA标准。 HashMap HashMap 可用来存储具有关联…

门控循环单元(GRU)

概述 门控循环单元&#xff08;Gated Recurrent Unit, GRU&#xff09;由Junyoung Chung等人于2014年提出&#xff0c;原论文为《Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling》。GRU是循环神经网络&#xff08;Recurrent Neural Network, …