使用函数
我绝对不想把它作为一个函数包含在内,因为这段代码被执行了数万次,因此我不想增加函数调用的开销。
你肯定错了。
您在某些语言中可能是正确的,但在 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.Const 或NamedTuple,
但在那之后,我认为 LLVM 的差异也很大
(如果您真的感兴趣,请查看@code_llvm。但我们已经非常了解杂草了。)
这可能是首先要优化的错误代码。
对您所做的任何优化进行基准测试的指导很长。
还应该分析代码以确定值得优化的内容。
一个只被调用 10,000 次并且不分配巨型数组等的函数,可能不值得太担心。
特别是如果您只是担心函数调用开销,
这只是几个 CPU 周期。