Разница скорости при составлении таблицы
Я читал полезный пост в блоге WRI по улучшению скорости кода, и мне нужна помощь в понимании этого.
Сравните эти скорости
Timing[
tbl = Table[i + j, {i, 1, 1000}, {j, 1, 1000}];
]
{0.031, Null}
и
Timing[
a = 1000;
tbl = Table[i + j, {i, 1, a}, {j, 1, a}];
]
{0.422, Null}
Таким образом, это намного быстрее при установке фактического значения предела внутри самой таблицы против внешнего. Объяснение этому, которое, я уверен, правильно, но мне нужна помощь в понимании, заключается в том, что Table
скомпилирован, если его предел является числовым или нет, это потому, что его атрибуты HoldAll
.
Но мой вопрос: как бы это было на самом деле, потому что ограничения на Table
должны в какой-то момент стать числовыми? Я не могу написать
Clear[a]
tbl = Table[i + j, {i, 1, a}, {j, 1, a}]
Вышеприведенная ошибка.
Итак, для меня запись a=1000
вне Table
по сравнению с внутри, не должна была иметь никакого значения, так как без a
с числовым значением Table[]
ничего не может сделать. Таким образом, замена a
на число 1000 должна произойти в один момент времени оценщиком до того, как Table[]
может сделать что-нибудь полезное, не так ли?
Другими словами, то, что Table
должно видеть, в конечном счете, {i, 1, 1000}, {j, 1, 1000}
в обоих случаях.
Итак, как я думал, это произойдет, это:
- Оценщик заменяет
a
на 1000 в аргументах таблицы
- Оценщик вызывает
Table
с результатом, который теперь является все числовым.
- Компилирует таблицы и работает быстрее.
Но, похоже, что-то другое. (из-за HoldAll
?)
- Таблица принимает свои аргументы, как есть. Поскольку он имеет HoldAll, он видит
a
, а не 1000.
- Он не вызывает компиляцию, поскольку ее аргументы не являются всеми числами.
- Теперь он генерирует таблицу с пределом
a
, Оценщик оценивает a
до 1000
- Теперь создается таблица, все лимиты являются числовыми, но медленнее, поскольку код не скомпилирован.
Вопрос: Есть ли что-то вроде того, что происходит? Может кто-нибудь объяснить шаги, которые могли бы произойти, чтобы объяснить эту разницу в сроках?
Кроме того, , как бы обеспечить, чтобы таблица была скомпилирована в обоих случаях в приведенном выше примере, даже если для ограничения используется переменная? Не всегда можно жестко кодировать числа для пределов таблицы, но для этого нужно использовать переменные для них. Следует ли явно использовать команду Compile
? (Я не использую Compile
напрямую, так как я предполагал, что он выполняется автоматически, когда это необходимо).
изменить (1)
В ответ на сообщение Майка ниже, не обнаружив разницы во времени при использовании вызова.
ClearAll[tblFunc];
Timing[a = 1000;
tblFunc[a_] := Table[i + j, {i, 1, a}, {j, 1, a}];
Developer`PackedArrayQ[tblFunc[a]]
]
дает
{0.031, True}
Но это потому, что a
теперь является числом 1000
Вставить функцию, как только она будет вызвана. Поскольку M передает объекты с помощью VALUE.
Если мы заставляем вызов быть ссылкой, так что a
остается неоцененным, мы получаем
ClearAll[tblFunc];
Timing[a = 1000;
tblFunc[a_] := Table[i + j, {i, 1, a}, {j, 1, a}];
Developer`PackedArrayQ[tblFunc[[email protected]]]
]
теперь мы видим ожидаемый результат, так как теперь a
по-прежнему является символом ВНУТРИ функции, мы возвращаемся к квадрату, и теперь он медленный, так как не упакован. И поскольку он не упакован, компиляция не используется.
{0.437, False}
изменить (2)
Спасибо всем за ответы, я думаю, что я научился выделять их.
Вот резюме, просто чтобы убедиться, что я все понял.
![enter image description here]()
изменить (3)
Вот ссылки, которые я специально связал с подсказками для использования, чтобы ускорить выполнение кода Mathematica.
Ответы
Ответ 1
Так вот что я думаю, что происходит. Причина, по которой вы видите замедление между числовым и символическим пределом на Table
, объясняется тем, что вы делаете двойной индекс. Каждая подкатегория (например, переходя по всем индексам j
для фиксированного индекса i
) построена отдельно, а когда предел является символическим, есть дополнительный шаг, связанный с вычислением этого предела перед построением каждой подкатегории. Вы можете это увидеть, исследуя, например.
Trace[a = 3;
tbl = Table[i + j, {i, 1, a}, {j, 1, a}];
]
David дает хороший пример того, почему вы хотите сделать эту проверку для каждого подписок. Что касается того, почему Mathematica не может понять, когда эта проверка не нужна, я понятия не имею. Если у вас есть только один индекс для суммирования, нет никакой разницы в скорости между символической и числовой версией
Timing[tbl = Table[i + j, {j, 1, 1000}];]
{0.0012, Null}
Timing[a = 1000;
tbl = Table[i + j, {j, 1, a}];
]
{0.0013, Null}
Чтобы ответить на вопрос о скорости; делая tbl
, функция быстрее как для числового, так и для символического пределов.
Timing[a = 1000;
tblFunc[a_] := Table[i + j, {i, 1, a}, {j, 1, a}];
tblFunc[a];
]
{0.045171, Null}
против.
Timing[tbl = Table[i + j, {i, 1, 1000}, {j, 1, 1000}];]
{0.066864, Null}
Timing[a = 1000;
tbl = Table[i + j, {i, 1, a}, {j, 1, a}];
]
{0.632128, Null}
Вы получаете еще большую скорость, если собираетесь повторно использовать конструкцию tbl
.
b=1000;
Timing[tblFunc[b];]
{0.000013, Null}
Ответ 2
Ключевыми моментами для мониторинга, как отмечали другие, являются упаковка и длина списка. На самом деле я не вижу различий, о которых сообщает Timo:
ClearAll[tblFunc];
Timing[a = 1000;
tblFunc[a_] := Table[i + j, {i, 1, a}, {j, 1, a}];
Developer`PackedArrayQ[tblFunc[a]]]
{0.077706, True}
vs
ClearAll[tbl];
Timing[
tbl = Table[i + j, {i, 1, 1000}, {j, 1, 1000}];
Developer`PackedArrayQ[tbl]]
{0.076661, True}
ClearAll[tbl];
Timing[a = 1000;
tbl = Table[i + j, {i, 1, a}, {j, 1, a}];
Developer`PackedArrayQ[tbl]]
{1.02879, False}
Так что для меня единственная разница в том, что список упакован. Независимо от того, является ли это функцией, не имеет никакого значения для синхронизации по моей настройке. И, как и ожидалось, когда вы выключаете автокомпиляцию, тайминги одинаковы для всех вышеперечисленных, поскольку упаковка не происходит:
SetSystemOptions["CompileOptions" -> {"TableCompileLength" -> Infinity}];
{1.05084, False}
vs
{1.00348, False}
{1.01537, False}
reset длина автокопирования таблицы:
SetSystemOptions["CompileOptions" -> {"TableCompileLength" -> 250}]
Ответ 3
Это немного OT, но для скорости здесь вы можете избежать использования обработки по каждому элементу, что подразумевается при использовании таблицы. Скорее используйте Outer. Вот что я вижу в своей системе:
Timing[Outer[Plus, Range[5000], Range[5000]];]
{0.066763,Null}
Timing[Table[i + j, {i, 1, 5000}, {j, 1, 5000}];]
{0.555197,Null}
Довольно резкое различие.