Подтвердить что ты не робот

Опасность метода подстроки С#?

Недавно я читал некоторые из недостатков с помощью метода подстроки Java, в частности, относящегося к памяти, и того, как Java поддерживает ссылку на исходную строку. По иронии судьбы, я также разрабатываю серверное приложение, которое использует С#.Net реализацию подстроки много десятков раз в секунду. Это заставило меня задуматься...

  • Есть ли проблемы с памятью с С# (.Net) string.Substring?
  • Какова производительность, например, на string.Substring? Есть ли более быстрый способ разбить строку на основе начальной/конечной позиции?
4b9b3361

Ответ 1

Глядя на .NET-реализацию String.Substring, подстрока не использует память с оригиналом.

private unsafe string InternalSubString(int startIndex, int length, bool fAlwaysCopy)
{
    if (((startIndex == 0) && (length == this.Length)) && !fAlwaysCopy)
    {
        return this;
    }

    // Allocate new (separate) string
    string str = FastAllocateString(length);

    // Copy chars from old string to new string
    fixed (char* chRef = &str.m_firstChar)
    {
        fixed (char* chRef2 = &this.m_firstChar)
        {
            wstrcpy(chRef, chRef2 + startIndex, length);
        }
    }
    return str;
}

Ответ 2

Каждый раз, когда вы используете подстроку, вы создаете новый экземпляр строки - он должен скопировать символ из старой строки в новую, а также связанное с ним новое распределение памяти — и не забывайте, что это символы Юникода. Это может быть или не быть плохим - в какой-то момент вы хотите использовать эти символы где-то в любом случае. В зависимости от того, что вы делаете, вы можете захотеть, чтобы ваш собственный метод просто обнаружил правильные индексы внутри строки, которые вы затем можете использовать позже.

Ответ 3

всегда полезно попробовать и измерить прошедшие миллисекунды.

Stopwatch watch = new Stopwatch();
watch.Start();
// run string.Substirng code
watch.Stop();
watch.ElapsedMilliseconds();

Ответ 4

В случае утечки памяти Java, которое может возникнуть при использовании subString, оно легко фиксируется путем создания экземпляра нового объекта String с помощью конструктора копирования (который является вызовом формы "новая строка (String)" ). Используя это, вы можете отбросить все ссылки на оригинал (и в случае, если это действительно проблема, довольно большая) String, и поддерживать только те части, которые вам нужны в памяти.

Не идеально, теоретически JVM может быть более умным и сжимать объект String (как было предложено выше), но это выполняет свою работу с тем, что у нас есть.

Что касается С#, как уже было сказано, эта проблема не существует.

Ответ 5

Просто добавьте еще одну точку зрения на это.

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

Частые распределения/деаллокация вызовет фрагментацию памяти. GC не может быть в состоянии де-фрагментировать во времени иск к видам операций, которые вы делаете. Я знаю, что сервер GC в .NET довольно хорош в дефрагментации памяти, но вы всегда можете голодать (не позволяя GC делать сбор) системы, написав плохой код.

Ответ 6

Мне кажется, что строки в Java были сохранены как фактические символы вместе с началом и длиной.

Это означает, что строка подстроки может совместно использовать одни и те же символы (поскольку они неизменяемы) и должна поддерживать только отдельный старт и длину.

Итак, я не совсем уверен, что проблемы с памятью связаны с строками Java.


Относительно этой статьи, опубликованной в вашем редактировании, мне кажется, что это не проблема.

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

Даже если у вас была строка 10M и вы создали 400 подстрок, вы используете только 10M для базового массива char - это не делает 400 копий этой подстроки. Единственное влияние на память - бит начала и длины каждого объекта подстроки.

Автор, похоже, жалуется на то, что читает огромную строку в памяти, а потом хочет только немного, но вся вещь была сохранена. Мое предложение состояло в том, что они могут захотеть переосмыслить, как они обрабатывают свои данные:-)

Чтобы называть это, ошибка Java также огромная. Ошибка - это то, что не работает для спецификации. Это было преднамеренное дизайнерское решение для повышения производительности, нехватки памяти, потому что вы не понимаете, как все работает, это не ошибка, IMNSHO. И это определенно не утечка памяти.


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

Это не то, что вы хотели бы сделать на GC первого прохождения, так как это было бы относительно дорого. Однако, когда каждая другая операция GC не смогла вернуть достаточно места, вы можете это сделать.

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

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

Я думаю, что если память закончится, я бы предпочел не поддерживать это сопоставление char -array-to-string, чтобы сделать этот уровень GC возможным, вместо этого я предпочел бы, чтобы память использовалась для мои строки.


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

Не потому, что разработчики Java слишком ленивы, но потому что это не проблема.

Вы можете реализовать свои собственные строковые методы, которые соответствуют С# (которые не разделяют базовые данные, за исключением некоторых ограниченных сценариев). Это устранит проблемы с памятью, но за счет повышения производительности, так как вы должны копировать данные каждый раз, когда вы вызываете подстроку. Как и в большинстве случаев в ИТ (и в жизни), это компромисс.

Ответ 7

реализация CLR (следовательно, С#) Substring не сохраняет ссылку на исходную строку, поэтому у нее нет проблемы с утечкой памяти строк Java.

Ответ 8

большинство этих типов проблем строки связаны с тем, что String неизменен. Класс StringBuilder предназначен для тех случаев, когда вы выполняете много строковых манипуляций:

http://msdn.microsoft.com/en-us/library/2839d5h5(VS.71).aspx

Обратите внимание, что реальной проблемой является распределение памяти, а не процессор, хотя избыточное выделение памяти занимает процессор...

Ответ 9

Для профилирования памяти при разработке вы можете использовать этот код:

bool forceFullCollection = false;

Int64 valTotalMemoryBefore = System.GC.GetTotalMemory(forceFullCollection);

//call String.Substring

Int64 valTotalMemoryAfter = System.GC.GetTotalMemory(forceFullCollection);

Int64 valDifferenceMemorySize = valTotalMemoryAfter - valTotalMemoryBefore;

О параметре forceFullCollection: "Если параметр forceFullCollection равен true, этот метод ждет короткий интервал перед возвратом, когда система собирает мусор и завершает объекты. Длительность интервала - это внутренний заданный предел по количеству циклов сбора мусора и изменению объема памяти, восстановленного между циклами. Сборщик мусора не гарантирует, что будет собрана вся недоступная память". Метод GC.GetTotalMemory

Удачи!;)