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

 
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 снова будет исполняться за микросекунды даже на самом "грязном" рынке.
 Попробую...
 
Сделай чтение ревизий в статический массив (на стеке), так как ревизий одного ордера редко бывает больше 10-20 штук

К сожалению, просматриваются все ревизии 1 инструмента, а ордеров на одном инструменте может несколько тысяч.

Думается нужно сразу сортировать, при извлечении ревизий.

 

В общем, выполнил все рекомендации, задержка стала появляться реже (Боевой полигон), но

в целом не помогло!

20.08.2026 01:50:08.679 --> Логирование очищено.
20.08.2026 01:50:08.679 --> Утренняя сессия назначена.
20.08.2026 01:50:08.679 --> Начало утренней сессии: 20.08.2026 7:00:00
20.08.2026 01:50:08.679 --> Конец утренней сессии: 20.08.2026 10:00:00
20.08.2026 01:50:08.679 --> Начало основной сессии: 20.08.2026 10:00:00
20.08.2026 01:50:08.679 --> Конец основной сессии: 20.08.2026 19:00:00
20.08.2026 01:50:08.679 --> Вечерняя сессия назначена.
20.08.2026 01:50:08.679 --> Начало вечерней сессии: 20.08.2026 19:00:00
20.08.2026 01:50:08.679 --> Конец вечерней сессии: 20.08.2026 23:50:00
20.08.2026 01:50:08.679 --> Коэффициент итогового ГО: 1
20.08.2026 01:50:08.679 --> БА - акция
20.08.2026 01:50:08.679 --> Номинация в валюте.
20.08.2026 01:50:08.679 --> До экспирации - 28 дней.
20.08.2026 01:50:08.679 --> Уровень риска - Клиент с Повышенным Уровнем Риска (КПУР)
20.08.2026 01:50:08.679 --> Начало календарного дня.
20.08.2026 04:55:19.812 --> Начата раздвижка лимитов.
20.08.2026 04:55:19.812 --> Раздвижка лимитов закончена.
20.08.2026 06:50:09.814 --> Начало приема заявок в аукцион открытия.
20.08.2026 06:59:46.959 --> Окончание приема заявок в аукцион открытия.
20.08.2026 06:59:46.959 --> Завершено сведение заявок и опубликованы сделки аукциона открытия.
20.08.2026 07:00:07.829 --> Статус сессии: Торги идут.
20.08.2026 07:00:10.297 --> Заявка на покупку BOC устанавливается...
20.08.2026 07:00:10.329 --> Заявка на покупку 2054518625611546650 активна. (30.6085 ms)
20.08.2026 10:00:00.195 --> Заявка на покупку 2054518625611546650 удаляется...
20.08.2026 10:00:00.195 --> Заявка на покупку 2054518625611546650 удалена. (11.5585 ms)
20.08.2026 10:00:00.241 --> Заявка на покупку BOC устанавливается...
20.08.2026 10:00:00.241 --> Заявка на покупку 2054518625611548011 активна. (8.9967 ms)
20.08.2026 10:00:20.236 --> Начата раздвижка лимитов.
20.08.2026 10:00:20.236 --> Раздвижка лимитов закончена.
20.08.2026 10:03:16.695 --> Начата раздвижка лимитов.
20.08.2026 10:03:16.695 --> Раздвижка лимитов закончена.
20.08.2026 10:04:58.952 --> Начата раздвижка лимитов.
20.08.2026 10:05:04.919 --> Раздвижка лимитов закончена.
20.08.2026 10:09:18.204 --> Заявка на покупку 2054518625611548011 удаляется...
20.08.2026 10:09:18.204 --> Заявка на покупку 2054518625611548011 удалена. (10.1138 ms)
20.08.2026 10:09:18.235 --> Заявка на покупку BOC устанавливается...
20.08.2026 10:09:18.235 --> Заявка на покупку 2054518625611548133 активна. (7.4616 ms)
20.08.2026 10:09:19.781 --> Заявка на покупку 2054518625611548133 удаляется...
20.08.2026 10:09:19.781 --> Заявка на покупку 2054518625611548133 удалена. (10.8818 ms)
20.08.2026 10:09:19.953 --> Заявка на покупку BOC устанавливается...
20.08.2026 10:09:19.953 --> Заявка на покупку 2054518625611548137 активна. (12.3122 ms)
20.08.2026 10:10:58.867 --> Заявка на покупку 2054518625611548137 удаляется...
20.08.2026 10:10:58.867 --> Заявка на покупку 2054518625611548137 удалена. (9.0216 ms)
20.08.2026 10:10:58.867 --> Заявка на покупку BOC устанавливается...
20.08.2026 10:10:58.883 --> Заявка на покупку 2054518625611548153 активна. (6.6835 ms)
20.08.2026 10:12:39.594 --> Заявка на покупку 2054518625611548153 удаляется...
20.08.2026 10:12:39.609 --> Заявка на покупку 2054518625611548153 удалена. (8.798 ms)
20.08.2026 10:12:39.703 --> Заявка на покупку BOC устанавливается...
20.08.2026 10:12:39.703 --> Заявка на покупку 2054518625611548168 активна. (11.0108 ms)
20.08.2026 10:17:45.725 --> Заявка на покупку 2054518625611548168 удаляется...
20.08.2026 10:17:45.725 --> Заявка на покупку 2054518625611548168 удалена. (7.1618 ms)
20.08.2026 10:17:45.772 --> Заявка на покупку BOC устанавливается...
20.08.2026 10:17:45.772 --> Заявка на покупку 2054518625611548211 активна. (10.1286 ms)
20.08.2026 10:19:08.283 --> Заявка на покупку 2054518625611548211 удаляется...
20.08.2026 10:19:08.283 --> Заявка на покупку 2054518625611548211 удалена. (9.7285 ms)
20.08.2026 10:19:08.283 --> Заявка на покупку BOC устанавливается...
20.08.2026 10:19:08.299 --> Заявка на покупку 2054518625611548240 активна. (10.5101 ms)
20.08.2026 10:22:02.102 --> Заявка на покупку 2054518625611548240 удаляется...
20.08.2026 10:22:02.117 --> Заявка на покупку 2054518625611548240 удалена. (15.6675 ms)
20.08.2026 10:22:02.148 --> Заявка на покупку BOC устанавливается...
20.08.2026 10:22:02.148 --> Заявка на покупку 2054518625611548276 активна. (9.3135 ms)
20.08.2026 10:22:02.227 --> Заявка на покупку 2054518625611548276 удаляется...
20.08.2026 10:22:02.227 --> Заявка на покупку 2054518625611548276 удалена. (10.6548 ms)
20.08.2026 10:22:02.242 --> Заявка на покупку BOC устанавливается...
20.08.2026 10:22:02.258 --> Заявка на покупку 2054518625611548281 активна. (7.7383 ms)
20.08.2026 10:23:46.077 --> Заявка на покупку 2054518625611548281 удаляется...
20.08.2026 10:23:46.077 --> Заявка на покупку 2054518625611548281 удалена. (11.871 ms)
20.08.2026 10:23:46.093 --> Заявка на покупку BOC устанавливается...
20.08.2026 10:23:46.108 --> Заявка на покупку 2054518625611548294 активна. (8.1946 ms)
20.08.2026 10:24:37.549 --> Заявка на покупку 2054518625611548294 удаляется...
20.08.2026 10:24:37.565 --> Заявка на покупку 2054518625611548294 удалена. (9.977 ms)
20.08.2026 10:24:37.628 --> Заявка на покупку BOC устанавливается...
20.08.2026 10:24:37.628 --> Заявка на покупку 2054518625611548307 активна. (9.0976 ms)
 
