簡體   English   中英

Erlang:為什么在列表上使用遞歸+反向而不是地圖最快的操作?

[英]Erlang : Why operation on Lists are fastest with recursivity + reverse instead of map?

我很好奇在List上執行操作最快的方法是什么。

我創建了一個小模塊來幫助我解決這個問題:

-module(tests).

-compile(export_all).

createRandomList(Nb) ->
    [random:uniform(10) || _ <- lists:seq(1, Nb)].

fastest() ->
    List = createRandomList(10000),
    StartFlatten = tools:getTimeStampMilli(),
    flatten(List),
    StopFlatten = tools:getTimeStampMilli(),
    debug:print("Flatten~n"),
    debug:print("Start at: ~p~nStop at: ~p~nTime elapsed: ~p~n", [StartFlatten, StopFlatten, (StopFlatten-StartFlatten)]),
    debug:print("#############~n~n"),
    StartReverse = tools:getTimeStampMilli(),
    reverse(List),
    StopReverse = tools:getTimeStampMilli(),
    debug:print("Reverse~n"),
    debug:print("Start at: ~p~nStop at: ~p~nTime elapsed: ~p~n", [StartReverse, StopReverse, (StopReverse-StartReverse)]),
    debug:print("#############~n~n"),
    StartMap = tools:getTimeStampMilli(),
    map(List),
    StopMap = tools:getTimeStampMilli(),
    debug:print("Map~n"),
    debug:print("Start at: ~p~nStop at: ~p~nTime elapsed: ~p~n", [StartMap, StopMap, (StopMap-StartMap)]),
    debug:print("#############~n~n").

op(A) ->
    A+2.

flatten(List) ->
    flatten(List, []).
flatten([], Accu) ->
    Accu;
flatten([Obj|Tail], Accu) ->
    flatten(Tail, lists:flatten([Accu, op(Obj)])).

reverse(List) ->
    reverse(List, []).
reverse([], Accu) ->
    lists:reverse(Accu);
reverse([Obj|Tail], Accu) ->
    reverse(Tail, [op(Obj)|Accu]).

map(List) ->
    lists:map(fun op/1, List).

結果如下:

----debug: Flatten
Start at: 1424535074364
Stop at: 1424535274083
Time elapsed: 1.99719 s
----debug: #############

----debug: Reverse
Start at: 1424535274208
Stop at: 1424535274551
Time elapsed: 0.00343 s
----debug: #############

----debug: Map
Start at: 1424535274631
Stop at: 1424535275095
Time elapsed: 0.00464 s
----debug: #############

我實際上可以理解為什么使用flatten方法比其他方法更長的時間...但是我認為為List上的操作創建的函數比我自己的帶有遞歸的遞歸函數要快...

有人解釋嗎?

或者我的模塊中可能只有一個難看的錯誤!

EDIT1:如果在更大的List上一次又一次調用測試,這就是我得到的:

3> tests:fastest().
----debug: Reverse
Start at: 1424536230201935
Stop at: 1424536230262924
Time elapsed: 60989
----debug: #############

----debug: Map
Start at: 1424536230263066
Stop at: 1424536230326419
Time elapsed: 63353
----debug: #############

ok
4> tests:fastest().
----debug: Reverse
Start at: 1424536231860951
Stop at: 1424536231917979
Time elapsed: 57028
----debug: #############

----debug: Map
Start at: 1424536231918116
Stop at: 1424536231975828
Time elapsed: 57712
----debug: #############

ok
5> tests:fastest().
----debug: Reverse
Start at: 1424536233253424
Stop at: 1424536233309301
Time elapsed: 55877
----debug: #############

----debug: Map
Start at: 1424536233309430
Stop at: 1424536233375391
Time elapsed: 65961
----debug: #############

ok
6> tests:fastest().
----debug: Reverse
Start at: 1424536235622322
Stop at: 1424536235675287
Time elapsed: 52965
----debug: #############

----debug: Map
Start at: 1424536235675424
Stop at: 1424536235739555
Time elapsed: 64131
----debug: #############

很奇怪,不是嗎?

編輯2:

確定成功使用timer:tc,結果如下:

1> tests:fastest().
ReverseTime: 58455
MapTime: 47507
ok
2> tests:fastest().
ReverseTime: 29887
MapTime: 61311
ok
3> tests:fastest().
ReverseTime: 61563
MapTime: 68040
ok
4> tests:fastest().
ReverseTime: 55874
MapTime: 57388
ok
5> tests:fastest().
ReverseTime: 56712
MapTime: 61326
ok

有點像以前

正如Pascal在對問題的評論中觀察到的那樣,Erlang標准庫中lists:map/2的實現按照其在原始列表中出現的確切順序從頭到尾構造了一個新的列表。 您的“反向”函數以相反的順序或從頭到尾構造它。

從頭到尾排序的副作用之一是虛擬機必須每次調用map函數的結果返回給調用它的函數。 也就是說,對函數的每次調用都會在調用堆棧中添加另一個返回地址 ,一旦完成最終調用就必須訪問該返回地址 ,以產生最終的返回值。 您可以在調用本身的語法中看到以下內容:

map(F, [H|T]) ->
    %% At this point, the result of map(F, T) is unknown,
    %% so we must evaluate it to determine what the tail
    %% of this expression should be.
    [F(H)|map(F, T)];
map(F, []) ->
    [].

在另一邊,尾至頭排序可以為尾遞歸優化,這意味着原來的堆棧的所有后續調用重用 這樣可以有效地優化我們在以其他方式構建列表時必須訪問的所有那些寄信人地址。 確實,如果您刪除對lists:reverse/1調用,則會看到從頭到尾的排序更加一致地勝過了從頭到尾的排序。

reverse(F, [H|T], Acc) ->
    %% Here, we know what Acc is, so the only thing
    %% we have to evaluate is F(H). 
    %% And since we're calling ourselves recursively and
    %% do nothing with the return value of the function, 
    %% we're eligible for tail-call optimization.
    reverse(F, T, [F(H)|Acc]);
reverse(F, [], Acc) ->
    %% Now we launch into another function that's eligible
    %% for tail-call optimization (though its implementation
    %% uses another form of stack sharing, known as a "while
    %% loop"
    lists:reverse(Acc).

最后, lists:reverse/1是經過C優化的BIF,其執行地獄的速度比任何等效的純Erlang函數(通過繞過BEAM解釋器和一些智能分配技巧)要快得多。 即使沒有考慮,反向操作也是另一種不需要訪問各個返回點的尾遞歸操作,由於Erlang對原始列表中的術語進行了淺拷貝 (實際上是使用指針),因此反向操作更加高效/對該術語的引用,而不是該術語本身的新副本)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM