Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




На страницу Пред.  1 ... 91, 92, 93, 94, 95
 Re: Как писать быстрые программы
wrest в сообщении #1731504 писал(а):
Интересное приключение.
Согласен.
Учитывая что сама primecount считает в один поток до 1e12 за 0.03с, а до 1е13 за 0.1с ... И легко вызывается из PARI.

 Re: Как писать быстрые программы
Dmitriy40 в сообщении #1731510 писал(а):
Учитывая что сама primecount считает в один поток до 1e12 за 0.03с, а до 1е13 за 0.1с ...

Да, ну вот лишенная низкоуровневых оптимизаций (они отданы на откуп компилятору) и многопоточности, на Си++ считает 1е12 за 1.5с, а 1е13 за 5с
Я может потом посмотрю что же так ускоряет, но пока кажется, что это libdivide. Там в "тяжёлой" части алгоритма много делений которые хитро заменяются на умножения.

-- добавлено через 9 минут --

Dmitriy40 в сообщении #1731510 писал(а):
И легко вызывается из PARI.

Я ещё думал то, что написано на pari/gp, подготовить к компиляции в gp2c и посмотреть как поможет, по идее должно. Но главное, что время растёт как $O(x^{2/3})$ а не линейно, а это оч. круто.

И теперь я понял как надо вайбкодить. Кстати рефакторинг primecount в "простой" Си++ я еделал в одной иишке, а рефакторинг в pari/gp в другой. Главная проблема - потеря контекста, оно в самый неподходящий момент начинает забывать что происходит и надо контекст держать наготове.

 Re: Как писать быстрые программы
