ಹೆಚ್ಚು ಶ್ರಮವಿಲ್ಲದೆ C/C++ ಫೈಲ್ I/O ಅನ್ನು ವೇಗಗೊಳಿಸುವುದು

ಹೆಚ್ಚು ಶ್ರಮವಿಲ್ಲದೆ C/C++ ಫೈಲ್ I/O ಅನ್ನು ವೇಗಗೊಳಿಸುವುದು

ಮುನ್ನುಡಿ

ಜಗತ್ತಿನಲ್ಲಿ ಅಂತಹ ಸರಳ ಮತ್ತು ಅತ್ಯಂತ ಉಪಯುಕ್ತ ಉಪಯುಕ್ತತೆ ಇದೆ - ಬಿಡಿಲ್ಟಾ, ಮತ್ತು ಅದು ನಮ್ಮ ಉತ್ಪಾದನಾ ಪ್ರಕ್ರಿಯೆಯಲ್ಲಿ ಬಹಳ ಸಮಯದಿಂದ ಬೇರೂರಿದೆ (ಆದರೂ ಅದರ ಆವೃತ್ತಿಯನ್ನು ಸ್ಥಾಪಿಸಲು ಸಾಧ್ಯವಾಗದಿದ್ದರೂ, ಇದು ಖಂಡಿತವಾಗಿಯೂ ಕೊನೆಯದಾಗಿ ಲಭ್ಯವಿರಲಿಲ್ಲ). ನಾವು ಅದನ್ನು ಅದರ ಉದ್ದೇಶಿತ ಉದ್ದೇಶಕ್ಕಾಗಿ ಬಳಸುತ್ತೇವೆ - ಬೈನರಿ ಪ್ಯಾಚ್‌ಗಳನ್ನು ನಿರ್ಮಿಸುವುದು. ರೆಪೊಸಿಟರಿಯಲ್ಲಿ ಏನಿದೆ ಎಂದು ನೀವು ನೋಡಿದರೆ, ಅದು ಸ್ವಲ್ಪ ದುಃಖವಾಗುತ್ತದೆ: ವಾಸ್ತವವಾಗಿ, ಇದು ಬಹಳ ಹಿಂದೆಯೇ ಕೈಬಿಡಲ್ಪಟ್ಟಿದೆ ಮತ್ತು ಅದರಲ್ಲಿ ಹೆಚ್ಚಿನವು ತುಂಬಾ ಹಳೆಯದಾಗಿದೆ (ನನ್ನ ಮಾಜಿ ಸಹೋದ್ಯೋಗಿ ಒಮ್ಮೆ ಅಲ್ಲಿ ಹಲವಾರು ಸಂಪಾದನೆಗಳನ್ನು ಮಾಡಿದರು, ಆದರೆ ಅದು ಬಹಳ ಹಿಂದೆಯೇ) . ಸಾಮಾನ್ಯವಾಗಿ, ನಾನು ಈ ವಿಷಯವನ್ನು ಪುನರುತ್ಥಾನಗೊಳಿಸಲು ನಿರ್ಧರಿಸಿದೆ: ನಾನು ಫೋರ್ಕ್ ಮಾಡಿದೆ, ನಾನು ಬಳಸಲು ಯೋಜಿಸದಿದ್ದನ್ನು ಎಸೆದಿದ್ದೇನೆ, ಯೋಜನೆಯನ್ನು ಸರಿಸಿದೆ ಸಿಮೇಕ್, ಇನ್‌ಲೈನ್ ಮಾಡಿದ “ಹಾಟ್” ಮೈಕ್ರೋಫಂಕ್ಷನ್‌ಗಳು, ಸ್ಟಾಕ್‌ನಿಂದ ದೊಡ್ಡ ಅರೇಗಳನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ (ಮತ್ತು ವೇರಿಯಬಲ್ ಉದ್ದದ ಸರಣಿಗಳು, ಇದು ನನ್ನನ್ನು "ಬಾಂಬ್" ಎಂದು ನಾನೂ ಮಾಡುತ್ತದೆ), ಪ್ರೊಫೈಲರ್ ಅನ್ನು ಮತ್ತೊಮ್ಮೆ ಓಡಿಸಿದೆ - ಮತ್ತು ಸುಮಾರು 40% ಸಮಯವನ್ನು ಖರ್ಚು ಮಾಡಲಾಗಿದೆ ಎಂದು ಕಂಡುಹಿಡಿದಿದೆ ಬರೆಯಿರಿ...

