我使用 Julia 对吗?

Am I using Julia right?

几个月前我开始使用 Julia,在听到人们称赞它的各种功能数周后决定尝试一下。我对它了解得越多,就越喜欢它的风格,它融合了用高级语言表达概念的便利性与对速度和可用性的关注。我在 Julia 中实现了一个用 C++ 和 R 编写的模型,发现 Julia 版本 运行 比 R 版本快得多,但仍然比 C++ 稍慢。尽管如此,代码在 Julia 中比在其他两种语言中更易读。这很有价值,特别是当我概括了模型时,为扩大 Julia 代码的范围所做的工作量远远少于其他语言可能面临的可比工作量。

最近,我一直专注于让我的 Julia 代码更快 运行,因为我需要 运行 这个模型数万亿次。在这样做的过程中,我一直受到 @code_warntype@time@profileProfileView 以及 track-allocation 标志的指导。伟大的。这个工具包不如其他一些语言的分析工具好,但它仍然指出了很多瓶颈。

我发现我的代码恰好拥有我在 Julia 中喜欢的高级表现力,当我重写这种表现力以避免不必要的分配时,我失去了那种非常好的表现力。作为一个简单的例子,我最近更改了一行代码

sum([x*y for x in some_list, y in similar_list])

循环遍历列表并添加到状态变量。不是火箭科学,我明白为什么不必分配数组会更快一些。实际上它快了很多。所以我做了类似的事情,避免使用 Dicts 或 "feel" 适合问题的复合类型,当我可以改为手动跟踪临时并行数组中的索引时,我讨厌这种编码风格,但显然运行 当我多次重复创建和简要使用小型数据结构的特定操作时,速度会快很多。

总的来说,这很好,因为我已经牢记编写简短方法的说明,因此构成我自己的较短方法行为的高级方法不需要 "worry"关于较短的方法如何工作;较短的部分可能读起来很笨重,但不会使我的程序的核心部分难以阅读。

但这让我想知道我是否 "missing something." 如果语言的全部意义(对我来说作为一个不关心理论的最终用户)部分是为了将速度与易用性结合起来 development/thought/reading/maintenance 等,即成为一种可写和可用的技术计算语言,那么这是否意味着我 不应该 永远花时间思考最快的加法方式一堆数字,不应该重写"easy to read"或利用映射、过滤器和高级函数概念的优雅代码到"clunky"代码中重新发明轮子来表达那些在低级别跟踪数组索引方面的东西? (我的某些部分期望一种语言在其设计背后具有如此多的智能,足以智能到 "figure out",以至于在我编写 sum([x*y]) 时它实际上不需要分配一个新数组,并且我太迟钝了,无法找出正确的词来告诉语言,除了字面上 "telling it" 手动整个循环业务。有一次我什至想过将 @macros 写成 "convert" 一些快速表达的代码变成了长但更快的循环表达式,但我想如果我本质上是想扩展编译器的功能只是为了解决相当简单的问题,我想我一定是在想这个问题,这就是我写的原因这个问题。)

也许答案是 "if you want really performant code, you pay the price no matter what." 或者换句话说,具有令人厌烦的阅读循环、数组、跟踪索引等的快速代码处于速度易读​​性权衡的有效边界 space.如果是这样,那是完全正确的,因此我不会说我看不起朱莉娅。我只是想知道这种编程风格是否真的处于前沿,或者我的经验是否就是这样,因为我只是不使用该语言编程 "well"。 (通过类比,请参阅问题 What is your most productive shortcut with Vim? ,其中公认的优秀答案基本上是 OP 只是没有 "get" 它。)我怀疑即使我已经成功获得了语言来做我想做的大部分事情,我只是不 "get" 一些东西,但我不知道该要求什么,因为我担心我没有得到的东西是一个未知的未知数对我来说。

TL;DR:我花​​了很多时间 "decomposing" 我的高级函数在循环和数组以获得更快的性能,或者这是否表明我没有考虑使用/正确使用该语言进行编程?

我认为这个主题与 Julia 用户组 Does Julia really solve the two-language problem? 已经进行的讨论密切相关,我想在这里引用该讨论中的一段话:

@Stefan Karpinski:

There are different styles and levels of writing code in every language. There's highly abstract C++ and there's low-level pointer-chasing C++ that's basically C. Even in C there's the void*-style of programming which is effectively dynamically typed without the safety. Given this fact, I'm not sure what solving the two language problem would look like from the perspective this post is posing. Enforcing only one style of programming sounds like a problem to me, not a solution. On the contrary, I think one of Julia's greatest strengths is its ability to accommodate a very broad range of programming styles and levels.

我自己在 Julia 编程方面的经验表明,它可以填补现代编程语言的空白,可以为科学家、工程师和所有人带来高级功能,如并行处理、套接字服务器和......希望使用 一体化 编程语言以高效、可维护和可读的方式完成工作的计算专家和务实程序员。
在我看来,您正在以正确的方式使用 Julia,与其他语言一样,Julia 代表针对不同情况的不同编程风格,您可以优化速度瓶颈,并使其他部分更具可读性。您也可以使用 Devectorize.jl 之类的工具来避免重写问题。

这是一个很难令人满意地回答的问题。因此,我将尝试做一个简短的贡献,希望 "pot" 的不同意见可能就足够了。所以目前我有 3 个想法:

  1. 编程语言可以比作具有动量的物体。用户群是其质量和他们的风格对其施加影响。最初的 users/developers 可以将语言拉向某个方向,因为它的质量仍然很低。即使非常流行(例如 C/C++),一门语言仍然可以发展,但这更难。 Julia 的未来仍未成文,但它的承诺是在其创造者和早期用户灌输的最初方向上。

  2. 最好延迟优化,直到正确性得到很好的测试。 "Premature optimization is the root of all evil" (D. Knuth)。再次记住这一点永远不会有坏处。因此,在可能混淆代码边界区域的优化阶段之前,最好保持代码的可读性和正确性。

  3. 表达式sum([x*y ...])可能要求编译器太聪明了,简单定义一个sumprod(x,y)可能会更好。这将允许 sumprod 利用 Julia 的通用函数多重调度框架,并针对 xy 保持优化,并且可能在以后甚至针对特定类型的 xy.

到此为止。意见很多。那么让我们继续讨论吧。

你能写一个 class 来为你抽象这些概念吗(即 FastDict 或 FastList 或其他)?在拥有高性能代码的同时,您将拥有相同的易读性(如果更像黑盒)。

背景: 我是一名 R/Rcpp 程序员,现在已经使用 Julia 大约一个月了。我编写的 R 代码中至少有一半调用了我编写的 C++ 代码。

结论: 我认为 Julia 尽可能地解决了两种编程语言的问题。这并不意味着可以像原生代码一样编写高性能代码 R/Python,但它大大降低了编写其他人可以轻松使用的高性能代码所需的工作量。

进一步讨论: 首先,我认为不考虑 C/C++ 类型问题就不可能编写高性能代码,即,声明类型,考虑高效的数据结构而不是可读的数据结构等。所以我认为,如果有人希望他们拥有一种不需要关心数据结构的高性能语言,那么他们就出局了运气。当然,我是一名统计学家而不是 CS 人员,所以我承认我在这里可能是错的。

但是,我有点惊讶使用 Julia 似乎减少了我的工作时间。特别是,我的很多工作都涉及首先为某些计算密集型部分编写一些 C++ 代码。一旦完成并且 运行,我基本上是在原生 R 中编写 R 包装器和数据处理器来为我的 C++ 代码准备数据。预处理通常在计算上并不昂贵,而且 R 有很多不错的工具可以用来做这件事。如果一切都提前精心计划,这在理论上并不是一个糟糕的过程。

然而,实际上,实际发生的是我把所有东西都准备好,然后 运行 然后我意识到出于某种原因我想更改一些 C++ 代码。这就是令人头疼的地方。不幸的是,在这一点上,我经常以所有数据的 两个 版本结束;一个将东西传递给一堆本地 R 预处理工具,另一个将东西作为 C++ 对象传递。处理某些 C++ 对象会对所有 R 对象产生很多影响。老实说,我浪费了太多时间试图解决所有这些问题。

到目前为止,我对 Julia 的体验是这种头痛消失了。我正在处理的数据不再有 R 版本和 C++ 版本。只是朱莉娅。这也意味着将其他人的代码引入我代码的 高性能 部分真的很容易。正如最近的一个例子,我一直想使用 Matern 协方差函数。这是一个非常重要的功能,我不想自己实现它。我可以找到很多人,他们拥有 R 包,并在本机 R 中通过良好、有据可查的调用实现了此功能。但是,就我而言,我真的很想从 C++ 中调用它,而无需所有本机 R 开销。这意味着要么 (a) 我需要在他们的 R 包中搜索每个人不一定有很好的文档记录,也不一定是用户友好的 C++ 代码,或者 (b) 重新发明轮子。另一方面,如果我使用的是 Julia,那应该是如果有人编写了一个很好的 Matern 协方差函数供世界使用,我应该能够直接使用它而不是在他们的代码中挖掘调用 I 其实想用。

当我开始构建更复杂的 Julia 项目时,我可能会发现一些我还没有意识到的类似问题。但到目前为止,它似乎确实改进了将易于使用的工具(即 R,Python 类调用)与高性能代码相结合的过程。