เขียนไฟล์ไบนารีใน C ++ อย่างรวดเร็ว


241

ฉันกำลังพยายามเขียนข้อมูลจำนวนมากบน SSD ของฉัน (โซลิดสเตทไดรฟ์) และโดยจำนวนมากฉันหมายถึง 80GB

ฉันเรียกค้นเว็บเพื่อหาแนวทางแก้ไข แต่สิ่งที่ดีที่สุดที่ฉันพบคือ:

#include <fstream>
const unsigned long long size = 64ULL*1024ULL*1024ULL;
unsigned long long a[size];
int main()
{
    std::fstream myfile;
    myfile = std::fstream("file.binary", std::ios::out | std::ios::binary);
    //Here would be some error handling
    for(int i = 0; i < 32; ++i){
        //Some calculations to fill a[]
        myfile.write((char*)&a,size*sizeof(unsigned long long));
    }
    myfile.close();
}

คอมไพล์ด้วย Visual Studio 2010 และการเพิ่มประสิทธิภาพเต็มรูปแบบและทำงานภายใต้ Windows7 โปรแกรมนี้มีขนาดสูงสุด 20MB / s สิ่งที่รบกวนจิตใจฉันจริงๆคือ Windows สามารถคัดลอกไฟล์จาก SSD อื่นไปยัง SSD นี้ได้ที่ระหว่าง 150MB / s และ 200MB / s ดังนั้นอย่างน้อย 7 ครั้งเร็วขึ้น นั่นเป็นเหตุผลที่ฉันคิดว่าฉันควรจะไปได้เร็วขึ้น

ความคิดใดที่ฉันสามารถเพิ่มความเร็วในการเขียนของฉันได้อย่างไร


11
ผลลัพธ์เวลาของคุณไม่รวมเวลาที่ใช้ในการคำนวณเพื่อเติม [] หรือไม่?
catchmeifyoutry

7
ฉันเคยทำงานนี้มาก่อน ใช้ง่ายfwrite()ฉันสามารถรับ 80% ของความเร็วในการเขียนสูงสุด เฉพาะกับที่FILE_FLAG_NO_BUFFERINGฉันเคยได้รับความเร็วสูงสุด
Mysticial

10
ฉันไม่แน่ใจว่ามันยุติธรรมที่จะเปรียบเทียบการเขียนไฟล์ของคุณกับการคัดลอก SSD เป็น SSD อาจเป็นเพราะ SSD-to-SSD ทำงานในระดับที่ต่ำกว่าหลีกเลี่ยงไลบรารี C ++ หรือใช้การเข้าถึงหน่วยความจำโดยตรง (DMA) การคัดลอกบางอย่างไม่เหมือนกับการเขียนค่าโดยพลการไปยังไฟล์การเข้าถึงแบบสุ่ม
Igor F.

4
@IgorF: นั่นเป็นเพียงการเก็งกำไรผิด เป็นการเปรียบเทียบที่สมบูรณ์แบบที่สุด การคัดลอกข้ามไดรฟ์ใน Windows เป็นเพียงการอ่านและเขียน ไม่มีอะไรแฟนซี / ซับซ้อน / แตกต่างกันเกิดขึ้นภายใต้
user541686

5
@ MaximYegorushkin: ลิงก์ไม่ได้เกิดขึ้น : P
user541686

คำตอบ:


233

นี่เป็นงาน (ในปี 2012):

#include <stdio.h>
const unsigned long long size = 8ULL*1024ULL*1024ULL;
unsigned long long a[size];

int main()
{
    FILE* pFile;
    pFile = fopen("file.binary", "wb");
    for (unsigned long long j = 0; j < 1024; ++j){
        //Some calculations to fill a[]
        fwrite(a, 1, size*sizeof(unsigned long long), pFile);
    }
    fclose(pFile);
    return 0;
}

ฉันเพิ่งตั้งเวลา 8GB ใน 36 วินาทีซึ่งเป็นประมาณ 220MB / s และฉันคิดว่า SSD ของฉันมีประสิทธิภาพสูงสุด นอกจากนี้ควรทราบรหัสในคำถามใช้หนึ่งคอร์ 100% ในขณะที่รหัสนี้ใช้ 2-5% เท่านั้น

ขอบคุณมากสำหรับทุกคน

อัปเดต : 5 ปีผ่านไปแล้วในปี 2017 ในขณะนี้ คอมไพเลอร์ฮาร์ดแวร์ไลบรารีและข้อกำหนดของฉันมีการเปลี่ยนแปลง นั่นเป็นสาเหตุที่ฉันทำการเปลี่ยนแปลงรหัสและทำการวัดใหม่

ก่อนรหัส:

#include <fstream>
#include <chrono>
#include <vector>
#include <cstdint>
#include <numeric>
#include <random>
#include <algorithm>
#include <iostream>
#include <cassert>

