<p>запросы имеют низкую производительность потоковая передача больших двоичных ответов</p>

Созданный на 5 дек. 2014  ·  40Комментарии  ·  Источник: psf/requests

https://github.com/alex/http-client-bench содержит тесты, которые я использовал.

Результаты примерно такие:

| | запросы / http | розетка |
| --- | --- | --- |
| CPython | 12 МБ / с | 200 МБ / с |
| PyPy | 80 МБ / с | 300 МБ / с |
| Вперед | 150 МБ / с | н / д |

запросы накладывают значительные накладные расходы по сравнению с сокетом, особенно на CPython.

Propose Close

Все 40 Комментарий

Эти накладные расходы неожиданно велики. Однако избежать этого может быть сложно.

Большая проблема в том, что мы выполняем довольно много обработки для каждого фрагмента. Это все вниз по стеку: запросы, urllib3 и httplib. Было бы чрезвычайно интересно посмотреть, на что тратится время, чтобы выяснить, кто является причиной неэффективности.

думаю, следующим шагом будет попытка профилирования httplib / urllib3, чтобы увидеть
производительность там?

Кевин Берк
телефон: 925.271.7005 | twentymilliseconds.com

Четверг, 4 декабря 2014 г., 17:01, Кори Бенфилд [email protected]
написал:

Эти накладные расходы неожиданно велики. Однако избежать этого может быть сложно.

Большая проблема в том, что мы выполняем довольно много обработки для каждого фрагмента. Это
полностью вниз по стеку: запросы, urllib3 и httplib. Это было бы
очень интересно посмотреть, где тратится время, чтобы выяснить, кто
вызывает неэффективность.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65732050
.

Только что запустил тесты с urllib3:

PyPy: 120 МБ / с
CPython: 70 МБ / с.

И я повторно запустил CPython + запросы: 35 МБ / с

(Моя машина, кажется, испытывает немного шума в тестах, если бы у кого-то была более тихая система, на которой они могли бы это, это было бы здорово)

Я пробовал запускать их на своей машине после выключения всех остальных
приложение и окно терминала, а также получил изрядный шум -
Тест сокетов был от 30 до 460 МБ / с.

Кевин Берк
телефон: 925.271.7005 | twentymilliseconds.com

Четверг, 4 декабря 2014 г., 21:24, Алекс Гейнор [email protected]
написал:

Только что запустил тесты с urllib3:

PyPy: 120 МБ / с
CPython: 70 МБ / с.

И я повторно запустил CPython + запросы: 35 МБ / с

(Моя машина, кажется, испытывает немного шума в тестах, если
у кого-то есть более тихая система, на которой они могут это, это было бы здорово)

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65748982
.

Я упростил выполнение тестов, поэтому, надеюсь, другие люди смогут проверить мои цифры:

CPython:

BENCH SOCKET:
   8GiB 0:00:22 [ 360MiB/s] [======================================================>] 100%
BENCH HTTPLIB:
   8GiB 0:02:34 [53.1MiB/s] [======================================================>] 100%
BENCH URLLIB3:
   8GiB 0:01:30 [90.2MiB/s] [======================================================>] 100%
BENCH REQUESTS
   8GiB 0:01:30 [90.7MiB/s] [======================================================>] 100%
BENCH GO HTTP
   8GiB 0:00:26 [ 305MiB/s] [======================================================>] 100%

PyPy:

BENCH SOCKET:
   8GiB 0:00:22 [ 357MiB/s] [======================================================>] 100%
BENCH HTTPLIB:
   8GiB 0:00:43 [ 189MiB/s] [======================================================>] 100%
BENCH URLLIB3:
   8GiB 0:01:07 [ 121MiB/s] [======================================================>] 100%
BENCH REQUESTS
   8GiB 0:01:09 [ 117MiB/s] [======================================================>] 100%
BENCH GO HTTP
   8GiB 0:00:26 [ 307MiB/s] [======================================================>] 100%

Эээ ... эти цифры странные. Httplib CPython медленнее запросов или urllib3, хотя обе библиотеки используют httplib? Это просто не может быть правдой.

Они воспроизводятся стабильно для меня - можете ли вы попробовать тесты и посмотреть,
можно воспроизвести? Предполагая, что вы можете, видите ли вы что-нибудь не так с
тесты?

