Быстрый и очень быстрый ввод-вывод
Оглавление
Ввод-вывод в C++
В этой лекции мы подробно разберем детали ввода-вывода и размберем, почему его нужно оптимизировать.
std::endl
Операция вывода в операционной системе - это дорогая операция. Поэтому если бы программа в реальности выводила всё посимвольно, то вывод бы замедлился в 10-20 раз!
Поэтому любой язык программирования, включая C++, использует буферизацию вывода. Это значит, что вывод не происходит сразу, а сначала записывается в буфер фиксированного размера, а когда буфер заполняется, то он выводится на экран.
Однако в данном подходе есть существенная проблема с точки зрения user-experience: если вы дебажите программу и выводите какую-то информацию, то она не появится на экране, пока буфер не заполнится.
Поэтому в C++ есть специальный символ std::endl
, который не только выводит перевод строки, но и сбрасывает буфер вывода (альтернативно есть отдельная команда для сброса буфера cin.flush()
). Это значит, что все, что было записано в буфер, будет выведено на экран.
1
std::cout << "Hello, world!" << std::endl;
Но если вам не нужно сбрасывать буфер (обычно это так), то используйте символ перевода строки \n
, иначе ваша программа будет работать существенно медленнее:
1
2
std::cout << "Hello, world!\n";
std::cout << int(1e9) << '\n';
ios_base::sync_with_stdio(false)
С++ - это надстройка над языком C, поэтому в нем есть возможность использовать функции ввода-вывода из языка C (scanf
и printf
, читают из стандартных потоков ввода-вывода stdin
и stdout
соответственно). Однако, в C++ есть свои собственные методы ввода-вывода cin
и cout
- потоки ввода и вывода соответственно.
C++ по умолчанию позволяет использовать оба метода ввода-вывода, в одной и той же программе, но одновременное использование обоих методов - это плохой паттерн (как в олимпиадах, так и в промышленном программировании). Более того, использование одновременно обоих методов ввода-вывода заставляет их синхронизироваться друг с другом, что замедляет программу. Синхронизация по умолчанию включена (даже если вы используете только один метод), чтобы отключить ее, используйте следующую строку кода:
1
std::ios_base::sync_with_stdio(false);
cin.tie(NULL)
cin
и cout
- это объекты класса istream
и ostream
соответственно. Они связаны друг с другом блокировкой: если вы в программе позвали сначала ввод, а затем вывод, то ввод будет сначала считан, а затем выведен. Это может замедлить программу. Чтобы отключить эту блокировку, используйте следующую строку кода:
1
std::cin.tie(NULL);
no checks + batch read
В олимпиадном программировани формат ввода-вывода фиксирован, а ошибок в нём не бывает. C++ же при каждом вводе целого числа проверят, корректно ли оно. Это замедляет программу. Бонусом мы можем считывать не по одному числу, а сразу большой кусок данных, чтобы уменьшить количество вызовов операции чтения, которые дорогостоящие сами по себе.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const int BUF_SIZE = 4096;
int readChar() {
static unsigned char buf[BUF_SIZE];
static int buf_pos = 0, buf_len = 0;
if (buf_pos == buf_len) {
buf_pos = 0;
buf_len = fread(buf, 1, BUF_SIZE, stdin);
if (!buf_len)
return -1;
}
return buf[buf_pos++];
}
template<class T = int>
T readInt() {
T x = 0;
int c;
while ((c = readChar()) <= 32)
;
while (c > 32)
x = x * 10 + c - '0', c = readChar();
return x;
}
int main() {
ios_base::sync_with_stdio(false);
int n = readInt();
for (int i = 0; i < n; ++i) {
int x = readInt();
}
cout << n;
}
Данный код всегда можно найти среди TL посылок у Burunduk1.
TLDR
Метод | Зачем нужен | Когда использовать |
---|---|---|
std::endl |
Сброс буфера вывода | Использовать только дня дебага, в остальных случаях \n |
std::ios_base::sync_with_stdio(false) |
Отключение синхронизации cin и stdin |
Всегда |
std::cin.tie(NULL) |
Отключение блокировки cin и cout |
Всегда |
no checks + batch read |
Ускорение ввода | В особо редких случаях (почти никогда) |