Оценка пройденной функции медленнее, чем прямая оценка в юлии.Есть ли обходной путь? - PullRequest
0 голосов
/ 27 сентября 2018

Скажем, у меня есть много очень простых функций, называемых f1, f2, .... Я хотел бы сохранить все функции f в fStruct и передать одну из функций f, например, g, когда мне это нужномой кодНо когда я передаю функцию f в g, вычисление функции g () происходит намного медленнее.Есть ли обходной путь?Мое безобразное решение - использовать общую функцию, которая выбирает правильную функцию f () с помощью операторов if-else.Ниже приведен минимальный пример медленного вычисления.

using BenchmarkTools
struct fFunction
    f1
    f2
end
f() = return 1
fStruct = fFunction(f, f)
g = fStruct.f1
@btime f() --> 0.001 ns (0 allocations: 0 bytes)
@btime g() --> 9.591 ns (0 allocations: 0 bytes)

EDIT1:

Я также мог бы спросить, почему функция g медленнее или как сделать это так быстрокак f в следующем минимальном примере

using BenchmarkTools
f() = return 1
func = "f"
g = eval(Meta.parse(func))
f == g -->true
@btime f() --> 0.001 ns (0 allocations: 0 bytes)
@btime g() --> 11.907 ns (0 allocations: 0 bytes)

EDIT2:

Спасибо за ваши ответы.Я обновляю сообщение решением.

using BenchmarkTools
f() = return 1
function g(x)
   h = f
   h()
end
const g2 = f
@btime f()
@btime g(f)
@btime g2()

f, g и g2 дают одинаковую скорость.

struct fFunctionAmbigiousType{F}
     f1::F
     f2::F
end
struct fFunctionDeclaredType{F}
     f1::F
     f2::F
end
fStructAmbigiousType = fFunctionAmbigiousType(f, f)
fStructDeclaredType = fFunctionDeclaredType(f, f)
fTuple = (f1 = f, f2 = f)
@btime $fStructAmbigiousType.f1
@btime $fStructDeclaredType.f1
@btime $fTuple.f1
fStructAmbigiousTypeFunctionPassed = fStructAmbigiousType.f1
fStructDeclaredTypeFunctionPassed = fStructDeclaredType.f1
fTupleFunctionPassed = fTuple.f1
@btime $fStructAmbigiousTypeFunctionPassed() 
@btime $fStructDeclaredTypeFunctionPassed() 
@btime $fTupleFunctionPassed() 

fFunctionAmbigiousType, fFunctionDeclaredType и fTuple дают одинаковую скорость.Объявление типа функции в структуре ничего не меняет.Юлия понимает в обоих случаях typeof {f}.Параметрическая структура или параметрический NamedTuple возможны, но, конечно, медленнее, если вы часто применяете функцию.Если вы часто применяете функцию f, вы должны сначала передать ее g или что-то в этом роде, чтобы избежать индексации каждый раз.

С уважением, До

Ответы [ 2 ]

0 голосов
/ 29 сентября 2018

Если вы заранее знаете, какую функцию вы будете передавать, я обнаружил, что одна вещь, которая значительно ускорит процесс, - это ручное преобразование требуемого замыкания в структуру.Так, если, например, вы всегда будете передавать функции вида

f(x) = x + a

для разных a, вы можете преобразовать это в

struct Adder
    a::Int
end

(adder::Adder)(x) = x + adder.a

и затем использовать его в fFunction like

fFunction(f::Adder, g::Adder) = rand() < 0.5 ? f(10) : g(10)

Таким образом, у вас есть конкретный тип для f и g и ручное управление вашими замыканиями, полностью исключая все накладные расходы на вызов метода, кроме перегруженного оператора вызова.И fFunction полностью специализирован и стабилен.

0 голосов
/ 27 сентября 2018

В вашем вопросе есть несколько вопросов.

Как сделать тесты

На самом деле в вашем коде обе функции одинаково быстры.Проблема в том, что 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 почти нет накладных расходов (единственная цена, которую вы платите - это извлечение поля).

Если что-то не понятно, пожалуйста, дайте мне знать, и я могу подробнее рассказать об этом.

...