【问题标题】:Using Macro to avoid typing in Julia使用宏来避免输入 Julia
【发布时间】:2019-09-15 08:20:22
【问题描述】:

我读到 Julia 有宏,但我不确定 Julia 提供的宏是否是我正在考虑的。

我有以下表达:

Global.data[
  Dates.value(Dates.Year(dtCursor)) - 2000, 
  Dates.value(Dates.Month(dtCursor)), 
  Dates.value(Dates.Day(dtCursor)),
  Dates.value(Dates.Hour(dtCursor)) + 1, 
  Dates.value(Dates.Minute(dtCursor)) + 1, 
  1
]

我重复了很多次。我想知道我是否可以有一个以 dtCursor 作为参数的宏(在其他情况下可能是其他变量)为我输入所有这些类型。因此,我正在寻找传统上在宏汇编器中找到的宏扩展功能。

我绝对不想把它作为一个函数包含在内,因为这段代码被执行了数万次,因此我不想增加函数调用的开销。

我试过了:

macro readData(_dtCursor, value)
  return :(
    Global.data[
      Dates.value(Dates.Year(_dtCursor)) - 2000, 
      Dates.value(Dates.Month(_dtCursor)), 
      Dates.value(Dates.Day(_dtCursor)),
      Dates.value(Dates.Hour(_dtCursor)) + 1, 
      Dates.value(Dates.Minute(_dtCursor)) + 1, 
      value
    ]
  )
end

稍后被以下调用:

println(@readData(dtCursor, 1))

其中dtCursor 是一个日期时间变量。

但我得到了:

ERROR: LoadError: UndefVarError: _dtCursor not defined

我已阅读 https://docs.julialang.org/en/v1/manual/metaprogramming/index.html#man-macros-1,但我们非常欢迎了解在这种情况下该怎么做。

