2013-08-16 29 views
6

Từ thông tin về đảm bảo dữ liệu nằm trên đĩa (http://winntfs.com/2012/11/29/windows-write-caching-part-2-an-overview-for-application-developers/), ngay cả trong trường hợp ví dụ: cúp điện, có vẻ như trên nền tảng Windows, bạn cần phải dựa vào phiên bản "fsync" FlushFileBuffers để đảm bảo tốt nhất các bộ đệm thực sự bị xóa khỏi bộ nhớ thiết bị đĩa trên chính phương tiện lưu trữ. Sự kết hợp của FILE_FLAG_NO_BUFFERING với FILE_FLAG_WRITE_THROUGH không đảm bảo xóa bộ nhớ cache của thiết bị, nhưng chỉ có tác dụng trên bộ nhớ cache của hệ thống tệp, nếu thông tin này là chính xác.Hiệu suất Windows fsync (FlushFileBuffers) với tệp lớn

Với thực tế là tôi sẽ làm việc với các tệp khá lớn, cần được cập nhật "giao dịch", điều này có nghĩa là thực hiện "fsync" ở cuối giao dịch cam kết. Vì vậy, tôi đã tạo một ứng dụng nhỏ để kiểm tra hiệu suất khi làm như vậy. Về cơ bản nó thực hiện việc ghi tuần tự của một loạt 8 byte bộ nhớ có kích thước ngẫu nhiên bằng cách sử dụng 8 lần viết và sau đó xóa đi. Lô được lặp đi lặp lại trong một vòng lặp, và sau mỗi rất nhiều trang văn bản ghi lại hiệu suất. Ngoài ra nó có hai tùy chọn cấu hình: để fsync trên một tuôn ra và cho dù để viết một byte đến vị trí cuối cùng của tập tin, trước khi bắt đầu viết trang.

// Code updated to reflect new results as discussed in answer below. 
// 26/Aug/2013: Code updated again to reflect results as discussed in follow up question. 
// 28/Aug/2012: Increased file stream buffer to ensure 8 page flushes. 
class Program 
{ 
    static void Main(string[] args) 
    { 
     BenchSequentialWrites(reuseExistingFile:false); 
    } 
    public static void BenchSequentialWrites(bool reuseExistingFile = false) 
    { 
     Tuple<string, bool, bool, bool, bool>[] scenarios = new Tuple<string, bool, bool, bool, bool>[] 
     { // output csv, fsync?, fill end?, write through?, mem map? 
      Tuple.Create("timing FS-E-B-F.csv", true, false, false, false), 
      Tuple.Create("timing NS-E-B-F.csv", false, false, false, false), 
      Tuple.Create("timing FS-LB-B-F.csv", true, true, false, false), 
      Tuple.Create("timing NS-LB-B-F.csv", false, true, false, false), 
      Tuple.Create("timing FS-E-WT-F.csv", true, false, true, false), 
      Tuple.Create("timing NS-E-WT-F.csv", false, false, true, false), 
      Tuple.Create("timing FS-LB-WT-F.csv", true, true, true, false), 
      Tuple.Create("timing NS-LB-WT-F.csv", false, true, true, false), 
      Tuple.Create("timing FS-E-B-MM.csv", true, false, false, true), 
      Tuple.Create("timing NS-E-B-MM.csv", false, false, false, true), 
      Tuple.Create("timing FS-LB-B-MM.csv", true, true, false, true), 
      Tuple.Create("timing NS-LB-B-MM.csv", false, true, false, true), 
      Tuple.Create("timing FS-E-WT-MM.csv", true, false, true, true), 
      Tuple.Create("timing NS-E-WT-MM.csv", false, false, true, true), 
      Tuple.Create("timing FS-LB-WT-MM.csv", true, true, true, true), 
      Tuple.Create("timing NS-LB-WT-MM.csv", false, true, true, true), 
     }; 
     foreach (var scenario in scenarios) 
     { 
      Console.WriteLine("{0,-12} {1,-16} {2,-16} {3,-16} {4:F2}", "Total pages", "Interval pages", "Total time", "Interval time", "MB/s"); 
      CollectGarbage(); 
      var timingResults = SequentialWriteTest("test.data", !reuseExistingFile, fillEnd: scenario.Item3, nPages: 200 * 1000, fSync: scenario.Item2, writeThrough: scenario.Item4, writeToMemMap: scenario.Item5); 
      using (var report = File.CreateText(scenario.Item1)) 
      { 
       report.WriteLine("Total pages,Interval pages,Total bytes,Interval bytes,Total time,Interval time,MB/s"); 
       foreach (var entry in timingResults) 
       { 
        Console.WriteLine("{0,-12} {1,-16} {2,-16} {3,-16} {4:F2}", entry.Item1, entry.Item2, entry.Item5, entry.Item6, entry.Item7); 
        report.WriteLine("{0},{1},{2},{3},{4},{5},{6}", entry.Item1, entry.Item2, entry.Item3, entry.Item4, entry.Item5.TotalSeconds, entry.Item6.TotalSeconds, entry.Item7); 
       } 
      } 
     } 
    } 

    public unsafe static IEnumerable<Tuple<long, long, long, long, TimeSpan, TimeSpan, double>> SequentialWriteTest(
     string fileName, 
     bool createNewFile, 
     bool fillEnd, 
     long nPages, 
     bool fSync = true, 
     bool writeThrough = false, 
     bool writeToMemMap = false, 
     long pageSize = 4096) 
    { 
     // create or open file and if requested fill in its last byte. 
     var fileMode = createNewFile ? FileMode.Create : FileMode.OpenOrCreate; 
     using (var tmpFile = new FileStream(fileName, fileMode, FileAccess.ReadWrite, FileShare.ReadWrite, (int)pageSize)) 
     { 
      Console.WriteLine("Opening temp file with mode {0}{1}", fileMode, fillEnd ? " and writing last byte." : "."); 
      tmpFile.SetLength(nPages * pageSize); 
      if (fillEnd) 
      { 
       tmpFile.Position = tmpFile.Length - 1; 
       tmpFile.WriteByte(1); 
       tmpFile.Position = 0; 
       tmpFile.Flush(true); 
      } 
     } 
     // Make sure any flushing/activity has completed 
     System.Threading.Thread.Sleep(TimeSpan.FromMinutes(1)); 
     System.Threading.Thread.SpinWait(50); // warm up. 

     var buf = new byte[pageSize]; 
     new Random().NextBytes(buf); 
     var ms = new System.IO.MemoryStream(buf); 

     var stopwatch = new System.Diagnostics.Stopwatch(); 
     var timings = new List<Tuple<long, long, long, long, TimeSpan, TimeSpan, double>>(); 
     var pageTimingInterval = 8 * 2000; 
     var prevPages = 0L; 
     var prevElapsed = TimeSpan.FromMilliseconds(0); 

     // Open file 
     const FileOptions NoBuffering = ((FileOptions)0x20000000); 
     var options = writeThrough ? (FileOptions.WriteThrough | NoBuffering) : FileOptions.None; 
     using (var file = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, (int)(16 *pageSize), options)) 
     { 
      stopwatch.Start(); 
      if (writeToMemMap) 
      { 
       // write pages through memory map. 
       using (var mmf = MemoryMappedFile.CreateFromFile(file, Guid.NewGuid().ToString(), file.Length, MemoryMappedFileAccess.ReadWrite, null, HandleInheritability.None, true)) 
       using (var accessor = mmf.CreateViewAccessor(0, file.Length, MemoryMappedFileAccess.ReadWrite)) 
       { 
        byte* base_ptr = null; 
        accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref base_ptr); 
        var offset = 0L; 
        for (long i = 0; i < nPages/8; i++) 
        { 
         using (var memStream = new UnmanagedMemoryStream(base_ptr + offset, 8 * pageSize, 8 * pageSize, FileAccess.ReadWrite)) 
         { 
          for (int j = 0; j < 8; j++) 
          { 
           ms.CopyTo(memStream); 
           ms.Position = 0; 
          } 
         } 
         FlushViewOfFile((IntPtr)(base_ptr + offset), (int)(8 * pageSize)); 
         offset += 8 * pageSize; 
         if (fSync) 
          FlushFileBuffers(file.SafeFileHandle); 

         if (((i + 1) * 8) % pageTimingInterval == 0) 
          timings.Add(Report(stopwatch.Elapsed, ref prevElapsed, (i + 1) * 8, ref prevPages, pageSize)); 
        } 
        accessor.SafeMemoryMappedViewHandle.ReleasePointer(); 
       } 
      } 
      else 
      { 
       for (long i = 0; i < nPages/8; i++) 
       { 
        for (int j = 0; j < 8; j++) 
        { 
         ms.CopyTo(file); 
         ms.Position = 0; 
        } 
        file.Flush(fSync); 
        if (((i + 1) * 8) % pageTimingInterval == 0) 
         timings.Add(Report(stopwatch.Elapsed, ref prevElapsed, (i + 1) * 8, ref prevPages, pageSize)); 
       } 
      } 
     } 
     timings.Add(Report(stopwatch.Elapsed, ref prevElapsed, nPages, ref prevPages, pageSize)); 
     return timings; 
    } 

    private static Tuple<long, long, long, long, TimeSpan, TimeSpan, double> Report(TimeSpan elapsed, ref TimeSpan prevElapsed, long curPages, ref long prevPages, long pageSize) 
    { 
     var intervalPages = curPages - prevPages; 
     var intervalElapsed = elapsed - prevElapsed; 
     var intervalPageSize = intervalPages * pageSize; 
     var mbps = (intervalPageSize/(1024.0 * 1024.0))/intervalElapsed.TotalSeconds; 
     prevElapsed = elapsed; 
     prevPages = curPages; 
     return Tuple.Create(curPages, intervalPages, curPages * pageSize, intervalPageSize, elapsed, intervalElapsed, mbps); 
    } 

    private static void CollectGarbage() 
    { 
     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 
     System.Threading.Thread.Sleep(200); 
     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 
     System.Threading.Thread.SpinWait(10); 
    } 

    [DllImport("kernel32.dll", SetLastError = true)] 
    static extern bool FlushViewOfFile(
     IntPtr lpBaseAddress, int dwNumBytesToFlush); 

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] 
    static extern bool FlushFileBuffers(SafeFileHandle hFile); 
} 

