2010-01-31 32 views
14

Xem xét một tệp rất lớn (có thể nhiều hơn 4GB) trên đĩa, tôi muốn quét qua tệp này và tính thời gian của một mẫu nhị phân cụ thể xảy ra.Làm thế nào để quét qua các tập tin thực sự lớn trên đĩa?

suy nghĩ của tôi là:

  1. Sử dụng bộ nhớ ánh xạ tập tin (CreateFileMap hoặc thúc đẩy mapped_file) để tải file vào bộ nhớ ảo.

  2. Đối với mỗi bộ nhớ được ánh xạ 100MB, hãy tạo một chuỗi để quét và tính kết quả.

Điều này có khả thi không? Có phương pháp nào tốt hơn để làm như vậy không?

Cập nhật:
tập tin bộ nhớ ánh xạ sẽ là một lựa chọn tốt, cho scaning thông qua một file 1.6GB có thể được xử lý trong vòng 11s.

cảm ơn.

+4

(2) Mẫu có thể mở rộng ranh giới 100MB không? Nếu bạn phải tự viết thuật toán tìm kiếm và chuỗi tìm kiếm dài hợp lý (dài hơn thì tốt hơn!), Hãy xem xét thuật toán Boyer-Moore http://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm – Kristen

+0

@ Kristen: Mô hình sẽ không mở rộng biên giới 100MB, bởi vì mẫu có chút '1'. – Jichao

+0

Mô hình là gì, nó thực sự là một bộ đơn? – GalacticJello

Trả lời

4

Đa luồng sẽ chỉ làm cho việc này diễn ra chậm hơn trừ khi bạn muốn quét nhiều tệp với mỗi tệp trên một ổ cứng khác. Nếu không, bạn sẽ chỉ tìm kiếm.

Tôi đã viết một chức năng thử nghiệm đơn giản bằng cách sử dụng các tệp ánh xạ bộ nhớ, với một chuỗi duy nhất một tệp 1,4 Gb mất khoảng 20 giây để quét. Với hai chủ đề, mỗi chủ đề lấy một nửa tập tin (thậm chí 1MB khối thành một chủ đề, lẻ với nhau), phải mất hơn 80 giây.

  • 1 Chủ đề: 20.015 mili giây
  • 2 chủ đề: 83.985 mili giây

Đúng vậy, 2 chủ đề là chậm hơn so với 1 chủ đề Bốn lần!

Đây là mã tôi đã sử dụng, đây là phiên bản luồng đơn, tôi đã sử dụng mẫu quét 1 byte, vì vậy mã để xác định vị trí phù hợp với ranh giới bản đồ không được kiểm tra.

HRESULT ScanForPattern(LPCTSTR pszFilename, LPBYTE pbPattern, UINT cbPattern, LONGLONG * pcFound) 
{ 
    HRESULT hr = S_OK; 

    *pcFound = 0; 
    if (! pbPattern || ! cbPattern) 
     return E_INVALIDARG; 

    // Open the file 
    // 
    HANDLE hf = CreateFile(pszFilename, 
          GENERIC_READ, 
          FILE_SHARE_READ, NULL, 
          OPEN_EXISTING, 
          FILE_FLAG_SEQUENTIAL_SCAN, 
          NULL); 

    if (INVALID_HANDLE_VALUE == hf) 
     { 
     hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); 
     // catch an open file that exists but is in use 
     if (ERROR_SHARING_VIOLATION == GetLastError()) 
     hr = HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION); 
     return hr; 
     } 

    // get the file length 
    // 
    ULARGE_INTEGER uli; 
    uli.LowPart = GetFileSize(hf, &uli.HighPart); 
    LONGLONG cbFileSize = uli.QuadPart; 
    if (0 == cbFileSize) 
     { 
     CloseHandle (hf); 
     return S_OK; 
     } 

    const LONGLONG cbStride = 1 * 1024 * 1024; // 1 MB stride. 
    LONGLONG cFound = 0; 
    LPBYTE pbGap = (LPBYTE) malloc(cbPattern * 2); 

    // Create a mapping of the file. 
    // 
    HANDLE hmap = CreateFileMapping(hf, NULL, PAGE_READONLY, 0, 0, NULL); 
    if (NULL != hmap) 
     { 
     for (LONGLONG ix = 0; ix < cbFileSize; ix += cbStride) 
     { 
     uli.QuadPart = ix; 
     UINT cbMap = (UINT) min(cbFileSize - ix, cbStride); 
     LPCBYTE pb = (LPCBYTE) MapViewOfFile(hmap, FILE_MAP_READ, uli.HighPart, uli.LowPart, cbMap); 
     if (! pb) 
      { 
      hr = HRESULT_FROM_WIN32(GetLastError()); 
      break; 
      } 
     // handle pattern scanning over the gap. 
     if (cbPattern > 1 && ix > 0) 
      { 
      CopyMemory(pbGap + cbPattern - 1, &pb[0], cbPattern - 1); 
      for (UINT ii = 1; ii < cbPattern; ++ii) 
       { 
       if (pb[ii] == pbPattern[0] && 0 == memcmp(&pb[ii], pbPattern, cbPattern)) 
        { 
        ++cFound; 
        // advance by cbPattern-1 to avoid detecting overlapping patterns 
        } 
       } 
      } 

     for (UINT ii = 0; ii < cbMap - cbPattern + 1; ++ii) 
      { 
      if (pb[ii] == pbPattern[0] && 
       ((cbPattern == 1) || 0 == memcmp(&pb[ii], pbPattern, cbPattern))) 
       { 
       ++cFound; 
       // advance by cbPattern-1 to avoid detecting overlapping patterns 
       } 
      } 
     if (cbPattern > 1 && cbMap >= cbPattern) 
      { 
      // save end of the view in our gap buffer so we can detect map-straddling patterns 
      CopyMemory(pbGap, &pb[cbMap - cbPattern + 1], cbPattern - 1); 
      } 
     UnmapViewOfFile(pb); 
     } 

     CloseHandle (hmap); 
     } 
    CloseHandle (hf); 

    *pcFound = cFound; 
    return hr; 
} 
+0

