Size: a a a

Язык программирования Julia / Julia programming language

2020 October 17

АО

Андрей Оськин... in Язык программирования Julia / Julia programming language
Я тут поиграл немного с тем, что он показывает и как обычно, выявилась опасность таких упрощённых тестов.

Если взять то, что он пишет и выделить часть, которая занимается изменением данных, то выглядит так:
using Statistics
using Setfield

struct Agent2I
   loc::Int
   junk::NTuple{100, Int}
end

mutable struct Agent2M
   loc::Int
   junk::NTuple{100, Int}
end

const REF_TUP = ntuple(i -> 0, 100)
const N = 10^5
xI = [Agent2I(i, REF_TUP) for i in 1:N]
xM = [Agent2M(i, REF_TUP) for i in 1:N]

function gI(x)
   for i in eachindex(x)
       xi = x[i]
       x[i] = @set xi.loc += 1
   end
   return mean(x -> x.loc, x)
end

function gM(x)
   for i in eachindex(x)
       x[i].loc += 1
   end
   return mean(x -> x.loc, x)
end


и тесты соответственно
julia> using BenchmarkTools
julia> @benchmark gI(x) setup=(x = deepcopy(xI)) evals=1
BenchmarkTools.Trial:
 memory estimate:  16 bytes
 allocs estimate:  1
 --------------
 minimum time:     2.074 ms (0.00% GC)
 median time:      2.577 ms (0.00% GC)
 mean time:        2.729 ms (0.00% GC)
 maximum time:     4.656 ms (0.00% GC)
 --------------
 samples:          108
 evals/sample:     1

julia> @benchmark gM(x) setup=(x = deepcopy(xM)) evals=1
BenchmarkTools.Trial:
 memory estimate:  16 bytes
 allocs estimate:  1
 --------------
 minimum time:     2.294 ms (0.00% GC)
 median time:      2.447 ms (0.00% GC)
 mean time:        2.691 ms (0.00% GC)
 maximum time:     3.729 ms (0.00% GC)
 --------------
 samples:          9
 evals/sample:     1
источник

АО

Андрей Оськин... in Язык программирования Julia / Julia programming language
То есть вроде бы как иммутабельный вариант немного побыстрее.

Но если немного усложнить функции...
function gI(x)
   for i in eachindex(x)
       xi = x[i]
       x[i] = @set xi.loc += 1
       x[i] = @set xi.loc *= 2
       x[i] = @set xi.loc -= 1
   end
   return mean(x -> x.loc, x)
end

function gM(x)
   for i in eachindex(x)
       x[i].loc += 1
       x[i].loc *= 2
       x[i].loc -= 1
   end
   return mean(x -> x.loc, x)
end
источник

АО

Андрей Оськин... in Язык программирования Julia / Julia programming language
То бенчмарки становятся уже такими:

julia> @benchmark gI(x) setup=(x = deepcopy(xI)) evals=1
BenchmarkTools.Trial:
 memory estimate:  16 bytes
 allocs estimate:  1
 --------------
 minimum time:     10.341 ms (0.00% GC)
 median time:      10.774 ms (0.00% GC)
 mean time:        11.016 ms (0.00% GC)
 maximum time:     13.029 ms (0.00% GC)
 --------------
 samples:          98
 evals/sample:     1

julia> @benchmark gM(x) setup=(x = deepcopy(xM)) evals=1
BenchmarkTools.Trial:
 memory estimate:  16 bytes
 allocs estimate:  1
 --------------
 minimum time:     2.089 ms (0.00% GC)
 median time:      2.275 ms (0.00% GC)
 mean time:        2.334 ms (0.00% GC)
 maximum time:     3.045 ms (0.00% GC)
 --------------
 samples:          9
 evals/sample:     1
источник

АО

Андрей Оськин... in Язык программирования Julia / Julia programming language
То есть пока манипуляции очень простые, то да, компилятор справляется и может написать очень хороший код, который лучше чем примитивные манипуляции с мутабельными контейнерами.

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