std::vector<uint64_t> GenerateData(std::size_t bytes)
{
    assert(bytes % sizeof(uint64_t) == 0);
    std::vector<uint64_t> data(bytes / sizeof(uint64_t));
    std::iota(data.begin(), data.end(), 0);
    std::shuffle(data.begin(), data.end(), std::mt19937{ std::random_device{}() });
    return data;
}

long long option_1(std::size_t bytes)
{
    std::vector<uint64_t> data = GenerateData(bytes);

    auto startTime = std::chrono::high_resolution_clock::now();
    auto myfile = std::fstream("file.binary", std::ios::out | std::ios::binary);
    myfile.write((char*)&data[0], bytes);
    myfile.close();
    auto endTime = std::chrono::high_resolution_clock::now();

    return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
}

long long option_2(std::size_t bytes)
{
    std::vector<uint64_t> data = GenerateData(bytes);

    auto startTime = std::chrono::high_resolution_clock::now();
    FILE* file = fopen("file.binary", "wb");
    fwrite(&data[0], 1, bytes, file);
    fclose(file);
    auto endTime = std::chrono::high_resolution_clock::now();

    return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
}

long long option_3(std::size_t bytes)
{
    std::vector<uint64_t> data = GenerateData(bytes);

    std::ios_base::sync_with_stdio(false);
    auto startTime = std::chrono::high_resolution_clock::now();
    auto myfile = std::fstream("file.binary", std::ios::out | std::ios::binary);
    myfile.write((char*)&data[0], bytes);
    myfile.close();
    auto endTime = std::chrono::high_resolution_clock::now();

    return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
}

int main()
{
    const std::size_t kB = 1024;
    const std::size_t MB = 1024 * kB;
    const std::size_t GB = 1024 * MB;

    for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option1, " << size / MB << "MB: " << option_1(size) << "ms" << std::endl;
    for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option2, " << size / MB << "MB: " << option_2(size) << "ms" << std::endl;
    for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option3, " << size / MB << "MB: " << option_3(size) << "ms" << std::endl;

    return 0;
}

รหัสนี้คอมไพล์ด้วย Visual Studio 2017 และ g ++ 7.2.0 (ข้อกำหนดใหม่) ฉันรันโค้ดด้วยการตั้งค่าสองแบบ:

  • แล็ปท็อป, Core i7, SSD, Ubuntu 16.04, g ++ เวอร์ชั่น 7.2.0 พร้อม -std = c ++ 11 -march = native -O3
  • เดสก์ท็อป, คอร์ i7, SSD, Windows 10, Visual Studio 2017 เวอร์ชัน 15.3.1 พร้อม / Ox / Ob2 / Oi / Ot / GT / GL / Gy

ซึ่งให้การวัดต่อไปนี้ (หลังจากทิ้งค่า 1MB เพราะมันเป็นค่าผิดปกติที่เห็นได้ชัด): ทั้งตัวเลือกที่ 1 และตัวเลือกที่ 3 สูงสุด SSD ของฉัน ฉันไม่ได้คาดหวังว่าสิ่งนี้จะเห็นเพราะตัวเลือก 2 เคยเป็นรหัสที่เร็วที่สุดในเครื่องเก่าของฉันแล้วป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่

TL; DR : การวัดของฉันบ่งชี้ในการใช้มากกว่าstd::fstreamFILE


8
+1 ใช่นี่เป็นสิ่งแรกที่ฉันพยายาม FILE*เร็วกว่าสตรีม ฉันไม่คาดหวังความแตกต่างดังกล่าวเนื่องจาก "ควร" เป็น I / O ที่ถูกผูกไว้อย่างไรก็ตาม
Mysticial

12
เราสามารถสรุปได้ว่า I / O สไตล์ C นั้นเร็วกว่าสตรีม C ++ หรือไม่?
SChepurin

21
@Schepurin: หากคุณเป็นคนที่คลั่งไคล้ หากคุณกำลังปฏิบัติอาจจะใช่ :)
user541686

10
คุณช่วยอธิบายได้ไหม (สำหรับ C ++ คนโง่อย่างฉัน) ความแตกต่างระหว่างสองแนวทางและทำไมสิ่งนี้ถึงทำงานเร็วกว่าต้นฉบับมาก?
Mike Chamberlain

11
การเติมข้อมูลios::sync_with_stdio(false);สร้างความแตกต่างให้กับรหัสด้วยกระแสหรือไม่ ฉันแค่อยากรู้ว่ามันมีความแตกต่างกันมากเพียงใดในการใช้บรรทัดนี้และไม่ใช่ แต่ฉันไม่มีดิสก์ที่เร็วพอที่จะตรวจสอบเคสมุม และถ้ามีความแตกต่างที่แท้จริง
Artur Czajka

