Программа жрёт много памяти

Как вы можете помнить, я написал свою скачивалку с ютуба. Ютуб хранит каждое видео в нескольких форматах. По-этому, их приходится парсить. Вот парсер:

Сейчас переписываю код в виде библиотеки. Дело дошло до переписывания этого парсера. И в последний момент обнаружилась странная хрень - обновлённый парсер стал жрать невероятно много памяти.
Вот код нового парсера:

ООПшность ещё надо допиливать. Но сейчас не об этом.
Память кушают строчки:

	YouTubeMediaTrack video = new YouTubeMediaTrackVideo(
		formatId, videoWidth, videoHeight, videoFrameRate, videoBitrate, videoBitrate,
		null, -1, null, null, -1, null, null, null, null,
		mimeType, mimeExtLowerCased, videoCodecs, fileExtension,
		isDash, false, false, dashManifestUrl, dashChunkUrls, null);
	YouTubeMediaTrack audio = new YouTubeMediaTrackAudio(
		formatId, audioBitrate, audioBitrate, null, -1, null, null, null,
		audioSampleRate, audioChannelCount, 0.0, -1, null, null, null,
		mimeType, mimeExtLowerCased, audioCodecs, fileExtension,
		isDash, false, false, dashManifestUrl, dashChunkUrls, null);

И только в этом месте. То есть, у DASH (когда видео на чанки разбито). Не пойму, почему столько памяти уходит? :thinking: Примерно на 25 видео уходит 700mb оперативы.
Сначала было подозрение на LinkedList. Потом перечитал код и понял, что не в нем дело. А в чём - непонятно :man_shrugging:

Попробовал в методе ParseDashManifest убрать строчки resList.AddLast(video) и resList.AddLast(audio). Память жрать перестало. Видимо, потому что объекты никуда не сохраняются и сразу освобождаются.
Но почему при добавлении их в список сжирается так дофига памяти? И почему это происходит только в методе ParseDashManifest?
Метод Parse делает то же самое, но память не съедает :man_shrugging:
Заменил LinkedList на List - не помогло.
Кто-нибудь пробовал у себя запускать? У всех так же?