Câu hỏi: Tại sao bạn sử dụng "if (pb [ii] == pbPattern [0] && 0 == memcmp (& pb [ii], pbPattern, cbPattern)) "? Sẽ không memcmp (& pb [ii], pbPattern, cbPattern) trả về false ngay sau khi so sánh các byte đầu tiên nếu chúng không bằng nhau? –

5

Mặc dù bạn có thể sử dụng ánh xạ bộ nhớ, bạn không phải làm như vậy. Nếu bạn đọc tệp theo tuần tự trong các đoạn nhỏ, hãy nói 1 MB mỗi tệp, tệp sẽ không bao giờ xuất hiện trong bộ nhớ cùng một lúc.

Nếu mã tìm kiếm của bạn thực sự chậm hơn đĩa cứng của bạn, bạn vẫn có thể chuyển khối thành chuỗi công việc nếu muốn.

+0

Tôi dám chắc rằng cách duy nhất để tìm kiếm có thể chậm hơn so với đọc từ đĩa sẽ là trường hợp bệnh lý thực sự (ví dụ: tìm kiếm 999,999 ký tự 'A' theo sau là' B' trong tệp chỉ chứa 1.000.000 'A' ký tự) khi sử dụng một phương thức tìm kiếm ngây thơ (như thường được thực hiện cho 'strstr()'). Đối với bất kỳ tìm kiếm chuỗi thời gian tuyến tính (như Knuth-Morris-Pratt) thì I/O đĩa sẽ chậm hơn ít nhất 100x. –

+2

Vâng, đó là lý do tại sao tôi đã viết "nếu" và "thực sự" trong "Nếu mã tìm kiếm của bạn thực sự chậm hơn ..." :) – Thomas

10

Tạo 20 chủ đề, mỗi chuỗi giả định xử lý khoảng 100 MB tệp có khả năng chỉ làm giảm hiệu suất kể từ khi HD sẽ phải đọc từ một số địa điểm không liên quan cùng một lúc.

Hiệu suất HD ở mức cao nhất khi đọc dữ liệu tuần tự. Vì vậy, giả sử tập tin khổng lồ của bạn không bị phân mảnh, điều tốt nhất để làm có lẽ sẽ chỉ sử dụng một luồng và đọc từ đầu đến cuối trong một số ít (nói 4) MB.

Nhưng tôi biết điều gì. Hệ thống tệp và bộ nhớ cache phức tạp. Làm một số thử nghiệm và xem những gì hoạt động tốt nhất.

0

Sử dụng tệp ánh xạ bộ nhớ có lợi ích bổ sung khi tránh bản sao từ bộ nhớ cache của hệ thống tệp sang bộ nhớ ứng dụng (được quản lý) nếu bạn sử dụng chế độ xem chỉ đọc (mặc dù bạn phải sử dụng con trỏ byte * để truy cập ký ức). Và thay vì tạo ra nhiều luồng chỉ sử dụng một luồng để quét tuần tự qua tệp bằng cách sử dụng các khung nhìn được ánh xạ bộ nhớ 100MB vào tệp (không ánh xạ toàn bộ tệp vào không gian địa chỉ quy trình cùng một lúc).

0

Tôi sẽ làm điều đó với các lần đọc không đồng bộ vào bộ đệm kép. Vì vậy, khi một bộ đệm đã được đọc từ tập tin, bắt đầu đọc bộ đệm tiếp theo trong khi quét bộ đệm đầu tiên. Điều này có nghĩa là bạn làm CPU và IO song song. Một ưu điểm nữa là bạn sẽ luôn có dữ liệu xung quanh ranh giới dữ liệu. Tuy nhiên, tôi không biết liệu bộ đệm đôi có thể thực hiện được với các tệp ánh xạ bộ nhớ hay không.

Hy vọng điều này sẽ hữu ích.

Kính trọng,

Sebastiaan

1

Tôi muốn đi với chỉ một thread quá, không chỉ đối với vấn đề hiệu suất HD, nhưng vì bạn có thể gặp khó khăn khi quản lý các tác dụng phụ khi chia tệp của bạn: những gì nếu có một sự xuất hiện của mẫu của bạn ngay khi bạn chia nhỏ tệp của mình?

2

Tôi sẽ có một luồng đọc tệp (có thể dưới dạng luồng) thành một mảng và có một luồng khác xử lý nó. Tôi sẽ không bản đồ một số tại một thời gian vì đĩa tìm kiếm. Tôi có lẽ sẽ có một ManualResetEvent để nói cho chủ đề của tôi khi tiếp theo? byte đã sẵn sàng để được xử lý. Giả sử mã quá trình của bạn nhanh hơn sau đó hdd tôi sẽ có 2 bộ đệm, một để lấp đầy và khác để xử lý và chỉ cần chuyển đổi giữa chúng mỗi lần.

0

Tim Bray (và độc giả của anh) đã khám phá điều này theo chiều sâu trong số Wide Finder ProjectWide Finder 2 của mình. Benchmark results cho thấy rằng việc triển khai đa luồng có thể hoạt động tốt hơn giải pháp đơn luồng trên máy chủ đa lõi mặt trời lớn. Trên phần cứng máy tính thông thường, đa luồng sẽ không giúp bạn nhiều, nếu có.

Các vấn đề liên quan