Prepend vs. Append perf в Mathematica - PullRequest
       28

Prepend vs. Append perf в Mathematica

8 голосов
/ 09 декабря 2011

в Lisp-подобных системах cons - это обычный способ ПОДГОТОВИТЬ элемент к списку.Функции, которые добавляются в список, намного дороже, потому что они проходят список до конца, а затем заменяют окончательный ноль ссылкой на добавленный элемент.IOW (pseudoLisp)

(prepend list item) = (cons item list) = cheap!
(append list item) = (cond ((null? list) (cons item null))
                           (#t (cons (car list (append (cdr list) item)))))

Вопрос в том, похожа ли ситуация в Mathemtica?В большинстве случаев списки Mathematica кажутся односвязными, как списки lisp, и, если это так, мы можем предположить, что Append [list, item] намного дороже, чем Prepend [list, item].Тем не менее, я не смог найти ничего в документации Mathematica для решения этого вопроса.Если списки Mathematica дважды связаны или реализованы более умно, скажем, в куче или просто с указателем на последний, вставка может иметь совершенно другой профиль производительности.

Буду признателен за любые советы или опыт..

Ответы [ 4 ]

21 голосов
/ 09 декабря 2011

Списки Mathematica не являются односвязными списками, как в Common Lisp. Лучше думать о списках mathematica как о структурах массива или вектора. Скорость вставки O (n), но скорость извлечения постоянна.

Проверьте эту страницу из Структуры данных и эффективные алгоритмы в Mathematica , в которых более подробно рассматриваются списки Mathematica.

Дополнительно, пожалуйста, проверьте этот вопрос переполнения стека в связанных списках и их производительность в mathematica.

8 голосов
/ 10 декабря 2011

Поскольку, как уже упоминалось, списки Mathematica реализованы в виде массивов, такие операции, как Append и Prepend, вызывают копирование списка каждый раз при добавлении элемента.Более эффективный метод состоит в том, чтобы предварительно выделить список и заполнить его, однако мой эксперимент ниже не показал столь большой разницы, как я ожидал.Еще лучше, по-видимому, метод связанного списка, который мне придется исследовать.

Needs["PlotLegends`"]
test[n_] := Module[{startlist = Range[1000]},
   datalist = RandomReal[1, n*1000];
   appendlist = startlist;
   appendtime = 
    First[AbsoluteTiming[AppendTo[appendlist, #] & /@ datalist]];
   preallocatedlist = Join[startlist, Table[Null, {Length[datalist]}]];
   count = -1;
   preallocatedtime = 
    First[AbsoluteTiming[
      Do[preallocatedlist[[count]] = datalist[[count]]; 
       count--, {Length[datalist]}]]];
   {{n, appendtime}, {n, preallocatedtime}}];
results = test[#] & /@ Range[26];
ListLinePlot[Transpose[results], Filling -> Axis, 
 PlotLegend -> {"Appending", "Preallocating"}, 
 LegendPosition -> {1, 0}]

Временная диаграмма, сравнивающая AppendTo с предварительным распределением.(Время выполнения: 82 секунды)

timing chart

Редактировать

Использование предложенной модификации nixeagle значительно улучшило время предварительного выделения, т. Е. С preallocatedlist = Join[startlist, ConstantArray[0, {Length[datalist]}]];

enter image description here

Второе редактирование

Связанный список вида {{{startlist}, data1}, data2} работает еще лучшеи имеет большое преимущество, заключающееся в том, что размер не нужно знать заранее, как это делается для предварительного распределения.

Needs["PlotLegends`"]
test[n_] := Module[{startlist = Range[1000]},
   datalist = RandomReal[1, n*1000];
   linkinglist = startlist;
   linkedlisttime = 
    First[AbsoluteTiming[
      Do[linkinglist = {linkinglist, datalist[[i]]}, {i, 
        Length[datalist]}];
      linkedlist = Flatten[linkinglist];]];
   preallocatedlist = 
    Join[startlist, ConstantArray[0, {Length[datalist]}]];
   count = -1;
   preallocatedtime = 
    First[AbsoluteTiming[
      Do[preallocatedlist[[count]] = datalist[[count]]; 
       count--, {Length[datalist]}]]];
   {{n, preallocatedtime}, {n, linkedlisttime}}];
results = test[#] & /@ Range[26];
ListLinePlot[Transpose[results], Filling -> Axis, 
 PlotLegend -> {"Preallocating", "Linked-List"}, 
 LegendPosition -> {1, 0}]

Сравнение времени связанного списка с предварительным распределением.(Время выполнения: 6 секунд)

enter image description here

7 голосов
/ 10 декабря 2011

Если вы знаете, сколько элементов будет иметь ваш результат, и если вы сможете рассчитать свои элементы, тогда весь Append, AppendTo, Linked-List и т. Д. Не требуется.В тесте скорости Криса, предварительное распределение работает только потому, что он заранее знает количество элементов.Операция доступа к datelist означает виртуальное вычисление текущего элемента.

Если бы ситуация была такой, я бы никогда не использовал такой подход.Простой стол в сочетании с объединением - это, черт возьми, быстрее.Позвольте мне повторно использовать код Криса: я добавляю предварительное распределение к измерению времени, потому что при использовании «Добавить» или связанного списка также определяется распределение памяти.Кроме того, я действительно использую полученные списки и проверяю, равны ли они, потому что умный интерпретатор может распознать простые, бесполезные команды и оптимизировать их.

Needs["PlotLegends`"]
test[n_] := Module[{
    startlist = Range[1000],
    datalist, joinResult, linkedResult, linkinglist, linkedlist, 
    preallocatedlist, linkedlisttime, preallocatedtime, count, 
    joinTime, preallocResult},


   datalist = RandomReal[1, n*1000];
   linkinglist = startlist;
   {linkedlisttime, linkedResult} = 
    AbsoluteTiming[
     Do[linkinglist = {linkinglist, datalist[[i]]}, {i, 
       Length[datalist]}];
     linkedlist = Flatten[linkinglist]
     ];

   count = -1;
   preallocatedtime = First@AbsoluteTiming[
      (preallocatedlist = 
        Join[startlist, ConstantArray[0, {Length[datalist]}]];
       Do[preallocatedlist[[count]] = datalist[[count]];
        count--, {Length[datalist]}]
       )
      ];

   {joinTime, joinResult} =
    AbsoluteTiming[
     Join[startlist, 
      Table[datalist[[i]], {i, 1, Length[datalist]}]]];
   PrintTemporary[
    Equal @@@ Tuples[{linkedResult, preallocatedlist, joinResult}, 2]];
   {preallocatedtime, linkedlisttime, joinTime}];

results = test[#] & /@ Range[40];
ListLinePlot[Transpose[results], PlotStyle -> {Black, Gray, Red}, 
 PlotLegend -> {"Prealloc", "Linked", "Joined"}, 
 LegendPosition -> {1, 0}]

enter image description here

На мой взгляд, интересны ситуации, когда вы заранее не знаете количество элементов и вам нужно решить, неважно, вы или нет.должен добавить / предварять что-то.В этих случаях стоит взглянуть на Рип [] и Соу [].В общем, я бы сказал, AppendTo - это зло, и прежде чем использовать его, взгляните на альтернативы:

n = 10.^5 - 1;
res1 = {};
t1 = First@AbsoluteTiming@Table[With[{y = Sin[x]},
      If[y > 0, AppendTo[res1, y]]], {x, 0, 2 Pi, 2 Pi/n}
     ];

{t2, res2} = AbsoluteTiming[With[{r = Release@Table[
        With[{y = Sin[x]},
         If[y > 0, y, Hold@Sequence[]]], {x, 0, 2 Pi, 2 Pi/n}]},
    r]];

{t3, res3} = AbsoluteTiming[Flatten@Table[
     With[{y = Sin[x]},
      If[y > 0, y, {}]], {x, 0, 2 Pi, 2 Pi/n}]];

{t4, res4} = AbsoluteTiming[First@Last@Reap@Table[With[{y = Sin[x]},
        If[y > 0, Sow[y]]], {x, 0, 2 Pi, 2 Pi/n}]];

{res1 == res2, res2 == res3, res3 == res4}
{t1, t2, t3, t4}

Дает {5.151575, 0.250336, 0.128624, 0.148084}.Конструкция

Flatten@Table[ With[{y = Sin[x]}, If[y > 0, y, {}]], ...]

, к счастью, действительно читабельна и быстра.

Замечание

Будьте осторожны, пытаясь повторить последний пример дома.Здесь, на моем Ubuntu 64bit и Mma 8.0.4, AppendTo с n = 10 ^ 5 занимает 10 ГБ памяти.n = 10 ^ 6 занимает всю мою оперативную память, которая составляет 32 ГБ, чтобы создать массив, содержащий 15 МБ данных.Забавно.

7 голосов
/ 10 декабря 2011

В качестве небольшого дополнения, вот эффективная альтернатива "AppendTo" в M-

myBag = Internal`Bag[]
Do[Internal`StuffBag[myBag, i], {i, 10}]
Internal`BagPart[myBag, All]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...