24

ลองวิธีต่อไปนี้ตามลำดับ:

  • ขนาดบัฟเฟอร์ที่เล็กลง การเขียนครั้งละ ~ 2 MiB อาจเป็นการเริ่มต้นที่ดี ในแล็ปท็อปตัวสุดท้ายของฉัน ~ 512 KiB เป็นจุดที่น่าสนใจ แต่ฉันยังไม่ได้ทดสอบกับ SSD เลย

    หมายเหตุ:ฉันสังเกตเห็นว่าบัฟเฟอร์ที่มีขนาดใหญ่มากมักจะลดประสิทธิภาพลง ฉันสังเกตเห็นการสูญเสียความเร็วด้วยการใช้ 16-MiB บัฟเฟอร์แทน 512-KiB บัฟเฟอร์ก่อน

  • การใช้งาน_open(หรือ_topenถ้าคุณต้องการที่จะใช้ Windows ที่ถูกต้อง) _writeเพื่อเปิดไฟล์การใช้งานแล้ว นี่อาจจะหลีกเลี่ยงการบัฟเฟอร์จำนวนมาก แต่ก็ไม่แน่นอน

  • การใช้ฟังก์ชั่นที่ใช้ Windows ที่เฉพาะเจาะจงเช่นและCreateFile WriteFileที่จะหลีกเลี่ยงการบัฟเฟอร์ใด ๆ ในไลบรารีมาตรฐาน


ตรวจสอบผลการวัดประสิทธิภาพที่โพสต์ออนไลน์ คุณต้องเขียน 4kB ด้วยความลึกของคิว 32 หรือมากกว่าหรืออื่น ๆ 512K หรือสูงกว่าการเขียนเพื่อให้ได้ปริมาณงานที่เหมาะสม
Ben Voigt

@ BenVoigt: ใช่แล้วมันมีความสัมพันธ์กับฉันว่า 512 KiB เป็นจุดที่ดีสำหรับฉัน :)
user541686

ใช่. จากประสบการณ์ของฉันขนาดบัฟเฟอร์ที่เล็กกว่ามักจะเหมาะสมที่สุด ข้อยกเว้นคือเมื่อคุณใช้FILE_FLAG_NO_BUFFERING- ซึ่งบัฟเฟอร์ขนาดใหญ่มักจะดีกว่า เนื่องจากฉันคิดว่าFILE_FLAG_NO_BUFFERINGเป็น DMA สวยมาก
Mysticial

22

ฉันไม่เห็นความแตกต่างระหว่าง std :: stream / FILE / device ระหว่างการบัฟเฟอร์และการไม่บัฟเฟอร์

ทราบด้วย:

  • ไดรฟ์ SSD "มีแนวโน้ม" ที่จะชะลอตัวลง (ลดอัตราการถ่ายโอน) ในขณะที่เติม
  • ไดรฟ์ SSD "มีแนวโน้ม" ที่จะชะลอตัวลง (ลดอัตราการถ่ายโอน) เนื่องจากพวกเขามีอายุมากขึ้น (เนื่องจากบิตที่ไม่ทำงาน)

ฉันเห็นรหัสทำงานใน 63 วินาที
ดังนั้นอัตราการถ่ายโอนของ: 260M / s (SSD ของฉันดูเร็วกว่าของคุณเล็กน้อย)

64 * 1024 * 1024 * 8 /*sizeof(unsigned long long) */ * 32 /*Chunks*/

= 16G
= 16G/63 = 260M/s

ฉันไม่ได้รับอะไรเพิ่มเมื่อย้ายไปที่ FILE * จาก std :: fstream

#include <stdio.h>

using namespace std;

int main()
{
    
    FILE* stream = fopen("binary", "w");

    for(int loop=0;loop < 32;++loop)
    {
         fwrite(a, sizeof(unsigned long long), size, stream);
    }
    fclose(stream);

}

ดังนั้นสตรีม C ++ จึงทำงานได้เร็วเท่าที่ไลบรารีพื้นฐานจะอนุญาต

แต่ฉันคิดว่ามันไม่ยุติธรรมเมื่อเทียบกับระบบปฏิบัติการกับแอปพลิเคชันที่สร้างขึ้นบนระบบปฏิบัติการ แอปพลิเคชันไม่สามารถทำการสันนิษฐานได้ (ไม่ทราบว่าไดรฟ์เป็น SSD) และใช้กลไกไฟล์ของระบบปฏิบัติการสำหรับการถ่ายโอน

ในขณะที่ระบบปฏิบัติการไม่จำเป็นต้องตั้งสมมติฐานใด ๆ สามารถบอกประเภทของไดรฟ์ที่เกี่ยวข้องและใช้เทคนิคที่ดีที่สุดสำหรับการถ่ายโอนข้อมูล ในกรณีนี้หน่วยความจำโดยตรงการถ่ายโอนหน่วยความจำ ลองเขียนโปรแกรมที่คัดลอก 80G จาก 1 ตำแหน่งในหน่วยความจำไปยังที่อื่นและดูว่ารวดเร็วแค่ไหน