ಹಾಗಾದರೆ ಫ್ರೈಟ್‌ನಲ್ಲಿ ಏನಾಗಿದೆ?

ಈ ಕೋಡ್‌ನಲ್ಲಿ, fwrite (ನನ್ನ ನಿರ್ದಿಷ್ಟ ಪರೀಕ್ಷಾ ಸಂದರ್ಭದಲ್ಲಿ: ನಿಕಟ 300 MB ಫೈಲ್‌ಗಳ ನಡುವೆ ಪ್ಯಾಚ್ ಅನ್ನು ನಿರ್ಮಿಸುವುದು, ಇನ್‌ಪುಟ್ ಡೇಟಾವು ಸಂಪೂರ್ಣವಾಗಿ ಮೆಮೊರಿಯಲ್ಲಿದೆ) ಸಣ್ಣ ಬಫರ್ ಗಾತ್ರದೊಂದಿಗೆ ಲಕ್ಷಾಂತರ ಬಾರಿ ಕರೆಯಲಾಗುತ್ತದೆ. ನಿಸ್ಸಂಶಯವಾಗಿ, ಈ ವಿಷಯವು ನಿಧಾನಗೊಳ್ಳುತ್ತದೆ ಮತ್ತು ಆದ್ದರಿಂದ ನಾನು ಹೇಗಾದರೂ ಈ ಅವಮಾನವನ್ನು ಪ್ರಭಾವಿಸಲು ಬಯಸುತ್ತೇನೆ. ವಿವಿಧ ರೀತಿಯ ಡೇಟಾ ಮೂಲಗಳನ್ನು ಕಾರ್ಯಗತಗೊಳಿಸಲು ಇನ್ನೂ ಯಾವುದೇ ಬಯಕೆ ಇಲ್ಲ, ಅಸಮಕಾಲಿಕ ಇನ್ಪುಟ್-ಔಟ್ಪುಟ್, ನಾನು ಸರಳವಾದ ಪರಿಹಾರವನ್ನು ಕಂಡುಹಿಡಿಯಲು ಬಯಸುತ್ತೇನೆ. ಮನಸ್ಸಿಗೆ ಬಂದ ಮೊದಲ ವಿಷಯವೆಂದರೆ ಬಫರ್ ಗಾತ್ರವನ್ನು ಹೆಚ್ಚಿಸುವುದು

setvbuf(file, nullptr, _IOFBF, 64* 1024)

ಆದರೆ ನಾನು ಫಲಿತಾಂಶದಲ್ಲಿ ಗಮನಾರ್ಹ ಸುಧಾರಣೆಯನ್ನು ಪಡೆಯಲಿಲ್ಲ (ಈಗ ಫ್ರೈಟ್ ಸುಮಾರು 37% ಸಮಯವನ್ನು ಹೊಂದಿದೆ) - ಅಂದರೆ ಇದು ಇನ್ನೂ ಆಗಾಗ್ಗೆ ಡಿಸ್ಕ್ಗೆ ಡೇಟಾವನ್ನು ಬರೆಯುವ ವಿಷಯವಲ್ಲ. Fwrite ನ “ಅಂಡರ್ ದಿ ಹುಡ್” ಅನ್ನು ನೋಡಿದಾಗ, ಲಾಕ್/ಅನ್‌ಲಾಕ್ FILE ರಚನೆಯು ಈ ರೀತಿಯ ಒಳಗೆ ನಡೆಯುತ್ತಿದೆ ಎಂದು ನೀವು ನೋಡಬಹುದು (ಹುಸಿ-ಕೋಡ್, ಎಲ್ಲಾ ವಿಶ್ಲೇಷಣೆಯನ್ನು ವಿಷುಯಲ್ ಸ್ಟುಡಿಯೋ 2017 ಅಡಿಯಲ್ಲಿ ನಡೆಸಲಾಗಿದೆ):


size_t fwrite (const void *buffer, size_t size, size_t count, FILE *stream)
{
   size_t retval = 0;
   _lock_str(stream);   /* lock stream */
   __try
   {
      retval = _fwrite_nolock(buffer, size, count, stream);
   }
   __finally 
   {
       _unlock_str(stream);   /* unlock stream */
   }
   return retval;
}