Kết quả hiệu suất tôi nhận được (64 bit Win 7, đĩa trục chậm) không đáng khích lệ. Có vẻ như hiệu suất "fsync" phụ thuộc rất nhiều vào kích thước của tệp đang được xóa, sao cho điều này chiếm ưu thế thời gian và không phải số lượng dữ liệu "bẩn" được xóa. Biểu đồ bên dưới hiển thị kết quả cho 4 tùy chọn cài đặt khác nhau của ứng dụng chuẩn nhỏ.

Benchmark timing for 4 the scenarios

Như bạn thấy, hiệu suất của "fsync" theo cấp số nhân giảm khi các tập tin phát triển (cho đến khi tại một vài GB nó thực sự kim để ngăn chặn). Ngoài ra, bản thân đĩa dường như không hoạt động nhiều (nghĩa là màn hình tài nguyên hiển thị thời gian hoạt động của nó chỉ khoảng vài phần trăm và hàng đợi đĩa của nó hầu như trống trong hầu hết thời gian). Tôi đã rõ ràng dự kiến ​​hiệu suất "fsync" sẽ khá tồi tệ hơn một chút so với việc thực hiện các bước đệm bình thường, nhưng tôi đã mong đợi nó ít nhiều liên tục và độc lập với kích thước tệp. Như thế này, nó có vẻ gợi ý rằng nó không thể sử dụng kết hợp với một tập tin lớn duy nhất.