แก้ไข

ฉันเปลี่ยนรหัสเพื่อใช้การโทรระดับต่ำกว่า:
เช่นไม่มีการบัฟเฟอร์

#include <fcntl.h>
#include <unistd.h>


const unsigned long long size = 64ULL*1024ULL*1024ULL;
unsigned long long a[size];
int main()
{
    int data = open("test", O_WRONLY | O_CREAT, 0777);
    for(int loop = 0; loop < 32; ++loop)
    {   
        write(data, a, size * sizeof(unsigned long long));
    }   
    close(data);
}

สิ่งนี้ทำให้ไม่มีความแตกต่าง

หมายเหตุ : ไดรฟ์ของฉันเป็นไดรฟ์ SSD หากคุณมีไดรฟ์ปกติคุณอาจเห็นความแตกต่างระหว่างสองเทคนิคด้านบน แต่อย่างที่ฉันคาดไว้ไม่ใช่การบัฟเฟอร์และการบัฟเฟอร์ (เมื่อเขียนชิ้นใหญ่มากกว่าขนาดบัฟเฟอร์) ก็ไม่ได้สร้างความแตกต่าง

แก้ไข 2:

คุณได้ลองวิธีคัดลอกไฟล์ที่เร็วที่สุดใน C ++ แล้วหรือยัง

int main()
{
    std::ifstream  input("input");
    std::ofstream  output("ouptut");

    output << input.rdbuf();
}

5
ฉันไม่ได้ลงคะแนน แต่ขนาดบัฟเฟอร์ของคุณเล็กเกินไป ฉันไม่ได้แบบเดียวกับที่ 512 MB buffer OP จะใช้และฉันได้รับ 20 MB / s กับกระแสเทียบกับ 90 MB / s FILE*ด้วย
Mysticial

ด้วยวิธีของคุณด้วย fwrite (a, sizeof (long long unsigned long) ขนาดและ stream); แทน fwrite (a, 1, size * sizeof (ยาวไม่ได้ลงนาม), pFile); ให้ฉัน 220MB / s ด้วยชิ้น 64MB ต่อการเขียน
Dominic Hofer

2
@ อย่างเป็นทางการ: มันน่าประหลาดใจที่ขนาดบัฟเฟอร์ของฉันสร้างความแตกต่าง (แม้ว่าฉันเชื่อว่าคุณ) บัฟเฟอร์มีประโยชน์เมื่อคุณมีการเขียนขนาดเล็กจำนวนมากเพื่อให้อุปกรณ์ที่อยู่ภายใต้ไม่รบกวนการร้องขอจำนวนมาก แต่เมื่อคุณเขียนชิ้นขนาดใหญ่ไม่จำเป็นต้องใช้บัฟเฟอร์เมื่อเขียน / อ่าน (บนอุปกรณ์ปิดกั้น) เช่นข้อมูลควรถูกส่งโดยตรงไปยังอุปกรณ์พื้นฐาน (เช่นโดยผ่านบัฟเฟอร์) แม้ว่าคุณจะเห็นความแตกต่างสิ่งนี้จะขัดแย้งกับสิ่งนี้และทำให้ฉันสงสัยว่าทำไมการเขียนจึงใช้บัฟเฟอร์จริง
Martin York

2
ทางออกที่ดีที่สุดคือไม่เพิ่มขนาดบัฟเฟอร์ แต่เพื่อลบบัฟเฟอร์และทำให้การส่งข้อมูลโดยตรงไปยังอุปกรณ์พื้นฐาน
Martin York

1
@Mysticial: 1) ไม่มีชิ้นเล็ก ๆ => มันใหญ่พอเสมอ (ในตัวอย่างนี้) ในกรณีนี้คือ 512M 2) นี่คือไดรฟ์ SSD (ทั้งของฉันและ OP) ดังนั้นจึงไม่มีสิ่งใดที่เกี่ยวข้อง ฉันได้อัพเดตคำตอบแล้ว
Martin York

13

ทางออกที่ดีที่สุดคือการใช้การเขียนแบบ async ด้วยการบัฟเฟอร์คู่

ดูที่เส้นเวลา:

------------------------------------------------>
FF|WWWWWWWW|FF|WWWWWWWW|FF|WWWWWWWW|FF|WWWWWWWW|

'F' หมายถึงเวลาสำหรับการเติมบัฟเฟอร์และ 'W' หมายถึงเวลาสำหรับการเขียนบัฟเฟอร์ไปยังดิสก์ ดังนั้นปัญหาในการเสียเวลาระหว่างการเขียนบัฟเฟอร์ลงไฟล์ อย่างไรก็ตามโดยการเขียนบนเธรดแยกคุณสามารถเริ่มเติมบัฟเฟอร์ถัดไปได้ทันทีเช่นนี้