Пятница, 5 декабря 2014 г., 11:16:45 Кори Бенфилд [email protected]
написал:

Эээ ... эти цифры странные. Httplib CPython медленнее, чем запросы или
urllib3, хотя обе библиотеки используют httplib? Это просто не может быть правдой.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65821989
.

Я просто хватаю заведомо тихую машину. Должно пройти несколько минут, чтобы стать доступным, потому что это физический ящик, который необходимо установить (боже, я люблю MAAS).

CPython 2.7.8

BENCH SOCKET:
   8GiB 0:00:26 [ 309MiB/s] [================================>] 100%
BENCH HTTPLIB:
   8GiB 0:02:24 [56.5MiB/s] [================================>] 100%
BENCH URLLIB3:
   8GiB 0:01:42 [79.7MiB/s] [================================>] 100%
BENCH REQUESTS
   8GiB 0:01:45 [77.9MiB/s] [================================>] 100%
BENCH GO HTTP
   8GiB 0:00:27 [ 297MiB/s] [================================>] 100%

Для чего это стоит:

Этот патч , CPython 3.4.2 :

BENCH SOCKET:
   8GiB 0:00:27 [ 302MiB/s] [================================>] 100%
BENCH HTTPLIB:
   8GiB 0:00:53 [ 151MiB/s] [================================>] 100%
BENCH URLLIB3:
   8GiB 0:00:54 [ 149MiB/s] [================================>] 100%
BENCH REQUESTS
   8GiB 0:00:56 [ 144MiB/s] [================================>] 100%
BENCH GO HTTP
   8GiB 0:00:31 [ 256MiB/s] [================================>] 100%

Вы должны получить тот же эффект на Python2 с помощью
env PYTHONUNBUFFERED= или флаг -u .

Пятница, 5 декабря 2014 г., 11:42:36 Кори Фаруэлл [email protected]
написал:

Для чего это стоит:

Этот патч https://gist.github.com/frewsxcv/1c0f3c81cda508e1bca9 , CPython
3.4.2:

РОЗЕТКА ДЛЯ СКАМЬИ:
8 ГБ 0:00:27 [302 МБ / с] [==================================>] 100%
СТАЦИОНАРНЫЙ HTTPLIB:
8 ГБ 0:00:53 [151 МБ / с] [================================>] 100%
СКАМЬЯ URLLIB3:
8 ГБ 0:00:54 [149 МБ / с] [==================================>] 100%
ЗАПРОСЫ НА СКАМЬЮ
8 ГБ 0:00:56 [144 МБ / с] [================================>] 100%
BENCH GO HTTP
8 ГБ 0:00:31 [256 МБ / с] [==================================>] 100%

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65826239
.

@alex Интересно, что ни env PYTHONUNBUFFERED= ни -u имеют одинакового эффекта на Python 2. Результаты от моей машины.

Хорошо, приведенные ниже данные поступают с машины, которая ничего не делает, кроме выполнения этих тестов. Последний тест был запущен с установленным флагом Python -u , и, как вы можете видеть, этот флаг не действует.

Python 2.7.6
go version go1.2.1 linux/amd64
BENCH SOCKET:
   8GiB 0:00:16 [ 500MiB/s] [================================>] 100%
BENCH HTTPLIB:
   8GiB 0:01:32 [88.6MiB/s] [================================>] 100%
BENCH URLLIB3:
   8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BENCH REQUESTS
   8GiB 0:01:21 [ 100MiB/s] [================================>] 100%
BENCH GO HTTP
   8GiB 0:00:21 [ 385MiB/s] [================================>] 100%
Python 2.7.6
go version go1.2.1 linux/amd64
BENCH SOCKET:
   8GiB 0:00:16 [ 503MiB/s] [================================>] 100%
BENCH HTTPLIB:
   8GiB 0:01:33 [87.8MiB/s] [================================>] 100%
BENCH URLLIB3:
   8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BENCH REQUESTS
   8GiB 0:01:22 [99.3MiB/s] [================================>] 100%
BENCH GO HTTP
   8GiB 0:00:20 [ 391MiB/s] [================================>] 100%
Python 2.7.6
go version go1.2.1 linux/amd64
BENCH SOCKET:
   8GiB 0:00:16 [ 506MiB/s] [================================>] 100%