Có ai có lời giải thích, trải nghiệm khác hoặc giải pháp khác cho phép đảm bảo dữ liệu trên đĩa và có hiệu suất dự đoán nhiều hoặc ít liên tục không?

CẬP NHẬT Xem thông tin mới trong câu trả lời bên dưới.

Trả lời

1

Tôi đã thử nghiệm và thực hiện một số chi tiết và tìm thấy một giải pháp có thể chấp nhận được với tôi (mặc dù hiện tại tôi chỉ được thử nghiệm tuần tự viết). Trong quá trình này, tôi phát hiện ra một số hành vi bất ngờ làm tăng thêm một số câu hỏi mới. Tôi sẽ đăng một câu hỏi SO mới (Explanation/information sought: Windows write I/O performance with "fsync" (FlushFileBuffers)) cho những câu hỏi đó.

tôi thêm hai tùy chọn bổ sung sau đây để benchmark của tôi:

  • Sử dụng unbuffered/writethrough viết (ví dụ: xác định FILE_FLAG_NO_BUFFERINGFILE_FLAG_WRITE_THROUGH cờ)
  • Viết thư cho các tập tin gián tiếp, thông qua một tập tin bộ nhớ ánh xạ.

Điều này cung cấp cho tôi một số kết quả không mong muốn, một trong số đó mang lại cho tôi giải pháp ít hoặc có thể chấp nhận được đối với vấn đề của tôi.Khi "fsyncing" kết hợp với I/O unbuffered/writethrough, tôi không quan sát sự phân rã theo hàm mũ trong tốc độ ghi. Do đó (mặc dù nó không phải là rất nhanh) điều này cung cấp cho tôi một giải pháp cho phép đảm bảo dữ liệu trên đĩa, và có một hiệu suất dự đoán liên tục không bị ảnh hưởng bởi kích thước của tệp.