------------------------------------------------> (main thread, fills buffers)
FF|ff______|FF______|ff______|________|
------------------------------------------------> (writer thread)
  |WWWWWWWW|wwwwwwww|WWWWWWWW|wwwwwwww|

F - การกรอกบัฟเฟอร์ที่ 1
f - การเติมบัฟเฟอร์ที่ 2
W - การเขียนบัฟเฟอร์ที่ 1 ลงในไฟล์
W - การเขียนบัฟเฟอร์ที่สองไปยังไฟล์
_ - รอในขณะที่การดำเนินการเสร็จสมบูรณ์

วิธีนี้พร้อมการแลกเปลี่ยนบัฟเฟอร์มีประโยชน์มากเมื่อเติมบัฟเฟอร์ต้องใช้การคำนวณที่ซับซ้อนมากขึ้น (ดังนั้นเวลามากขึ้น) ฉันมักจะใช้คลาส CSequentialStreamWriter ที่ซ่อนการเขียนแบบอะซิงโครนัสภายในดังนั้นสำหรับผู้ใช้ปลายทางอินเทอร์เฟซที่มีเพียงฟังก์ชั่นการเขียน

และขนาดบัฟเฟอร์ต้องเป็นขนาดของดิสก์คลัสเตอร์หลายขนาด มิฉะนั้นคุณจะจบลงด้วยประสิทธิภาพที่ไม่ดีโดยการเขียนบัฟเฟอร์เดียวไปยังดิสก์ 2 คลัสเตอร์ที่อยู่ติดกัน

เขียนบัฟเฟอร์สุดท้าย
เมื่อคุณเรียกใช้ฟังก์ชั่นการเขียนเป็นครั้งสุดท้ายคุณต้องตรวจสอบให้แน่ใจว่าบัฟเฟอร์ปัจจุบันถูกเติมเต็มแล้วควรจะถูกเขียนไปยังดิสก์เช่นกัน ดังนั้น CSequentialStreamWriter ควรมีวิธีที่แยกกันสมมุติว่า Finalize (final buffer flush) ซึ่งควรเขียนลงดิสก์ส่วนสุดท้ายของข้อมูล

การจัดการข้อผิดพลาด
ในขณะที่โค้ดเริ่มเติมบัฟเฟอร์ที่ 2 และอันที่ 1 กำลังถูกเขียนบนเธรดแยกต่างหาก แต่การเขียนล้มเหลวด้วยเหตุผลบางอย่างเธรดหลักควรทราบถึงความล้มเหลวนั้น

------------------------------------------------> (main thread, fills buffers)
FF|fX|
------------------------------------------------> (writer thread)
__|X|

สมมติว่าอินเทอร์เฟซของ CSequentialStreamWriter นั้นมีฟังก์ชั่นเขียนส่งคืนบูลหรือมีข้อผิดพลาดดังนั้นจึงมีข้อผิดพลาดในเธรดแยกต่างหากคุณต้องจำสถานะนั้นดังนั้นครั้งต่อไปที่คุณเรียกใช้ Write หรือ Finilize บนเธรดหลัก เท็จหรือจะโยนข้อยกเว้น และมันก็ไม่สำคัญว่าคุณจะหยุดเติมบัฟเฟอร์ที่ใดแม้ว่าคุณจะเขียนข้อมูลไปข้างหน้าหลังจากความล้มเหลว - ไฟล์ส่วนใหญ่จะเสียหายและไร้ประโยชน์


3
การแสดง I / O นั้นขนานกับการคำนวณเป็นความคิดที่ดีมาก แต่สำหรับ Windows คุณไม่ควรใช้เธรดเพื่อทำให้สำเร็จ ให้ใช้ "I / O ที่ทับซ้อนกัน" ซึ่งจะไม่บล็อกเธรดใดข้อความหนึ่งของคุณระหว่างการโทร I / O หมายความว่าคุณแทบจะไม่ต้องกังวลกับการซิงโครไนซ์เธรด (เพียงไม่เข้าถึงบัฟเฟอร์ที่มีการดำเนินการ I / O ที่ใช้งานอยู่)
Ben Voigt

11

ผมขอแนะนำให้พยายามแมปแฟ้ม ฉันใช้mmapในอดีตในสภาพแวดล้อม UNIX และฉันประทับใจในประสิทธิภาพที่ฉันสามารถทำได้


1
@nalply ยังคงเป็นวิธีการทำงานที่มีประสิทธิภาพและน่าสนใจที่ควรทราบ
Yam Marcovic

