如果从命令行运行 Julia 脚本,是不是每次都需要重新编译?

Posted

技术标签:

【中文标题】如果从命令行运行 Julia 脚本,是不是每次都需要重新编译?【英文标题】:If a Julia script is run from the command line, does it need to be re-compiled every time?如果从命令行运行 Julia 脚本,是否每次都需要重新编译? 【发布时间】:2018-05-30 15:54:51 【问题描述】:

我已经阅读了很多文档和问题,但我仍然对此感到困惑。

在文档的Profiling 部分中,建议首先在 REPL 中运行目标函数一次,以便在分析之前它已经编译。但是,如果脚本相当复杂并且打算在命令行中运行并接受参数怎么办?当julia 进程完成并且我第二次运行脚本时,是否再次执行编译?像https://***.com/a/42040763/1460448、Julia compiles the script every time? 这样的帖子给出了相互矛盾的答案。在 Julia 不断发展的同时,它们似乎也很老了。

在我看来,根据我的经验,第二次运行所花费的时间与第一次运行的时间完全相同。启动时间相当长。我应该如何优化这样的程序?添加__precompile__() 似乎根本没有改变执行时间。

另外,当我想对这样的程序进行概要分析时应该怎么做?所有关于分析的资源都在 REPL 中讨论了这样做。

【问题讨论】:

是的,每次从 REPL 外部运行时都会重新编译它,除非您能够编译成二进制文件(我相信这有点困难。)推荐的工作流程在REPL,并从那里调用东西。此外,如果您想要良好的性能,请记住将您的代码包装在一个函数中。我不明白为什么接受参数意味着你必须从命令行运行它。把它变成一个函数,并以参数作为输入变量调用函数。 @DNF 谢谢。我确实拥有功能中的一切。只是我正在运行一个需要很长时间才能完成的模型(并最终保存经过训练的模型文件或在测试集上显示结果),因此这样的程序通常通过命令行运行,就像使用例如C++、pypy 或 Tensorflow。既然我了解 Julia 是这样工作的,那么我将在 REPL 中做所有事情。虽然对于人们习惯于以不同方式做事的长期任务来说,这不会让 Julia 有点尴尬吗? 就个人而言,我不明白为什么在 Julia 提示符下运行程序比在 bash 提示符下运行程序更尴尬。无论我使用的是 Matlab、Python 还是 Julia,我总是从 repl 中运行。您同样可以从 Julia 中保存结果。 确实如此。我能想到的一个问题可能是当您需要向用户显示帮助消息时,显示运行程序需要哪些参数以及每个标志的含义,就像传统的命令行程序一样。用户还可以在相应标志之后输入参数。使用纯函数调用会更复杂吗?这就是为什么我觉得 Julia 还不适合命令行使用,而且目前确实更适合 REPL 工作流程。如果可以编译二进制可执行文件,那就更好了,尽管这可能会违背动态。 长启动时间也不利于将其用作命令行程序。 【参考方案1】:

我有点不同意我的同事。在某些绝对有效的情况下,人们会依赖运行 Julia 脚本。例如。当你有一个脚本管道(例如 matlab、python 等)并且你需要在所有这些中间的某个地方插入一个 julia 脚本,并从一个 shell 脚本控制整个管道时。但是,无论用例如何,说“只使用 REPL”都不是这个问题的正确答案,即使一个 无法 提出“有效”的场景,它仍然是一个值得直接回答的问题,而不是解决方法。

我同意的是,拥有适当代码的解决方案是将需要预编译的所有关键内容包装到模块中,并且只将除最外部命令之外的所有命令留在脚本顶层。无论如何,这与 matlab 或 C++ 世界并没有太大的不同,在那里您需要编写完整的函数,并且只将您的脚本/主函数视为某种非常简短的***入口点,其工作是简单地准备初始环境,然后相应地运行那些更专业的功能。

这是我的意思的一个例子:

# in file 'myscript.jl'
push!( LOAD_PATH, "./" )
import MyPrecompiledModule
println( "Hello from the script. The arguments passed into it were $ARGS" )
MyPrecompiledModule.exportedfun()

# in file 'MyPrecompiledModule.jl' (e.g. in the same directory as myscript.jl)
__precompile__()
module MyPrecompiledModule
  export exportedfun;
  function innerfun()
    println("Hello from MyPrecompiledModule.innerfun");
  end

  function exportedfun()
    innerfun()
    print("Hello from MyPrecompiledModule.exportedfun");
  end
end

在上述场景中,MyPrecompiledModule 的编译版本将在脚本中使用(如果不存在,则会在您第一次运行脚本时编译),因此编译中的任何优化都不会在脚本结束时会丢失,但您仍然会得到一个独立的 julia 脚本,您可以将其用作 bash shell 脚本管道过程的一部分,也可以将参数传递给该脚本。然后myscript.jl 脚本只需在必要时将这些传递给导入的模块函数,并执行您不特别关心它们是否被编译/优化的任何其他命令,例如执行基准测试,提供脚本使用说明等。

【讨论】:

附言。还可以选择依赖模块的 __init__() 函数,尽管我个人更喜欢像上面那样拆分东西。 This other answer 我的一个类似问题你也可能感兴趣。 这似乎是合理的,我希望它可以大大提高性能,以便在管道中间使用 julia。但是,从我阅读的内容来看,__precompile__() 似乎不一定会编译所有内容,而只是其中的一部分? 这不是一个简单的问题,我自己也不确定实现细节,但是是的,基本上它会编译它所能编译的。但是,请注意 Julia 对函数和方法进行了区分,前者是无类型的,而后者具有特定类型作为参数。方法几乎肯定会被预编译,但函数可能只会针对会话期间使用的特定参数“即时”编译;我不知道这些是否会以某种方式保存。还要注意手册中关于__init__() 的部分,这永远不会预编译。 @xji 我同意这里的所有内容,除了 Tasos 和我不同意的断言 :-) 我的回答可能有点强烈,并且绝对同意有时你可能想打电话来自命令行的 Julia(我想我稍微误判了我的听众)。如果我们要认真讨论预编译,您一定要阅读this awesome answer。 @Slaus 我可以确认,虽然上面的代码现在已经很老了(从 julia v0.5 开始),但它仍然可以在最新的 julia (v1.7.1) 中使用。如果您收到此错误,则可能是因为 MyPrecompiledModule.jl 与我的示例不在同一目录中,或者您没有从其自己的目录运行示例。请注意,在myscript.jl 中我做了push!( LOAD_PATH, "./" ),假设这是您可以找到模块的地方。如果您将模块保存在其他地方,则需要使加载路径知道其他地方的位置,以便它可以从该位置加载模块。【参考方案2】:

如果我错了,请纠正我,但听起来你写了一些长脚本,比如myfile.jl,然后从你的操作系统命令行调用julia myfile.jl args...。它是否正确?另外,听起来myfile.jl 并没有在函数的方式上定义太多,而只是一个命令序列。它是否正确?如果是这样,那么正如 cmets 关于该问题所建议的那样,这不是 julia 的典型工作流程,原因有两个:

1) 从命令行调用julia,即julia myfile.jl args...,相当于打开一个REPL,在myfile.jl上运行include命令,然后关闭REPL。对include 的初始调用将编译myfile.jl 中的操作所需的任何方法,这需要时间。但是由于您是从命令行运行的,一旦include 完成,REPL 会自动关闭,并且所有已编译的代码都会被丢弃。这就是 DNF 的意思,他说推荐的工作流程是在单个 REPL 会话中工作,并且在您完成一天之前不要关闭它,或者除非您有意重新编译您正在使用的所有方法。

2) 即使您在单个 REPL 会话中工作,非常 将您所做的几乎所有事情都包装在函数中(这与 Matlab 等语言的工作流程非常不同)。如果您这样做,Julia 将为每个函数编译专门针对您正在使用的输入参数类型的方法。这基本上就是 Julia 速度快的原因。一旦一个方法被编译一次,它在整个 REPL 会话中仍然可用,但在你关闭 REPL 时被释放。至关重要的是,如果您不将操作包装在函数中,则不会发生这种专门的编译,因此您可能会遇到非常慢的代码。在 Julia 中,我们称之为“在全球范围内工作”。请注意,Julia 的这一特性鼓励一种编码风格,即把你的任务分解成许多小的专用函数,而不是一个由 1000 行代码组成的庞然大物。这是一个好主意,原因有很多。 (在我自己的代码库中,很多函数都是单行的,大多数是 5 行或更少)

以上两点对于了解您是否在 Julia 工作是绝对关键的。但是,一旦您对它们感到满意,我建议您实际上将所有函数放在 modules 中,然后在需要时从活动的 REPL 会话中调用您的模块。这还有一个额外的好处,您可以在模块顶部添加一个__precompile__() 语句,然后 julia 将预编译该模块中的一些(但不一定是全部)代码。完成此操作后,关闭 REPL 时模块中的预编译代码不会消失,因为它以 .ji 文件的形式存储在硬盘驱动器上。因此,您可以开始一个新的 REPL 会话,输入 using MyModule,您的预编译代码立即可用。如果你改变了模块的内容,它只需要重新编译(这一切都是自动发生的)。

【讨论】:

谢谢。我确实拥有功能中的一切。只是我正在运行一个需要很长时间才能完成的模型(并最终保存经过训练的模型文件或在测试集上显示结果),因此这样的程序通常通过命令行运行,就像使用例如C++、pypy 或 Tensorflow。既然我了解 Julia 是这样工作的,那么我将在 REPL 中做所有事情。虽然对于人们习惯以不同方式做事的长期任务来说,这不会让 Julia 有点尴尬吗? @xji 正如 DNF 上面所说,我真的看不出使用命令行和在命令提示符下运行的 REPL 之间的区别。但是如前所述,如果您将所有函数放在一个模块中并预编译它,那么对于较长的例程,您应该不会注意到太大的区别。顺便说一句,只是为了强调我上面关于小函数和长函数的观点,在我自己的代码库中,许多函数都是单行的,大多数是 5 行或更少。 当然。我非常了解函数式编程概念。这也是我非常喜欢 Julia 的原因。 @xji 对不起,如果我听起来有点过头了。我可能对此过于敏感,因为我自己来自 Matlab 背景,不得不花费大量时间重新训练自己养成更好的习惯 :-) 长启动时间也不利于将其用作命令行程序。

以上是关于如果从命令行运行 Julia 脚本,是不是每次都需要重新编译?的主要内容,如果未能解决你的问题,请参考以下文章

Julia 从脚本请求用户输入

为啥这个带有 shell_exec 调用的 PHP 脚本从 Windows 10 的命令行运行,而不是浏览器/本地主机?

从命令行运行 PHP 脚本

从命令行停止 Abaqus 中正在运行的 Python 脚本

如何在命令行中从脚本运行函数?

该脚本从命令行运行,但 crontab 失败