Một vài kết quả bất ngờ khác là như sau:

  • Nếu một byte được ghi vào vị trí cuối cùng trong tập tin, trước khi thực hiện trang viết với "fsync" và "không có bộ đệm/writethrough" tùy chọn, thông lượng ghi gần gấp đôi.
  • Hiệu suất của không bị quấy rối/writethrough có hoặc không có fsync gần như giống hệt nhau, ngoại trừ khi một byte được ghi vào vị trí cuối cùng trong tệp. Thông lượng ghi của kịch bản "unbuffered/writethrough" không có "fsync" trên một tệp trống là khoảng 12,5 MB/s, trong khi trong cùng một kịch bản trên tệp có byte được viết ở vị trí cuối cùng trong tệp, thông lượng là ba lần cao hơn ở mức 37 MB/s.
  • Ghi vào tệp gián tiếp thông qua tệp ánh xạ bộ nhớ kết hợp với "fsync" cho thấy giảm thông lượng theo cấp số nhân như được quan sát trong phần đệm được ghi trực tiếp vào tệp, ngay cả khi "unbuffered/writethrough" được đặt trên tệp.

Tôi đã thêm mã được cập nhật mà tôi đã sử dụng cho điểm chuẩn cho câu hỏi ban đầu của mình.

Biểu đồ dưới đây cho thấy một số kết quả mới bổ sung.

Throughput for different combinations of options

0

[Sai; xem các ý kiến.]

Tôi tin rằng bài viết bạn tham chiếu không chính xác trong việc nói rằng FlushFileBuffers có bất kỳ tác dụng hữu ích nào trên I/O không bị chặn. Nó đề cập đến một bài báo của Microsoft, nhưng bài báo được đề cập không đưa ra tuyên bố như vậy.