ಪ್ರೊಫೈಲರ್ ಪ್ರಕಾರ, _fwrite_nolock ಕೇವಲ 6% ಸಮಯಕ್ಕೆ ಖಾತೆಗಳನ್ನು ಹೊಂದಿದೆ, ಉಳಿದವು ಓವರ್ಹೆಡ್ ಆಗಿದೆ. ನನ್ನ ನಿರ್ದಿಷ್ಟ ಸಂದರ್ಭದಲ್ಲಿ, ಥ್ರೆಡ್ ಸುರಕ್ಷತೆಯು ಸ್ಪಷ್ಟವಾಗಿ ಮಿತಿಮೀರಿದೆ, ಆದ್ದರಿಂದ ನಾನು fwrite ಕರೆಯನ್ನು ಬದಲಿಸುವ ಮೂಲಕ ಅದನ್ನು ತ್ಯಾಗ ಮಾಡುತ್ತೇನೆ _fwrite_nolock - ನೀವು ವಾದಗಳೊಂದಿಗೆ ಬುದ್ಧಿವಂತರಾಗಿರಬೇಕಾಗಿಲ್ಲ. ಒಟ್ಟು: ಈ ಸರಳ ಕುಶಲತೆಯು ಫಲಿತಾಂಶವನ್ನು ರೆಕಾರ್ಡಿಂಗ್ ಮಾಡುವ ವೆಚ್ಚವನ್ನು ಗಮನಾರ್ಹವಾಗಿ ಕಡಿಮೆ ಮಾಡುತ್ತದೆ, ಇದು ಮೂಲ ಆವೃತ್ತಿಯಲ್ಲಿ ಸುಮಾರು ಅರ್ಧದಷ್ಟು ಸಮಯವನ್ನು ಕಳೆದಿದೆ. ಅಂದಹಾಗೆ, POSIX ಜಗತ್ತಿನಲ್ಲಿ ಇದೇ ರೀತಿಯ ಕಾರ್ಯವಿದೆ - fwrite_unlocked. ಸಾಮಾನ್ಯವಾಗಿ ಹೇಳುವುದಾದರೆ, ಅದೇ ಫ್ರೆಡ್ಗೆ ಅನ್ವಯಿಸುತ್ತದೆ. ಹೀಗಾಗಿ, # ಡಿಫೈನ್‌ಗಳ ಜೋಡಿಯನ್ನು ಬಳಸಿ, ಅನಗತ್ಯ ಲಾಕ್‌ಗಳಿಲ್ಲದೆಯೇ ನೀವು ಸಂಪೂರ್ಣವಾಗಿ ಅಡ್ಡ-ಪ್ಲಾಟ್‌ಫಾರ್ಮ್ ಪರಿಹಾರವನ್ನು ಪಡೆಯಬಹುದು (ಮತ್ತು ಇದು ಆಗಾಗ್ಗೆ ಸಂಭವಿಸುತ್ತದೆ).

fwrite, _fwrite_nolock, setvbuf

ಮೂಲ ಯೋಜನೆಯಿಂದ ದೂರ ಸರಿದು ನಿರ್ದಿಷ್ಟ ಪ್ರಕರಣವನ್ನು ಪರೀಕ್ಷಿಸುವತ್ತ ಗಮನಹರಿಸೋಣ: ದೊಡ್ಡ ಫೈಲ್ (512 MB) ಅನ್ನು ಅತ್ಯಂತ ಸಣ್ಣ ಭಾಗಗಳಲ್ಲಿ ಬರೆಯುವುದು - ತಲಾ ಒಂದು ಬೈಟ್. ಪರೀಕ್ಷಾ ವ್ಯವಸ್ಥೆ: AMD Ryzen 7 1700, 16 GB RAM, 7200 rpm HDD, 64 MB ಸಂಗ್ರಹ. Windows 10 1809 ರಲ್ಲಿ, ಬೈನರಿಯನ್ನು 32-ಬಿಟ್ ಆಗಿ ನಿರ್ಮಿಸಲಾಯಿತು, ಆಪ್ಟಿಮೈಸೇಶನ್‌ಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ, ಲೈಬ್ರರಿಯನ್ನು ಸ್ಥಿರವಾಗಿ ಲಿಂಕ್ ಮಾಡಲಾಗಿದೆ.