stackoverflow.com/a/2895799/220060เกี่ยวกับข้อดีข้อเสียของ mmap โดยเฉพาะอย่างยิ่งหมายเหตุ "เพื่อการเข้าถึงไฟล์อย่างต่อเนื่องโดยบริสุทธิ์มันไม่ได้เป็นทางออกที่ดีกว่า [... ]" เช่นเดียวกันกับstackoverflow.com/questions/726471มันบอกได้อย่างมีประสิทธิภาพว่าในระบบ 32 บิตคุณ จำกัด เพียง 2 หรือ 3 GB - โดยวิธีการที่ไม่ใช่ฉันที่ downvoted คำตอบนั้น
nalply

8

คุณสามารถใช้FILE*แทนและวัดประสิทธิภาพที่คุณได้รับ? มีสองตัวเลือกให้ใช้fwrite/writeแทนfstream:

#include <stdio.h>

int main ()
{
  FILE * pFile;
  char buffer[] = { 'x' , 'y' , 'z' };
  pFile = fopen ( "myfile.bin" , "w+b" );
  fwrite (buffer , 1 , sizeof(buffer) , pFile );
  fclose (pFile);
  return 0;
}

หากคุณตัดสินใจที่จะใช้writeลองสิ่งที่คล้ายกัน:

#include <unistd.h>
#include <fcntl.h>

int main(void)
{
    int filedesc = open("testfile.txt", O_WRONLY | O_APPEND);

    if (filedesc < 0) {
        return -1;
    }

    if (write(filedesc, "This will be output to testfile.txt\n", 36) != 36) {
        write(2, "There was an error writing to testfile.txt\n", 43);
        return -1;
    }

    return 0;
}

memory mapฉันยังอยากจะแนะนำให้คุณมองเข้าไปใน นั่นอาจเป็นคำตอบของคุณ เมื่อฉันต้องประมวลผลไฟล์ 20GB ในอื่น ๆ เพื่อเก็บไว้ในฐานข้อมูลและไฟล์ที่ไม่ได้เปิด ดังนั้นวิธีการแก้ปัญหาที่จะใช้แผนที่แบบ Moemory ผมว่าในPythonแต่


อันที่จริงแล้วFILE*โค้ดต้นฉบับที่เทียบเท่ากันโดยใช้บัฟเฟอร์ 512 MB เดียวกันนั้นได้รับความเร็วเต็มที่ บัฟเฟอร์ปัจจุบันของคุณเล็กเกินไป
Mysticial

1
@ Mysticial แต่นั่นเป็นเพียงตัวอย่าง
cybertextron

ในระบบส่วนใหญ่2สอดคล้องกับข้อผิดพลาดมาตรฐาน แต่ก็ยังคงแนะนำว่าคุณต้องการใช้แทนSTDERR_FILENO 2ปัญหาสำคัญอีกประการหนึ่งคือคุณสามารถรับ errorno ได้หนึ่งรายการคือ EINTR เมื่อคุณได้รับสัญญาณขัดจังหวะซึ่งไม่ใช่ข้อผิดพลาดจริงและคุณควรลองอีกครั้ง
Peyman

6

ลองใช้ open () / write () / close () การเรียก API และทดสอบด้วยขนาดบัฟเฟอร์เอาต์พุต ฉันหมายความว่าอย่าส่งบัฟเฟอร์ "หลายหลายไบต์" ทั้งหมดในครั้งเดียวให้ทำการเขียนสองสามครั้ง (เช่น TotalNumBytes / OutBufferSize) OutBufferSize สามารถจาก 4096 ไบต์ถึงเมกะไบต์

ลองอีกครั้ง - ใช้ WinAPI OpenFile / CreateFile และใช้บทความ MSDN นี้เพื่อปิดการบัฟเฟอร์ (FILE_FLAG_NO_BUFFERING) และบทความ MSDN นี้บน WriteFile ()แสดงวิธีรับขนาดบล็อกสำหรับไดรฟ์เพื่อทราบขนาดบัฟเฟอร์ที่เหมาะสม

อย่างไรก็ตาม std :: ofstream เป็น wrapper และอาจมีการบล็อกในการดำเนินการ I / O โปรดทราบว่าการสำรวจอาร์เรย์ N-กิกะไบต์ทั้งหมดนั้นต้องใช้เวลาพอสมควรเช่นกัน ในขณะที่คุณกำลังเขียนบัฟเฟอร์ขนาดเล็กมันจะเข้าสู่แคชและทำงานได้เร็วขึ้น


6

fstreams ไม่ช้ากว่าสตรีม C ต่อ se แต่ใช้CPU มากขึ้น (โดยเฉพาะถ้าบัฟเฟอร์ไม่ได้รับการกำหนดค่าอย่างเหมาะสม) เมื่อ CPU อิ่มตัวจะ จำกัด อัตรา I / O