Theo tài liệu, việc sử dụng I/O không có bộ đệm có tác dụng tương tự như, nhưng hiệu quả hơn, gọi FlushFileBuffer sau mỗi lần viết. Vì vậy, giải pháp thực tế là sử dụng I/O không bị chặn thay vì sử dụng FlushFileBuffer.

Lưu ý rằng việc sử dụng tệp ánh xạ bộ nhớ sẽ đánh bại cài đặt bộ đệm. Tôi sẽ không khuyên bạn nên sử dụng một tập tin ánh xạ bộ nhớ nếu bạn đang cố gắng đẩy dữ liệu vào đĩa càng sớm càng tốt.

+0

Từ những gì tôi hiểu bài viết là chính xác. Cũng xác nhận trong ví dụ: tại đây: http://support.microsoft.com/kb/332023 (xem phần "Thông tin thêm"). 'FlushFileBuffers' chuyển thành lệnh' SYNCHRONIZE CACHE' cho các thiết bị SCSI và lệnh 'FLUSH CACHE' cho các thiết bị IDE/ATAPI. 'Write Through' được phát hành dưới dạng' ForceUnitAccess' mà dường như thường không được các thiết bị IDE/ITAPI vinh danh. Tôi tin rằng đây cũng là những gì được đề cập trong bài viết được tham chiếu ftp://ftp.research.microsoft.com/pub/tr/TR-2008-36.pdf. – Alex

+0

@alex những bài viết này đang nói về một bộ nhớ cache bên trong đĩa/bộ điều khiển, không phải là cấp độ hệ điều hành. Khi sử dụng writethrough, nó nên đã làm cho nó thành 'trong bộ nhớ cache đĩa', nhưng nó không thực sự 'trên đĩa'. Đối với các thử nghiệm như Q của bạn, bộ nhớ cache này có thể giải thích sự suy giảm khi lượng IO của bạn tăng lên. Bộ nhớ đệm này là một vấn đề lớn đối với cơ sở dữ liệu khi các bộ nhớ cache trong ổ đĩa bị lỗi trong điều kiện powerfail trước khi được ghi vào một sector, xem http://support.microsoft.com/kb/234656 – rlb

+0

Tôi không thể tìm thấy bất kỳ thứ gì trong đó giấy nói bất cứ điều gì về API Win32. Tuy nhiên, bài viết KB chắc chắn không! –

4

Thử nghiệm của bạn cho thấy tốc độ giảm theo cấp số nhân trên đồng bộ hóa do bạn đang tạo lại tệp mỗi lần. Trong trường hợp này, nó không còn là một ghi hoàn toàn tuần tự - mỗi lần viết cũng phát triển tệp, yêu cầu nhiều tìm kiếm để cập nhật siêu dữ liệu tệp trong hệ thống tệp. Nếu bạn chạy tất cả các công việc này bằng cách sử dụng tệp được phân bổ đầy đủ từ trước, bạn sẽ thấy kết quả nhanh hơn nhiều vì không có bản cập nhật siêu dữ liệu nào có thể can thiệp.

Tôi đã chạy thử nghiệm tương tự trên hộp Linux của mình. Các kết quả trong khi tái tạo lại tập tin mỗi lần:

mmap direct last sync time 
0 0 0 0 0.882293s 
0 0 0 1 27.050636s 
0 0 1 0 0.832495s 
0 0 1 1 26.966625s 
0 1 0 0 5.775266s 
0 1 0 1 22.063392s 
0 1 1 0 5.265739s 
0 1 1 1 24.203251s 
1 0 0 0 1.031684s 
1 0 0 1 28.244678s 
1 0 1 0 1.031888s 
1 0 1 1 29.540660s 
1 1 0 0 1.032883s 
1 1 0 1 29.408005s 
1 1 1 0 1.035110s 
1 1 1 1 28.948555s 

kết quả bằng cách sử dụng tập tin tồn tại trước đó (rõ ràng trường hợp last_byte là không thích hợp ở đây Ngoài ra, kết quả đầu tiên cũng đã phải tạo ra các tập tin.):

