您好,登錄后才能下訂單哦!
排序計算是一個非常消耗資源的操作,特別是對于大數據排序,如果內存無法裝下數據,常規的做法就需要借助外存,不過因此也會增加對數據的讀寫操作,而讀寫操作通常又會比排序操作更消耗資源。
本文介紹的SPL排序優化技巧,除了提供常規的排序算法外,還根據不同場景下的數據特性提供了排序的替代算法,從而減少比較次數和IO量,提升運算性能。
當數據可以輕松裝入內存時,可以使用SPL的內存排序函數,如A.sort()。SPL默認的排序算法是基于merge sort的多線程排序算法,也就是說,此時的優化方式主要是通過增加線程數量實現的。實際采用的線程數由集算器配置中的[最大并行數]指定。示例代碼如下:
A | B | |
1 | =5000*1000 | /元素數 |
2 | =A1\1000 | /隨機數最大值 |
3 | =to(A1).(rand(A2)) | /生成隨機序列 |
4 | =now() | /當前時間 |
5 | =A3.sort() | /升序排序 |
6 | =interval@ms(A4,now()) | /排序花費的時間 |
實測使用的的測試機CPU是酷睿i7 ,4核心 8線程,根據 [最大并行數]配置的不同,測試結果如下:
最大并行數 | 平均花費時間(毫秒) |
1(即單線程) | 1800 |
4 | 800 |
8 | 660 |
可見,多核心CPU或多CPU計算機通過多線程排序可以充分利用每個核心的并行計算能力,顯著提升排序性能。
此例中每個值的重復量平均為1000,對A.sort()函數來說,重復數量的多少對性能影響不大。但在重復數量較多時,我們還可以通過分組法A.group@s()進行排序,進一步提高性能。此方法首先利用哈希法對元素進行分組,然后再對組進行排序,最后合并排序后的組得到排序結果。示例代碼如下:
A | B | |
1 | =5000*1000 | /元素數 |
2 | =A1\1000 | /隨機數最大值 |
3 | =to(A1).(rand(A2)) | /生成隨機序列 |
4 | =now() | /當前時間 |
5 | =A3.group@s() | /每個值平均有1000個重復的,使用分組法進行升序排序 |
6 | =interval@ms(A4,now()) | /排序花費的時間 |
使用分組法排序后,平均花費時間為360毫秒,可見,此方法適合重復數量較多的數據,重復數量越多性能越好。
當數據量大到無法裝入內存時就需要借助外存進行排序。外存排序會分步讀入數據,排序后寫出到臨時文件,最后對所有生成的臨時文件歸并得到最終結果。
臨時文件的數量會影響排序的效率,如果數量過多,歸并階段會占用更多的資源,而歸并路數過多也會影響歸并效率。所以每次讀入較多的數據量可以提升sortx的性能。
SPL外存排序的函數是cs.sortx(),排序中生成的臨時文件數量由原始數據量和每次讀入的數據量決定,而每次讀入的數據量可以通過sortx函數的參數指定。用戶可以根據記錄大小和空閑內存容量指定一個合理的每次讀入數據量以達到最優的性能。如果沒有指定,SPL會根據虛擬機可用內存估算出一個大概值。
臨時文件的釋放會在結果游標取數結束或者close方法被調用時觸發。
測試外存排序需要大數據量,而由于使用JDBC從數據庫取數的效率非常差,因此本文將采用效率更好的集文件進行測試。
測試數據模擬訂單數據,表結構為{ order_id ,order_datetime , customer_id, employee_id , product_id , quantity ,price},按order_id 、order_datetime有序,隨機生成2000萬條記錄。
測試數據的生成也使用SPL,造數代碼如下:
A | B | |
1 | 2018-01-01 00:00:00 | =file("orders_2018.btx") |
2 | =to(1000*1000) | 0 |
3 | for 20 | =A2.new(B2+~:order_id, elapse@s(A1,order_id):order_datetime, rand(1000000) + 1:customer_id, rand(1000) + 1:employee_id, rand(10000) + 1:product_id, rand(1000) + 1:quantity, rand(100000000)/100:price) |
4 | =B1.export@ab(B3) | |
5 | >B2=B2+A2.len() |
排序計算的代碼如下:
A | B | |
1 | =now() | |
2 | =file("orders_2018.btx").cursor@b() | /生成訂單集文件的取數游標 |
3 | =A2.sortx(customer_id; 2000000) | /對游標按客戶編碼排序,第二個參數為每次讀入的數據量 |
4 | =file("orders_customer_2018.btx").export@b(A3) | /導出排序結果到集文件 |
5 | =interval@s(A1,now()) | /耗時,單位秒 |
測試結果如下:
每次讀入數據量 | 耗時(秒) |
200萬 | 73 |
20萬 | 216 |
使用SPL處理大數據運算時,為了獲取更好的性能通常會把數據外置成集文件或組表,同時按照常用的過濾維度排序以獲得更高的過濾性能。這樣,如果有新的數據要追加到歷史文件,并需要對所有數據重新排序,我們就可以利用集文件或者組表的這個特點了。由于歷史數據已經有序,此時我們可以先把新數據按照維度排序,然后再和歷史數據進行歸并就可以得到最終的有序數據了。使用這個方法排序涉及的數據量遠小于對所有數據進行大排序的數據量。具體的測試如下:
在前面外存排序的造數代碼中,將A1格改為2017-01-01 00:00:00,B1格改為=file("orders_2017.btx"),生成模擬2017年的訂單數據文件“orders_2017.btx”。然后使用外存排序代碼按customer_id字段排序,生成排序結果文件“orders_2017_customer.btx”。
然后,我們需要將2017、2018兩年的所有數據進行整體排序,也就是使用歸并方法合并orders_2017_customer.btx、orders_2018_customer.btx兩個文件成一個新的有序文件。示例代碼如下:
A | B | |
1 | =now() | |
2 | =file("orders_2017_customer.btx").cursor@b() | |
3 | =file("orders_2018_customer.btx").cursor@b() | |
4 | =[A2,A3].mergex(customer_id) | /對兩個按customer_id有序的游標做歸并,合成一個按customer_id有序的新游標 |
5 | =file("orders1.btx").export@b(A4) | /把排序后的數據導出到集文件 |
6 | =now() | =interval@s(A1,A6) |
7 | =file("orders_2017_customer.btx").cursor@b() | |
8 | =file("orders_2018_customer.btx").cursor@b() | |
9 | =[A7,A8].conjx().sortx(customer_id; 2000000) | /縱向連接兩個游標并進行外存排序 |
10 | =file("orders2.btx").export@b(A9) | |
11 | =now() | =interval@s(A6,A11) |
前6行代碼采用了歸并方法,耗時30秒,而后5行代碼模擬了簡單的大排序方法,耗時133秒。
如果需要按照多個字段對數據進行排序,而數據已經按照排序字段中的幾個字段有序了,則可以按照已經有序的字段分組讀入數據,在內存排序后輸出。這種處理方法比cs.sortx()少了一遍讀寫操作,因此性能大幅優于cs.sortx()。
例如訂單表的數據是按照日期時間有序生成的,如果想按照日期、客戶兩個字段對訂單表進行排序則可以使用這個方法。示例代碼如下:
A | B | |
1 | =now() | |
2 | =file("orders_2017.btx").cursor@b() | |
3 | =A2.run(order_datetime=date(order_datetime)) | /把日期時間轉為日期類型 |
4 | =A2.group@qs(order_datetime;customer_id) | /數據已按order_datetime有序,對數據按order_datetime,customer_id排序 |
5 | =file("orders_2017_date_customer.btx").export@b(A4) | |
6 | =interval@s(A1,now()) |
經測試耗時為38秒(A6格的值),而如果把A4格表達式替換為下面代碼則耗時95秒。
A | B | |
4 | =A2.sortx(order_datetime,customer_id;2000000) | /數據已按order_datetime有序,對數據按order_datetime,customer_id排序 |
SPL的組表提供了為某些列創建索引的功能,一些常用的列也可以存到索引里,這樣如果訪問的列都在索引里就不需要再訪問整個源文件,從而節省大量IO操作。而如果排序字段是索引字段,并且需要訪問的字段也都在索引里,則可以利用索引的有序性,使用T.icursor@s()返回有序游標。
創建索引的代碼如下:
A | B | |
1 | 2018-01-01 00:00:00 | =file("orders.ctx") |
2 | =to(1000*1000) | 0 |
3 | =B1.create@y(#order_id,#order_datetime,customer_id,employee_id,product_id,quantity, price) | |
4 | /以下循環為創建組表數據程序 | |
5 | for 20 | =A2.new(B2+~:order_id, elapse@s(A1,order_id):order_datetime, rand(1000000) + 1:customer_id, rand(1000) + 1:employee_id, rand(10000) + 1:product_id, rand(1000) + 1:quantity, rand(100000000)/100:price) |
6 | =A3.append(B5.cursor()) | |
7 | >B2=B2+A2.len() | |
8 | =A3.index(PriceIndex;price;product_id,order_datetime) | /按金額字段建索引,同時保持產品,日期兩個字段的值 |
利用索引產生有序游標代碼如下:
A | B | |
1 | =now() | =file("orders.ctx").create() |
2 | =B1.icursor@s(order_datetime,product_id,price;true,PriceIndex) | /利用索引返回有序游標 |
3 | for A2,10000 | /遍歷游標數據 |
4 | =now() | =interval@ms(A1,A4) |
5 | =B1.cursor(order_datetime,product_id,price) | |
6 | =A5.sortx(price) | /外存排序 |
7 | for A6,10000 | /遍歷游標數據 |
8 | =now() | =interval@ms(A4,A8) |
經測試有序游標耗時為4秒(B4格的值),而外存排序遍歷耗時為54秒(B8格的值)。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。