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

ಸರಾಸರಿ 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
