1. List<> выделяет память кусками «про запас». Это называется capacity и отвечает за него
одноимённое свойство. Если вы знаете, что ваш список
в среднем будет содержать не больше N элементов, то это N нужно передать в соответствующий конструктор List<>.
2. Можно подсчитать байты и использовать
выравнивание. Процесс трудоёмкий, но если сейчас много «пустого места», то в итоге вполне могут выйти желанные ~80МБ.
3. Ну и упомянутые утечки памяти. GC.Collect и memory profiling вам в помощь.
Ну а людям ноющим о медленности «управляемых» языков, больших DLL и 100 МБ памяти для .NET по-умолчанию и предлагающих перейти на другие языки, я предлагаю пойти поучиться у англоязычных коллег, которые не ноют по каждому случаю, а стараются действительно помочь разобраться в проблеме, в данном случае — увеличить производительность.
Извините, накипело.