Ungepuffert auf Platte schreiben



  • hustbaer schrieb:

    Der grosse Unterschied zwischen ANSI und STL ist ein weiteres Zeichen dafür dass da irgendwas nicht stimmt -- ich sehe keinen Grund warum die ANSI APIs so extrem langsamer sein sollten.

    Testcode posten, vielleicht steckt da ein Fehler.



  • Diese Tests wurden alle mit der gleichen Festplatte durchführt. Die Festplatte wurde frisch formatiert und es griffen während des Testes keine anderen Programm auf die Festplatte zu. Daher sollte die Resultat auch stimmen. Ein flush wurde übrigens nicht durchgeführt.

    Ich kenne den internen Aufbau der einzelnen Bibliotheken nicht, daher kann ich auch nur raten.
    Die Geschwindigkeiten der api sind in etwa gleich wie bei der ansi, vermutlich greift ansi direkt auf die api zu. Wieso die api bei grossen Blöcken so langsam ist, weiss ich auch nicht.
    Die api ist bei Blockgrössen von 128k am schnellste, das könnte sich ja die stl zu nutzen machen. Es ist auch möglich, dass die stl die Daten selbst puffert und mit FILE_FLAG_NO_BUFFERING schreibt.
    Api mit FILE_FLAG_WRITE_THROUGH ist dann langsam, wenn der Block eine "krumme" Grösse hat (!PAGE_SIZE?).

    CPPWiedereinsteiger schrieb:

    Ich schreibe hier mit dem gcc/BCB4 und ofstream die Platte voll, da merke ich ich nichts von einem 2GB Limit

    Hast du schon mal versucht ein seek auf eine Datei > 2GB zu machen, ohne murks über class fpos?



  • Hier noch was zum selber testen. Ansi habe ich weg gelassen

    #include <iostream>
    #include <fstream>
    #include <cassert>
    #include <vector>
    using namespace std;
    #include <windows.h>
    
    class FileTest
    {
    public:
      virtual unsigned write(__int64 Pos, char *Data, unsigned Count) = 0;
      virtual void flush() = 0;
    };
    
    class FileTestAPI : public FileTest
    {
    public:
      void create(const char *Name)
      {
        //FILE_WRITE_DATA | FILE_READ_DATA
        File = CreateFileA(Name, GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
        assert(File != INVALID_HANDLE_VALUE);
      }
    
      void createWt(const char *Name)
      {
        File = CreateFileA(Name, GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, 0);
        assert(File != INVALID_HANDLE_VALUE);
      }
    
      void close()
      {
        CloseHandle(File);
      }
    
      void flush()
      {
        FlushFileBuffers(File);
      }
    
      unsigned write(__int64 Pos, char *Data, unsigned Count)
      {
        DWORD CountRet;
        OVERLAPPED Overlapped;
        memset(&Overlapped, 0, sizeof(OVERLAPPED));
        LARGE_INTEGER ToHighLow;
        ToHighLow.QuadPart = Pos;
        Overlapped.Offset = ToHighLow.LowPart;
        Overlapped.OffsetHigh = ToHighLow.HighPart;
        BOOL Success = WriteFile(File, Data, Count, &CountRet, &Overlapped);
        assert(Success);
        return CountRet;
      }
    private:
      HANDLE File;
    };
    
    class FileTestSTL : public FileTest
    {
    public:
      void create(const char *Name)
      {
        File.open(Name, ios::out | ios::in | ios::trunc | ios::binary);
        assert(File);
      }
    
      void close()
      {
        File.close();
      }
    
      void flush()
      {
        File.flush();
      }
    
      unsigned write(__int64 Pos, char *Data, unsigned Count)
      {
        File.seekp(static_cast<streamoff>(Pos));
        File.write(Data, Count);
        assert(File.good());
        return Count;
      }
    private:
      fstream File;
    };
    
    void write(FileTest &FileTest, unsigned NumBlocks, unsigned BlockSize)
    {
      vector<char> Buffer(BlockSize);
      LARGE_INTEGER Freq;
      QueryPerformanceFrequency(&Freq);
      LARGE_INTEGER StartTime;
      QueryPerformanceCounter(&StartTime);
    
      for (unsigned Block = 0; Block < NumBlocks; Block++)
        FileTest.write(Block * BlockSize, &Buffer[0], BlockSize);
      FileTest.flush();
    
      LARGE_INTEGER StopTime;
      QueryPerformanceCounter(&StopTime);
      double Time = static_cast<double>(StopTime.QuadPart - StartTime.QuadPart) / Freq.QuadPart;
      cout << "time = " << Time << "s; tp = " << NumBlocks * BlockSize / Time / 1e6 << "MB/s" << endl;
    
    }
    
    int main()
    {
      const char *FileName = "d:test.dat";
      FileTestAPI Test1;
      Test1.create(FileName);
      cout << "API; number of blocks = 1000; block size = 1M" << endl;
      write(Test1, 1000, 1024*1024);
      cout << "API; number of blocks = 10000; block size = 128k" << endl;
      write(Test1, 10000, 128*1024);
      Test1.close();
    
      FileTestAPI Test2;
      Test2.createWt(FileName);
      cout << "API WT; number of blocks = 1000; block size = 1M" << endl;
      write(Test2, 1000, 1024*1024);
      cout << "API WT; number of blocks = 10000; block size = 128k" << endl;
      write(Test2, 10000, 128*1024);
      Test2.close();
    
      FileTestSTL Test3;
      Test3.create(FileName);
      cout << "STL; number of blocks = 1000; block size = 1M" << endl;
      write(Test3, 1000, 1024*1024);
      cout << "STL; number of blocks = 10000; block size = 128k" << endl;
      write(Test3, 10000, 128*1024);
      Test3.close();
    
      cin.get();
    }
    


  • und noch die Resultate:

    API; number of blocks = 1000; block size = 1M
    time = 45.0773s; tp = 23.2617MB/s
    API; number of blocks = 10000; block size = 128k
    time = 20.2676s; tp = 64.6707MB/s
    API WT; number of blocks = 1000; block size = 1M
    time = 15.7388s; tp = 66.6236MB/s
    API WT; number of blocks = 10000; block size = 128k
    time = 19.9971s; tp = 65.5453MB/s
    STL; number of blocks = 1000; block size = 1M
    time = 24.4787s; tp = 42.8362MB/s
    STL; number of blocks = 10000; block size = 128k
    time = 21.432s; tp = 61.1571MB/s

    Dieser Test wurden auf einem anderen PC durchgeführt als die letzten.



  • lesam schrieb:

    Hast du schon mal versucht ein seek auf eine Datei > 2GB zu machen, ohne murks über class fpos?

    Nein, da das Programm das die Datein weiterverarbeitet nicht von mir ist.
    Ich weiss auch nicht mit welcher Funktion es auf die Daten zugreift, ich habe keinen Source davon. Mein Programm generiert nur die Daten und schreibt sie weg.

    Laut HexEditor sehen die Daten am Ende korrekt aus und bestimmte wichtige Zwischensequenzen sind auch vorhanden und an der richtigen Stelle, gehe also mal davon aus, dass die Datei ok ist, werde das aber noch näher überprüfen.

    Danke für den Hinweis.



  • Compiliert mit BCB4, geteste auf völlig leerer NTFS-Partition:
    API; number of blocks = 1000; block size = 1M
    time = 32.7581s; tp = 32.0097MB/s
    API; number of blocks = 10000; block size = 128k
    time = 26.8433s; tp = 48.8286MB/s
    API WT; number of blocks = 1000; block size = 1M
    time = 29.4683s; tp = 35.5832MB/s
    API WT; number of blocks = 10000; block size = 128k
    time = 26.4855s; tp = 49.4881MB/s
    STL; number of blocks = 1000; block size = 1M
    time = 33.0335s; tp = 31.7428MB/s
    STL; number of blocks = 10000; block size = 128k
    time = 26.7339s; tp = 49.0284MB/s

    Die Blockgrösse scheint wohl wichtiger als alles andere zu sein. Dass WT kaum Gewinn bringt überrascht mich stark.


  • Mod

    Dieser Test Code ist nicht gut und sagt nichts.

    1. Er benutzt immer die selbe Datei.
    2. Der zweite Schreibzyklus erfolgt bereits in eine allokierten Bereich.
    3. Für was sind die seeks bei jedem Zyklus. Das ist unnötig.

    Habe den Code verändert.
    Ganz klar zu sehen: STL ist die lahme Ente!
    Die CRT baut auch einen Buffer ntern auf, deswegen bringen große Blöcke gar nichts.
    API mit write through und 1MB Blöcken ist der Spitzenreiter.

    Man merkt, dass zu große Blöcke nichts bringen! Außer man verwendet Write through.

    Alle Zeiten sind aus dem Debug Build:
    API number of blocks 1000 block size = 1024KB
    time = 40.4724s; tp = 25.9084MB/s
    API number of blocks 4000 block size = 256KB
    time = 33.9254s; tp = 30.9083MB/s
    API number of blocks 8000 block size = 128KB
    time = 36.0559s; tp = 29.082MB/s
    API WT number of blocks 1000 block size = 1024K
    time = 28.5668s; tp = 36.7061MB/s
    API WT number of blocks 4000 block size = 256KB
    time = 38.7442s; tp = 27.0641MB/s
    API WT number of blocks 8000 block size = 128KB
    time = 34.4901s; tp = 30.4023MB/s
    CRT number of blocks 1000 block size = 1024KB
    time = 46.2519s; tp = 22.671MB/s
    CRT number of blocks 4000 block size = 256KB
    time = 42.6777s; tp = 24.5696MB/s
    CRT number of blocks 8000 block size = 128KB
    time = 35.5861s; tp = 29.4659MB/s
    STL number of blocks 1000 block size = 1024KB
    time = 58.6202s; tp = 17.8876MB/s
    STL number of blocks 4000 block size = 256KB
    time = 52.7365s; tp = 19.8833MB/s
    STL number of blocks 8000 block size = 128KB
    time = 55.4942s; tp = 18.8952MB/s

    BTW: Nachfolgenden Code würdeich niemals so schreiben, ich habe nur auf die schnelle den bestehenden Code ergänzt. 🤡

    // WriteSpeed.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    
    #include <iostream> 
    #include <fstream> 
    #include <cassert> 
    #include <vector> 
    using namespace std; 
    #include <windows.h> 
    
    const DWORD dwFileSizeKB = 1000*1024;
    const DWORD adwBlockSizeKB[] = 
    {
    	1024,
    	256,
    	128,
    };
    
    class FileTest 
    { 
    public: 
    	virtual void create(const char *Name) = 0;
    	virtual unsigned write(char *Data, unsigned Count) = 0; 
    	virtual void flush()=0;
    	virtual void close()=0;
    }; 
    
    class FileTestAPI : public FileTest 
    { 
    public: 
    	void create(const char *Name) 
    	{ 
    		//FILE_WRITE_DATA | FILE_READ_DATA 
    		File = CreateFileA(Name, GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); 
    		assert(File != INVALID_HANDLE_VALUE); 
    	} 
    
    	void createWt(const char *Name) 
    	{ 
    		File = CreateFileA(Name, GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, 0); 
    		assert(File != INVALID_HANDLE_VALUE); 
    	} 
    
    	void close() 
    	{ 
    		CloseHandle(File); 
    	} 
    
    	void flush() 
    	{ 
    		FlushFileBuffers(File); 
    	} 
    
    	unsigned write(char *Data, unsigned Count) 
    	{ 
    		DWORD CountRet; 
    		BOOL Success = WriteFile(File, Data, Count, &CountRet, NULL); 
    		assert(Success); 
    		return CountRet; 
    	} 
    protected: 
    	HANDLE File; 
    }; 
    
    class FileTestAPI_WT : public FileTestAPI
    { 
    public: 
    	void create(const char *Name) 
    	{ 
    		File = CreateFileA(Name, GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, 0); 
    		assert(File != INVALID_HANDLE_VALUE); 
    	} 
    }; 
    
    class FileTestCRT : public FileTest 
    { 
    public: 
    	void create(const char *Name) 
    	{ 
    		pFile = fopen(Name, "wb"); 
    		assert(pFile); 
    	} 
    
    	void close() 
    	{ 
    		fclose(pFile); 
    		pFile = NULL;
    	} 
    
    	void flush() 
    	{ 
    		fflush(pFile); 
    	} 
    
    	unsigned write(char *Data, unsigned Count) 
    	{ 
    		fwrite(Data,Count,sizeof(char),pFile);
    		return Count; 
    	} 
    private: 
    	FILE *pFile; 
    }; 
    
    class FileTestSTL : public FileTest 
    { 
    public: 
    	void create(const char *Name) 
    	{ 
    		File.open(Name, ios::out | ios::in | ios::trunc | ios::binary); 
    		assert(File); 
    	} 
    
    	void close() 
    	{ 
    		File.close(); 
    	} 
    
    	void flush() 
    	{ 
    		File.flush(); 
    	} 
    
    	unsigned write(char *Data, unsigned Count) 
    	{ 
    		File.write(Data, Count); 
    		assert(File.good()); 
    		return Count; 
    	} 
    private: 
    	fstream File; 
    }; 
    
    void write(FileTest &FileTest, unsigned NumBlocks, unsigned BlockSize) 
    { 
    	vector<char> Buffer(BlockSize);
    	for (unsigned i=0; i<BlockSize; ++i)
    		Buffer[i] = (char)i;
    
    	LARGE_INTEGER Freq; 
    	QueryPerformanceFrequency(&Freq); 
    	LARGE_INTEGER StartTime; 
    	QueryPerformanceCounter(&StartTime); 
    
    	for (unsigned Block = 0; Block < NumBlocks; Block++) 
    		FileTest.write(&Buffer[0], BlockSize); 
    	FileTest.flush();
    
    	LARGE_INTEGER StopTime; 
    	QueryPerformanceCounter(&StopTime); 
    	double Time = static_cast<double>(StopTime.QuadPart - StartTime.QuadPart) / Freq.QuadPart; 
    	cout << "time = " << Time << "s; tp = " << NumBlocks * BlockSize / Time / 1e6 << "MB/s" << endl; 
    
    } 
    
    void write(FileTest &FileTest, const char *filename, const char *testname)
    {
    	for (int i=0; i<3; ++i)
    	{
    		DWORD dwBlockSize = adwBlockSizeKB[i];
    		DWORD dwNumBlocks = dwFileSizeKB/dwBlockSize;
    		cout << testname << " number of blocks " << dwNumBlocks << " block size = " << dwBlockSize << "KB" << endl; 
    		FileTest.create(filename);
    		write(FileTest, dwNumBlocks, dwBlockSize*1024); 
    		FileTest.close();
    	}
    }
    
    int main() 
    { 
    	write(FileTestAPI(),"test1.dat","API");
    	write(FileTestAPI_WT(),"test2.dat","API WT");
    	write(FileTestCRT(),"test3.dat","CRT");
    	write(FileTestSTL(),"test4.dat","STL");
    }
    


  • Martin Richter schrieb:

    Dieser Test Code ist nicht gut und sagt nichts.
    1. Er benutzt immer die selbe Datei.

    Wenn man die Festplatte zumüllt, werden die Resultate dadurch auch nicht genauer. Eigentlich spielt es keine Rolle, immer in die gleiche Datei zu schreiben.

    Martin Richter schrieb:

    2. Der zweite Schreibzyklus erfolgt bereits in eine allokierten Bereich.

    🙄

    Martin Richter schrieb:

    3. Für was sind die seeks bei jedem Zyklus. Das ist unnötig.

    Soll nur zeigen, dass mit der stl bei 2GB schluss ist.

    API number of blocks 1000 block size = 1024KB
    time = 31.9021s; tp = 32.8685MB/s
    API number of blocks 4000 block size = 256KB
    time = 17.8999s; tp = 58.5799MB/s
    API number of blocks 8000 block size = 128KB
    time = 18.7416s; tp = 55.9492MB/s
    API WT number of blocks 1000 block size = 1024KB
    time = 18.2901s; tp = 57.3302MB/s
    API WT number of blocks 4000 block size = 256KB
    time = 19.3387s; tp = 54.2218MB/s
    API WT number of blocks 8000 block size = 128KB
    time = 20.8967s; tp = 50.1789MB/s
    CRT number of blocks 1000 block size = 1024KB
    time = 30.3426s; tp = 34.5579MB/s
    CRT number of blocks 4000 block size = 256KB
    time = 17.3728s; tp = 60.3575MB/s
    CRT number of blocks 8000 block size = 128KB
    time = 17.6469s; tp = 59.4199MB/s
    STL number of blocks 1000 block size = 1024KB
    time = 19.5674s; tp = 53.588MB/s
    STL number of blocks 4000 block size = 256KB
    time = 19.7635s; tp = 53.0561MB/s
    STL number of blocks 8000 block size = 128KB
    time = 19.9676s; tp = 52.5138MB/s



  • lesam schrieb:

    Eigentlich spielt es keine Rolle, immer in die gleiche Datei zu schreiben.

    Spielt es natürlich...
    Die MFT für dieses zweite schriben ist schon allokiert worden und muss nicht nochmals neu gesucht werden. Somit ist das erste schreiben immer langsamer als ein folgendes in die gleiche Datei.

    Ein "richtiger" Vergleich ist nur möglich mit gleichen Anfangsbedingungen. Diese sind bisher in keinem Testfall vorhanden.

    Gleich heisst für mich:
    - XP-SP2 ohne zusätzliche Software installiert
    - Rechner neu gebootet (aus dem "Off" Zustand)
    - Eigene Festplatte um die Testdaten drauf zu schreiben
    - Die Festplatte muss entweder komplett neu Formatiert worden sein (natürlich vor dem booten) oder immer der korrekte Zustand wieder hergestellt worden sein (also immer das gleiche Image; natürlich auch vor dem Booten)
    - Nach dem booten dann 60 Sekunden warten und dann den Test starten
    - Dann jeweils immer nur *ein* Testfall durchführen

    Die ganzen obigen Schritte dann führ jeden Testfall von Anfang an durchführen.



  • Jochen Kalmbach schrieb:

    Spielt es natürlich...
    Die MFT für dieses zweite schriben ist schon allokiert worden und muss nicht nochmals neu gesucht werden. Somit ist das erste schreiben immer langsamer als ein folgendes in die gleiche Datei.

    Bei einer Festplatte mit genügend kapazität, werden die Daten beim zweiten Durchgang in andere Sektoren geschrieben als beim ersten Durchgang!

    Jochen Kalmbach schrieb:

    Ein "richtiger" Vergleich ist nur möglich mit gleichen Anfangsbedingungen. Diese sind bisher in keinem Testfall vorhanden.

    Da hast du recht. Aber man sieht zumindest, dass man die stl nicht generell als langsam bezeichen kann.


  • Mod

    lesam schrieb:

    API number of blocks 1000 block size = 1024KB
    time = 31.9021s; tp = 32.8685MB/s
    API number of blocks 4000 block size = 256KB
    time = 17.8999s; tp = 58.5799MB/s
    API number of blocks 8000 block size = 128KB
    time = 18.7416s; tp = 55.9492MB/s
    API WT number of blocks 1000 block size = 1024KB
    time = 18.2901s; tp = 57.3302MB/s
    API WT number of blocks 4000 block size = 256KB
    time = 19.3387s; tp = 54.2218MB/s
    API WT number of blocks 8000 block size = 128KB
    time = 20.8967s; tp = 50.1789MB/s
    CRT number of blocks 1000 block size = 1024KB
    time = 30.3426s; tp = 34.5579MB/s
    CRT number of blocks 4000 block size = 256KB
    time = 17.3728s; tp = 60.3575MB/s
    CRT number of blocks 8000 block size = 128KB
    time = 17.6469s; tp = 59.4199MB/s
    STL number of blocks 1000 block size = 1024KB
    time = 19.5674s; tp = 53.588MB/s
    STL number of blocks 4000 block size = 256KB
    time = 19.7635s; tp = 53.0561MB/s
    STL number of blocks 8000 block size = 128KB
    time = 19.9676s; tp = 52.5138MB/s

    Eigentümlich, dass sich bei dir Write Through nichbt auszahlt. Scheinbar ist hier schon ein gravierender Unterschied in den Unterschiedlichen Hardware Systemen zu sehen. Ich habe ein Hardware RAID 1 System installliert.
    Wie sieht es bei Dir aus?



  • 1MB sind keine grossen Blöcke 🙂
    100MB wäre ein grosser Block 😃


  • Mod

    hustbaer schrieb:

    1MB sind keine grossen Blöcke 🙂
    100MB wäre ein grosser Block 😃

    Was ist viel für wen? Controer, Hauptspeicher, Software, User...
    alles relativ. 🕶

    Da ich aber noch aus den Zeiten stamme in denen man mit seinem C Programm irgendwie alles in 48KB Hauptspeicher packen musste und eine Festplatte mit 20MB Wagenrad Größe hatte habe ich immer noch Respekt vor diesen Datenmengen.


  • Mod

    lesam schrieb:

    API number of blocks 1000 block size = 1024KB
    time = 31.9021s; tp = 32.8685MB/s
    API WT number of blocks 1000 block size = 1024KB
    time = 18.2901s; tp = 57.3302MB/s
    CRT number of blocks 1000 block size = 1024KB
    time = 30.3426s; tp = 34.5579MB/s
    STL number of blocks 1000 block size = 1024KB
    time = 19.5674s; tp = 53.588MB/s

    😕 Habe mit gerade noch mal Deinen Code angesehen. Das ist aber sehr eigentümlich.
    Wie kann die STL schneller sein...

    Welchen Compiler und welche STL verwendest Du? Debug/Release?



  • Martin Richter schrieb:

    Ich habe ein Hardware RAID 1 System installliert.
    Wie sieht es bei Dir aus?

    Bei mir habe ich eine zusätzliche Festplatte drinn.

    Martin Richter schrieb:

    Welchen Compiler und welche STL verwendest Du? Debug/Release?

    Microsoft Visual C++ 2005 mit der mitgelieferten stl. Release

    Martin Richter schrieb:

    Habe mit gerade noch mal Deinen Code angesehen. Das ist aber sehr eigentümlich. Wie kann die STL schneller sein...

    Die Frage ist eher, wieso ist die api bei grossen Blöcken so langsam.


Anmelden zum Antworten