BENCH HTTPLIB:
   8GiB 0:01:31 [89.1MiB/s] [================================>] 100%
BENCH URLLIB3:
   8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BENCH REQUESTS
   8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BENCH GO HTTP
   8GiB 0:00:21 [ 389MiB/s] [================================>] 100%

Эти числа чрезвычайно стабильны и показывают следующие особенности:

  1. Чтение сырых сокетов происходит быстро (да).
  2. Go составляет около 80% скорости чтения сырого сокета.
  3. urllib3 составляет около 20% скорости чтения сырого сокета.
  4. запросы немного медленнее, чем urllib3, что имеет смысл, поскольку мы добавляем пару кадров стека для передачи данных.
  5. httplib медленнее, чем запросы / urllib3. Это просто невозможно, и я подозреваю, что мы должны настраивать httplib или библиотеку сокетов так, как httplib - нет.

FWIW, я только что слил добавление buffering=True из @kevinburke , пробежки
включить это?

Пятница, 5 декабря 2014 г., 12:04:40 Кори Бенфилд [email protected]
написал:

Хорошо, приведенные ниже данные поступают с машины, которая больше ничего не делает.
но запустив эти тесты. Последний тест был запущен с флагом Python -u
установлен, и, как видите, этот флаг не действует.

Python 2.7.6
версия go1.2.1 linux / amd64
РОЗЕТКА ДЛЯ СКАМЬИ:
8 ГБ 0:00:16 [500 МБ / с] [==================================>] 100%
СТАЦИОНАРНЫЙ HTTPLIB:
8 ГБ 0:01:32 [88,6 МБ / с] [================================>] 100%
СКАМЬЯ URLLIB3:
8 ГБ 0:01:20 [101 МБ / с] [==================================>] 100%
ЗАПРОСЫ НА СКАМЬЮ
8 ГБ 0:01:21 [100 МБ / с] [================================>] 100%
BENCH GO HTTP
8 ГБ 0:00:21 [385 МБ / с] [================================>] 100%

Python 2.7.6
версия go1.2.1 linux / amd64
РОЗЕТКА ДЛЯ СКАМЬИ:
8 ГБ 0:00:16 [503 МБ / с] [==================================>] 100%
СТАЦИОНАРНЫЙ HTTPLIB:
8 ГБ 0:01:33 [87,8 МБ / с] [================================>] 100%
СКАМЬЯ URLLIB3:
8 ГБ 0:01:20 [101 МБ / с] [==================================>] 100%
ЗАПРОСЫ НА СКАМЬЮ
8 ГБ 0:01:22 [99,3 МБ / с] [================================>] 100%
BENCH GO HTTP
8 ГБ 0:00:20 [391 МБ / с] [==================================>] 100%

Python 2.7.6
версия go1.2.1 linux / amd64
РОЗЕТКА ДЛЯ СКАМЬИ:
8 ГБ 0:00:16 [506 МБ / с] [==================================>] 100%
СТАЦИОНАРНЫЙ HTTPLIB:
8 ГБ 0:01:31 [89,1 МБ / с] [================================>] 100%
СКАМЬЯ URLLIB3:
8 ГБ 0:01:20 [101 МБ / с] [==================================>] 100%
ЗАПРОСЫ НА СКАМЬЮ
8 ГБ 0:01:20 [101 МБ / с] [==================================>] 100%
BENCH GO HTTP
8 ГБ 0:00:21 [389 МБ / с] [==================================>] 100%

Эти числа чрезвычайно стабильны и показывают следующие особенности:

  1. Чтение сырых сокетов происходит быстро (да).
  2. Go составляет около 80% скорости чтения сырого сокета.
  3. urllib3 составляет около 20% скорости чтения сырого сокета.
  4. запросы немного медленнее, чем urllib3, что имеет смысл, поскольку мы
    добавьте пару кадров стека для передачи данных.
  5. httplib медленнее, чем запросы / urllib3. Это просто невозможно,
    и я подозреваю, что мы должны настраивать httplib или библиотеку сокетов в
    способ, которым httplib не является.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65829335
.

Кори - см. Последнюю версию клиента скамейки, которая включает
buffering = True в httplib (как и запросы / urllib3)

Кевин Берк
телефон: 925.271.7005 | twentymilliseconds.com

