1. JavaScript / Говнокод #27616

    +2

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    Steps to reproduce:
    
      var s = "a huge, huge, huge string...";
      s = s.substring(0, 5);
    
    Expected results: s takes five bytes of memory, plus some overhead.
    Actual results: s takes a huge, huge, huge amount of memory.
    Unfortunately, most String functions use substring() or no-ops internally: concatenating with empty string, trim(), slice(), match(), search(), replace() with no match, split(), substr(), substring(), toString(), trim(), valueOf().
    My workaround is:
    
    function unleakString(s) { return (' ' + s).substr(1); }
    
    But it's not satisfying, because it breaks an abstraction and forces me to think about memory allocation.

    https://bugs.chromium.org/p/v8/issues/detail?id=2869

    Status: Assigned (Open)
    Reported on: Sep 3, 2013

    Запостил: 3.14159265, 26 Августа 2021

    Комментарии (49) RSS

    • Пиздец
      Ответить
    • Спамить нечем
      Ответить
    • > Использовать технологию 8 (ВОСЕМЬ) лет и не разобраться почему в ней течёт память.
      1. Мне за эту питушню не заплатили ни евроцента.
      2. Была и другая кодопитушня поинтереснее. Там надо было реализовать что-то новое, а не пердолиться с забытым кодом.
      3. Оно и так работало. Только раз в пару лет при полном перекачивании надо было запустить 4-5 раз парсер ради 20-25к постов. Это 4-5 запусков команды вместо одного раз в пару лет. При регулярной докачке по 50-200 только изменившихся постов питушня не падает.
      Ответить
      • Суть не в этом. А в том что ВОСЕМЬ лет в движке висит открытый баг.

        И ноду предлагают пихать в СЕРВЕРА (!) и ни о чём ни думать.

        И тут мы опять приходим к тому о чём я уже говорил:

        Они не сделали язык с автоматическим управлением памятью.

        Они просто сделали говно на котором писать нельзя.

        И в этом и заключается фокус.

        Нода нам даёт абстракции на которых мы уже и пытаемся что-то сваять.

        Очевидно, что сваять ничего нельзя - поэтому отовсюду течёт память.

        Как мы видим гц-мразь опять обосралась, но с неё так никто за это и не спросил.
        Ответить
        • > А в том что ноду предлагают пихать в СЕРВЕРА (!) и ни о чём ни думать.
          Вот. А я использовал серверную технологию для написания клиентского скрипта, поэтому и обосрался.

          > сваять ничего нельзя - поэтому отовсюду течёт память
          У меня есть сервер под Node, там крутится более нетривиальная питушня, чем парсинг базы, но даже после месяцев работы потребление остаётся в рамках 80-150МБ. Иногда смотришь: после инициализации становится 160МБ, а потом обновляешь статистику, а там ГЦ всё смыл.
          Ответить
          • > более нетривиальная питушня, чем парсинг базы

            Оно тоже парсит строки?

            Речь о конкретной ошибке:
            https://www.just-bi.nl/a-tale-of-a-javascript-memory-leak/

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

            А самое печально что даже нет нормального метода для явного копирования части буфера в новую строку.
            > return (' ' + s).substr(1);
            Это хак, и довольно неочевидный.
            Ответить
            • > Это плохо говорит о такой серверной технологии.
              Действительно. На C++ аналогичная программа бы заняла без GC 10 ГБ и упала бы только в 2025 году, когда постов было бы настолько много, что свободной памяти и места на диске бы не хватило.

              Кстати, сейчас и 20К постов вывозит. Или 64битная версия работает с 2ГБ, или сборщик мусора стал агрессивнее.

              Интересно было наблюдать за работой в content mode. Если версии с регулярками за минуту жрали память линейно, то в версии с парсером HTML за свои минуты работы GC натужно чистил память вилкой и не давал ей линейно расти. Но всё равно мой код его подебил. Причём во второй половине времени бОльшая часть ресурсов, похоже, тратилась на работу GC, чтобы урвать хоть какой-то кусок памяти.
              Ответить
            • list mode: create only entities lists (regexp parser)
              сломалось на 25733
              Last few GCs:
              56551 ms: Mark-sweep 1974.5 (2089.6) -> 1971.4 (2085.9) MB, 204.5 / 0.0 ms  (average mu = 0.187, current mu = 0.130) allocation failure scavenge might not succeed
              56776 ms: Mark-sweep 1975.0 (2088.6) -> 1974.1 (2088.6) MB, 214.1 / 0.0 ms  (average mu = 0.116, current mu = 0.050) allocation failure scavenge might not succeed
              
              content mode: parse all available information (HTML parser; biggest file size)
              сломалось на 22757
              265066 ms: Mark-sweep 1949.0 (2087.1) -> 1945.5 (2087.6) MB, 429.3 / 0.0 ms  (average mu = 0.264, current mu = 0.237) allocation failure scavenge might not succeed
              265456 ms: Mark-sweep 1945.5 (2087.6) -> 1945.5 (2087.9) MB, 389.1 / 0.0 ms  (average mu = 0.147, current mu = 0.000) allocation failure scavenge might not succeed
              
              stok mode: create only bormand-oriented comment info list (regexp parser)
              сломалось на 26673
              61427 ms: Mark-sweep 1952.4 (2057.0) -> 1952.4 (2055.5) MB, 43.7 / 0.0 ms  (average mu = 0.089, current mu = 0.022) last resort GC in old space requested
              61490 ms: Mark-sweep 1953.9 (2057.1) -> 1952.4 (2056.3) MB, 42.7 / 0.0 ms  (average mu = 0.210, current mu = 0.325) allocation failure scavenge might not succeed
              
              html mode: create only entities lists with HTML of comments and post description (regexp parser)
              сломалось на 25673
              54249 ms: Mark-sweep 1932.6 (2088.0) -> 1928.7 (2084.1) MB, 89.5 / 0.0 ms  (average mu = 0.363, current mu = 0.322) allocation failure scavenge might not succeed
              54383 ms: Mark-sweep 1934.2 (2088.0) -> 1931.2 (2085.5) MB, 91.5 / 0.0 ms  (average mu = 0.340, current mu = 0.317) allocation failure scavenge might not succeed
              Ответить
              • Нужно попробовать обернуть выхлоп re.matchera в function unleakString(s) { return (' ' + s).substr(1); }
                Ответить
                • Нашёл опцию --max-old-space-size=8192, которая решает проблему!
                  Если бы погуглил её лет пять назад, вообще бы никаких проблем не было.

                  list mode: 3.2GB (во время парсинга было 3GB, потом парсинг закончился, GC убрал до 2.6GB и докачались 24 обнаруженных повреждённых файла, после сохранения JSON стало 3.2)
                  content mode: 5.7GB (после 4ГБ стал активнее чистить и понизил коэффициент прямой потребления, но стал безбожно тормозить, по завершении парсинга занял 5ГБ, после сохранения JSON было 5.7ГБ)
                  stok mode: 2.4GB
                  html mode: 3.7GB

                  Есть мысль, что content mode может заработать и на 3ГБ (с 2ГБ сломался на 22757 из 27645, а с 8ГБ просто не так агрессивно водил вилкой).

                  Вообще, главная проблема тут в том, что я гружу в память всю базу. Если бы это был хотя бы MySQL-сервер, Node.js бы отправила ему в запросе новую питушню и освободила целиком, отправила и освободила, и так для каждого поста на ГК.

                  Так что тут не надо пердолиться, если архитектура кривая.
                  Ответить
                  • --max-old-space-size=3000 не хватило (прошу прощения за некруглое число)
                    Ответить
                  • --max-old-space-size=3500 тоже не хватило
                    Ответить
                  • --max-old-space-size=4096 работает. Питушня сожрала 4.6ГБ private bytes и завершилась.
                    Ответить
                    • Ну всё-равно вопрос, если алгоритм обрабатывает посты по одному, зачем ему столько old genа?
                      По идее ему нужна память для обработки одной самой большой страницы (хохлосрача) + немного для статы по юзерам.

                      > Питушня сожрала 4.6ГБ
                      O(N) по памяти.
                      Ответить
                      • > для обработки одной самой большой страницы (хохлосрача) + немного для статы по юзерам.
                        И для хранения всей базы ещё. Она же в памяти у меня, какой багор.
                        Ответить
                        • > Что интересно, от загрузки JSON со всем ГК парсер не падает.

                          Мы уже поняли что вся база СТОЛЬКО не занимает.
                          Ответить
                  • >Нашёл опцию -
                    node --v8-options

                    удачи
                    Ответить
                    • Мне пока оттуда понадобился только max-stack-size, чтобы смотреть, где в моей гиперабстрактной питушне что-то произошло.
                      А так я сегодня больше времени потратил на поиск и проверку, чем все эти годы - на запуск проги.
                      Ответить
                      • Если node основной инструмент, то полезно это изучить.

                        Реально работа с gc языком включает в себя снятие дампов, изучение таких флагов, тюнинг gc.
                        Ответить
                        • Пиздливая маркетолоблядь: "GC" позволяет программисту не думать о памяти!

                          Программист: Реально работа с gc языком включает в себя снятие дампов, изучение таких флагов, тюнинг gc.
                          Ответить
                          • Иными словами, работа с динамической памятью включает в себя работу с динамической памятью.
                            Ответить
                            • A general-purpose programming language is a programming language dedicated to a general-purpose
                              Ответить
                              • ...Использование динамической памяти включает в себя еблю с ней. Так лучше?
                                Ответить
                • Оказалось, у меня там не совсем полное говно, есть зачатки модульной архитектуры.
                  Подмодули парсеров разных режимов в качестве интерфейса получают функции добавления поста, комментария и пользователя. Соответственно, если в них провести очистку строки от греха, эффект сработает сразу для всех парсером.

                  Добавил такое:
                  function unleakString (s) { return (' ' + s).substr(1); }
                  function unleakObject (o) { return JSON.parse(JSON.stringify(o)); }

                  и обернул в это пришедшую от парсеров питушню

                  После этого в режиме pitux=4096 график mode content стал пилообразным. Намечался линейный рост но раз секунд в 10 ГЦ сбрасывал по 200-300МБ.
                  Парсинг закончился на 1.5ГБ, сохранение - на 2.1ГБ.

                  В режиме pitux=1024 (ко-кок, даже меньше, чем было с дефолтом) поцребление было с микропилами (ГЦ срезал десятками мегабайт), линейного роста не было. Что интересно, после опреелённого момента приложение тормозит секунд 5 и тактика ГЦ меняется. Для 4096 это были просто тормоза и линейный рост продолжался, а для 1024 стало линейное убывание. Парсинг закончился на 880МБ, сохранение - на 1.6ГБ.

                  Мда, мы с Вами были правы про
                  > кусков строк, от которых используется только небольшой слайс
                  Ответить
                  • Потребление (private bytes после сохранения результатов в файл) заметно сократилось. Для сравнения (поскольку от достатка памяти ГЦ жиреет и плохо вертится) прогнал с одним и тем же восьмигиговым питухом, а также добавил размер "базы данных", которая 1 раз грузится и 1 раз сохраняется. Исходники говнокодов занимают 1.21ГБ.
                    objects | sinful |    cleansed   || JSON
                    pitux   |   8192 |  8192 |  1024 || size
                    --------+--------+-------+-------++------
                    content |  5.7GB | 2.1GB | 1.6GB || 215MB
                    list    |  3.2GB | 472MB | 340MB ||  57MB
                    stok    |  2.4GB | 141MB |  95MB ||   9MB
                    html    |  3.7GB | 1.7GB | 1.5GB || 206MB


                    Парсер стока, судя по размеру, реально тянул за собой все считанные из файлов данные.
                    Ответить
                    • А я знаю в чём главная проблема.

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

                      Чем больше современному программисту доступно памяти, тем большее говнище он пишет.
                      Ответить
                      • Да блин, у NGK на серваке 512 вроде и стояло изначально... И ничего, прекрасно парсились все треды. Месяцами. Без ребута скрипта.
                        Ответить
                        • Так вот и у меня с учётом того, что весь сток держался в памяти, всё приложение Node.js в итоге заняло 95-141МБ (когда начал очищать строки от греха)

                          P.S. Уверен, что скрипт бы и без очистки строк работал и так месяцами, поскольку при обновлении поста GC бы удалял старую психозу :) Правда, на 4-8ГБ
                          Ответить
                          • У меня просто всё в базу сливалось, а сами скрипты были stateless. Там даже нодовский мусорщик нормально справился бы с уборкой.
                            Ответить
                      • Замечу, что чем большее говнище пишет чем проще написать программу, тем её вероятнее написать.
                        Не было бы JS и куч памяти - не было бы всей питушни, что я писал. На сишке под микроконтроллер я бы не брался писать парсер ГК.
                        Ответить
                        • Это только так кажется.

                          Выскоуровневые ЯПы быстро развращают просто. Если бы ты никогда в жизни не ел ничего слаще `Borland C 3.1` под DOS, то как миленький бы писал на нём что угодно.

                          Писали же чуваки всякие тосеры, редакторы для фидонета, целые файловые менеджеры...
                          Ответить
                          • Явно же им больше времени надо было на всё или больше скилла. Программы были либо дороже, либо имели меньше функциональности.
                            Ответить
                            • > меньше функциональности

                              Ой не факт... Сейчас везде тенденция сводить полезную функциональность к минимуму.
                              Ответить
        • Ну тут дело не в gc, как я понял. Можно и на с++ сделать такой класс строк, который может внутренне указывать на кусок разделяемых данных, а substr не копирует данные:
          if (...) {
            MyString huge = loadFile();
            m_sig = huge.substr(8, 4);
          }
          // <- m_sig занимает много места
          m_sig.squeeze();
          // <- а вот теперь хорошо

          или c собственным gc:
          parseHugeFile(file_name);
          MyString::optimizeAllStrings();

          можно даже фоновым:
          MyString::enableBackgroundOptimization(true);
          Ответить
          • В Qt, например, есть QStringRef - легкая подстрока, но хранить их нельзя, только использовать, пока жив исходный QString. Так что это не то.
            Ответить
            • > В Qt, например, есть QStringRef - легкая подстрока, но хранить их нельзя, только использовать

              Сравнение немного некорректно.

              Во-первых, js не предоставляет программисту альтернативной реализации строк.

              Во-вторых, они даже не удосужились добавить метод подобный «squeeze».
              Программист вынужден использовать неочевидные, недокументированные хаки, основанные на предположениях как оно работает внутри.

              Проблема эта существует много лет. Но решать её не торопятся. Вместо этого в новые стандарты напихивают разные class, constructor, extends, super,

              И самое главное: В-третьих, достойная реализация гц в случае критической нехватки памяти могла бы анализировать ссылки подстрок (которые полностью спрятаны под капотом) и копировать использующиеся фрагменты в меньшие подстроки.

              Ничего этого в js нет.
              Ответить
              • > могла бы анализировать ссылки подстрок (которые полностью спрятаны под капотом) и копировать использующиеся фрагменты в меньшие подстроки.

                Это было бы самое лучшее решение. И не было бы тормозов из-за лишних маллоков как в разных крестах, и память бы не просиралась.
                Оптимизация с хранением слайсов, в общем-то, полезная, пока памяти на неё хватает.
                Ответить
    • Предыстория:

      1024-- рассказал что в node утекает память.

      https://govnokod.ru/27615#comment664411

      > Мой парсер постов ГК (работает с HTML-файлами постов на диске, которые скачал загрузчик) раньше мог распарсить не более 5К постов, от большего количества он тупо набирал 1.5ГБ и дох.
      > Сборщику мусора просто не давали нормально отработать, какие тут тормоза!
      > Вероятно, память жрётся из-за ошмётков объектов парсинга и кусков строк, от которых используется только небольшой слайс.
      Ответить
      • Теперь пользователям ГК придётся сначала прочитать всё до конца, а потом понять, что за предыстория.
        Ответить
    • Так вот почему «Хром» подобно газу занимает весь объём...
      Ответить
    • У меня в Си такой хуйни нет, именно поэтому я за Си.
      Ответить
    • test
      Ответить

    Добавить комментарий