Благодаря некоторым очень полезным пользователям stackOverflow в Бит-twiddling: какой бит установлен?, я построил свою функцию (размещенную в конце вопроса).
Любые предложения - даже небольшие предложения - будут оценены. Надеюсь, это сделает мой код лучше, но, по крайней мере, он должен научить меня чему-то.:)
Обзор
Эта функция будет называться не менее 10 13 раз и, возможно, так же часто, как 10 15. То есть, этот код будет работать в течение нескольких месяцев, по всей вероятности, поэтому любые рекомендации по производительности будут полезны.
Эта функция составляет 72-77% от времени программы на основе профилирования и около дюжины прогонов в разных конфигурациях (оптимизация некоторых параметров, которые здесь не актуальны).
В данный момент функция работает в среднем 50 часов. Я не уверен, насколько это можно улучшить, но я был бы в восторге от того, что он будет работать в 30 лет.
Наблюдение за ключами
Если в какой-то момент расчета вы можете сказать, что возвращаемое значение будет небольшим (точное значение оборотное - скажем, менее миллиона), вы можете прервать раньше. Меня интересуют только большие значения.
Вот как я надеюсь сэкономить больше времени, а не дальнейшие микро-оптимизации (хотя они, конечно же, тоже приветствуются!).
Информация о производительности
- smallprimes - бит-бит (64 бит); в среднем будет установлено около 8 бит, но это может быть всего лишь 0 или целых 12.
- q обычно будет отличным от нуля. (Обратите внимание, что функция выходит раньше, если q и smallprimes равны нулю.)
- r и s часто будут равны 0. Если q равно нулю, r и s тоже будут; если r равно нулю, s тоже будет.
- Как говорится в конце комментария, nu обычно 1 к концу, поэтому у меня есть эффективный частный случай для него.
- Вычисления, приведенные ниже специального случая, могут показаться переполненными рисками, но с помощью соответствующего моделирования я доказал, что для моего ввода этого не произойдет - так что не беспокойтесь об этом случае.
- Функции, не определенные здесь (ugcd, minuu, star и т.д.) уже оптимизированы; никто не займет много времени. pr - малый массив (все в L1). Кроме того, все функции, называемые здесь, чистые функции.
- Но если вам действительно все равно... ugcd - это gcd, minuu - это минимум, vals - это число конечных двоичных 0, __builtin_ffs - это расположение самого левого двоичного кода 1, звезда (n-1) → vals (n-1), pr - массив простых чисел от 2 до 313.
- В настоящее время расчеты выполняются на Phenom II 920 x4, хотя оптимизация для i7 или Woodcrest по-прежнему представляет интерес (если я получаю время вычисления на других узлах).
- Я был бы рад ответить на любые ваши вопросы о функции или ее составляющих.
Что он на самом деле делает
Добавлен в ответ на запрос. Вам не нужно читать эту часть.
Ввод представляет собой нечетное число n с 1 < n < 4282250400097. Другие входы обеспечивают факторизацию числа в этом конкретном смысле:
smallprimes & 1 устанавливается, если число делится на 3, smallprimes & 2 устанавливается, если число делится на 5, smallprimes & 4 устанавливается, если число делится на 7, smallprimes & 8 задано, если число делится на 11 и т.д. до самого значащего бита, который представляет 313. Число, делящееся на квадрат простого числа, не представлено иначе, чем число, делящееся именно этим числом. (На самом деле, кратные квадраты могут быть отброшены; на этапе предварительной обработки в другой функции кратные квадраты простых чисел <= lim имеют небольшие числа, а q - 0, поэтому они будут отброшены, где оптимальное значение lim определяется экспериментами.)
q, r и s представляют собой более крупные коэффициенты числа. Любой оставшийся множитель (который может быть больше квадратного корня из числа, или если s отличен от нуля, может быть даже меньше) можно найти, разделив коэффициенты из n.
Как только все факторы восстанавливаются таким образом, количество оснований, 1 <= b < n, к которому n является сильным псевдоприважем, подсчитывается с использованием математической формулы, лучше всего объясняемой кодом.
Улучшения до сих пор
- Продвинутый тест на выход. Это явно экономит работу, поэтому я внес изменения.
- Соответствующие функции уже встроены, поэтому
__attribute__ ((inline))
ничего не делает. Как ни странно, отмечая основную функциюbases
, а некоторые из помощников с__attribute ((hot))
ухудшают производительность почти на 2%, и я не могу понять, почему (но он воспроизводится с более чем 20 тестами). Поэтому я не сделал этого изменения. Точно так же__attribute__ ((const))
, в лучшем случае, не помогло. Я был несколько удивлен этим.
код
ulong bases(ulong smallprimes, ulong n, ulong q, ulong r, ulong s)
{
if (!smallprimes & !q)
return 0;
ulong f = __builtin_popcountll(smallprimes) + (q > 1) + (r > 1) + (s > 1);
ulong nu = 0xFFFF; // "Infinity" for the purpose of minimum
ulong nn = star(n);
ulong prod = 1;
while (smallprimes) {
ulong bit = smallprimes & (-smallprimes);
ulong p = pr[__builtin_ffsll(bit)];
nu = minuu(nu, vals(p - 1));
prod *= ugcd(nn, star(p));
n /= p;
while (n % p == 0)
n /= p;
smallprimes ^= bit;
}
if (q) {
nu = minuu(nu, vals(q - 1));
prod *= ugcd(nn, star(q));
n /= q;
while (n % q == 0)
n /= q;
} else {
goto BASES_END;
}
if (r) {
nu = minuu(nu, vals(r - 1));
prod *= ugcd(nn, star(r));
n /= r;
while (n % r == 0)
n /= r;
} else {
goto BASES_END;
}
if (s) {
nu = minuu(nu, vals(s - 1));
prod *= ugcd(nn, star(s));
n /= s;
while (n % s == 0)
n /= s;
}
BASES_END:
if (n > 1) {
nu = minuu(nu, vals(n - 1));
prod *= ugcd(nn, star(n));
f++;
}
// This happens ~88% of the time in my tests, so special-case it.
if (nu == 1)
return prod << 1;
ulong tmp = f * nu;
long fac = 1 << tmp;
fac = (fac - 1) / ((1 << f) - 1) + 1;
return fac * prod;
}