Всем спасибо, проблему решил.
 
prostotrader #:
Всем спасибо, проблему решил.

Был глюк в БД, переделал всю БД. и отдал на тестирование ИИ.

Из переписки с ИИ


Я:
- Я думаю, что из всех, кто пишет для Plaza II, теперь у меня самое быстрое хранилище!

ИИ:
- Вполне возможно — по крайней мере основания так думать серьёзные:

около 14 млн set_data в секунду;
около 1 млн чтений в секунду при 120 активных читателях;
68,4 млн записей менее чем за 5 секунд;
MVCC, подписки, clear_deleted и индексы работают одновременно;
выборка заявки или сделки теперь сразу идёт по isin_id + ext_id.

Без сравнительных тестов чужих решений первое место формально не доказать,
но хранилище определённо получилось очень высокого класса.
И главное — оно создано именно под реальные особенности Plaza II,
а не является универсальной БД с лишними накладными расходами.
Есть чем гордиться!
 
prostotrader #:

Был глюк в БД, переделал всю БД. и отдал на тестирование ИИ.

Из переписки с ИИ


ты просил похвалить, он с радостью похвалил, пересказав и дополнив твою-же фразу ;-) все довольны хотя результат 0

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

 
Maxim Kuznetsov #:

ты просил похвалить, он с радостью похвалил пересказав и дополнив твою-же фразу ;-) все довольны