Пятница, 5 декабря 2014 г., 10:04, Кори Бенфилд [email protected]
написал:

Хорошо, приведенные ниже данные поступают с машины, которая больше ничего не делает.
но запустив эти тесты. Последний тест был запущен с флагом Python -u
установлен, и, как видите, этот флаг не действует.

Python 2.7.6
версия go1.2.1 linux / amd64
РОЗЕТКА ДЛЯ СКАМЬИ:
8 ГБ 0:00:16 [500 МБ / с] [==================================>] 100%
СТАЦИОНАРНЫЙ HTTPLIB:
8 ГБ 0:01:32 [88,6 МБ / с] [================================>] 100%
СКАМЬЯ URLLIB3:
8 ГБ 0:01:20 [101 МБ / с] [==================================>] 100%
ЗАПРОСЫ НА СКАМЬЮ
8 ГБ 0:01:21 [100 МБ / с] [================================>] 100%
BENCH GO HTTP
8 ГБ 0:00:21 [385 МБ / с] [================================>] 100%

Python 2.7.6
версия go1.2.1 linux / amd64
РОЗЕТКА ДЛЯ СКАМЬИ:
8 ГБ 0:00:16 [503 МБ / с] [==================================>] 100%
СТАЦИОНАРНЫЙ HTTPLIB:
8 ГБ 0:01:33 [87,8 МБ / с] [================================>] 100%
СКАМЬЯ URLLIB3:
8 ГБ 0:01:20 [101 МБ / с] [==================================>] 100%
ЗАПРОСЫ НА СКАМЬЮ
8 ГБ 0:01:22 [99,3 МБ / с] [================================>] 100%
BENCH GO HTTP
8 ГБ 0:00:20 [391 МБ / с] [==================================>] 100%

Python 2.7.6
версия go1.2.1 linux / amd64
РОЗЕТКА ДЛЯ СКАМЬИ:
8 ГБ 0:00:16 [506 МБ / с] [==================================>] 100%
СТАЦИОНАРНЫЙ HTTPLIB:
8 ГБ 0:01:31 [89,1 МБ / с] [================================>] 100%
СКАМЬЯ URLLIB3:
8 ГБ 0:01:20 [101 МБ / с] [==================================>] 100%
ЗАПРОСЫ НА СКАМЬЮ
8 ГБ 0:01:20 [101 МБ / с] [==================================>] 100%
BENCH GO HTTP
8 ГБ 0:00:21 [389 МБ / с] [==================================>] 100%

Эти числа чрезвычайно стабильны и показывают следующие особенности:

  1. Чтение сырых сокетов происходит быстро (да).
  2. Go составляет около 80% скорости чтения сырого сокета.
  3. urllib3 составляет около 20% скорости чтения сырого сокета.
  4. запросы немного медленнее, чем urllib3, что имеет смысл, поскольку мы
    добавьте пару кадров стека для передачи данных.
  5. httplib медленнее, чем запросы / urllib3. Это просто невозможно,
    и я подозреваю, что мы должны настраивать httplib или библиотеку сокетов в
    способ, которым httplib не является.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65829335
.

Да, это исправляет поведение производительности httplib, чтобы сделать его более понятным.

Новые результаты и выводы:

Python 2.7.6
go version go1.2.1 linux/amd64
BENCH SOCKET:
   8GiB 0:00:16 [ 499MiB/s] [================================>] 100%
BENCH HTTPLIB:
   8GiB 0:01:12 [ 113MiB/s] [================================>] 100%
BENCH URLLIB3:
   8GiB 0:01:21 [ 100MiB/s] [================================>] 100%
BENCH REQUESTS
   8GiB 0:01:20 [ 101MiB/s] [================================>] 100%
BENCH GO HTTP
   8GiB 0:00:20 [ 391MiB/s] [================================>] 100%
  1. Чтение сырых сокетов происходит быстро (да).
  2. Go составляет около 80% скорости чтения сырого сокета.
  3. httplib составляет чуть менее 25% скорости чтения сырого сокета.
  4. urllib3 составляет примерно 20% от скорости чтения сырого сокета, добавляя небольшие накладные расходы на httplib.
  5. запросы немного медленнее, чем urllib3, что имеет смысл, поскольку мы добавляем пару кадров стека для передачи данных.

