2011-02-23 18 views
22

在數學,我創建單鏈表,像這樣:數學「鏈表」和性能

toLinkedList[x_List] := Fold[pair[#2, #1] &, pair[], Reverse[x]]; 

fromLinkedList[ll_pair] := List @@ Flatten[ll]; 

emptyQ[pair[]] := True; 
emptyQ[_pair] := False;  

使用符號pair的利弊細胞具有Flatten安全工作,即使列表包含Mathematica-優勢style List s,並且允許您使用MakeExpression/MakeBoxes來定義自定義符號,這使得一切都變得更加愉快。爲了避免與$IterationLimit混淆,我編寫了使用While循環或NestWhile而不是遞歸使用這些列表的函數。當然,我想看看哪種方法會更快,所以我寫了兩個候選人,所以我能看到「時間打:

nestLength[ll_pair] := 
With[{step = {#[[1, -1]], #[[-1]] + 1} &}, 
    [email protected][step, {ll, 0}, ! [email protected]@# &]]; 

whileLength[ll_pair] := 
Module[{result = 0, current = ll}, 
    While[! [email protected], 
    current = current[[2]]; 
    ++result]; 
    result]; 

結果非常奇怪。我測試了長度爲10000的鏈表上的函數,並且whileLength通常快了大約50%,在大約0.035秒到nestLength的0.055秒處。但是,偶爾whileLength需要約4秒。我認爲可能存在一些緩存行爲,所以我開始生成新的隨機列表來檢查,並且whileLength在第一次運行時不一定會很慢,並且具有新的列表;它可能需要幾十次才能看到放緩,但不會再發生(至少不是我試圖用每個列表進行的200次)。

可能會發生什麼?

僅供參考,我用於測試的功能是這樣的:

getTimes[f_, n_] := 
With[{ll = [email protected][100, 10000]}, 
    Table[Timing[[email protected]], {n}][[All, 1]]] 

編輯:我忘了前面提到的版本;我得到這些結果與數學8.

編輯第二:當我讀到Daniel Lichtblau's answer,我意識到,我的時間「典型」運行省略了領先的0它已經固定。

編輯第三個:我認爲Leonid Shifrin是正確的關聯問題與Module;我可以用Module更換With得到相同的排序從NestWhile基於版本的行爲:

nestModuleLength[ll_pair] := 
    Module[{step = {#[[1, -1]], #[[-1]] + 1} &}, 
    [email protected][step, {ll, 0}, ! [email protected]@# &]]; 

In[15]:= Select[getTimes[nestModuleLength, 100], # > 3 &] 
Out[15]= {3.797} 
+0

Developer'PackedArrayQ可能是相關 – 2011-02-23 20:36:13

+0

@Yaroslav Bulatov:我不明白爲什麼包裝陣列將是相關的,因爲沒有應裝除非'RandomInteger生成的初始'List' ',它立即轉換成樹狀表達式。 – Pillsy 2011-02-23 20:52:57

+2

您使用的是版本7還是版本8?無論如何,對於它的價值,我認爲你已經發現了一些錯誤,或者至少是評估行爲中的一個弱點。 – 2011-02-24 02:28:56

回答

9

的例子給出了典型的結果。

長度爲20的一個緩慢示例。

In[18]:= getTimes[whileLength, 20] 

Out[18]= {0.031, 0.032, 0.031, 0.031, 0.031, 0.032, 0.031, 0.031, \ 
0.031, 0.047, 0.032, 0.031, 0.031, 3.547, 0.047, 0.031, 0.031, 0.032, \ 
0.031, 0.031} 

我順便指出,而定時爲10倍〜比在原崗位快,除了速度慢的情況具有可比性。不確定什麼說明了這種差異。

沒有緩慢的例子。

In[17]:= getTimes[nestLength, 20] 

Out[17]= {0.047, 0.047, 0.062, 0.047, 0.047, 0.062, 0.047, 0.047, \ 
0.047, 0.063, 0.046, 0.047, 0.047, 0.063, 0.047, 0.046, 0.047, 0.063, \ 
0.047, 0.047} 

長度爲100的一個緩慢示例。

In[19]:= getTimes[whileLength, 100] 

Out[19]= {0.031, 0.031, 0.031, 0.032, 0.031, 3.594, 0.047, 0.031, \ 
0.031, 0.031, 0.032, 0.031, 0.031, 0.031, 0.032, 0.031, 0.047, 0.031, \ 
0.031, 0.031, 0.032, 0.031, 0.031, 0.031, 0.032, 0.047, 0.031, 0.031, \ 
0.031, 0.032, 0.031, 0.031, 0.031, 0.032, 0.031, 0.031, 0.047, 0.031, \ 
0.031, 0.032, 0.031, 0.031, 0.031, 0.032, 0.031, 0.031, 0.047, 0.031, \ 
0.032, 0.031, 0.031, 0.031, 0.032, 0.031, 0.031, 0.047, 0.031, 0.031, \ 
0.032, 0.031, 0.031, 0.031, 0.032, 0.031, 0.047, 0.031, 0.031, 0.032, \ 
0.031, 0.031, 0.031, 0.032, 0.031, 0.031, 0.031, 0.032, 0.046, 0.032, \ 
0.031, 0.031, 0.031, 0.032, 0.031, 0.031, 0.047, 0.031, 0.032, 0.031, \ 
0.031, 0.031, 0.032, 0.031, 0.047, 0.031, 0.031, 0.031, 0.032, 0.031, \ 
0.031, 0.031} 

Mathematica不完美地實現了所謂的「無限評估」。也就是說,表達式會重新評估直到它停止改變。爲了使這個過程合理快速,有各種優化嘗試儘可能短路過程。

在某些情況下,可能很難辨別(由於與散列衝突類似的效果),並且可能不必要地重新評估表達式。深度嵌套的表達式往往是最糟糕的情況。我們還有更多的代碼,即使在發生碰撞的情況下也可以解決這些問題。

在這個例子中的罪魁禍首正是這個代碼,試圖迅速確定表達式是否需要重新評估。這是特殊的,但可能是一個線索(對某人),在While循環內最多發生一次這種情況。因此,在壞情況下發生的事情可以防止在同一時間內再次發生。

有一段時間我很熟悉重新評估檢測代碼,寫了一大塊。但是它被重寫爲版本8.所以即使在調試器中看到這種不理想的行爲,這對我來說也是一個謎。關於我現在可以說的一切,就是我提交了一個錯誤報告。

正如Leonid Shifrin所觀察到的,具有HoldAllComplete屬性的符號不受此問題影響。所以使用這個屬性對於這種類型的代碼可能是有益的。

丹尼爾Lichtblau 沃爾夫勒姆研究

+0

只是供參考。我正試圖重現在Workbench下運行的行爲,以便對其進行配置。但它只是完美無缺! – 2011-02-25 16:49:45

+0

@belisarius這個版本8.0?這在理論上可以有所作爲。除此之外,我很沮喪。 (不知道這意味着什麼,但我猜想它與毆打頭部被毆打有關,在某些日子裏它就是這種感覺。)另外,如果你改變了某些事物的名字,例如對 - >天才,這可能會導致碰撞消失。 – 2011-02-25 17:33:35

+0

@belisarius這可能會引發不良行爲。在getTimes中將最後一行更改爲Table [Update [];定時[f @ ll],{n}] [[All,1]] – 2011-02-25 17:35:20

4

好像它關係到模塊局部符號內存管理。

我會顯示一些運行的時序系列。每次運行當然會給出一個獨特的繪圖,但我在運行中檢查了「一致性」。看:

whileLength[l2_pair] := 
    Module[{result = 0}, current = l2; 
    While[! [email protected], current = current[[2]]; 
    ++result]; 
    result]; 

給出以下計時系列:

enter image description here

,而只使用全局符號:

whileLength[l2_pair] := 
    Module[{}, result = 0; current = l2; 
    While[! [email protected], current = current[[2]]; 
    ++result]; 
    result]; 

給出:

enter image description here

7

A免責聲明:以下是推測。這似乎與搜索UpValues有關。看起來這已經針對全局變量進行了優化(以便系統可以在確定可以執行此操作時跳過此步驟),但不會生成局部變量Module。爲了測試這一點,分配HoldAllComplete屬性pair,效果消失(從此,UpValues不檢查用於current):

SetAttributes[pair, HoldAllComplete]; 

In[17]:= ll = [email protected][100, 10000]; 
Max[Table[Timing[whileLength[ll]], {1000}][[All, 1]]] 

Out[18]= 0.047 

HTH下面

+0

「自那時起,UpValues不被檢查爲當前」;你能詳細說明一下嗎? – acl 2011-02-24 00:27:00

+2

@acl:當評估者符合表達式'f [a]'並且'f'具有'HoldAllComplete'屬性時,不僅'a'在'f'之前未被評估(發生在'a'的情況完全取決於規則對於'f'),但是也不會檢查與'a'相關聯的'UpValues',而當'f'具有'HoldAll'時會檢查它們。我不完全確定這是在這裏扮演什麼角色,但賦值的r.h.s看起來像'Part [pair [num,pair [..]],2]'。評估'Part'時,計算'pair [...]'(搜索'pair [___,element,___]'形式的'UpValues'),但如果'pair'爲'HoldAllComplete'則不會。 – 2011-02-24 01:08:14

+0

@acl:這被稱爲版本3中的「in-place」列表修改問題,但似乎已在後續版本中進行了優化(David Wagner在他的書中第7章第211頁對此進行了討論)。我的猜測是就地列表(表達式)修改的優化沒有覆蓋(或不完全覆蓋)由'Module'生成的局部變量的情況。正如我所說,我可能是錯的,這只是一個猜測。 – 2011-02-24 01:12:07