อย่างน้อยการนำ MSVC 2015 ไปใช้จะคัดลอก1 อักขระพร้อมกันไปยังบัฟเฟอร์เอาต์พุตเมื่อไม่ได้ตั้งค่าบัฟเฟอร์ของกระแสข้อมูล (ดูstreambuf::xsputn) ดังนั้นให้ตรวจสอบการตั้งค่าบัฟเฟอร์กระแส (> 0)

ฉันสามารถรับความเร็วในการเขียนที่ 1500MB / s (ความเร็วเต็มที่ของ M.2 SSD ของฉัน) ด้วยการfstreamใช้รหัสนี้:

#include <iostream>
#include <fstream>
#include <chrono>
#include <memory>
#include <stdio.h>
#ifdef __linux__
#include <unistd.h>
#endif
using namespace std;
using namespace std::chrono;
const size_t sz = 512 * 1024 * 1024;
const int numiter = 20;
const size_t bufsize = 1024 * 1024;
int main(int argc, char**argv)
{
  unique_ptr<char[]> data(new char[sz]);
  unique_ptr<char[]> buf(new char[bufsize]);
  for (size_t p = 0; p < sz; p += 16) {
    memcpy(&data[p], "BINARY.DATA.....", 16);
  }
  unlink("file.binary");
  int64_t total = 0;
  if (argc < 2 || strcmp(argv[1], "fopen") != 0) {
    cout << "fstream mode\n";
    ofstream myfile("file.binary", ios::out | ios::binary);
    if (!myfile) {
      cerr << "open failed\n"; return 1;
    }
    myfile.rdbuf()->pubsetbuf(buf.get(), bufsize); // IMPORTANT
    for (int i = 0; i < numiter; ++i) {
      auto tm1 = high_resolution_clock::now();
      myfile.write(data.get(), sz);
      if (!myfile)
        cerr << "write failed\n";
      auto tm = (duration_cast<milliseconds>(high_resolution_clock::now() - tm1).count());
      cout << tm << " ms\n";
      total += tm;
    }
    myfile.close();
  }
  else {
    cout << "fopen mode\n";
    FILE* pFile = fopen("file.binary", "wb");
    if (!pFile) {
      cerr << "open failed\n"; return 1;
    }
    setvbuf(pFile, buf.get(), _IOFBF, bufsize); // NOT important
    auto tm1 = high_resolution_clock::now();
    for (int i = 0; i < numiter; ++i) {
      auto tm1 = high_resolution_clock::now();
      if (fwrite(data.get(), sz, 1, pFile) != 1)
        cerr << "write failed\n";
      auto tm = (duration_cast<milliseconds>(high_resolution_clock::now() - tm1).count());
      cout << tm << " ms\n";
      total += tm;
    }
    fclose(pFile);
    auto tm2 = high_resolution_clock::now();
  }
  cout << "Total: " << total << " ms, " << (sz*numiter * 1000 / (1024.0 * 1024 * total)) << " MB/s\n";
}

ฉันลองใช้รหัสนี้บนแพลตฟอร์มอื่น ๆ (Ubuntu, FreeBSD) และสังเกตว่าไม่มีความแตกต่างของอัตรา I / O แต่ความแตกต่างของการใช้งาน CPUประมาณ 8: 1 ( fstreamใช้CPU มากกว่า 8 เท่า ) ดังนั้นหนึ่งสามารถจินตนาการได้ว่าฉันมีดิสก์ที่เร็วขึ้นการfstreamเขียนจะช้าลงเร็วกว่าstdioเวอร์ชั่น


3

ลองใช้ไฟล์ที่แม็พหน่วยความจำ


@ Mehrdad แต่ทำไม เพราะมันเป็นวิธีแก้ปัญหาขึ้นอยู่กับแพลตฟอร์ม?
qehgt

3
ไม่ ... เพราะการเขียนไฟล์แบบต่อเนื่องอย่างรวดเร็วคุณต้องเขียนข้อมูลจำนวนมากในครั้งเดียว (บอกว่าชิ้นส่วน 2-MiB อาจเป็นจุดเริ่มต้นที่ดี) ไฟล์ที่แมปหน่วยความจำจะไม่ยอมให้คุณควบคุมความละเอียดได้ โดยทั่วไปฉันไม่เคยเห็นว่าพวกเขามีประสิทธิภาพเท่ากับการอ่าน / การเขียนตามปกติReadFileและสำหรับการเข้าถึงตามลำดับแม้ว่าการเข้าถึงแบบสุ่มอาจทำได้ดีกว่า
user541686

แต่ไฟล์ที่แมปหน่วยความจำจะถูกใช้โดยระบบปฏิบัติการสำหรับเพจจิ้ง ฉันคิดว่ามันเป็นวิธีที่ดีที่สุด (ในแง่ของความเร็ว) ในการอ่าน / เขียนข้อมูล
qehgt

