Написание высокоэффективного кеша - PullRequest
2 голосов
/ 22 декабря 2011

Я написал симулятор фондового рынка, который использует ConcurrentHashMap в качестве кэша.

Кэш содержит около 75 элементов, но они обновляются и извлекаются очень быстро (~ 500 раз в секунду).

Вот что я сделал:

Тема 1:

Подключен к внешней системе, которая предоставляет мне потоковые кавычки для данного символа акции.

Поток 2 (поток обратного вызова):

Ожидает, когда данные будут переданы ему внешней системой.Получив данные, он анализирует их, создает неизменный объект DataEntry, кэширует его и отправляет сигнал в thread3.

Поток 3 (Поток потребителя): Получив сигнал, получитеDataEntry из кеша и использует его.(Это часть задачи, чтобы не позволить thread2 передавать данные напрямую в thread3).

public final class DataEntry{

      private final String field1;
      private final String field2;
      //...
      private final String field25;

      // Corresponding setters and getters

}

public final class Cache{

        private final Map<String, DataEntry> cache;

        public Cache( ){
           this.cache = new ConcurrentHashMap<String, DataEntry> ( 65, 0.75, 32 );
        }

        // Methods to update and retrieve DataEntry from the cache.
}

После запуска через профилировщик я заметил, что я создаю lot of DataEntry объект.И, следовательно, eden заполняется очень быстро.

Итак, я думаю немного изменить дизайн с помощью:

a) Создание класса DataEntry изменчивым.

b) Предварительное заполнение кэша пустыми DataEntry объектами.

c) Когда обновление будет получено, получите DataEntryобъект из карты и заполнить поля.

Таким образом, число DataEntry объекта будет постоянным и равным количеству элементов.

Мои вопросы:

a) Имеет ли этот дизайн какие-либо проблемы с параллелизмом, которые я мог бы внести, сделав изменяемым DataEntry.

b) Что еще я могу сделать, чтобы оптимизировать кэш?

Спасибо.

Ответы [ 4 ]

1 голос
/ 22 декабря 2011

Я бы не стал беспокоиться о скорости ConcurrentHashMap

Map<Integer, Integer> map = new ConcurrentHashMap<>();
long start = System.nanoTime();
int runs = 200*1000*1000;
for (int r = 0; r < runs; r++) {
    map.put(r & 127, r & 127);
    map.get((~r) & 127);
}
long time = System.nanoTime() - start;
System.out.printf("Throughput of %.1f million accesses per second%n",
        2 * runs / 1e6 / (time / 1e9));

печать

Throughput of 72.6 million accesses per second

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

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

1 голос
/ 22 декабря 2011
  • а.Да, это так.Изменяемые DataEntry объекты могут быть обновлены без уведомления читателя, что приведет к несовместимым состояниям.
  • b.Да, вы можете: сделать изменяемый DataEntryCache, который возвращает неизменный DataEntry по запросу.Таким образом, вы создадите новые DataEntry объекты для чтения, а не для записи.DataEntryCache может внутренне кэшировать неизменяемый DataEntry, который он создает и возвращает, и аннулировать этот «кэш» при мутирующих вызовах.

Редактировать: я предполагаю, что причина, по которой вы кешируете (в отличиесоздание очереди между потоками 2 и 3) заключается в том, что потребительский поток может читать другие записи в дополнение к той, из которой поток 2 отправляет уведомление.Если это предположение неверно, вам может вообще не понадобиться кеш.

1 голос
/ 22 декабря 2011

Звучит так, как будто вы используете ConcurrentHashMap, когда то, что вам действительно нужно, является чем-то вроде параллельной очереди - скажем, LinkedBlockingQueue?

0 голосов
/ 22 декабря 2011

a) В моем коде создание объектов часто проявляется как узкое место, поэтому я думаю, что ваша собственная идея повторного использования DataEntry объектов тоже заслуживает реализации.Однако, как прокомментировал kdgregory, простая перезапись текущих элементов приведет к непоследовательному чтению записей.Таким образом, при обновлении записи вместо этого запишите новую или, если доступно, повторно использованную запись в режиме ожидания (скажем, в течение нескольких минут) и поместите ее в карту.Поместив новую запись в карту, поместите старую запись в какой-то неактивный список.Чтобы быть полностью безопасными, читающим потокам не должен быть разрешен доступ к DataEntry, доставляемому кешем, например, через 1 минуту.Если потоки могут блокироваться, они должны скопировать объекты DataEntry, возможно, повторно используя для этого собственные объекты.

b) Ваш текущий дизайн является модульным, но включает много переключений контекста, поскольку потоки отражают модули,Я хотел бы попробовать дизайн, где один запрос от начала до завершения обслуживается одним потоком.Запросом может быть полная обработка нового объекта DataEntry.Конструктивными схемами параллелизма, достигающими этого, являются Лидер / Последователь и Полусинхронизация / Полусинхронизация .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...