mmap direct last sync time 
0 0 0 0 1.199310s 
0 0 0 1 7.858803s 
0 0 1 0 0.184925s 
0 0 1 1 8.320572s 
0 1 0 0 4.047780s 
0 1 0 1 4.066993s 
0 1 1 0 4.042564s 
0 1 1 1 4.307159s 
1 0 0 0 3.596712s 
1 0 0 1 8.284428s 
1 0 1 0 0.242584s 
1 0 1 1 8.070947s 
1 1 0 0 0.240500s 
1 1 0 1 8.213450s 
1 1 1 0 0.240922s 
1 1 1 1 8.265024s 

(Lưu ý rằng tôi chỉ sử dụng 10.000 khối không phải 25.000 khối, vì vậy đây chỉ là viết 320MB, sử dụng hệ thống tệp ext2. Tôi không có tiện ích ext2fs lớn hơn, fs lớn hơn của tôi là XFS và từ chối cho phép mmap + I/O trực tiếp.)

Dưới đây là đoạn code, nếu bạn quan tâm:

#define _GNU_SOURCE 1 

#include <malloc.h> 
#include <string.h> 
#include <stdlib.h> 
#include <errno.h> 
#include <sys/types.h> 
#include <sys/time.h> 
#include <sys/mman.h> 
#include <sys/stat.h> 
#include <fcntl.h> 

#define USE_MMAP 8 
#define USE_DIRECT 4 
#define USE_LAST 2 
#define USE_SYNC 1 

#define PAGE 4096 
#define CHUNK (8*PAGE) 
#define NCHUNKS 10000 
#define STATI 1000 

#define FSIZE (NCHUNKS*CHUNK) 

main() 
{ 
    int i, j, fd, rc, stc; 
    char *data = valloc(CHUNK); 
    char *map, *dst; 
    char sfname[8]; 
    struct timeval start, end, stats[NCHUNKS/STATI+1]; 
    FILE *sfile; 

    printf("mmap\tdirect\tlast\tsync\ttime\n"); 
    for (i=0; i<16; i++) { 
     int oflag = O_CREAT|O_RDWR|O_TRUNC; 

     if (i & USE_DIRECT) 
      oflag |= O_DIRECT; 
     fd = open("dummy", oflag, 0666); 
     ftruncate(fd, FSIZE); 
     if (i & USE_LAST) { 
      lseek(fd, 0, SEEK_END); 
      write(fd, data, 1); 
      lseek(fd, 0, SEEK_SET); 
     } 
     if (i & USE_MMAP) { 
      map = mmap(NULL, FSIZE, PROT_WRITE, MAP_SHARED, fd, 0); 
      if (map == (char *)-1L) { 
       perror("mmap"); 
       exit(1); 
      } 
      dst = map; 
     } 
     sprintf(sfname, "%x.csv", i); 
     sfile = fopen(sfname, "w"); 
     stc = 1; 
     printf("%d\t%d\t%d\t%d\t", 
      (i&USE_MMAP)!=0, (i&USE_DIRECT)!=0, (i&USE_LAST)!=0, i&USE_SYNC); 
     fflush(stdout); 
     gettimeofday(&start, NULL); 
     stats[0] = start; 
     for (j = 1; j<=NCHUNKS; j++) { 
      if (i & USE_MMAP) { 
       memcpy(dst, data, CHUNK); 
       if (i & USE_SYNC) 
        msync(dst, CHUNK, MS_SYNC); 
       dst += CHUNK; 
      } else { 
       write(fd, data, CHUNK); 
       if (i & USE_SYNC) 
        fdatasync(fd); 
      } 
      if (!(j % STATI)) { 
       gettimeofday(&end, NULL); 
       stats[stc++] = end; 
      } 
     } 
     end.tv_usec -= start.tv_usec; 
     if (end.tv_usec < 0) { 
      end.tv_sec--; 
      end.tv_usec += 1000000; 
     } 
     end.tv_sec -= start.tv_sec; 
     printf(" %d.%06ds\n", (int)end.tv_sec, (int)end.tv_usec); 
     if (i & USE_MMAP) 
      munmap(map, FSIZE); 
     close(fd); 
     for (j=NCHUNKS/STATI; j>0; j--) { 
      stats[j].tv_usec -= stats[j-1].tv_usec; 
      if (stats[j].tv_usec < 0) { 
       stats[j].tv_sec--; 
       stats[j].tv_usec+= 1000000; 
      } 
      stats[j].tv_sec -= stats[j-1].tv_sec; 
     } 
     for (j=1; j<=NCHUNKS/STATI; j++) 
      fprintf(sfile, "%d\t%d.%06d\n", j*STATI*CHUNK, 
       (int)stats[j].tv_sec, (int)stats[j].tv_usec); 
     fclose(sfile); 
    } 
} 
+0

