您好,登錄后才能下訂單哦!
SPL的特征之一是數據有序,適當地利用位置,可以顯著提高性能。讓我們先從一個典型場景開始,逐步掌握利用位置的各種技巧。
對排序后的數據進行二分查找,可以獲得較高的性能,但有些算法需用到原始順序,看上去似乎不該再排序。比如下面的案例:
PerformanceRanking.txt有三個字段,分別是empID(銷售員編號)、dep(部門名稱)、amount(銷售額)。該文件記錄著各部門各銷售員本季度的業績排名,已按銷售額逆序存放,現在需根據指定的銷售員ID,計算出:他應當再增加多少銷售額,才能提高業績排名。如果該員工已經是第1名,則無需增加銷售額。
本算法需要用排名高一位的銷售員的銷售額,減去該銷售員的銷售額,即對原始數據做相對位置計算。既然要用到原始順序,似乎就不該再排序,否則兩者難以互轉,而且其他算法可能用到原始數據。這種思路下會把腳本寫成這樣:
A | B | |
1 | =file("PerformanceRanking.txt").import@t() | /讀取數據 |
2 | =A1.pselect(empID:10) | /不利用位置,按empID查詢記錄序號 |
3 | =A1.calc(A2,if(#>1,amount[-1]-amount,0)) | /在原始數據中做相對位置計算 |
上述腳本沒有對數據排序,所以不能進行二分查找,性能不高。
事實上,我們可以在保留原始數據的前提下,利用位置進行排序,從而提高查詢性能。腳本如下:
A | B | |
5 | =oPos=A1.psort(empID) | /排序后記錄在原數據中的位置 |
6 | =index=A1(oPos) | /利用位置制造排序數據 |
7 | =oPos(index.pselect@b(empID: 10)) | /二分查找,獲得序號 |
8 | =A1.calc(A7,if(#>1,amount[-1]-amount,0)) | /在原始數據中做相對位置計算 |
A5:函數psort只獲得排序后記錄在原數據中的位置,并不會對原數據真正排序。
A6:利用oPos制造一份排序后的數據。注意,此時原數據不受影響,而且oPos可以作為排序后數據index和原始數據之間互轉的橋梁。
A7:對排序后的數據做二分查找,并轉回原始數據中對應的記錄序號。
為了驗證利用位置之前、之后兩種算法的性能差別,可以隨機取出銷售員編號做參數,用循環模擬大量訪問,并分別執行兩種算法。如下:
A | B | |
10 | =100000.(A1(rand(A1.len())+1).empID) | /制造1萬個empID |
11 | =now() | |
12 | for A10 | =A1.pselect(empID:A12) |
13 | =A1.calc(B12,if(#>1,amount[-1]-amount,0)) | |
14 | =interval@ms(A11,now()) | /不利用位置,耗時:13552毫秒 |
15 | ||
16 | =now() | |
17 | for A10 | =oPos(index.pselect@b(empID: A17)) |
18 | =A1.calc(B17,if(#>1,amount[-1]-amount,0)) | |
19 | =interval@ms(A16,now()) | /利用位置,耗時:165毫秒 |
可以看到,利用位置后性能提高幾十倍。例子中數據量較少,隨著數據量的增加,性能差距會急劇拉大,這是因為遍歷查找的時間復雜度為線性,而二分查找為對數。
函數align可將數據按序列對齊,比如輸入條件:=pOrderList= [10250,10247,10248,10249,10251],將訂單明細按該列表對齊,求每個訂單的金額小計。代碼如下:
A | |
1 | =connect("demo").query@x("select orderID,productID,price,quantity from orderDetail") |
2 | =A1.align@a(pOrderList,orderID).new(orderID,~.sum(price * quantity):小計) |
但上述寫法沒有利用位置,性能因此不高。要想提高性能,可以將序列排序(手工建立索引表),再用二分法對齊,最后恢復為原順序,代碼如下:
A | |
1 | =connect("demo").query@x("select orderID,productID,price,quantity from orderDetail") |
2 | =oPos= pOrderList.psort() |
3 | =index= pOrderList (oPos) |
4 | =A1.align@ab(index,orderID).new(orderID,~.sum(price * quantity):total) |
5 | =A4.inv(oPos) |
A2-A3:手工建立索引表。
A4:將訂單明細表與訂單列表對齊,求出金額小計。由于索引表有序,因此可用二分法對齊,即@b選項。
A5:將A4按原位置調整,與pOrderList的順序保持一致。函數inv可按指定位置調整成員,這里按原位置調整成員,相當于恢復成原位置。
對利用位置前后的兩種算法,模擬大訪問量測試,可以看到性能提升顯著:
A | B | |
8 | =now() | |
9 | for A9 | =A1.align@a(A12,orderID).new(orderID,~.sum(price*quantity):total) |
10 | =interval@ms(A11,now()) | /不利用位置,耗時43456毫秒 |
11 | ||
12 | =now() | |
13 | for A9 | =oPos=A16.psort() |
14 | =index=A16(oPos) | |
15 | =A1.align@ab(index,orderID).new(orderID,~.sum(price*quantity):total) | |
16 | =B18.inv(oPos) | |
17 | =interval@ms(A15,now()) | /利用位置,耗時7313毫秒 |
18 | =now() | |
19 | for A9 | =A1.align@a(A12,orderID).new(orderID,~.sum(price*quantity):total) |
20 | =interval@ms(A11,now()) | /不利用位置,耗時43456毫秒 |
有時要對有序數據進行批量查詢,比如pOrderList=[10877,10588,10611,11037,10685],請統計符合該列表的訂單的運貨費合計,代碼可以這樣寫:
A | B | |
1 | =connect("demo").query@x("select orderID,customerID,orderDate,shippingCharge from order order by orderID") | |
2 | =pOrderList=[10877,10588,10611,11037,10685] | /列表參數 |
3 | =A1.select(pOrderList.pos(OrderID)).sum(shippingCharge) | /不利用位置,單次代碼 |
解釋:函數pos和select配合,可實現批量查詢。其中函數pos可返回某個值在序列中的位置,如該值不在序列中,則返回null。函數select用于查詢,當條件非null且非false時,可返回當前記錄。
但上述代碼沒有利用位置,所以性能不高。
應當注意到,訂單記錄是有序的,所以可以用二分法取得符合條件的訂單位置,再用位置取記錄并計算。具體代碼如下:
A | B | |
5 | =A1.(orderID).pos@b(pOrderList) | |
6 | =A1(A5).sum(shippingCharge) | /利用位置,單次代碼 |
A1.(orderID)可取得orderID列,pos@b可針對有序數據,用二分法快速取得成員位置。A6按位置取數據。
對利用位置前后的兩種算法,模擬大訪問量測試,可以看到性能提升顯著:
A | B | |
8 | =A1.(OrderID) | /性能測試準備 |
9 | =100000.(A1.(OrderID).sort(rand()).to(rand(100))) | /隨機生成100000個列表 |
10 | ||
11 | =now() | |
12 | for A9 | =A1.select(A12.pos(OrderID)).sum(shippingCharge) |
13 | =interval@ms(A11,now()) | /不利用位置,85166毫秒 |
14 | ||
15 | =now() | |
16 | for A9 | =A1.(OrderID).pos@b(A16) |
17 | =A1(B16).sum(shippingCharge) | |
18 | =interval@ms(A15,now()) | /利用位置,3484毫秒 |
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。