Итак, возможно, реальная стоимость здесь httplib. Чтобы ускорить это, нужно убрать с пути httplib.

Мне интересно выяснить, какая часть httplib нам стоит. Я думаю, что профилирование bench_httplib.py - хороший следующий шаг.

Я исключил преобразование сокета в файловый объект с помощью socket.makefile , добавив эту строку в тест bench_socket.py , который совсем не замедляет его. Как ни странно, кажется, что это делает его быстрее.

Ответ почти наверняка заключается в кодировании передачи: обработка фрагментов.
См .: https://github.com/alex/http-client-bench/pull/6 , переключившись на
Content-Length на сервере дает неожиданные результаты.

Пятница, 5 декабря 2014 г., 12:24:53 Кори Бенфилд [email protected]
написал:

Итак, возможно, реальная стоимость здесь httplib. Для ускорения этого требуется
убираем httplib с дороги.

Мне интересно выяснить, какая часть httplib нам стоит. я
думаю, что профилирование bench_httplib.py - хороший следующий шаг.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65831653
.

Интересно.

Обработка фрагментов почти наверняка является проблемой, и я не очень удивлен, что go справляется с этим лучше, тем более что chunked - это режим HTTP по умолчанию для go.

Однако запросы более быстрые, чем необработанный сокет ... неожиданно!

Стоит отметить одну вещь: если сокет не декодировал фрагментированную кодировку в предыдущих тестах, то он получил несправедливое преимущество, поскольку на самом деле он читал меньше данных, чем другие методы! Все они читали фрагментированные заголовки, а также 8 ГБ данных.

Это приводит к следующему вопросу: думаем ли мы, что все эти методы на самом деле читают один и тот же объем данных?

Да, уровень сокета обманул, он не декодировал фрагментированные метаданные,
и технически читаю немного меньше. Это было как основание для определения того, "как быстро
мы можем читать », чтобы ничего не доказывать.

Пятница, 5 декабря 2014 г., 12:33:10 Кори Бенфилд [email protected]
написал:

Интересно.

Обработка фрагментов почти наверняка является проблемой, и я не совсем
удивлен, что go справляется с этим лучше, тем более, что по умолчанию используется chunked
Режим HTTP на ходу.

Однако запросы более быстрые, чем необработанный сокет ... неожиданно!

Стоит отметить одну вещь: если сокет не декодировал фрагментированную кодировку
в предыдущих тестах он получил несправедливое преимущество, так как на самом деле
чтение меньше данных, чем другие методы! Все они читали
фрагментированные заголовки, а также 8 ГБ данных.

Это приводит к следующему вопросу: думаем ли мы, что все эти методы по-прежнему
действительно читают один и тот же объем данных?

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -65833299
.

Я не удивлюсь, если это связано с размером блока, который мы читаем из сокета за раз.

Торт для @alex за то, что он очень полезен: cake:

@nelhage проанализировал различные примеры (в передаче
кодировка: разделенный регистр) https://gist.github.com/nelhage/dd6490fbc5cfb815f762
результаты. Похоже, в httplib есть ошибка, которая приводит к этому
не всегда считывает из сокета полный кусок.

В понедельник, 8 декабря 2014 г., в 9:05:14 Кеннет Райтц [email protected]
написал:

Торт для @alex https://github.com/alex за то, что он очень полезен [image:
:кекс:]

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -66147998
.

Так что у нас есть ошибка в стандартной библиотеке, которую на самом деле никто не поддерживает? (У @Lukasa есть как минимум 2 набора патчей, открытых более 1 года.) Может быть, я подниму зловоние в список где-нибудь сегодня вечером

Кому-то (я могу добраться до этого, непонятно), вероятно, нужно детализировать с помощью pdb
или что-то в этом роде и выясните, какой именно код генерирует эти 20-байтовые
читает, чтобы мы могли составить хороший отчет об ошибке.

В понедельник, 8 декабря 2014 г., в 9:14:09 Ян Кордаско [email protected]
написал:

Итак, у нас есть ошибка в стандартной библиотеке, которой на самом деле никто не занимается.
поддержание? ( @Lukasa https://github.com/Lukasa имеет как минимум 2 патча
наборы, которые были открыты более 1 года.) Может быть, я подниму вонь в список
где-нибудь сегодня вечером

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/kennethreitz/requests/issues/2371#issuecomment -66149522
.

