Строка против массива байтов, производительность

#c# #java #c #oop

#c# #java #c #ооп

Вопрос:

(Этот пост касается программирования высокочастотных типов)

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

Один из приемов повышения производительности при работе с любым языком, C , Java, C # — избегать создания объектов. Это не стоимость выделения или сбора данных, это стоимость доступа к большим массивам памяти, которые не помещаются в кэш процессора.

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

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

Так, например, вместо того, чтобы использовать String и split для анализа сообщения, используйте массивы байтов, которые можно обновлять на месте. Вы действительно хотите избежать случайного доступа к памяти через большие структуры данных, по крайней мере, во внутренних циклах.

Он просто говорит: «Не используйте строки, потому что они являются объектом, а создание объектов обходится дорого»? Или он говорит что-то еще?

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

Ответ №1:

Он говорит, что если вы разобьете фрагмент текста на отдельные строковые объекты, эти строковые объекты будут иметь худшую локальность, чем большой массив текста. Каждая строка и массив символов, который она содержит, будут находиться где-то еще в памяти; они могут быть разбросаны по всему пространству. Вполне вероятно, что кешу памяти придется входить и выходить, чтобы получить доступ к различным строкам по мере обработки данных. Напротив, один большой массив имеет наилучшую возможную локальность, так как все данные находятся в одной области памяти, и перерасход кэша будет сведен к минимуму.

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

Комментарии:

1. Вы сказали, что «они могут быть распространены повсюду». Хранятся ли символы строки в непрерывной памяти или в виде связанного списка?

2. Символы находятся в постоянной памяти. Но обычно строковый объект состоит из двух независимых блоков: самого строкового объекта и массива для хранения символов. Затем, если вы создадите много строк, каждая из этих строк и каждый из их массивов будут где -то находиться, и нет никакой гарантии, что любой из этого множества объектов будет находиться в одной и той же области памяти; каждый из них, будучи выделенным отдельно, может быть где угодно. В C сами строковые объекты могли бы находиться в одном и том же месте, если бы они были выделены в массиве значений; в Java у вас даже этого не было бы.

3. Символы в строке являются непрерывными, однако, если у вас несколько строк, они могут быть повсюду. Если вы используете String.substring в Java, это представление базовой строки, поэтому этого не произойдет, однако C и C # берут копии исходных данных при получении подстроки другой строки.

Ответ №2:

Есть много других причин использовать byte[] or char* вместо строк для HFT. Строки состоят из 16 бит char в Java и являются неизменяемыми. byte[] или ByteBuffer легко перерабатываются, имеют хорошее расположение кэша, могут быть удалены из кучи (напрямую), сохраняя копию, избегая кодировщиков символов. Все это предполагает, что вы используете данные ASCII.

char* или байтовые буферы также могут быть сопоставлены сетевым адаптерам для сохранения другой копии. (С некоторыми изменениями для байтовых буферов)

В HFT вы редко имеете дело с большими объемами данных одновременно. В идеале вы хотите обрабатывать данные, как только они поступают в сокет, то есть по одному пакету за раз. (около 1,5 КБ)

Комментарии:

1. Как бы вы сохранили байтовый массив вне кучи, разве вам не пришлось бы использовать ‘new’ в объявлении?

2. В C вам нужно использовать new или malloc в Java вы можете использовать a ByteBuffer.allocateDirect() (который является оболочкой для отдельного malloc блока памяти) Используя отражение или JNI, вы можете изменить, на что address указывает, чтобы он мог напрямую обращаться к сетевому адаптеру (если вы используете обход ядра) Вы можете полностью отказаться от ByteBuffer, если используете Unsafe класс (хотя это редко имеет большое значение).

3. Уважаемый Питер, не могли бы вы уточнить, «Используя отражение или JNI, вы можете изменить, на что указывает адрес, чтобы он мог напрямую обращаться к сетевому адаптеру (если вы используете обход ядра)». Знаете ли вы какие-нибудь веб-сайты с небольшим примером кода? Я полагаю, это намного проще сделать на C , чем на Java?

4. Это тривиально сделать на C или C . Все, что вам нужно, это указатель, который является второй натурой. В Java вам нужно перепрыгнуть через несколько обручей, но вы можете добиться того же. Я не уверен, какие примеры я могу вам показать, кроме того, что он просто устанавливает поле с использованием reflection. т.е. Field.setLong();