tl;dr не используйте ни diffhtml, ни morphdom. Они даже на синтетике не могут показать хорошие результаты.
1. Открываем http://mathieuancelin.github.io/js-repaint-perfs/ в режиме инкогнито дабы исключить влияние плагинов браузера. (Прим. есть и более сложные способы получить более аккуратные результаты)
2. Chrome devtools. Sources. Press Escape. Rendering. FPS meter. Включаем.
3. Открываем http://mathieuancelin.github.io/js-repaint-perfs/diffhtml/index.html на разрешении 1920*1080. (Дабы потом результаты не расходились с таблицей ниже)
4. Ждем 10 сек устаканивания fps.
ВНИМАНИЕ. На каждый новый тест закрываем все вкладки инкогнито и открываем снова.
В статье:
diffhtml 14-17
morphdom 11-17
mainstream:
(тут должен быть jquery)
vanilla-optimized 31-33
React 31-34
Backbone 25-27
Angular 28-31
Angular 2.0 Alpha 38-40
vuejs 35-36
ember 0-1 (нужен тест с последней версией, не верю)
Aurelia 33-36 ( http://jdanyow.github.io/aurelia-dbmonster/ )
hipsters:
riot 21-23
Elm 32-36
vidom 41-44
Monkberry 43-45
Inferno 40-43
frzr 40-42
моя поделка 40-43
вне конкурса:
canvas 60
Software:
Chrome 52.0.2743.116 m.
Windows 7 64bit
Hardware:
CPU : AMD A10-7850K (не самый плохой процессор https://www.cpubenchmark.net/high_end_cpus.html 5548 где-то сзади в high end)
GPU : AMD 7970 (не самая плохая видеокарта http://www.videocardbenchmark.net/high_end_gpus.html 5246 где-то top 42)
А теперь представьте как всё это будет тормозить на чём-то по-слабее
Не удовлетворяемся результатами т.к. библиотека развивается. Может что-то пофиксили.
1. Скачиваем содержимое http://mathieuancelin.github.io/js-repaint-perfs/diffhtml/index.html (chrome сохранить страницу целиком)
2. Забрасываем в папку dbmon (diffHTML)_files https://raw.githubusercontent.com/tbranyen/diffhtml/master/dist/diffhtml.js
3. Патчим до https://gist.github.com/vird/ff236a38b6b72e23a9e87c88e38760cd
4. Запускаем по инструкции выше.
Получаем тот же fps.
morphdom, честно, было лень патчить и проверять.
Замечания к методике тестирования и результатам принимаются.
P.s. Есть еще todomvc benchmark. Кто бросит ссылку на большую сборную солянку 20+, тому отдельное спасибо. Ато на http://todomvc.com/ только примеры.
Ну из того, что первым приходит на ум.
Алгоритм 1.
1. Движем окно вдоль какого-то направления (X или Y)
2. На каждую итерацию добавляем 5 пикселей и убираем 5 пикселей.
3. Те 5 пикселей, которые добавляем, сортируем сразу и храним все 5 итераций сортированными (3 операции для опеределения предварительных min, max, avg, 3 операции для определения самого минимального, 3 операции для определения самого максимального, 3 операции дабы отсортировать оставшиеся 3 элемента. Итого 12 операций самым наивным методом)
4. Дальше у нас есть массив из 25 элементов, которые частично отсортированы. Утверждение 1. Из всех минимумов только самый большой может быть средним. (аналогичное утверждение для максимума). Используя две операции min и две операции max находим нужные элементы. Эту и все дальнейшие операции выполняем в другом буфере.
5. Необходимо найти среднее из 5*3+2 элементов, о которых мы кое-что уже знаем т.к. 5 троек у нас отсортированы. Расположим эти элементы следующим способом сначала самый минимум(из шага 4) потом все минимумы из троек, потом средние из троек, потом максимумы из троек, потом самый максимум(из шага 4). Дальше подбираем необходимое количество итераций следующего алгоритма:
Проходимся по тройкам чисел с пересечением в 1 число. Ддля каждой тройки осуществляем сортировку этих трёх элементов. После такого прохода справа будет самый большой элемент. Его мы теперь можем гарантированно игнорировать. Проходимся влево. Слева будет самый минимальный элемент, его тоже можно игнорировать. На краях можно сэкономить одну операцию т.к. нам не надо записывать.
Первая итерация будет проходить на буфере размером N=5*3+2. Количество операций сравнения 3 на 3 числа таких троек будет (N-1)/2 = 8. Т.е. 8*3 операции. 2 прохода -> 8*3*2. Минус на краях = 8*3*2-2 = 46 операций.
Вторая итерация будет проходить на массиве на 2 элемента меньше. Сортируем до конца. Получаем точный ответ. Один из самых наивных подходов, но точный. Можно пробовать другой подход к сортировке.
Алгоритм 2.
Первые 4 операции идентичны алгоритму 1
5. Введём операцию сортировки троек. Мы будем сортировать центральный элемент и элементы, которые отстоят от него на +i -i. Применяя последовательно такую операцию мы добьемся момента, когда слева будут элементы меньше центрального, а справа будут элементы больше от центрального. Количество итераций для хорошей точности вычисляется либо аналитически, либо перебором.
Алгоритм 3.
Первые 2 операции идентичны алгоритму
3. Записываем только медианы каждой пятерки (т.е. по факту используем median_5, которая работает за меньше чем 12 операций)
4. Просто находим медиану из 5 медиан.
Данный алгоритм самый быстрый, но дает хуже всего точность и его нельзя принципиально сделать полностью точным.
Для простоты понимания blockchain это большая амбарная книга, где написано сколько у кого появлялось денег, сколько денег переходило из рук в руки и время этого события.
Каждый блок в blockchain'е служит следующим целям:
Поддерживать валидность системы. Т.е. никто не может потратить средства дважды, никто не может зайти в минус по своему балансу. Это достигается тем, что любой участник системы доказав, что он решил сложную математическую проблему, может подтвердить, что да, вот эти вот транзакции действительно валидны. После 6 подтверждений транзакция считается завершённой. За каждое подтверждение транзакции подписывающий получает fee.
Поддерживать тех, кто майнит. Решение задачи SHA256(SHA256(header)) < target сложная задача. Каждый нолик в начале имеет вероятность 0,5. Соответственно, чтобы найти решение у которого в начале 32 нолика нужно перебрать очень много вариантов. Вероятность, что случайно выбранный вариант будет верным является 1/(2^32). Это если target=1^(256-32). Это при самой маленькой difficulty=1. Чем больше мощности сети, тем больше difficulty. Так вот за каждый блок предоставляется награда которую получает тот, кто посчитал счастливый header. Задача сложная для решения, но очень простая для проверки.
Распределённая эмиссия средств.
Безопасность blockchain'а строится на том, что злоумышленник не сможет заполучить достаточно мощности сети, чтобы в короткий промежуток времени подписать транзакцию, которая нарушает правила амбарной книги (например, начислить себе 1М BTC). А если транзакция не будет подписана участниками сети, то её содержимое не имеет силы.
Безопасность транзакции строится на том, что у тебя есть закрытый ключ, которым ты подписываешь приказ перечислить другому кошельку столько-то денег. Адрес биткоин кошелька является открытым ключем. Итого все могут увидеть, что ты действительно владеешь закрытым ключем, но никто не может от твоего имени передавать деньги.
BTW.
median_5
Сортируем первые 3 элемента. 3 операции
Сортируем последние 3 элемента 2 операции, т.к. max нам не нужен т.к. это глобальный максимум.
Снова сортируем первые 3 элемента. 2 операции т.к. min нам не нужен т.к. это глобальный минимум
Смотрим на 3 центральных элемента и выполняем median3. 1 операция
3+2+2+1 = 8 операций.
Возможно, можно за меньшее количество операций.
Для попарных сравнений операций понадобится явно больше. Плюс я уже писал, что можно захватывать по 4 числа и сортировать их параллельно.
А конкретно в случае APU там ситуация еще лучше.
Рассмотрим выполнение kernel'а
1. Скопировать с медленной CPU памяти в быструю GPU.
2. Выполнить на быстрой GPU памяти.
3. Скопировать обратно в медленную CPU память.
Итак. У нас на самом-то деле bottleneck по пропускной способности никуда не делся. Мы не можем выполнять быстрее, чем скорость передачи CPU памяти. А вот задержка стала меньше.
А вот тут спешу разочаровать по поводу волшебности HBM1 памяти.
1. Посмотрите бенчмарки в играх. Поймите, что что-то не так.
2. Загляните в документацию AMD и посмотрите как правильно вынимать нужно с памяти данные, чтобы нормально использовать 4096битную шину.
2.1. Bank conflict'ы там по-жестче чем у nvidia (т.к. их бывает два типа и даже банально по битам получается что нужно разбрасывать массив в памяти сильнее, что для 1920*1080 нереально)
2.2. Вынимать нужно порциями по 16 байт (насколько я помню) (т.е. int4, или char16) это нужно переписать достаточно сильно многие алгоритмы, а если нет — мы не используем мощность HBM на полную)
Ждем HBM2 или Vega, может как-то сгладят недостатки.
С чего вы взяли, что обращение идет в CPU'шную память?
По умолчанию всё идет на быструю память видеокарты. Это нужно специально включать pinned memory, если нужен массив памяти больше, чем есть на видеокарте.
amd_median5 и median7 нету по понятной причине. Каждая операция работает с максимум 4 аргументами. amd_median3+amd_min3+amd_max3 достаточно, чтобы за 3 операции отсортировать 3 набора по 4 числа (т.к. int4).
По поводу производительности, посчитайте на досуге производительность AMD/$ и nvidia/$, в чистых tflops/$. Я за те же деньги поставлю больше мощности. В моем случае это 100% можно сделать т.к. у меня просто большой поток однотипной работы, которая не зависит друг от друга. (Прим. Да, это не всем подойдет)
Прим. Пишу из opencl огорода.
У AMD есть специальная инструкция для этого
https://www.khronos.org/registry/cl/extensions/amd/cl_amd_media_ops2.txt
amd_median3
Если не хватит, есть еще amd_min3, amd_max3.
В opencl есть char4. Это векторный тип данных который позволяет параллельно выполнять несколько операций. https://www.khronos.org/files/opencl-1-2-quick-reference-card.pdf судя по этому можно выполнять min(char4, char4), потому не надо помнить о вендорно-зависимой __vminu4.
Про bank conflict'ы ни слова. Хотя в принципе я тоже их игнорирую.
Если уже используется локальный буфер а для предзагрузки, то можно увеличить его в 2 раза и закачивать полностью только на первом такте, а все остальные докачивать только новую линию. Ну и тогда нужен индекс для того, чтобы ходить по такому буферу. На 5*5 это вообще жёстко.
Общие пожелания
for (int t = 0; t < 4; ++t)
//...
for (int z2 = 0; z2 < 3; ++z2)
for (int z1 = 0; z1 < 3; ++z1)
Три цикла фиксированного размера. Ну тут просится либо pragma unroll, либо написать свой небольшой кодогенератор, который сгенерирует развёрнутый цикл с подставленными индексами.
if (idy < H — 1)
не рекомендуется использовать в принципе либо использовать тернарные операции, либо
select (который лично мне не нравится за необходимость жёстко задавать определённые типы).
idy * W + idx лишнее умножение
Правильно делать dst_offset = idx
И потом dst_offset += W каждую итерацию.
То же про 3 * z2 + z1 и другие места
Если умножение сильно нужно, но там гарантированно не используется все 32 бита, то есть mul24 и mad24 специально для работы с индексами. Работают в 4 раза быстрее чем честное 32-битное умножение.
Вместо if (idx < W — 1) {} лучше писать if (idx >= W — 1) return; Как минимум код чище, минус одна лесенка.
Дополнительная оптимизация. Убираем const int H, const int W, т.к. скорее всего у нас будет фиксированный размер картинки (не всегда так бывает) и вписываем как константы прямо в код kernel'а получаем еще +5% производительности.
Было ~50 фич, которые переводили слово в некоторый диапазон.
1. Длина слова от 3 до 30
2. ASCII Код буквы на позиции от 0 до 20 (' преобразовывал в `)
3. Количество вхождений буквы a-z`
Для каждой фичи для 8M слов подсчитан результат и закэширован.
В начале есть дерево из 1 узла. Точность решения 50%
Дальше перебираются все возможные варианты как можно поделить этот узел на N узлов при помощи какой-то фичи.
Выбирается фича и узел которые имеют больше всего сумму abs(pos-neg) на всех узлах, которые образуются после разделения узла на много узлов.
А теперь всё тоже самое, только 8000 раз.
Главное, чтобы решение не вылетало за пределы 512 Мб оперативки. (Кажется такой дефолтный лимит по памяти)
Мне очень интересно какую методологию тестирования будут использовать. Насколько я понял минимальным гарантированным квантом является один блок по 100 слов.
Я просто оставлю это здесь.
Судя по комментариям участники не брезговали использовать читерскими методами подкручивания статистик.
Например накопление словаря или статистик аргументов вызова функции.
Т.к. цитирование переписки с организаторами здесь поощряется, то
Некоторые детали запуска скрипта
Действительно, на каком-то по счёту блоке компилятор-оптимизатор V8 вешается. Какой-то баг в версии 6.0.0. Если такое будет в с финальными решениями, будем решать вопрос или об апгрейде версии Node.js, или о разделении работы на несколько запусков программы.
Я ничего не берусь говорить, но читая предыдущие отзывы о конкурсах от hola, я специально очень сильно расспрашивал про методику проведения и оценки. Я пока сделаю гипотезу, что скрипт, который будет проверять решения будет сбрасывать состояние решения каждого участника каждые 1000 блоков, к примеру. Потому все решения, которые базируются на запоминании потока вызовов внезапно превратятся в тыкву. У меня в кармане тоже были читерские методы подкрутки решения, например корректировка до априорной вероятности, но я не рискнул их включать в решение из-за боязни изменения условий тестирования в последний момент. Ждем комментариев от организаторов.
1. 1000 блоков вообще ничто. Я начинаю чему-либо верить от 50k блоков
2. Это набросок, который потом ушёл под тюнинг на видеокарту, и там уже были совсем другие коэфициенты.
Перепосчитал
Решение
data.gz
function c(a,l,h){return(a<l)?0:(a>h)?h-l:a-l};r=function(w){w=w.replace(/\'/g,'`');f1=c(w.length,3,32);return!!([0,0,0,1,1,1,1,1,1,1][f1])}
solution.js
var a;module.exports={init:function(b){a=eval(b.toString())},test:function(b){return a(b)}}
test 0-200k 61.98225%
crossvalidation 200k-400k 61.9829%
http://mathieuancelin.github.io/js-repaint-perfs/angular/opt.html
http://mathieuancelin.github.io/js-repaint-perfs/angular-track-by/
1. Открываем http://mathieuancelin.github.io/js-repaint-perfs/ в режиме инкогнито дабы исключить влияние плагинов браузера. (Прим. есть и более сложные способы получить более аккуратные результаты)
2. Chrome devtools. Sources. Press Escape. Rendering. FPS meter. Включаем.
3. Открываем http://mathieuancelin.github.io/js-repaint-perfs/diffhtml/index.html на разрешении 1920*1080. (Дабы потом результаты не расходились с таблицей ниже)
4. Ждем 10 сек устаканивания fps.
ВНИМАНИЕ. На каждый новый тест закрываем все вкладки инкогнито и открываем снова.
Не удовлетворяемся результатами т.к. библиотека развивается. Может что-то пофиксили.
1. Скачиваем содержимое http://mathieuancelin.github.io/js-repaint-perfs/diffhtml/index.html (chrome сохранить страницу целиком)
2. Забрасываем в папку dbmon (diffHTML)_files https://raw.githubusercontent.com/tbranyen/diffhtml/master/dist/diffhtml.js
3. Патчим до https://gist.github.com/vird/ff236a38b6b72e23a9e87c88e38760cd
4. Запускаем по инструкции выше.
Получаем тот же fps.
morphdom, честно, было лень патчить и проверять.
Замечания к методике тестирования и результатам принимаются.
P.s. Есть еще todomvc benchmark. Кто бросит ссылку на большую сборную солянку 20+, тому отдельное спасибо. Ато на http://todomvc.com/ только примеры.
Алгоритм 1.
1. Движем окно вдоль какого-то направления (X или Y)
2. На каждую итерацию добавляем 5 пикселей и убираем 5 пикселей.
3. Те 5 пикселей, которые добавляем, сортируем сразу и храним все 5 итераций сортированными (3 операции для опеределения предварительных min, max, avg, 3 операции для определения самого минимального, 3 операции для определения самого максимального, 3 операции дабы отсортировать оставшиеся 3 элемента. Итого 12 операций самым наивным методом)
4. Дальше у нас есть массив из 25 элементов, которые частично отсортированы. Утверждение 1. Из всех минимумов только самый большой может быть средним. (аналогичное утверждение для максимума). Используя две операции min и две операции max находим нужные элементы. Эту и все дальнейшие операции выполняем в другом буфере.
5. Необходимо найти среднее из 5*3+2 элементов, о которых мы кое-что уже знаем т.к. 5 троек у нас отсортированы. Расположим эти элементы следующим способом сначала самый минимум(из шага 4) потом все минимумы из троек, потом средние из троек, потом максимумы из троек, потом самый максимум(из шага 4). Дальше подбираем необходимое количество итераций следующего алгоритма:
Проходимся по тройкам чисел с пересечением в 1 число. Ддля каждой тройки осуществляем сортировку этих трёх элементов. После такого прохода справа будет самый большой элемент. Его мы теперь можем гарантированно игнорировать. Проходимся влево. Слева будет самый минимальный элемент, его тоже можно игнорировать. На краях можно сэкономить одну операцию т.к. нам не надо записывать.
Первая итерация будет проходить на буфере размером N=5*3+2. Количество операций сравнения 3 на 3 числа таких троек будет (N-1)/2 = 8. Т.е. 8*3 операции. 2 прохода -> 8*3*2. Минус на краях = 8*3*2-2 = 46 операций.
Вторая итерация будет проходить на массиве на 2 элемента меньше. Сортируем до конца. Получаем точный ответ. Один из самых наивных подходов, но точный. Можно пробовать другой подход к сортировке.
Алгоритм 2.
Первые 4 операции идентичны алгоритму 1
5. Введём операцию сортировки троек. Мы будем сортировать центральный элемент и элементы, которые отстоят от него на +i -i. Применяя последовательно такую операцию мы добьемся момента, когда слева будут элементы меньше центрального, а справа будут элементы больше от центрального. Количество итераций для хорошей точности вычисляется либо аналитически, либо перебором.
Алгоритм 3.
Первые 2 операции идентичны алгоритму
3. Записываем только медианы каждой пятерки (т.е. по факту используем median_5, которая работает за меньше чем 12 операций)
4. Просто находим медиану из 5 медиан.
Данный алгоритм самый быстрый, но дает хуже всего точность и его нельзя принципиально сделать полностью точным.
Каждый блок в blockchain'е служит следующим целям:
Безопасность blockchain'а строится на том, что злоумышленник не сможет заполучить достаточно мощности сети, чтобы в короткий промежуток времени подписать транзакцию, которая нарушает правила амбарной книги (например, начислить себе 1М BTC). А если транзакция не будет подписана участниками сети, то её содержимое не имеет силы.
Безопасность транзакции строится на том, что у тебя есть закрытый ключ, которым ты подписываешь приказ перечислить другому кошельку столько-то денег. Адрес биткоин кошелька является открытым ключем. Итого все могут увидеть, что ты действительно владеешь закрытым ключем, но никто не может от твоего имени передавать деньги.
Упрощённо как-то так.
median_5
Сортируем первые 3 элемента. 3 операции
Сортируем последние 3 элемента 2 операции, т.к. max нам не нужен т.к. это глобальный максимум.
Снова сортируем первые 3 элемента. 2 операции т.к. min нам не нужен т.к. это глобальный минимум
Смотрим на 3 центральных элемента и выполняем median3. 1 операция
3+2+2+1 = 8 операций.
Возможно, можно за меньшее количество операций.
Для попарных сравнений операций понадобится явно больше. Плюс я уже писал, что можно захватывать по 4 числа и сортировать их параллельно.
Рассмотрим выполнение kernel'а
1. Скопировать с медленной CPU памяти в быструю GPU.
2. Выполнить на быстрой GPU памяти.
3. Скопировать обратно в медленную CPU память.
Итак. У нас на самом-то деле bottleneck по пропускной способности никуда не делся. Мы не можем выполнять быстрее, чем скорость передачи CPU памяти. А вот задержка стала меньше.
1. Посмотрите бенчмарки в играх. Поймите, что что-то не так.
2. Загляните в документацию AMD и посмотрите как правильно вынимать нужно с памяти данные, чтобы нормально использовать 4096битную шину.
2.1. Bank conflict'ы там по-жестче чем у nvidia (т.к. их бывает два типа и даже банально по битам получается что нужно разбрасывать массив в памяти сильнее, что для 1920*1080 нереально)
2.2. Вынимать нужно порциями по 16 байт (насколько я помню) (т.е. int4, или char16) это нужно переписать достаточно сильно многие алгоритмы, а если нет — мы не используем мощность HBM на полную)
Ждем HBM2 или Vega, может как-то сгладят недостатки.
По умолчанию всё идет на быструю память видеокарты. Это нужно специально включать pinned memory, если нужен массив памяти больше, чем есть на видеокарте.
amd_median5 и median7 нету по понятной причине. Каждая операция работает с максимум 4 аргументами. amd_median3+amd_min3+amd_max3 достаточно, чтобы за 3 операции отсортировать 3 набора по 4 числа (т.к. int4).
По поводу производительности, посчитайте на досуге производительность AMD/$ и nvidia/$, в чистых tflops/$. Я за те же деньги поставлю больше мощности. В моем случае это 100% можно сделать т.к. у меня просто большой поток однотипной работы, которая не зависит друг от друга. (Прим. Да, это не всем подойдет)
У AMD есть специальная инструкция для этого
https://www.khronos.org/registry/cl/extensions/amd/cl_amd_media_ops2.txt
amd_median3
Если не хватит, есть еще amd_min3, amd_max3.
В opencl есть char4. Это векторный тип данных который позволяет параллельно выполнять несколько операций. https://www.khronos.org/files/opencl-1-2-quick-reference-card.pdf судя по этому можно выполнять min(char4, char4), потому не надо помнить о вендорно-зависимой __vminu4.
Про bank conflict'ы ни слова. Хотя в принципе я тоже их игнорирую.
Если уже используется локальный буфер а для предзагрузки, то можно увеличить его в 2 раза и закачивать полностью только на первом такте, а все остальные докачивать только новую линию. Ну и тогда нужен индекс для того, чтобы ходить по такому буферу. На 5*5 это вообще жёстко.
Общие пожелания
Три цикла фиксированного размера. Ну тут просится либо pragma unroll, либо написать свой небольшой кодогенератор, который сгенерирует развёрнутый цикл с подставленными индексами.
if (idy < H — 1)
не рекомендуется использовать в принципе либо использовать тернарные операции, либо
select (который лично мне не нравится за необходимость жёстко задавать определённые типы).
idy * W + idx лишнее умножение
Правильно делать dst_offset = idx
И потом dst_offset += W каждую итерацию.
То же про 3 * z2 + z1 и другие места
Если умножение сильно нужно, но там гарантированно не используется все 32 бита, то есть mul24 и mad24 специально для работы с индексами. Работают в 4 раза быстрее чем честное 32-битное умножение.
Вместо if (idx < W — 1) {} лучше писать if (idx >= W — 1) return; Как минимум код чище, минус одна лесенка.
Дополнительная оптимизация. Убираем const int H, const int W, т.к. скорее всего у нас будет фиксированный размер картинки (не всегда так бывает) и вписываем как константы прямо в код kernel'а получаем еще +5% производительности.
1. Длина слова от 3 до 30
2. ASCII Код буквы на позиции от 0 до 20 (' преобразовывал в `)
3. Количество вхождений буквы a-z`
Для каждой фичи для 8M слов подсчитан результат и закэширован.
В начале есть дерево из 1 узла. Точность решения 50%
Дальше перебираются все возможные варианты как можно поделить этот узел на N узлов при помощи какой-то фичи.
Выбирается фича и узел которые имеют больше всего сумму abs(pos-neg) на всех узлах, которые образуются после разделения узла на много узлов.
А теперь всё тоже самое, только 8000 раз.
Мне очень интересно какую методологию тестирования будут использовать. Насколько я понял минимальным гарантированным квантом является один блок по 100 слов.
Судя по комментариям участники не брезговали использовать читерскими методами подкручивания статистик.
Например накопление словаря или статистик аргументов вызова функции.
Т.к. цитирование переписки с организаторами здесь поощряется, то
Я ничего не берусь говорить, но читая предыдущие отзывы о конкурсах от hola, я специально очень сильно расспрашивал про методику проведения и оценки. Я пока сделаю гипотезу, что скрипт, который будет проверять решения будет сбрасывать состояние решения каждого участника каждые 1000 блоков, к примеру. Потому все решения, которые базируются на запоминании потока вызовов внезапно превратятся в тыкву. У меня в кармане тоже были читерские методы подкрутки решения, например корректировка до априорной вероятности, но я не рискнул их включать в решение из-за боязни изменения условий тестирования в последний момент. Ждем комментариев от организаторов.
2. Это набросок, который потом ушёл под тюнинг на видеокарту, и там уже были совсем другие коэфициенты.
Перепосчитал
solution.js
test 0-200k 61.98225%
crossvalidation 200k-400k 61.9829%