Вы можете написать:
Ncircs(n) = x -> cat(Iterators.repeated(circ(x), n)...; dims = 1)
, и если вы знаете, что вы будете делать dims=1
всегда, тогда выполните репликацию cat
с vcat
и reduce
Ncircs(n) = x -> reduce(vcat, Iterators.repeated(circ(x), n))
будет более эффективным для больших n
.
В качестве примечания: использование другой опции (vcat
) приведет к стабильному типу результата, тогда как первая опция не стабильна по типу.
EDIT
Почему сокращение пустой коллекции не допускается?
В общем, причина в том, что тогда вы не можете сказать, что должно быть результатом сокращения. Если вы хотите разрешить пустую коллекцию, вы должны добавить init
аргумент ключевого слова. Вот пример:
julia> reduce(vcat, [])
ERROR: ArgumentError: reducing over an empty collection is not allowed
julia> reduce(vcat, [], init = [1])
1-element Array{Int64,1}:
1
julia> reduce(vcat, [[2,3], [4,5]], init = [1])
5-element Array{Int64,1}:
1
2
3
4
5
Что это означает, что результат является стабильным типом
Это означает, что Джулия может определить тип возвращаемого значения функции во время компиляции (перед выполнением кода). Тип стабильного кода, как правило, работает быстрее (хотя это широкая тема - я рекомендую вам прочитать руководство Julia, чтобы понять его подробно). Вы можете проверить, является ли функция стабильной по типу, используя @code_warntype
и Test.@inferred
.
Здесь позвольте мне дать вам объяснение в вашем конкретном случае (я сократил некоторые результаты, чтобы сократить ответ).
julia> x = [1,2,3]
3-element Array{Int64,1}:
1
2
3
julia> y = [4,5,6]
3-element Array{Int64,1}:
4
5
6
julia> @code_warntype vcat(x,y)
Body::Array{Int64,1}
...
julia> @code_warntype cat(x,y, dims=1)
Body::Any
...
julia> using Test
julia> @inferred vcat(x,y)
6-element Array{Int64,1}:
1
2
3
4
5
6
julia> @inferred cat(x,y, dims=1)
ERROR: return type Array{Int64,1} does not match inferred return type Any
Any
выше означает, что компилятор не знает, какой будет тип ответа. Причина в том, что этот тип зависит от параметра dims
. Если это 1
, то это будет вектор, если это 2
, то это будет матрица.
Откуда я знаю, что это будет более эффективно для больших n
Вы можете запустить @which
макрос:
julia> @which reduce(vcat, [[1,2,3], [4,5,6]])
reduce(::typeof(vcat), A::AbstractArray{#s72,1} where #s72<:(Union{AbstractArray{T,2}, AbstractArray{T,1}} where T)) in Base at abstractarray.jl:1321
И вы видите, что есть специальный reduce
метод для vcat
.
Теперь, если вы запустите:
@edit reduce(vcat, [[1,2,3], [4,5,6]])
Откроется редактор, и вы увидите, что он вызывает внутреннюю функцию _typed_vcat
, оптимизированную для vcat
, что позволяет использовать множество массивов. Эта оптимизация была введена, потому что использование такого сплаттинга, как этот vcat([[1,2,3], [4,5,6]]...)
, эквивалентно в результате, но вы должны сделать сплаттинг (...
), который сам по себе имеет определенную стоимость, которую можно избежать, используя версию reduce
.
Чтобы убедиться, что то, что я говорю, верно, вы можете выполнить следующий тест:
julia> using BenchmarkTools
julia> y = [[i] for i in 1:10000];
julia> @benchmark vcat($y...)
BenchmarkTools.Trial:
memory estimate: 156.45 KiB
allocs estimate: 3
--------------
minimum time: 67.200 μs (0.00% GC)
median time: 77.800 μs (0.00% GC)
mean time: 102.804 μs (8.50% GC)
maximum time: 35.179 ms (99.47% GC)
--------------
samples: 10000
evals/sample: 1
julia> @benchmark reduce(vcat, $y)
BenchmarkTools.Trial:
memory estimate: 78.20 KiB
allocs estimate: 2
--------------
minimum time: 67.700 μs (0.00% GC)
median time: 69.700 μs (0.00% GC)
mean time: 82.442 μs (6.39% GC)
maximum time: 32.719 ms (99.58% GC)
--------------
samples: 10000
evals/sample: 1
julia> @benchmark cat($y..., dims=1)
ERROR: StackOverflowError:
И вы видите, что reduce
версия немного быстрее, чем всплывающая версия vcat
, в то время как cat
просто терпит неудачу для очень большого n
(для меньшего n
это будет работать, но просто будет медленнее).