Cảm ơn bạn đã xem Howard này. Tôi sẽ nghiên cứu các kết quả này và chạy lại các thử nghiệm của tôi bằng cách sử dụng kịch bản tệp đã tồn tại từ trước mà bạn đề cập đến. Kết quả trên một Linux và một hộp Windows có thể khác nhau mặc dù như tôi nghi ngờ rằng bộ đệm ẩn tập tin hệ điều hành và/hoặc lớp phân trang hạt nhân có thể đóng một vai trò quan trọng trong việc làm giảm hiệu suất. Nhưng có lẽ, như bạn đề cập, điều này có liên quan đến cập nhật siêu dữ liệu tệp. – Alex

2

Dưới đây là một phiên bản Windows của mã synctest tôi. Tôi chỉ chạy nó bên trong một VirtualBox vm, vì vậy tôi không nghĩ rằng tôi có bất kỳ số hữu ích để so sánh nhưng bạn có thể cho nó một shot để so sánh với số C# của bạn trên máy tính của bạn. Tôi đang chuyển OPEN_ALWAYS sang CreateFile, vì vậy nó sẽ sử dụng lại tệp hiện có. Thay đổi cờ đó thành CREATE_ALWAYS nếu bạn muốn kiểm tra lại bằng một tệp trống mỗi lần.

Một điều tôi nhận thấy là kết quả nhanh hơn nhiều lần đầu tiên tôi chạy chương trình này. Có lẽ NTFS không phải là rất hiệu quả trong việc ghi đè dữ liệu hiện có, và các hiệu ứng phân mảnh tập tin xuất hiện trên các lần chạy tiếp theo.

#include <windows.h> 
#include <stdio.h> 

#define USE_MMAP 8 
#define USE_DIRECT 4 
#define USE_LAST 2 
#define USE_SYNC 1 

#define PAGE 4096 
#define CHUNK (8*PAGE) 
#define NCHUNKS 10000 
#define STATI 1000 

#define FSIZE (NCHUNKS*CHUNK) 

static LARGE_INTEGER cFreq; 

int gettimeofday(struct timeval *tv, void *unused) 
{ 
    LARGE_INTEGER count; 
    if (!cFreq.QuadPart) { 
     QueryPerformanceFrequency(&cFreq); 
    } 
    QueryPerformanceCounter(&count); 
    tv->tv_sec = count.QuadPart/cFreq.QuadPart; 
    count.QuadPart %= cFreq.QuadPart; 
    count.QuadPart *= 1000000; 
    tv->tv_usec = count.QuadPart/cFreq.QuadPart; 
    return 0; 
} 