7
@Mysticial: ผู้คนรู้จัก "หลายสิ่งหลายอย่างที่ผิดธรรมดา
เบ็น Voigt

1
@qehgt: หากมีสิ่งใดเพจจะได้รับการปรับให้เหมาะสมที่สุดสำหรับการเข้าถึงแบบสุ่มมากกว่าการเข้าถึงแบบลำดับ การอ่านข้อมูล 1 หน้านั้นช้ากว่าการอ่านข้อมูลขนาด 1 เมกะไบต์ในการทำงานครั้งเดียว
user541686

3

หากคุณคัดลอกบางสิ่งจากดิสก์ A ไปยังดิสก์ B ใน explorer Windows จะใช้ DMA นั่นหมายถึงกระบวนการคัดลอกส่วนใหญ่โดยทั่วไปแล้วซีพียูจะไม่ทำอะไรนอกจากบอกตัวควบคุมดิสก์ว่าจะวางที่ใดและรับข้อมูลจากกำจัดขั้นตอนทั้งหมดในโซ่และที่ไม่เหมาะสำหรับการย้ายจำนวนมาก ของข้อมูล - และฉันหมายถึงฮาร์ดแวร์

สิ่งที่คุณทำเกี่ยวข้องกับ CPU เป็นอย่างมาก ฉันต้องการให้คุณชี้ไปที่ส่วน "การคำนวณบางอย่างเพื่อเติม []" ซึ่งฉันคิดว่าเป็นสิ่งจำเป็น คุณสร้าง [] จากนั้นคัดลอกจาก [] ไปยังบัฟเฟอร์เอาต์พุต (นั่นคือสิ่งที่ fstream :: write ทำ) จากนั้นคุณสร้างอีกครั้งเป็นต้น

จะทำอย่างไร? Multithreading! (ฉันหวังว่าคุณจะมีหน่วยประมวลผลแบบมัลติคอร์)

  • ส้อม.
  • ใช้หนึ่งเธรดเพื่อสร้างข้อมูล []
  • ใช้อีกอันเพื่อเขียนข้อมูลจาก [] ไปยังดิสก์
  • คุณจะต้องมีสองอาร์เรย์ a1 [] และ a2 [] และสลับไปมาระหว่างกัน
  • คุณจะต้องทำการซิงโครไนซ์ระหว่างเธรดของคุณ (เซมาฟอร์คิวข้อความ ฯลฯ )
  • ใช้ระดับต่ำกว่า unbuffered ฟังก์ชั่นเช่นฟังก์ชั่นWriteFile ที่ Mehrdad พูดถึง

1

หากคุณต้องการเขียนสตรีมไฟล์อย่างรวดเร็วคุณสามารถเพิ่มสตรีมบัฟเฟอร์การอ่านให้ใหญ่ขึ้นได้:

wfstream f;
const size_t nBufferSize = 16184;
wchar_t buffer[nBufferSize];
f.rdbuf()->pubsetbuf(buffer, nBufferSize);

นอกจากนี้เมื่อเขียนข้อมูลจำนวนมากไปยังไฟล์บางครั้งก็เป็นได้เร็วขึ้นเพื่อเหตุผลขยายขนาดของไฟล์แทนร่างกายนี้เป็นเพราะเมื่อมีเหตุผลการขยายไฟล์ระบบไฟล์ไม่เป็นศูนย์พื้นที่ใหม่ออกมาก่อนที่จะเขียนมัน นอกจากนี้ยังเป็นการฉลาดที่จะขยายไฟล์อย่างมีเหตุผลมากกว่าที่คุณต้องการเพื่อป้องกันการขยายไฟล์จำนวนมาก การขยายไฟล์แบบลอจิคัลรองรับ Windows โดยการโทรSetFileValidDataหรือxfsctlด้วยXFS_IOC_RESVSP64ระบบ XFS


0

ฉันรวบรวมโปรแกรมของฉันใน gcc ในGNU / Linuxและmingwใน win 7 และชนะ xp และทำงานได้ดี

คุณสามารถใช้โปรแกรมของฉันและสร้างไฟล์ 80 GB เพียงแค่เปลี่ยนบรรทัด 33 เป็น

makeFile("Text.txt",1024,8192000);

เมื่อออกจากโปรแกรมไฟล์จะถูกทำลายจากนั้นตรวจสอบไฟล์เมื่อมันกำลังทำงาน

เพื่อให้โปรแกรมที่คุณต้องการเพียงแค่เปลี่ยนโปรแกรม

firt one เป็นโปรแกรม windows และที่สองสำหรับ GNU / Linux

http://mustafajf.persiangig.com/Projects/File/WinFile.cpp

http://mustafajf.persiangig.com/Projects/File/File.cpp

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.