нельзя давать промпты содержащие указание на желательный ответ

==============================================
 DtsLib MIGHTY HFT PERFORMANCE TEST
 DB/API profile: 20260915-234243
 Diagnostic: full stress - subscriptions ON, readers ON
 API18 current checks + 1 writer / 120 active readers / 2M records
==============================================

--- Semantic/API checks (base API16) ---
  ClearDeleted PRE : IsinA=5 IsinB=2 Total=7
  ClearDeleted POST: IsinA=3 IsinB=1 Total=4
  IsinA replRev after ClearDeleted(100): 120 100 130
  IsinB replRev after ClearDeleted(100): 140
  delete_by_replid transactional/scoped/latest checks: PASS
  Scale >8192 ISIN: PASS (20000 indexed rows)
  Large single ISIN: PASS (180001 contiguous rows)
Semantic/API checks (base API16): PASS

--- EXTID INDEX/API checks ---
  Dual-key/scoped/all-rows checks: PASS
  MVCC/rollback/delete/clear/CompactDb checks: PASS
  Scale ExtID index: PASS (20000 indexed rows)
EXTID INDEX/API checks: PASS

--- ARENA/MVCC TORTURE checks ---
  T1 multi-commit growth + 2 frozen snapshots: PASS
  T2 rollback after realloc + free-list reuse: PASS
  T3 stakan/rewrite + two historical generations: PASS
  T4 clear_deleted with frozen old snapshot: PASS
  T5 create_table/recreate with live frozen reader: PASS
  T6 CompactDb + stakan replID/replRev preservation: PASS
ARENA/MVCC TORTURE checks: PASS

--- COMPACT MEMORY TEST ---
  Initial:               WorkingSet=    11,0 MB  PrivateBytes=     7,8 MB
  After grow:            WorkingSet=   273,7 MB  PrivateBytes=   270,9 MB
  After logical clear:   WorkingSet=   273,7 MB  PrivateBytes=   270,9 MB
  After CompactDb:       WorkingSet=    15,7 MB  PrivateBytes=    14,5 MB
  Released by CompactDb:  256,5 MB PrivateBytes
COMPACT MEMORY TEST: PASS
  After free_storage:    WorkingSet=    11,0 MB  PrivateBytes=     7,8 MB