ಪ್ರಯೋಗಕ್ಕಾಗಿ ಮಾದರಿ:


#include <chrono>
#include <cstdio>
#include <inttypes.h>
#include <memory>

#ifdef _MSC_VER
#define fwrite_unlocked _fwrite_nolock
#endif

using namespace std::chrono;

int main()
{
    std::unique_ptr<FILE, int(*)(FILE*)> file(fopen("test.bin", "wb"), fclose);
    if (!file)
        return 1;

    constexpr size_t TEST_BUFFER_SIZE = 256 * 1024;
    if (setvbuf(file.get(), nullptr, _IOFBF, TEST_BUFFER_SIZE) != 0)
        return 2;

    auto start = steady_clock::now();
    const uint8_t b = 77;
    constexpr size_t TEST_FILE_SIZE = 512 * 1024 * 1024;
    for (size_t i = 0; i < TEST_FILE_SIZE; ++i)
        fwrite_unlocked(&b, 1, sizeof(b), file.get());

    auto end = steady_clock::now();
    auto interval = duration_cast<microseconds>(end - start);
    printf("Time: %lldn", interval.count());

    return 0;
}

ವೇರಿಯೇಬಲ್‌ಗಳು TEST_BUFFER_SIZE ಆಗಿರುತ್ತದೆ ಮತ್ತು ಒಂದೆರಡು ಸಂದರ್ಭಗಳಲ್ಲಿ ನಾವು fwrite_unlocked ಅನ್ನು fwrite ನೊಂದಿಗೆ ಬದಲಾಯಿಸುತ್ತೇವೆ. ಬಫರ್ ಗಾತ್ರವನ್ನು ಸ್ಪಷ್ಟವಾಗಿ ಹೊಂದಿಸದೆಯೇ fwrite ಕೇಸ್‌ನೊಂದಿಗೆ ಪ್ರಾರಂಭಿಸೋಣ (setvbuf ಮತ್ತು ಸಂಬಂಧಿತ ಕೋಡ್ ಅನ್ನು ಕಾಮೆಂಟ್ ಮಾಡಿ): ಸಮಯ 27048906 µs, ಬರೆಯುವ ವೇಗ - 18.93 MB/s. ಈಗ ಬಫರ್ ಗಾತ್ರವನ್ನು 64 KB ಗೆ ಹೊಂದಿಸೋಣ: ಸಮಯ - 25037111 μs, ವೇಗ - 20.44 Mb / s. ಈಗ setvbuf ಗೆ ಕರೆ ಮಾಡದೆಯೇ _fwrite_nolock ನ ಕಾರ್ಯಾಚರಣೆಯನ್ನು ಪರೀಕ್ಷಿಸೋಣ: 7262221 µs, ವೇಗ - 70.5 Mb/s!

ಮುಂದೆ, ಬಫರ್ ಗಾತ್ರವನ್ನು ಪ್ರಯೋಗಿಸೋಣ (setvbuf):

ಹೆಚ್ಚು ಶ್ರಮವಿಲ್ಲದೆ C/C++ ಫೈಲ್ I/O ಅನ್ನು ವೇಗಗೊಳಿಸುವುದು