main() 
{ 
    int i, j, rc, stc; 
    HANDLE fd; 
    char *data = _aligned_malloc(CHUNK, PAGE); 
    char *map, *dst; 
    char sfname[8]; 
    struct timeval start, end, stats[NCHUNKS/STATI+1]; 
    FILE *sfile; 
    DWORD len; 

    printf("mmap\tdirect\tlast\tsync\ttime\n"); 
    for (i=0; i<16; i++) { 
     int oflag = FILE_ATTRIBUTE_NORMAL; 

     if (i & USE_DIRECT) 
      oflag |= FILE_FLAG_NO_BUFFERING|FILE_FLAG_WRITE_THROUGH; 
     fd = CreateFile("dummy", GENERIC_READ|GENERIC_WRITE, 0, NULL, 
      OPEN_ALWAYS, oflag, NULL); 
     SetFilePointer(fd, FSIZE, NULL, FILE_BEGIN); 
     SetEndOfFile(fd); 
     if (i & USE_LAST) 
      WriteFile(fd, data, 1, &len, NULL); 
     SetFilePointer(fd, 0, NULL, FILE_BEGIN); 
     if (i & USE_MMAP) { 
      HANDLE mh; 
      mh = CreateFileMapping(fd, NULL, PAGE_READWRITE, 
       0, FSIZE, NULL); 
      map = MapViewOfFile(mh, FILE_MAP_WRITE, 0, 0, 
       FSIZE); 
      CloseHandle(mh); 
      dst = map; 
     } 
     sprintf(sfname, "%x.csv", i); 
     sfile = fopen(sfname, "w"); 
     stc = 1; 
     printf("%d\t%d\t%d\t%d\t", 
      (i&USE_MMAP)!=0, (i&USE_DIRECT)!=0, (i&USE_LAST)!=0, i&USE_SYNC); 
     fflush(stdout); 
     gettimeofday(&start, NULL); 
     stats[0] = start; 
     for (j = 1; j<=NCHUNKS; j++) { 
      if (i & USE_MMAP) { 
       memcpy(dst, data, CHUNK); 
       FlushViewOfFile(dst, CHUNK); 
       dst += CHUNK; 
      } else { 
       WriteFile(fd, data, CHUNK, &len, NULL); 
      } 
      if (i & USE_SYNC) 
       FlushFileBuffers(fd); 
      if (!(j % STATI)) { 
       gettimeofday(&end, NULL); 
       stats[stc++] = end; 
      } 
     } 
     end.tv_usec -= start.tv_usec; 
     if (end.tv_usec < 0) { 
      end.tv_sec--; 
      end.tv_usec += 1000000; 
     } 
     end.tv_sec -= start.tv_sec; 
     printf(" %d.%06ds\n", (int)end.tv_sec, (int)end.tv_usec); 
     if (i & USE_MMAP) 
      UnmapViewOfFile(map); 
     CloseHandle(fd); 
     for (j=NCHUNKS/STATI; j>0; j--) { 
      stats[j].tv_usec -= stats[j-1].tv_usec; 
      if (stats[j].tv_usec < 0) { 
       stats[j].tv_sec--; 
       stats[j].tv_usec+= 1000000; 
      } 
      stats[j].tv_sec -= stats[j-1].tv_sec; 
     } 
     for (j=1; j<=NCHUNKS/STATI; j++) 
      fprintf(sfile, "%d\t%d.%06d\n", j*STATI*CHUNK, 
       (int)stats[j].tv_sec, (int)stats[j].tv_usec); 
     fclose(sfile); 
    } 
} 
+0

Cảm ơn Howard, tôi cũng sẽ chạy chương trình đó. Nó có thể làm sáng tỏ một số kết quả của việc chạy lại của tôi bằng cách sử dụng một tập tin hoàn toàn bằng văn bản. Tôi chỉ đang trong quá trình thêm những câu hỏi đó vào câu hỏi của tôi. Bạn sẽ thấy rằng một số hành vi không giải thích được và bất ngờ vẫn còn, trong khi lời giải thích bạn cung cấp phù hợp tốt với một số thay đổi hiệu suất được quan sát thấy trong thử nghiệm đó. – Alex