Подтвердить что ты не робот

Как написать "хороший" код Юлии при работе с несколькими типами и массивами (множественная отправка)

Я новичок в Julia, и, учитывая мое происхождение Matlab, у меня возникли трудности с определением того, как написать "хороший" код Юлии, который использует преимущества множественной отправки и системы типа Julia.

Рассмотрим случай, когда у меня есть функция, которая предоставляет квадрат a Float64. Я мог бы написать это как:

function mysquare(x::Float64)
    return(x^2);
end

Иногда я хочу скомпоновать все Float64 в одномерном массиве, но не хочу каждый раз выписывать цикл через mysquare, поэтому я использую множественную отправку и добавляю следующее:

function mysquare(x::Array{Float64, 1})
    y = Array(Float64, length(x));
    for k = 1:length(x)
        y[k] = x[k]^2;
    end
    return(y);
end

Но теперь я иногда работаю с Int64, поэтому я пишу еще две функции, которые используют множественную отправку:

function mysquare(x::Int64)
    return(x^2);
end
function mysquare(x::Array{Int64, 1})
    y = Array(Float64, length(x));
    for k = 1:length(x)
        y[k] = x[k]^2;
    end
    return(y);
end

Правильно ли это? Или существует более идеотический способ справиться с этой ситуацией? Должен ли я использовать параметры типа, подобные этому?

function mysquare{T<:Number}(x::T)
    return(x^2);
end
function mysquare{T<:Number}(x::Array{T, 1})
    y = Array(Float64, length(x));
    for k = 1:length(x)
        y[k] = x[k]^2;
    end
    return(y);
end

Это кажется разумным, но будет ли мой код работать так же быстро, как в случае, когда я избегаю параметрических типов?

Итак, на мой вопрос две части:

  • Если мне нужен быстрый код, следует ли использовать параметрические типы, как описано выше, или написать несколько версий для разных конкретных типов? Или я должен делать что-то еще?

  • Когда мне нужна функция, которая работает как с массивами, так и с скалярами, полезно ли писать две версии функции: одну для скаляра и одну для массива? Или я должен делать что-то еще?

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

OP UPDATE: Обратите внимание, что в последней версии Julia (v0.5) идиоматический подход к ответу на этот вопрос состоит в том, чтобы просто определить mysquare(x::Number) = x^2. Этот векторизованный случай покрывается с использованием автоматического широковещания, т.е. x = randn(5) ; mysquare.(x)

4b9b3361

Ответ 1

Julia компилирует конкретную версию вашей функции для каждого набора входов по мере необходимости. Таким образом, чтобы ответить на часть 1, разница в производительности отсутствует. Параметрический путь - путь.

Как и в части 2, в некоторых случаях может быть хорошей идеей написать отдельную версию (иногда по соображениям производительности, например, чтобы избежать копирования). Однако в вашем случае вы можете использовать встроенный макрос @vectorize_1arg для автоматического создания версии массива, например:

function mysquare{T<:Number}(x::T)
    return(x^2)
end
@vectorize_1arg Number mysquare
println(mysquare([1,2,3]))

Что касается общего стиля, не используйте точки с запятой, а mysquare(x::Number) = x^2 намного короче.

Что касается вашего векторизованного mysquare, рассмотрим случай, когда T является BigFloat. Ваш выходной массив, однако, Float64. Один из способов справиться с этим - изменить его на

function mysquare{T<:Number}(x::Array{T,1})
    n = length(x)
    y = Array(T, n)
    for k = 1:n
        @inbounds y[k] = x[k]^2
    end
    return y
 end

где я добавил макрос @inbounds для увеличения скорости, потому что нам не нужно каждый раз проверять нарушение связи - мы знаем длины. Эта функция все еще может иметь проблемы в случае, если тип x[k]^2 не T. Возможно, еще более защитная версия будет

function mysquare{T<:Number}(x::Array{T,1})
    n = length(x)
    y = Array(typeof(one(T)^2), n)
    for k = 1:n
        @inbounds y[k] = x[k]^2
    end
    return y
 end

где one(T) даст 1, если T является Int, а 1.0, если T является Float64 и т.д. Эти соображения имеют значение только в том случае, если вы хотите создать гиперпрочный библиотечный код. Если вы действительно будете иметь дело с Float64 или вещами, которые могут быть повышены до Float64 s, то это не проблема. Это похоже на тяжелую работу, но сила потрясающая. Вы всегда можете просто согласиться на производительность, подобную Python, и игнорировать всю информацию о типе.