- PVSM.RU - https://www.pvsm.ru -

Несколько месяцев назад в новостях всплыла потрясающая статья [1] [переводы на Хабре: один [2] и второй [3]] о Grand Theft Auto Online.
Советую прочитать статью целиком, но если вкратце, GTA Online имела внезапно квадратичную [4] производительность при парсинге большого JSON-блоба (из-за многократных вызовов strlen); после устранения этой ошибки время загрузки уменьшилось почти на 70%.
Это вызвало оживлённые дискуссии [5]: в этом виноват C? Или, возможно, "web shit" [6]? Или капитализм и его стимулы [7]?
Однако все были солидарны в одном: они бы ни за что не написали подобной глупости.
(Вы уже чувствуете, что надвигается?)

Благодаря продуманному коду она открывает 97-мегабайтный двоичный файл STL на Macbook Pro 2013 года всего за 165 миллисекунд. Это потрясающая скорость.
Из соображений совместимости я написал небольшой парсер и для ASCII STL [9].
ASCII STL — это формат обычного текста с плохой спецификацией, который выглядит вот так:
solid cube_corner
facet normal 0.0 -1.0 0.0
outer loop
vertex 0.0 0.0 0.0
vertex 1.0 0.0 0.0
vertex 0.0 0.0 1.0
endloop
endfacet
facet normal 0.0 0.0 -1.0
outer loop
vertex 0.0 0.0 0.0
vertex 0.0 1.0 0.0
vertex 1.0 0.0 0.0
endloop
endfacet
...
endsolid
Я написал чрезвычайно надёжный [10] парсер, добавив в комментарий такое описание:
/* Самый либеральный парсер ASCII STL: игнорирует всё, кроме
* слова 'vertex', а затем одно за другим считывает три значения float. */
Загрузка ASCII STL всегда казалась немного медленной, но я предполагал, что причина этого в неэффективном текстовом формате.
(Тучи сгущаются.)
парсинг может быть квадратичным из-за многократных вызовов sscanf [12]Вот логи загрузки 1,5-мегабайтного ASCII STL метками времени (в секундах):
[erizo] (0.000000) main.c:10 | Startup!
[erizo] (0.162895) window.c:91 | Created window
[erizo] (0.162900) window.c:95 | Made context current
[erizo] (0.168715) window.c:103 | Initialized GLEW
[erizo] (0.178329) window.c:91 | Created window
[erizo] (0.178333) window.c:95 | Made context current
[erizo] (1.818734) loader.c:109 | Parsed ASCII STL
[erizo] (1.819471) loader.c:227 | Workers have deduplicated vertices
[erizo] (1.819480) loader.c:237 | Got 5146 vertices (7982 triangles)
[erizo] (1.819530) loader.c:240 | Waiting for buffer...
[erizo] (1.819624) loader.c:326 | Allocated buffer
[erizo] (1.819691) loader.c:253 | Sent buffers to worker threads
[erizo] (1.819883) loader.c:258 | Joined worker threads
[erizo] (1.819887) loader.c:279 | Loader thread done
[erizo] (1.821291) instance.c:32 | Showed window
С момента запуска до отображения окна прошло больше 1,8 секунды!
Посмотрев на парсер ASCII свежим взглядом, я увидел, что причина очевидна:
/* The most liberal ASCII STL parser: Ignore everything except
* the word 'vertex', then read three floats after each one. */
const char VERTEX_STR[] = "vertex ";
while (1) {
data = strstr(data, VERTEX_STR);
if (!data) {
break;
}
/* Skip to the first character after 'vertex' */
data += strlen(VERTEX_STR);
for (unsigned i=0; i < 3; ++i) {
SKIP_WHILE(isspace);
float f;
const int r = sscanf(data, "%f", &f);
ABORT_IF(r == 0 || r == EOF, "Failed to parse float");
if (buf_size == buf_count) {
buf_size *= 2;
buffer = (float*)realloc(buffer, buf_size * sizeof(float));
}
buffer[buf_count++] = f;
SKIP_WHILE(!isspace);
}
}
Можно заметить, что в коде есть sscanf, считывающая одно значение float из начала потока данных и каждый раз проверяющая длину всей строки.
Да, я совершил ту же ошибку, что и программисты, работавшие над GTA Online: написал внезапно квадратичный парсер!
Замена вызова sscanf на вызов strtof снизила время загрузки почти в 10 раз: с 1,8 секунды до 199 миллисекунд.
[erizo] (0.000000) main.c:10 | Startup!
[erizo] (0.178082) window.c:91 | Created window
[erizo] (0.178086) window.c:95 | Made context current
[erizo] (0.184226) window.c:103 | Initialized GLEW
[erizo] (0.194469) window.c:91 | Created window
[erizo] (0.194472) window.c:95 | Made context current
[erizo] (0.196126) loader.c:109 | Parsed ASCII STL
[erizo] (0.196866) loader.c:227 | Workers have deduplicated vertices
[erizo] (0.196871) loader.c:237 | Got 5146 vertices (7982 triangles)
[erizo] (0.196921) loader.c:240 | Waiting for buffer...
[erizo] (0.197013) loader.c:326 | Allocated buffer
[erizo] (0.197082) loader.c:253 | Sent buffers to worker threads
[erizo] (0.197303) loader.c:258 | Joined worker threads
[erizo] (0.197306) loader.c:279 | Loader thread done
[erizo] (0.199328) instance.c:32 | Showed window
документации sscanf [13] не указана её временная сложность, поэтому это особо хитрый пистолет для выстрела себе в ногу, и мне кажется, что не один я блуждал во тьме невежества.
Возможно, вы сами не столкнётесь с подобным напоминанием, но всякий раз, когда вы будете читать потрясающую историю о плохом коде, помните — это может случиться и с вами!
(Очевидно, мораль истории такова: не используйте sscanf для многократного парсинга одиночных токенов из начала строки; уверен, у вас всё будет нормально, если вы просто избежите этого.)
VDSina предлагает мощные и недорогие VPS [14] с посуточной оплатой. Интернет-канал для каждого сервера — 500 Мегабит, защита от DDoS-атак включена в тариф, возможность установить Windows, Linux или вообще ОС со своего образа, а ещё очень удобная панель управления серверами собственной разработки [15]. Обязательно попробуйте!
Автор: Mikhail
Источник [16]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/revers-inzhiniring/365181
Ссылки в тексте:
[1] потрясающая статья: https://nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times-by-70/
[2] один: https://habr.com/ru/post/544764/
[3] второй: https://habr.com/ru/company/itsumma/blog/544830/
[4] внезапно квадратичную: https://accidentallyquadratic.tumblr.com/
[5] оживлённые дискуссии: https://news.ycombinator.com/item?id=26296339
[6] "web shit": https://twitter.com/Jonathan_Blow/status/1366452792563359744
[7] капитализм и его стимулы: https://twitter.com/fasterthanlime/status/1366187333507293184
[8] Erizo: https://www.pvsm.ru/projects/erizo
[9] ASCII STL: https://en.wikipedia.org/wiki/STL_(file_format)#ASCII_STL
[10] надёжный: https://en.wikipedia.org/wiki/Robustness_principle
[11] ошибку фокусировки на macOS: https://github.com/mkeeter/erizo/commit/d4683f94a4aa0b674bdde0fa53fb6f92d6e1979c
[12] парсинг может быть квадратичным из-за многократных вызовов sscanf: https://news.ycombinator.com/item?id=26302744
[13] документации sscanf: https://en.cppreference.com/mwiki/index.php?title=cpp/io/c/fscanf&oldid=125683
[14] мощные и недорогие VPS: https://vdsina.ru/cloud-servers?partner=habr427
[15] собственной разработки: https://habr.com/ru/company/vdsina/blog/460107/
[16] Источник: https://habr.com/ru/post/562370/?utm_source=habrahabr&utm_medium=rss&utm_campaign=562370
Нажмите здесь для печати.