R vs. Python vs. Julia

简介: R vs. Python vs. Julia

整体比较

如果你是一名数据科学家,你很有可能使用Python或R编程。但是有一个叫Julia的新成员承诺在不影响数据科学家编写代码和与数据交互的情况下拥有c一样的性能。

我将R与Julia进行了比较,展示了Julia是如何为数据科学社区带来全新的编程思维方式的。主要的结论是,有了Julia,您不再需要向量化来提高性能,良好地使用循环可能会提供最好的性能。

在这篇文章中,我将添加Python对比。因为对于数据科学家来说我们使用任何算法最好有现成的实现可用,并且从对算法进行编程使用需要非常的简单。这都是我们需要编写高效代码时所必需的。

线性搜索测试

让我们考虑对未排序的整数向量进行隶属关系测试的问题。

julia>10 [71,38,10,65,38]
truejulia>20 [71,38,10,65,38]
false

这个问题可以通过线性搜索解决。该算法遍历输入向量的元素,直到找到要搜索的值(成功搜索)或到达向量的末尾(不成功搜索)为止。目的是判断向量中是否有给定的整数。

为了评估R,Python和Julia中的不同实现,我生成了一个数据集,该数据集包含1.000.000范围从1到2.000.000的唯一整数,并执行了1.000个从1到1.000的所有整数的搜索。搜索成功的可能性约为50%,因此算法将扫描整个向量的一半时间得出搜索不成功的结论。在其余情况下,算法应(平均)需要进行(n + 1)/ 2次评估才能找到元素,其中n为向量的长度。

我通过3次运行CPU时间中值来测量每个实现的性能。这些实验的目的不是为不同的语言和实现制定一个准确的基准。其目的是强调当性能很重要时,语言对数据科学家造成的障碍。

C实现

我用C实现了线性搜索,以了解静态类型编译语言的性能,并设置基线。二进制可执行文件执行1.000搜索花费了0.26秒CPU时间。

// 0.26 secondsintfor_search(constint*vec, intsize, intx) {
for (inti=0;i<size;i++)
if (vec[i] ==x)
return1;
return0;
}

R实现

我尝试了R中不同风格的测试,从专用操作符(in)到使用循环的类c实现,通过向量化方法。

#0.93secondsin_search<-function(vec, x) x%in%vec#2.68secondsvec_search<-function(vec, x) any(x==vec)
#13.33secondsforeach_search<-function(vec, x) {
for (vinvec)
if (v==x)
return (TRUE)
FALSE}
#21.94secondsfor_search<-function(vec, x) {
for (iin1:length(vec))
if (vec[i] ==x)
return (TRUE)
FALSE}

当我们从in_search移动到for_search时,我们得到了对算法的更高控制。但是在R中,随着控制的增加,性能会下降。使用向量化操作(如vec_search)比遍历元素直到找到匹配的元素要快一个数量级。尽管向量化需要更多的内存和(冗余的)操作,但它还是有回报的。正如预期的那样,其中的专用运算符具有最高的性能和更清晰的代码。

我也尝试了Map-Reduce操作,但没有耐心等到它们完成……如果你追求性能,这不是一个好的方式。

Python实现

说实话,最初的目标是只使用原生函数和原生数据结构,但当使用Python的原生列表时,in操作符比R慢了约10倍。因此,我还特意测试了NumPy数组的结果(它给Python带来了向量化的操作)。CPU时间从9.13秒减少到0.57秒,大约是基准时间的2倍。然而,当转向循环方法时,原生领先了一个数量级……通过使用Numba包添加JIT编译,我给了NumPy第二次机会。Numba有一些限制,但是使用起来很简单:您只需要包含Numba包并标记希望看到已编译JIT的函数(并仔细阅读手册)。使用NumPy + Numba的循环提供了与向量化/专门操作相当(或更好)的性能,但要达到这一点并不容易,因为其中存在一些问题。例如使用Numba在本地列表上执行循环是令人失望的……我再次停止执行,因为要花5分钟才能完成。

