Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐰 Wrap code in functions to make it faster #720

Merged
merged 10 commits into from
Nov 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 8 additions & 15 deletions sample/Interactivity.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
### A Pluto.jl notebook ###
# v0.10.2
# v0.12.11

using Markdown
using InteractiveUtils
Expand Down Expand Up @@ -165,24 +165,17 @@ In fact, **_any package_ can add bindable values to their objects**. For example

A package _does not need to add `Pluto.jl` as a dependency to do so_: only the `Base.show(io, MIME("text/html"), obj)` function needs to be extended to contain a `<script>` that triggers the `input` event with a value. (It's up to the package creator _when_ and _what_.) This _does not affect_ how the object is displayed outside of Pluto.jl: uncaught events are ignored by your browser."""

# ╔═╡ 36ca3050-7f36-11ea-3caf-cb10e945ca99
md"""## Tips
# ╔═╡ aa8f6a0e-303a-11eb-02b7-5597c167596d

#### Wrap large code blocks
If you have a large block of code that depends on a bound variable `t`, it will be faster to wrap that code inside a function `f(my_t)` (which depends on `my_t` instead of `t`), and then _call_ that function from another cell, with `t` as parameter.

This way, only the Julia code "`f(t)`" needs to be lowered and re-evaluated, instead of the entire code block."""

# ╔═╡ 03701e62-7f37-11ea-3b9a-d9d5ae2344e6
md"""#### Separate definition and reference

If you put a bond and a reference to the same variable together, it will keep evaluating in a loop.
# ╔═╡ 5c1ececa-303a-11eb-1faf-0f3a6f94ac48
md"""## Separate definition and reference
Interactivity works through reactivity. If you put a bond and a reference to the same variable together, then setting the bond will trigger the _entire cell_ to re-evaluate, including the bond itself.

So **do not** write
```julia
md""\"$(@bind r html"<input type=range>") $(r^2)""\"
```

Instead, create two cells:
```julia
md""\"$(@bind r html"<input type=range>")""\"
Expand All @@ -193,7 +186,7 @@ r^2
"""

# ╔═╡ 55783466-7eb1-11ea-32d8-a97311229e93
md""


# ╔═╡ 582769e6-7eb1-11ea-077d-d9b4a3226aac
md"## Behind the scenes
Expand Down Expand Up @@ -272,8 +265,8 @@ md"That's it for now! Let us know what you think using the feedback button below
# ╟─d774fafa-7f34-11ea-290d-37805806e14b
# ╟─8db857f8-7eae-11ea-3e53-058a953f2232
# ╟─d5b3be4a-7f52-11ea-2fc7-a5835808207d
# ╟─36ca3050-7f36-11ea-3caf-cb10e945ca99
# ╟─03701e62-7f37-11ea-3b9a-d9d5ae2344e6
# ╟─aa8f6a0e-303a-11eb-02b7-5597c167596d
# ╟─5c1ececa-303a-11eb-1faf-0f3a6f94ac48
# ╟─55783466-7eb1-11ea-32d8-a97311229e93
# ╟─582769e6-7eb1-11ea-077d-d9b4a3226aac
# ╟─8f829274-7eb1-11ea-3888-13c00b3ba70f
Expand Down
33 changes: 29 additions & 4 deletions src/analysis/ExpressionExplorer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ function split_funcname(::Any)::FunctionName
Symbol[]
end

"Turn :(.+) into :(+)"
"""Turn `Symbol(".+")` into `:(+)`"""
function without_dotprefix(funcname::Symbol)::Symbol
fn_str = String(funcname)
if length(fn_str) > 0 && fn_str[1] == '.'
Expand All @@ -235,7 +235,7 @@ function without_dotprefix(funcname::Symbol)::Symbol
end
end

"Turn :(sqrt.) into :(sqrt)"
"""Turn `Symbol("sqrt.")` into `:sqrt`"""
function without_dotsuffix(funcname::Symbol)::Symbol
fn_str = String(funcname)
if length(fn_str) > 0 && fn_str[end] == '.'
Expand All @@ -253,6 +253,11 @@ function join_funcname_parts(parts::FunctionName)::Symbol
join(parts .|> String, ".") |> Symbol
end

# this is stupid -- désolé
function is_joined_funcname(joined::Symbol)
occursin('.', String(joined))
end

assign_to_kw(e::Expr) = e.head == :(=) ? Expr(:kw, e.args...) : e
assign_to_kw(x::Any) = x

Expand Down Expand Up @@ -573,10 +578,10 @@ function explore!(ex::Expr, scopestate::ScopeState)::SymbolsState
#
# 🤔
# we turn it into two expressions:
#
#
# (a, b) = (2, 3)
# (c = 1)
#
#
# and explore those :)

indexoffirstassignment = findfirst(a -> isa(a, Expr) && a.head == :(=), ex.args)
Expand Down Expand Up @@ -919,4 +924,24 @@ end

get_rootassignee(ex::Any, recuse::Bool=true)::Union{Symbol,Nothing} = nothing

"Is this code simple enough that we can wrap it inside a function to boost performance? Look for [`PlutoRunner.Computer`](@ref) to learn more."
function can_be_function_wrapped(x::Expr)
if x.head === :global || # better safe than sorry
x.head === :using ||
x.head === :import ||
x.head === :module ||
x.head === :function ||
x.head === :macro ||
x.head === :macrocall || # we might want to get rid of this one, but that requires some work
x.head === :struct ||
x.head === :abstract ||
(x.head === :(=) && x.args[1] isa Expr && x.args[1].head === :call) # f(x) = ...
false
else
all(can_be_function_wrapped, x.args)
end

end
can_be_function_wrapped(x::Any) = true

end
46 changes: 0 additions & 46 deletions src/analysis/Parse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -109,49 +109,3 @@ end

# for expressions that are just values, like :(1) or :(x)
preprocess_expr(val::Any) = val

"Wrap `expr` inside a timing block."
function timed_expr(expr::Expr, return_proof::Any=nothing)::Expr
# @assert ExpressionExplorer.is_toplevel_expr(expr)

linenumbernode = expr.args[1]
root = expr.args[2] # pretty much equal to what `Meta.parse(cell.code)` would give

@gensym result
@gensym elapsed_ns
# we don't use `quote ... end` here to avoid the LineNumberNodes that it adds (these would taint the stack trace).
Expr(:block,
:(local $elapsed_ns = time_ns()),
linenumbernode,
:(local $result = $root),
:($elapsed_ns = time_ns() - $elapsed_ns),
:(($result, $elapsed_ns, $return_proof)),
)
end

"Wrap `expr` inside a timing block, and then inside a try ... catch block."
function trycatch_expr(expr::Expr, module_name::Symbol, cell_id::UUID)
# I use this to make sure the result from the `expr` went through `timed_expr`, as opposed to when `expr`
# has an explicit `return` that causes it to jump to the result of `Core.eval` directly.
return_proof = Ref(123)
# This seems a bit like a petty check ("I don't want people to play with Pluto!!!") but I see it more as a
# way to protect people from finding this obscure bug in some way - DRAL

quote
ans, runtime = try
# We eval `expr` in the global scope of the workspace module:
local invocation = Core.eval($(module_name), $(timed_expr(expr, return_proof) |> QuoteNode))

if !isa(invocation, Tuple{Any,Number,Any}) || invocation[3] !== $(return_proof)
throw("Pluto: You can only use return inside a function.")
else
local ans, runtime, _ = invocation
(ans, runtime)
end
catch ex
bt = stacktrace(catch_backtrace())
(CapturedException(ex, bt), missing)
end
setindex!(Main.PlutoRunner.cell_results, ans, $(cell_id))
end
end
10 changes: 5 additions & 5 deletions src/evaluation/Run.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import REPL: ends_with_semicolon
import .Configuration
import .ExpressionExplorer: FunctionNameSignaturePair
import .ExpressionExplorer: FunctionNameSignaturePair, is_joined_funcname

Base.push!(x::Set{Cell}) = x

Expand Down Expand Up @@ -75,7 +75,7 @@ function run_reactive!(session::ServerSession, notebook::Notebook, old_topology:
if any_interrupted
relay_reactivity_error!(cell, InterruptException())
else
run = run_single!((session, notebook), cell)
run = run_single!((session, notebook), cell, new_topology[cell])
any_interrupted |= run.interrupted
end

Expand Down Expand Up @@ -103,8 +103,8 @@ function defined_functions(topology::NotebookTopology, cells)
end

"Run a single cell non-reactively, set its output, return run information."
function run_single!(session_notebook::Union{Tuple{ServerSession,Notebook},WorkspaceManager.Workspace}, cell::Cell)
run = WorkspaceManager.eval_format_fetch_in_workspace(session_notebook, cell.parsedcode, cell.cell_id, ends_with_semicolon(cell.code))
function run_single!(session_notebook::Union{Tuple{ServerSession,Notebook},WorkspaceManager.Workspace}, cell::Cell, reactive_node::ReactiveNode)
run = WorkspaceManager.eval_format_fetch_in_workspace(session_notebook, cell.parsedcode, cell.cell_id, ends_with_semicolon(cell.code), cell.function_wrapped ? (filter(!is_joined_funcname, reactive_node.references), reactive_node.definitions) : nothing)
set_output!(cell, run)
return run
end
Expand Down Expand Up @@ -145,7 +145,7 @@ function update_save_run!(session::ServerSession, notebook::Notebook, cells::Arr

to_run_offline = filter(c -> !c.running && is_just_text(new, c) && is_just_text(old, c), cells)
for cell in to_run_offline
run_single!(offline_workspace, cell)
run_single!(offline_workspace, cell, new[cell])
end

cd(original_pwd)
Expand Down
1 change: 1 addition & 0 deletions src/evaluation/Update.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ function update_caches!(notebook::Notebook, cells)
cell.parsedcode = parse_custom(notebook, cell)
cell.module_usings = ExpressionExplorer.compute_usings(cell.parsedcode)
cell.rootassignee = ExpressionExplorer.get_rootassignee(cell.parsedcode)
cell.function_wrapped = ExpressionExplorer.can_be_function_wrapped(cell.parsedcode)
end
end
end
Expand Down
9 changes: 3 additions & 6 deletions src/evaluation/WorkspaceManager.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module WorkspaceManager
import UUIDs: UUID
import ..Pluto: Configuration, Notebook, Cell, ServerSession, ExpressionExplorer, pluto_filename, trycatch_expr, Token, withtoken, Promise, tamepath, project_relative_path, putnotebookupdates!, UpdateMessage
import ..Pluto: Configuration, Notebook, Cell, ServerSession, ExpressionExplorer, pluto_filename, Token, withtoken, Promise, tamepath, project_relative_path, putnotebookupdates!, UpdateMessage
import ..Configuration: CompilerOptions
import ..Pluto.ExpressionExplorer: FunctionName
import ..PlutoRunner
Expand Down Expand Up @@ -208,24 +208,21 @@ end
"Evaluate expression inside the workspace - output is fetched and formatted, errors are caught and formatted. Returns formatted output and error flags.

`expr` has to satisfy `ExpressionExplorer.is_toplevel_expr`."
function eval_format_fetch_in_workspace(session_notebook::Union{Tuple{ServerSession,Notebook},Workspace}, expr::Expr, cell_id::UUID, ends_with_semicolon::Bool=false)::NamedTuple{(:output_formatted, :errored, :interrupted, :runtime),Tuple{PlutoRunner.MimedOutput,Bool,Bool,Union{UInt64,Missing}}}
function eval_format_fetch_in_workspace(session_notebook::Union{Tuple{ServerSession,Notebook},Workspace}, expr::Expr, cell_id::UUID, ends_with_semicolon::Bool=false, function_wrapped_info::Union{Nothing,Tuple}=nothing)::NamedTuple{(:output_formatted, :errored, :interrupted, :runtime),Tuple{PlutoRunner.MimedOutput,Bool,Bool,Union{UInt64,Missing}}}
workspace = get_workspace(session_notebook)

# if multiple notebooks run on the same process, then we need to `cd` between the different notebook paths
if workspace.pid == Distributed.myid() && session_notebook isa Tuple
cd_workspace(workspace, session_notebook[2].path)
end

# We wrap the expression in a try-catch block, because we want to capture and format the exception on the worker itself.
wrapped = trycatch_expr(expr, workspace.module_name, cell_id)

# run the code 🏃‍♀️

# a try block (on this process) to catch an InterruptException
take!(workspace.dowork_token)
try
# we use [pid] instead of pid to prevent fetching output
Distributed.remotecall_eval(Main, [workspace.pid], wrapped)
Distributed.remotecall_eval(Main, [workspace.pid], :(PlutoRunner.run_expression($(QuoteNode(expr)), $cell_id, $function_wrapped_info)))
put!(workspace.dowork_token)
catch exs
# We don't use a `finally` because the token needs to be back asap
Expand Down
1 change: 1 addition & 0 deletions src/notebook/Cell.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Base.@kwdef mutable struct Cell
parsedcode::Union{Nothing,Expr}=nothing
module_usings::Set{Expr}=Set{Expr}()
rootassignee::Union{Nothing,Symbol}=nothing
function_wrapped::Bool=false
end

Cell(cell_id, code) = Cell(cell_id=cell_id, code=code)
Expand Down
Loading