【问题讨论】:

    标签: julia


    【解决方案1】:

    使用函数

    我绝对不想把它作为一个函数包含在内,因为这段代码被执行了数万次,因此我不想增加函数调用的开销。

    肯定错了。
    您在某些语言中可能是正确的,但在 JuliaLang 中可能不是。
    (我确实认为这是一个非常有用的问题,因为可以突出显示其他人不要这样做?)

    该函数调用内联,即使它没有我们想要在使用宏之前使用的其他工具 (@inline)。

    宏用于句法转换。
    如果您不进行语法转换,请在使用宏之前再三考虑。

    这里是 Steven G. Johnson 在 juliacon 的主题演讲中提出的一个好观点的链接: “功能对 Jeff Bezason 来说已经足够好了。不要试图智取 Jeff Bezason”

    如何将其编写为宏和函数

    以下回答您的原始问题

    using Dates
    using BenchmarkTools
    
    
    macro readData(_dtCursor, value)
      return :(
        Global.data[
          Dates.value(Dates.Year($(esc(_dtCursor)))) - 2000, 
          Dates.value(Dates.Month($(esc(_dtCursor)))), 
          Dates.value(Dates.Day($(esc(_dtCursor)))),
          Dates.value(Dates.Hour($(esc(_dtCursor)))) + 1, 
          Dates.value(Dates.Minute($(esc(_dtCursor)))) + 1, 
          $value
        ]
      )
    end
    
    
    function readData(_dtCursor, value)
        Global.data[
          Dates.value(Dates.Year(_dtCursor)) - 2000, 
          Dates.value(Dates.Month(_dtCursor)), 
          Dates.value(Dates.Day(_dtCursor)),
          Dates.value(Dates.Hour(_dtCursor)) + 1, 
          Dates.value(Dates.Minute(_dtCursor)) + 1, 
          value
        ]
    end
    

    对其进行基准测试。

    您说这将运行 10,000 次。 因此,为了安全起见,我将对 100_000 次使用进行基准测试。

    
    const Global = (; data=[join((y, m, d, h, M, s)," ") for y in 2000:2010, m in 1:3, d in 1:20, h in 1:10, M in 1:30, s in 1:30]);
    size(Global.data)
    length(Global.data)
    
    const sample_dts = map(1:100_000) do _
       y, m, d, h, M, s = rand.(axes(Global.data))
       dt = DateTime(y+2000, m, d, h-1, M-1)
    end;
    
    
    func_demo() = [readData(dt, 3) for dt in sample_dts];
    macro_demo() = [@readData(dt, 3) for dt in sample_dts];
    
    
    @btime func_demo()
    @btime macro_demo()
    
    

    它们的基准是相同的

    julia> @btime macro_demo();
      5.409 ms (3 allocations: 781.34 KiB)
    
    julia> @btime func_demo();
      5.393 ms (3 allocations: 781.34 KiB)
    

    事实上他们专注于(基本上)相同的代码。

    julia> @code_typed macro_demo()
    CodeInfo(
    1 ─ %1 = Main.sample_dts::Core.Compiler.Const(DateTime[2002-01-18T04:19:00, 2001-01-19T08:22:00, 2006-02-08T04:07:00, 2011-01-08T09:03:00, 2006-02-10T06:18:00, 2002-03-12T00:05:00, 2011-02-20T08:29:00, 2011-02-20T07:12:00, 2005-01-13T03:22:00, 2006-01-01T00:29:00  …
      2005-03-10T04:29:00, 2002-03-12T09:11:00, 2002-03-11T00:28:00, 2007-02-12T02:26:00, 2003-02-15T07:29:00, 2009-01-01T02:02:00, 2009-
    01-03T02:11:00, 2001-02-16T03:16:00, 2004-01-17T05:12:00, 2010-02-02T05:10:00], false)
    │   %2 = %new(Base.Generator{Array{DateTime,1},getfield(Main, Symbol("##50#51"))}, getfield(Main, Symbol("##50#51"))(), %1)::Base.Gen
    erator{Array{DateTime,1},getfield(Main, Symbol("##50#51"))}
    │   %3 = invoke Base.collect(%2::Base.Generator{Array{DateTime,1},getfield(Main, Symbol("##50#51"))})::Array{String,1}
    └──      return %3
    ) => Array{String,1}
    
    
    julia> @code_typed getfield(Main, Symbol("##50#51")).instance(1)  # check the internals
    │   %1 = %1 = Main.Global::Core.Compiler.Const((#==GIANT Inlined Const ==#)
    │   %2 = Base.getfield(%1, :data)::Array{String,6}
    │   %3 = Base.sub_int(dt, 2000)::Int64
    │   %4 = Base.add_int(dt, 1)::Int64
    │   %5 = Base.add_int(dt, 1)::Int64
    │   %6 = Base.arrayref(true, %2, %3, dt, dt, %4, %5, 3)::String
    └──      return %6
    ) => String
    
    
    
    julia> @code_typed func_demo()
    CodeInfo(
    1 ─ %1 = Main.sample_dts::Core.Compiler.Const(DateTime[2002-01-18T04:19:00, 2001-01-19T08:22:00, 2006-02-08T04:07:00, 2011-01-08T09:03:00, 2006-02-10T06:18:00, 2002-03-12T00:05:00, 2011-02-20T08:29:00, 2011-02-20T07:12:00, 2005-01-13T03:22:00, 2006-01-01T00:29:00  …  2005-03-10T04:29:00, 2002-03-12T09:11:00, 2002-03-11T00:28:00, 2007-02-12T02:26:00, 2003-02-15T07:29:00, 2009-01-01T02:02:00, 2009-
    01-03T02:11:00, 2001-02-16T03:16:00, 2004-01-17T05:12:00, 2010-02-02T05:10:00], false)
    │   %2 = %new(Base.Generator{Array{DateTime,1},getfield(Main, Symbol("##43#44"))}, getfield(Main, Symbol("##43#44"))(), %1)::Base.Gen
    erator{Array{DateTime,1},getfield(Main, Symbol("##43#44"))}
    │   %3 = invoke Base.collect(%2::Base.Generator{Array{DateTime,1},getfield(Main, Symbol("##43#44"))})::Array{String,1}
    └──      return %3
    ) => Array{String,1}
    
    julia> @code_typed getfield(Main, Symbol("##43#44")).instance(1)
    CodeInfo(
    1 ─ %1 = Main.Global::NamedTuple{(:data,),Tuple{Array{String,6}}}
    │   %2 = Base.getfield(%1, :data)::Array{String,6}
    │   %3 = Base.sub_int(dt, 2000)::Int64
    │   %4 = Base.add_int(dt, 1)::Int64
    │   %5 = Base.add_int(dt, 1)::Int64
    │   %6 = Base.arrayref(true, %2, %3, dt, dt, %4, %5, 3)::String
    └──      return %6
    ) => String
    

    两者之间的生成器功能有非常小的差异。 在内联时,值变为Compliler.ConstNamedTuple, 但在那之后,我认为 LLVM 的差异也很大 (如果您真的感兴趣,请查看@code_llvm。但我们已经非常了解杂草了。)

    这可能是首先要优化的错误代码。

    对您所做的任何优化进行基准测试的指导很长。 还应该分析代码以确定值得优化的内容。 一个只被调用 10,000 次并且不分配巨型数组等的函数,可能不值得太担心。 特别是如果您只是担心函数调用开销, 这只是几个 CPU 周期。

    【讨论】:

      【解决方案2】:

      你必须拼接你作为宏参数传递的变量:

      julia> macro readData(dtCursor, value)
               return :(
                 Global.data[
                   Dates.value(Dates.Year($dtCursor)) - 2000, 
                   Dates.value(Dates.Month($dtCursor)), 
                   Dates.value(Dates.Day($dtCursor)),
                   Dates.value(Dates.Hour($dtCursor)) + 1, 
                   Dates.value(Dates.Minute($dtCursor)) + 1, 
                   $value
                 ]
               )
             end
      @readData (macro with 1 method)
      
      julia> @macroexpand @readData(dtCursor, 1)
      :((Main.Global).data[(Main.Dates).value((Main.Dates).Year(Main.dtCursor)) - 2000, (Main.Dates).value((Main.Dates).Month(Main.dtCursor)), (Main.Dates).value((Main.Dates).Day(Main.dtCursor)), (Main.Dates).value((Main.Dates).Hour(Main.dtCursor)) + 1, (Main.Dates).value((Main.Dates).Minute(Main.dtCursor)) + 1, 1])
      

      此外,Julia 宏是卫生的;这意味着不会混淆宏定义中的名称_dtCursor 和调用站点中的名称dtCursor。不过,您可能需要做的一件事是escape 输入。

      另外,这可能有点矫枉过正。您应该将宏版本与功能版本进行基准测试;也许,发生了足够多的内联,宏实际上并不重要。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-07
        • 1970-01-01
        • 1970-01-01
        • 2013-06-15
        • 2012-02-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多