Быстрый и очень быстрый ввод-вывод

Ввод-вывод в 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 Ускорение ввода В особо редких случаях (почти никогда)