none
請問文字檔裡的資料如何分頁(搭配多執行緒) RRS feed

  • 問題

  • 請問各位大大

    現在我有一個類別如下

      public class Member
      {
                public long ID { get; set; }
                public string Name { get; set; }
            
      }

    如果要從DB撈資料到程式,然後使用多執行緒並行處理資料,我是會寫...

        private static List<Member> HandleMemberFromDB(List<Member> dbSource)
            {
    
                //回傳變數
                ConcurrentQueue<Member> result = new ConcurrentQueue<Member>();
                 
                //拆分資料
    //總筆數 int totalCount = dbSource.Count; //一頁幾筆 int pageSize = 5000; //頁數 int pageCount = totalCount / pageSize; if (totalCount % pageSize > 0)//有餘數 { pageCount++;//拆幾頁(幾條執行緒) } List<Thread> ts = new List<Thread>(); //執行多條執行緒 for (int currentPage = 1; currentPage <= pageCount; currentPage++) { //另開新的一條執行緒 Thread t = new Thread(new ParameterizedThreadStart((o) => { try { //執行內容 int currentPage2 = (int)o;//頁數 //抓小批資料,分頁 List<Member> smallSource = dbSource.Skip((currentPage2 - 1) * pageSize).Take(pageSize).ToList(); foreach (Member member in smallSource) { //處理資料.. Member m2 = new Member(); m2.ID = member.ID; m2.Name = member.Name; result.Enqueue(m2);//處理完的資料放入總集合 }//End foreach } catch (Exception ex) { //寫log } }));//end new thread ts.Add(t); //避免子執行緒執行時,currentPage已被另一執行緒的for迴圈改變,所以當參數傳入 t.Start(currentPage); }//end for //全部 執行緒都執行完畢 foreach (Thread t in ts) { t.Join(); } return result.ToList(); }

    現在資料來源變成文字檔,如下(1開頭 表行頭,3表結尾,2表資料)

    由於用戶資料有6百多萬筆,我目前以下的寫法從頭讀到尾太耗時間(要花45分鐘)

       private static List<Member> ReadFileToMemberDatas(FileInfo file)
            {
                //回傳變數
                ConcurrentQueue<Member> result = new ConcurrentQueue<Member>();
    
                string readFilePath = file.FullName;//用戶文字檔路徑
                if (File.Exists(readFilePath))//用戶文字檔路徑有存在
                {
                    //開檔
                    using (FileStream fs = new FileStream(readFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
                    {
                        //讀檔
                        using (StreamReader sr = new StreamReader(fs, Encoding.UTF8))//準備讀取
                        {
                            string line = "";//一列字
                            while ((line = sr.ReadLine()) != null)//一列一列地讀取
                            {
                                string[] items = line.Split(new string[] { "|" }, StringSplitOptions.None);
                                if (items != null && items.Length > 0 && items[0].Trim() == "2")//資料列(6百多萬筆)
                                {
                                    long ID = Convert.ToInt64(items[1].Trim());
                                    string Name = items[2].Trim();
                                    result.Enqueue(new Member()
                                    {
                                      ID = ID,
                                      Name = Name
                                    });
                                }//end if 
                            }//end while
                        }//end using
                    }//end using
                }//end if 
    
                return result.ToList();
            }

    想請問如何改成多執行緒+分頁的寫法呢?

    謝謝。


    ====以下是簽名檔,不是針對發帖者的回覆==== <br/> <a href="http://www.dotblogs.com.tw/shadow">My Blog 高級打字員的技術雲</a> <br/> /*提醒網友們理性和平討論,酸言酸語不只曝露你是憤青的直男癌,更是一種網路霸凌*/ <br/> /*If my concept is wrong ,please correct me.Thanks.*/


    2017年4月10日 上午 03:39

解答

  • 要加快讀取檔案的速度可以考慮使用能夠結合緩衝區的技術, 請參考:

    C# .Net: Fastest Way to Read Text Files

    • 已標示為解答 Shadow .Net 2017年4月11日 上午 12:55
    2017年4月10日 上午 09:10
  • 1. 在同一台電腦上,有 IO 限制下,多緒並不會變得比較快。

    2. 檔案加速讀取方式是先將整個檔案大範圍讀到記憶體,再由記憶體拆解,則可利用到最大匯流排頻寬。早期記憶體有限的情況下,預設是 64kb ,在目前記憶體可隨便揮霍下,你可以考慮 16 MB 為單位,128 MB 以下你可以考慮一次載入,2xx MB 屬於有點尷尬的大小。

    3. 多緒可針對記憶體存取處理,較不受限制。


    不精確的問法,就會得到隨便猜的答案;自己都不肯花時間好好描述問題,又何必期望網友會認真回答?

    • 已標示為解答 Shadow .Net 2017年4月11日 上午 12:55
    2017年4月10日 下午 12:57
  • 向各位回報,昨晚放著程式跑出來的結果

    前三名讀檔最快的寫法

    ※如果在逐筆讀檔中,有存取DB的話,我想這↓應該也是最快的方式,花費31分鐘左右

               //回傳變數
                ConcurrentQueue<Member> result = new ConcurrentQueue<Member>();
    
                string readFilePath = file.FullName;//查詢檔路徑
                if (File.Exists(readFilePath))//查詢檔路徑有存在
                {
                   IEnumerable<string> lines =
                    File.ReadLines(readFilePath, Encoding.UTF8)
                        .Where(line => line.StartsWith("2"));
    
                   //拆分資料
                   //總筆數
                   int totalCount = lines.Count();
                   //一頁幾筆
                   int pageSize = 500000;
                   //頁數
                   int pageCount = totalCount / pageSize;
                   if (totalCount % pageSize > 0)//有餘數
                   {
                       pageCount++;//拆幾頁(幾條執行緒)
                   }
                   List<Thread> ts = new List<Thread>();
                   //執行多條執行緒
                   for (int currentPage = 1; currentPage <= pageCount; currentPage++)
                   {
                       //另開新的一條執行緒
                       Thread t = new Thread(new ParameterizedThreadStart((o) =>
                       {
                               int currentPage2 = (int)o;//頁數
                               //抓小批資料,分頁
                               IEnumerable<string> smallLines = lines.Skip((currentPage2 - 1) * pageSize).Take(pageSize);
                             
                               foreach (string line in smallLines)
                               {
                               
                                   //處理資料..
                                   string[] items = line.Split(new string[] { "|" }, StringSplitOptions.None);
                                    
                                       int ID = Convert.ToInt32(items[1].Trim());
                                       string Name = Convert.ToString(items[2].Trim());
                                    
                                       result.Enqueue(new Member()
                                       {
                                            ID = ID,
                                            Name = Name
                                       });
                                   
                                    
                               }//End foreach  
                          
                       }));//end new thread
                       ts.Add(t);
                       //避免子執行緒執行時,currentPage已被另一執行緒的for迴圈改變,所以當參數傳入
                       t.Start(currentPage);
                   }//end for
                   //全部 執行緒都執行完畢
                   foreach (Thread t in ts)
                   {
                       t.Join();
                   }
                   return result.ToList();
                     
                }//end if 
    
                return result.ToList();

    第二名:使用ReadLines(),而不是ReadAllLines() (.net 4之後支援),花費約31分鐘左右

    List<Member> result = new List<Member>();
    
                string readFilePath = file.FullName;//查詢檔路徑
                if (File.Exists(readFilePath))//查詢檔路徑有存在
                {
                    IEnumerable<string> lines  =
                      File.ReadLines(readFilePath,Encoding.UTF8)
                          .Where(line => line.StartsWith("2"));
                   
                        foreach (string line in lines)
                        {
                          string[] items = line.Split(new string[] { "|" }, StringSplitOptions.None);
            
                     int ID = Convert.ToInt32(items[1].Trim());
                     string Name = Convert.ToString(items[2].Trim());
    
                    result.Add(new Member()
                    {
                        ID = ID,Name = Name
                    });
                
                        }
                    }
                      
                }//end if 
    
                return result;

    第三名:把上面的File.ReadLines()改成File.ReadAllLines(),第一次跑花費31分鐘,第二次約33分鐘

    結論:情況就如同 心冷熱情熄大大 說得差不多,.net framework應該沒有提供直接分頁檔案的寫法

    所以只好把資料全部載入記憶體中後再處理。

    另外給FAE9D91C大大,我的檔案大小約200多MB,我想你的一千万条数据會那麼快,是因為你不是讀取一個大檔案進來

    而是因為你都在記憶體中處理,所以你的結果才會那麼快。



    ====以下是簽名檔,不是針對發帖者的回覆==== <br/> <a href="http://www.dotblogs.com.tw/shadow">My Blog 高級打字員的技術雲</a> <br/> /*提醒網友們理性和平討論,酸言酸語不只曝露你是憤青的直男癌,更是一種網路霸凌*/ <br/> /*If my concept is wrong ,please correct me.Thanks.*/

    • 已標示為解答 Shadow .Net 2017年4月11日 上午 12:55
    2017年4月11日 上午 12:55

所有回覆

  •   while ((line = sr.ReadLine()) != null)//一列一列地讀取

    直覺上,花這一行就很難搞多執行緒了。

    1. 檔案格式有討論的機會嗎?例如,以 JSON 格式提供。

    2. 一次讀入,用 LINQ 去轉換,會不會好一些。


    理直氣和,切記。

    個人

    2017年4月10日 上午 05:36
  • 現在我把原本的

        using (StreamReader sr = new StreamReader(fs, Encoding.UTF8))//準備讀取
     {
    
    
    }

    改成

        using (StreamReader sr = new StreamReader(fs, Encoding.UTF8,true,128))//準備讀取
                        {
    
    }

    讀文字是有快一些些。

    然後對方(提供文字檔的人),表示資料格式就是訂死的,他們也不會把文字檔分小量資料拆檔

    檔案大小約200多MB

    一次讀入,用 LINQ 去轉換,這方法也挺耗時間。

    我的目標是看能不能10分鐘內讀取完畢。


    ====以下是簽名檔,不是針對發帖者的回覆==== <br/> <a href="http://www.dotblogs.com.tw/shadow">My Blog 高級打字員的技術雲</a> <br/> /*提醒網友們理性和平討論,酸言酸語不只曝露你是憤青的直男癌,更是一種網路霸凌*/ <br/> /*If my concept is wrong ,please correct me.Thanks.*/

    2017年4月10日 上午 05:43
  • 要加快讀取檔案的速度可以考慮使用能夠結合緩衝區的技術, 請參考:

    C# .Net: Fastest Way to Read Text Files

    • 已標示為解答 Shadow .Net 2017年4月11日 上午 12:55
    2017年4月10日 上午 09:10
  • 1. 在同一台電腦上,有 IO 限制下,多緒並不會變得比較快。

    2. 檔案加速讀取方式是先將整個檔案大範圍讀到記憶體,再由記憶體拆解,則可利用到最大匯流排頻寬。早期記憶體有限的情況下,預設是 64kb ,在目前記憶體可隨便揮霍下,你可以考慮 16 MB 為單位,128 MB 以下你可以考慮一次載入,2xx MB 屬於有點尷尬的大小。

    3. 多緒可針對記憶體存取處理,較不受限制。


    不精確的問法,就會得到隨便猜的答案;自己都不肯花時間好好描述問題,又何必期望網友會認真回答?

    • 已標示為解答 Shadow .Net 2017年4月11日 上午 12:55
    2017年4月10日 下午 12:57
  • 向各位回報,昨晚放著程式跑出來的結果

    前三名讀檔最快的寫法

    ※如果在逐筆讀檔中,有存取DB的話,我想這↓應該也是最快的方式,花費31分鐘左右

               //回傳變數
                ConcurrentQueue<Member> result = new ConcurrentQueue<Member>();
    
                string readFilePath = file.FullName;//查詢檔路徑
                if (File.Exists(readFilePath))//查詢檔路徑有存在
                {
                   IEnumerable<string> lines =
                    File.ReadLines(readFilePath, Encoding.UTF8)
                        .Where(line => line.StartsWith("2"));
    
                   //拆分資料
                   //總筆數
                   int totalCount = lines.Count();
                   //一頁幾筆
                   int pageSize = 500000;
                   //頁數
                   int pageCount = totalCount / pageSize;
                   if (totalCount % pageSize > 0)//有餘數
                   {
                       pageCount++;//拆幾頁(幾條執行緒)
                   }
                   List<Thread> ts = new List<Thread>();
                   //執行多條執行緒
                   for (int currentPage = 1; currentPage <= pageCount; currentPage++)
                   {
                       //另開新的一條執行緒
                       Thread t = new Thread(new ParameterizedThreadStart((o) =>
                       {
                               int currentPage2 = (int)o;//頁數
                               //抓小批資料,分頁
                               IEnumerable<string> smallLines = lines.Skip((currentPage2 - 1) * pageSize).Take(pageSize);
                             
                               foreach (string line in smallLines)
                               {
                               
                                   //處理資料..
                                   string[] items = line.Split(new string[] { "|" }, StringSplitOptions.None);
                                    
                                       int ID = Convert.ToInt32(items[1].Trim());
                                       string Name = Convert.ToString(items[2].Trim());
                                    
                                       result.Enqueue(new Member()
                                       {
                                            ID = ID,
                                            Name = Name
                                       });
                                   
                                    
                               }//End foreach  
                          
                       }));//end new thread
                       ts.Add(t);
                       //避免子執行緒執行時,currentPage已被另一執行緒的for迴圈改變,所以當參數傳入
                       t.Start(currentPage);
                   }//end for
                   //全部 執行緒都執行完畢
                   foreach (Thread t in ts)
                   {
                       t.Join();
                   }
                   return result.ToList();
                     
                }//end if 
    
                return result.ToList();

    第二名:使用ReadLines(),而不是ReadAllLines() (.net 4之後支援),花費約31分鐘左右

    List<Member> result = new List<Member>();
    
                string readFilePath = file.FullName;//查詢檔路徑
                if (File.Exists(readFilePath))//查詢檔路徑有存在
                {
                    IEnumerable<string> lines  =
                      File.ReadLines(readFilePath,Encoding.UTF8)
                          .Where(line => line.StartsWith("2"));
                   
                        foreach (string line in lines)
                        {
                          string[] items = line.Split(new string[] { "|" }, StringSplitOptions.None);
            
                     int ID = Convert.ToInt32(items[1].Trim());
                     string Name = Convert.ToString(items[2].Trim());
    
                    result.Add(new Member()
                    {
                        ID = ID,Name = Name
                    });
                
                        }
                    }
                      
                }//end if 
    
                return result;

    第三名:把上面的File.ReadLines()改成File.ReadAllLines(),第一次跑花費31分鐘,第二次約33分鐘

    結論:情況就如同 心冷熱情熄大大 說得差不多,.net framework應該沒有提供直接分頁檔案的寫法

    所以只好把資料全部載入記憶體中後再處理。

    另外給FAE9D91C大大,我的檔案大小約200多MB,我想你的一千万条数据會那麼快,是因為你不是讀取一個大檔案進來

    而是因為你都在記憶體中處理,所以你的結果才會那麼快。



    ====以下是簽名檔,不是針對發帖者的回覆==== <br/> <a href="http://www.dotblogs.com.tw/shadow">My Blog 高級打字員的技術雲</a> <br/> /*提醒網友們理性和平討論,酸言酸語不只曝露你是憤青的直男癌,更是一種網路霸凌*/ <br/> /*If my concept is wrong ,please correct me.Thanks.*/

    • 已標示為解答 Shadow .Net 2017年4月11日 上午 12:55
    2017年4月11日 上午 12:55
  • Hi Shadow:

    我覺得你有必要把你對於效能的測試拆開來測。我刻意做了一個將近 300 MB,也是一千萬條的資料

    基本上執行過程是 (利用 Stopwatch 類別測量時間)

    (1) 使用 File.ReadAllLines

    (2) 將 (1) 的結果使用 foreach 迭代,在 foreach 區塊內使用傳統的 String.Split 方法將每一行再行分割

    測量結果

    (1) 耗時大約 5~11  秒 (跑了好幾次,結果有點差異,可能因為我在測試時有開其他東西導致干擾,但大致落在 5 秒那一邊)
    (2) 耗時大約 35~39 秒

    CPU 是 i7-2630QM,使用傳統硬碟,而且我並沒有用多執行緒。我沒有測試到的就是把 split 完的結果轉給強型別物件。

    但是你測出 30 幾分鐘的確有點奇怪,難道你每轉換一筆就 Console.WriteLine 一次嗎 ?


    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。

    2017年4月11日 上午 11:34
    版主
  • 喔,忘了說,我的 split 比較多一點, 一個 line 有六個逗號,可能因為這樣做 split 的時間比較多吧。

    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。

    2017年4月11日 下午 03:06
    版主
  • 回Bill 叔:

    是的,我每跑一次迴圈就 Console.WriteLine 一次

    昨天沒仔細看FAE9D91C大大的程式,可能因為這樣(我有使用 Console.WriteLine)才導致我們兩者速度差這麼多


    ====以下是簽名檔,不是針對發帖者的回覆==== My Blog 高級打字員的技術雲:http://www.dotblogs.com.tw/shadow /*提醒網友們理性和平討論,酸言酸語不只曝露你是憤青的直男癌,更是一種網路霸凌*/ /*If my concept is wrong ,please correct me.Thanks.*/

    2017年4月12日 上午 12:12
  • 以下是我的設備資訊


    ====以下是簽名檔,不是針對發帖者的回覆==== My Blog 高級打字員的技術雲:http://www.dotblogs.com.tw/shadow /*提醒網友們理性和平討論,酸言酸語不只曝露你是憤青的直男癌,更是一種網路霸凌*/ /*If my concept is wrong ,please correct me.Thanks.*/

    2017年4月12日 上午 01:21
  • 所以我猜測有可能是 Console.WriteLine 拖慢的


    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。

    2017年4月12日 上午 02:28
    版主
  • 寫 log 檔應該只要寫必要的東西吧...

    把所有東西都寫進去,除了 log 檔會超大,還會拖慢速度。

    Console.WriteLine 會有額外的 I/O,而不只是讀檔而己。


    強力監督SQL Injection問題!!

      • 小朱的技術隨手寫:http://www.dotblogs.com.tw/regionbbs/
      • 雲端學堂Facebook: http://www.facebook.com/studyazure

    2017年4月12日 上午 02:59
    版主