photonНу вот, за 8ч рано утром досчиталось от
до
. Ничего не найдено. Значит до
решение всего одно. Дальше считать не вижу смысла (лень программу переписывать, да и медленно уже).
Удивительно.
(Оптимизации)
kotenok gavИх несколько.
- Вместо подсчёта квадрата произведения цифр для каждого числа предварительно просчитана табличка для чисел
. Забавно что все возможные квадраты произведений цифр вмещаются в uint32 (т.е.
). Именно поэтому выбрано деление на такие группы цифр.
- Соответственно всё число делится на группы по 5 цифр и для них таблично определяется квадрат произведения цифр, перемножив которые получаем результат.
- Как только этот результат превышает величину
или проверяемое число - переходим к следующему числу, бывает даже не заходя во внутренний цикл.
- Таблица отсортирована по возрастанию квадрата произведений цифр, вместо цикла перебора
в каждой группе сделан лишь проход по таблице для чисел
(с обрывом цикла по превышению), заодно исключены и все "лишние" числа (с нулями или единицами в записи), из ста тысяч осталось лишь ровно
.
- Парочка мелких оптимизаций на ассемблере во внутреннем цикле, оставлено минимально необходимое количество операций: одно умножение 64х64 и одно деление 64:64, остальное вынесено наружу. Плюс все условные переходы оптимизированы чтобы в большинстве случаев не выполнялись, кроме единственного перехода организации цикла. Плюс точка начала цикла выравнена на 16 байт. Плюс размер таблицы урезан до минимального чтобы вмещалось в кэш. Плюс таблица организована для линейного прохода. Плюс все операции в регистрах, без обращений в память, кроме одной таблицы. Но это уже мелочи, по сравнению с операцией деления на почти сотню тактов. Подумывал разделить одну операцию деления 64:64 на две 64:32 (это и само по себе быстрее, и вторую очень часто можно не выполнять), но лень значительно переписывать программу ради выигрыша 3-4-х часов счёта.
Ну пожалуй и всё, как видите ничего сложного, всё самые обычные техники оптимизации программ.
Наибольший эффект дают разумеется четыре вещи: уменьшение операций деления (ну и умножения) во внутреннем цикле; замена вычислений на таблицу в кэше; сортировка таблицы по возрастанию; своевременный обрыв перебора.