АО

Андрей Оськин... in Язык программирования Julia / Julia programming language
И никогда не известно, в какой момент это произойдёт.
источник

RS

Roman Samarev in Язык программирования Julia / Julia programming language
Миллисекунды - маловат интервал для измерения… Проблема точности сама по себе
источник

АО

Андрей Оськин... in Язык программирования Julia / Julia programming language
?
Почему? Это же бенчмарки, тут всегда так.
источник

АО

Андрей Оськин... in Язык программирования Julia / Julia programming language
Можно увеличить в 10 раз и посмотреть обычным time
источник

RS

Roman Samarev in Язык программирования Julia / Julia programming language
Самый быстрый/точный способ измерения времени на AMD64 - счётчик тактов rdtsc. Но проблема в том, что в сыром виде сейчас этот счётчик использовать уже нельзя - плавает в зависимости от текущей частоты и не синхронизирован между ядрами. Любая обвязка даёт лишние наносекунды-десятки наносекунд. Если интервалы - микросекунды, имеем погрешность от единиц до десятков процентов
источник

АО

Андрей Оськин... in Язык программирования Julia / Julia programming language
O_O
источник

АО

Андрей Оськин... in Язык программирования Julia / Julia programming language
Ну так ради того, чтобы эту проблему обойти и сделали BenchmarkTools
источник

A

Andrey in Язык программирования Julia / Julia programming language
Roman Samarev
Самый быстрый/точный способ измерения времени на AMD64 - счётчик тактов rdtsc. Но проблема в том, что в сыром виде сейчас этот счётчик использовать уже нельзя - плавает в зависимости от текущей частоты и не синхронизирован между ядрами. Любая обвязка даёт лишние наносекунды-десятки наносекунд. Если интервалы - микросекунды, имеем погрешность от единиц до десятков процентов
Что-то напутали с порядком величин
источник

АО

Андрей Оськин... in Язык программирования Julia / Julia programming language
Делается много тестов, собирается статистика и т.п.
источник

A

Andrey in Язык программирования Julia / Julia programming language
Нано - это 1/1 000 000 от милли
источник

RS

Roman Samarev in Язык программирования Julia / Julia programming language
Andrey
Нано - это 1/1 000 000 от милли
Пожалуй, да. На длительности в 2 миллисекунды, ошибка уже не очень существенная
источник

RS

Roman Samarev in Язык программирования Julia / Julia programming language
Вообще, надо бы проверить это для современных процессоров. Сколько сейчас на них получается погрешность измерения времени - вопрос интересный…
источник

АО

Андрей Оськин... in Язык программирования Julia / Julia programming language
Насколько я понимаю ошибка от измерения времени относительно невелика, гораздо большее влияние оказывает то, что задача вычисляется внутри операционной системы, где паралельно может ещё куча других процессов бежать.
источник

АО

Андрей Оськин... in Язык программирования Julia / Julia programming language
Ну а вообще, ребята из BenchmarkTools не дураки, у них для снижения ошибки есть фактор evals per sample.

То есть, если у тебя функция вычисляется 20 наносекунд, то они обернут её в цикл и будут измерять суммарное время 1000 запусков этой функции, тем самым уменьшая в соответствующее количество раз ошибку измерения времени.
источник

RS

Roman Samarev in Язык программирования Julia / Julia programming language
А тут вступает в дело вытесняющая многозадачность и размер кванта процессорного времени. Вообще, он довольно большой. Порядка 5-25 миллисекунд для настольных операционок и даже больше для серверных.
источник

АО

Андрей Оськин... in Язык программирования Julia / Julia programming language
Ну да, поэтому они берут много сэмплов (по дефолту - 10 000) и собирают статистику нужную.
Если у тебя слишком маленькое время, то запустив 10 000 раз вычисления и взяв самое меньшее из них, ты получишь достаточно хорошую оценку времени вычисления самой функции без влияния операционки.
источник