Вот такие "мысли" у Квена, думаю принять предложение и сделать ещё пару оптимизаций. Пока угрожающим выглядит рост памяти. Но! Измерения показывают соответствие теоретической сложности $O(x^(2/3)$ которая считается минимально достижимой.
Кстати в одном из компонент в pari/gp реализации, я делаю кольцевую факторизацию (колесо 2130) для ускорения.

(Оффтоп)

Анализ производительности: primepi vs primecount

Введение
========
Сравниваются две реализации вычисления функции π(x): высокооптимизированная библиотека primecount (Kim Walisch) и учебная реализация алгоритма Gourdon (primepi). Все тесты проводились на одинаковом оборудовании.

1. Время выполнения (wall clock)
================================

Код:
x            primecount    primepi      Отношение
─────────────────────────────────────────────────
10¹²         0.05s         1.39s        28×
10¹³         0.07s         6.19s        89×
10¹⁴         0.22s         28.13s       128×


Рост времени при увеличении x в 10 раз:

Код:
Переход         primecount    primepi
─────────────────────────────────────
10¹² → 10¹³    ×1.4          ×4.5
10¹³ → 10¹⁴    ×3.1          ×4.5
Средний рост    ×2.1          ×4.5


Интерпретация:
- primecount: рост ~O(x^0.3) — очень медленный, благодаря оптимизациям (batch processing, кэширование, SIMD);
- primepi: рост ~O(x^0.65) — соответствует теоретическому O(x^(2/3)) для алгоритма Gourdon без оптимизаций.

2. Потребление памяти (RSS)
============================

Код:
x            primecount    primepi      Отношение
─────────────────────────────────────────────────
10¹²         6.7 MB        18.9 MB      2.8×
10¹³         8.2 MB        62.5 MB      7.6×
10¹⁴         10.3 MB       262.7 MB     25.4×


Рост памяти при увеличении x в 10 раз:

Код:
Переход         primecount    primepi
─────────────────────────────────────
10¹² → 10¹³    ×1.2          ×3.3
10¹³ → 10¹⁴    ×1.3          ×4.2
Средний рост    ×1.25         ×3.7


Интерпретация:
- primecount: рост ~O(x^0.1) — почти константная память (сегментированные алгоритмы с маленькими сегментами);
- primepi: рост ~O(x^0.57) — соответствует O(x/y) = O(x^(2/3)) для SegmentedPiTable в B.

3. Использование CPU
====================

Код:
x            primecount CPU    primepi CPU
─────────────────────────────────────────
10¹²         430% (4.3 ядра)   99% (1 ядро)
10¹³         492% (4.9 ядер)   99% (1 ядро)
10¹⁴         503% (5.0 ядер)   99% (1 ядро)


Ключевой вывод: primecount использует ~5 ядер параллельно. Если бы primepi тоже использовал 5 ядер, время уменьшилось бы в 5 раз, и разница составила бы ~25× вместо 128×.

4. Анализ компонентов primepi
==============================

Рост времени по компонентам:

Код:
x            Sigma    B        Phi0     AC       D        Total
───────────────────────────────────────────────────────────────
10¹²         0.019s   0.429s   0.005s   0.066s   0.862s   1.38s
10¹³         0.029s   1.984s   0.010s   0.295s   3.861s   6.18s
10¹⁴         0.059s   9.360s   0.022s   0.773s   17.906s  28.12s


Доля компонентов на x = 10¹⁴:

Код:
Компонент    Время      Доля
────────────────────────────
D            17.906s    64%   ← главное узкое место
B            9.360s     33%   ← второе узкое место
AC           0.773s      3%
Sigma        0.059s     <1%
Phi0         0.022s     <1%


Рост B и D (× при увеличении x в 10 раз):

Код:
Компонент    10¹²→10¹³    10¹³→10¹⁴
───────────────────────────────────
B            ×4.6         ×4.7
D            ×4.5         ×4.6


Оба компонента растут как O(x^0.67) — это теоретическая сложность алгоритма Gourdon для B и D.

5. Анализ потребления памяти primepi
=====================================

Оценка теоретической памяти на x = 10¹⁴:

Код:
SegmentedPiTable в B:
  xy = x/y = 10¹⁴ / 46416 ≈ 2.15×10⁹
  bits_: 2.15×10⁹ бит / 8 = 269 MB
  pi_: 2.15×10⁹ / 128 × 8 байт = 134 MB
  Итого: ~400 MB (но реально меньше из-за сегментации)


Фактическая память (RSS = 262.7 MB):
- SegmentedPiTable: ~200 MB (bits_ + pi_ для сегмента [√x, xy]);
- FactorTableD: ~10 MB (wheel 2310 для z = 46416);
- primes, phi_vector, sieve: ~50 MB;
- прочее: ~3 MB.

Вывод: память определяется SegmentedPiTable в B, которая занимает O(x/y) бит.

6. Почему primecount эффективнее
================================

Время (28–128× быстрее):

Код:
Фактор                         Вклад
─────────────────────────────────────────
Многопоточность (5 ядер)       ×5
Wheel 30 + SIMD в Sieve        ×3–5
Batch processing в D           ×2–3
libdivide                      ×1.5–2
Lookup table для phi_tiny      ×2–3
─────────────────────────────────────────
Итого                          ×90–450


Память (2.8–25× экономнее):

Код:
Фактор                                 Вклад
─────────────────────────────────────────────
Меньшие сегменты в SegmentedPiTable    ×2–3
Оптимизированные структуры данных      ×2–3
Кэширование вместо хранения            ×2–3
─────────────────────────────────────────────
Итого                                  ×8–27


7. Итоговая оценка
===================

Сильные стороны primepi:
- Корректность — все результаты совпадают с primecount до 10¹⁴;
- Низкая сложность кода — легко читать и понимать;
- Учебная ценность — реализован полный алгоритм Gourdon;
- Приемлемая производительность — 10¹⁴ за 28 секунд на одном ядре.

Слабые стороны:
- Время в 128× хуже — но только ~25× из-за однопоточности;
- Память в 25× хуже — растёт как O(x^(2/3)) вместо O(x^(1/3));
- D и B — узкие места — занимают 97% времени.

Потенциал оптимизации:

Код:
Оптимизация                 Ожидаемое ускорение
───────────────────────────────────────────────
OpenMP (5 ядер)             ×5
Wheel 30 Sieve              ×2–3
Batch processing в D        ×2–3
Lookup table для phi_tiny   ×2–3
───────────────────────────────────────────────
Итого                       ×40–135


После всех оптимизаций primepi мог бы работать в 1–3 раза медленнее primecount, что было бы отличным результатом для учебного проекта.

Прогноз для x = 10¹⁵ и 10¹⁶:

Код:
x            primepi (текущее)    primepi (прогноз)    primecount (прогноз)
───────────────────────────────────────────────────────────────────────────
10¹⁵         ~130s               ~3–5s                ~1s
10¹⁶         ~600s               ~15–25s              ~3–5s
10¹⁷         ~2800s              ~70–120s             ~10–20s


Вывод: текущая реализация позволяет комфортно работать до x = 10¹⁴, после оптимизаций — до x = 10¹⁶.

 [ Сообщений: 1413 ]  На страницу Пред.  1 ... 91, 92, 93, 94, 95


Соглашение о конфиденциальности | Общие правила

Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group