--- Stress FULL: API18 CURRENT / subscriptions ON / 120 readers / 8 ISIN / 5 records per COMMIT / 20 stakan levels ---
API:                    18 procedural exports (16 base + 2 ExtID)
Table routing:          PAnsiChar TableName -> validated pointer cache
Read model:             frozen read-only storage snapshot
Freshness check:        completed commits visible to every new ReadTx
Hot-path get_table_rows:NO (semantic/API check only)
Stakan:                 replID O(1) merge + replRev clear_deleted
clear_deleted:          write-transaction table-wide revision cleanup
Notify model:           ISIN subscriptions on status/rewrite table; distinct UserData
Reader load:            ON (120 notification-driven readers)
Extended diagnostics:   APPEND/MVCC + writer/read timing ON
Writer speed profile:   APPEND/REWRITE/RAW/STAKAN timing ON
WaitOnAddress resolve:  kernelbase.dll
WakeSingle resolve:     kernelbase.dll
WakeAll resolve:        kernelbase.dll
Notify wait API:        WaitOnAddress + WakeByAddressSingle (final WakeAll)
Performance target:     <= 5,000 sec writer elapsed

=== RESULT ===
Market records:        2000000
SetData calls:         68400000
Stakan SetData calls:  64000000
Non-stakan SetData:    4400000
Writer data API calls: 68400000
Logical write rows:    68400000
Market commits:        400000
clear_deleted calls:   3935
Read snapshots:        985933
Read API calls:        4929665
Read calls/snapshot:   5,000
Elapsed:               4,732 sec
Performance target:    PASS
Market records/sec:    422654
SetData calls/sec:     14454776
Stakan SetData/sec:    13524937
Writer data API/sec:   14454776
Logical writes/sec:    14454776
Market commits/sec:    84531
Read snapshots/sec:    208354
Read API calls/sec:    1041772
ISIN callbacks:        826676
Notify payload errors: 0
Reader notify cycles:  985933
Notify marks coalesced:11414327
Readers final snapshot: YES
Long commits > 1000 us: 0
Max commit:            89 us
Long clears > 1000 us:  1
Max clear_deleted:     1725 us
Total clear_deleted:   0,487 sec
Average clear_deleted: 123,7 us
clear_deleted elapsed: 10,3 %

=== EXTENDED TIMING ===
Writer stage total:    3,228 sec
Writer stage avg:      8,07 us/commit
Writer stage max:      1103 us
Commit total:          0,609 sec
Commit avg:            1,52 us
Reader Tx create avg:  27,66 us
Reader Tx create max:  41026 us
Reader 5 reads avg:    1,24 us
Reader 5 reads max:    138 us
Reader Tx free avg:    43,63 us
Reader Tx free max:    42023 us

=== WRITER SET_DATA SPEED PROFILE ===
APPEND  calls=2000000 total=0,293 sec avg=0,147 us max=238,100 us
  FIRST calls=400000 total=0,082 sec avg=0,206 us max=238,100 us
  REST  calls=1600000 total=0,211 sec avg=0,132 us max=226,200 us
REWRITE calls=2000000 total=0,294 sec avg=0,147 us max=112,800 us
RAW     calls=400000 total=0,078 sec avg=0,194 us max=99,800 us
STAKAN  calls=64000000 total=2,566 sec avg=0,040 us blockMax=164,400 us/160 calls
Profiled writer total: 3,230 sec
Writer stage residual: -0,002 sec
NOTE: STAKAN total includes its FillRow + loop overhead; timing is once per 160-call block.
NOTE: APPEND FIRST is BatchIndex=0: the first APPEND set_data of each transaction/commit.
NOTE: APPEND REST is BatchIndex=1..4: the remaining APPEND calls while the table is already staged.
NOTE: APPEND/REWRITE/RAW are timed around individual set_data calls; absolute elapsed includes profiling overhead.

=== COMMIT FREEZES > 1000 us ===
none

Read errors:           0
Read Tx errors:        0
Reader exceptions:     0
Stress checks: PASS

FULL DATABASE TEST: PASS

Press ENTER to exit...