Существует ли быстрая операция с продуктом для PackedArrays?
В Mathematica вектор (или прямоугольный массив), содержащий все целые числа или числа с плавающей запятой, может храниться в упакованном массиве. Эти объекты занимают меньше памяти, а некоторые операции с ними выполняются намного быстрее.
RandomReal
производит упакованный массив, когда это возможно. Упакованный массив можно распаковать с Developer
функция FromPackedArray
Рассмотрим эти сроки
lst = RandomReal[1, 5000000];
Total[lst] // Timing
Plus @@ lst // Timing
lst = Developer`FromPackedArray[lst];
Total[lst] // Timing
Plus @@ lst // Timing
Out[1]= {0.016, 2.50056*10^6}
Out[2]= {0.859, 2.50056*10^6}
Out[3]= {0.625, 2.50056*10^6}
Out[4]= {0.64, 2.50056*10^6}
Следовательно, в случае упакованного массива Total
во много раз быстрее чем Plus @@
но примерно то же самое для неупакованного массива. Обратите внимание, что Plus @@
на самом деле немного медленнее на упакованном массиве.
Теперь рассмотрим
lst = RandomReal[100, 5000000];
Times @@ lst // Timing
lst = Developer`FromPackedArray[lst];
Times @@ lst // Timing
Out[1]= {0.875, 5.8324791357*10^7828854}
Out[1]= {0.625, 5.8324791357*10^7828854}
Наконец, мой вопрос: существует ли быстрый метод в Mathematica для списка продуктов упакованного массива, аналогичный Total
?
Я подозреваю, что это может быть невозможно из-за того, что числовые ошибки сочетаются с умножением. Кроме того, функция должна иметь возможность возвращать не-машинные числа с плавающей точкой, чтобы быть полезной.
3 ответа
Я также задавался вопросом, был ли мультипликативный эквивалент Total
,
Действительно не так уж плохое решение
In[1]:= lst=RandomReal[2,5000000];
Times@@lst//Timing
Exp[Total[Log[lst]]]//Timing
Out[2]= {2.54,4.370467929041*10^-666614}
Out[3]= {0.47,4.370467940*10^-666614}
Пока числа положительные и не слишком большие или маленькие, ошибки округления не так уж и плохи. Предположение относительно того, что может происходить во время оценки, заключается в следующем: (1) При условии, что числа являются положительными числами, Log
Операция может быть быстро применена к упакованному массиву. (2) Числа могут быть быстро добавлены с помощью Total
Метод упакованного массива. (3) Тогда это только последний шаг, когда возникнет необходимость в поплавке не машинного размера.
Посмотрите этот ответ SO для решения, которое работает как для положительных, так и для отрицательных чисел с плавающей точкой.
Давайте быстро проверим, что это решение работает с числами с плавающей запятой, которые дают ответ не машинного размера. Сравните с Эндрю (намного быстрее) compiledListProduct
:
In[10]:= compiledListProduct =
Compile[{{l, _Real, 1}},
Module[{tot = 1.}, Do[tot *= x, {x, l}]; tot],
CompilationTarget -> "C"]
In[11]:= lst=RandomReal[{0.05,.10},15000000];
Times@@lst//Timing
Exp[Total[Log[lst]]]//Timing
compiledListProduct[lst]//Timing
Out[12]= {7.49,2.49105025389*10^-16998863}
Out[13]= {0.5,2.4910349*10^-16998863}
Out[14]= {0.07,0.}
Если вы выбираете больше (>1
) значит, то compiledListProduct
выдаст предупреждениеCompiledFunction::cfne: Numerical error encountered; proceeding with uncompiled evaluation.
и займет некоторое время, чтобы дать результат...
Любопытно, что оба Sum
а также Product
может принимать произвольные списки. Sum
работает отлично
In[4]:= lst=RandomReal[2,5000000];
Sum[i,{i,lst}]//Timing
Total[lst]//Timing
Out[5]= {0.58,5.00039*10^6}
Out[6]= {0.02,5.00039*10^6}
но надолго PackedArray
s, такие как тестовые примеры здесь, Product
происходит сбой, так как автоматически скомпилированный код (в версии 8.0) не улавливает переполнения / переполнения должным образом:
In[7]:= lst=RandomReal[2,5000000];
Product[i,{i,lst}]//Timing
Times@@lst//Timing
Out[8]= {0.,Compile`AutoVar12!}
Out[9]= {2.52,1.781498881673*10^-666005}
Обходной путь, предоставляемый службой технической поддержки WRI, заключается в отключении компиляции продукта с помощью SetSystemOptions["CompileOptions" -> {"ProductCompileLength" -> Infinity}]
, Другой вариант заключается в использовании lst=Developer`FromPackedArray[lst]
,
Во-первых, чтобы избежать путаницы, взгляните на пример, все результаты которого представляются в виде чисел точности аппаратного обеспечения, которые должны быть меньше
In[1]:= $MaxMachineNumber
Out[1]= 1.79769*10^308
Ваш общий пример уже имел это хорошее (и быстрое) свойство. Вот вариант на вашем примере с использованием номеров машин:
In[2]:= lst = RandomReal[{0.99, 1.01}, 5000000];
Times @@ lst // Timing
Out[3]= {1.435, 1.38851*10^-38}
Теперь мы можем использовать Compile для создания скомпилированной функции для эффективного выполнения этой операции:
In[4]:= listproduct =
Compile[{{l, _Real, 1}},
Module[{tot = 1.}, Do[tot *= x, {x, l}]; tot]]
Out[4]= CompiledFunction[{l},Module[{tot=1.},Do[tot*=x,{x,l}];tot],-CompiledCode-]
Это намного быстрее:
In[5]:= listproduct[lst] // Timing
Out[5]= {0.141, 1.38851*10^-38}
Предполагая, что у вас есть компилятор C и Mathematica 8, вы также можете автоматически скомпилировать весь код на C. Временная DLL создается и связывается обратно с Mathematica во время выполнения.
In[6]:= compiledlistproduct =
Compile[{{l, _Real, 1}},
Module[{tot = 1.}, Do[tot *= x, {x, l}]; tot],
CompilationTarget -> "C"]
Out[6]= CompiledFunction[{l},Module[{tot=1.},Do[tot*=x,{x,l}];tot],-CompiledCode-]
Это дает производительность, не сильно отличающуюся от той, которая была бы у встроенной функции Mathematica:
In[7]:= compiledlistproduct[lst] // Timing
Out[7]= {0.015, 1.38851*10^-38}
Обратите внимание, что если ваш продукт действительно выйдет за пределы $ MaxMachineNumber (или $ MinMachineNumber), то вам лучше придерживаться Apply[Times, list]
, Тот же комментарий относится и к Total, если ваши результаты могут стать такими большими:
In[11]:= lst = RandomReal[10^305, 5000000];
Plus @@ lst // Timing
Out[12]= {1.435, 2.499873364498981*10^311}
In[13]:= lst = RandomReal[10^305, 5000000];
Total[lst] // Timing
Out[14]= {1.576, 2.500061580905602*10^311}
Метод Саймона быстр, но он не работает на отрицательных значениях. В сочетании с его ответом на другой мой вопрос, вот быстрое решение, которое обрабатывает негативы. Спасибо, Саймон.
функция
f = (-1)^(-1 /. Rule @@@ Tally@Sign@# /. -1 -> 0) * Exp@Total@Log@Abs@# &;
тестирование
lst = RandomReal[{-50, 50}, 5000000];
Times @@ lst // Timing
f@lst // Timing
lst = Developer`FromPackedArray[lst];
Times @@ lst // Timing
f@lst // Timing
{0.844, -4.42943661963*10^6323240}
{0.062, -4.4294366*10^6323240}
{0.64, -4.42943661963*10^6323240}
{0.203, -4.4294366*10^6323240}