Почему этот код выводит другой результат в Windows и Linux? - PullRequest
0 голосов
/ 19 января 2019

Этот код печатает различную строку между Windows и Linux.

test.py:

print(";".join([str(i) for i in range(10000)]))

Платформа: x86_64 Linux 4.4 .0-17763 - Microsoft
Версия Python:3.7.2
Терминалы: bash, fish

Сокращенный вывод:

$ python --version
Python 3.7.2
$ python test.py
0;1;2;3;4;5;6....9997;9998;9999
$ python -u test.py
0;1;2;3;4;5;6....9997;9998;9999

Платформа: Windows 10 1809
Версия Python: 3.6.8, 3.7.0, 3.7.2
Терминалы: cmd, powershell

Сокращенный вывод:

./python --version
Python 3.6.8
./python test.py
0;1;2;3;4;5;6....9997;9998;9999
./python -u test.py
0;1;2;3;4;5;6....2663;2664;2665;26
./python --version
Python 3.7.0
./python test.py
0;1;2;3;4;5;6....9997;9998;9999
./python -u test.py
0;1;2;3;4;5;6....2663;2664;2665;26
./python --version
Python 3.7.2
./python test.py
0;1;2;3;4;5;6....9997;9998;9999
./python -u test.py
0;1;2;3;4;5;6....2663;2664;2665;26

Так почему в Windows аргумент -u вызывает усечение вывода?(только от 0 до 2666)?
(При использовании python -u test.py > a.txt для перенаправления вывода в файл, он работает правильно.)

Может быть что-то с буферизацией?

1 Ответ

0 голосов
/ 20 января 2019

Размер записи консоли через WINAPI WriteFile и WriteConsoleW задокументирован для неопределенного ограничения, как указано ниже:

nNumberOfCharsToWrite [in]
Количество символов для записи.Если общий размер указанного числа символов превышает доступную кучу, функция завершается ошибкой с ERROR_NOT_ENOUGH_MEMORY .

Не задокументировано, к какой «куче» это относится.Процесс может иметь несколько куч разных размеров (фиксированных или динамических).Реализация собственной кучи в библиотеке времени выполнения NT (например, RtlCreateHeap) может создать кучу по указанному адресу, что обеспечивает удобный доступ к памяти, которая используется совместно с другими процессами.Использование общей кучи часто сочетается с портами Local Inter-Process Communication (LPC) - или с асинхронным LPC в NT 6.0+.Порты LPC используются для передачи сообщений между приложениями и системными службами, такими как диспетчер сеансов (smss.exe), диспетчер управления службами (services.exe), локальный центр безопасности (lsass.exe), сервер сеансов рабочего стола (csrss.exe).и экземпляры хоста консоли (conhost.exe).Сообщения, помещенные в очередь непосредственно на порт LPC, ограничены 256 байтами.Большие сообщения передаются путем помещения в очередь сообщений в порт, который ссылается на разделяемую память.

Оказывается, что старая реализация консоли (до NT 6.3) использует LPC в качестве канала ввода-вывода, и выше-компонентная куча составляет всего 64 КиБ .Это был своеобразный выбор дизайна.Я думаю, что кто-то слишком много выпил из подсистемы пользовательского режима - передачи сообщений Kool-Aid.Правильный ввод / вывод NT использует устройство с системными службами ввода / вывода, включая NtCreateFile, NtReadFile, NtWriteFile и NtDeviceIoControlFile.

Консольное приложение не знает, сколько из этогокуча доступна для записи.Python может начинаться с 64 КиБ и работать в обратном направлении, но его raw file I / O требует одного системного вызова на вызов.Вместо этого он записывает в 32 КиБ, что должно быть успешным.Это ограничение позволяет писать строки широких символов с 16K кодовыми точками UTF-16.Сложность заключается в том, что в стеке ввода-вывода консоли используется UTF-8 версии 3.6+, который необходимо декодировать с помощью MultiByteToWideChar.В настоящее время он просто многократно делит буфер UTF-8 пополам, пока длина не станет меньше 16K.Таким образом, в примере вопроса написание 48 889 символов уменьшается вдвое до 24 444 символов и снова уменьшается вдвое до 12 222 символов.(IMO, было бы лучше попытаться записать до 16K кодовых точек; получить фактически записанное число и вызвать WideCharToMultiByte на подстроке, чтобы определить количество записанных байтов UTF-8. В текущем проекте действительно есть ошибка, еслиUTF-8 2-4-байтовая последовательность перекрывает точку вырезания.)

В NT 6.3+ (Windows 8.1+) консольный ввод-вывод не имеет такого ограничения размера, поскольку он использует устройство ConDrv и I /O системные вызовы вместо LPC.Однако не стоит специально помещать код в код только для поддержки небуферизованного стека ввода-вывода текста, как настроено параметром командной строки -u.Мы ожидаем, что интерактивный консольный ввод / вывод будет буферизован.Текстовый ввод / вывод без буферизации фактически запрещен обычным вызовом open.Например:

>>> open('conout$', 'w', buffering=0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: can't have unbuffered text I/O

Расширенная поддержка Windows 7 заканчивается 14 января 2020 года, поэтому Python 3.8 будет последней версией, которая его поддержит.Предел записи консоли должен быть удален в Python 3.9.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...