ಸರಾಸರಿ 5 ಪ್ರಯೋಗಗಳ ಮೂಲಕ ಡೇಟಾವನ್ನು ಪಡೆಯಲಾಗಿದೆ; ದೋಷಗಳನ್ನು ಲೆಕ್ಕಾಚಾರ ಮಾಡಲು ನಾನು ತುಂಬಾ ಸೋಮಾರಿಯಾಗಿದ್ದೆ. ನನ್ನ ಪ್ರಕಾರ, ನಿಯಮಿತ HDD ಗೆ 93 ಬೈಟ್ ಅನ್ನು ಬರೆಯುವಾಗ 1 MB/s ಉತ್ತಮ ಫಲಿತಾಂಶವಾಗಿದೆ, ನೀವು ಸೂಕ್ತವಾದ ಬಫರ್ ಗಾತ್ರವನ್ನು ಆರಿಸಬೇಕಾಗುತ್ತದೆ (ನನ್ನ ಸಂದರ್ಭದಲ್ಲಿ, 256 KB ಸರಿಯಾಗಿದೆ) ಮತ್ತು fwrite ಅನ್ನು _fwrite_nolock/fwrite_unlocked ( ಥ್ರೆಡ್ ಸುರಕ್ಷತೆ ಅಗತ್ಯವಿಲ್ಲದಿದ್ದರೆ, ಸಹಜವಾಗಿ).
ಅದೇ ರೀತಿಯ ಪರಿಸ್ಥಿತಿಗಳಲ್ಲಿ ಫ್ರೆಡ್ನೊಂದಿಗೆ. ನನ್ನ ಕೈಯಲ್ಲಿ ಲಿನಕ್ಸ್‌ನೊಂದಿಗೆ ಹಾರ್ಡ್‌ವೇರ್ ಯಂತ್ರವಿಲ್ಲದ್ದರಿಂದ (ಸಿಂಗಲ್-ಬೋರ್ಡ್ ಕಂಪ್ಯೂಟರ್‌ಗಳು ಲೆಕ್ಕಿಸುವುದಿಲ್ಲ), ನಾನು ವರ್ಚುವಲ್ ಗಣಕದಲ್ಲಿ ಸೀಮಿತ ಪ್ರಯೋಗವನ್ನು ನಡೆಸಲು ನಿರ್ಧರಿಸಿದೆ (ಹೈಪರ್-ವಿ, ಓಪನ್‌ಸುಸ್ 15, ಜಿಸಿಸಿ 8.3.1) - ದಿ ಮಾದರಿಯು ತಾತ್ವಿಕವಾಗಿ ಒಂದೇ ಆಗಿರುತ್ತದೆ: "ನೇಕೆಡ್" ಫ್ರೈಟ್ 20 Mb/s, fwrite + 256 KB ಬಫರ್ 23 Mb/s ಅನ್ನು ಉತ್ಪಾದಿಸಿತು, ಅದೇ ಬಫರ್‌ನೊಂದಿಗೆ fwrite_unlocked - 35 Mb/s (64-ಬಿಟ್ ಬೈನರಿ, ಜೋಡಿಸಲಾದ g++ -o2 - s -static-libgcc -static-libstdc++ fwrite_test. cpp -o fwrite_test).

ನಂತರದ

ಈ ಲೇಖನವನ್ನು ಬರೆಯುವ ಉದ್ದೇಶವು ಅನೇಕ ಸಂದರ್ಭಗಳಲ್ಲಿ ಸರಳ ಮತ್ತು ಪರಿಣಾಮಕಾರಿ ತಂತ್ರವನ್ನು ವಿವರಿಸುವುದು (ನಾನು ಮೊದಲು _fwrite_nolock/fwrite_unlocked ಕಾರ್ಯಗಳನ್ನು ಕಂಡಿಲ್ಲ, ಅವು ಹೆಚ್ಚು ಜನಪ್ರಿಯವಾಗಿಲ್ಲ - ಆದರೆ ವ್ಯರ್ಥವಾಗಿದೆ). ವಸ್ತುವು ಹೊಸದು ಎಂದು ನಾನು ನಟಿಸುವುದಿಲ್ಲ, ಆದರೆ ಲೇಖನವು ಸಮುದಾಯಕ್ಕೆ ಉಪಯುಕ್ತವಾಗಿದೆ ಎಂದು ನಾನು ಭಾವಿಸುತ್ತೇನೆ.

ಮೂಲ: www.habr.com

DDoS ರಕ್ಷಣೆ, VPS VDS ಸರ್ವರ್‌ಗಳೊಂದಿಗೆ ಸೈಟ್‌ಗಳಿಗೆ ವಿಶ್ವಾಸಾರ್ಹ ಹೋಸ್ಟಿಂಗ್ ಅನ್ನು ಖರೀದಿಸಿ 🔥 DDoS ರಕ್ಷಣೆ, VPS VDS ಸರ್ವರ್‌ಗಳೊಂದಿಗೆ ವಿಶ್ವಾಸಾರ್ಹ ವೆಬ್‌ಸೈಟ್ ಹೋಸ್ಟಿಂಗ್ ಅನ್ನು ಖರೀದಿಸಿ | ProHoster