向量化函数调用组

Vectorizing groups of function calls

我正在尝试编写一个功能(使用宏、生成的函数或其他东西),它有效地将 Julia 函数调用向量化为我编写的函数。基本上,我正在尝试编写我自己的@版本。宏,但相反,我希望它接受函数而不是 for 循环——如果我理解正确的话。以下是我阅读过的有关该主题的一些文档:

https://docs.julialang.org/en/v1/manual/functions/#man-vectorized-1

https://github.com/JuliaLang/julia/blob/master/base/broadcast.jl

https://julialang.org/blog/2017/01/moredots

https://docs.julialang.org/en/v1/manual/metaprogramming/index.html#Code-Generation-1

这是我为实现这样的功能而正在使用的初步玩具示例:

function add!(v_add::Vector{Float64}, a_add::Float64, j::Int64)
  v_add[j] = v_add[j]+a_add
end

function add!(v_add::Vector{Float64}, a_add::Float64)
  for j in 1:length(v_add)
    v_add[j] = v_add[j]+a_add
  end
end

macro vectorize(args)
  print("\n****************** args\n")
  print(args)
  print("\n******************\n")
  e = :(:call,
    $args[1],
    $args[2],
    $args[3])
  print("\n****************** expression\n")
  show(e)
  print(e)
  print("\n******************\n")
  return e
end

function test!(v_test, a_test)
  # # Traverse vector twice
  # add!(v_test, a_test)
  # add!(v_test, a_test)
  # Traverse vector once
  args = [
  add!, v_test, a_test,
  add!, v_test, a_test
  ]
  e = @vectorize(args)
  # eval(e) # Next step
end

v_main = Vector([Float64(i) for i in 1:3])
a_main = Float64(2.0)
print("\n",v_main, "\n")
Main.test!(v_main, a_main)
print("\n",v_main, "\n")

我目前遇到的问题是我什至无法使用宏获得去矢量化版本 运行。此示例导致 LoadError: UndefVarError: args not defined。我非常感谢任何帮助让这个脚本按预期工作(输入是 [1, 2, 3],输出应该是 [5, 6, 7])。

非常感谢help/suggestions。

更新

更具体地说,给定以下定义的函数:

function add!(v::Vector{Float64}, a::Float64)
  for j in 1:length(v)
    v[j]+= a
  end
end
function add!(v::Vector{Float64}, a::Float64, j::Int64)
  v[j]+= a
end

我希望能够使用宏来转换以下代码行:

v = [Float64(j) for j in 1:10]
a = 1
b = 2
@vectorize_I_would_like_to_define(
# I don't know the exact form that the args to this macro should take.
add!(v, a),
add!(v, b)
)

要生成这样编译的代码:

v = [Float64(j) for j in 1:10]
a = 1
b = 2
for j in 1:length(v)
  add!(v, a, j)
  add!(v, b, j)
end

我的目标是编写需要单次内存遍历的代码。

更好的是,如果我可以在编译时生成如下所示的代码:

v = [Float64(j) for j in 1:10]
a = 1
b = 2
for j in 1:length(v)
  v[j]+= a # taken from add!(v::Vector{Float64}, a::Float64, j::Int64)
  v[j]+= b # taken from add!(v::Vector{Float64}, a::Float64, j::Int64)
end

但我不确定与这个玩具示例相比,这对于我正在考虑的更复杂的情况是否可行。

** 更新 2**

这是@Bogumił Kamiński 解决方案的 MWE——除了我已将宏调用移动到一个函数中,所以现在它不起作用,因为它抱怨 v_test 未定义。

macro vectorize(args...)
    expr = :()
    for arg in args
        a = deepcopy(arg) # for safety in case arg is also used somewhere else
        push!(a.args, :j)
        expr = :($expr; $a)
    end
    quote
        for j in 1:length(v)
            $expr
        end
    end
end

function add!(v::Vector{Float64}, a::Float64)
  for j in 1:length(v)
    v[j]+= a
  end
end

function add!(v::Vector{Float64}, a::Float64, j::Int64)
  v[j]+= a
end

v = [Float64(j) for j in 1:10]
a = 1.0
b = 2.0

function test!(v_test, a_test, b_test)
  @vectorize(
  add!(v_test, a_test),
  add!(v_test, b_test)
  )
end

test!(v, a, b)

这是你想要的吗?

macro vectorize(args...)
    expr = :()
    for arg in args
        a = deepcopy(arg) # for safety in case arg is also used somewhere else
        push!(a.args, :j)
        expr = :($expr; $a)
    end
    quote
        for j in 1:length(v)
            $expr
        end
    end
end

现在

function add!(v::Vector{Float64}, a::Float64)
  for j in 1:length(v)
    v[j]+= a
  end
end

function add!(v::Vector{Float64}, a::Float64, j::Int64)
  v[j]+= a
end

v = [Float64(j) for j in 1:10]
a = 1.0
b = 2.0

@vectorize(add!(v, a), add!(v, b))

请注意,我已经更改了 ab 的定义,因为您的 add! 需要 Float64 作为第二个参数。

编辑:如果你想在一个函数中使用这个宏,最简单的做法是 esc 它的整个 return 值:

macro vectorize(args...)
    expr = :()
    for arg in args
        a = deepcopy(arg) # for safety in case arg is also used somewhere else
        push!(a.args, :j)
        expr = :($expr; $a)
    end
    esc(quote
        for j in 1:length(v)
            $expr
        end
    end)
end

然后你可以定义例如:

function f()
    v = [Float64(j) for j in 1:10]
    a = 1.0
    b = 2.0
    @vectorize(add!(v, a), add!(v, b))
    v
end

和 运行 f() 在全局范围内得到与上面相同的结果。

编辑 2:我刚刚意识到实际上我必须清理 j,否则以下代码将失败:

test!(v_test, j, b_test) =
    @vectorize(add!(v_test, j), add!(v_test, b_test))

这是你应该如何做的:

macro vectorize(args...)
    expr = :()
    j = gensym()
    for arg in args
        a = deepcopy(arg) # for safety in case arg is also used somewhere else
        push!(a.args, j)
        expr = :($expr; $a)
    end
    esc(quote
        for $j in 1:length(v)
            $expr
        end
    end)
end

如您所见,开发宏是一项非显而易见的任务(希望最终配方没有错误 :))。

编辑 3:这是正确处理 length 的代码。同样现在在每个表达式中实际上你可以传递一个不同的值作为第一个参数(这样你就可以独立处理不同的向量)。如果你确实想处理相同的向量检查是 a.args[2] 总是相同的符号:

macro vectorize(args...)
    expr = :()
    j = gensym()
    for arg in args
        a = deepcopy(arg) # for safety in case arg is also used somewhere else
        var = a.args[2]
        push!(a.args, j)
        q = quote
            for $j in 1:length($var)
                $a
            end
        end
        expr = :($expr; $q)
    end
    esc(expr)
end