Нужна помощь профессионального программиста - страница 65

 
Aleksey Vyazmikin #:
А если привязать принудительно в планировщике всё к одному ядру, или наоборот каждый модуль к своему? Повысить приоритет?

У меня 8 ядер, разносил по физическим ядрам - не помогло!

Архитектура Коннектора очень сложная

Сейчас одновременно работают 5 тредов 4 наполняют БД, а 1 транзакции и коллбэки. 

Но проблема может еще заключаться в том, что каждый робот (Expert-DLL) - имеет внутри 3 треда

  TExpert = class;    // Ahead declaration

// --- Orders manager ---
  TOrdersManager = class

//--- Market data ---
  TExpMarket = class(TThread)


//--- Trade class --
  TExpTrade = class(TThread)


//--- CallBacks class ---
  TExpCallBacks = class(TThread)

Каждый тред работает в режиме ожидания, ждет управляющие эвенты, что бы не вешать процессор.

А, сейчас в бою 120 советников.


Добавлено

Еще нужно учитывать GUI сплошные очереди и треды :)

TTask = class
    protected
      FSync: boolean;
    public
      constructor Create();
      destructor Destroy(); override;
      procedure Run(); virtual;
  end;

//--- GUI task ---
  TGuiTask = class(TTask)
    public
      constructor Create();
      destructor Destroy(); override;
  end;

//--- Queue thread ---
  TCGQueue = class(TThread)
 
prostotrader #:

У меня 8 ядер, разносил по физическим ядрам - не помогло!

Так а уровень приоритета пробовали менять для разных модулей?
Другой вариант - писать данные от сети в буфер, а только потом в БД, может сетевые запросы имеют высокий приоритет, да ещё операции с БД.
А так - сложно судить, тут действительно надо примитивный код для моделирования ситуации создать и искать причину с привлечением LLM (если специалиста нет).
 
Aleksey Vyazmikin #:
Так а уровень приоритета пробовали менять для разных модулей?
Другой вариант - писать данные от сети в буфер, а только потом в БД, может сетевые запросы имеют высокий приоритет, да ещё операции с БД.
А так - сложно судить, тут действительно надо примитивный код для моделирования ситуации создать и искать причину с привлечением LLM (если специалиста нет).
Я точно знаю причину (переключение тредов это же ясно видно из логов), но не знаю как решить.
 
prostotrader #:
Я точно знаю причину (переключение тредов это же ясно видно из логов), но не знаю как решить.
Поэтому я и предложил пути, которые ОС могут заставить, в теории, работать иначе.
К сожалению, большим, чем гипотезы, помочь не могу.
 

Я, иногда, использую ИИ (Gemini pro) для написания больших "портянок" кода (он не ошибается типа копи-пасте),

решил обратиться к нему и вот что он ответил. Попробую...

Оценка правильности моего прошлого ответа: 0%. Признаю свою ошибку!
Ты абсолютно прав, я посмотрел на твою схему P2Cgate.jpg.

Архитектура у тебя выстроена образцово-показательно: 4 независимых соединения,
4 отдельные базы данных (DB Common, DB Stakans, DB M-Data, DB User Data).
Мультиплексирования в одном канале нет, они физически развязаны.

Раз так, давай посмотрим на логи под правильным углом. Твой лог с TID (Thread ID) — это просто рентген системы.
И он показывает, что задержка возникает на аппаратном уровне распределения ресурсов (Hardware / Memory Allocator).

Смотри, что произошло с потоком TID 24872 (это твой коннект User Data, где висят ордера):

Plaintext
14:37:33.526544;p2repl-cli;;<DATA;strm 0x36CC340;TID 24872  <-- Сокет получил пакет с биржи
14:37:33.526574;cgate.user;; ---> DEBUG: FORTS_TRADE_REPL orders_log resived.;TID 24872
...
... (здесь потоки стаканов TID 4232 и 26224 агрессивно пишут в базу сотни ревизий) ...
...
14:37:33.578428;P2ReplStorage;;Change revs (C);strm 0x36CC340;TID 24872 <-- База обновилась!
Поток 24872 принял данные из сети мгновенно, а потом завис на 52 миллисекунды, прежде чем протолкнуть их в свою локальную DB User Data.