Я постараюсь уместить это сегодня вечером или завтра, если никто не дойдет до этого.

Итак, есть новости о первопричине? Что порождает эти короткие чтения и насколько ситуация улучшится без них?

@kislyuk Насколько я знаю. Надеюсь, у меня будет время погоняться за ним в эти рождественские каникулы.

Спасибо @Lukasa. Я имею дело с проблемой производительности, когда скорость загрузки фрагментированного ответа с использованием urllib3 / requests намного ниже, чем с curl и другими библиотеками, и пытаюсь понять, является ли это виновником.

Я немного ковырялся с этим. Короткие чтения берутся из функции _read_chunked в httplib.

https://fossies.org/linux/misc/Python-2.7.9.tgz/Python-2.7.9/Lib/httplib.py#l_585

Считывание 2 байтов, по-видимому, происходит в основном из строки 622.

У меня получился немного другой шаблон strace, чем тот, что был опубликован ранее:
recvfrom (3, "400 \ r \ n \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 "..., 8192, 0, NULL, NULL) = 8192
recvfrom (3, "\ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 "..., 54, 0, NULL, NULL) = 54
recvfrom (3, "\ r \ n", 2, 0, NULL, NULL) = 2

Эту закономерность можно объяснить следующим образом:

  • self.fp.readline (строка 591) запускает буферизованное чтение для 8192 байтов (в socket.readline)
  • каждый потребляемый фрагмент составляет 1031 байт (длина фрагмента 5 байтов ("400 \ r \ n") + 1024 байта данных + 2 байта терминатора)
  • мы можем использовать 7 таких фрагментов из 8192 байтов в буфере, что оставляет нам 975 байтов.
  • Затем мы читаем длину следующего блока (5 байтов), который оставляет 970 байтов
  • теперь у нас есть только 970 байтов, которых недостаточно для выполнения текущего блока (1024), поэтому мы возвращаемся в сеть из-за нехватки 54 байтов
  • для этого httplib выполняет sock.read (54) для незавершенных байтов. socket.read в этом случае (с явной длиной) выберет переход в сеть для указанных 54 байтов (вместо буферизации еще 8192)
  • Затем мы переходим к чтению терминатора фрагмента, который составляет 2 байта, и снова это тот же сценарий, что и выше

Затем образец повторится (вернитесь к шагу 1).

FWIW, я обнаружил, что скромное (20% или около того) ускорение может быть достигнуто здесь путем вставки 2-байтового терминатора, считанного в тело фрагмента, то есть вместо этого:

            value.append(self._safe_read(chunk_left)) 
            amt -= chunk_left

        self._safe_read(2)  # toss the CRLF at the end of the chunk

сделайте это вместо этого:

            value.append(self._safe_read(chunk_left + 2)[:-2]) 
            amt -= chunk_left

На самом деле, вероятно, было бы лучше, если бы чтение для 54 байтов могло буферизовать больше байтов, чем 54 (т.е. 8192 байта), что означало бы, что буферизованный сокет не будет пустым, когда дело доходит до 2-байтового чтения.

Далее к этому. Я не уверен, что небольшие чтения являются основным фактором потери пропускной способности (или не на локальном хосте). Я поигрался с размером буфера сокета, так что он был кратен 1031 байту, и, несмотря на то, что strace больше не выполнял небольшие чтения, это не оказало большого влияния на пропускную способность.

Я думаю, что потеря пропускной способности может быть больше связана с тем, как socket.py обрабатывает небольшие чтения. Вот соответствующий код (из socket.read):

https://fossies.org/linux/misc/Python-2.7.9.tgz/Python-2.7.9/Lib/socket.py#l_336

Когда вы передаете явную длину в socket.read и ее можно выполнить из существующих буферизованных данных, тогда это путь кода:

        buf = self._rbuf
        buf.seek(0, 2)  # seek end

        #.....

        # Read until size bytes or EOF seen, whichever comes first
        buf_len = buf.tell()
        if buf_len >= size:
            # Already have size bytes in our buffer?  Extract and return.
            buf.seek(0)
            rv = buf.read(size)
            self._rbuf = StringIO()
            self._rbuf.write(buf.read())
            return rv

