Вот более краткая формулировка того же алгоритма, которая должна сделать принцип более понятным (часть full, runnable .cpp for segment size timings @ pastebin). Это инициализирует упакованное (только для шансов) сито вместо подсчета простых чисел, но задействованные принципы одинаковы. Загрузите и запустите .cpp, чтобы увидеть влияние размеров сегмента. В принципе, оптимальный размер должен быть вокруг размера кеша L1 для вашего процессора. Слишком мало, и накладные расходы из-за увеличения количества раундов начинают доминировать; слишком большой, и вы будете наказаны медленными таймингами кэшей L2 и L3. См. Также How does segmentation improve the running time of Sieve of Eratosthenes?.
void initialise_packed_sieve_4G (void *data, unsigned segment_bytes = 1 << 15, unsigned end_bit = 1u << 31)
{
typedef std::vector<prime_and_offset_t>::iterator prime_iter_t;
std::vector<prime_and_offset_t> small_factors;
initialise_odd_primes_and_offsets_64K(small_factors);
unsigned segment_bits = segment_bytes * CHAR_BIT;
unsigned partial_bits = end_bit % segment_bits;
unsigned segments = end_bit/segment_bits + (partial_bits != 0);
unsigned char *segment = static_cast<unsigned char *>(data);
unsigned bytes = segment_bytes;
for (; segments--; segment += segment_bytes)
{
if (segments == 0 && partial_bits)
{
segment_bits = partial_bits;
bytes = (partial_bits + CHAR_BIT - 1)/CHAR_BIT;
}
std::memset(segment, 0, bytes);
for (prime_iter_t p = small_factors.begin(); p != small_factors.end(); ++p)
{
unsigned n = p->prime;
unsigned i = p->next_offset;
for (; i < segment_bits; i += n)
{
set_bit(segment, i);
}
p->next_offset = i - segment_bits;
}
}
}
Если смещения не запомнились от сегмента к сегменту, то они должны быть пересчитаны каждый раз, используя, по меньшей мере, одно деление и умножение один за пересчитывается индекса, а также условными или серьезный битную фокусы. При просеивании полного диапазона номеров 2^32 (8192 сегмента по 32 Кбайт каждый), это не менее 53 583 872 медленных делений и столько же несколько более быстрых умножений; это примерно одна секунда, добавленная к времени, необходимому для инициализации полного сита 2^32 (2^31 бит для только для коэффициентов Eratosthenes).
Вот некоторый реальный код от одного из моих старых сит, который использует эту «преобразовывающую» математику:
for (index_t k = 1; k <= max_factor_bit; ++k)
{
if (bitmap_t::traits::bt(bm.bm, k)) continue;
index_t n = (k << 1) + 1; // == index_for_value(value_for_index(k) * 2) == n
index_t i = square(n) >> 1; // == index_for_value(square(n))
if (i < offset)
{
i += ((offset - i)/n) * n;
}
for (; i <= new_max_bit; i += n)
{
bitmap_t::traits::bts(bm.bm, i);
}
}
Это занимает около 5,5 секунд для полного ситя (VC++); сначала показан код, который занимает всего 4,5 секунды с тем же компилятором или 3,5 секунды с использованием gcc 4.8.1 (MinGW64).
Вот GCC тайминги:
sieve bits = 2147483648 (equiv. number = 4294967295)
segment size 4096 (2^12) bytes ... 4.091 s 1001.2 M/s
segment size 8192 (2^13) bytes ... 3.723 s 1100.2 M/s
segment size 16384 (2^14) bytes ... 3.534 s 1159.0 M/s
segment size 32768 (2^15) bytes ... 3.418 s 1198.4 M/s
segment size 65536 (2^16) bytes ... 3.894 s 1051.9 M/s
segment size 131072 (2^17) bytes ... 4.265 s 960.4 M/s
segment size 262144 (2^18) bytes ... 4.453 s 919.8 M/s
segment size 524288 (2^19) bytes ... 5.002 s 818.9 M/s
segment size 1048576 (2^20) bytes ... 5.176 s 791.3 M/s
segment size 2097152 (2^21) bytes ... 5.135 s 797.7 M/s
segment size 4194304 (2^22) bytes ... 5.251 s 780.0 M/s
segment size 8388608 (2^23) bytes ... 7.412 s 552.6 M/s
digest { 203280221, 0C903F86, 5B253F12, 774A3204 }
Примечания: дополнительная второй может быть выбрито с этого времени с помощью трюка под названием «presieving», т.е. подрывного предварительно вычисленный шаблон в битовые карты вместо обнуления его в начале. Это приводит к отключению gcc до 2.1 с для полного сита с this hacked copy of the earlier .cpp. Этот трюк работает очень хорошо вместе с сегментированным просеиванием в кусках размером с кеш.
Зарезервируйте ваши векторы, которые вы push_back. –