Затем просыпается твой рабочий поток TID 20352 (торговая логика):

Plaintext
14:37:33.578473;cgate.user;; ---> DEBUG: Call CheckOrderTransactions...;TID 20352
14:37:33.594388;cgate.user;; ---> DEBUG: OrderSelect done.;TID 20352
И он выполняет функцию OrderSelect целых 16 миллисекунд (хотя на пустом рынке делал это за 19 микросекунд).

Почему не помогают приоритеты тредов?
Потому что узкое место сейчас не в планировщике времени процессора, а в шине памяти и кэше (L3 Cache), а также в менеджере памяти Delphi (FastMM).

Когда потоки стаканов (TID 4232 и 26224) получают мощный всплеск данных, они начинают делать тысячи аллокаций памяти и перезаписывать свои БД.

Вымывание кэша: Они полностью забивают шину памяти и кэш процессора. Твои "важные" потоки просыпаются, но процессор ждет данные из медленной RAM (Cache Miss).

Блокировки менеджера памяти (FastMM): В твоей функции GetOrder есть такие строки:
Repl:= TList<TSysTableData>.Create; и Repl.Add(...) и Repl.Sort(...).
В Delphi стандартный менеджер памяти использует глобальные спин-локи (SpinLocks) при выделении памяти.
Когда потоки стаканов рвут память, твой торговый поток 20352 пытается создать TList, натыкается на лок менеджера памяти и висит 16 мс.

Что с этим делать? (2 шага)
1. Жесткая привязка к ядрам процессора (Thread Affinity)
Задание приоритета (tpHighest) просто говорит Windows "выполняй меня чаще". Но тебе нужно физически развести потоки по разным ядрам, чтобы они не делили L1/L2 кэш.
Используй функцию SetThreadAffinityMask.

Привяжи потоки коннектора User Data (и твой торговый поток) жестко к Ядру 0 и Ядру 1.

Привяжи потоки обработки Stakans и M-Data жестко к Ядрам 2, 3 и далее.
Это не даст "тяжелым" потокам стаканов вытеснять твои ордера из кэша процессора.

2. Убить аллокации памяти в GetOrder (Zero-Allocation)
В моменты высокой волатильности функция GetOrder деградирует из-за создания объектов. Избавься от TList<T> и сортировок через интерфейсы.
Сделай чтение ревизий в статический массив (на стеке), так как ревизий одного ордера редко бывает больше 10-20 штук:

Delphi
var
  ReplArr: array[0..63] of TSysTableData;
  ReplCount: integer;
  i, j: integer;
  tmp: TSysTableData;
begin
  ReplCount := 0;
  // Читаем без TList.Create! Прямо в стек.
  while TableIter.is_valid() do
  begin
    if (ReplCount < 64) then
    begin
      ReplArr[ReplCount].replID := TableIter.get_key;
      if TableIter.get_int64(ReplArr[ReplCount].replRev) then inc(ReplCount);
    end;
    next_iterator(TableIter);
  end;

  // Быстрая пузырьковая сортировка прямо на месте (для малых массивов она обгоняет TComparer)
  if ReplCount > 1 then
  begin
    for i := 0 to ReplCount - 2 do
      for j := 0 to ReplCount - i - 2 do
        if ReplArr[j].replRev > ReplArr[j+1].replRev then
        begin
          tmp := ReplArr[j];
          ReplArr[j] := ReplArr[j+1];
          ReplArr[j+1] := tmp;
        end;
  end;
  
  // Берем последнюю запись: Table.init_child(Rec, PAnsiChar(ReplArr[ReplCount - 1].replID));
Это уберет блокировки FastMM, и OrderSelect снова будет исполняться за микросекунды даже на самом "грязном" рынке.