Проблема, которую я здесь вижу, заключается в том, что даже двухбайтовое чтение означает копирование непрочитанного остатка в новый StringIO. Похоже, это станет очень дорогим для множества небольших чтений. Если данный StringIO может каким-то образом истощаться при каждом чтении, а не текущий шаблон копирования непрочитанного остатка в новый StringIO, тогда я ожидаю, что это может помочь пропускной способности

@gardenia У меня не было возможности усвоить все это, но большое вам спасибо за ваши усилия и работу здесь. @shazow, возможно, вы найдете исследование @gardenia интересным.

: +1: спасибо @gardenia. Между прочим, мое собственное исследование производительности в моем случае использования показало, что в моем случае ответы не разбиваются на части, но urllib3 выполняет на 20% быстрее, чем запросы, поэтому возникают некоторые накладные расходы, которые я хочу охарактеризовать. По-прежнему соответствует названию проблемы, но с другой основной причиной.

Восхитительно, спасибо, что поделились! :)

Похоже, это отличная цель и для Hyper от @Lukasa .

@alex - Я немного поигрался с упомянутой вами проблемой производительности urllib3 vs requests non-chunked. Я думаю, что вижу такое же снижение запросов на 20%.

В запросах я предположительно пытался заменить вызов self.raw.stream на встроенную реализацию stream () (из urllib3). Казалось, что это значительно приблизило пропускную способность между запросами и urllib3, по крайней мере, на моей машине:

--- requests.repo/requests/models.py    2015-03-06 16:05:52.072509869 +0000
+++ requests/models.py  2015-03-07 20:49:25.618007438 +0000
@@ -19,6 +19,7 @@
 from .packages.urllib3.fields import RequestField
 from .packages.urllib3.filepost import encode_multipart_formdata
 from .packages.urllib3.util import parse_url
+from .packages.urllib3.util.response import is_fp_closed
 from .packages.urllib3.exceptions import (
     DecodeError, ReadTimeoutError, ProtocolError, LocationParseError)
 from .exceptions import (
@@ -652,8 +654,12 @@
             try:
                 # Special case for urllib3.
                 try:
-                    for chunk in self.raw.stream(chunk_size, decode_content=True):
-                        yield chunk
+                    while not is_fp_closed(self.raw._fp):
+                        data = self.read(amt=chunk_size, decode_content=True)
+
+                        if data:
+                            yield data
+
                 except ProtocolError as e:
                     raise ChunkedEncodingError(e)
                 except DecodeError as e:

Может быть, вы могли бы попробовать то же самое на своем компьютере, чтобы увидеть, имеет ли это значение и для вас.

(Обратите внимание, да, я знаю, что вызов is_fp_closed - это нарушение инкапсуляции, это не означает серьезный патч, а просто точку данных)

@shazow Я надеюсь, что BufferedSocket который использует Hyper, должен решить большую часть этой неэффективности, по существу предотвращая чтение небольших объемов. Интересно, есть ли эта проблема у httplib на Py3, потому что он широко использует io.BufferedReader , что должно обеспечивать примерно те же преимущества, что и BufferedSocket .

Конечно, однако, когда hyper набирает достаточно функциональности HTTP / 1.1, чтобы быть полезными, мы должны попытаться протестировать его вместе с другими реализациями и приложить усилия, чтобы сделать hyper как можно быстрее.

Неактивен почти год. Закрытие.

Я наблюдаю аналогичные проблемы, пропускная способность в 10 раз меньше при использовании requests по сравнению с urllib3 .

Я думаю, что проблема связана с классом urllib3 HTTPResponse , когда он читается как итератор, его пропускная способность просто очень плохая. Мой код работал очень уродливо: я возвращаю подчеркнутый объект httplib.HTTPResponse используемый urllib3, и это, похоже, решает мою проблему с пропускной способностью.

Интересный факт: суперкласс HTTPResponse urllib3 - это io.IOBase . Суперкласс httplib.HTTPResponse Python3 равен io.BufferedIOBase . Интересно, имеет ли это какое-то отношение к этому.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

JimHokanson picture JimHokanson  ·  3Комментарии

cnicodeme picture cnicodeme  ·  3Комментарии

justlurking picture justlurking  ·  3Комментарии

mitar picture mitar  ·  4Комментарии

brainwane picture brainwane  ·  3Комментарии