Архитектура опроса низкого использования ЦП между двумя JVM

Серверная среда

  • Linux / RedHat
  • 6 ядер
  • Java 7/8

О приложении:

  • Мы работаем над созданием высокоскоростной торговой платформы с низкой задержкой (7-8 мс) с использованием Java.
  • Есть 2 модуля A & B, каждый из которых работает на своей собственной JVM
  • B получает данные от A

Архитектура:

  • мы использовали MemoryMaps & Unsafe. В этом случае модуль A записывает данные в файл, отображенный в памяти, а модуль B выполняет чтение из файла (оба хранят адрес в файле).
  • Мы пошли дальше и использовали бесконечный цикл while для продолжения чтения, пока желаемое значение не будет получено из файла отображения памяти

проблема

  • Загрузка процессора увеличивается до 100% и остается неизменной до его жизненного цикла

Вопрос:

Существует ли более сложный способ опроса значения в файле отображения памяти, который включает минимальные издержки, минимальную задержку и минимальную загрузку ЦП? Обратите внимание, что каждая микросекундная задержка ухудшает производительность

Фрагмент кода

Фрагмент кода для модуля B (бесконечный цикл while, который опрашивает и читает из отображенного в памяти файла) ниже

FileChannel fc_pointer = new RandomAccessFile(file, "rw").getChannel();
      MappedByteBuffer mem_file_pointer =fc_pointer.map(FileChannel.MapMode.READ_ONLY, 0, bufferSize);
      long address_file_pointer = ((DirectBuffer) mem_file_pointer).address();


    while(true)
    {
        int value_from_memory_mapped_file = unsafe.getInt(address_file_pointer);

        if (value_from_memory_mapped_file .. is different from the last read value)
        {
            //do some operation.... 
        //exit the routine;
        }
        else
        {
            continue;
        }
}//end of while

1 ответ

Решение
  1. Высокая загрузка ЦП - это реальная стоимость с минимальной задержкой. В практической архитектуре, которая использует сигнализацию без блокировок, вы должны запускать не более пары пар потоков Consumer-Producer на процессорное гнездо. Одна пара съедает одно или два (одно ядро ​​на поток, если не прикреплено к одному ядру ЦП Intel с включенной гиперпоточностью) ядра почти полностью (поэтому в большинстве случаев вам приходится задумываться о горизонтальной масштабируемости при создании серверной системы со сверхнизкими задержками). для многих клиентов). Кстати, не забудьте использовать "набор задач", чтобы привязать каждый процесс к определенному ядру перед тестами производительности и отключить управление питанием.

  2. Хорошо известен трюк, когда вы блокируете Потребителя после долгого периода вращения без результата. Но вы должны потратить некоторое время, чтобы припарковаться, а затем отключить нить. Это, конечно, момент увеличения спорадической задержки, но ядро ​​процессора свободно, когда поток припаркован. См., Например: http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf (8.4.4 Синхронизация для Более длительные периоды) Также, хорошая иллюстрация для различных стратегий ожидания для java может быть найдена здесь: https://github.com/LMAX-Exchange/disruptor/wiki/Getting-Started (Альтернативные стратегии ожидания)

  3. Если вы говорите о миллисекундах (мс), а не микросекундах (мкс), вы можете просто попробовать связь через сокет TCP по шлейфу. Он добавляет около 10 мкс для передачи небольшого количества данных от производителя к потребителю, и это техника блокировки. У Named Pipes характеристики задержки лучше, чем у сокетов, но на самом деле они не блокируют, и вам придется снова создать нечто вроде спин-петли. Файлы с отображением в память + встроенный Unsafe.getXXX (который представляет собой один x86 MOV) по-прежнему является лучшим методом IPC с точки зрения как задержки, так и пропускной способности, поскольку он не требует системных вызовов при чтении и записи.

  4. Если вы по-прежнему будете использовать файлы без блокировки и файлы с отображением в памяти, а также прямой доступ через Unsafe, не забывайте о соответствующих барьерах памяти как для производителя, так и для потребителя. Например, "unsafe.getIntVolatile" вместо первого "unsafe.getInt", если вы не уверены, что ваш код всегда работает на более поздних версиях x86.

  5. Если вы видите неожиданную загрузку ЦП, которая должна составлять не более 30-40% (2 используемых ядра для 6-ядерного ЦП) на пару производитель-потребитель, вы должны использовать стандартные инструменты, чтобы проверить, что работает на других ядрах, и общую производительность системы., Если вы видите интенсивный ввод-вывод, связанный с вашим сопоставленным файлом, то убедитесь, что он сопоставлен с файловой системой tmpfs, чтобы предотвратить реальный дисковый ввод-вывод. Проверьте загрузку шины памяти и пропуски кэша L3 для "самых жирных" процессов, потому что, как мы знаем, время ЦП = (такты выполнения процессора + _memory_stall_cycles_) * время такта

И, наконец, довольно похожий и интересный проект с открытым исходным кодом с хорошим примером использования файлов с отображенной памятью: http://openhft.net/products/chronicle-queue/

Другие вопросы по тегам