Замер тут рэдка складаецца з аднаго агента. Звычайная праца ў мяне гэта 12 штук, на якія трэба паглядзець, а з кожнай па 3 дзеянні: прачытаць крыніцу, потым выцягнуць лікі і зверыць іх назад з ёй. Выходзіць 36 агентаў і запускае іх файл на JavaScript з цыклам унутры. Калі ў мяне пытаюцца, як гэта зроблена, я называю такую зборку дэтэрмінаванай. На гэтым тыдні я сеў разабрацца, якую частку прагону гэтае слова ахоплівае.
Патрэбна мне ад яго аднаўленне. Прагон з 36 каштуе грошай і прыкладна 20 хвілін чакання. Таму калі я праўлю сказ у трэцяй стадыі, я хачу заплаціць за трэцюю стадыю і ўзяць астатніх 24 агентаў з журнала мінулага прагону. У гэтым усё абяцанне. Трымаецца яно толькі калі другі прагон просіць тое самае ў тым самым парадку і я ні разу не правяраў, што гэта так.
Стэнд
У аркестратары 118 радкоў. Там ёсць agent(), parallel(), які ставіць бар’ер паміж стадыямі, pipeline(), які не ставіць, семафор FIFO на столь паралельнасці і журнал, куды трапляе кожны выклік. Нішто ў ім не звяртаецца да мадэлі. Агент там чакае зададзеную колькасць мілісекунд і вяртае радок, сабраны з уласнага імя.
Мілісекунды сапраўдныя. Я запісаў 30 выклікаў праз claude CLI 2.1.235 на claude-opus-5, з пустой папкі. CLAUDE.md у рабочым каталогу трапляе ў промпт моўчкі і зрушвае адказ і час. Аднаслоўныя адказы далі ў сярэднім 6742 мс. Адказы на 700 слоў далі 40164 мс, сярэдняя вага стала на 11043. Увесь набор уклаўся ў прамежак ад 5790 да 54520 мс і я прайграю гэтыя працягласці падзеленымі на 20, каб сетка з 36 заканчвалася за секунды. Node 22.23.1, 4 ядры.
Заадно я паставіў лік на мяжу працэсу, бо і гэтага раней не рабіў. Той жа прагон сапраўднымі дачэрнімі працэсамі замест таймераў заняў 5674 мс супраць 5497, гэта значыць 4.9 мс на агента. Паўтор даў 5730 і 6.5 мс. Побач з агентам, якому трэба ад 6 да 55 секунд, любы з гэтых лікаў нішто. Толькі цану планавальніка гэта не мерае. Семафор і журнал працуюць у абодвух плячах і скарачаюцца, так што цыкл вакол агентаў застаецца незмераным.
Бар’ер супраць канвеера
На столі 4 версія з бар’ерам займае 8514 мс, версія з канвеерам 6986. Калі зняць столь, каб усе 36 ішлі разам, выходзіць 6644 супраць 5053, разрыў у 23.9 працэнта. Абедзве формы ў гэты момант стаяць за 14 мс ад уласнай мяжы. Бар’ер не можа ўкласціся хутчэй за суму самых павольных агентаў па стадыях, а гэта 6630 мс на такой сетцы. Канвеер не можа ўкласціся хутчэй за самы павольны ланцужок з 3, а гэта 5044.
Я хацеў пераканацца, што выйгрыш дае менавіта разброс працягласцей, таму я прагнаў тую ж сетку з усімі працягласцямі, выстаўленымі ў сярэднюю. Бар’ер пасля гэтага не каштуе нічога на столях 4, 16 і 36. На столі 8 ён усё роўна каштуе 16.7 працэнта, бо 12 элементаў не дзеляцца на 8 і у апошняй хвалі кожнай стадыі працуюць 4 агенты, пакуль 4 слоты прастойваюць.
Замер, які мяне падмануў
Далей я прагнаў тую ж сетку 30 разоў на столі 4 і захэшаваў парадак, у якім рабіліся выклікі. Абедзве формы вярнулі 1 адрозны парадак на 30 прагонаў. Парадкаў завяршэння выйшла 2 з 30 у бар’ера і 1 у канвеера. Паўтор усяго замеру даў у бар’ера таксама 1, значыць і тая 2 была шумам таймера. Ідэальная паўтаральнасць. Я верыў у яе хвілін 10, пакуль не заўважыў, што мае падробленыя агенты заўсёды займаюць роўна адзін і той жа час. Сапраўдныя так не ўмеюць. Я адправіў адзін і той жа промпт 24 разы ў 2 заходы і адказ прыходзіў у прамежку ад 7347 да 10586 мс.
Так што я вярнуў гэты разброс на месца. Кожная прайграваная працягласць цяпер множыцца на адзін з тых 24 памераных каэфіцыентаў. З дрыжаннем бар’ер па-ранейшаму дае 1 адрозны парадак выкліку на 30 прагонаў, а канвеер дае 26. Парадак завяршэння ў абодвух 30 з 30.
Прычына ў тым, адкуль бярэцца нумар выкліку. У бар’ера ўсе 12 выклікаў стадыі робяцца адным сінхронным праходам, таму выклік нумар 0 гэта элемент 0 стадыі 0 у любым прагоне, які наогул здарыцца. У канвеера выклік робіцца, калі вярнулася папярэдняя стадыя гэтага элемента, таму нумар дастаецца таму, хто скончыў першым.
Журнал
У майго журнала ключ гэта нумар выкліку. Ён паўтарае самы доўгі прэфікс, які яшчэ супадае. Для скрыпта, які і ёсць цыкл, гэта відавочнае правіла. З бар’ерам яно працуе роўна як абяцана. Без правак выходзіць 36 трапленняў з 36, праўка апошняй стадыі дае 24, праўка сярэдняй дае 12, праўка першай дае 0.
Канвеер з тым жа ключом вяртае 12 з 36, калі не папраўлена наогул нічога і ўваходы тыя самыя. 24 агенты купляюцца другі раз. Першыя 12 супадаюць заўсёды, бо гэта тыя 12 выклікаў першай стадыі, якія канвеер робіць адразу, да таго як хоць нешта паспявае вярнуцца. З выкліку 12 журнал і адноўлены прагон разыходзяцца. 2 свежыя прагоны таго ж скрыпта разыходзяцца ўпершыню на выкліку 13, а адноўлены разыходзіцца на 1 выклік раней. Прычына вастрэйшая, чым шум. Паўтораны агент вяртаецца імгненна, таму ўсе выклікі другой стадыі ідуць па парадку элементаў і нумар 12 дастаецца элементу 0. Я праверыў гэта на 60 сетках і на ўсіх 60 трымалася. У журнале нумар 12 належыць таму, хто выйграў гонку, а на гэтай сетцы гэта быў элемент 1.
Лік 12 гэта часты выпадак, а не закон. З тых жа 60 сетак 47 парваліся роўна на 12, 12 парваліся на 13 і 1 на 14, бо часам элемент 0 сваю гонку ўсё-такі выйграе.
Ключ па задачы замест ключа па пазіцыі гэта выпраўляе. Абедзве формы пасля гэтага вяртаюць 36 з 36 на некранутым скрыпце і 24 з 36 пасля праўкі любой адной стадыі. Для працы з 3 стадый гэта сумленны лік, бо змененая стадыя і ёсць траціна працы. Трымаецца ён толькі пакуль промпт агента не цягне ў сабе тэкст папярэдняга. Палова маіх сапраўдных стадый робіць роўна гэта, так што іх ключ мяняецца, як толькі мяняецца адказ папярэдняй стадыі.
Самі агенты не паўтараюцца наогул
Той жа промпт, 24 разы, даў 24 розныя адказы даўжынёй ад 375 да 473 знакаў. Адноўлены прагон таму кладзе старыя адказы побач з новымі. Гэта ніколі не той прагон, які атрымаўся б, каб я пачаў нанова. Нічога ў выніку тут не паўтараецца, таму адзінае, што журнал здольны ўтрымаць, гэта план і ключ.
Чаго я не правяраў
Аркестратар не кліча мадэлі, таму ўсё гэта пра планаванне запускаў і нічога пра якасць. Адна машына, 4 ядры, без сеткі і без лімітаў. Ніводзін агент на стэндзе не падае, а гэта якраз той выпадак, дзеля якога аднаўленне і патрэбнае. Ліку для яго ў мяне няма. Адна сетка, адно зерне, 3 паўторы на кропку ў часавым замеры супраць 30 у замеры парадку і 10 у замеры аднаўлення. Гэтыя 10 прагонаў аднаўлення чытаюць адзін і той жа журнал, таму разысціся паміж сабой яны і не маглі. Цана самога планавальніка нідзе не аддзеленая ад цаны працэсу, які ён запускае. Ключ па змесце ў мяне да таго ж проста імя задачы, а сапраўдны быў бы хэшам промпта і опцый.
Свой журнал я перавёў на ключ па змесце і канвеер пакінуў як быў. Наступнае, што варта памераць, гэта што з усім гэтым робіць падзенне пасярэдзіне.