importnumpyasnpfromnumbaimportjit#9.13secondswhenvecisanativelist#0.57secondswhenvecisanumpyarraydefin_search(vec, x):
returnxinvec#0.57secondswhenvecisanumpyarraydefvec_search(vec, x):
returnnp.any(vec==x)
#18.70secondswhenvecisanativelist#121.48secondswhenvecisanumpyarray#0.47secondswhenvecisanumpyarraywithnumba@jitdefforeach_search(vec, x):
forvinvec:
ifv==x:
returnTruereturnFalse#40.75secondswhenvecisanativelist#161.36secondswhenvecisanumpyarray#0.55secondswhenvecisanumpyarraywithnumba@jitdeffor_search(vec, x):
foriinrange(len(vec)):
ifvec[i] ==x:
returnTruereturnFalse

Julia实现

在Julia中,我添加了另外两种风格,以展示本地可用功能的多样性和性能。

#0.37secondsin_search(vec, x) =xinvec#0.32secondsff_search(vec, x) =findfirst(el->el==x, vec) !=nothing#0.38secondsmapr_search(vec, x) =mapreduce(el->el==x, |, vec)
#1.10secondsvec_search(vec, x) =any(vec .==x)
#0.33secondsfunctionforeach_search(vec, x)
@simdforvinvecifv==xreturntrueendendfalseend#0.32secondsfunctionfor_search(vec, x)
@inbounds@simdfori=1:length(vec)
ifvec[i] ==xreturntrueendendfalseend

除了向量化操作之外,性能与C语言的实现非常接近,仅下降了20%-50%。向量化的性能相当不错,大约是4x C的CPU时间,但在向量化操作上,也减少了大约NumPy的两倍CPU时间。并且对于代码的自由度也非常的好,因为你可以在Julia中编写几乎任何算法!为了在For循环上获得最佳性能,我使用提示告诉编译器不要检查索引是否在数组范围内(inbounds宏),并告诉编译器它在执行迭代的顺序上有额外的自由度(simd宏)。因为不提供这些提示将使循环的性能接近in_search。

整体比较

640.png

通过对这个简单问题的结果进行对比,我们发现:

  • 在执行方面,Julia的性能几乎与C相当;
  • Julia的例外是在编写类似R的矢量化代码时,性能下降了大约3倍。
  • 在将JIT编译(Numba)添加到Python时,基于循环的实现接近于Julia的性能。Numba仍然在您的Python代码上施加了约束,这使该选项成为一种折衷;
  • 在Python中,最好在原生列表和NumPy数组之间以及何时使用Numba之间进行选择:对于经验不足的人来说,最好的数据结构(性能方面)并不明显,也没有明显的赢家尤其是如果包括了动态添加元素的情况(此处未介绍);
  • R不是最快的,但是跟Python差不多:R中最慢的实现比最快的实现慢约24倍,而Python的实现是343x(Julia的3倍多);
  • 原生 R总是比原生Python更好。
  • 每当您无法避免在Python或R中循环时,基于元素的循环比基于索引的循环更有效。

细节很重要

我可以在这里停止本文,并写出在Julia中编写高效代码的无缝性。尽管如此,细节仍然很重要,程序员需要注意Julia的内部构造。您能猜出最能影响性能的代码行是什么?这是一个提示:您不会在之前提供的任何代码段中找到它…

map(line -> parse(Int, line), eachline(f))

这行代码解析输入文本文件f,该文件每行包含一个数字(请注意,读取文件不属于基准测试的一部分)。那么,这一行代码有何特别之处?简而言之,Julia 的推断:

匿名函数的返回类型(map的第一个参数)(总是)是整数,因此,映射的输出是一个整数数组。

由于Julia知道正在存储整数数组,因此它会分配一个连续的内存块,其中每个项都包含一个整数。这允许有效的读取操作。

我们看看下面的代码:

a= []
forlineineachline(f)
push!(a, parse(Int, line))
end

理论上应该是一样的,对吧, 但是:

>typeof(a)
Array{Any,1}