Сделал ещё один тест:


    internal class Program
    {
        static void Main(string[] args)
        {
            List<YouTubeMediaTrack> list = new List<YouTubeMediaTrack>();
            for (int i = 0; i < 9999; ++i)
            {
                YouTubeMediaTrack trackVideo = new YouTubeMediaTrackVideo(
                    0, 0, 0, 0, 0, 0, null, 0L, null, null, 0, null, null, null,
                    null, null, null, null, null, false, false, false, null, null, null);
                list.Add(trackVideo);
            }
            Console.ReadLine();

10000 объектов, а память не жрёт. То есть, проблема именно в методе ParseDashManifest, почему-то :thinking:
Кстати, если в консольный тест забить ссылку на DASH-видео, то сжирается 124 мегабайта (на одно видео, Карл!). А если без DASH - всего 20мб. Тоже не мало! Но это в 6 раз меньше! Откуда такая большая разница? :thinking:
В GUI-тесте можно 4 страницы по 30 видео загрузить, а памяти около 170мб съедает (если без DASH) :man_shrugging:

dashChunkUrls занимает 80МБ в текущем виде (в консольном тесте). Не знаю, есть ли в NET контейнер, который сам будет разруливать хранение похожих строк компактно. Может не стоит склеивать baseUrl и media в segmentUrl, а хранить их отдельно и склеивать только при использовании ссылки?
UPD. Проверил: сохранение baseUrl в отдельном атрибуте сократило потребление с 128МБ до 39МБ.

Это как можно посмотреть? Где отображается?
На какой ссылке вы тестировали?

Что значит “в отдельном аттрибуте”? :thinking: В отдельной переменной?

Просто закомментировал строки заполнения dashChunkUrls в ParseDashManifest и посмотрел общее потребление памяти.

На исходной в ConsoleTest (Z9c9SbfGvec).

Добавил поле классу YouTubeMediaTrack, куда сохранял ссылку. Хотя в данном случае не это главное. Главное, что не приписывал baseUrl к каждой ссылке:

                             if (nodeInitialization != null)
                             {
                                 XmlAttribute attrSourceUrl = nodeInitialization.Attributes["sourceURL"];
                                 if (attrSourceUrl != null)
                                 {
-                                    dashChunkUrls.Add(baseUrl + attrSourceUrl.Value);
+                                    dashChunkUrls.Add(attrSourceUrl.Value);
                                 }
                             }
                             else
                             {
                                 System.Diagnostics.Debug.WriteLine("Warning! The \"nodeInitialization\" is NULL! The first track chunk may be lost!");
                             }
 
                             foreach (XmlNode nodeSegment in nodeSegmentList)
                             {
                                 if (nodeSegment.Name == "SegmentURL")
                                 {
-                                    string segmentUrl = baseUrl + nodeSegment.Attributes["media"]?.Value;
+                                    string segmentUrl = nodeSegment.Attributes["media"]?.Value;
                                     dashChunkUrls.Add(segmentUrl);
                                 }
                             }

Сделал ещё тест:

            LinkedList<LinkedList<string>> lists = new LinkedList<LinkedList<string>>();
            int max2 = 10;
            for (int j = 0; j < max2; ++j)
            {
                LinkedList<string> list = new LinkedList<string>();
                int max = 9999;
                string baseUrl = "https://rr4---sn-gvnuxaxjvh-c5me.googlevideo.com/videoplayback/expire/1668947932/ei/fMt5Y5zuGIbB7QShoYOwDA/ip/1.1.1.1/id/67d73d49b7c6bde7/itag/133/source/yt_otf/requiressl/yes/mh/bD/mm/31,29/mn/sn-gvnuxaxjvh-c5me,sn-n8v7znsy/ms/au,rdu/mv/m/mvi/4/pcm2cms/yes/pl/24/initcwndbps/376250/spc/SFxXNgg-X045EmVcrMC7MO09vdsbnok/vprv/1/ratebypass/yes/mime/video%2Fmp4/otfp/1/otf/1/lmt/1657236427614659/mt/1668925969/fvip/14/keepalive/yes/fexp/24001373,24007246/sparams/expire,ei,ip,id,itag,source,requiressl,spc,vprv,ratebypass,mime,otfp,otf,lmt/sig/AOq0QJ8wRQIhAL430GkSS1p3sz1aPHiq0CYNY1U6WZvOc7fw4OoTDPkbAiA841JjPG1BNdA0ZG5NNh1eZgaqjwR-2PI4ovG5e0Hn8A%3D%3D/lsparams/mh,mm,mn,ms,mv,mvi,pcm2cms,pl,initcwndbps/lsig/AG3C_xAwRAIgDoCIUdpxSzcE6V5XU363b4kH5mp77U_rdNwMMYGUHqsCIAFCGEsItvDgvYyqjhPt6N-0CPMJkhrWu2toAxD1tj44/";
                for (int i = 0; i < max; ++i)
                {
                    list.AddLast($"{baseUrl}/{i}.ts");
                }
                lists.AddLast(list);
                Console.WriteLine($"{j + 1} / {max2}: Completed {max + 1} strings");
            }
            Console.ReadLine();

Сожрало 193мб памяти. Действительно, строчки текста жрут как-то слегка так многовато памяти :thinking:
Но тогда почему старый парсер вообще не жрёт память? Там же всё точно так же. Разница только в том, что все поля открытые и в конструктор передаётся только 3 параметра. Сейчас вот запустил свой YouTube downloader (со старым парсером) и вставил в него эту ссылку (Z9c9SbfGvecg). Вижуалка показывает, что сожрано 42мб. А в диспетчере винды написано - 19,9мб. Наверное, если оптимизировать вашим способом - было бы ещё меньше. Но, всё равно, откуда такая разница (в 3 раза! :dizzy_face:)? :thinking: Код ведь почти тот же самый. Только в новом инкапсуляция (вроде так называется) соблюдена.
А когда я получал большой список видео канала и их скачивал (было очень много DASH), я вообще не замечал, что память ушла. У меня индикатор RAM уже очень много кучу лет отдельным окошком постоянно выведен. И я, обычно, замечаю, если память куда-то сожралась :man_shrugging:

Тоже попробовал. В разделе “Поиск видео по ссылке или ID” вбил Z9c9SbfGvec и нажал “Искать” - потребление 38МБ. После нажатия “Скачать” (и подтягивания доступных форматов) потребление возрастает до 145МБ. Так что не вижу разницы с текущим ConsoleTest.

Хм, действительно :thinking: Очень странно. Никогда не замечал :man_shrugging:
Но тогда проблема ясна :point_up: Это потому что старая версия получает список форматов только при нажатии кнопки “Скачать” и не хранит его постоянно. Так что сборщик мусора просто удаляет эти объекты. А в новой версии список хранится всегда.
Спасибо, оч помогли!
Надо будет этот момент продумать :thinking: