В вашем вопросе есть несколько вопросов.
Как сделать тесты
На самом деле в вашем коде обе функции одинаково быстры.Проблема в том, что g
не является const
в глобальной области, которая вводит штраф.Чтобы увидеть это, объявите g
как const или используйте $g
в @btime
вызове, чтобы увидеть, что нет никакой разницы:
julia> using BenchmarkTools
julia> struct fFunction
f1
f2
end
julia> f() = return 1
f (generic function with 1 method)
julia> fStruct = fFunction(f, f)
fFunction(f, f)
julia> const g = fStruct.f1
f (generic function with 1 method)
julia> @btime f()
0.001 ns (0 allocations: 0 bytes)
1
julia> @btime g()
0.001 ns (0 allocations: 0 bytes)
1
и
julia> using BenchmarkTools
julia> struct fFunction
f1
f2
end
julia> f() = return 1
f (generic function with 1 method)
julia> fStruct = fFunction(f, f)
fFunction(f, f)
julia> g = fStruct.f1
f (generic function with 1 method)
julia> @btime f()
0.001 ns (0 allocations: 0 bytes)
1
julia> @btime $g()
0.001 ns (0 allocations: 0 bytes)
1
Как проанализировать вашcode
Однако эта эквивалентность является искусственной, поскольку вы извлекаете g
из fStruct
в глобальной области видимости, поэтому она оценивается перед вызовом @btime
.Более подходящим тестом было бы:
julia> using BenchmarkTools
julia> struct fFunction
f1
f2
end
julia> f() = return 1
f (generic function with 1 method)
julia> fStruct = fFunction(f, f)
fFunction(f, f)
julia> test1() = f()
test1 (generic function with 1 method)
julia> test2(fStruct) = fStruct.f1()
test2 (generic function with 1 method)
julia> @btime test1()
0.001 ns (0 allocations: 0 bytes)
1
julia> @btime test2($fStruct)
14.462 ns (0 allocations: 0 bytes)
1
julia> @code_warntype test1()
Body::Int64
1 1 ─ return 1 │
julia> @code_warntype test2(fStruct)
Body::Any
1 1 ─ %1 = (Base.getfield)(fStruct, :f1)::Any │╻ getproperty
│ %2 = (%1)()::Any │
└── return %2
И вы видите, что использование fFunction
struct неэффективно, потому что его поля f1
и f2
имеют абстрактный тип (конкретнее Any
).
Как написать эффективную структуру, содержащую функции
Используйте Tuple
, NamedTuple
или структуру с параметрами, поскольку все они предоставляют информацию о типе.Кортеж будет просто определен как (f,f)
, NamedTuple
будет (f1=f, f2=f)
.Наиболее сложный случай - это параметрическая структура, которую я покажу вам здесь (код для Tuple
и NamedTuple
был бы еще проще):
julia> using BenchmarkTools
julia> struct fFunction{F1,F2}
f1::F1
f2::F2
end
julia> f() = return 1
f (generic function with 1 method)
julia> fStruct = fFunction(f, f)
fFunction{typeof(f),typeof(f)}(f, f)
julia> test1() = f()
test1 (generic function with 1 method)
julia> test2(fStruct) = fStruct.f1()
test2 (generic function with 1 method)
julia> @btime test1()
0.001 ns (0 allocations: 0 bytes)
1
julia> @btime test2($fStruct)
1.866 ns (0 allocations: 0 bytes)
1
julia> @code_warntype test1()
Body::Int64
1 1 ─ return 1 │
julia> @code_warntype test2(fStruct)
Body::Int64
1 1 ─ (Base.getfield)(fStruct, :f1) │╻ getproperty
└── return 1
И вы можете видеть, что использование fFunction
определено как параметрическоеУ type почти нет накладных расходов (единственная цена, которую вы платите - это извлечение поля).
Если что-то не понятно, пожалуйста, дайте мне знать, и я могу подробнее рассказать об этом.