句子a = []看起来很方便,它创建了一个Any数组,这意味着可以在该数组的每个元素上存储任何类型的数据。在内部,Julia在内存中存储了一个指针数组,以配合Any提供的灵活性。结果,Julia在处理数组时无法再处理连续的连续内存块。对性能有什么影响?慢大约50到100倍!

修改此代码非常简单:a = Int [](而不是a = [])将完成此工作,因为它指定了元素的类型。

最后

从本文涵盖的所有语言来看,Julia显然是编写高效代码的最简单方法。不过,您仍然需要知道自己在做什么。幸运的是,提供了一些性能提示,可以使您走上正确的道路。

目录
相关文章
|
2月前
|
数据采集 缓存 Java
Python vs Java:爬虫任务中的效率比较
Python vs Java:爬虫任务中的效率比较
|
1月前
|
安全 数据库 C++
Python Web框架比较:Django vs Flask vs Pyramid
Python Web框架比较:Django vs Flask vs Pyramid
44 1
|
2月前
|
安全 数据库 C++
Python Web框架比较:Django vs Flask vs Pyramid
Python Web框架比较:Django vs Flask vs Pyramid
46 4
|
2月前
|
安全 数据库 C++
Python Web框架比较:Django vs Flask vs Pyramid
【10月更文挑战第10天】本文比较了Python中三个最受欢迎的Web框架:Django、Flask和Pyramid。Django以功能全面、文档完善著称,适合快速开发;Flask轻量灵活,易于上手;Pyramid介于两者之间,兼顾灵活性和安全性。选择框架时需考虑项目需求和个人偏好。
40 1
|
2月前
|
安全 数据库 C++
Python Web框架比较:Django vs Flask vs Pyramid
【10月更文挑战第6天】本文比较了Python中三个最受欢迎的Web框架:Django、Flask和Pyramid。Django功能全面,适合快速开发;Flask灵活轻量,易于上手;Pyramid介于两者之间,兼顾灵活性和可扩展性。文章分析了各框架的优缺点,帮助开发者根据项目需求和个人偏好做出合适的选择。
51 4
|
2月前
|
C++ Python
Python Tricks--- Object Comparisons:“is” vs “==”
Python Tricks--- Object Comparisons:“is” vs “==”
21 1
|
4月前
|
Shell 数据处理 C++
【震撼揭秘】Python正则VS Shell正则:一场跨越编程边界的史诗级对决!你绝不能错过的精彩较量,带你领略文本处理的极致魅力!
【8月更文挑战第19天】正则表达式是文本处理的强大工具,在Python与Shell中有广泛应用。两者虽语法各异,但仍共享许多基本元素,如`.`、`*`及`[]`等。Python通过`re`模块支持丰富的功能,如非捕获组及命名捕获组;而Shell则依赖`grep`、`sed`和`awk`等命令实现类似效果。尽管Python提供了更高级的特性和函数,Shell在处理文本文件方面仍有其独特优势。选择合适工具需根据具体需求和个人偏好决定。
44 1
|
4月前
|
机器学习/深度学习 数据可视化 数据处理
Python vs R:机器学习项目中的实用性与生态系统比较
【8月更文第6天】Python 和 R 是数据科学和机器学习领域中最受欢迎的两种编程语言。两者都有各自的优点和适用场景,选择哪种语言取决于项目的具体需求、团队的技能水平以及个人偏好。本文将从实用性和生态系统两个方面进行比较,并提供代码示例来展示这两种语言在典型机器学习任务中的应用。
137 1
|
4月前
|
机器学习/深度学习 数据可视化 算法
决策树VS世界:掌握Python机器学习中的这棵树,决策从此不再迷茫
【8月更文挑战第2天】在数据驱动时代,决策树作为一种直观且易于解释的机器学习方法,因其强大的分类与回归能力备受青睐。本文介绍决策树的基础概念:通过属性测试划分数据,优化选择以提高预测准确度。使用Python的scikit-learn库,我们演示了如何加载鸢尾花数据集,构建并训练决策树模型,评估其准确性,以及利用`plot_tree`函数可视化决策过程,从而更好地理解模型的工作原理。掌握这些技能,你将在面对复杂决策时更加自信。
35 2
|
4月前
|
存储 C语言 Python